LinePathTest.java
/*
* 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
*
* https://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.path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
import org.apache.commons.geometry.euclidean.twod.Line;
import org.apache.commons.geometry.euclidean.twod.LineConvexSubset;
import org.apache.commons.geometry.euclidean.twod.LinecastChecker2D;
import org.apache.commons.geometry.euclidean.twod.Lines;
import org.apache.commons.geometry.euclidean.twod.Ray;
import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
import org.apache.commons.geometry.euclidean.twod.ReverseRay;
import org.apache.commons.geometry.euclidean.twod.Segment;
import org.apache.commons.geometry.euclidean.twod.Vector2D;
import org.apache.commons.geometry.euclidean.twod.path.LinePath.Builder;
import org.apache.commons.numbers.angle.Angle;
import org.apache.commons.numbers.core.Precision;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class LinePathTest {
private static final double TEST_EPS = 1e-10;
private static final Precision.DoubleEquivalence TEST_PRECISION =
Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
@Test
void testFrom_empty() {
// act
final LinePath path = LinePath.from(new ArrayList<>());
// assert
Assertions.assertTrue(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertFalse(path.isClosed());
Assertions.assertEquals(0, path.getSize(), TEST_EPS);
Assertions.assertNull(path.getStart());
Assertions.assertNull(path.getEnd());
Assertions.assertEquals(0, path.getElements().size());
Assertions.assertEquals(0, path.getVertexSequence().size());
}
@Test
void testFrom_singleFiniteSegment() {
// arrange
final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
// act
final LinePath path = LinePath.from(a);
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertFalse(path.isClosed());
Assertions.assertEquals(1, path.getSize(), TEST_EPS);
Assertions.assertSame(a, path.getStart());
Assertions.assertSame(a, path.getEnd());
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(1, segments.size());
Assertions.assertSame(a, segments.get(0));
Assertions.assertEquals(Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0)), path.getVertexSequence());
}
@Test
void testFrom_singleInfiniteSegment() {
// arrange
final LineConvexSubset a = Lines.fromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION).span();
// act
final LinePath path = LinePath.from(a);
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertTrue(path.isInfinite());
Assertions.assertFalse(path.isFinite());
Assertions.assertFalse(path.isClosed());
GeometryTestUtils.assertPositiveInfinity(path.getSize());
Assertions.assertSame(a, path.getStart());
Assertions.assertSame(a, path.getEnd());
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(1, segments.size());
Assertions.assertSame(a, segments.get(0));
Assertions.assertEquals(0, path.getVertexSequence().size());
}
@Test
void testFrom_finiteSegments_notClosed() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Segment a = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
final Segment b = Lines.segmentFromPoints(p2, p3, TEST_PRECISION);
// act
final LinePath path = LinePath.from(a, b);
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertFalse(path.isClosed());
Assertions.assertEquals(2, path.getSize(), TEST_EPS);
Assertions.assertSame(a, path.getStart());
Assertions.assertSame(b, path.getEnd());
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(2, segments.size());
Assertions.assertSame(a, segments.get(0));
Assertions.assertSame(b, segments.get(1));
Assertions.assertEquals(Arrays.asList(p1, p2, p3), path.getVertexSequence());
}
@Test
void testFrom_finiteSegments_closed() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Segment a = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
final Segment b = Lines.segmentFromPoints(p2, p3, TEST_PRECISION);
final Segment c = Lines.segmentFromPoints(p3, p1, TEST_PRECISION);
// act
final LinePath path = LinePath.from(Arrays.asList(a, b, c));
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertTrue(path.isClosed());
Assertions.assertSame(a, path.getStart());
Assertions.assertSame(c, path.getEnd());
Assertions.assertEquals(2 + Math.sqrt(2), path.getSize(), TEST_EPS);
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(3, segments.size());
Assertions.assertSame(a, segments.get(0));
Assertions.assertSame(b, segments.get(1));
Assertions.assertSame(c, segments.get(2));
Assertions.assertEquals(Arrays.asList(p1, p2, p3, p1), path.getVertexSequence());
}
@Test
void testFrom_infiniteSegments() {
// arrange
final ReverseRay a = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION)
.reverseRayTo(1.0);
final Ray b = Lines.fromPointAndAngle(Vector2D.of(1, 0), Angle.PI_OVER_TWO, TEST_PRECISION)
.rayFrom(0.0);
// act
final LinePath path = LinePath.from(Arrays.asList(a, b));
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertTrue(path.isInfinite());
Assertions.assertFalse(path.isFinite());
Assertions.assertFalse(path.isClosed());
GeometryTestUtils.assertPositiveInfinity(path.getSize());
Assertions.assertSame(a, path.getStart());
Assertions.assertSame(b, path.getEnd());
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(2, segments.size());
Assertions.assertSame(a, segments.get(0));
Assertions.assertSame(b, segments.get(1));
Assertions.assertEquals(Collections.singletonList(Vector2D.of(1, 0)), path.getVertexSequence());
}
@Test
void testFrom_finiteAndInfiniteSegments_startInfinite() {
// arrange
final ReverseRay a = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION).reverseRayTo(1.0);
final Segment b = Lines.segmentFromPoints(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION);
// act
final LinePath path = LinePath.from(Arrays.asList(a, b));
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertTrue(path.isInfinite());
Assertions.assertFalse(path.isFinite());
Assertions.assertFalse(path.isClosed());
Assertions.assertSame(a, path.getStart());
Assertions.assertSame(b, path.getEnd());
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(2, segments.size());
Assertions.assertSame(a, segments.get(0));
Assertions.assertSame(b, segments.get(1));
Assertions.assertEquals(Arrays.asList(Vector2D.of(1, 0), Vector2D.of(1, 1)), path.getVertexSequence());
}
@Test
void testFrom_finiteAndInfiniteSegments_endInfinite() {
// arrange
final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
final Ray b = Lines.fromPointAndAngle(Vector2D.of(1, 0), Angle.PI_OVER_TWO, TEST_PRECISION)
.rayFrom(0.0);
// act
final LinePath path = LinePath.from(Arrays.asList(a, b));
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertTrue(path.isInfinite());
Assertions.assertFalse(path.isFinite());
Assertions.assertFalse(path.isClosed());
Assertions.assertSame(a, path.getStart());
Assertions.assertSame(b, path.getEnd());
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(2, segments.size());
Assertions.assertSame(a, segments.get(0));
Assertions.assertSame(b, segments.get(1));
Assertions.assertEquals(Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0)), path.getVertexSequence());
}
@Test
void testFrom_segmentsNotConnected() {
// arrange
final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
final Segment b = Lines.segmentFromPoints(Vector2D.of(1.01, 0), Vector2D.of(1, 0), TEST_PRECISION);
final LineConvexSubset c = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION).span();
final LineConvexSubset d = Lines.fromPointAndAngle(Vector2D.of(1, 0), Angle.PI_OVER_TWO, TEST_PRECISION).span();
// act/assert
Assertions.assertThrows(IllegalStateException.class, () -> LinePath.from(a, b));
Assertions.assertThrows(IllegalStateException.class, () -> LinePath.from(c, b));
Assertions.assertThrows(IllegalStateException.class, () -> LinePath.from(a, d));
}
@Test
void testFromVertices_empty() {
// act
final LinePath path = LinePath.fromVertices(new ArrayList<>(), TEST_PRECISION);
// assert
Assertions.assertTrue(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertFalse(path.isClosed());
Assertions.assertNull(path.getStart());
Assertions.assertNull(path.getEnd());
Assertions.assertEquals(0, path.getElements().size());
Assertions.assertEquals(0, path.getVertexSequence().size());
}
@Test
void testFromVertices_singleVertex_failsToCreatePath() {
// act/assert
Assertions.assertThrows(IllegalStateException.class, () -> LinePath.fromVertices(Collections.singletonList(Vector2D.ZERO), TEST_PRECISION));
}
@Test
void testFromVertices_twoVertices() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
// act
final LinePath path = LinePath.fromVertices(Arrays.asList(p1, p2), TEST_PRECISION);
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertFalse(path.isClosed());
assertFiniteSegment(path.getStart(), p1, p2);
Assertions.assertSame(path.getStart(), path.getEnd());
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(1, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
Assertions.assertEquals(Arrays.asList(p1, p2), path.getVertexSequence());
}
@Test
void testFromVertices_multipleVertices_notClosed() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Vector2D p4 = Vector2D.of(0, 1);
// act
final LinePath path = LinePath.fromVertices(Arrays.asList(p1, p2, p3, p4), TEST_PRECISION);
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertFalse(path.isClosed());
assertFiniteSegment(path.getStart(), p1, p2);
assertFiniteSegment(path.getEnd(), p3, p4);
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(3, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p4);
Assertions.assertEquals(Arrays.asList(p1, p2, p3, p4), path.getVertexSequence());
}
@Test
void testFromVertices_multipleVertices_closed() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Vector2D p4 = Vector2D.of(0, 1);
// act
final LinePath path = LinePath.fromVertices(Arrays.asList(p1, p2, p3, p4, p1), TEST_PRECISION);
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertTrue(path.isClosed());
assertFiniteSegment(path.getStart(), p1, p2);
assertFiniteSegment(path.getEnd(), p4, p1);
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(4, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p4);
assertFiniteSegment(segments.get(3), p4, p1);
Assertions.assertEquals(Arrays.asList(p1, p2, p3, p4, p1), path.getVertexSequence());
}
@Test
void testFromVertexLoop_empty() {
// act
final LinePath path = LinePath.fromVertexLoop(new ArrayList<>(), TEST_PRECISION);
// assert
Assertions.assertTrue(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertFalse(path.isClosed());
Assertions.assertNull(path.getStart());
Assertions.assertNull(path.getEnd());
Assertions.assertEquals(0, path.getElements().size());
Assertions.assertEquals(0, path.getVertexSequence().size());
}
@Test
void testFromVertexLoop_singleVertex_failsToCreatePath() {
// act/assert
Assertions.assertThrows(IllegalStateException.class, () -> LinePath.fromVertexLoop(Collections.singletonList(Vector2D.ZERO), TEST_PRECISION));
}
@Test
void testFromVertexLoop_closeRequired() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
// act
final LinePath path = LinePath.fromVertexLoop(Arrays.asList(p1, p2, p3), TEST_PRECISION);
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertTrue(path.isClosed());
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(3, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p1);
Assertions.assertEquals(Arrays.asList(p1, p2, p3, p1), path.getVertexSequence());
}
@Test
void testFromVertexLoop_closeNotRequired() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
// act
final LinePath path = LinePath.fromVertexLoop(Arrays.asList(p1, p2, p3, Vector2D.of(0, 0)), TEST_PRECISION);
// assert
Assertions.assertFalse(path.isEmpty());
Assertions.assertFalse(path.isInfinite());
Assertions.assertTrue(path.isFinite());
Assertions.assertTrue(path.isClosed());
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(3, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p1);
Assertions.assertEquals(Arrays.asList(p1, p2, p3, p1), path.getVertexSequence());
}
@Test
void testFromVertices_booleanArg() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(0, 1);
// act
final LinePath open = LinePath.fromVertices(Arrays.asList(p1, p2, p3), false, TEST_PRECISION);
final LinePath closed = LinePath.fromVertices(Arrays.asList(p1, p2, p3), true, TEST_PRECISION);
// assert
Assertions.assertFalse(open.isClosed());
final List<LineConvexSubset> openSegments = open.getElements();
Assertions.assertEquals(2, openSegments.size());
assertFiniteSegment(openSegments.get(0), p1, p2);
assertFiniteSegment(openSegments.get(1), p2, p3);
Assertions.assertTrue(closed.isClosed());
final List<LineConvexSubset> closedSegments = closed.getElements();
Assertions.assertEquals(3, closedSegments.size());
assertFiniteSegment(closedSegments.get(0), p1, p2);
assertFiniteSegment(closedSegments.get(1), p2, p3);
assertFiniteSegment(closedSegments.get(2), p3, p1);
}
@Test
void testGetElements_listIsNotModifiable() {
// arrange
final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
final List<LineConvexSubset> inputSegments = new ArrayList<>(Collections.singletonList(a));
// act
final LinePath path = LinePath.from(inputSegments);
inputSegments.clear();
// assert
Assertions.assertNotSame(inputSegments, path.getElements());
Assertions.assertEquals(1, path.getElements().size());
Assertions.assertThrows(UnsupportedOperationException.class, () -> path.getElements().add(a));
}
@Test
void testBoundaryStream() {
// arrange
final Segment seg = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
final LinePath path = LinePath.from(Collections.singletonList(seg));
// act
final List<LineConvexSubset> segments = path.boundaryStream().collect(Collectors.toList());
// assert
Assertions.assertEquals(1, segments.size());
Assertions.assertSame(seg, segments.get(0));
}
@Test
void testBoundaryStream_empty() {
// arrange
final LinePath path = LinePath.empty();
// act
final List<LineConvexSubset> segments = path.boundaryStream().collect(Collectors.toList());
// assert
Assertions.assertEquals(0, segments.size());
}
@Test
void testTransform_empty() {
// arrange
final LinePath path = LinePath.empty();
final AffineTransformMatrix2D t = AffineTransformMatrix2D.createTranslation(Vector2D.Unit.PLUS_X);
// act/assert
Assertions.assertSame(path, path.transform(t));
}
@Test
void testTransform_finite() {
// arrange
final LinePath path = LinePath.builder(TEST_PRECISION)
.append(Vector2D.Unit.ZERO)
.append(Vector2D.Unit.PLUS_X)
.append(Vector2D.Unit.PLUS_Y)
.close();
final AffineTransformMatrix2D t =
AffineTransformMatrix2D.createRotation(Vector2D.of(1, 1), Angle.PI_OVER_TWO);
// act
final LinePath result = path.transform(t);
// assert
Assertions.assertNotSame(path, result);
Assertions.assertTrue(result.isClosed());
Assertions.assertTrue(result.isFinite());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(3, segments.size());
assertFiniteSegment(segments.get(0), Vector2D.of(2, 0), Vector2D.of(2, 1));
assertFiniteSegment(segments.get(1), Vector2D.of(2, 1), Vector2D.Unit.PLUS_X);
assertFiniteSegment(segments.get(2), Vector2D.Unit.PLUS_X, Vector2D.of(2, 0));
}
@Test
void testTransform_infinite() {
// arrange
final LinePath path = LinePath.from(
Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION).span());
final AffineTransformMatrix2D t = AffineTransformMatrix2D.createTranslation(Vector2D.Unit.PLUS_X);
// act
final LinePath result = path.transform(t);
// assert
Assertions.assertNotSame(path, result);
Assertions.assertFalse(result.isClosed());
Assertions.assertFalse(result.isFinite());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(1, segments.size());
final LineConvexSubset segment = segments.get(0);
Assertions.assertTrue(segment.isInfinite());
Assertions.assertNull(segment.getStartPoint());
Assertions.assertNull(segment.getEndPoint());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X, segment.getLine().getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_Y, segment.getLine().getDirection(), TEST_EPS);
}
@Test
void testReverse_empty() {
// arrange
final LinePath path = LinePath.empty();
// act/assert
Assertions.assertSame(path, path.reverse());
}
@Test
void testReverse() {
// arrange
final LinePath path = LinePath.builder(TEST_PRECISION)
.append(Vector2D.Unit.ZERO)
.append(Vector2D.Unit.PLUS_X)
.append(Vector2D.Unit.PLUS_Y)
.close();
// act
final LinePath result = path.reverse();
// assert
Assertions.assertNotSame(path, result);
Assertions.assertTrue(result.isClosed());
Assertions.assertTrue(result.isFinite());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(3, segments.size());
assertFiniteSegment(segments.get(0), Vector2D.Unit.ZERO, Vector2D.Unit.PLUS_Y);
assertFiniteSegment(segments.get(1), Vector2D.Unit.PLUS_Y, Vector2D.Unit.PLUS_X);
assertFiniteSegment(segments.get(2), Vector2D.Unit.PLUS_X, Vector2D.Unit.ZERO);
}
@Test
void testReverse_singleInfinite() {
// arrange
final LinePath path = LinePath.from(
Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION).span());
// act
final LinePath result = path.reverse();
// assert
Assertions.assertNotSame(path, result);
Assertions.assertFalse(result.isClosed());
Assertions.assertFalse(result.isFinite());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(1, segments.size());
final LineConvexSubset segment = segments.get(0);
Assertions.assertTrue(segment.isInfinite());
Assertions.assertNull(segment.getStartPoint());
Assertions.assertNull(segment.getEndPoint());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.ZERO, segment.getLine().getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y, segment.getLine().getDirection(), TEST_EPS);
}
@Test
void testReverse_doubleInfinite() {
// arrange
final LineConvexSubset a = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION).reverseRayTo(Vector2D.ZERO);
final LineConvexSubset b = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION).rayFrom(Vector2D.ZERO);
final LinePath path = LinePath.from(a, b);
// act
final LinePath result = path.reverse();
// assert
Assertions.assertNotSame(path, result);
Assertions.assertFalse(result.isClosed());
Assertions.assertFalse(result.isFinite());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(2, segments.size());
final LineConvexSubset bResult = segments.get(0);
Assertions.assertTrue(bResult.isInfinite());
Assertions.assertNull(bResult.getStartPoint());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, bResult.getEndPoint(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, bResult.getLine().getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_X, bResult.getLine().getDirection(), TEST_EPS);
final LineConvexSubset aResult = segments.get(1);
Assertions.assertTrue(aResult.isInfinite());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, aResult.getStartPoint(), TEST_EPS);
Assertions.assertNull(aResult.getEndPoint());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, aResult.getLine().getOrigin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.MINUS_Y, aResult.getLine().getDirection(), TEST_EPS);
}
@Test
void testToTree() {
// arrange
final LinePath path = LinePath.builder(TEST_PRECISION)
.appendVertices(Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), Vector2D.of(0, 1))
.close();
// act
final RegionBSPTree2D tree = path.toTree();
// assert
Assertions.assertEquals(1, tree.getSize(), TEST_EPS);
Assertions.assertEquals(4, tree.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(RegionLocation.INSIDE, tree.classify(Vector2D.of(0.5, 0.5)));
Assertions.assertEquals(RegionLocation.OUTSIDE, tree.classify(Vector2D.of(0.5, -1)));
Assertions.assertEquals(RegionLocation.OUTSIDE, tree.classify(Vector2D.of(0.5, 2)));
Assertions.assertEquals(RegionLocation.OUTSIDE, tree.classify(Vector2D.of(-1, 0.5)));
Assertions.assertEquals(RegionLocation.OUTSIDE, tree.classify(Vector2D.of(2, 0.5)));
}
@Test
void testSimplify() {
// arrange
final Builder builder = LinePath.builder(TEST_PRECISION);
final LinePath path = builder.appendVertices(
Vector2D.of(-1, 0),
Vector2D.ZERO,
Vector2D.of(1, 0),
Vector2D.of(1, 1),
Vector2D.of(1, 2))
.build();
// act
final LinePath result = path.simplify();
// assert
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(2, segments.size());
assertFiniteSegment(segments.get(0), Vector2D.of(-1, 0), Vector2D.of(1, 0));
assertFiniteSegment(segments.get(1), Vector2D.of(1, 0), Vector2D.of(1, 2));
}
@Test
void testSimplify_startAndEndCombined() {
// arrange
final Builder builder = LinePath.builder(TEST_PRECISION);
final LinePath path = builder.appendVertices(
Vector2D.ZERO,
Vector2D.of(1, 0),
Vector2D.of(0, 1),
Vector2D.of(-1, 0))
.close();
// act
final LinePath result = path.simplify();
// assert
Assertions.assertNotSame(path, result);
Assertions.assertTrue(result.isClosed());
Assertions.assertFalse(result.isInfinite());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(3, segments.size());
assertFiniteSegment(segments.get(0), Vector2D.of(-1, 0), Vector2D.of(1, 0));
assertFiniteSegment(segments.get(1), Vector2D.of(1, 0), Vector2D.of(0, 1));
assertFiniteSegment(segments.get(2), Vector2D.of(0, 1), Vector2D.of(-1, 0));
}
@Test
void testSimplify_empty() {
// arrange
final Builder builder = LinePath.builder(TEST_PRECISION);
final LinePath path = builder.build();
// act
final LinePath result = path.simplify();
// assert
Assertions.assertNotSame(path, result);
Assertions.assertFalse(result.isClosed());
Assertions.assertFalse(result.isInfinite());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(0, segments.size());
}
@Test
void testSimplify_infiniteSegment() {
// arrange
final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
final Builder builder = LinePath.builder(TEST_PRECISION);
final LinePath path = builder
.append(line.span())
.build();
// act
final LinePath result = path.simplify();
// assert
Assertions.assertNotSame(path, result);
Assertions.assertFalse(result.isClosed());
Assertions.assertTrue(result.isInfinite());
Assertions.assertNotNull(path.getStart());
Assertions.assertNotNull(path.getEnd());
Assertions.assertSame(path.getStart(), path.getEnd());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(1, segments.size());
Assertions.assertSame(line, segments.get(0).getLine());
}
@Test
void testSimplify_combinedInfiniteSegment() {
// arrange
final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
final Split<LineConvexSubset> split = line.span().split(
Lines.fromPointAndAngle(Vector2D.ZERO, Angle.PI_OVER_TWO, TEST_PRECISION));
final Builder builder = LinePath.builder(TEST_PRECISION);
final LinePath path = builder
.append(split.getMinus())
.append(split.getPlus())
.build();
// act
final LinePath result = path.simplify();
// assert
Assertions.assertNotSame(path, result);
Assertions.assertFalse(result.isClosed());
Assertions.assertTrue(result.isInfinite());
Assertions.assertNotNull(result.getStart());
Assertions.assertNotNull(result.getEnd());
Assertions.assertSame(result.getStart(), result.getEnd());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(1, segments.size());
Assertions.assertSame(line, segments.get(0).getLine());
}
@Test
void testSimplify_startAndEndNotCombinedWhenNotClosed() {
// arrange
final Line xAxis = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
final Builder builder = LinePath.builder(TEST_PRECISION);
final LinePath path = builder
.append(xAxis.segment(0, 1))
.appendVertices(
Vector2D.of(2, 1),
Vector2D.of(3, 0))
.append(xAxis.segment(3, 4))
.build();
// act
final LinePath result = path.simplify();
// assert
Assertions.assertNotSame(path, result);
Assertions.assertFalse(result.isClosed());
Assertions.assertFalse(result.isInfinite());
final List<LineConvexSubset> segments = result.getElements();
Assertions.assertEquals(4, segments.size());
assertFiniteSegment(segments.get(0), Vector2D.ZERO, Vector2D.of(1, 0));
assertFiniteSegment(segments.get(1), Vector2D.of(1, 0), Vector2D.of(2, 1));
assertFiniteSegment(segments.get(2), Vector2D.of(2, 1), Vector2D.of(3, 0));
assertFiniteSegment(segments.get(3), Vector2D.of(3, 0), Vector2D.of(4, 0));
}
@Test
void testSimplify_subsequentCallsToReturnedObjectReturnSameObject() {
// arrange
final Builder builder = LinePath.builder(TEST_PRECISION);
final LinePath path = builder.appendVertices(
Vector2D.ZERO,
Vector2D.of(1, 0),
Vector2D.of(2, 0))
.build();
// act
final LinePath result = path.simplify();
// assert
Assertions.assertNotSame(path, result);
Assertions.assertSame(result, result.simplify());
}
@Test
void testLinecast_empty() {
// arrange
final LinePath path = LinePath.empty();
// act/assert
LinecastChecker2D.with(path)
.expectNothing()
.whenGiven(Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION));
LinecastChecker2D.with(path)
.expectNothing()
.whenGiven(Lines.segmentFromPoints(Vector2D.Unit.MINUS_X, Vector2D.Unit.PLUS_X, TEST_PRECISION));
}
@Test
void testLinecast() {
// arrange
final LinePath path = LinePath.fromVertexLoop(Arrays.asList(
Vector2D.ZERO, Vector2D.of(1, 0),
Vector2D.of(1, 1), Vector2D.of(0, 1)
), TEST_PRECISION);
// act/assert
LinecastChecker2D.with(path)
.expectNothing()
.whenGiven(Lines.fromPoints(Vector2D.of(0, 5), Vector2D.of(1, 6), TEST_PRECISION));
LinecastChecker2D.with(path)
.expect(Vector2D.ZERO, Vector2D.Unit.MINUS_X)
.and(Vector2D.ZERO, Vector2D.Unit.MINUS_Y)
.and(Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y)
.and(Vector2D.of(1, 1), Vector2D.Unit.PLUS_X)
.whenGiven(Lines.fromPoints(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION));
LinecastChecker2D.with(path)
.expect(Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y)
.and(Vector2D.of(1, 1), Vector2D.Unit.PLUS_X)
.whenGiven(Lines.segmentFromPoints(Vector2D.of(0.5, 0.5), Vector2D.of(1, 1), TEST_PRECISION));
}
@Test
void testToString() {
// arrange
final Line yAxis = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
final Line xAxis = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
final LinePath empty = LinePath.empty();
final LinePath singleFullSegment = LinePath.from(xAxis.span());
final LinePath singleFiniteSegment = LinePath.from(
Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION));
final LinePath startOpenPath = LinePath.builder(TEST_PRECISION)
.append(xAxis.reverseRayTo(Vector2D.Unit.PLUS_X))
.append(Vector2D.of(1, 1))
.build();
final LinePath endOpenPath = LinePath.builder(TEST_PRECISION)
.append(Vector2D.of(0, 1))
.append(Vector2D.ZERO)
.append(xAxis.rayFrom(Vector2D.ZERO))
.build();
final LinePath doubleOpenPath = LinePath.from(yAxis.reverseRayTo(Vector2D.ZERO),
xAxis.rayFrom(Vector2D.ZERO));
final LinePath nonOpenPath = LinePath.builder(TEST_PRECISION)
.append(Vector2D.ZERO)
.append(Vector2D.Unit.PLUS_X)
.append(Vector2D.of(1, 1))
.build();
// act/assert
final String emptyStr = empty.toString();
GeometryTestUtils.assertContains("LinePath[empty= true", emptyStr);
final String singleFullStr = singleFullSegment.toString();
GeometryTestUtils.assertContains("LinePath[single= LineSpanningSubset[", singleFullStr);
final String singleFiniteStr = singleFiniteSegment.toString();
GeometryTestUtils.assertContains("LinePath[single= Segment[", singleFiniteStr);
final String startOpenStr = startOpenPath.toString();
GeometryTestUtils.assertContains("LinePath[startDirection= ", startOpenStr);
GeometryTestUtils.assertContains("vertexSequence=", startOpenStr);
final String endOpenStr = endOpenPath.toString();
GeometryTestUtils.assertContains("LinePath[vertexSequence= ", endOpenStr);
GeometryTestUtils.assertContains("endDirection= ", endOpenStr);
final String doubleOpenStr = doubleOpenPath.toString();
GeometryTestUtils.assertContains("startDirection= ", doubleOpenStr);
GeometryTestUtils.assertContains("vertexSequence= ", doubleOpenStr);
GeometryTestUtils.assertContains("endDirection= ", doubleOpenStr);
final String nonOpenStr = nonOpenPath.toString();
GeometryTestUtils.assertContains("LinePath[vertexSequence= ", nonOpenStr);
}
@Test
void testBuilder_prependAndAppend_segments() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Vector2D p4 = Vector2D.of(1, 0);
final Segment a = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
final Segment b = Lines.segmentFromPoints(p2, p3, TEST_PRECISION);
final Segment c = Lines.segmentFromPoints(p3, p4, TEST_PRECISION);
final Segment d = Lines.segmentFromPoints(p4, p1, TEST_PRECISION);
final Builder builder = LinePath.builder(null);
// act
builder.prepend(b)
.append(c)
.prepend(a)
.append(d);
final LinePath path = builder.build();
// assert
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(4, segments.size());
Assertions.assertSame(a, segments.get(0));
Assertions.assertSame(b, segments.get(1));
Assertions.assertSame(c, segments.get(2));
Assertions.assertSame(d, segments.get(3));
}
@Test
void testBuilder_prependAndAppend_disconnectedSegments() {
// arrange
final Segment a = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
final Builder builder = LinePath.builder(null);
builder.append(a);
// act
Assertions.assertThrows(IllegalStateException.class, () -> builder.append(a));
Assertions.assertThrows(IllegalStateException.class, () -> builder.prepend(a));
}
@Test
void testBuilder_prependAndAppend_vertices() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Vector2D p4 = Vector2D.of(1, 0);
final Builder builder = LinePath.builder(TEST_PRECISION);
// act
builder.prepend(p2)
.append(p3)
.prepend(p1)
.append(p4)
.append(p1);
final LinePath path = builder.build();
// assert
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(4, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p4);
assertFiniteSegment(segments.get(3), p4, p1);
}
@Test
void testBuilder_prependAndAppend_noPrecisionSpecified() {
// arrange
final Vector2D p = Vector2D.ZERO;
final Builder builder = LinePath.builder(null);
final String msg = "Unable to create line segment: no vertex precision specified";
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
builder.append(p);
}, IllegalStateException.class, msg);
GeometryTestUtils.assertThrowsWithMessage(() -> {
builder.prepend(p);
}, IllegalStateException.class, msg);
}
@Test
void testBuilder_prependAndAppend_addingToInfinitePath() {
// arrange
final Vector2D p = Vector2D.Unit.PLUS_X;
final Builder builder = LinePath.builder(TEST_PRECISION);
builder.append(Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION).span());
// act/assert
Assertions.assertThrows(IllegalStateException.class, () -> builder.prepend(p));
Assertions.assertThrows(IllegalStateException.class, () -> builder.append(p));
}
@Test
void testBuilder_prependAndAppend_ignoresEquivalentVertices() {
// arrange
final Vector2D p = Vector2D.ZERO;
final Builder builder = LinePath.builder(TEST_PRECISION);
builder.append(p);
// act
builder.append(p)
.prepend(p)
.append(Vector2D.of(0, 1e-20))
.prepend(Vector2D.of(1e-20, 0));
builder.append(Vector2D.Unit.PLUS_X);
// assert
final LinePath path = builder.build();
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(1, segments.size());
assertFiniteSegment(segments.get(0), p, Vector2D.Unit.PLUS_X);
}
@Test
void testBuilder_prependAndAppend_mixedVerticesAndSegments() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Vector2D p4 = Vector2D.of(0, 1);
final Segment a = Lines.segmentFromPoints(p1, p2, TEST_PRECISION);
final Segment c = Lines.segmentFromPoints(p3, p4, TEST_PRECISION);
final Builder builder = LinePath.builder(TEST_PRECISION);
// act
builder.prepend(p2)
.append(p3)
.append(c)
.prepend(a)
.append(p1);
final LinePath path = builder.build();
// assert
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(4, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p4);
assertFiniteSegment(segments.get(3), p4, p1);
}
@Test
void testBuilder_appendVertices() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Vector2D p4 = Vector2D.of(0, 1);
final Builder builder = LinePath.builder(TEST_PRECISION);
// act
builder.appendVertices(p1, p2)
.appendVertices(Arrays.asList(p3, p4, p1));
final LinePath path = builder.build();
// assert
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(4, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p4);
assertFiniteSegment(segments.get(3), p4, p1);
}
@Test
void testBuilder_prependVertices() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Vector2D p4 = Vector2D.of(0, 1);
final Builder builder = LinePath.builder(TEST_PRECISION);
// act
builder.prependVertices(p3, p4, p1)
.prependVertices(Arrays.asList(p1, p2));
final LinePath path = builder.build();
// assert
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(4, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p4);
assertFiniteSegment(segments.get(3), p4, p1);
}
@Test
void testBuilder_close_notYetClosed() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Builder builder = LinePath.builder(TEST_PRECISION);
// act
builder.append(p1)
.append(p2)
.append(p3);
final LinePath path = builder.close();
// assert
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(3, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p1);
}
@Test
void testBuilder_close_alreadyClosed() {
// arrange
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(1, 0);
final Vector2D p3 = Vector2D.of(1, 1);
final Builder builder = LinePath.builder(TEST_PRECISION);
// act
builder.append(p1)
.append(p2)
.append(p3)
.append(p1);
final LinePath path = builder.close();
// assert
final List<LineConvexSubset> segments = path.getElements();
Assertions.assertEquals(3, segments.size());
assertFiniteSegment(segments.get(0), p1, p2);
assertFiniteSegment(segments.get(1), p2, p3);
assertFiniteSegment(segments.get(2), p3, p1);
}
@Test
void testBuilder_close_infiniteSegmentAtStart() {
// arrange
final Builder builder = LinePath.builder(TEST_PRECISION);
builder.append(Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION)
.reverseRayTo(1))
.append(Vector2D.of(1, 1));
// act/assert
GeometryTestUtils.assertThrowsWithMessage(builder::close, IllegalStateException.class,
"Unable to close line path: line path is infinite");
}
@Test
void testBuilder_close_infiniteSegmentAtEnd() {
// arrange
final Builder builder = LinePath.builder(TEST_PRECISION);
builder
.append(Vector2D.ZERO)
.append(Vector2D.Unit.PLUS_X)
.append(Lines.fromPointAndAngle(Vector2D.Unit.PLUS_X, Angle.PI_OVER_TWO, TEST_PRECISION)
.rayFrom(0));
// act/assert
GeometryTestUtils.assertThrowsWithMessage(builder::close, IllegalStateException.class,
"Unable to close line path: line path is infinite");
}
@Test
void testBuilder_close_emptyPath() {
// arrange
final Builder builder = LinePath.builder(TEST_PRECISION);
// act
final LinePath path = builder.close();
// assert
Assertions.assertEquals(0, path.getElements().size());
}
@Test
void testBuilder_close_obtuseTriangle() {
// arrange
final Builder builder = LinePath.builder(TEST_PRECISION);
builder.appendVertices(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(2, 1));
// act
final LinePath path = builder.close();
// assert
Assertions.assertEquals(3, path.getElements().size());
assertFiniteSegment(path.getElements().get(0), Vector2D.ZERO, Vector2D.of(1, 0));
assertFiniteSegment(path.getElements().get(1), Vector2D.of(1, 0), Vector2D.of(2, 1));
assertFiniteSegment(path.getElements().get(2), Vector2D.of(2, 1), Vector2D.ZERO);
}
private static void assertFiniteSegment(final LineConvexSubset segment, final Vector2D start, final Vector2D end) {
Assertions.assertFalse(segment.isInfinite());
Assertions.assertTrue(segment.isFinite());
EuclideanTestUtils.assertCoordinatesEqual(start, segment.getStartPoint(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(end, segment.getEndPoint(), TEST_EPS);
}
}