ConvexAreaTest.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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
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.core.partitioning.SplitLocation;
import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
import org.apache.commons.geometry.euclidean.twod.path.LinePath;
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 ConvexAreaTest {
private static final double TEST_EPS = 1e-10;
private static final Precision.DoubleEquivalence TEST_PRECISION =
Precision.doubleEquivalenceOfEpsilon(TEST_EPS);
@Test
void testFull() {
// act
final ConvexArea area = ConvexArea.full();
// assert
Assertions.assertTrue(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(0.0, area.getBoundarySize(), TEST_EPS);
GeometryTestUtils.assertPositiveInfinity(area.getSize());
Assertions.assertNull(area.getCentroid());
Assertions.assertNull(area.getBounds());
}
@Test
void testBoundaryStream() {
// arrange
final Line line = Lines.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION);
final ConvexArea area = ConvexArea.fromBounds(line);
// act
final List<LineConvexSubset> segments = area.boundaryStream().collect(Collectors.toList());
// assert
Assertions.assertEquals(1, segments.size());
final LineConvexSubset segment = segments.get(0);
Assertions.assertNull(segment.getStartPoint());
Assertions.assertNull(segment.getEndPoint());
Assertions.assertSame(line, segment.getLine());
}
@Test
void testBoundaryStream_full() {
// arrange
final ConvexArea area = ConvexArea.full();
// act
final List<LineConvexSubset> segments = area.boundaryStream().collect(Collectors.toList());
// assert
Assertions.assertEquals(0, segments.size());
}
@Test
void testToList() {
// arrange
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(0, 1)
), TEST_PRECISION);
// act
final BoundaryList2D list = area.toList();
// assert
Assertions.assertEquals(3, list.count());
Assertions.assertEquals(area.getBoundaries(), list.getBoundaries());
}
@Test
void testToList_full() {
// arrange
final ConvexArea area = ConvexArea.full();
// act
final BoundaryList2D list = area.toList();
// assert
Assertions.assertEquals(0, list.count());
}
@Test
void testToTree() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(
Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION),
Lines.fromPointAndAngle(Vector2D.of(1, 0), Angle.PI_OVER_TWO, TEST_PRECISION),
Lines.fromPointAndAngle(Vector2D.of(1, 1), Math.PI, TEST_PRECISION),
Lines.fromPointAndAngle(Vector2D.of(0, 1), -Angle.PI_OVER_TWO, TEST_PRECISION)
);
// act
final RegionBSPTree2D tree = area.toTree();
// assert
Assertions.assertFalse(tree.isFull());
Assertions.assertFalse(tree.isEmpty());
Assertions.assertEquals(1, tree.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), tree.getCentroid(), TEST_EPS);
}
@Test
void testToTree_full() {
// arrange
final ConvexArea area = ConvexArea.full();
// act
final RegionBSPTree2D tree = area.toTree();
// assert
Assertions.assertTrue(tree.isFull());
Assertions.assertFalse(tree.isEmpty());
}
@Test
void testTransform_full() {
// arrange
final AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(3);
final ConvexArea area = ConvexArea.full();
// act
final ConvexArea transformed = area.transform(transform);
// assert
Assertions.assertSame(area, transformed);
}
@Test
void testTransform_infinite() {
// arrange
final AffineTransformMatrix2D mat = AffineTransformMatrix2D
.createRotation(Vector2D.of(0, 1), Angle.PI_OVER_TWO)
.scale(Vector2D.of(3, 2));
final ConvexArea area = ConvexArea.fromBounds(
Lines.fromPointAndAngle(Vector2D.ZERO, 0.25 * Math.PI, TEST_PRECISION),
Lines.fromPointAndAngle(Vector2D.ZERO, -0.25 * Math.PI, TEST_PRECISION));
// act
final ConvexArea transformed = area.transform(mat);
// assert
Assertions.assertNotSame(area, transformed);
final List<LinePath> paths = transformed.getBoundaryPaths();
Assertions.assertEquals(1, paths.size());
final List<LineConvexSubset> segments = paths.get(0).getElements();
Assertions.assertEquals(2, segments.size());
final LineConvexSubset firstSegment = segments.get(0);
Assertions.assertNull(firstSegment.getStartPoint());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 2), firstSegment.getEndPoint(), TEST_EPS);
Assertions.assertEquals(Math.atan2(2, 3), firstSegment.getLine().getAngle(), TEST_EPS);
final LineConvexSubset secondSegment = segments.get(1);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 2), secondSegment.getStartPoint(), TEST_EPS);
Assertions.assertNull(secondSegment.getEndPoint());
Assertions.assertEquals(Math.atan2(2, -3), secondSegment.getLine().getAngle(), TEST_EPS);
}
@Test
void testTransform_finite() {
// arrange
final AffineTransformMatrix2D mat = AffineTransformMatrix2D.createScale(Vector2D.of(1, 2));
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.of(1, 1), Vector2D.of(2, 1),
Vector2D.of(2, 2), Vector2D.of(1, 2)
), TEST_PRECISION);
// act
final ConvexArea transformed = area.transform(mat);
// assert
Assertions.assertNotSame(area, transformed);
final List<LineConvexSubset> segments = transformed.getBoundaries();
Assertions.assertEquals(4, segments.size());
Assertions.assertEquals(2, transformed.getSize(), TEST_EPS);
Assertions.assertEquals(6, transformed.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1.5, 3), transformed.getCentroid(), TEST_EPS);
EuclideanTestUtils.assertRegionLocation(transformed, RegionLocation.BOUNDARY,
Vector2D.of(1, 2), Vector2D.of(2, 2), Vector2D.of(2, 4), Vector2D.of(1, 4));
EuclideanTestUtils.assertRegionLocation(transformed, RegionLocation.INSIDE, transformed.getCentroid());
}
@Test
void testTransform_finite_withSingleReflection() {
// arrange
final AffineTransformMatrix2D mat = AffineTransformMatrix2D.createScale(Vector2D.of(-1, 2));
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.of(1, 1), Vector2D.of(2, 1),
Vector2D.of(2, 2), Vector2D.of(1, 2)
), TEST_PRECISION);
// act
final ConvexArea transformed = area.transform(mat);
// assert
Assertions.assertNotSame(area, transformed);
final List<LineConvexSubset> segments = transformed.getBoundaries();
Assertions.assertEquals(4, segments.size());
Assertions.assertEquals(2, transformed.getSize(), TEST_EPS);
Assertions.assertEquals(6, transformed.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1.5, 3), transformed.getCentroid(), TEST_EPS);
EuclideanTestUtils.assertRegionLocation(transformed, RegionLocation.BOUNDARY,
Vector2D.of(-1, 2), Vector2D.of(-2, 2), Vector2D.of(-2, 4), Vector2D.of(-1, 4));
EuclideanTestUtils.assertRegionLocation(transformed, RegionLocation.INSIDE, transformed.getCentroid());
}
@Test
void testTransform_finite_withDoubleReflection() {
// arrange
final AffineTransformMatrix2D mat = AffineTransformMatrix2D.createScale(Vector2D.of(-1, -2));
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.of(1, 1), Vector2D.of(2, 1),
Vector2D.of(2, 2), Vector2D.of(1, 2)
), TEST_PRECISION);
// act
final ConvexArea transformed = area.transform(mat);
// assert
Assertions.assertNotSame(area, transformed);
final List<LineConvexSubset> segments = transformed.getBoundaries();
Assertions.assertEquals(4, segments.size());
Assertions.assertEquals(2, transformed.getSize(), TEST_EPS);
Assertions.assertEquals(6, transformed.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1.5, -3), transformed.getCentroid(), TEST_EPS);
EuclideanTestUtils.assertRegionLocation(transformed, RegionLocation.BOUNDARY,
Vector2D.of(-1, -2), Vector2D.of(-2, -2), Vector2D.of(-2, -4), Vector2D.of(-1, -4));
EuclideanTestUtils.assertRegionLocation(transformed, RegionLocation.INSIDE, transformed.getCentroid());
}
@Test
void testGetVertices_full() {
// arrange
final ConvexArea area = ConvexArea.full();
// act/assert
Assertions.assertEquals(0, area.getVertices().size());
}
@Test
void testGetVertices_twoParallelLines() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(
Lines.fromPointAndAngle(Vector2D.of(0, 1), Math.PI, TEST_PRECISION),
Lines.fromPointAndAngle(Vector2D.of(0, -1), 0.0, TEST_PRECISION)
);
// act/assert
Assertions.assertEquals(0, area.getVertices().size());
}
@Test
void testGetVertices_infiniteWithVertices() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(
Lines.fromPointAndAngle(Vector2D.of(0, 1), Math.PI, TEST_PRECISION),
Lines.fromPointAndAngle(Vector2D.of(0, -1), 0.0, TEST_PRECISION),
Lines.fromPointAndAngle(Vector2D.of(1, 0), Angle.PI_OVER_TWO, TEST_PRECISION)
);
// act
final List<Vector2D> vertices = area.getVertices();
// assert
Assertions.assertEquals(2, vertices.size());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, -1), vertices.get(0), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), vertices.get(1), TEST_EPS);
}
@Test
void testGetVertices_finite() {
// arrange
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.ZERO,
Vector2D.Unit.PLUS_X,
Vector2D.Unit.PLUS_Y
), TEST_PRECISION);
// act
final List<Vector2D> vertices = area.getVertices();
// assert
Assertions.assertEquals(3, vertices.size());
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, vertices.get(0), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_X, vertices.get(1), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.Unit.PLUS_Y, vertices.get(2), TEST_EPS);
}
@Test
void testGetVertices_mismatchedEndpoints() {
// This test checks the case where we have a valid set of boundary segments but
// with a small mismatch in the endpoints of some of the segments (possibly due
// to floating point errors).
// arrange
final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-2);
final Vector2D p1 = Vector2D.ZERO;
final Vector2D p2 = Vector2D.of(0.99, 0);
final Vector2D p3 = Vector2D.of(1, 0.002);
final Vector2D p4 = Vector2D.of(0.995, -0.001);
final Vector2D p5 = Vector2D.of(1, 1);
final ConvexArea area = new ConvexArea(Arrays.asList(
Lines.segmentFromPoints(p1, p2, precision),
Lines.segmentFromPoints(p2, p3, precision),
Lines.segmentFromPoints(p4, p5, precision),
Lines.segmentFromPoints(p5, p1, precision)
));
// act
final List<Vector2D> vertices = area.getVertices();
// assert
Assertions.assertEquals(Arrays.asList(p1, p2, p3, p5), vertices);
}
@Test
void testGetBounds_infinite() {
// act/assert
Assertions.assertNull(ConvexArea.full().getBounds());
Assertions.assertNull(ConvexArea.fromBounds(
Lines.fromPointAndAngle(Vector2D.ZERO, Angle.PI_OVER_TWO, TEST_PRECISION)).getBounds());
}
@Test
void testGetBounds_square() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.of(-1, -1), 2, 1));
// act
final Bounds2D bounds = area.getBounds();
// assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, -1), bounds.getMin(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 0), bounds.getMax(), TEST_EPS);
}
@Test
void testProject_full() {
// arrange
final ConvexArea area = ConvexArea.full();
// act/assert
Assertions.assertNull(area.project(Vector2D.ZERO));
Assertions.assertNull(area.project(Vector2D.Unit.PLUS_X));
}
@Test
void testProject_halfSpace() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(
Lines.fromPointAndAngle(Vector2D.ZERO, Angle.PI_OVER_TWO, TEST_PRECISION));
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 1), area.project(Vector2D.of(1, 1)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 2), area.project(Vector2D.of(-2, 2)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -3), area.project(Vector2D.of(1, -3)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, -4), area.project(Vector2D.of(-2, -4)), TEST_EPS);
}
@Test
void testProject_square() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.ZERO, 1, 1));
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), area.project(Vector2D.of(1, 1)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), area.project(Vector2D.of(2, 2)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, area.project(Vector2D.ZERO), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, area.project(Vector2D.of(-1, -1)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0, 0.5), area.project(Vector2D.of(0.1, 0.5)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.2, 1), area.project(Vector2D.of(0.2, 0.9)), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0), area.project(Vector2D.of(0.5, 0.5)), TEST_EPS);
}
@Test
void testTrim_full() {
// arrange
final ConvexArea area = ConvexArea.full();
final Segment segment = Lines.segmentFromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION);
// act
final LineConvexSubset trimmed = area.trim(segment);
// assert
Assertions.assertSame(segment, trimmed);
}
@Test
void testTrim_halfSpace() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION));
final LineConvexSubset segment = Lines.fromPoints(Vector2D.Unit.MINUS_Y, Vector2D.Unit.PLUS_Y, TEST_PRECISION).span();
// act
final LineConvexSubset trimmed = area.trim(segment);
// assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, trimmed.getStartPoint(), TEST_EPS);
GeometryTestUtils.assertPositiveInfinity(trimmed.getSubspaceEnd());
}
@Test
void testTrim_square() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.ZERO, 1, 1));
final LineConvexSubset segment = Lines.fromPoints(Vector2D.of(0.5, 0), Vector2D.of(0.5, 1), TEST_PRECISION).span();
// act
final LineConvexSubset trimmed = area.trim(segment);
// assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0), trimmed.getStartPoint(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 1), trimmed.getEndPoint(), TEST_EPS);
}
@Test
void testTrim_segmentOutsideOfRegion() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.ZERO, 1, 1));
final LineConvexSubset segment = Lines.fromPoints(Vector2D.of(-0.5, 0), Vector2D.of(-0.5, 1), TEST_PRECISION).span();
// act
final LineConvexSubset trimmed = area.trim(segment);
// assert
Assertions.assertNull(trimmed);
}
@Test
void testTrim_segmentDirectlyOnBoundaryOfRegion() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.ZERO, 1, 1));
final LineConvexSubset segment = Lines.fromPoints(Vector2D.of(1, 0), Vector2D.of(1, 1), TEST_PRECISION).span();
// act
final LineConvexSubset trimmed = area.trim(segment);
// assert
Assertions.assertNull(trimmed);
}
@Test
void testSplit_full() {
// arrange
final ConvexArea input = ConvexArea.full();
final Line splitter = Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION);
// act
final Split<ConvexArea> split = input.split(splitter);
// act
Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
final ConvexArea minus = split.getMinus();
Assertions.assertFalse(minus.isFull());
Assertions.assertFalse(minus.isEmpty());
GeometryTestUtils.assertPositiveInfinity(minus.getBoundarySize());
GeometryTestUtils.assertPositiveInfinity(minus.getSize());
Assertions.assertNull(minus.getCentroid());
final List<LineConvexSubset> minusSegments = minus.getBoundaries();
Assertions.assertEquals(1, minusSegments.size());
Assertions.assertEquals(splitter, minusSegments.get(0).getLine());
final ConvexArea plus = split.getPlus();
Assertions.assertFalse(plus.isFull());
Assertions.assertFalse(plus.isEmpty());
GeometryTestUtils.assertPositiveInfinity(plus.getBoundarySize());
GeometryTestUtils.assertPositiveInfinity(plus.getSize());
Assertions.assertNull(plus.getCentroid());
final List<LineConvexSubset> plusSegments = plus.getBoundaries();
Assertions.assertEquals(1, plusSegments.size());
Assertions.assertEquals(splitter, plusSegments.get(0).getLine().reverse());
}
@Test
void testSplit_halfSpace_split() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION));
final Line splitter = Lines.fromPointAndAngle(Vector2D.ZERO, 0.25 * Math.PI, TEST_PRECISION);
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
final ConvexArea minus = split.getMinus();
Assertions.assertFalse(minus.isFull());
Assertions.assertFalse(minus.isEmpty());
GeometryTestUtils.assertPositiveInfinity(minus.getBoundarySize());
GeometryTestUtils.assertPositiveInfinity(minus.getSize());
Assertions.assertNull(minus.getCentroid());
Assertions.assertEquals(2, minus.getBoundaries().size());
final ConvexArea plus = split.getPlus();
Assertions.assertFalse(plus.isFull());
Assertions.assertFalse(plus.isEmpty());
GeometryTestUtils.assertPositiveInfinity(plus.getBoundarySize());
GeometryTestUtils.assertPositiveInfinity(plus.getSize());
Assertions.assertNull(plus.getCentroid());
Assertions.assertEquals(2, plus.getBoundaries().size());
}
@Test
void testSplit_halfSpace_splitOnBoundary() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION));
final Line splitter = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION);
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
Assertions.assertSame(area, split.getMinus());
Assertions.assertNull(split.getPlus());
}
@Test
void testSplit_halfSpace_splitOnBoundaryWithReversedSplitter() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION));
final Line splitter = Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION).reverse();
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.PLUS, split.getLocation());
Assertions.assertNull(split.getMinus());
Assertions.assertSame(area, split.getPlus());
}
@Test
void testSplit_square_split() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.of(1, 1), 2, 1));
final Line splitter = Lines.fromPointAndAngle(Vector2D.of(2, 1), Angle.PI_OVER_TWO, TEST_PRECISION);
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
final ConvexArea minus = split.getMinus();
Assertions.assertFalse(minus.isFull());
Assertions.assertFalse(minus.isEmpty());
Assertions.assertEquals(4, minus.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(1, minus.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1.5, 1.5), minus.getCentroid(), TEST_EPS);
Assertions.assertEquals(4, minus.getBoundaries().size());
final ConvexArea plus = split.getPlus();
Assertions.assertFalse(plus.isFull());
Assertions.assertFalse(plus.isEmpty());
Assertions.assertEquals(4, plus.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(1, plus.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2.5, 1.5), plus.getCentroid(), TEST_EPS);
Assertions.assertEquals(4, plus.getBoundaries().size());
}
@Test
void testSplit_square_splitOnVertices() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.of(1, 1), 1, 1));
final Line splitter = Lines.fromPoints(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION);
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
final ConvexArea minus = split.getMinus();
Assertions.assertFalse(minus.isFull());
Assertions.assertFalse(minus.isEmpty());
Assertions.assertEquals(2 + Math.sqrt(2), minus.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(0.5, minus.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4.0 / 3.0, 5.0 / 3.0), minus.getCentroid(), TEST_EPS);
Assertions.assertEquals(3, minus.getBoundaries().size());
final ConvexArea plus = split.getPlus();
Assertions.assertFalse(plus.isFull());
Assertions.assertFalse(plus.isEmpty());
Assertions.assertEquals(2 + Math.sqrt(2), plus.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(0.5, plus.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(5.0 / 3.0, 4.0 / 3.0), plus.getCentroid(), TEST_EPS);
Assertions.assertEquals(3, plus.getBoundaries().size());
}
@Test
void testSplit_square_splitOnVerticesWithReversedSplitter() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.of(1, 1), 1, 1));
final Line splitter = Lines.fromPoints(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION).reverse();
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.BOTH, split.getLocation());
final ConvexArea minus = split.getMinus();
Assertions.assertFalse(minus.isFull());
Assertions.assertFalse(minus.isEmpty());
Assertions.assertEquals(2 + Math.sqrt(2), minus.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(0.5, minus.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(5.0 / 3.0, 4.0 / 3.0), minus.getCentroid(), TEST_EPS);
Assertions.assertEquals(3, minus.getBoundaries().size());
final ConvexArea plus = split.getPlus();
Assertions.assertFalse(plus.isFull());
Assertions.assertFalse(plus.isEmpty());
Assertions.assertEquals(2 + Math.sqrt(2), plus.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(0.5, plus.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4.0 / 3.0, 5.0 / 3.0), plus.getCentroid(), TEST_EPS);
Assertions.assertEquals(3, plus.getBoundaries().size());
}
@Test
void testSplit_square_entirelyOnMinus() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.of(1, 1), 1, 1));
final Line splitter = Lines.fromPoints(Vector2D.of(3, 1), Vector2D.of(3, 2), TEST_PRECISION);
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
Assertions.assertSame(area, split.getMinus());
Assertions.assertNull(split.getPlus());
}
@Test
void testSplit_square_onMinusBoundary() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.of(1, 1), 1, 1));
final Line splitter = Lines.fromPoints(Vector2D.of(2, 1), Vector2D.of(2, 2), TEST_PRECISION);
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
Assertions.assertSame(area, split.getMinus());
Assertions.assertNull(split.getPlus());
}
@Test
void testSplit_square_entirelyOnPlus() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.of(1, 1), 1, 1));
final Line splitter = Lines.fromPoints(Vector2D.of(0, 1), Vector2D.of(0, 2), TEST_PRECISION);
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.PLUS, split.getLocation());
Assertions.assertNull(split.getMinus());
Assertions.assertSame(area, split.getPlus());
}
@Test
void testSplit_square_onPlusBoundary() {
// arrange
final ConvexArea area = ConvexArea.fromBounds(createSquareBoundingLines(Vector2D.of(1, 1), 1, 1));
final Line splitter = Lines.fromPoints(Vector2D.of(1, 1), Vector2D.of(1, 2), TEST_PRECISION);
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.PLUS, split.getLocation());
Assertions.assertNull(split.getMinus());
Assertions.assertSame(area, split.getPlus());
}
@Test
void testSplit_fannedLines() {
// arrange
final Line a = Lines.fromPointAndDirection(
Vector2D.of(0.00600526260605261, -0.3392565140336253),
Vector2D.of(0.9998433697734339, 0.017698472253402094), TEST_PRECISION);
final Line b = Lines.fromPointAndDirection(
Vector2D.of(-0.05020576603061953, 1.7524758059156824),
Vector2D.of(0.9995898847600798, 0.02863672965494457), TEST_PRECISION);
final ConvexArea area = ConvexArea.fromBounds(a, b.reverse());
final Line splitter = Lines.fromPointAndDirection(
Vector2D.of(0.01581855191043128, -2.5270731411451215),
Vector2D.of(0.999980409069402, 0.006259510954681248), TEST_PRECISION);
// act
final Split<ConvexArea> split = area.split(splitter);
// assert
Assertions.assertEquals(SplitLocation.MINUS, split.getLocation());
Assertions.assertSame(area, split.getMinus());
Assertions.assertNull(split.getPlus());
}
@Test
void testSplit_trimmedSplitterDiscrepancy() {
// The following example came from a failed invocation of the Sphere.toTree() method.
// This test checks the case where the splitter trimmed to the area is non-empty but
// the boundaries split by the splitter all lies on a single side.
// arrange
final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-10);
final Vector2D p1 = Vector2D.of(-100.27622744776312, -39.236143934478704);
final Vector2D p2 = Vector2D.of(-100.23149336840831, -39.28090397981739);
final Vector2D p3 = Vector2D.of(-96.28607710958399, -39.25486984391497);
final ConvexArea area = ConvexArea.fromBounds(
Lines.fromPointAndDirection(p1, Vector2D.of(-0.00601644753700725, -0.9999819010157307), precision),
Lines.fromPoints(p1, p2, precision),
Lines.fromPoints(p2, p3, precision),
Lines.fromPointAndDirection(p3, Vector2D.of(0.9999648811047153, 0.008380725340508379), precision)
);
final Line splitter = Lines.fromPointAndDirection(
Vector2D.of(-68.9981806624852, -70.04669274578112),
Vector2D.of(0.7124186895479748, -0.7017546656651072),
precision);
// act
final Split<ConvexArea> minusSplit = area.split(splitter);
final Split<ConvexArea> plusSplit = area.split(splitter.reverse());
// assert
Assertions.assertEquals(SplitLocation.MINUS, minusSplit.getLocation());
Assertions.assertSame(area, minusSplit.getMinus());
Assertions.assertNull(minusSplit.getPlus());
Assertions.assertEquals(SplitLocation.PLUS, plusSplit.getLocation());
Assertions.assertNull(plusSplit.getMinus());
Assertions.assertSame(area, plusSplit.getPlus());
}
@Test
void testLinecast_full() {
// arrange
final ConvexArea area = ConvexArea.full();
// act/assert
LinecastChecker2D.with(area)
.expectNothing()
.whenGiven(Lines.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION));
LinecastChecker2D.with(area)
.expectNothing()
.whenGiven(Lines.segmentFromPoints(Vector2D.Unit.MINUS_X, Vector2D.Unit.PLUS_X, TEST_PRECISION));
}
@Test
void testLinecast() {
// arrange
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.ZERO, Vector2D.of(1, 0),
Vector2D.of(1, 1), Vector2D.of(0, 1)
), TEST_PRECISION);
// act/assert
LinecastChecker2D.with(area)
.expectNothing()
.whenGiven(Lines.fromPoints(Vector2D.of(0, 5), Vector2D.of(1, 6), TEST_PRECISION));
LinecastChecker2D.with(area)
.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(area)
.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 ConvexArea area = ConvexArea.full();
// act
final String str = area.toString();
// assert
Assertions.assertTrue(str.contains("ConvexArea"));
Assertions.assertTrue(str.contains("boundaries= "));
}
@Test
void testConvexPolygonFromVertices_notEnoughUniqueVertices() {
// arrange
final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(1e-3);
final Pattern unclosedPattern = Pattern.compile("Cannot construct convex polygon from unclosed path.*");
final Pattern notEnoughElementsPattern =
Pattern.compile("Cannot construct convex polygon from path with less than 3 elements.*");
final Pattern nonConvexPattern = Pattern.compile("Cannot construct convex polygon from non-convex path.*");
final Pattern singleVertexPattern =
Pattern.compile("Unable to create line path; only a single unique vertex provided.*");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(Collections.emptyList(), precision);
}, IllegalArgumentException.class, unclosedPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(Collections.singletonList(Vector2D.ZERO), precision);
}, IllegalStateException.class, singleVertexPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(Arrays.asList(Vector2D.ZERO, Vector2D.of(1e-4, 1e-4)), precision);
}, IllegalStateException.class, singleVertexPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(Arrays.asList(Vector2D.ZERO, Vector2D.Unit.PLUS_X), precision);
}, IllegalArgumentException.class, notEnoughElementsPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(
Arrays.asList(Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.of(1, 1e-4)), precision);
}, IllegalArgumentException.class, notEnoughElementsPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(
Arrays.asList(Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.of(1, -1)), precision);
}, IllegalArgumentException.class, nonConvexPattern);
}
@Test
void testConvexPolygonFromVertices_triangle() {
// arrange
final Vector2D p0 = Vector2D.of(1, 2);
final Vector2D p1 = Vector2D.of(2, 2);
final Vector2D p2 = Vector2D.of(2, 3);
// act
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(p0, p1, p2), TEST_PRECISION);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(0.5, area.getSize(), TEST_EPS);
Assertions.assertEquals(2 + Math.sqrt(2), area.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.centroid(p0, p1, p2), area.getCentroid(), TEST_EPS);
}
@Test
void testConvexPolygonFromVertices_square_closeRequired() {
// act
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.ZERO,
Vector2D.Unit.PLUS_X,
Vector2D.of(1, 1),
Vector2D.of(0, 1)
), TEST_PRECISION);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(1, area.getSize(), TEST_EPS);
Assertions.assertEquals(4, area.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), area.getCentroid(), TEST_EPS);
}
@Test
void testConvexPolygonFromVertices_square_closeNotRequired() {
// act
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.ZERO,
Vector2D.Unit.PLUS_X,
Vector2D.of(1, 1),
Vector2D.of(0, 1),
Vector2D.ZERO
), TEST_PRECISION);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(1, area.getSize(), TEST_EPS);
Assertions.assertEquals(4, area.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), area.getCentroid(), TEST_EPS);
}
@Test
void testConvexPolygonFromVertices_handlesDuplicatePoints() {
// arrange
final double eps = 1e-3;
final Precision.DoubleEquivalence precision = Precision.doubleEquivalenceOfEpsilon(eps);
// act
final ConvexArea area = ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.ZERO,
Vector2D.of(1e-4, 1e-4),
Vector2D.Unit.PLUS_X,
Vector2D.of(1, 1e-4),
Vector2D.of(1, 1),
Vector2D.of(0, 1),
Vector2D.of(1e-4, 1),
Vector2D.of(1e-4, 1e-4)
), precision);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(1, area.getSize(), eps);
Assertions.assertEquals(4, area.getBoundarySize(), eps);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), area.getCentroid(), eps);
}
@Test
void testConvexPolygonFromPath() {
// act
final ConvexArea area = ConvexArea.convexPolygonFromPath(LinePath.fromVertexLoop(
Arrays.asList(
Vector2D.ZERO,
Vector2D.Unit.PLUS_X,
Vector2D.of(1, 1),
Vector2D.Unit.PLUS_Y
), TEST_PRECISION));
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(1, area.getSize(), TEST_EPS);
Assertions.assertEquals(4, area.getBoundarySize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), area.getCentroid(), TEST_EPS);
}
@Test
void testConvexPolygonFromVertices_notConvex() {
// arrange
final Pattern msgPattern = Pattern.compile("Cannot construct convex polygon from non-convex path.*");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(2, 0)
), TEST_PRECISION);
}, IllegalArgumentException.class, msgPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(1, -1)
), TEST_PRECISION);
}, IllegalArgumentException.class, msgPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(
Arrays.asList(
Vector2D.ZERO,
Vector2D.Unit.PLUS_Y,
Vector2D.of(1, 1),
Vector2D.Unit.PLUS_X
), TEST_PRECISION);
}, IllegalArgumentException.class, msgPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromVertices(Arrays.asList(
Vector2D.ZERO, Vector2D.of(2, 0),
Vector2D.of(2, 2), Vector2D.of(1, 1),
Vector2D.of(1.5, 1)
), TEST_PRECISION);
}, IllegalArgumentException.class, msgPattern);
}
@Test
void testConvexPolygonFromPath_invalidPaths() {
// arrange
final Pattern unclosedPattern = Pattern.compile("Cannot construct convex polygon from unclosed path.*");
final Pattern notEnoughElementsPattern =
Pattern.compile("Cannot construct convex polygon from path with less than 3 elements.*");
final Pattern nonConvexPattern = Pattern.compile("Cannot construct convex polygon from non-convex path.*");
// act/assert
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromPath(LinePath.empty());
}, IllegalArgumentException.class, unclosedPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromPath(LinePath.fromVertices(
Arrays.asList(Vector2D.ZERO, Vector2D.Unit.PLUS_X), TEST_PRECISION));
}, IllegalArgumentException.class, unclosedPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromPath(LinePath.fromVertices(
Arrays.asList(Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.ZERO), TEST_PRECISION));
}, IllegalArgumentException.class, notEnoughElementsPattern);
GeometryTestUtils.assertThrowsWithMessage(() -> {
ConvexArea.convexPolygonFromPath(LinePath.fromVertexLoop(
Arrays.asList(
Vector2D.ZERO,
Vector2D.Unit.PLUS_Y,
Vector2D.of(1, 1),
Vector2D.Unit.PLUS_X
), TEST_PRECISION));
}, IllegalArgumentException.class, nonConvexPattern);
}
@Test
void testFromBounds_noLines() {
// act
final ConvexArea area = ConvexArea.fromBounds(Collections.emptyList());
// assert
Assertions.assertSame(ConvexArea.full(), area);
}
@Test
void testFromBounds_singleLine() {
// arrange
final Line line = Lines.fromPoints(Vector2D.of(0, 1), Vector2D.of(1, 3), TEST_PRECISION);
// act
final ConvexArea area = ConvexArea.fromBounds(line);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
GeometryTestUtils.assertPositiveInfinity(area.getBoundarySize());
GeometryTestUtils.assertPositiveInfinity(area.getSize());
Assertions.assertNull(area.getCentroid());
final List<LineConvexSubset> segments = area.getBoundaries();
Assertions.assertEquals(1, segments.size());
Assertions.assertSame(line, segments.get(0).getLine());
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.INSIDE, Vector2D.of(-1, 1), Vector2D.of(0, 2));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.BOUNDARY, Vector2D.of(0, 1), Vector2D.of(2, 5));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.OUTSIDE, Vector2D.ZERO, Vector2D.of(2, 3));
}
@Test
void testFromBounds_twoLines() {
// arrange
final Line a = Lines.fromPointAndAngle(Vector2D.ZERO, Angle.PI_OVER_TWO, TEST_PRECISION);
final Line b = Lines.fromPointAndAngle(Vector2D.ZERO, Math.PI, TEST_PRECISION);
// act
final ConvexArea area = ConvexArea.fromBounds(a, b);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
GeometryTestUtils.assertPositiveInfinity(area.getBoundarySize());
GeometryTestUtils.assertPositiveInfinity(area.getSize());
Assertions.assertNull(area.getCentroid());
final List<LineConvexSubset> segments = area.getBoundaries();
Assertions.assertEquals(2, segments.size());
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.INSIDE, Vector2D.of(-1, -1));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.BOUNDARY,
Vector2D.ZERO, Vector2D.of(-1, 0), Vector2D.of(0, -1));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.OUTSIDE,
Vector2D.of(-1, 1), Vector2D.of(1, 1), Vector2D.of(1, -1));
}
@Test
void testFromBounds_triangle() {
// arrange
final Line a = Lines.fromPointAndAngle(Vector2D.ZERO, Angle.PI_OVER_TWO, TEST_PRECISION);
final Line b = Lines.fromPointAndAngle(Vector2D.ZERO, Math.PI, TEST_PRECISION);
final Line c = Lines.fromPointAndAngle(Vector2D.of(-2, 0), -0.25 * Math.PI, TEST_PRECISION);
// act
final ConvexArea area = ConvexArea.fromBounds(a, b, c);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(4 + (2 * Math.sqrt(2)), area.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(2, area.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2.0 / 3.0, -2.0 / 3.0), area.getCentroid(), TEST_EPS);
final List<LineConvexSubset> segments = area.getBoundaries();
Assertions.assertEquals(3, segments.size());
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.INSIDE, Vector2D.of(-0.5, -0.5));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.BOUNDARY,
Vector2D.ZERO, Vector2D.of(-1, 0), Vector2D.of(0, -1));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.OUTSIDE,
Vector2D.of(-1, 1), Vector2D.of(1, 1), Vector2D.of(1, -1), Vector2D.of(-2, -2));
}
@Test
void testFromBounds_square() {
// arrange
final List<Line> square = createSquareBoundingLines(Vector2D.ZERO, 1, 1);
// act
final ConvexArea area = ConvexArea.fromBounds(square);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(4, area.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(1, area.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), area.getCentroid(), TEST_EPS);
final List<LineConvexSubset> segments = area.getBoundaries();
Assertions.assertEquals(4, segments.size());
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.INSIDE, Vector2D.of(0.5, 0.5));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.BOUNDARY,
Vector2D.ZERO, Vector2D.of(1, 1),
Vector2D.of(0.5, 0), Vector2D.of(0.5, 1),
Vector2D.of(0, 0.5), Vector2D.of(1, 0.5));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.OUTSIDE,
Vector2D.of(-1, -1), Vector2D.of(2, 2));
}
@Test
void testFromBounds_square_extraLines() {
// arrange
final List<Line> extraLines = new ArrayList<>();
extraLines.add(Lines.fromPoints(Vector2D.of(10, 10), Vector2D.of(10, 11), TEST_PRECISION));
extraLines.add(Lines.fromPoints(Vector2D.of(-10, 10), Vector2D.of(-10, 9), TEST_PRECISION));
extraLines.add(Lines.fromPoints(Vector2D.of(0, 10), Vector2D.of(-1, 11), TEST_PRECISION));
extraLines.addAll(createSquareBoundingLines(Vector2D.ZERO, 1, 1));
// act
final ConvexArea area = ConvexArea.fromBounds(extraLines);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(4, area.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(1, area.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), area.getCentroid(), TEST_EPS);
final List<LineConvexSubset> segments = area.getBoundaries();
Assertions.assertEquals(4, segments.size());
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.INSIDE, Vector2D.of(0.5, 0.5));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.BOUNDARY,
Vector2D.ZERO, Vector2D.of(1, 1),
Vector2D.of(0.5, 0), Vector2D.of(0.5, 1),
Vector2D.of(0, 0.5), Vector2D.of(1, 0.5));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.OUTSIDE,
Vector2D.of(-1, -1), Vector2D.of(2, 2));
}
@Test
void testFromBounds_square_duplicateLines() {
// arrange
final List<Line> duplicateLines = new ArrayList<>();
duplicateLines.addAll(createSquareBoundingLines(Vector2D.ZERO, 1, 1));
duplicateLines.addAll(createSquareBoundingLines(Vector2D.ZERO, 1, 1));
// act
final ConvexArea area = ConvexArea.fromBounds(duplicateLines);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
Assertions.assertEquals(4, area.getBoundarySize(), TEST_EPS);
Assertions.assertEquals(1, area.getSize(), TEST_EPS);
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), area.getCentroid(), TEST_EPS);
final List<LineConvexSubset> segments = area.getBoundaries();
Assertions.assertEquals(4, segments.size());
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.INSIDE, Vector2D.of(0.5, 0.5));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.BOUNDARY,
Vector2D.ZERO, Vector2D.of(1, 1),
Vector2D.of(0.5, 0), Vector2D.of(0.5, 1),
Vector2D.of(0, 0.5), Vector2D.of(1, 0.5));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.OUTSIDE,
Vector2D.of(-1, -1), Vector2D.of(2, 2));
}
@Test
void testFromBounds_duplicateLines_similarOrientation() {
// arrange
final Line a = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
final Line b = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
final Line c = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
// act
final ConvexArea area = ConvexArea.fromBounds(a, b, c);
// assert
Assertions.assertFalse(area.isFull());
Assertions.assertFalse(area.isEmpty());
GeometryTestUtils.assertPositiveInfinity(area.getBoundarySize());
GeometryTestUtils.assertPositiveInfinity(area.getSize());
Assertions.assertNull(area.getCentroid());
final List<LineConvexSubset> segments = area.getBoundaries();
Assertions.assertEquals(1, segments.size());
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.BOUNDARY, Vector2D.of(0, 1), Vector2D.of(1, 1), Vector2D.of(-1, 1));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.INSIDE, Vector2D.of(0, 2), Vector2D.of(1, 2), Vector2D.of(-1, 2));
EuclideanTestUtils.assertRegionLocation(area, RegionLocation.OUTSIDE, Vector2D.of(0, 0), Vector2D.of(1, 0), Vector2D.of(-1, 0));
}
@Test
void testFromBounds_duplicateLines_differentOrientation() {
// arrange
final Line a = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
final Line b = Lines.fromPointAndAngle(Vector2D.of(0, 1), Math.PI, TEST_PRECISION);
final Line c = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
// act/assert
Assertions.assertThrows(IllegalArgumentException.class, () -> ConvexArea.fromBounds(a, b, c));
}
@Test
void testFromBounds_boundsDoNotProduceAConvexRegion() {
// act/assert
Assertions.assertThrows(IllegalArgumentException.class, () -> ConvexArea.fromBounds(Arrays.asList(
Lines.fromPointAndAngle(Vector2D.ZERO, 0.0, TEST_PRECISION),
Lines.fromPointAndAngle(Vector2D.of(0, -1), Math.PI, TEST_PRECISION),
Lines.fromPointAndAngle(Vector2D.ZERO, Angle.PI_OVER_TWO, TEST_PRECISION)
)));
}
private static List<Line> createSquareBoundingLines(final Vector2D lowerLeft, final double width, final double height) {
final Vector2D lowerRight = Vector2D.of(lowerLeft.getX() + width, lowerLeft.getY());
final Vector2D upperRight = Vector2D.of(lowerLeft.getX() + width, lowerLeft.getY() + height);
final Vector2D upperLeft = Vector2D.of(lowerLeft.getX(), lowerLeft.getY() + height);
return Arrays.asList(
Lines.fromPoints(lowerLeft, lowerRight, TEST_PRECISION),
Lines.fromPoints(upperRight, upperLeft, TEST_PRECISION),
Lines.fromPoints(lowerRight, upperRight, TEST_PRECISION),
Lines.fromPoints(upperLeft, lowerLeft, TEST_PRECISION)
);
}
}