blob: 32471b79022c11d5c9fd68adc8878e61be2c02a3 [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.core.partitioning.bsp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partition.test.PartitionTestUtils;
import org.apache.commons.geometry.core.partition.test.TestLine;
import org.apache.commons.geometry.core.partition.test.TestLineSegment;
import org.apache.commons.geometry.core.partition.test.TestLineSegmentCollection;
import org.apache.commons.geometry.core.partition.test.TestPoint2D;
import org.apache.commons.geometry.core.partition.test.TestTransform2D;
import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SplitLocation;
import org.apache.commons.geometry.core.partitioning.SubHyperplane;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree.AbstractRegionNode;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree.RegionSizeProperties;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class AbstractRegionBSPTreeTest {
private TestRegionBSPTree tree;
private TestRegionNode root;
@Before
public void setup() {
tree = new TestRegionBSPTree();
root = tree.getRoot();
}
@Test
public void testDefaultConstructor() {
// assert
Assert.assertNotNull(root);
Assert.assertNull(root.getParent());
PartitionTestUtils.assertIsLeafNode(root);
Assert.assertFalse(root.isPlus());
Assert.assertFalse(root.isMinus());
Assert.assertSame(tree, root.getTree());
Assert.assertEquals(RegionLocation.INSIDE, root.getLocation());
}
@Test
public void testParameterizedConstructor_true() {
// act
tree = new TestRegionBSPTree(true);
root = tree.getRoot();
// assert
Assert.assertNotNull(root);
Assert.assertNull(root.getParent());
PartitionTestUtils.assertIsLeafNode(root);
Assert.assertFalse(root.isPlus());
Assert.assertFalse(root.isMinus());
Assert.assertSame(tree, root.getTree());
Assert.assertEquals(RegionLocation.INSIDE, root.getLocation());
}
@Test
public void testParameterizedConstructor_false() {
// act
tree = new TestRegionBSPTree(false);
root = tree.getRoot();
// assert
Assert.assertNotNull(root);
Assert.assertNull(root.getParent());
PartitionTestUtils.assertIsLeafNode(root);
Assert.assertFalse(root.isPlus());
Assert.assertFalse(root.isMinus());
Assert.assertSame(tree, root.getTree());
Assert.assertEquals(RegionLocation.OUTSIDE, root.getLocation());
}
@Test
public void testGetLocation_emptyRoot() {
// act/assert
Assert.assertEquals(RegionLocation.INSIDE, root.getLocation());
}
@Test
public void testGetLocation_singleCut() {
// arrange
root.insertCut(TestLine.X_AXIS);
// act/assert
Assert.assertNull(root.getLocation());
Assert.assertEquals(RegionLocation.INSIDE, root.getMinus().getLocation());
Assert.assertEquals(RegionLocation.OUTSIDE, root.getPlus().getLocation());
}
@Test
public void testGetLocation_multipleCuts() {
// arrange
tree.insert(Arrays.asList(
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(1, 0)),
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, 1)),
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, -1))));
// act/assert
Assert.assertNull(root.getLocation());
TestRegionNode plus = root.getPlus();
Assert.assertNull(plus.getLocation());
TestRegionNode plusPlus = plus.getPlus();
Assert.assertEquals(RegionLocation.OUTSIDE, plusPlus.getLocation());
TestRegionNode plusMinus = plus.getMinus();
Assert.assertEquals(RegionLocation.INSIDE, plusMinus.getLocation());
TestRegionNode minus = root.getMinus();
Assert.assertNull(minus.getLocation());
TestRegionNode minusPlus = minus.getPlus();
Assert.assertEquals(RegionLocation.OUTSIDE, minusPlus.getLocation());
TestRegionNode minusMinus = minus.getMinus();
Assert.assertEquals(RegionLocation.INSIDE, minusMinus.getLocation());
}
@Test
public void testGetLocation_resetsLocationWhenNodeCleared() {
// arrange
tree.insert(Arrays.asList(
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(1, 0)),
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, 1)),
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, -1))));
// act
root.getPlus().clearCut();
root.getMinus().clearCut();
// assert
Assert.assertNull(root.getLocation());
Assert.assertEquals(RegionLocation.INSIDE, root.getMinus().getLocation());
Assert.assertEquals(RegionLocation.OUTSIDE, root.getPlus().getLocation());
}
@Test
public void testGetLocation_resetRoot() {
// arrange
tree.insert(Arrays.asList(
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(1, 0)),
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, 1)),
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, -1))));
TestRegionNode root = tree.getRoot();
// act
root.clearCut();
// assert
Assert.assertEquals(RegionLocation.INSIDE, root.getLocation());
}
@Test
public void testBoundaries_fullAndEmpty() {
// act/assert
tree.setFull();
Assert.assertFalse(tree.boundaries().iterator().hasNext());
tree.setEmpty();
Assert.assertFalse(tree.boundaries().iterator().hasNext());
}
@Test
public void testBoundaries_finite() {
// arrange
insertBox(tree, new TestPoint2D(0, 1), new TestPoint2D(1, 0));
// act
List<TestLineSegment> segments = new ArrayList<>();
for (ConvexSubHyperplane<TestPoint2D> sub : tree.boundaries()) {
segments.add((TestLineSegment) sub);
}
// assert
Assert.assertEquals(4, segments.size());
assertContainsSegment(segments, new TestPoint2D(0, 0), new TestPoint2D(1, 0));
assertContainsSegment(segments, new TestPoint2D(1, 0), new TestPoint2D(1, 1));
assertContainsSegment(segments, new TestPoint2D(1, 1), new TestPoint2D(0, 1));
assertContainsSegment(segments, new TestPoint2D(0, 1), new TestPoint2D(0, 0));
}
@Test
public void testBoundaries_finite_inverted() {
// arrange
insertBox(tree, new TestPoint2D(0, 1), new TestPoint2D(1, 0));
tree.complement();
// act
List<TestLineSegment> segments = new ArrayList<>();
for (ConvexSubHyperplane<TestPoint2D> sub : tree.boundaries()) {
segments.add((TestLineSegment) sub);
}
// assert
Assert.assertEquals(4, segments.size());
assertContainsSegment(segments, new TestPoint2D(0, 0), new TestPoint2D(0, 1));
assertContainsSegment(segments, new TestPoint2D(0, 1), new TestPoint2D(1, 1));
assertContainsSegment(segments, new TestPoint2D(1, 1), new TestPoint2D(1, 0));
assertContainsSegment(segments, new TestPoint2D(1, 0), new TestPoint2D(0, 0));
}
@Test
public void testGetBoundaries_fullAndEmpty() {
// act/assert
tree.setFull();
Assert.assertEquals(0, tree.getBoundaries().size());
tree.setEmpty();
Assert.assertEquals(0, tree.getBoundaries().size());
}
@Test
public void testGetBoundaries_finite() {
// arrange
insertBox(tree, new TestPoint2D(0, 1), new TestPoint2D(1, 0));
// act
List<TestLineSegment> segments = new ArrayList<>();
for (ConvexSubHyperplane<TestPoint2D> sub : tree.getBoundaries()) {
segments.add((TestLineSegment) sub);
}
// assert
Assert.assertEquals(4, segments.size());
assertContainsSegment(segments, new TestPoint2D(0, 0), new TestPoint2D(1, 0));
assertContainsSegment(segments, new TestPoint2D(1, 0), new TestPoint2D(1, 1));
assertContainsSegment(segments, new TestPoint2D(1, 1), new TestPoint2D(0, 1));
assertContainsSegment(segments, new TestPoint2D(0, 1), new TestPoint2D(0, 0));
}
@Test
public void testGetBoundaries_finite_inverted() {
// arrange
insertBox(tree, new TestPoint2D(0, 1), new TestPoint2D(1, 0));
tree.complement();
// act
List<TestLineSegment> segments = new ArrayList<>();
for (ConvexSubHyperplane<TestPoint2D> sub : tree.getBoundaries()) {
segments.add((TestLineSegment) sub);
}
// assert
Assert.assertEquals(4, segments.size());
assertContainsSegment(segments, new TestPoint2D(0, 0), new TestPoint2D(0, 1));
assertContainsSegment(segments, new TestPoint2D(0, 1), new TestPoint2D(1, 1));
assertContainsSegment(segments, new TestPoint2D(1, 1), new TestPoint2D(1, 0));
assertContainsSegment(segments, new TestPoint2D(1, 0), new TestPoint2D(0, 0));
}
@Test
public void testClassify() {
// arrange
insertSkewedBowtie(tree);
// act/assert
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(3, 1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(-3, -1)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(-3, 1)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(3, -1)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(4, 5)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-4, -5)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(5, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(4, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(3, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(2, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(1, 0)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(0, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-1, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-2, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-3, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-4, 0)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(-5, 0)));
}
@Test
public void testClassify_emptyTree() {
// act/assert
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(TestPoint2D.ZERO));
}
@Test
public void testClassify_NaN() {
// act/assert
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(0, Double.NaN)));
}
@Test
public void testContains() {
// arrange
insertSkewedBowtie(tree);
// act/assert
Assert.assertTrue(tree.contains(new TestPoint2D(3, 1)));
Assert.assertTrue(tree.contains(new TestPoint2D(-3, -1)));
Assert.assertFalse(tree.contains(new TestPoint2D(-3, 1)));
Assert.assertFalse(tree.contains(new TestPoint2D(3, -1)));
Assert.assertTrue(tree.contains(new TestPoint2D(4, 5)));
Assert.assertTrue(tree.contains(new TestPoint2D(-4, -5)));
Assert.assertFalse(tree.contains(new TestPoint2D(5, 0)));
Assert.assertTrue(tree.contains(new TestPoint2D(4, 0)));
Assert.assertTrue(tree.contains(new TestPoint2D(3, 0)));
Assert.assertTrue(tree.contains(new TestPoint2D(2, 0)));
Assert.assertTrue(tree.contains(new TestPoint2D(1, 0)));
Assert.assertTrue(tree.contains(new TestPoint2D(0, 0)));
Assert.assertTrue(tree.contains(new TestPoint2D(-1, 0)));
Assert.assertTrue(tree.contains(new TestPoint2D(-2, 0)));
Assert.assertTrue(tree.contains(new TestPoint2D(-3, 0)));
Assert.assertTrue(tree.contains(new TestPoint2D(-4, 0)));
Assert.assertFalse(tree.contains(new TestPoint2D(-5, 0)));
}
@Test
public void testSetFull() {
// arrange
insertSkewedBowtie(tree);
// act
tree.setFull();
// assert
Assert.assertTrue(tree.isFull());
Assert.assertFalse(tree.isEmpty());
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(TestPoint2D.ZERO));
Assert.assertTrue(tree.contains(TestPoint2D.ZERO));
}
@Test
public void testSetEmpty() {
// arrange
insertSkewedBowtie(tree);
// act
tree.setEmpty();
// assert
Assert.assertFalse(tree.isFull());
Assert.assertTrue(tree.isEmpty());
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(TestPoint2D.ZERO));
Assert.assertFalse(tree.contains(TestPoint2D.ZERO));
}
@Test
public void testGetRegionSizeProperties_cachesValueBasedOnVersion() {
// act
RegionSizeProperties<TestPoint2D> first = tree.getRegionSizeProperties();
RegionSizeProperties<TestPoint2D> second = tree.getRegionSizeProperties();
tree.getRoot().cut(TestLine.X_AXIS);
RegionSizeProperties<TestPoint2D> third = tree.getRegionSizeProperties();
// assert
Assert.assertSame(first, second);
Assert.assertNotSame(second, third);
Assert.assertEquals(1, first.getSize(), PartitionTestUtils.EPS);
Assert.assertSame(TestPoint2D.ZERO, first.getBarycenter());
}
@Test
public void testGetSize() {
// act/assert
// make sure our stub value is pulled
Assert.assertEquals(1, tree.getSize(), PartitionTestUtils.EPS);
}
@Test
public void testGetBarycenter() {
// act/assert
// make sure our stub value is pulled
Assert.assertSame(TestPoint2D.ZERO, tree.getBarycenter());
}
@Test
public void testGetBoundarySize_fullAndEmpty() {
// act/assert
Assert.assertEquals(0.0, fullTree().getBoundarySize(), PartitionTestUtils.EPS);
Assert.assertEquals(0.0, emptyTree().getBoundarySize(), PartitionTestUtils.EPS);
}
@Test
public void testGetBoundarySize_infinite() {
// arrange
TestRegionBSPTree halfPos = new TestRegionBSPTree(true);
halfPos.getRoot().cut(TestLine.X_AXIS);
TestRegionBSPTree halfPosComplement = new TestRegionBSPTree(true);
halfPosComplement.complement(halfPos);
// act/assert
Assert.assertEquals(Double.POSITIVE_INFINITY, halfPos.getBoundarySize(), PartitionTestUtils.EPS);
Assert.assertEquals(Double.POSITIVE_INFINITY, halfPosComplement.getBoundarySize(), PartitionTestUtils.EPS);
}
@Test
public void testGetBoundarySize_alignedCuts() {
// arrange
TestPoint2D p0 = TestPoint2D.ZERO;
TestPoint2D p1 = new TestPoint2D(0, 3);
TestRegionNode node = tree.getRoot();
tree.cutNode(node, new TestLineSegment(p0, p1));
node = node.getMinus();
tree.cutNode(node, new TestLineSegment(0, 0, new TestLine(p1, new TestPoint2D(-1, 3))));
node = node.getMinus();
tree.cutNode(node, new TestLineSegment(p1, p0));
node = node.getMinus();
tree.cutNode(node, new TestLineSegment(0, 0, new TestLine(p0, new TestPoint2D(1, 3))));
// act/assert
Assert.assertEquals(6, tree.getBoundarySize(), PartitionTestUtils.EPS);
}
@Test
public void testGetBoundarySize_box() {
// arrange
insertBox(tree, new TestPoint2D(2, 2), new TestPoint2D(4, 1));
// act/assert
Assert.assertEquals(6.0, tree.getBoundarySize(), PartitionTestUtils.EPS);
}
@Test
public void testGetBoundarySize_boxComplement() {
// arrange
insertBox(tree, new TestPoint2D(2, 2), new TestPoint2D(4, 1));
tree.complement();
// act/assert
Assert.assertEquals(6.0, tree.getBoundarySize(), PartitionTestUtils.EPS);
}
@Test
public void testGetBoundarySize_recomputesAfterChange() {
// arrange
insertBox(tree, new TestPoint2D(2, 2), new TestPoint2D(4, 1));
// act
double first = tree.getBoundarySize();
tree.insert(new TestLineSegment(new TestPoint2D(3, 1), new TestPoint2D(3, 2)));
double second = tree.getBoundarySize();
double third = tree.getBoundarySize();
// assert
Assert.assertEquals(6.0, first, PartitionTestUtils.EPS);
Assert.assertEquals(4.0, second, PartitionTestUtils.EPS);
Assert.assertEquals(4.0, third, PartitionTestUtils.EPS);
}
@Test
public void testGetCutBoundary_emptyTree() {
// act
RegionCutBoundary<TestPoint2D> boundary = root.getCutBoundary();
// assert
Assert.assertNull(boundary);
}
@Test
public void tetsGetCutBoundary_singleCut() {
// arrange
tree.insert(new TestLineSegment(new TestPoint2D(0, 0), new TestPoint2D(1, 0)));
// act
RegionCutBoundary<TestPoint2D> boundary = root.getCutBoundary();
// assert
Assert.assertTrue(boundary.getInsideFacing().isEmpty());
assertCutBoundarySegment(boundary.getOutsideFacing(),
new TestPoint2D(Double.NEGATIVE_INFINITY, 0.0), new TestPoint2D(Double.POSITIVE_INFINITY, 0.0));
}
@Test
public void tetsGetCutBoundary_singleCut_leafNode() {
// arrange
tree.insert(new TestLineSegment(new TestPoint2D(0, 0), new TestPoint2D(1, 0)));
// act
RegionCutBoundary<TestPoint2D> boundary = root.getMinus().getCutBoundary();
// assert
Assert.assertNull(boundary);
}
@Test
public void tetsGetCutBoundary_singleCorner() {
// arrange
tree.insert(new TestLineSegment(new TestPoint2D(-1, 0), new TestPoint2D(1, 0)));
tree.insert(new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, 1)));
// act/assert
RegionCutBoundary<TestPoint2D> rootBoundary = root.getCutBoundary();
Assert.assertTrue(rootBoundary.getInsideFacing().isEmpty());
assertCutBoundarySegment(rootBoundary.getOutsideFacing(),
new TestPoint2D(Double.NEGATIVE_INFINITY, 0.0), TestPoint2D.ZERO);
RegionCutBoundary<TestPoint2D> childBoundary = tree.getRoot().getMinus().getCutBoundary();
Assert.assertTrue(childBoundary.getInsideFacing().isEmpty());
assertCutBoundarySegment(childBoundary.getOutsideFacing(),
TestPoint2D.ZERO, new TestPoint2D(0.0, Double.POSITIVE_INFINITY));
}
@Test
public void tetsGetCutBoundary_leafNode() {
// arrange
tree.insert(new TestLineSegment(new TestPoint2D(-1, 0), new TestPoint2D(1, 0)));
tree.insert(new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, 1)));
// act/assert
Assert.assertNull(root.getPlus().getCutBoundary());
Assert.assertNull(root.getMinus().getMinus().getCutHyperplane());
Assert.assertNull(root.getMinus().getPlus().getCutHyperplane());
}
@Test
public void testFullEmpty_fullTree() {
// act/assert
Assert.assertTrue(tree.isFull());
Assert.assertFalse(tree.isEmpty());
Assert.assertEquals(RegionLocation.INSIDE, tree.getRoot().getLocation());
}
@Test
public void testFullEmpty_emptyTree() {
// arrange
tree.complement();
// act/assert
Assert.assertFalse(tree.isFull());
Assert.assertTrue(tree.isEmpty());
Assert.assertEquals(RegionLocation.OUTSIDE, tree.getRoot().getLocation());
}
@Test
public void testTransform_noCuts() {
// arrange
Transform<TestPoint2D> t = new TestTransform2D(p -> new TestPoint2D(p.getX(), p.getY() + 2));
// act
tree.transform(t);
// assert
Assert.assertTrue(tree.isFull());
Assert.assertFalse(tree.isEmpty());
assertPointLocations(tree, RegionLocation.INSIDE, TestPoint2D.ZERO);
}
@Test
public void testTransform_singleCut() {
// arrange
tree.getRoot().insertCut(TestLine.X_AXIS);
Transform<TestPoint2D> t = new TestTransform2D(p -> new TestPoint2D(p.getX(), p.getY() + 2));
// act
tree.transform(t);
// assert
Assert.assertFalse(tree.isFull());
Assert.assertFalse(tree.isEmpty());
assertPointLocations(tree, RegionLocation.OUTSIDE,
new TestPoint2D(0, -1), TestPoint2D.ZERO, new TestPoint2D(0, 1));
assertPointLocations(tree, RegionLocation.BOUNDARY, new TestPoint2D(0, 2));
assertPointLocations(tree, RegionLocation.INSIDE,
new TestPoint2D(0, 3), new TestPoint2D(0, 4));
}
@Test
public void testTransform_multipleCuts() {
// arrange
insertSkewedBowtie(tree);
Transform<TestPoint2D> t = new TestTransform2D(p -> new TestPoint2D(0.5 * p.getX(), p.getY() + 5));
// act
tree.transform(t);
// assert
Assert.assertFalse(tree.isFull());
Assert.assertFalse(tree.isEmpty());
assertPointLocations(tree, RegionLocation.INSIDE,
new TestPoint2D(0, 5), new TestPoint2D(-1, 4), new TestPoint2D(1, 6));
assertPointLocations(tree, RegionLocation.BOUNDARY,
new TestPoint2D(-2, 4), new TestPoint2D(2, 6));
assertPointLocations(tree, RegionLocation.OUTSIDE,
new TestPoint2D(-3, 5), new TestPoint2D(3, 5));
}
@Test
public void testTransform_xAxisReflection() {
// arrange
insertSkewedBowtie(tree);
Transform<TestPoint2D> t = new TestTransform2D(p -> new TestPoint2D(-p.getX(), p.getY()));
// act
tree.transform(t);
// assert
Assert.assertFalse(tree.isFull());
Assert.assertFalse(tree.isEmpty());
assertPointLocations(tree, RegionLocation.INSIDE,
TestPoint2D.ZERO, new TestPoint2D(-1, 1), new TestPoint2D(1, -1));
assertPointLocations(tree, RegionLocation.BOUNDARY,
new TestPoint2D(0, 1), new TestPoint2D(0, -1),
new TestPoint2D(-4, 0), new TestPoint2D(4, 0));
assertPointLocations(tree, RegionLocation.OUTSIDE,
new TestPoint2D(1, 1), new TestPoint2D(-1, -1));
}
@Test
public void testTransform_yAxisReflection() {
// arrange
insertSkewedBowtie(tree);
Transform<TestPoint2D> t = new TestTransform2D(p -> new TestPoint2D(p.getX(), -p.getY()));
// act
tree.transform(t);
// assert
Assert.assertFalse(tree.isFull());
Assert.assertFalse(tree.isEmpty());
assertPointLocations(tree, RegionLocation.INSIDE,
TestPoint2D.ZERO, new TestPoint2D(1, -1), new TestPoint2D(-1, 1));
assertPointLocations(tree, RegionLocation.BOUNDARY,
new TestPoint2D(0, 1), new TestPoint2D(0, -1),
new TestPoint2D(-4, 0), new TestPoint2D(4, 0));
assertPointLocations(tree, RegionLocation.OUTSIDE,
new TestPoint2D(-1, -1), new TestPoint2D(1, 1));
}
@Test
public void testTransform_xAndYAxisReflection() {
// arrange
insertSkewedBowtie(tree);
Transform<TestPoint2D> t = new TestTransform2D(p -> new TestPoint2D(-p.getX(), -p.getY()));
// act
tree.transform(t);
// assert
Assert.assertFalse(tree.isFull());
Assert.assertFalse(tree.isEmpty());
assertPointLocations(tree, RegionLocation.INSIDE,
TestPoint2D.ZERO, new TestPoint2D(1, 1), new TestPoint2D(-1, -1));
assertPointLocations(tree, RegionLocation.BOUNDARY,
new TestPoint2D(0, 1), new TestPoint2D(0, -1),
new TestPoint2D(-4, 0), new TestPoint2D(4, 0));
assertPointLocations(tree, RegionLocation.OUTSIDE,
new TestPoint2D(-1, 1), new TestPoint2D(1, -1));
}
@Test
public void testTransform_resetsCutBoundary() {
// arrange
insertSkewedBowtie(tree);
TestRegionNode node = tree.findNode(new TestPoint2D(1, 1)).getParent();
Transform<TestPoint2D> t = new TestTransform2D(p -> new TestPoint2D(0.5 * p.getX(), p.getY() + 5));
// act
RegionCutBoundary<TestPoint2D> origBoundary = node.getCutBoundary();
tree.transform(t);
RegionCutBoundary<TestPoint2D> resultBoundary = node.getCutBoundary();
// assert
Assert.assertNotSame(origBoundary, resultBoundary);
assertCutBoundarySegment(origBoundary.getOutsideFacing(), new TestPoint2D(4, 5), new TestPoint2D(-1, 0));
assertCutBoundarySegment(resultBoundary.getOutsideFacing(), new TestPoint2D(2, 10), new TestPoint2D(-0.5, 5));
}
@Test
public void testComplement_rootOnly() {
// act
tree.complement();
// assert
Assert.assertTrue(tree.isEmpty());
Assert.assertFalse(tree.isFull());
Assert.assertEquals(RegionLocation.OUTSIDE, root.getLocation());
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(TestPoint2D.ZERO));
}
@Test
public void testComplement_singleCut() {
// arrange
root.insertCut(TestLine.X_AXIS);
// act
tree.complement();
// assert
Assert.assertFalse(tree.isEmpty());
Assert.assertFalse(tree.isFull());
Assert.assertEquals(RegionLocation.OUTSIDE, root.getMinus().getLocation());
Assert.assertEquals(RegionLocation.INSIDE, root.getPlus().getLocation());
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(0, 1)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(TestPoint2D.ZERO));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(0, -1)));
}
@Test
public void testComplement_skewedBowtie() {
// arrange
insertSkewedBowtie(tree);
// act
tree.complement();
// assert
Assert.assertFalse(tree.isEmpty());
Assert.assertFalse(tree.isFull());
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(3, 1)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(-3, -1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(-3, 1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(3, -1)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(4, 5)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-4, -5)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(5, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(4, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(3, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(2, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(1, 0)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(0, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-1, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-2, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-3, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-4, 0)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(-5, 0)));
}
@Test
public void testComplement_addCutAfterComplement() {
// arrange
tree.insert(new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(1, 0)));
tree.complement();
// act
tree.insert(new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, 1)));
// assert
Assert.assertFalse(tree.isEmpty());
Assert.assertFalse(tree.isFull());
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(TestPoint2D.ZERO));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(1, 1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(-1, 1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(1, -1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(-1, -1)));
}
@Test
public void testComplement_clearCutAfterComplement() {
// arrange
tree.insert(Arrays.asList(
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(1, 0)),
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, 1))
));
tree.complement();
// act
root.getMinus().clearCut();
// assert
Assert.assertFalse(tree.isEmpty());
Assert.assertFalse(tree.isFull());
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(TestPoint2D.ZERO));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(1, 1)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(-1, 1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(1, -1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(-1, -1)));
}
@Test
public void testComplement_clearRootAfterComplement() {
// arrange
tree.insert(Arrays.asList(
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(1, 0)),
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, 1))
));
tree.complement();
// act
root.clearCut();
// assert
Assert.assertTrue(tree.isEmpty());
Assert.assertFalse(tree.isFull());
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(TestPoint2D.ZERO));
}
@Test
public void testComplement_isReversible_root() {
// act
tree.complement();
tree.complement();
// assert
Assert.assertFalse(tree.isEmpty());
Assert.assertTrue(tree.isFull());
Assert.assertEquals(RegionLocation.INSIDE, root.getLocation());
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(TestPoint2D.ZERO));
}
@Test
public void testComplement_isReversible_skewedBowtie() {
// arrange
insertSkewedBowtie(tree);
// act
tree.complement();
tree.complement();
// assert
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(3, 1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(-3, -1)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(-3, 1)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(3, -1)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(4, 5)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-4, -5)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(5, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(4, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(3, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(2, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(1, 0)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(0, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-1, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-2, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-3, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, tree.classify(new TestPoint2D(-4, 0)));
Assert.assertEquals(RegionLocation.OUTSIDE, tree.classify(new TestPoint2D(-5, 0)));
}
@Test
public void testComplement_getCutBoundary() {
// arrange
tree.insert(Arrays.asList(
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(1, 0)),
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(0, 1))));
tree.complement();
// act
RegionCutBoundary<TestPoint2D> xAxisBoundary = root.getCutBoundary();
RegionCutBoundary<TestPoint2D> yAxisBoundary = root.getMinus().getCutBoundary();
// assert
Assert.assertTrue(xAxisBoundary.getOutsideFacing().isEmpty());
Assert.assertFalse(xAxisBoundary.getInsideFacing().isEmpty());
TestLineSegmentCollection xAxisInsideFacing = (TestLineSegmentCollection) xAxisBoundary.getInsideFacing();
Assert.assertEquals(1, xAxisInsideFacing.getLineSegments().size());
TestLineSegment xAxisSeg = xAxisInsideFacing.getLineSegments().get(0);
PartitionTestUtils.assertPointsEqual(new TestPoint2D(Double.NEGATIVE_INFINITY, 0), xAxisSeg.getStartPoint());
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, xAxisSeg.getEndPoint());
Assert.assertTrue(yAxisBoundary.getOutsideFacing().isEmpty());
Assert.assertFalse(yAxisBoundary.getInsideFacing().isEmpty());
TestLineSegmentCollection yAxisInsideFacing = (TestLineSegmentCollection) yAxisBoundary.getInsideFacing();
Assert.assertEquals(1, yAxisInsideFacing.getLineSegments().size());
TestLineSegment yAxisSeg = yAxisInsideFacing.getLineSegments().get(0);
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, yAxisSeg.getStartPoint());
PartitionTestUtils.assertPointsEqual(new TestPoint2D(0, Double.POSITIVE_INFINITY), yAxisSeg.getEndPoint());
}
@Test
public void testComplementOf_rootOnly() {
// arrange
TestRegionBSPTree other = fullTree();
insertSkewedBowtie(other);
// act
other.complement(tree);
// assert
Assert.assertFalse(tree.isEmpty());
Assert.assertTrue(tree.isFull());
Assert.assertEquals(RegionLocation.INSIDE, root.getLocation());
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(TestPoint2D.ZERO));
Assert.assertTrue(other.isEmpty());
Assert.assertFalse(other.isFull());
Assert.assertEquals(RegionLocation.OUTSIDE, other.getRoot().getLocation());
Assert.assertEquals(RegionLocation.OUTSIDE, other.classify(TestPoint2D.ZERO));
}
@Test
public void testComplementOf_skewedBowtie() {
// arrange
insertSkewedBowtie(tree);
TestRegionBSPTree other = fullTree();
// act
other.complement(tree);
// assert
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(3, 1)));
Assert.assertEquals(RegionLocation.INSIDE, tree.classify(new TestPoint2D(-3, -1)));
Assert.assertFalse(other.isEmpty());
Assert.assertFalse(other.isFull());
Assert.assertEquals(RegionLocation.OUTSIDE, other.classify(new TestPoint2D(3, 1)));
Assert.assertEquals(RegionLocation.OUTSIDE, other.classify(new TestPoint2D(-3, -1)));
Assert.assertEquals(RegionLocation.INSIDE, other.classify(new TestPoint2D(-3, 1)));
Assert.assertEquals(RegionLocation.INSIDE, other.classify(new TestPoint2D(3, -1)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(4, 5)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(-4, -5)));
Assert.assertEquals(RegionLocation.INSIDE, other.classify(new TestPoint2D(5, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(4, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(3, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(2, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(1, 0)));
Assert.assertEquals(RegionLocation.OUTSIDE, other.classify(new TestPoint2D(0, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(-1, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(-2, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(-3, 0)));
Assert.assertEquals(RegionLocation.BOUNDARY, other.classify(new TestPoint2D(-4, 0)));
Assert.assertEquals(RegionLocation.INSIDE, other.classify(new TestPoint2D(-5, 0)));
}
@Test
public void testCopy() {
// arrange
insertSkewedBowtie(tree);
// act
TestRegionBSPTree copy = fullTree();
copy.copy(tree);
// assert
Assert.assertNotSame(tree, copy);
Assert.assertEquals(tree.count(), copy.count());
List<RegionLocation> origLocations = new ArrayList<>();
tree.forEach(n -> origLocations.add(n.getLocationValue()));
List<RegionLocation> copyLocations = new ArrayList<>();
copy.forEach(n -> copyLocations.add(n.getLocationValue()));
Assert.assertEquals(origLocations, copyLocations);
}
@Test
public void testExtract() {
// arrange
insertSkewedBowtie(tree);
TestRegionBSPTree result = fullTree();
TestPoint2D pt = new TestPoint2D(2, 2);
// act
result.extract(tree.findNode(pt));
// assert
assertPointLocations(result, RegionLocation.INSIDE,
new TestPoint2D(0, 0.5), new TestPoint2D(2, 2));
assertPointLocations(result, RegionLocation.OUTSIDE,
new TestPoint2D(-2, 2),
new TestPoint2D(-2, -2), new TestPoint2D(0, -0.5), new TestPoint2D(-2, 2));
assertPointLocations(tree, RegionLocation.INSIDE,
new TestPoint2D(0, 0.5), new TestPoint2D(2, 2),
new TestPoint2D(-2, -2), new TestPoint2D(0, -0.5));
assertPointLocations(tree, RegionLocation.OUTSIDE,
new TestPoint2D(2, -2), new TestPoint2D(-2, 2));
}
@Test
public void testExtract_complementedTree() {
// arrange
insertSkewedBowtie(tree);
tree.complement();
TestRegionBSPTree result = fullTree();
TestPoint2D pt = new TestPoint2D(2, 2);
// act
result.extract(tree.findNode(pt));
// assert
assertPointLocations(result, RegionLocation.OUTSIDE,
new TestPoint2D(0, 0.5), new TestPoint2D(2, 2));
assertPointLocations(result, RegionLocation.INSIDE,
new TestPoint2D(-2, 2),
new TestPoint2D(-2, -2), new TestPoint2D(0, -0.5), new TestPoint2D(-2, 2));
assertPointLocations(tree, RegionLocation.OUTSIDE,
new TestPoint2D(0, 0.5), new TestPoint2D(2, 2),
new TestPoint2D(-2, -2), new TestPoint2D(0, -0.5));
assertPointLocations(tree, RegionLocation.INSIDE,
new TestPoint2D(2, -2), new TestPoint2D(-2, 2));
}
@Test
public void testUnion_singleNodeTrees() {
// act/assert
unionChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::emptyTree)
.full(false)
.empty(true)
.count(1)
.check();
unionChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::emptyTree)
.full(true)
.empty(false)
.count(1)
.check();
unionChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::fullTree)
.full(true)
.empty(false)
.count(1)
.check();
unionChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::fullTree)
.full(true)
.empty(false)
.count(1)
.check();
}
@Test
public void testUnion_simpleCrossingCuts() {
// act/assert
unionChecker(AbstractRegionBSPTreeTest::xAxisTree, AbstractRegionBSPTreeTest::emptyTree)
.full(false)
.empty(false)
.count(3)
.check();
unionChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::xAxisTree)
.full(false)
.empty(false)
.count(3)
.check();
unionChecker(AbstractRegionBSPTreeTest::yAxisTree, AbstractRegionBSPTreeTest::fullTree)
.full(true)
.empty(false)
.count(1)
.check();
unionChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::yAxisTree)
.full(true)
.empty(false)
.count(1)
.check();
unionChecker(AbstractRegionBSPTreeTest::xAxisTree, AbstractRegionBSPTreeTest::yAxisTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(1, 1), new TestPoint2D(-1, 1), new TestPoint2D(-1, -1))
.outside(new TestPoint2D(1, -1))
.boundary(TestPoint2D.ZERO)
.count(5)
.check(tree -> {
TestLineSegment seg = (TestLineSegment) tree.getRoot().getPlus().getCut();
PartitionTestUtils.assertPointsEqual(new TestPoint2D(0.0, Double.NEGATIVE_INFINITY), seg.getStartPoint());
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, seg.getEndPoint());
});
}
@Test
public void testUnion_boxTreeWithSingleCutTree() {
// arrange
Supplier<TestRegionBSPTree> boxFactory = () -> {
TestRegionBSPTree box = fullTree();
insertBox(box, TestPoint2D.ZERO, new TestPoint2D(2, -4));
return box;
};
Supplier<TestRegionBSPTree> horizonalFactory = () -> {
TestRegionBSPTree horizontal = fullTree();
horizontal.getRoot().insertCut(new TestLine(new TestPoint2D(2, 2), new TestPoint2D(0, 2)));
return horizontal;
};
// act/assert
unionChecker(horizonalFactory, boxFactory)
.count(3)
.inside(TestPoint2D.ZERO, new TestPoint2D(1, 1), new TestPoint2D(1, -1))
.outside(new TestPoint2D(0, 3), new TestPoint2D(3, 3))
.boundary(new TestPoint2D(-1, 2), new TestPoint2D(3, 2))
.check();
}
@Test
public void testUnion_treeWithComplement() {
// arrange
Supplier<TestRegionBSPTree> treeFactory = () -> {
TestRegionBSPTree tree = fullTree();
insertSkewedBowtie(tree);
return tree;
};
Supplier<TestRegionBSPTree> complementFactory = () -> {
TestRegionBSPTree tree = treeFactory.get();
tree.complement();
return tree;
};
// act/assert
unionChecker(treeFactory, complementFactory)
.full(true)
.empty(false)
.count(1)
.check();
}
@Test
public void testIntersection_singleNodeTrees() {
// act/assert
intersectionChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::emptyTree)
.full(false)
.empty(true)
.count(1)
.check();
intersectionChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::emptyTree)
.full(false)
.empty(true)
.count(1)
.check();
intersectionChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::fullTree)
.full(false)
.empty(true)
.count(1)
.check();
intersectionChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::fullTree)
.full(true)
.empty(false)
.count(1)
.check();
}
@Test
public void testIntersection_simpleCrossingCuts() {
// act/assert
intersectionChecker(AbstractRegionBSPTreeTest::xAxisTree, AbstractRegionBSPTreeTest::emptyTree)
.full(false)
.empty(true)
.count(1)
.check();
intersectionChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::xAxisTree)
.full(false)
.empty(true)
.count(1)
.check();
intersectionChecker(AbstractRegionBSPTreeTest::yAxisTree, AbstractRegionBSPTreeTest::fullTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(-1, 1), new TestPoint2D(-1, -1))
.outside(new TestPoint2D(1, 1), new TestPoint2D(1, -1))
.boundary(new TestPoint2D(0, 1), new TestPoint2D(0, -1))
.count(3)
.check();
intersectionChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::yAxisTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(-1, 1), new TestPoint2D(-1, -1))
.outside(new TestPoint2D(1, 1), new TestPoint2D(1, -1))
.boundary(new TestPoint2D(0, 1), new TestPoint2D(0, -1))
.count(3)
.check();
intersectionChecker(AbstractRegionBSPTreeTest::xAxisTree, AbstractRegionBSPTreeTest::yAxisTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(-1, 1))
.outside(new TestPoint2D(1, 1), new TestPoint2D(1, -1), new TestPoint2D(-1, -1))
.boundary(TestPoint2D.ZERO)
.count(5)
.check(tree -> {
TestLineSegment seg = (TestLineSegment) tree.getRoot().getMinus().getCut();
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, seg.getStartPoint());
PartitionTestUtils.assertPointsEqual(new TestPoint2D(0.0, Double.POSITIVE_INFINITY), seg.getEndPoint());
});
}
@Test
public void testIntersection_boxTreeWithSingleCutTree() {
// arrange
Supplier<TestRegionBSPTree> boxFactory = () -> {
TestRegionBSPTree box = fullTree();
insertBox(box, TestPoint2D.ZERO, new TestPoint2D(2, -4));
return box;
};
Supplier<TestRegionBSPTree> horizonalFactory = () -> {
TestRegionBSPTree horizontal = fullTree();
horizontal.getRoot().insertCut(new TestLine(new TestPoint2D(2, -2), new TestPoint2D(0, -2)));
return horizontal;
};
// act/assert
intersectionChecker(horizonalFactory, boxFactory)
.inside(new TestPoint2D(1, -3))
.outside(new TestPoint2D(1, -1), new TestPoint2D(-1, -3),
new TestPoint2D(1, -5), new TestPoint2D(3, -3))
.boundary(new TestPoint2D(0, -2), new TestPoint2D(2, -2),
new TestPoint2D(0, -4), new TestPoint2D(2, -4))
.count(9)
.check();
}
@Test
public void testIntersection_treeWithComplement() {
// arrange
Supplier<TestRegionBSPTree> treeFactory = () -> {
TestRegionBSPTree tree = fullTree();
insertSkewedBowtie(tree);
return tree;
};
Supplier<TestRegionBSPTree> complementFactory = () -> {
TestRegionBSPTree tree = treeFactory.get();
tree.complement();
return tree;
};
// act/assert
intersectionChecker(treeFactory, complementFactory)
.full(false)
.empty(true)
.count(1)
.check();
}
@Test
public void testDifference_singleNodeTrees() {
// act/assert
differenceChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::emptyTree)
.full(false)
.empty(true)
.count(1)
.check();
differenceChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::emptyTree)
.full(true)
.empty(false)
.count(1)
.check();
differenceChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::fullTree)
.full(false)
.empty(true)
.count(1)
.check();
differenceChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::fullTree)
.full(false)
.empty(true)
.count(1)
.check();
}
@Test
public void testDifference_simpleCrossingCuts() {
// act/assert
differenceChecker(AbstractRegionBSPTreeTest::xAxisTree, AbstractRegionBSPTreeTest::emptyTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(0, 1))
.outside(new TestPoint2D(0, -1))
.boundary(TestPoint2D.ZERO)
.count(3)
.check();
differenceChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::xAxisTree)
.full(false)
.empty(true)
.count(1)
.check();
differenceChecker(AbstractRegionBSPTreeTest::yAxisTree, AbstractRegionBSPTreeTest::fullTree)
.full(false)
.empty(true)
.count(1)
.check();
differenceChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::yAxisTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(1, 1), new TestPoint2D(1, -1))
.outside(new TestPoint2D(-1, 1), new TestPoint2D(-1, -1))
.boundary(new TestPoint2D(0, 1), new TestPoint2D(0, -1))
.count(3)
.check();
differenceChecker(AbstractRegionBSPTreeTest::xAxisTree, AbstractRegionBSPTreeTest::yAxisTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(1, 1))
.outside(new TestPoint2D(-1, 1), new TestPoint2D(1, -1), new TestPoint2D(-1, -1))
.boundary(TestPoint2D.ZERO)
.count(5)
.check(tree -> {
TestLineSegment seg = (TestLineSegment) tree.getRoot().getMinus().getCut();
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, seg.getStartPoint());
PartitionTestUtils.assertPointsEqual(new TestPoint2D(0.0, Double.POSITIVE_INFINITY), seg.getEndPoint());
});
}
@Test
public void testDifference_boxTreeWithSingleCutTree() {
// arrange
Supplier<TestRegionBSPTree> boxFactory = () -> {
TestRegionBSPTree box = fullTree();
insertBox(box, TestPoint2D.ZERO, new TestPoint2D(2, -4));
return box;
};
Supplier<TestRegionBSPTree> horizonalFactory = () -> {
TestRegionBSPTree horizontal = fullTree();
horizontal.getRoot().insertCut(new TestLine(new TestPoint2D(2, -2), new TestPoint2D(0, -2)));
return horizontal;
};
// act/assert
differenceChecker(horizonalFactory, boxFactory)
.inside(new TestPoint2D(-1, -3), new TestPoint2D(-1, -5),
new TestPoint2D(1, -5), new TestPoint2D(3, -5),
new TestPoint2D(4, -3))
.outside(new TestPoint2D(1, -1), new TestPoint2D(1, -1),
new TestPoint2D(3, -1), new TestPoint2D(1, -3))
.boundary(new TestPoint2D(0, -2), new TestPoint2D(0, -4),
new TestPoint2D(2, -4), new TestPoint2D(2, -2))
.count(9)
.check();
}
@Test
public void testDifference_treeWithCopy() {
// arrange
Supplier<TestRegionBSPTree> treeFactory = () -> {
TestRegionBSPTree tree = fullTree();
insertSkewedBowtie(tree);
return tree;
};
// act/assert
differenceChecker(treeFactory, treeFactory)
.full(false)
.empty(true)
.count(1)
.check();
}
@Test
public void testXor_singleNodeTrees() {
// act/assert
xorChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::emptyTree)
.full(false)
.empty(true)
.count(1)
.check();
xorChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::emptyTree)
.full(true)
.empty(false)
.count(1)
.check();
xorChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::fullTree)
.full(true)
.empty(false)
.count(1)
.check();
xorChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::fullTree)
.full(false)
.empty(true)
.count(1)
.check();
}
@Test
public void testXor_simpleCrossingCuts() {
// act/assert
xorChecker(AbstractRegionBSPTreeTest::xAxisTree, AbstractRegionBSPTreeTest::emptyTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(0, 1))
.outside(new TestPoint2D(0, -1))
.boundary(TestPoint2D.ZERO)
.count(3)
.check();
xorChecker(AbstractRegionBSPTreeTest::emptyTree, AbstractRegionBSPTreeTest::xAxisTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(0, 1))
.outside(new TestPoint2D(0, -1))
.boundary(TestPoint2D.ZERO)
.count(3)
.check();
xorChecker(AbstractRegionBSPTreeTest::yAxisTree, AbstractRegionBSPTreeTest::fullTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(1, 1), new TestPoint2D(1, -1))
.outside(new TestPoint2D(-1, 1), new TestPoint2D(-1, -1))
.boundary(new TestPoint2D(0, 1), new TestPoint2D(0, -1))
.count(3)
.check();
xorChecker(AbstractRegionBSPTreeTest::fullTree, AbstractRegionBSPTreeTest::yAxisTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(1, 1), new TestPoint2D(1, -1))
.outside(new TestPoint2D(-1, 1), new TestPoint2D(-1, -1))
.boundary(new TestPoint2D(0, 1), new TestPoint2D(0, -1))
.count(3)
.check();
xorChecker(AbstractRegionBSPTreeTest::xAxisTree, AbstractRegionBSPTreeTest::yAxisTree)
.full(false)
.empty(false)
.inside(new TestPoint2D(1, 1), new TestPoint2D(-1, -1))
.outside(new TestPoint2D(-1, 1), new TestPoint2D(1, -1))
.boundary(TestPoint2D.ZERO)
.count(7)
.check(tree -> {
TestLineSegment minusSeg = (TestLineSegment) tree.getRoot().getMinus().getCut();
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, minusSeg.getStartPoint());
PartitionTestUtils.assertPointsEqual(new TestPoint2D(0.0, Double.POSITIVE_INFINITY), minusSeg.getEndPoint());
TestLineSegment plusSeg = (TestLineSegment) tree.getRoot().getPlus().getCut();
PartitionTestUtils.assertPointsEqual(new TestPoint2D(0.0, Double.NEGATIVE_INFINITY), plusSeg.getStartPoint());
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, plusSeg.getEndPoint());
});
}
@Test
public void testXor_boxTreeWithSingleCutTree() {
// arrange
Supplier<TestRegionBSPTree> boxFactory = () -> {
TestRegionBSPTree box = fullTree();
insertBox(box, TestPoint2D.ZERO, new TestPoint2D(2, -4));
return box;
};
Supplier<TestRegionBSPTree> horizonalFactory = () -> {
TestRegionBSPTree horizontal = fullTree();
horizontal.getRoot().insertCut(new TestLine(new TestPoint2D(2, -2), new TestPoint2D(0, -2)));
return horizontal;
};
// act/assert
xorChecker(horizonalFactory, boxFactory)
.inside(new TestPoint2D(-1, -3), new TestPoint2D(-1, -5),
new TestPoint2D(1, -5), new TestPoint2D(3, -5),
new TestPoint2D(4, -3), new TestPoint2D(1, -1))
.outside(new TestPoint2D(3, -1), new TestPoint2D(1, -3),
new TestPoint2D(1, 1), new TestPoint2D(5, -1))
.boundary(new TestPoint2D(0, -2), new TestPoint2D(0, -4),
new TestPoint2D(2, -4), new TestPoint2D(2, -2),
TestPoint2D.ZERO, new TestPoint2D(2, 0))
.count(15)
.check();
}
@Test
public void testXor_treeWithComplement() {
// arrange
Supplier<TestRegionBSPTree> treeFactory = () -> {
TestRegionBSPTree tree = fullTree();
insertSkewedBowtie(tree);
return tree;
};
Supplier<TestRegionBSPTree> complementFactory = () -> {
TestRegionBSPTree tree = treeFactory.get();
tree.complement();
return tree;
};
// act/assert
xorChecker(treeFactory, complementFactory)
.full(true)
.empty(false)
.count(1)
.check();
}
@Test
public void testProject_emptyAndFull() {
// arrange
TestRegionBSPTree full = fullTree();
TestRegionBSPTree empty = emptyTree();
// act/assert
Assert.assertNull(full.project(new TestPoint2D(0, 0)));
Assert.assertNull(empty.project(new TestPoint2D(-1, 1)));
}
@Test
public void testProject_halfSpace() {
// arrange
TestRegionBSPTree tree = fullTree();
tree.getRoot().cut(TestLine.X_AXIS);
// act/assert
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, tree.project(TestPoint2D.ZERO));
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, tree.project(new TestPoint2D(0, 7)));
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, tree.project(new TestPoint2D(0, -7)));
PartitionTestUtils.assertPointsEqual(new TestPoint2D(4, 0), tree.project(new TestPoint2D(4, 10)));
PartitionTestUtils.assertPointsEqual(new TestPoint2D(-5, 0), tree.project(new TestPoint2D(-5, -2)));
}
@Test
public void testProject_box() {
// arrange
TestRegionBSPTree tree = fullTree();
insertBox(tree, new TestPoint2D(0, 1), new TestPoint2D(1, 0));
// act/assert
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, tree.project(TestPoint2D.ZERO));
PartitionTestUtils.assertPointsEqual(TestPoint2D.ZERO, tree.project(new TestPoint2D(-1, -4)));
PartitionTestUtils.assertPointsEqual(new TestPoint2D(1, 1), tree.project(new TestPoint2D(2, 9)));
PartitionTestUtils.assertPointsEqual(new TestPoint2D(0.5, 1), tree.project(new TestPoint2D(0.5, 3)));
}
@Test
public void testSplit_empty() {
// arrange
TestRegionBSPTree tree = emptyTree();
// act
Split<TestRegionBSPTree> split = tree.split(TestLine.X_AXIS);
// assert
Assert.assertEquals(SplitLocation.NEITHER, split.getLocation());
Assert.assertNull(split.getMinus());
Assert.assertNull(split.getPlus());
}
@Test
public void testSplit_full() {
// arrange
TestRegionBSPTree tree = fullTree();
// act
Split<TestRegionBSPTree> split = tree.split(TestLine.X_AXIS);
// assert
Assert.assertEquals(SplitLocation.BOTH, split.getLocation());
TestRegionBSPTree minus = split.getMinus();
assertPointLocations(minus, RegionLocation.INSIDE,
new TestPoint2D(-1, 1), new TestPoint2D(0, 1), new TestPoint2D(1, 1));
assertPointLocations(minus, RegionLocation.BOUNDARY,
new TestPoint2D(-1, 0), new TestPoint2D(0, 0), new TestPoint2D(1, 0));
assertPointLocations(minus, RegionLocation.OUTSIDE,
new TestPoint2D(-1, -1), new TestPoint2D(0, -1), new TestPoint2D(1, -1));
TestRegionBSPTree plus = split.getPlus();
assertPointLocations(plus, RegionLocation.OUTSIDE,
new TestPoint2D(-1, 1), new TestPoint2D(0, 1), new TestPoint2D(1, 1));
assertPointLocations(plus, RegionLocation.BOUNDARY,
new TestPoint2D(-1, 0), new TestPoint2D(0, 0), new TestPoint2D(1, 0));
assertPointLocations(plus, RegionLocation.INSIDE,
new TestPoint2D(-1, -1), new TestPoint2D(0, -1), new TestPoint2D(1, -1));
}
@Test
public void testSplit_halfSpace() {
// arrange
TestRegionBSPTree tree = fullTree();
tree.getRoot().insertCut(TestLine.X_AXIS);
TestLine splitter = TestLine.Y_AXIS;
// act
Split<TestRegionBSPTree> split = tree.split(splitter);
// assert
Assert.assertEquals(SplitLocation.BOTH, split.getLocation());
TestRegionBSPTree minus = split.getMinus();
assertPointLocations(minus, RegionLocation.INSIDE, new TestPoint2D(-1, 1));
assertPointLocations(minus, RegionLocation.OUTSIDE,
new TestPoint2D(1, 1), new TestPoint2D(-1, -1), new TestPoint2D(1, -1));
TestRegionBSPTree plus = split.getPlus();
assertPointLocations(plus, RegionLocation.INSIDE, new TestPoint2D(1, 1));
assertPointLocations(plus, RegionLocation.OUTSIDE,
new TestPoint2D(-1, 1), new TestPoint2D(-1, -1), new TestPoint2D(1, -1));
}
@Test
public void testSplit_box() {
// arrange
TestRegionBSPTree tree = fullTree();
insertBox(tree, new TestPoint2D(0, 1), new TestPoint2D(1, 0));
TestLine splitter = new TestLine(new TestPoint2D(1, 0), new TestPoint2D(0, 1));
// act
Split<TestRegionBSPTree> split = tree.split(splitter);
// assert
Assert.assertEquals(SplitLocation.BOTH, split.getLocation());
TestRegionBSPTree minus = split.getMinus();
assertPointLocations(minus, RegionLocation.INSIDE, new TestPoint2D(0.25, 0.25));
assertPointLocations(minus, RegionLocation.BOUNDARY,
new TestPoint2D(0.5, 0), new TestPoint2D(0, 0.5));
assertPointLocations(minus, RegionLocation.OUTSIDE,
new TestPoint2D(1, 0.5), new TestPoint2D(0.5, 1), new TestPoint2D(0.75, 0.75));
TestRegionBSPTree plus = split.getPlus();
assertPointLocations(plus, RegionLocation.INSIDE, new TestPoint2D(0.75, 0.75));
assertPointLocations(plus, RegionLocation.OUTSIDE,
new TestPoint2D(0.5, 0), new TestPoint2D(0, 0.5), new TestPoint2D(0.25, 0.25));
assertPointLocations(plus, RegionLocation.BOUNDARY,
new TestPoint2D(1, 0.5), new TestPoint2D(0.5, 1));
}
@Test
public void testSplit_box_onMinusOnly() {
// arrange
TestRegionBSPTree tree = fullTree();
insertBox(tree, new TestPoint2D(0, 1), new TestPoint2D(1, 0));
TestLine splitter = new TestLine(new TestPoint2D(2, 0), new TestPoint2D(1, 1));
// act
Split<TestRegionBSPTree> split = tree.split(splitter);
// assert
Assert.assertEquals(SplitLocation.MINUS, split.getLocation());
TestRegionBSPTree minus = split.getMinus();
assertPointLocations(minus, RegionLocation.INSIDE, new TestPoint2D(0.5, 0.5));
assertPointLocations(minus, RegionLocation.BOUNDARY,
new TestPoint2D(0.5, 0), new TestPoint2D(0, 0.5),
new TestPoint2D(1, 0.5), new TestPoint2D(0.5, 1));
Assert.assertNull(split.getPlus());
}
@Test
public void testSplit_box_onPlusOnly() {
// arrange
TestRegionBSPTree tree = fullTree();
insertBox(tree, new TestPoint2D(0, 1), new TestPoint2D(1, 0));
TestLine splitter = new TestLine(new TestPoint2D(0, 0), new TestPoint2D(-1, 1));
// act
Split<TestRegionBSPTree> split = tree.split(splitter);
// assert
Assert.assertEquals(SplitLocation.PLUS, split.getLocation());
Assert.assertNull(split.getMinus());
TestRegionBSPTree plus = split.getPlus();
assertPointLocations(plus, RegionLocation.INSIDE, new TestPoint2D(0.5, 0.5));
assertPointLocations(plus, RegionLocation.BOUNDARY,
new TestPoint2D(0.5, 0), new TestPoint2D(0, 0.5),
new TestPoint2D(1, 0.5), new TestPoint2D(0.5, 1));
}
@Test
public void testToString() {
// arrange
TestRegionBSPTree tree = fullTree();
tree.getRoot().cut(TestLine.X_AXIS);
// act
String str = tree.toString();
// assert
Assert.assertEquals("TestRegionBSPTree[count= 3, height= 1]", str);
Assert.assertTrue(tree.getRoot().toString().contains("TestRegionNode"));
}
/** Assert that all given points lie in the expected location of the region.
* @param tree region tree
* @param points points to test
* @param location expected location of all points
*/
private static void assertPointLocations(final TestRegionBSPTree tree, final RegionLocation location,
final TestPoint2D ... points) {
assertPointLocations(tree, location, Arrays.asList(points));
}
/** Assert that all given points lie in the expected location of the region.
* @param tree region tree
* @param points points to test
* @param location expected location of all points
*/
private static void assertPointLocations(final TestRegionBSPTree tree, final RegionLocation location,
final List<TestPoint2D> points) {
for (TestPoint2D p : points) {
Assert.assertEquals("Unexpected location for point " + p, location, tree.classify(p));
}
}
private static MergeChecker unionChecker(
final Supplier<TestRegionBSPTree> r1,
final Supplier<TestRegionBSPTree> r2) {
MergeOperation constOperation = (a, b) -> {
TestRegionBSPTree result = fullTree();
result.union(a, b);
return result;
};
MergeOperation inPlaceOperation = (a, b) -> {
a.union(b);
return a;
};
return new MergeChecker(r1, r2, constOperation, inPlaceOperation);
}
private static MergeChecker intersectionChecker(
final Supplier<TestRegionBSPTree> tree1Factory,
final Supplier<TestRegionBSPTree> tree2Factory) {
MergeOperation constOperation = (a, b) -> {
TestRegionBSPTree result = fullTree();
result.intersection(a, b);
return result;
};
MergeOperation inPlaceOperation = (a, b) -> {
a.intersection(b);
return a;
};
return new MergeChecker(tree1Factory, tree2Factory, constOperation, inPlaceOperation);
}
private static MergeChecker differenceChecker(
final Supplier<TestRegionBSPTree> tree1Factory,
final Supplier<TestRegionBSPTree> tree2Factory) {
MergeOperation constOperation = (a, b) -> {
TestRegionBSPTree result = fullTree();
result.difference(a, b);
return result;
};
MergeOperation inPlaceOperation = (a, b) -> {
a.difference(b);
return a;
};
return new MergeChecker(tree1Factory, tree2Factory, constOperation, inPlaceOperation);
}
private static MergeChecker xorChecker(
final Supplier<TestRegionBSPTree> tree1Factory,
final Supplier<TestRegionBSPTree> tree2Factory) {
MergeOperation constOperation = (a, b) -> {
TestRegionBSPTree result = fullTree();
result.xor(a, b);
return result;
};
MergeOperation inPlaceOperation = (a, b) -> {
a.xor(b);
return a;
};
return new MergeChecker(tree1Factory, tree2Factory, constOperation, inPlaceOperation);
}
private static void insertBox(final TestRegionBSPTree tree, final TestPoint2D upperLeft, final TestPoint2D lowerRight) {
final TestPoint2D upperRight = new TestPoint2D(lowerRight.getX(), upperLeft.getY());
final TestPoint2D lowerLeft = new TestPoint2D(upperLeft.getX(), lowerRight.getY());
tree.insert(Arrays.asList(
new TestLineSegment(lowerRight, upperRight),
new TestLineSegment(upperRight, upperLeft),
new TestLineSegment(upperLeft, lowerLeft),
new TestLineSegment(lowerLeft, lowerRight)
));
}
private static void insertSkewedBowtie(final TestRegionBSPTree tree) {
tree.insert(Arrays.asList(
new TestLineSegment(TestPoint2D.ZERO, new TestPoint2D(1, 0)),
new TestLineSegment(new TestPoint2D(4, 0), new TestPoint2D(4, 1)),
new TestLineSegment(new TestPoint2D(-4, 0), new TestPoint2D(-4, -1)),
new TestLineSegment(new TestPoint2D(4, 5), new TestPoint2D(-1, 0)),
new TestLineSegment(new TestPoint2D(-4, -5), new TestPoint2D(1, 0))));
}
private static void assertCutBoundarySegment(final SubHyperplane<TestPoint2D> boundary, final TestPoint2D start, final TestPoint2D end) {
Assert.assertFalse("Expected boundary to not be empty", boundary.isEmpty());
TestLineSegmentCollection segmentCollection = (TestLineSegmentCollection) boundary;
Assert.assertEquals(1, segmentCollection.getLineSegments().size());
TestLineSegment segment = segmentCollection.getLineSegments().get(0);
PartitionTestUtils.assertPointsEqual(start, segment.getStartPoint());
PartitionTestUtils.assertPointsEqual(end, segment.getEndPoint());
}
private static void assertContainsSegment(final List<TestLineSegment> boundaries, final TestPoint2D start, final TestPoint2D end) {
boolean found = false;
for (TestLineSegment seg : boundaries) {
TestPoint2D startPt = seg.getStartPoint();
TestPoint2D endPt = seg.getEndPoint();
if (PartitionTestUtils.PRECISION.eq(start.getX(), startPt.getX()) &&
PartitionTestUtils.PRECISION.eq(start.getY(), startPt.getY()) &&
PartitionTestUtils.PRECISION.eq(end.getX(), endPt.getX()) &&
PartitionTestUtils.PRECISION.eq(end.getY(), endPt.getY())) {
found = true;
break;
}
}
Assert.assertTrue("Expected to find segment start= " + start + ", end= " + end , found);
}
private static TestRegionBSPTree emptyTree() {
return new TestRegionBSPTree(false);
}
private static TestRegionBSPTree fullTree() {
return new TestRegionBSPTree(true);
}
private static TestRegionBSPTree xAxisTree() {
TestRegionBSPTree tree = fullTree();
tree.getRoot().cut(TestLine.X_AXIS);
return tree;
}
private static TestRegionBSPTree yAxisTree() {
TestRegionBSPTree tree = fullTree();
tree.getRoot().cut(TestLine.Y_AXIS);
return tree;
}
/** Helper interface used when testing tree merge operations.
*/
@FunctionalInterface
private static interface MergeOperation {
TestRegionBSPTree apply(TestRegionBSPTree tree1, TestRegionBSPTree tree2);
}
/** Helper class with a fluent API used to construct assert conditions on tree merge operations.
*/
private static class MergeChecker {
/** First tree in the merge operation */
private final Supplier<TestRegionBSPTree> tree1Factory;
/** Second tree in the merge operation */
private final Supplier<TestRegionBSPTree> tree2Factory;
/** Merge operation that does not modify either input tree */
private final MergeOperation constOperation;
/** Merge operation that stores the result in the first input tree
* and leaves the second one unmodified.
*/
private final MergeOperation inPlaceOperation;
/** If true, the resulting tree will be printed to stdout to help with
* debugging.
*/
private boolean print;
/** The expected node count of the merged tree */
private int expectedCount = -1;
/** The expected full state of the merged tree */
private boolean expectedFull = false;
/** The expected empty state of the merged tree */
private boolean expectedEmpty = false;
/** Points expected to lie in the inside of the region */
private List<TestPoint2D> insidePoints = new ArrayList<>();
/** Points expected to lie on the outside of the region */
private List<TestPoint2D> outsidePoints = new ArrayList<>();
/** Points expected to lie on the boundary of the region */
private List<TestPoint2D> boundaryPoints = new ArrayList<>();
/** Construct a new instance that will verify the output of performing the given merge operation
* on the input trees.
* @param tree1 first tree in the merge operation
* @param tree2 second tree in the merge operation
* @param constOperation object that performs the merge operation in a form that
* leaves both argument unmodified
* @param inPlaceOperation object that performs the merge operation in a form
* that stores the result in the first input tree and leaves the second
* input unchanged.
*/
public MergeChecker(
final Supplier<TestRegionBSPTree> tree1Factory,
final Supplier<TestRegionBSPTree> tree2Factory,
final MergeOperation constOperation,
final MergeOperation inPlaceOperation) {
this.tree1Factory = tree1Factory;
this.tree2Factory = tree2Factory;
this.constOperation = constOperation;
this.inPlaceOperation = inPlaceOperation;
}
/** Set the expected node count of the merged tree
* @param expectedCount the expected node count of the merged tree
* @return this instance
*/
public MergeChecker count(final int expectedCount) {
this.expectedCount = expectedCount;
return this;
}
/** Set the expected full state of the merged tree.
* @param expectedFull the expected full state of the merged tree.
* @return this instance
*/
public MergeChecker full(final boolean expectedFull) {
this.expectedFull = expectedFull;
return this;
}
/** Set the expected empty state of the merged tree.
* @param expectedEmpty the expected empty state of the merged tree.
* @return this instance
*/
public MergeChecker empty(final boolean expectedEmpty) {
this.expectedEmpty = expectedEmpty;
return this;
}
/** Add points expected to be on the inside of the merged region.
* @param points point expected to be on the inside of the merged
* region
* @return this instance
*/
public MergeChecker inside(TestPoint2D ... points) {
insidePoints.addAll(Arrays.asList(points));
return this;
}
/** Add points expected to be on the outside of the merged region.
* @param points point expected to be on the outside of the merged
* region
* @return this instance
*/
public MergeChecker outside(TestPoint2D ... points) {
outsidePoints.addAll(Arrays.asList(points));
return this;
}
/** Add points expected to be on the boundary of the merged region.
* @param points point expected to be on the boundary of the merged
* region
* @return this instance
*/
public MergeChecker boundary(TestPoint2D ... points) {
boundaryPoints.addAll(Arrays.asList(points));
return this;
}
/** Set the flag for printing the merged tree to stdout before performing assertions.
* This can be useful for debugging tests.
* @param print if set to true, the merged tree will be printed to stdout
* @return this instance
*/
@SuppressWarnings("unused")
public MergeChecker print(final boolean print) {
this.print = print;
return this;
}
/** Perform the merge operation and verify the output.
*/
public void check() {
check(null);
}
/** Perform the merge operation and verify the output. The given consumer
* is passed the merge result and can be used to perform extra assertions.
* @param assertions consumer that will be passed the merge result; may
* be null
*/
public void check(final Consumer<TestRegionBSPTree> assertions) {
checkConst(assertions);
checkInPlace(assertions);
}
private void checkConst(final Consumer<TestRegionBSPTree> assertions) {
checkInternal(false, constOperation, assertions);
}
private void checkInPlace(final Consumer<TestRegionBSPTree> assertions) {
checkInternal(true, inPlaceOperation, assertions);
}
private void checkInternal(final boolean inPlace, final MergeOperation operation,
final Consumer<TestRegionBSPTree> assertions) {
final TestRegionBSPTree tree1 = tree1Factory.get();
final TestRegionBSPTree tree2 = tree2Factory.get();
// store the number of nodes in each tree before the operation
final int tree1BeforeCount = tree1.count();
final int tree2BeforeCount = tree2.count();
// perform the operation
final TestRegionBSPTree result = operation.apply(tree1, tree2);
if (print) {
System.out.println((inPlace ? "In Place" : "Const") + " Result:");
System.out.println(result.treeString());
}
// verify the internal consistency of all of the involved trees
PartitionTestUtils.assertTreeStructure(tree1);
PartitionTestUtils.assertTreeStructure(tree2);
PartitionTestUtils.assertTreeStructure(result);
// check full/empty status
Assert.assertEquals("Unexpected tree 'full' property", expectedFull, result.isFull());
Assert.assertEquals("Unexpected tree 'empty' property", expectedEmpty, result.isEmpty());
// check the node count
if (expectedCount > -1) {
Assert.assertEquals("Unexpected node count", expectedCount, result.count());
}
// check in place or not
if (inPlace) {
Assert.assertSame("Expected merge operation to be in place", tree1, result);
}
else {
Assert.assertNotSame("Expected merge operation to return a new instance", tree1, result);
// make sure that tree1 wasn't modified
Assert.assertEquals("Tree 1 node count should not have changed", tree1BeforeCount, tree1.count());
}
// make sure that tree2 wasn't modified
Assert.assertEquals("Tree 2 node count should not have changed", tree2BeforeCount, tree2.count());
// check region point locations
assertPointLocations(result, RegionLocation.INSIDE, insidePoints);
assertPointLocations(result, RegionLocation.OUTSIDE, outsidePoints);
assertPointLocations(result, RegionLocation.BOUNDARY, boundaryPoints);
// pass the result to the given function for any additional assertions
if (assertions != null) {
assertions.accept(result);
}
}
}
private static class TestRegionBSPTree extends AbstractRegionBSPTree<TestPoint2D, TestRegionNode> {
private static final long serialVersionUID = 20190405L;
TestRegionBSPTree() {
this(true);
}
TestRegionBSPTree(final boolean full) {
super(full);
}
/**
* Expose the direct node cut method for easier creation of test tree structures.
*/
@Override
public void cutNode(final TestRegionNode node, final ConvexSubHyperplane<TestPoint2D> cut) {
super.cutNode(node, cut);
}
@Override
protected TestRegionNode createNode() {
return new TestRegionNode(this);
}
@Override
protected RegionSizeProperties<TestPoint2D> computeRegionSizeProperties() {
// return a set of stub values
return new RegionSizeProperties<>(1, TestPoint2D.ZERO);
}
@Override
public boolean contains(TestPoint2D pt) {
return classify(pt) != RegionLocation.OUTSIDE;
}
@Override
public Split<TestRegionBSPTree> split(Hyperplane<TestPoint2D> splitter) {
return split(splitter, new TestRegionBSPTree(), new TestRegionBSPTree());
}
}
private static class TestRegionNode extends AbstractRegionNode<TestPoint2D, TestRegionNode> {
private static final long serialVersionUID = 20190405L;
protected TestRegionNode(AbstractBSPTree<TestPoint2D, TestRegionNode> tree) {
super(tree);
}
@Override
protected TestRegionNode getSelf() {
return this;
}
}
}