blob: 45e8701aa444f3e2b5cbf928b9d3e0d2adf98278 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.geometry.euclidean.twod;
import org.apache.commons.geometry.core.Geometry;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.exception.GeometryValueException;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
import org.apache.commons.geometry.euclidean.oned.Interval;
import org.apache.commons.geometry.euclidean.oned.Vector1D;
import org.apache.commons.geometry.euclidean.twod.Line.SubspaceTransform;
import org.apache.commons.numbers.angle.PlaneAngleRadians;
import org.junit.Assert;
import org.junit.Test;
public class LineTest {
private static final double TEST_EPS = 1e-10;
private static final DoublePrecisionContext TEST_PRECISION =
new EpsilonDoublePrecisionContext(TEST_EPS);
@Test
public void testFromPoints() {
// act/assert
checkLine(Line.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION),
Vector2D.ZERO, Vector2D.Unit.PLUS_X);
checkLine(Line.fromPoints(Vector2D.ZERO, Vector2D.of(100, 0), TEST_PRECISION),
Vector2D.ZERO, Vector2D.Unit.PLUS_X);
checkLine(Line.fromPoints(Vector2D.of(100, 0), Vector2D.ZERO, TEST_PRECISION),
Vector2D.ZERO, Vector2D.Unit.MINUS_X);
checkLine(Line.fromPoints(Vector2D.of(-100, 0), Vector2D.of(100, 0), TEST_PRECISION),
Vector2D.ZERO, Vector2D.Unit.PLUS_X);
checkLine(Line.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 2), TEST_PRECISION),
Vector2D.of(-1, 1), Vector2D.of(1, 1).normalize());
checkLine(Line.fromPoints(Vector2D.of(0, 2), Vector2D.of(-2, 0), TEST_PRECISION),
Vector2D.of(-1, 1), Vector2D.of(-1, -1).normalize());
}
@Test
public void testFromPoints_pointsTooClose() {
// act/assert
GeometryTestUtils.assertThrows(() -> Line.fromPoints(Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_X, TEST_PRECISION),
GeometryValueException.class, "Line direction cannot be zero");
GeometryTestUtils.assertThrows(() -> Line.fromPoints(Vector2D.Unit.PLUS_X, Vector2D.of(1 + 1e-11, 1e-11), TEST_PRECISION),
GeometryValueException.class, "Line direction cannot be zero");
}
@Test
public void testFromPointAndDirection() {
// act/assert
checkLine(Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION),
Vector2D.ZERO, Vector2D.Unit.PLUS_X);
checkLine(Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.of(100, 0), TEST_PRECISION),
Vector2D.ZERO, Vector2D.Unit.PLUS_X);
checkLine(Line.fromPointAndDirection(Vector2D.of(-100, 0), Vector2D.of(100, 0), TEST_PRECISION),
Vector2D.ZERO, Vector2D.Unit.PLUS_X);
checkLine(Line.fromPointAndDirection(Vector2D.of(-2, 0), Vector2D.of(1, 1), TEST_PRECISION),
Vector2D.of(-1, 1), Vector2D.of(1, 1).normalize());
checkLine(Line.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(-1, -1), TEST_PRECISION),
Vector2D.of(-1, 1), Vector2D.of(-1, -1).normalize());
}
@Test
public void testFromPointAndDirection_directionIsZero() {
// act/assert
GeometryTestUtils.assertThrows(() -> Line.fromPointAndDirection(Vector2D.Unit.PLUS_X, Vector2D.ZERO, TEST_PRECISION),
GeometryValueException.class, "Line direction cannot be zero");
GeometryTestUtils.assertThrows(() -> Line.fromPointAndDirection(Vector2D.Unit.PLUS_X, Vector2D.of(1e-11, -1e-12), TEST_PRECISION),
GeometryValueException.class, "Line direction cannot be zero");
}
@Test
public void testFromPointAndAngle() {
// act/assert
checkLine(Line.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION),
Vector2D.ZERO, Vector2D.Unit.PLUS_X);
checkLine(Line.fromPointAndAngle(Vector2D.of(1, 1), Geometry.HALF_PI, TEST_PRECISION),
Vector2D.of(1, 0), Vector2D.Unit.PLUS_Y);
checkLine(Line.fromPointAndAngle(Vector2D.of(-1, -1), Geometry.PI, TEST_PRECISION),
Vector2D.of(0, -1), Vector2D.Unit.MINUS_X);
checkLine(Line.fromPointAndAngle(Vector2D.of(1, -1), Geometry.MINUS_HALF_PI, TEST_PRECISION),
Vector2D.of(1, 0), Vector2D.Unit.MINUS_Y);
checkLine(Line.fromPointAndAngle(Vector2D.of(-1, 1), Geometry.TWO_PI, TEST_PRECISION),
Vector2D.of(0, 1), Vector2D.Unit.PLUS_X);
}
@Test
public void testGetAngle() {
// arrange
Vector2D vec = Vector2D.of(1, 2);
for (double theta = -4 * Geometry.PI; theta < 2 * Geometry.PI; theta += 0.1) {
Line line = Line.fromPointAndAngle(vec, theta, TEST_PRECISION);
// act/assert
Assert.assertEquals(PlaneAngleRadians.normalizeBetweenZeroAndTwoPi(theta),
line.getAngle(), TEST_EPS);
}
}
@Test
public void testGetAngle_multiplesOfPi() {
// arrange
Vector2D vec = Vector2D.of(-1, -2);
// act/assert
Assert.assertEquals(0, Line.fromPointAndAngle(vec, Geometry.ZERO_PI, TEST_PRECISION).getAngle(), TEST_EPS);
Assert.assertEquals(Geometry.PI, Line.fromPointAndAngle(vec, Geometry.PI, TEST_PRECISION).getAngle(), TEST_EPS);
Assert.assertEquals(0, Line.fromPointAndAngle(vec, Geometry.TWO_PI, TEST_PRECISION).getAngle(), TEST_EPS);
Assert.assertEquals(0, Line.fromPointAndAngle(vec, -2 * Geometry.PI, TEST_PRECISION).getAngle(), TEST_EPS);
Assert.assertEquals(Geometry.PI, Line.fromPointAndAngle(vec, -3 * Geometry.PI, TEST_PRECISION).getAngle(), TEST_EPS);
Assert.assertEquals(0, Line.fromPointAndAngle(vec, -4 * Geometry.TWO_PI, TEST_PRECISION).getAngle(), TEST_EPS);
}
@Test
public void testGetDirection() {
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X,
Line.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 0), TEST_PRECISION).getDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
Line.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, -1), TEST_PRECISION).getDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X,
Line.fromPoints(Vector2D.of(2, 2), Vector2D.of(1, 2), TEST_PRECISION).getDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X,
Line.fromPoints(Vector2D.of(10, -2), Vector2D.of(10.1, -2), TEST_PRECISION).getDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
Line.fromPoints(Vector2D.of(3, 2), Vector2D.of(3, 1), TEST_PRECISION).getDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_Y,
Line.fromPoints(Vector2D.of(-3, 10), Vector2D.of(-3, 10.1), TEST_PRECISION).getDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, -1).normalize(),
Line.fromPoints(Vector2D.of(0, 2), Vector2D.of(2, 0), TEST_PRECISION).getDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1).normalize(),
Line.fromPoints(Vector2D.of(2, 0), Vector2D.of(0, 2), TEST_PRECISION).getDirection(), TEST_EPS);
}
@Test
public void testGetOffsetDirection() {
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
Line.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 0), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X,
Line.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, -1), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_Y,
Line.fromPoints(Vector2D.of(2, 2), Vector2D.of(1, 2), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y,
Line.fromPoints(Vector2D.of(10, -2), Vector2D.of(10.1, -2), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X,
Line.fromPoints(Vector2D.of(3, 2), Vector2D.of(3, 1), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X,
Line.fromPoints(Vector2D.of(-3, 10), Vector2D.of(-3, 10.1), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, -1).normalize(),
Line.fromPoints(Vector2D.of(0, 2), Vector2D.of(2, 0), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1).normalize(),
Line.fromPoints(Vector2D.of(2, 0), Vector2D.of(0, 2), TEST_PRECISION).getOffsetDirection(), TEST_EPS);
}
@Test
public void testGetOrigin() {
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO,
Line.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 0), TEST_PRECISION).getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO,
Line.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, -1), TEST_PRECISION).getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2),
Line.fromPoints(Vector2D.of(2, 2), Vector2D.of(3, 2), TEST_PRECISION).getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -2),
Line.fromPoints(Vector2D.of(10, -2), Vector2D.of(10.1, -2), TEST_PRECISION).getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 0),
Line.fromPoints(Vector2D.of(3, 2), Vector2D.of(3, 1), TEST_PRECISION).getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 0),
Line.fromPoints(Vector2D.of(-3, 10), Vector2D.of(-3, 10.1), TEST_PRECISION).getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1),
Line.fromPoints(Vector2D.of(0, 2), Vector2D.of(2, 0), TEST_PRECISION).getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1),
Line.fromPoints(Vector2D.of(2, 0), Vector2D.of(0, 2), TEST_PRECISION).getOrigin(), TEST_EPS);
}
@Test
public void testGetOriginOffset() {
// arrange
double sqrt2 = Math.sqrt(2);
// act/assert
Assert.assertEquals(0.0,
Line.fromPoints(Vector2D.of(0, 0), Vector2D.of(1, 1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
Assert.assertEquals(0.0,
Line.fromPoints(Vector2D.of(0, 0), Vector2D.of(-1, -1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
Assert.assertEquals(sqrt2,
Line.fromPoints(Vector2D.of(-1, 1), Vector2D.of(0, 2), TEST_PRECISION).getOriginOffset(), TEST_EPS);
Assert.assertEquals(-sqrt2,
Line.fromPoints(Vector2D.of(0, -2), Vector2D.of(1, -1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
Assert.assertEquals(-sqrt2,
Line.fromPoints(Vector2D.of(0, 2), Vector2D.of(-1, 1), TEST_PRECISION).getOriginOffset(), TEST_EPS);
Assert.assertEquals(sqrt2,
Line.fromPoints(Vector2D.of(1, -1), Vector2D.of(0, -2), TEST_PRECISION).getOriginOffset(), TEST_EPS);
}
@Test
public void testGetPrecision() {
// act/assert
Assert.assertSame(TEST_PRECISION, Line.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION).getPrecision());
Assert.assertSame(TEST_PRECISION, Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION).getPrecision());
Assert.assertSame(TEST_PRECISION, Line.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION).getPrecision());
}
@Test
public void testReverse() {
// arrange
Vector2D pt = Vector2D.of(0, 1);
Vector2D dir = Vector2D.Unit.PLUS_X;
Line line = Line.fromPointAndDirection(pt, dir, TEST_PRECISION);
// act
Line reversed = line.reverse();
Line doubleReversed = reversed.reverse();
// assert
checkLine(reversed, pt, dir.negate());
Assert.assertEquals(-1, reversed.getOriginOffset(), TEST_EPS);
checkLine(doubleReversed, pt, dir);
Assert.assertEquals(1, doubleReversed.getOriginOffset(), TEST_EPS);
}
@Test
public void testAbscissa() {
// arrange
Line line = Line.fromPoints(Vector2D.of(-2, -2), Vector2D.of(2, 1), TEST_PRECISION);
// act/assert
Assert.assertEquals(0.0, line.abscissa(Vector2D.of(-3, 4)), TEST_EPS);
Assert.assertEquals(0.0, line.abscissa(Vector2D.of( 3, -4)), TEST_EPS);
Assert.assertEquals(5.0, line.abscissa(Vector2D.of(7, -1)), TEST_EPS);
Assert.assertEquals(-5.0, line.abscissa(Vector2D.of(-1, -7)), TEST_EPS);
}
@Test
public void testToSubspace() {
// arrange
Line line = Line.fromPoints(Vector2D.of(2, 1), Vector2D.of(-2, -2), TEST_PRECISION);
// act/assert
Assert.assertEquals(0.0, line.toSubspace(Vector2D.of(-3, 4)).getX(), TEST_EPS);
Assert.assertEquals(0.0, line.toSubspace(Vector2D.of( 3, -4)).getX(), TEST_EPS);
Assert.assertEquals(-5.0, line.toSubspace(Vector2D.of(7, -1)).getX(), TEST_EPS);
Assert.assertEquals(5.0, line.toSubspace(Vector2D.of(-1, -7)).getX(), TEST_EPS);
}
@Test
public void testToSpace_throughOrigin() {
// arrange
double invSqrt2 = 1 / Math.sqrt(2);
Vector2D dir = Vector2D.of(invSqrt2, invSqrt2);
Line line = Line.fromPoints(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION);
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, line.toSpace(Vector1D.of(0)), TEST_EPS);
for (int i=0; i<100; ++i) {
EuclideanTestUtils.assertCoordinatesEqual(dir.multiply(i), line.toSpace(Vector1D.of(i)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(dir.multiply(-i), line.toSpace(Vector1D.of(-i)), TEST_EPS);
}
}
@Test
public void testToSpace_offsetFromOrigin() {
// arrange
double angle = Geometry.PI / 6;
double cos = Math.cos(angle);
double sin = Math.sin(angle);
Vector2D pt = Vector2D.of(-5, 0);
double h = Math.abs(pt.getX()) * cos;
double d = h * cos;
Vector2D origin = Vector2D.of(
pt.getX() + d,
h * sin
);
Vector2D dir = Vector2D.of(cos, sin);
Line line = Line.fromPointAndAngle(pt, angle, TEST_PRECISION);
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(origin, line.toSpace(Vector1D.of(0)), TEST_EPS);
for (int i=0; i<100; ++i) {
EuclideanTestUtils.assertCoordinatesEqual(origin.add(dir.multiply(i)), line.toSpace(Vector1D.of(i)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(origin.add(dir.multiply(-i)), line.toSpace(Vector1D.of(-i)), TEST_EPS);
}
}
@Test
public void testIntersection() {
// arrange
Line a = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line b = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
Line c = Line.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
Line d = Line.fromPointAndDirection(Vector2D.of(0, -1), Vector2D.of(2, -1), TEST_PRECISION);
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, a.intersection(b), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, b.intersection(a), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 0), a.intersection(c), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 0), c.intersection(a), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), a.intersection(d), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), d.intersection(a), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), b.intersection(c), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), c.intersection(b), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -1), b.intersection(d), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -1), d.intersection(b), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 0.5), c.intersection(d), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 0.5), d.intersection(c), TEST_EPS);
}
@Test
public void testIntersection_parallel() {
// arrange
Line a = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line b = Line.fromPointAndDirection(Vector2D.of(0, 1), Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line c = Line.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
Line d = Line.fromPointAndDirection(Vector2D.of(0, -1), Vector2D.of(2, 1), TEST_PRECISION);
// act/assert
Assert.assertNull(a.intersection(b));
Assert.assertNull(b.intersection(a));
Assert.assertNull(c.intersection(d));
Assert.assertNull(d.intersection(c));
}
@Test
public void testIntersection_coincident() {
// arrange
Line a = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line b = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line c = Line.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
Line d = Line.fromPointAndDirection(Vector2D.of(0, 2), Vector2D.of(2, 1), TEST_PRECISION);
// act/assert
Assert.assertNull(a.intersection(b));
Assert.assertNull(b.intersection(a));
Assert.assertNull(c.intersection(d));
Assert.assertNull(d.intersection(c));
}
@Test
public void testAngle() {
// arrange
Line a = Line.fromPointAndAngle(Vector2D.ZERO, Geometry.ZERO_PI, TEST_PRECISION);
Line b = Line.fromPointAndAngle(Vector2D.of(1, 4), Geometry.PI, TEST_PRECISION);
Line c = Line.fromPointAndDirection(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION);
// act/assert
Assert.assertEquals(Geometry.ZERO_PI, a.angle(a), TEST_EPS);
Assert.assertEquals(-Geometry.PI, a.angle(b), TEST_EPS);
Assert.assertEquals(0.25 * Geometry.PI, a.angle(c), TEST_EPS);
Assert.assertEquals(Geometry.ZERO_PI, b.angle(b), TEST_EPS);
Assert.assertEquals(-Geometry.PI, b.angle(a), TEST_EPS);
Assert.assertEquals(-0.75 * Geometry.PI, b.angle(c), TEST_EPS);
Assert.assertEquals(Geometry.ZERO_PI, c.angle(c), TEST_EPS);
Assert.assertEquals(-0.25 * Geometry.PI, c.angle(a), TEST_EPS);
Assert.assertEquals(0.75 * Geometry.PI, c.angle(b), TEST_EPS);
}
@Test
public void testProject() {
// --- arrange
Line xAxis = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line yAxis = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
double diagonalYIntercept = 1;
Vector2D diagonalDir = Vector2D.of(1, 2);
Line diagonal = Line.fromPointAndDirection(Vector2D.of(0, diagonalYIntercept), diagonalDir, TEST_PRECISION);
EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
Vector2D pt = Vector2D.of(x, y);
// --- act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(x, 0), xAxis.project(pt), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, y), yAxis.project(pt), TEST_EPS);
Vector2D diagonalPt = diagonal.project(pt);
Assert.assertTrue(diagonal.contains(diagonalPt));
Assert.assertEquals(diagonal.distance(pt), pt.distance(diagonalPt), TEST_EPS);
// check that y = mx + b is true
Assert.assertEquals(diagonalPt.getY(),
(diagonalDir.getY() * diagonalPt.getX() / diagonalDir.getX()) + diagonalYIntercept, TEST_EPS);
});
}
@Test
public void testSpan() {
// arrange
Line line = Line.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
// act
Segment result = line.span();
// assert
Assert.assertSame(line, result.getHyperplane());
GeometryTestUtils.assertPositiveInfinity(result.getSize());
}
@Test
public void testSegment_interval() {
// arrange
Line line = Line.fromPointAndAngle(Vector2D.of(0, 1), Geometry.ZERO_PI, TEST_PRECISION);
Interval interval = Interval.of(1, 2, TEST_PRECISION);
// act
Segment segment = line.segment(interval);
// assert
Assert.assertSame(line, segment.getLine());
Assert.assertSame(interval, segment.getSubspaceRegion());
}
@Test
public void testSegment_doubles() {
// arrange
Line line = Line.fromPointAndAngle(Vector2D.of(0, 1), Geometry.ZERO_PI, TEST_PRECISION);
// act
Segment segment = line.segment(1, 2);
// assert
Assert.assertSame(line, segment.getLine());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segment.getStartPoint(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 1), segment.getEndPoint(), TEST_EPS);
}
@Test
public void testSegment_pointsOnLine() {
// arrange
Line line = Line.fromPointAndAngle(Vector2D.of(0, 1), Geometry.ZERO_PI, TEST_PRECISION);
// act
Segment segment = line.segment(Vector2D.of(3, 1), Vector2D.of(2, 1));
// assert
Assert.assertSame(line, segment.getLine());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 1), segment.getStartPoint(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 1), segment.getEndPoint(), TEST_EPS);
}
@Test
public void testSegment_pointsProjectedOnLine() {
// arrange
Line line = Line.fromPointAndAngle(Vector2D.of(0, 1), Geometry.ZERO_PI, TEST_PRECISION);
// act
Segment segment = line.segment(Vector2D.of(-3, 2), Vector2D.of(2, -1));
// assert
Assert.assertSame(line, segment.getLine());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), segment.getStartPoint(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 1), segment.getEndPoint(), TEST_EPS);
}
@Test
public void testSegmentTo_pointOnLine() {
// arrange
Line line = Line.fromPointAndAngle(Vector2D.of(0, 1), Geometry.PI, TEST_PRECISION);
// act
Segment segment = line.segmentTo(Vector2D.of(-3, 1));
// assert
Assert.assertSame(line, segment.getLine());
Assert.assertTrue(segment.isInfinite());
Assert.assertNull(segment.getStartPoint());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), segment.getEndPoint(), TEST_EPS);
Assert.assertTrue(segment.contains(Vector2D.of(1, 1)));
Assert.assertFalse(segment.contains(Vector2D.of(-4, 1)));
}
@Test
public void testSegmentTo_pointProjectedOnLine() {
// arrange
Line line = Line.fromPointAndAngle(Vector2D.of(0, 1), Geometry.PI, TEST_PRECISION);
// act
Segment segment = line.segmentTo(Vector2D.of(-3, 5));
// assert
Assert.assertSame(line, segment.getLine());
Assert.assertTrue(segment.isInfinite());
Assert.assertNull(segment.getStartPoint());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), segment.getEndPoint(), TEST_EPS);
Assert.assertTrue(segment.contains(Vector2D.of(1, 1)));
Assert.assertFalse(segment.contains(Vector2D.of(-4, 1)));
}
@Test
public void testSegmentFrom_pointOnLine() {
// arrange
Line line = Line.fromPointAndAngle(Vector2D.of(0, 1), Geometry.PI, TEST_PRECISION);
// act
Segment segment = line.segmentFrom(Vector2D.of(-3, 1));
// assert
Assert.assertSame(line, segment.getLine());
Assert.assertTrue(segment.isInfinite());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), segment.getStartPoint(), TEST_EPS);
Assert.assertNull(segment.getEndPoint());
Assert.assertFalse(segment.contains(Vector2D.of(1, 1)));
Assert.assertTrue(segment.contains(Vector2D.of(-4, 1)));
}
@Test
public void testSegmentFrom_pointProjectedOnLine() {
// arrange
Line line = Line.fromPointAndAngle(Vector2D.of(0, 1), Geometry.PI, TEST_PRECISION);
// act
Segment segment = line.segmentFrom(Vector2D.of(-3, 5));
// assert
Assert.assertSame(line, segment.getLine());
Assert.assertTrue(segment.isInfinite());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), segment.getStartPoint(), TEST_EPS);
Assert.assertNull(segment.getEndPoint());
Assert.assertFalse(segment.contains(Vector2D.of(1, 1)));
Assert.assertTrue(segment.contains(Vector2D.of(-4, 1)));
}
@Test
public void testSubline() {
// arrange
Line line = Line.fromPointAndAngle(Vector2D.of(0, 1), Geometry.ZERO_PI, TEST_PRECISION);
// act
SubLine subline = line.subline();
// assert
Assert.assertSame(line, subline.getLine());
Assert.assertTrue(subline.isEmpty());
}
@Test
public void testOffset_parallelLines() {
// arrange
double dist = Math.sin(Math.atan2(2, 1));
Line a = Line.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
Line b = Line.fromPoints(Vector2D.of(-3, 0), Vector2D.of(0, 6), TEST_PRECISION);
Line c = Line.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
Line d = Line.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, -2), TEST_PRECISION);
// act/assert
Assert.assertEquals(-dist, a.offset(b), TEST_EPS);
Assert.assertEquals(dist, b.offset(a), TEST_EPS);
Assert.assertEquals(dist, a.offset(c), TEST_EPS);
Assert.assertEquals(-dist, c.offset(a), TEST_EPS);
Assert.assertEquals(3 * dist, a.offset(d), TEST_EPS);
Assert.assertEquals(3 * dist, d.offset(a), TEST_EPS);
}
@Test
public void testOffset_coincidentLines() {
// arrange
Line a = Line.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
Line b = Line.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
Line c = b.reverse();
// act/assert
Assert.assertEquals(0, a.offset(a), TEST_EPS);
Assert.assertEquals(0, a.offset(b), TEST_EPS);
Assert.assertEquals(0, b.offset(a), TEST_EPS);
Assert.assertEquals(0, a.offset(c), TEST_EPS);
Assert.assertEquals(0, c.offset(a), TEST_EPS);
}
@Test
public void testOffset_nonParallelLines() {
// arrange
Line a = Line.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line b = Line.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
Line c = Line.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
Line d = Line.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, 4), TEST_PRECISION);
// act/assert
Assert.assertEquals(0, a.offset(b), TEST_EPS);
Assert.assertEquals(0, b.offset(a), TEST_EPS);
Assert.assertEquals(0, a.offset(c), TEST_EPS);
Assert.assertEquals(0, c.offset(a), TEST_EPS);
Assert.assertEquals(0, a.offset(d), TEST_EPS);
Assert.assertEquals(0, d.offset(a), TEST_EPS);
}
@Test
public void testOffset_point() {
// arrange
Line line = Line.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
Line reversed = line.reverse();
// act/assert
Assert.assertEquals(0.0, line.offset(Vector2D.of(-0.5, 1)), TEST_EPS);
Assert.assertEquals(0.0, line.offset(Vector2D.of(-1.5, -1)), TEST_EPS);
Assert.assertEquals(0.0, line.offset(Vector2D.of(0.5, 3)), TEST_EPS);
double d = Math.sin(Math.atan2(2, 1));
Assert.assertEquals(d, line.offset(Vector2D.ZERO), TEST_EPS);
Assert.assertEquals(-d, line.offset(Vector2D.of(-1, 2)), TEST_EPS);
Assert.assertEquals(-d, reversed.offset(Vector2D.ZERO), TEST_EPS);
Assert.assertEquals(d, reversed.offset(Vector2D.of(-1, 2)), TEST_EPS);
}
@Test
public void testOffset_point_permute() {
// arrange
Line line = Line.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
Vector2D lineOrigin = line.getOrigin();
EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
Vector2D pt = Vector2D.of(x, y);
// act
double offset = line.offset(pt);
// arrange
Vector2D vec = lineOrigin.vectorTo(pt).reject(line.getDirection());
double dot = vec.dot(line.getOffsetDirection());
double expected = Math.signum(dot) * vec.norm();
Assert.assertEquals(expected, offset, TEST_EPS);
});
}
@Test
public void testSimilarOrientation() {
// arrange
Line a = Line.fromPointAndAngle(Vector2D.ZERO, Geometry.ZERO_PI, TEST_PRECISION);
Line b = Line.fromPointAndAngle(Vector2D.of(4, 5), Geometry.ZERO_PI, TEST_PRECISION);
Line c = Line.fromPointAndAngle(Vector2D.of(-1, -3), 0.4 * Geometry.PI, TEST_PRECISION);
Line d = Line.fromPointAndAngle(Vector2D.of(1, 0), -0.4 * Geometry.PI, TEST_PRECISION);
Line e = Line.fromPointAndAngle(Vector2D.of(6, -3), Geometry.PI, TEST_PRECISION);
Line f = Line.fromPointAndAngle(Vector2D.of(8, 5), 0.8 * Geometry.PI, TEST_PRECISION);
Line g = Line.fromPointAndAngle(Vector2D.of(6, -3), -0.8 * Geometry.PI, TEST_PRECISION);
// act/assert
Assert.assertTrue(a.similarOrientation(a));
Assert.assertTrue(a.similarOrientation(b));
Assert.assertTrue(b.similarOrientation(a));
Assert.assertTrue(a.similarOrientation(c));
Assert.assertTrue(c.similarOrientation(a));
Assert.assertTrue(a.similarOrientation(d));
Assert.assertTrue(d.similarOrientation(a));
Assert.assertFalse(c.similarOrientation(d));
Assert.assertFalse(d.similarOrientation(c));
Assert.assertTrue(e.similarOrientation(f));
Assert.assertTrue(f.similarOrientation(e));
Assert.assertTrue(e.similarOrientation(g));
Assert.assertTrue(g.similarOrientation(e));
Assert.assertFalse(a.similarOrientation(e));
Assert.assertFalse(e.similarOrientation(a));
}
@Test
public void testSimilarOrientation_orthogonal() {
// arrange
Line a = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line b = Line.fromPointAndDirection(Vector2D.of(4, 5), Vector2D.Unit.PLUS_Y, TEST_PRECISION);
Line c = Line.fromPointAndDirection(Vector2D.of(-4, -5), Vector2D.Unit.MINUS_Y, TEST_PRECISION);
// act/assert
Assert.assertTrue(a.similarOrientation(b));
Assert.assertTrue(b.similarOrientation(a));
Assert.assertTrue(a.similarOrientation(c));
Assert.assertTrue(c.similarOrientation(a));
}
@Test
public void testDistance_parallelLines() {
// arrange
double dist = Math.sin(Math.atan2(2, 1));
Line a = Line.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
Line b = Line.fromPoints(Vector2D.of(-3, 0), Vector2D.of(0, 6), TEST_PRECISION);
Line c = Line.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
Line d = Line.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, -2), TEST_PRECISION);
// act/assert
Assert.assertEquals(dist, a.distance(b), TEST_EPS);
Assert.assertEquals(dist, b.distance(a), TEST_EPS);
Assert.assertEquals(dist, a.distance(c), TEST_EPS);
Assert.assertEquals(dist, c.distance(a), TEST_EPS);
Assert.assertEquals(3 * dist, a.distance(d), TEST_EPS);
Assert.assertEquals(3 * dist, d.distance(a), TEST_EPS);
}
@Test
public void testDistance_coincidentLines() {
// arrange
Line a = Line.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
Line b = Line.fromPoints(Vector2D.of(-2, 0), Vector2D.of(0, 4), TEST_PRECISION);
Line c = b.reverse();
// act/assert
Assert.assertEquals(0, a.distance(a), TEST_EPS);
Assert.assertEquals(0, a.distance(b), TEST_EPS);
Assert.assertEquals(0, b.distance(a), TEST_EPS);
Assert.assertEquals(0, a.distance(c), TEST_EPS);
Assert.assertEquals(0, c.distance(a), TEST_EPS);
}
@Test
public void testDistance_nonParallelLines() {
// arrange
Line a = Line.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line b = Line.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
Line c = Line.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
Line d = Line.fromPoints(Vector2D.of(1, 0), Vector2D.of(0, 4), TEST_PRECISION);
// act/assert
Assert.assertEquals(0, a.distance(b), TEST_EPS);
Assert.assertEquals(0, b.distance(a), TEST_EPS);
Assert.assertEquals(0, a.distance(c), TEST_EPS);
Assert.assertEquals(0, c.distance(a), TEST_EPS);
Assert.assertEquals(0, a.distance(d), TEST_EPS);
Assert.assertEquals(0, d.distance(a), TEST_EPS);
}
@Test
public void testDistance() {
// arrange
Line line = Line.fromPoints(Vector2D.of(2, 1), Vector2D.of(-2, -2), TEST_PRECISION);
// act/assert
Assert.assertEquals(0, line.distance(line.getOrigin()), TEST_EPS);
Assert.assertEquals(+5.0, line.distance(Vector2D.of(5, -3)), TEST_EPS);
Assert.assertEquals(+5.0, line.distance(Vector2D.of(-5, 2)), TEST_EPS);
}
@Test
public void testPointAt() {
// arrange
Vector2D origin = Vector2D.of(-1, 1);
double d = Math.sqrt(2);
Line line = Line.fromPointAndDirection(origin, Vector2D.of(1, 1), TEST_PRECISION);
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(origin, line.pointAt(0, 0), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, line.pointAt(0, d), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 2), line.pointAt(0, -d), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), line.pointAt(-d, 0), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), line.pointAt(d, 0), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), line.pointAt(d, d), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-3, 1), line.pointAt(-d, -d), TEST_EPS);
}
@Test
public void testPointAt_abscissaOffsetRoundtrip() {
// arrange
Line line = Line.fromPoints(Vector2D.of(2, 1), Vector2D.of(-2, -2), TEST_PRECISION);
for (double abscissa = -2.0; abscissa < 2.0; abscissa += 0.2) {
for (double offset = -2.0; offset < 2.0; offset += 0.2) {
// act
Vector2D point = line.pointAt(abscissa, offset);
// assert
Assert.assertEquals(abscissa, line.toSubspace(point).getX(), TEST_EPS);
Assert.assertEquals(offset, line.offset(point), TEST_EPS);
}
}
}
@Test
public void testContains_line() {
// arrange
Vector2D pt = Vector2D.of(1, 2);
Vector2D dir = Vector2D.of(3, 7);
Line a = Line.fromPointAndDirection(pt, dir, TEST_PRECISION);
Line b = Line.fromPointAndDirection(Vector2D.of(0, -4), dir, TEST_PRECISION);
Line c = Line.fromPointAndDirection(Vector2D.of(-2, -2), dir.negate(), TEST_PRECISION);
Line d = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line e = Line.fromPointAndDirection(pt, dir, TEST_PRECISION);
Line f = Line.fromPointAndDirection(pt, dir.negate(), TEST_PRECISION);
// act/assert
Assert.assertTrue(a.contains(a));
Assert.assertTrue(a.contains(e));
Assert.assertTrue(e.contains(a));
Assert.assertTrue(a.contains(f));
Assert.assertTrue(f.contains(a));
Assert.assertFalse(a.contains(b));
Assert.assertFalse(a.contains(c));
Assert.assertFalse(a.contains(d));
}
@Test
public void testIsParallel_closeToEpsilon() {
// arrange
double eps = 1e-3;
DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
Vector2D p = Vector2D.of(1, 2);
Line line = Line.fromPointAndAngle(p, Geometry.ZERO_PI, precision);
// act/assert
Vector2D offset1 = Vector2D.of(0, 1e-4);
Vector2D offset2 = Vector2D.of(0, 2e-3);
Assert.assertTrue(line.contains(Line.fromPointAndAngle(p.add(offset1), Geometry.ZERO_PI, precision)));
Assert.assertTrue(line.contains(Line.fromPointAndAngle(p.subtract(offset1), Geometry.ZERO_PI, precision)));
Assert.assertFalse(line.contains(Line.fromPointAndAngle(p.add(offset2), Geometry.ZERO_PI, precision)));
Assert.assertFalse(line.contains(Line.fromPointAndAngle(p.subtract(offset2), Geometry.ZERO_PI, precision)));
Assert.assertTrue(line.contains(Line.fromPointAndAngle(p, 1e-4, precision)));
Assert.assertFalse(line.contains(Line.fromPointAndAngle(p, 1e-2, precision)));
}
@Test
public void testContains_point() {
// arrange
Vector2D p1 = Vector2D.of(-1, 0);
Vector2D p2 = Vector2D.of(0, 2);
Line line = Line.fromPoints(p1, p2, TEST_PRECISION);
// act/assert
Assert.assertTrue(line.contains(p1));
Assert.assertTrue(line.contains(p2));
Assert.assertFalse(line.contains(Vector2D.ZERO));
Assert.assertFalse(line.contains(Vector2D.of(100, 79)));
Vector2D offset1 = Vector2D.of(0.1, 0);
Vector2D offset2 = Vector2D.of(0, -0.1);
Vector2D v;
for (double t=-2; t<=2; t+=0.1) {
v = p1.lerp(p2, t);
Assert.assertTrue(line.contains(v));
Assert.assertFalse(line.contains(v.add(offset1)));
Assert.assertFalse(line.contains(v.add(offset2)));
}
}
@Test
public void testContains_point_closeToEpsilon() {
// arrange
double eps = 1e-3;
DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
Vector2D p1 = Vector2D.of(-1, 0);
Vector2D p2 = Vector2D.of(0, 2);
Vector2D mid = p1.lerp(p2, 0.5);
Line line = Line.fromPoints(p1, p2, precision);
Vector2D dir = line.getOffsetDirection();
// act/assert
Assert.assertTrue(line.contains(mid.add(dir.multiply(1e-4))));
Assert.assertTrue(line.contains(mid.add(dir.multiply(-1e-4))));
Assert.assertFalse(line.contains(mid.add(dir.multiply(2e-3))));
Assert.assertFalse(line.contains(mid.add(dir.multiply(-2e-3))));
}
@Test
public void testDistance_point() {
// arrange
Line line = Line.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
Line reversed = line.reverse();
// act/assert
Assert.assertEquals(0.0, line.distance(Vector2D.of(-0.5, 1)), TEST_EPS);
Assert.assertEquals(0.0, line.distance(Vector2D.of(-1.5, -1)), TEST_EPS);
Assert.assertEquals(0.0, line.distance(Vector2D.of(0.5, 3)), TEST_EPS);
double d = Math.sin(Math.atan2(2, 1));
Assert.assertEquals(d, line.distance(Vector2D.ZERO), TEST_EPS);
Assert.assertEquals(d, line.distance(Vector2D.of(-1, 2)), TEST_EPS);
Assert.assertEquals(d, reversed.distance(Vector2D.ZERO), TEST_EPS);
Assert.assertEquals(d, reversed.distance(Vector2D.of(-1, 2)), TEST_EPS);
}
@Test
public void testDistance_point_permute() {
// arrange
Line line = Line.fromPoints(Vector2D.of(-1, 0), Vector2D.of(0, 2), TEST_PRECISION);
Vector2D lineOrigin = line.getOrigin();
EuclideanTestUtils.permute(-5, 5, 0.5, (x, y) -> {
Vector2D pt = Vector2D.of(x, y);
// act
double dist = line.distance(pt);
// arrange
Vector2D vec = lineOrigin.vectorTo(pt).reject(line.getDirection());
Assert.assertEquals(vec.norm(), dist, TEST_EPS);
});
}
@Test
public void testIsParallel() {
// arrange
Vector2D dir = Vector2D.of(3, 7);
Line a = Line.fromPointAndDirection(Vector2D.of(1, 2), dir, TEST_PRECISION);
Line b = Line.fromPointAndDirection(Vector2D.of(0, -4), dir, TEST_PRECISION);
Line c = Line.fromPointAndDirection(Vector2D.of(-2, -2), dir.negate(), TEST_PRECISION);
Line d = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
// act/assert
Assert.assertTrue(a.isParallel(a));
Assert.assertTrue(a.isParallel(b));
Assert.assertTrue(b.isParallel(a));
Assert.assertTrue(a.isParallel(c));
Assert.assertTrue(c.isParallel(a));
Assert.assertFalse(a.isParallel(d));
Assert.assertFalse(d.isParallel(a));
}
@Test
public void testIsParallel_closeToParallel() {
// arrange
double eps = 1e-3;
DoublePrecisionContext precision = new EpsilonDoublePrecisionContext(eps);
Vector2D p1 = Vector2D.of(1, 2);
Vector2D p2 = Vector2D.of(1, -2);
Line line = Line.fromPointAndAngle(p1, Geometry.ZERO_PI, precision);
// act/assert
Assert.assertTrue(line.isParallel(Line.fromPointAndAngle(p2, 1e-4, precision)));
Assert.assertFalse(line.isParallel(Line.fromPointAndAngle(p2, 1e-2, precision)));
}
@Test
public void testTransform() {
// arrange
AffineTransformMatrix2D scale = AffineTransformMatrix2D.createScale(2, 3);
AffineTransformMatrix2D reflect = AffineTransformMatrix2D.createScale(-1, 1);
AffineTransformMatrix2D translate = AffineTransformMatrix2D.createTranslation(3, 4);
AffineTransformMatrix2D rotate = AffineTransformMatrix2D.createRotation(Geometry.HALF_PI);
AffineTransformMatrix2D rotateAroundPt = AffineTransformMatrix2D.createRotation(Vector2D.of(0, 1), Geometry.HALF_PI);
Vector2D p1 = Vector2D.of(0, 1);
Vector2D p2 = Vector2D.of(1, 0);
Line horizontal = Line.fromPointAndDirection(p1, Vector2D.Unit.PLUS_X, TEST_PRECISION);
Line vertical = Line.fromPointAndDirection(p2, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
Line diagonal = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION);
// act/assert
Assert.assertSame(TEST_PRECISION, horizontal.transform(scale).getPrecision());
checkLine(horizontal.transform(scale), Vector2D.of(0, 3), Vector2D.Unit.PLUS_X);
checkLine(vertical.transform(scale), Vector2D.of(2, 0), Vector2D.Unit.PLUS_Y);
checkLine(diagonal.transform(scale), Vector2D.ZERO, Vector2D.of(2, 3).normalize());
checkLine(horizontal.transform(reflect), p1, Vector2D.Unit.MINUS_X);
checkLine(vertical.transform(reflect), Vector2D.of(-1, 0), Vector2D.Unit.PLUS_Y);
checkLine(diagonal.transform(reflect), Vector2D.ZERO, Vector2D.of(-1, 1).normalize());
checkLine(horizontal.transform(translate), Vector2D.of(0, 5), Vector2D.Unit.PLUS_X);
checkLine(vertical.transform(translate), Vector2D.of(4, 0), Vector2D.Unit.PLUS_Y);
checkLine(diagonal.transform(translate), Vector2D.of(-0.5, 0.5), Vector2D.of(1, 1).normalize());
checkLine(horizontal.transform(rotate), Vector2D.of(-1, 0), Vector2D.Unit.PLUS_Y);
checkLine(vertical.transform(rotate), Vector2D.of(0, 1), Vector2D.Unit.MINUS_X);
checkLine(diagonal.transform(rotate), Vector2D.ZERO, Vector2D.of(-1, 1).normalize());
checkLine(horizontal.transform(rotateAroundPt), Vector2D.ZERO, Vector2D.Unit.PLUS_Y);
checkLine(vertical.transform(rotateAroundPt), Vector2D.of(0, 2), Vector2D.Unit.MINUS_X);
checkLine(diagonal.transform(rotateAroundPt), Vector2D.of(1, 1), Vector2D.of(-1, 1).normalize());
}
@Test
public void testTransform_collapsedPoints() {
// arrange
AffineTransformMatrix2D scaleCollapse = AffineTransformMatrix2D.createScale(0, 1);
Line line = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
// act/assert
GeometryTestUtils.assertThrows(() -> {
line.transform(scaleCollapse);
}, GeometryValueException.class, "Line direction cannot be zero");
}
@Test
public void testSubspaceTransform() {
// arrange
Line line = Line.fromPoints(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION);
// act/assert
checkSubspaceTransform(line.subspaceTransform(AffineTransformMatrix2D.createScale(2, 3)),
Vector2D.of(2, 0), Vector2D.Unit.PLUS_Y,
Vector2D.of(2, 0), Vector2D.of(2, 3));
checkSubspaceTransform(line.subspaceTransform(AffineTransformMatrix2D.createTranslation(2, 3)),
Vector2D.of(3, 0), Vector2D.Unit.PLUS_Y,
Vector2D.of(3, 3), Vector2D.of(3, 4));
checkSubspaceTransform(line.subspaceTransform(AffineTransformMatrix2D.createRotation(Geometry.HALF_PI)),
Vector2D.of(0, 1), Vector2D.Unit.MINUS_X,
Vector2D.of(0, 1), Vector2D.of(-1, 1));
}
private void checkSubspaceTransform(SubspaceTransform st, Vector2D origin, Vector2D dir, Vector2D tZero, Vector2D tOne) {
Line line = st.getLine();
AffineTransformMatrix1D transform = st.getTransform();
checkLine(line, origin, dir);
EuclideanTestUtils.assertCoordinatesEqual(tZero, line.toSpace(transform.apply(Vector1D.ZERO)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(tOne, line.toSpace(transform.apply(Vector1D.Unit.PLUS)), TEST_EPS);
}
@Test
public void testSubspaceTransform_transformsPointsCorrectly() {
// arrange
Line line = Line.fromPointAndDirection(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION);
EuclideanTestUtils.permuteSkipZero(-2, 2, 0.5, (a, b) -> {
// create a somewhat complicate transform to try to hit all of the edge cases
AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(Vector2D.of(a, b))
.rotate(a * b)
.scale(0.1, 4);
// act
SubspaceTransform st = line.subspaceTransform(transform);
// assert
for (double x=-5.0; x<=5.0; x+=1) {
Vector1D subPt = Vector1D.of(x);
Vector2D expected = transform.apply(line.toSpace(subPt));
Vector2D actual = st.getLine().toSpace(
st.getTransform().apply(subPt));
EuclideanTestUtils.assertCoordinatesEqual(expected, actual, TEST_EPS);
};
});
}
@Test
public void testEq() {
// arrange
DoublePrecisionContext precision1 = new EpsilonDoublePrecisionContext(1e-3);
DoublePrecisionContext precision2 = new EpsilonDoublePrecisionContext(1e-2);
Vector2D p = Vector2D.of(1, 2);
double angle = 1.0;
Line a = Line.fromPointAndAngle(p, angle, precision1);
Line b = Line.fromPointAndAngle(Vector2D.ZERO, angle, precision1);
Line c = Line.fromPointAndAngle(p, angle + 1.0, precision1);
Line d = Line.fromPointAndAngle(p, angle, precision2);
Line e = Line.fromPointAndAngle(p, angle, precision1);
Line f = Line.fromPointAndAngle(p.add(Vector2D.of(1e-4, 1e-4)), angle, precision1);
Line g = Line.fromPointAndAngle(p, angle + 1e-4, precision1);
// act/assert
Assert.assertTrue(a.eq(a));
Assert.assertTrue(a.eq(e));
Assert.assertTrue(e.eq(a));
Assert.assertTrue(a.eq(f));
Assert.assertTrue(f.eq(a));
Assert.assertTrue(a.eq(g));
Assert.assertTrue(g.eq(a));
Assert.assertFalse(a.eq(b));
Assert.assertFalse(a.eq(c));
Assert.assertFalse(a.eq(d));
}
@Test
public void testHashCode() {
// arrange
DoublePrecisionContext precision1 = new EpsilonDoublePrecisionContext(1e-4);
DoublePrecisionContext precision2 = new EpsilonDoublePrecisionContext(1e-5);
Vector2D p = Vector2D.of(1, 2);
Vector2D v = Vector2D.of(1, 1);
Line a = Line.fromPointAndDirection(p, v, precision1);
Line b = Line.fromPointAndDirection(Vector2D.ZERO, v, precision1);
Line c = Line.fromPointAndDirection(p, v.negate(), precision1);
Line d = Line.fromPointAndDirection(p, v, precision2);
Line e = Line.fromPointAndDirection(p, v, precision1);
// act/assert
int aHash = a.hashCode();
Assert.assertEquals(aHash, a.hashCode());
Assert.assertEquals(aHash, e.hashCode());
Assert.assertNotEquals(aHash, b.hashCode());
Assert.assertNotEquals(aHash, c.hashCode());
Assert.assertNotEquals(aHash, d.hashCode());
}
@Test
public void testEquals() {
// arrange
DoublePrecisionContext precision1 = new EpsilonDoublePrecisionContext(1e-4);
DoublePrecisionContext precision2 = new EpsilonDoublePrecisionContext(1e-5);
Vector2D p = Vector2D.of(1, 2);
Vector2D v = Vector2D.of(1, 1);
Line a = Line.fromPointAndDirection(p, v, precision1);
Line b = Line.fromPointAndDirection(Vector2D.ZERO, v, precision1);
Line c = Line.fromPointAndDirection(p, v.negate(), precision1);
Line d = Line.fromPointAndDirection(p, v, precision2);
Line e = Line.fromPointAndDirection(p, v, precision1);
// act/assert
Assert.assertTrue(a.equals(a));
Assert.assertTrue(a.equals(e));
Assert.assertTrue(e.equals(a));
Assert.assertFalse(a.equals(null));
Assert.assertFalse(a.equals(new Object()));
Assert.assertFalse(a.equals(b));
Assert.assertFalse(a.equals(c));
Assert.assertFalse(a.equals(d));
}
@Test
public void testToString() {
// arrange
Line line = Line.fromPointAndDirection(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
// act
String str = line.toString();
// assert
Assert.assertTrue(str.contains("Line"));
Assert.assertTrue(str.contains("origin= (0.0, 0.0)"));
Assert.assertTrue(str.contains("direction= (1.0, 0.0)"));
}
/**
* Check that the line has the given defining properties.
* @param line
* @param origin
* @param dir
*/
private void checkLine(Line line, Vector2D origin, Vector2D dir) {
EuclideanTestUtils.assertCoordinatesEqual(origin, line.getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(dir, line.getDirection(), TEST_EPS);
}
}