GEOMETRY-72: adding BoundarySource interface and implementations in Euclidean 2D, 3D and spherical 2D; making a number of internal BSP tree classes private
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java
new file mode 100644
index 0000000..6dffc6b
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import java.util.stream.Stream;
+
+import org.apache.commons.geometry.core.Point;
+
+/** Interface representing an object that can produce region boundaries as a stream
+ * of convex subhyperplanes.
+ * @param <C> Convex subhyperplane implementation type
+ */
+@FunctionalInterface
+public interface BoundarySource<C extends ConvexSubHyperplane<? extends Point<?>>> {
+
+ /** Return a stream containing the boundaries for this instance.
+ * @return a stream containing the boundaries for this instance
+ */
+ Stream<C> boundaryStream();
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTree.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTree.java
index d2dc7b0..060f995 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTree.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTree.java
@@ -20,9 +20,11 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
+import java.util.stream.Stream;
import org.apache.commons.geometry.core.Point;
import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.BoundarySource;
import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
@@ -117,10 +119,18 @@
}
}
- /** Return an iterator over the nodes in the tree. */
+ /** {@inheritDoc} */
@Override
- public Iterator<N> iterator() {
- return new NodeIterator<>(getRoot());
+ public void insert(final BoundarySource<? extends ConvexSubHyperplane<P>> boundarySrc) {
+ try (Stream<? extends ConvexSubHyperplane<P>> stream = boundarySrc.boundaryStream()) {
+ stream.forEach(this::insert);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Iterable<N> nodes() {
+ return () -> new NodeIterator<>(getRoot());
}
/** {@inheritDoc} */
@@ -894,8 +904,8 @@
/** {@inheritDoc} */
@Override
- public Iterator<N> iterator() {
- return new NodeIterator<>(getSelf());
+ public Iterable<N> nodes() {
+ return () -> new NodeIterator<>(getSelf());
}
/** {@inheritDoc} */
@@ -1075,7 +1085,7 @@
* @param <P> Point implementation type
* @param <N> Node implementation type
*/
- public static class NodeIterator<P extends Point<P>, N extends AbstractNode<P, N>> implements Iterator<N> {
+ private static final class NodeIterator<P extends Point<P>, N extends AbstractNode<P, N>> implements Iterator<N> {
/** The current node stack. */
private final Deque<N> stack = new LinkedList<>();
@@ -1083,7 +1093,7 @@
/** Create a new instance for iterating over the nodes in the given subtree.
* @param subtreeRoot the root node of the subtree to iterate
*/
- public NodeIterator(final N subtreeRoot) {
+ NodeIterator(final N subtreeRoot) {
stack.push(subtreeRoot);
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java
index 19faf35..15a1680 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java
@@ -123,7 +123,7 @@
double sum = 0.0;
RegionCutBoundary<P> boundary;
- for (final AbstractRegionNode<P, N> node : this) {
+ for (final AbstractRegionNode<P, N> node : nodes()) {
boundary = node.getCutBoundary();
if (boundary != null) {
sum += boundary.getInsideFacing().getSize();
@@ -157,10 +157,9 @@
protected <C extends ConvexSubHyperplane<P>> Iterable<C> createBoundaryIterable(
final Function<ConvexSubHyperplane<P>, C> typeConverter) {
- return () -> {
- final NodeIterator<P, N> nodeIterator = new NodeIterator<>(getRoot());
- return new RegionBoundaryIterator<>(nodeIterator, typeConverter);
- };
+ return () -> new RegionBoundaryIterator<>(
+ getRoot().nodes().iterator(),
+ typeConverter);
}
/** Return a list containing the boundaries of the region. Each boundary is oriented such
@@ -183,7 +182,7 @@
final List<C> result = new ArrayList<>();
- final RegionBoundaryIterator<P, C, N> it = new RegionBoundaryIterator<>(iterator(), typeConverter);
+ final RegionBoundaryIterator<P, C, N> it = new RegionBoundaryIterator<>(nodes().iterator(), typeConverter);
it.forEachRemaining(result::add);
return result;
@@ -646,133 +645,6 @@
}
}
- /** Class containing the basic algorithm for merging region BSP trees.
- * @param <P> Point implementation type
- * @param <N> BSP tree node implementation type
- */
- public abstract static class RegionMergeOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
- extends AbstractBSPTreeMergeOperator<P, N> {
-
- /** Merge two input trees, storing the output in the third. The output tree can be one of the
- * input trees. The output tree is condensed before the method returns.
- * @param inputTree1 first input tree
- * @param inputTree2 second input tree
- * @param outputTree the tree that will contain the result of the merge; may be one
- * of the input trees
- */
- public void apply(final AbstractRegionBSPTree<P, N> inputTree1, final AbstractRegionBSPTree<P, N> inputTree2,
- final AbstractRegionBSPTree<P, N> outputTree) {
-
- this.performMerge(inputTree1, inputTree2, outputTree);
-
- outputTree.condense();
- }
- }
-
- /** Class for performing boolean union operations on region trees.
- * @param <P> Point implementation type
- * @param <N> BSP tree node implementation type
- */
- public static class UnionOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
- extends RegionMergeOperator<P, N> {
-
- /** {@inheritDoc} */
- @Override
- protected N mergeLeaf(final N node1, final N node2) {
- if (node1.isLeaf()) {
- return node1.isInside() ? node1 : node2;
- }
-
- // call again with flipped arguments
- return mergeLeaf(node2, node1);
- }
- }
-
- /** Class for performing boolean intersection operations on region trees.
- * @param <P> Point implementation type
- * @param <N> BSP tree node implementation type
- */
- public static class IntersectionOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
- extends RegionMergeOperator<P, N> {
-
- /** {@inheritDoc} */
- @Override
- protected N mergeLeaf(final N node1, final N node2) {
- if (node1.isLeaf()) {
- return node1.isInside() ? node2 : node1;
- }
-
- // call again with flipped arguments
- return mergeLeaf(node2, node1);
- }
- }
-
- /** Class for performing boolean difference operations on region trees.
- * @param <P> Point implementation type
- * @param <N> BSP tree node implementation type
- */
- public static class DifferenceOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
- extends RegionMergeOperator<P, N> {
-
- /** {@inheritDoc} */
- @Override
- protected N mergeLeaf(final N node1, final N node2) {
- // a region is included if it belongs in tree1 and is not in tree2
-
- if (node1.isInside()) {
- // this region is inside of tree1, so only include subregions that are
- // not in tree2, ie include everything in node2's complement
- final N output = outputSubtree(node2);
- output.getTree().complementRecursive(output);
-
- return output;
- } else if (node2.isInside()) {
- // this region is inside of tree2 and so cannot be in the result region
- final N output = outputNode();
- output.setLocation(RegionLocation.OUTSIDE);
-
- return output;
- }
-
- // this region is not in tree2, so we can include everything in tree1
- return node1;
- }
- }
-
- /** Class for performing boolean symmetric difference (xor) operations on region trees.
- * @param <P> Point implementation type
- * @param <N> BSP tree node implementation type
- */
- public static class XorOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
- extends RegionMergeOperator<P, N> {
-
- /** {@inheritDoc} */
- @Override
- protected N mergeLeaf(final N node1, final N node2) {
- // a region is included if it belongs in tree1 and is not in tree2 OR
- // it belongs in tree2 and is not in tree1
-
- if (node1.isLeaf()) {
- if (node1.isInside()) {
- // this region is inside node1, so only include subregions that are
- // not in node2, ie include everything in node2's complement
- final N output = outputSubtree(node2);
- output.getTree().complementRecursive(output);
-
- return output;
- } else {
- // this region is not in node1, so only include subregions that
- // in node2
- return node2;
- }
- }
-
- // the operation is symmetric, so perform the same operation but with the
- // nodes flipped
- return mergeLeaf(node2, node1);
- }
- }
-
/** Class used to compute the point on the region's boundary that is closest to a target point.
* @param <P> Point implementation type
* @param <N> BSP tree node implementation type
@@ -887,12 +759,139 @@
}
}
+ /** Class containing the basic algorithm for merging region BSP trees.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ private abstract static class RegionMergeOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
+ extends AbstractBSPTreeMergeOperator<P, N> {
+
+ /** Merge two input trees, storing the output in the third. The output tree can be one of the
+ * input trees. The output tree is condensed before the method returns.
+ * @param inputTree1 first input tree
+ * @param inputTree2 second input tree
+ * @param outputTree the tree that will contain the result of the merge; may be one
+ * of the input trees
+ */
+ public void apply(final AbstractRegionBSPTree<P, N> inputTree1, final AbstractRegionBSPTree<P, N> inputTree2,
+ final AbstractRegionBSPTree<P, N> outputTree) {
+
+ this.performMerge(inputTree1, inputTree2, outputTree);
+
+ outputTree.condense();
+ }
+ }
+
+ /** Class for performing boolean union operations on region trees.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ private static final class UnionOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
+ extends RegionMergeOperator<P, N> {
+
+ /** {@inheritDoc} */
+ @Override
+ protected N mergeLeaf(final N node1, final N node2) {
+ if (node1.isLeaf()) {
+ return node1.isInside() ? node1 : node2;
+ }
+
+ // call again with flipped arguments
+ return mergeLeaf(node2, node1);
+ }
+ }
+
+ /** Class for performing boolean intersection operations on region trees.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ private static final class IntersectionOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
+ extends RegionMergeOperator<P, N> {
+
+ /** {@inheritDoc} */
+ @Override
+ protected N mergeLeaf(final N node1, final N node2) {
+ if (node1.isLeaf()) {
+ return node1.isInside() ? node2 : node1;
+ }
+
+ // call again with flipped arguments
+ return mergeLeaf(node2, node1);
+ }
+ }
+
+ /** Class for performing boolean difference operations on region trees.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ private static final class DifferenceOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
+ extends RegionMergeOperator<P, N> {
+
+ /** {@inheritDoc} */
+ @Override
+ protected N mergeLeaf(final N node1, final N node2) {
+ // a region is included if it belongs in tree1 and is not in tree2
+
+ if (node1.isInside()) {
+ // this region is inside of tree1, so only include subregions that are
+ // not in tree2, ie include everything in node2's complement
+ final N output = outputSubtree(node2);
+ output.getTree().complementRecursive(output);
+
+ return output;
+ } else if (node2.isInside()) {
+ // this region is inside of tree2 and so cannot be in the result region
+ final N output = outputNode();
+ output.setLocation(RegionLocation.OUTSIDE);
+
+ return output;
+ }
+
+ // this region is not in tree2, so we can include everything in tree1
+ return node1;
+ }
+ }
+
+ /** Class for performing boolean symmetric difference (xor) operations on region trees.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ private static final class XorOperator<P extends Point<P>, N extends AbstractRegionNode<P, N>>
+ extends RegionMergeOperator<P, N> {
+
+ /** {@inheritDoc} */
+ @Override
+ protected N mergeLeaf(final N node1, final N node2) {
+ // a region is included if it belongs in tree1 and is not in tree2 OR
+ // it belongs in tree2 and is not in tree1
+
+ if (node1.isLeaf()) {
+ if (node1.isInside()) {
+ // this region is inside node1, so only include subregions that are
+ // not in node2, ie include everything in node2's complement
+ final N output = outputSubtree(node2);
+ output.getTree().complementRecursive(output);
+
+ return output;
+ } else {
+ // this region is not in node1, so only include subregions that
+ // in node2
+ return node2;
+ }
+ }
+
+ // the operation is symmetric, so perform the same operation but with the
+ // nodes flipped
+ return mergeLeaf(node2, node1);
+ }
+ }
+
/** Class that iterates over the boundary convex subhyperplanes from a set of region nodes.
* @param <P> Point implementation type
* @param <C> Boundary convex subhyperplane implementation type
* @param <N> BSP tree node implementation type
*/
- protected static final class RegionBoundaryIterator<
+ private static final class RegionBoundaryIterator<
P extends Point<P>,
C extends ConvexSubHyperplane<P>,
N extends AbstractRegionNode<P, N>>
@@ -905,7 +904,7 @@
* @param inputIterator iterator that will provide all nodes in the tree
* @param typeConverter function that converts from the convex subhyperplane type to the output type
*/
- private RegionBoundaryIterator(final Iterator<N> inputIterator,
+ RegionBoundaryIterator(final Iterator<N> inputIterator,
final Function<ConvexSubHyperplane<P>, C> typeConverter) {
super(inputIterator);
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPSubtree.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPSubtree.java
index 5943765..a09eff3 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPSubtree.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPSubtree.java
@@ -16,9 +16,6 @@
*/
package org.apache.commons.geometry.core.partitioning.bsp;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
import org.apache.commons.geometry.core.Point;
/** Interface for types that form the root of BSP subtrees. This includes trees
@@ -26,7 +23,7 @@
* @param <P> Point implementation type
* @param <N> Node implementation type
*/
-public interface BSPSubtree<P extends Point<P>, N extends BSPTree.Node<P, N>> extends Iterable<N> {
+public interface BSPSubtree<P extends Point<P>, N extends BSPTree.Node<P, N>> {
/** Return the total number of nodes in the subtree.
* @return the total number of nodes in the subtree.
@@ -44,10 +41,10 @@
*/
void accept(BSPTreeVisitor<P, N> visitor);
- /** Create a stream over the nodes in this subtree.
- * @return a stream for accessing the nodes in this subtree
+ /** Get an iterable for accessing the nodes in this subtree. This provides a simple
+ * alternative to {@link #accept(BSPTreeVisitor)} for accessing tree nodes but is not
+ * as powerful or flexible since the node iteration order is fixed.
+ * @return an iterable for accessing the nodes in this subtree
*/
- default Stream<N> stream() {
- return StreamSupport.stream(spliterator(), false);
- }
+ Iterable<N> nodes();
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java
index 758ca90..50dd742 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java
@@ -18,6 +18,7 @@
import org.apache.commons.geometry.core.Point;
import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.BoundarySource;
import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.SubHyperplane;
@@ -103,6 +104,12 @@
*/
void insert(Iterable<? extends ConvexSubHyperplane<P>> convexSubs);
+ /** Insert all convex subhyperplanes from the given source into the tree.
+ * @param boundarySrc source of boundary convex subhyperplanes to insert
+ * into the tree
+ */
+ void insert(BoundarySource<? extends ConvexSubHyperplane<P>> boundarySrc);
+
/** Make the current instance a deep copy of the argument.
* @param src the tree to copy
*/
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeMergeOperatorTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeMergeOperatorTest.java
index d3c2f91..8e25253 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeMergeOperatorTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeMergeOperatorTest.java
@@ -16,6 +16,8 @@
*/
package org.apache.commons.geometry.core.partitioning.bsp;
+import java.util.stream.StreamSupport;
+
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.TestPoint2D;
@@ -550,7 +552,9 @@
String attr = leaf.getAttribute();
AttributeNode<TestPoint2D, String> output = outputSubtree(subtree);
- output.stream().filter(BSPTree.Node::isLeaf).forEach(n -> n.setAttribute(attr + n.getAttribute()));
+ StreamSupport.stream(output.nodes().spliterator(), false)
+ .filter(BSPTree.Node::isLeaf)
+ .forEach(n -> n.setAttribute(attr + n.getAttribute()));
return output;
}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeTest.java
index 18e5a48..39e6a11 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeTest.java
@@ -24,6 +24,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partition.test.PartitionTestUtils;
@@ -34,6 +35,7 @@
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.BoundarySource;
import org.apache.commons.geometry.core.partitioning.bsp.BSPTree.NodeCutRule;
import org.junit.Assert;
import org.junit.Test;
@@ -713,6 +715,59 @@
}
@Test
+ public void testInsert_boundarySource() {
+ // arrange
+ TestBSPTree tree = new TestBSPTree();
+
+ TestLineSegment a = new TestLineSegment(-1, -1, 1, -1);
+ TestLineSegment b = new TestLineSegment(1, -1, 0, 0);
+ TestLineSegment c = new TestLineSegment(0, 0, 1, 1);
+ TestLineSegment d = new TestLineSegment(1, 1, -1, 1);
+ TestLineSegment e = new TestLineSegment(-1, 1, -1, -1);
+
+ BoundarySource<TestLineSegment> src = () -> Arrays.asList(a, b, c, d, e).stream();
+
+ // act
+ tree.insert(src);
+
+ // assert
+ List<TestLineSegment> segments = getLineSegments(tree);
+
+ Assert.assertEquals(5, segments.size());
+
+ PartitionTestUtils.assertSegmentsEqual(
+ new TestLineSegment(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, new TestLine(-1, -1, 1, -1)),
+ segments.get(0));
+ PartitionTestUtils.assertSegmentsEqual(
+ new TestLineSegment(-Math.sqrt(2), Double.POSITIVE_INFINITY, new TestLine(1, -1, 0, 0)),
+ segments.get(1));
+ PartitionTestUtils.assertSegmentsEqual(
+ new TestLineSegment(-1, 1, -1, -1),
+ segments.get(2));
+
+ PartitionTestUtils.assertSegmentsEqual(
+ new TestLineSegment(0, Double.POSITIVE_INFINITY, new TestLine(0, 0, 1, 1)),
+ segments.get(3));
+ PartitionTestUtils.assertSegmentsEqual(
+ new TestLineSegment(1, 1, -1, 1),
+ segments.get(4));
+ }
+
+ @Test
+ public void testInsert_boundarySource_emptySource() {
+ // arrange
+ TestBSPTree tree = new TestBSPTree();
+
+ BoundarySource<TestLineSegment> src = () -> new ArrayList<TestLineSegment>().stream();
+
+ // act
+ tree.insert(src);
+
+ // assert
+ Assert.assertEquals(1, tree.count());
+ }
+
+ @Test
public void testCount() {
// arrange
TestBSPTree tree = new TestBSPTree();
@@ -1081,13 +1136,13 @@
}
@Test
- public void testIterable_emptyTree() {
+ public void testNodesIterable_emptyTree() {
// arrange
TestBSPTree tree = new TestBSPTree();
List<TestNode> nodes = new ArrayList<>();
// act
- for (TestNode node : tree) {
+ for (TestNode node : tree.nodes()) {
nodes.add(node);
}
@@ -1097,7 +1152,7 @@
}
@Test
- public void testIterable_multipleNodes() {
+ public void testNodesIterable_multipleNodes() {
// arrange
TestBSPTree tree = new TestBSPTree();
@@ -1112,7 +1167,7 @@
List<TestNode> nodes = new ArrayList<>();
// act
- for (TestNode node : tree) {
+ for (TestNode node : tree.nodes()) {
nodes.add(node);
}
@@ -1131,11 +1186,11 @@
@Test
- public void testIterable_iteratorThrowsNoSuchElementExceptionAtEnd() {
+ public void testNodesIterable_iteratorThrowsNoSuchElementExceptionAtEnd() {
// arrange
TestBSPTree tree = new TestBSPTree();
- Iterator<TestNode> it = tree.iterator();
+ Iterator<TestNode> it = tree.nodes().iterator();
it.next();
// act
@@ -1148,49 +1203,7 @@
}
@Test
- public void testStream_emptyTree() {
- // arrange
- TestBSPTree tree = new TestBSPTree();
-
- // act
- List<TestNode> nodes = tree.stream().collect(Collectors.toList());
-
- // assert
- Assert.assertEquals(1, nodes.size());
- Assert.assertSame(tree.getRoot(), nodes.get(0));
- }
-
- @Test
- public void testStream_multipleNodes() {
- // arrange
- TestBSPTree tree = new TestBSPTree();
-
- TestNode root = tree.getRoot();
- root.cut(TestLine.X_AXIS)
- .getMinus()
- .cut(TestLine.Y_AXIS)
- .getParent()
- .getPlus()
- .cut(TestLine.Y_AXIS);
-
- // act
- List<TestNode> nodes = tree.stream().collect(Collectors.toList());
-
- // assert
- Assert.assertEquals(7, nodes.size());
- Assert.assertSame(root, nodes.get(0));
-
- Assert.assertSame(root.getMinus(), nodes.get(1));
- Assert.assertSame(root.getMinus().getMinus(), nodes.get(2));
- Assert.assertSame(root.getMinus().getPlus(), nodes.get(3));
-
- Assert.assertSame(root.getPlus(), nodes.get(4));
- Assert.assertSame(root.getPlus().getMinus(), nodes.get(5));
- Assert.assertSame(root.getPlus().getPlus(), nodes.get(6));
- }
-
- @Test
- public void testNodeIterable_singleNodeSubtree() {
+ public void testSubtreeNodesIterable_singleNodeSubtree() {
// arrange
TestBSPTree tree = new TestBSPTree();
TestNode node = tree.getRoot().cut(TestLine.X_AXIS)
@@ -1200,7 +1213,7 @@
List<TestNode> nodes = new ArrayList<>();
// act
- for (TestNode n : node) {
+ for (TestNode n : node.nodes()) {
nodes.add(n);
}
@@ -1210,7 +1223,7 @@
}
@Test
- public void testNodeIterable_multipleNodeSubtree() {
+ public void testSubtreeNodesIterable_multipleNodeSubtree() {
// arrange
TestBSPTree tree = new TestBSPTree();
TestNode node = tree.getRoot().cut(TestLine.X_AXIS)
@@ -1219,7 +1232,7 @@
List<TestNode> nodes = new ArrayList<>();
// act
- for (TestNode n : node) {
+ for (TestNode n : node.nodes()) {
nodes.add(n);
}
@@ -1231,41 +1244,6 @@
}
@Test
- public void testNodeStream_singleNodeSubtree() {
- // arrange
- TestBSPTree tree = new TestBSPTree();
- TestNode node = tree.getRoot().cut(TestLine.X_AXIS)
- .getMinus()
- .cut(TestLine.Y_AXIS)
- .getMinus();
-
- // act
- List<TestNode> nodes = node.stream().collect(Collectors.toList());
-
- // assert
- Assert.assertEquals(1, nodes.size());
- Assert.assertSame(node, nodes.get(0));
- }
-
- @Test
- public void testNodeStream_multipleNodeSubtree() {
- // arrange
- TestBSPTree tree = new TestBSPTree();
- TestNode node = tree.getRoot().cut(TestLine.X_AXIS)
- .getMinus()
- .cut(TestLine.Y_AXIS);
-
- // act
- List<TestNode> nodes = node.stream().collect(Collectors.toList());
-
- // assert
- Assert.assertEquals(3, nodes.size());
- Assert.assertSame(node, nodes.get(0));
- Assert.assertSame(node.getMinus(), nodes.get(1));
- Assert.assertSame(node.getPlus(), nodes.get(2));
- }
-
- @Test
public void testCopy_rootOnly() {
// arrange
TestBSPTree tree = new TestBSPTree();
@@ -1909,7 +1887,7 @@
}
private static List<TestLineSegment> getLineSegments(TestBSPTree tree) {
- return tree.stream()
+ return StreamSupport.stream(tree.nodes().spliterator(), false)
.filter(BSPTree.Node::isInternal)
.map(n -> (TestLineSegment) n.getCut())
.collect(Collectors.toList());
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTreeTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTreeTest.java
index d78b268..a634ed2 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTreeTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTreeTest.java
@@ -1036,10 +1036,10 @@
Assert.assertEquals(tree.count(), copy.count());
List<RegionLocation> origLocations = new ArrayList<>();
- tree.forEach(n -> origLocations.add(n.getLocationValue()));
+ tree.nodes().forEach(n -> origLocations.add(n.getLocationValue()));
List<RegionLocation> copyLocations = new ArrayList<>();
- copy.forEach(n -> copyLocations.add(n.getLocationValue()));
+ copy.nodes().forEach(n -> copyLocations.add(n.getLocationValue()));
Assert.assertEquals(origLocations, copyLocations);
}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AttributeBSPTreeTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AttributeBSPTreeTest.java
index 6fe05dd..99b1606 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AttributeBSPTreeTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AttributeBSPTreeTest.java
@@ -157,11 +157,11 @@
root.getMinus().attr("A");
root.getPlus().attr("B");
- root.getMinus().getMinus().forEach(n -> n.attr("a"));
- root.getMinus().getPlus().forEach(n -> n.attr("b"));
+ root.getMinus().getMinus().nodes().forEach(n -> n.attr("a"));
+ root.getMinus().getPlus().nodes().forEach(n -> n.attr("b"));
- root.getPlus().getPlus().forEach(n -> n.attr("c"));
- root.getPlus().getMinus().forEach(n -> n.attr("d"));
+ root.getPlus().getPlus().nodes().forEach(n -> n.attr("c"));
+ root.getPlus().getMinus().nodes().forEach(n -> n.attr("d"));
AttributeBSPTree<TestPoint2D, String> result = new AttributeBSPTree<>();
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1D.java
index 38c8fef..b326213 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1D.java
@@ -239,7 +239,7 @@
* insides node's convex region
*/
private void visitInsideIntervals(final BiConsumer<OrientedPoint, OrientedPoint> visitor) {
- for (RegionNode1D node : this) {
+ for (RegionNode1D node : nodes()) {
if (node.isInside()) {
node.visitNodeInterval(visitor);
}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Boundaries3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Boundaries3D.java
new file mode 100644
index 0000000..6e5a2ae
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Boundaries3D.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+
+/** Utility class for constructing {@link BoundarySource3D} objects to produce common
+ * shapes.
+ */
+public final class Boundaries3D {
+
+ /** Private constructor. */
+ private Boundaries3D() {
+ }
+
+ /** Return a {@link BoundarySource3D} instance defining an axis-aligned rectangular prism. The points {@code a}
+ * and {@code b} are taken to represent opposite corner points in the prism and may be specified in
+ * any order.
+ * @param a first corner point in the prism (opposite of {@code b})
+ * @param b second corner point in the prism (opposite of {@code a})
+ * @param precision precision context used to construct boundary instances
+ * @return a boundary source defining the boundaries of the rectangular prism
+ * @throws IllegalArgumentException if the width, height, or depth of the defined prism is zero
+ * as evaluated by the precision context.
+ */
+ public static BoundarySource3D rect(final Vector3D a, final Vector3D b, final DoublePrecisionContext precision) {
+
+ final double minX = Math.min(a.getX(), b.getX());
+ final double maxX = Math.max(a.getX(), b.getX());
+
+ final double minY = Math.min(a.getY(), b.getY());
+ final double maxY = Math.max(a.getY(), b.getY());
+
+ final double minZ = Math.min(a.getZ(), b.getZ());
+ final double maxZ = Math.max(a.getZ(), b.getZ());
+
+ if (precision.eq(minX, maxX) || precision.eq(minY, maxY) || precision.eq(minZ, maxZ)) {
+ throw new IllegalArgumentException("Rectangular prism has zero size: " + a + ", " + b + ".");
+ }
+
+ final Vector3D[] vertices = {
+ Vector3D.of(minX, minY, minZ),
+ Vector3D.of(maxX, minY, minZ),
+ Vector3D.of(maxX, maxY, minZ),
+ Vector3D.of(minX, maxY, minZ),
+
+ Vector3D.of(minX, minY, maxZ),
+ Vector3D.of(maxX, minY, maxZ),
+ Vector3D.of(maxX, maxY, maxZ),
+ Vector3D.of(minX, maxY, maxZ)
+ };
+
+ List<ConvexSubPlane> facets = Arrays.asList(
+ // -z and +z sides
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertices[0], vertices[3], vertices[2], vertices[1]), precision),
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertices[4], vertices[5], vertices[6], vertices[7]), precision),
+
+ // -x and +x sides
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertices[0], vertices[4], vertices[7], vertices[3]), precision),
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertices[5], vertices[1], vertices[2], vertices[6]), precision),
+
+ // -y and +y sides
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertices[0], vertices[1], vertices[5], vertices[4]), precision),
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertices[3], vertices[7], vertices[6], vertices[2]), precision)
+ );
+
+ return () -> facets.stream();
+ }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySource3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySource3D.java
new file mode 100644
index 0000000..556a559
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySource3D.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed;
+
+import org.apache.commons.geometry.core.partitioning.BoundarySource;
+
+/** Extension of the {@link BoundarySource} interface for Euclidean 3D
+ * space.
+ */
+public interface BoundarySource3D extends BoundarySource<ConvexSubPlane> {
+
+ /** Construct a new BSP tree from the boundaries contained in this
+ * instance.
+ * @return a new BSP tree constructed from the boundaries in this
+ * instance
+ * @see RegionBSPTree3D#from(BoundarySource)
+ */
+ default RegionBSPTree3D toTree() {
+ return RegionBSPTree3D.from(this);
+ }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexVolume.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexVolume.java
index 74ded24..1089027 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexVolume.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexVolume.java
@@ -19,6 +19,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Stream;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.AbstractConvexHyperplaneBoundedRegion;
@@ -30,7 +31,9 @@
/** Class representing a finite or infinite convex volume in Euclidean 3D space.
* The boundaries of this area, if any, are composed of convex subplanes.
*/
-public final class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D, ConvexSubPlane> {
+public final class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D, ConvexSubPlane>
+ implements BoundarySource3D {
+
/** Instance representing the full 3D volume. */
private static final ConvexVolume FULL = new ConvexVolume(Collections.emptyList());
@@ -44,6 +47,12 @@
/** {@inheritDoc} */
@Override
+ public Stream<ConvexSubPlane> boundaryStream() {
+ return getBoundaries().stream();
+ }
+
+ /** {@inheritDoc} */
+ @Override
public double getSize() {
if (isFull()) {
return Double.POSITIVE_INFINITY;
@@ -134,13 +143,6 @@
return transformInternal(transform, this, ConvexSubPlane.class, ConvexVolume::new);
}
- /** Return a BSP tree instance representing the same region as the current instance.
- * @return a BSP tree instance representing the same region as the current instance
- */
- public RegionBSPTree3D toTree() {
- return RegionBSPTree3D.from(this);
- }
-
/** Return an instance representing the full 3D volume.
* @return an instance representing the full 3D volume.
*/
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
index 081d396..db238bc 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
@@ -17,10 +17,11 @@
package org.apache.commons.geometry.euclidean.threed;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.apache.commons.geometry.core.partitioning.BoundarySource;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.SubHyperplane;
@@ -28,15 +29,15 @@
import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
import org.apache.commons.geometry.core.partitioning.bsp.BSPTreeVisitor;
import org.apache.commons.geometry.core.partitioning.bsp.RegionCutBoundary;
-import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
-import org.apache.commons.geometry.euclidean.twod.Polyline;
import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
import org.apache.commons.geometry.euclidean.twod.Vector2D;
/** Binary space partitioning (BSP) tree representing a region in three dimensional
* Euclidean space.
*/
-public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, RegionBSPTree3D.RegionNode3D> {
+public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, RegionBSPTree3D.RegionNode3D>
+ implements BoundarySource3D {
+
/** Create a new, empty region. */
public RegionBSPTree3D() {
this(false);
@@ -70,6 +71,12 @@
/** {@inheritDoc} */
@Override
+ public Stream<ConvexSubPlane> boundaryStream() {
+ return StreamSupport.stream(boundaries().spliterator(), false);
+ }
+
+ /** {@inheritDoc} */
+ @Override
public List<ConvexSubPlane> getBoundaries() {
return createBoundaryList(b -> (ConvexSubPlane) b);
}
@@ -179,26 +186,19 @@
return new RegionBSPTree3D(false);
}
- /** Create a new BSP tree instance representing the same region as the argument.
- * @param volume convex volume instance
- * @return a new BSP tree instance representing the same region as the argument
+ /** Construct a new tree from the boundaries in the given boundary source. If no boundaries
+ * are present in the given source, their the returned tree contains the full space.
+ * @param boundarySrc boundary source to construct a tree from
+ * @return a new tree instance constructed from the boundaries in the
+ * given source
*/
- public static RegionBSPTree3D from(final ConvexVolume volume) {
+ public static RegionBSPTree3D from(final BoundarySource<ConvexSubPlane> boundarySrc) {
RegionBSPTree3D tree = RegionBSPTree3D.full();
- tree.insert(volume.getBoundaries());
+ tree.insert(boundarySrc);
return tree;
}
- /** Create a new {@link RegionBSPTree3D.Builder} instance for creating BSP
- * trees from boundary representations.
- * @param precision precision context to use for floating point comparisons.
- * @return a new builder instance
- */
- public static Builder builder(final DoublePrecisionContext precision) {
- return new Builder(precision);
- }
-
/** BSP tree node for three dimensional Euclidean space.
*/
public static final class RegionNode3D extends AbstractRegionBSPTree.AbstractRegionNode<Vector3D, RegionNode3D> {
@@ -238,259 +238,6 @@
}
}
- /** Class used to construct {@link RegionBSPTree3D} instances from boundary representations.
- */
- public static final class Builder {
-
- /** Precision object used to perform floating point comparisons. This object is
- * used when constructing geometric types.
- */
- private final DoublePrecisionContext precision;
-
- /** The BSP tree being constructed. */
- private final RegionBSPTree3D tree = RegionBSPTree3D.empty();
-
- /** List of vertices to use for indexed facet operations. */
- private List<Vector3D> vertexList;
-
- /** Create a new builder instance. The given precision context will be used when
- * constructing geometric types.
- * @param precision precision object used to perform floating point comparisons
- */
- public Builder(final DoublePrecisionContext precision) {
- this.precision = precision;
- }
-
- /** Set the list of vertices to use for indexed facet operations.
- * @param vertices array of vertices
- * @return this builder instance
- * @see #addIndexedFacet(int...)
- * @see #addIndexedFacet(List)
- * @see #addIndexedFacets(int[][])
- */
- public Builder withVertexList(final Vector3D... vertices) {
- return withVertexList(Arrays.asList(vertices));
- }
-
- /** Set the list of vertices to use for indexed facet operations.
- * @param vertices list of vertices
- * @return this builder instance
- * @see #addIndexedFacet(int...)
- * @see #addIndexedFacet(List)
- * @see #addIndexedFacets(int[][])
- */
- public Builder withVertexList(final List<Vector3D> vertices) {
- this.vertexList = vertices;
- return this;
- }
-
- /** Add a subplane to the tree.
- * @param subplane subplane to add
- * @return this builder instance
- */
- public Builder add(final SubPlane subplane) {
- tree.insert(subplane);
- return this;
- }
-
- /** Add a convex subplane to the tree.
- * @param convex convex subplane to add
- * @return this builder instance
- */
- public Builder add(final ConvexSubPlane convex) {
- tree.insert(convex);
- return this;
- }
-
- /** Add a facet defined by the given array of vertices. The vertices
- * are considered to form a loop, even if the first vertex is not included
- * again at the end of the array.
- * @param vertices array of vertices defining the facet
- * @return this builder instance
- */
- public Builder addFacet(final Vector3D... vertices) {
- return addFacet(Arrays.asList(vertices));
- }
-
- /** Add a facet defined by the given list of vertices. The vertices
- * are considered to form a loop, even if the first vertex is not included
- * again at the end of the list.
- * @param vertices list of vertices defining the facet
- * @return this builder instance
- */
- public Builder addFacet(final List<Vector3D> vertices) {
- // if there are only 3 vertices, then we know for certain that the area is convex
- if (vertices.size() < 4) {
- return add(ConvexSubPlane.fromVertexLoop(vertices, precision));
- }
-
- final Plane plane = Plane.fromPoints(vertices, precision);
- final List<Vector2D> subspaceVertices = plane.toSubspace(vertices);
-
- final Polyline path = Polyline.fromVertexLoop(subspaceVertices, precision);
- return add(new SubPlane(plane, path.toTree()));
- }
-
- /** Add multiple facets, each one defined by an array of indices into the current
- * vertex list.
- * @param facets array of facet definitions, where each definition consists of an
- * array of indices into the current vertex list
- * @return this builder instance
- * @see #withVertexList(List)
- */
- public Builder addIndexedFacets(int[][] facets) {
- for (int[] facet : facets) {
- addIndexedFacet(facet);
- }
-
- return this;
- }
-
- /** Add a facet defined by an array of indices into the current vertex list.
- * @param vertexIndices indices into the current vertex list, defining the vertices
- * for the facet
- * @return this builder instance
- * @see #withVertexList(List)
- */
- public Builder addIndexedFacet(int... vertexIndices) {
- final Vector3D[] vertices = new Vector3D[vertexIndices.length];
- for (int i = 0; i < vertexIndices.length; ++i) {
- vertices[i] = vertexList.get(vertexIndices[i]);
- }
-
- return addFacet(vertices);
- }
-
- /** Add a facet defined by a list of indices into the current vertex list.
- * @param vertexIndices indices into the current vertex list, defining the vertices
- * for the facet
- * @return this builder instance
- * @see #withVertexList(List)
- */
- public Builder addIndexedFacet(final List<Integer> vertexIndices) {
- final List<Vector3D> vertices = vertexIndices.stream()
- .map(idx -> vertexList.get(idx)).collect(Collectors.toList());
-
- return addFacet(vertices);
- }
-
- /** Add an axis-oriented cube with the given dimensions to this instance.
- * @param center the cube center point
- * @param size the size of the cube
- * @return this builder instance
- * @throws IllegalArgumentException if the width, height, or depth of the defined region is zero
- * as evaluated by the precision context.
- */
- public Builder addCenteredCube(final Vector3D center, final double size) {
- return addCenteredRect(center, size, size, size);
- }
-
- /** Add an axis-oriented cube with the given dimensions to this instance.
- * @param corner a corner of the cube
- * @param size the size of the cube
- * @return this builder instance
- * @throws IllegalArgumentException if the width, height, or depth of the defined region is zero
- * as evaluated by the precision context.
- */
- public Builder addCube(final Vector3D corner, final double size) {
- return addRect(corner, size, size, size);
- }
-
- /** Add an axis-oriented rectangular prism to this instance. The prism is centered at the given point and
- * has the specified dimensions.
- * @param center center point for the rectangular prism
- * @param xSize size of the prism along the x-axis
- * @param ySize size of the prism along the y-axis
- * @param zSize size of the prism along the z-axis
- * @return this builder instance
- * @throws IllegalArgumentException if the width, height, or depth of the defined region is zero
- * as evaluated by the precision context.
- */
- public Builder addCenteredRect(final Vector3D center, final double xSize, final double ySize,
- final double zSize) {
-
- return addRect(Vector3D.of(
- center.getX() - (xSize * 0.5),
- center.getY() - (ySize * 0.5),
- center.getZ() - (zSize * 0.5)
- ), xSize, ySize, zSize);
- }
-
- /** Add an axis-oriented rectangular prism to this instance. The prism
- * is constructed by taking {@code pt} as one corner of the region and adding {@code xDelta},
- * {@code yDelta}, and {@code zDelta} to its components to create the opposite corner.
- * @param pt point lying in a corner of the region
- * @param xDelta distance to move along the x axis to place the other points in the
- * prism; this value may be negative.
- * @param yDelta distance to move along the y axis to place the other points in the
- * prism; this value may be negative.
- * @param zDelta distance to move along the z axis to place the other points in the
- * prism; this value may be negative.
- * @return this builder instance
- * @throws IllegalArgumentException if the width, height, or depth of the defined region is zero
- * as evaluated by the precision context.
- */
- public Builder addRect(final Vector3D pt, final double xDelta, final double yDelta, final double zDelta) {
- return addRect(pt, Vector3D.of(
- pt.getX() + xDelta,
- pt.getY() + yDelta,
- pt.getZ() + zDelta));
- }
-
- /** Add an axis-oriented rectangular prism to this instance. The points {@code a} and {@code b}
- * are taken to represent opposite corner points in the prism and may be specified in any order.
- * @param a first corner point in the rectangular prism (opposite of {@code b})
- * @param b second corner point in the rectangular prism (opposite of {@code a})
- * @return this builder instance
- * @throws IllegalArgumentException if the width, height, or depth of the defined region is zero
- * as evaluated by the precision context.
- */
- public Builder addRect(final Vector3D a, final Vector3D b) {
- final double minX = Math.min(a.getX(), b.getX());
- final double maxX = Math.max(a.getX(), b.getX());
-
- final double minY = Math.min(a.getY(), b.getY());
- final double maxY = Math.max(a.getY(), b.getY());
-
- final double minZ = Math.min(a.getZ(), b.getZ());
- final double maxZ = Math.max(a.getZ(), b.getZ());
-
- if (precision.eq(minX, maxX) || precision.eq(minY, maxY) || precision.eq(minZ, maxZ)) {
- throw new IllegalArgumentException("Rectangular prism has zero size: " + a + ", " + b + ".");
- }
-
- final Vector3D[] vertices = {
- Vector3D.of(minX, minY, minZ),
- Vector3D.of(maxX, minY, minZ),
- Vector3D.of(maxX, maxY, minZ),
- Vector3D.of(minX, maxY, minZ),
-
- Vector3D.of(minX, minY, maxZ),
- Vector3D.of(maxX, minY, maxZ),
- Vector3D.of(maxX, maxY, maxZ),
- Vector3D.of(minX, maxY, maxZ)
- };
-
- addFacet(vertices[0], vertices[3], vertices[2], vertices[1]);
- addFacet(vertices[4], vertices[5], vertices[6], vertices[7]);
-
- addFacet(vertices[5], vertices[1], vertices[2], vertices[6]);
- addFacet(vertices[0], vertices[4], vertices[7], vertices[3]);
-
- addFacet(vertices[0], vertices[1], vertices[5], vertices[4]);
- addFacet(vertices[3], vertices[7], vertices[6], vertices[2]);
-
- return this;
- }
-
- /** Get the created BSP tree.
- * @return the created BSP tree
- */
- public RegionBSPTree3D build() {
- return tree;
- }
- }
-
/** Class used to project points onto the 3D region boundary.
*/
private static final class BoundaryProjector3D extends BoundaryProjector<Vector3D, RegionNode3D> {
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Boundaries2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Boundaries2D.java
new file mode 100644
index 0000000..37de050
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Boundaries2D.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+
+/** Utility class for creating {@link BoundarySource2D} instances for generating common
+ * shapes.
+ */
+public final class Boundaries2D {
+
+ /** Private constructor. */
+ private Boundaries2D() {
+ }
+
+ /** Create a {@link BoundarySource2D} defining an axis-aligned rectangular region. The points {@code a}
+ * and {@code b} are taken to represent opposite corner points in the rectangle and may be specified in
+ * any order.
+ * @param a first corner point in the rectangle (opposite of {@code b})
+ * @param b second corner point in the rectangle (opposite of {@code a})
+ * @param precision precision context used to construct prism instances
+ * @return a boundary source defining the boundaries of the rectangular region
+ * @throws IllegalArgumentException if the width or height of the defined rectangle is zero
+ * as evaluated by the precision context.
+ */
+ public static BoundarySource2D rect(final Vector2D a, final Vector2D b,
+ final DoublePrecisionContext precision) {
+
+ final double minX = Math.min(a.getX(), b.getX());
+ final double maxX = Math.max(a.getX(), b.getX());
+
+ final double minY = Math.min(a.getY(), b.getY());
+ final double maxY = Math.max(a.getY(), b.getY());
+
+ if (precision.eq(minX, maxX) || precision.eq(minY, maxY)) {
+ throw new IllegalArgumentException("Rectangle has zero size: " + a + ", " + b + ".");
+ }
+
+ final Vector2D lowerLeft = Vector2D.of(minX, minY);
+ final Vector2D upperLeft = Vector2D.of(minX, maxY);
+
+ final Vector2D upperRight = Vector2D.of(maxX, maxY);
+ final Vector2D lowerRight = Vector2D.of(maxX, minY);
+
+ List<Segment> segments = Arrays.asList(
+ Segment.fromPoints(lowerLeft, lowerRight, precision),
+ Segment.fromPoints(upperRight, upperLeft, precision),
+ Segment.fromPoints(lowerRight, upperRight, precision),
+ Segment.fromPoints(upperLeft, lowerLeft, precision)
+ );
+
+ return () -> segments.stream();
+ }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySource2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySource2D.java
new file mode 100644
index 0000000..ade62b3
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySource2D.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import org.apache.commons.geometry.core.partitioning.BoundarySource;
+
+/** Extension of the {@link BoundarySource} interface for Euclidean 2D
+ * space.
+ */
+public interface BoundarySource2D extends BoundarySource<Segment> {
+
+ /** Construct a new BSP tree from the boundaries contained in this
+ * instance.
+ * @return a new BSP tree constructed from the boundaries in this
+ * instance
+ * @see RegionBSPTree2D#from(BoundarySource)
+ */
+ default RegionBSPTree2D toTree() {
+ return RegionBSPTree2D.from(this);
+ }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ConvexArea.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ConvexArea.java
index bc5acc3..87594f9 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ConvexArea.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ConvexArea.java
@@ -21,6 +21,8 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.AbstractConvexHyperplaneBoundedRegion;
@@ -32,7 +34,9 @@
/** Class representing a finite or infinite convex area in Euclidean 2D space.
* The boundaries of this area, if any, are composed of line segments.
*/
-public final class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D, Segment> {
+public final class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D, Segment>
+ implements BoundarySource2D {
+
/** Instance representing the full 2D plane. */
private static final ConvexArea FULL = new ConvexArea(Collections.emptyList());
@@ -44,6 +48,12 @@
super(boundaries);
}
+ /** {@inheritDoc} */
+ @Override
+ public Stream<Segment> boundaryStream() {
+ return getBoundaries().stream();
+ }
+
/** Get the connected line segment paths comprising the boundary of the area. The
* segments are oriented so that their minus sides point toward the interior of the
* region. The size of the returned list is
@@ -155,13 +165,6 @@
return splitInternal(splitter, this, Segment.class, ConvexArea::new);
}
- /** Return a BSP tree instance representing the same region as the current instance.
- * @return a BSP tree instance representing the same region as the current instance
- */
- public RegionBSPTree2D toTree() {
- return RegionBSPTree2D.from(this);
- }
-
/** Return an instance representing the full 2D area.
* @return an instance representing the full 2D area.
*/
@@ -247,10 +250,9 @@
* @return a convex area constructed from the lines in the given path
*/
public static ConvexArea fromPath(final Polyline path) {
- final List<Line> lines = new ArrayList<>();
- for (Segment segment : path) {
- lines.add(segment.getLine());
- }
+ final List<Line> lines = path.boundaryStream()
+ .map(Segment::getLine)
+ .collect(Collectors.toList());
return fromBounds(lines);
}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Polyline.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Polyline.java
index 9ef8a1c..9ceb1b6 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Polyline.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Polyline.java
@@ -21,9 +21,9 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
@@ -36,7 +36,7 @@
* <p>Instances of this class are guaranteed to be immutable.</p>
* @see <a href="https://en.wikipedia.org/wiki/Polygonal_chain">Polygonal chain</a>
*/
-public class Polyline implements Iterable<Segment> {
+public class Polyline implements BoundarySource2D {
/** Polyline instance containing no segments. */
private static final Polyline EMPTY = new Polyline(Collections.emptyList());
@@ -50,6 +50,12 @@
this.segments = Collections.unmodifiableList(segments);
}
+ /** {@inheritDoc} */
+ @Override
+ public Stream<Segment> boundaryStream() {
+ return getSegments().stream();
+ }
+
/** Get the line segments comprising the polyline.
* @return the line segments comprising the polyline
*/
@@ -198,16 +204,6 @@
return this;
}
- /** Construct a {@link RegionBSPTree2D} from the line segments in this instance.
- * @return a bsp tree constructed from the line segments in this instance
- */
- public RegionBSPTree2D toTree() {
- RegionBSPTree2D tree = RegionBSPTree2D.empty();
- tree.insert(this);
-
- return tree;
- }
-
/** Simplify this polyline, if possible, by combining adjacent segments that lie on the
* same line (as determined by {@link Line#equals(Object)}).
* @return a simplified instance
@@ -261,12 +257,6 @@
return new SimplifiedPolyline(simplified);
}
- /** {@inheritDoc} */
- @Override
- public Iterator<Segment> iterator() {
- return segments.iterator();
- }
-
/** Return a string representation of the segment polyline.
*
* <p>In order to keep the string representation short but useful, the exact format of the return
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2D.java
index 25812ed..8aa61b7 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2D.java
@@ -20,17 +20,21 @@
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.apache.commons.geometry.core.partitioning.BoundarySource;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
-import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
/** Binary space partitioning (BSP) tree representing a region in two dimensional
* Euclidean space.
*/
-public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, RegionBSPTree2D.RegionNode2D> {
+public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, RegionBSPTree2D.RegionNode2D>
+ implements BoundarySource2D {
+
/** List of line segment paths comprising the region boundary. */
private List<Polyline> boundaryPaths;
@@ -68,6 +72,12 @@
/** {@inheritDoc} */
@Override
+ public Stream<Segment> boundaryStream() {
+ return StreamSupport.stream(boundaries().spliterator(), false);
+ }
+
+ /** {@inheritDoc} */
+ @Override
public List<Segment> getBoundaries() {
return createBoundaryList(b -> (Segment) b);
}
@@ -244,27 +254,19 @@
return new RegionBSPTree2D(false);
}
- /** Construct a tree from a convex area.
- * @param area the area to construct a tree from
- * @return tree instance representing the same area as the given
- * convex area
+ /** Construct a new tree from the boundaries in the given boundary source. If no boundaries
+ * are present in the given source, their the returned tree contains the full space.
+ * @param boundarySrc boundary source to construct a tree from
+ * @return a new tree instance constructed from the boundaries in the
+ * given source
*/
- public static RegionBSPTree2D from(final ConvexArea area) {
+ public static RegionBSPTree2D from(final BoundarySource<Segment> boundarySrc) {
final RegionBSPTree2D tree = RegionBSPTree2D.full();
- tree.insert(area.getBoundaries());
+ tree.insert(boundarySrc);
return tree;
}
- /** Create a new {@link RegionBSPTree2D.Builder} instance for creating BSP
- * trees from boundary representations.
- * @param precision precision context to use for floating point comparisons.
- * @return a new builder instance
- */
- public static Builder builder(final DoublePrecisionContext precision) {
- return new Builder(precision);
- }
-
/** BSP tree node for two dimensional Euclidean space.
*/
public static final class RegionNode2D extends AbstractRegionBSPTree.AbstractRegionNode<Vector2D, RegionNode2D> {
@@ -304,166 +306,6 @@
}
}
- /** Class used to construct {@link RegionBSPTree2D} instances from boundary representations.
- */
- public static final class Builder {
-
- /** Precision object used to perform floating point comparisons. This object is
- * used when constructing geometric types.
- */
- private final DoublePrecisionContext precision;
-
- /** The BSP tree being constructed. */
- private final RegionBSPTree2D tree = RegionBSPTree2D.empty();
-
- /** Create a new builder instance. The given precision context will be used when
- * constructing geometric types.
- * @param precision precision object used to perform floating point comparisons
- */
- public Builder(final DoublePrecisionContext precision) {
- this.precision = precision;
- }
-
- /** Add a subline to the tree.
- * @param subline subline to add
- * @return this builder instance
- */
- public Builder add(final SubLine subline) {
- tree.insert(subline);
- return this;
- }
-
- /** Add a segment to the tree.
- * @param segment segment to add
- * @return this builder instance
- */
- public Builder add(final Segment segment) {
- tree.insert(segment);
- return this;
- }
-
- /** Add the line segments defined in the given segment path.
- * @param path path containing line segments to add
- * @return this builder instance
- */
- public Builder add(final Polyline path) {
- for (Segment segment : path) {
- add(segment);
- }
-
- return this;
- }
-
- /** Add a segment defined by the given points.
- * @param start start point
- * @param end end point
- * @return this builder instance
- */
- public Builder addSegment(final Vector2D start, final Vector2D end) {
- return add(Segment.fromPoints(start, end, precision));
- }
-
- /** Add segments defining an axis-oriented square with the given corner point and size.
- * @param center center point of the square
- * @param size the size of the square
- * @return this builder instance
- * @throws IllegalArgumentException if the width or height of the defined rectangle is zero
- * as evaluated by the precision context.
- */
- public Builder addCenteredSquare(final Vector2D center, final double size) {
- return addCenteredRect(center, size, size);
- }
-
- /** Add segments defining an axis-oriented square with the given corner point and size.
- * @param corner point in the corner of the square
- * @param size the size of the square
- * @return this builder instance
- * @throws IllegalArgumentException if the width or height of the defined rectangle is zero
- * as evaluated by the precision context.
- */
- public Builder addSquare(final Vector2D corner, final double size) {
- return addRect(corner, size, size);
- }
-
- /** Add segments defining an axis-oriented rectangular region with the given center point and size.
- * @param center center point for the region
- * @param xSize size along the x-axis
- * @param ySize size along the y-axis
- * @return this builder instance
- * @throws IllegalArgumentException if the width or height of the defined rectangle is zero
- * as evaluated by the precision context.
- */
- public Builder addCenteredRect(final Vector2D center, final double xSize, final double ySize) {
- return addRect(Vector2D.of(
- center.getX() - (0.5 * xSize),
- center.getY() - (0.5 * ySize)
- ), xSize, ySize);
- }
-
- /** Add segments defining an axis-oriented rectangular region. The region
- * is constructed by taking {@code pt} as one corner of the region and adding {@code xDelta}
- * and {@code yDelta} to its components to create the opposite corner. If {@code xDelta}
- * and {@code yDelta} are both positive, then the constructed rectangle will have {@code pt}
- * as its lower-left corner and will have a width and height of {@code xDelta} and {@code yDelta}
- * respectively.
- * @param pt point lying in a corner of the region
- * @param xDelta distance to move along the x axis to place the other points in the
- * rectangle; this value may be negative, in which case {@code pt} will lie
- * on the right side of the constructed rectangle
- * @param yDelta distance to move along the y axis to place the other points in the
- * rectangle; this value may be negative, in which case {@code pt} will lie
- * on the top of the rectangle
- * @return this builder instance
- * @throws IllegalArgumentException if the width or height of the defined rectangle is zero
- * as evaluated by the precision context.
- */
- public Builder addRect(final Vector2D pt, final double xDelta, final double yDelta) {
- return addRect(pt, Vector2D.of(
- pt.getX() + xDelta,
- pt.getY() + yDelta));
- }
-
- /** Add segments defining an axis-oriented rectangular region. The points {@code a} and {@code b}
- * are taken to represent opposite corner points in the rectangle and may be specified in any order.
- * @param a first corner point in the rectangle (opposite of {@code b})
- * @param b second corner point in the rectangle (opposite of {@code a})
- * @return this builder instance
- * @throws IllegalArgumentException if the width or height of the defined rectangle is zero
- * as evaluated by the precision context.
- */
- public Builder addRect(final Vector2D a, final Vector2D b) {
- final double minX = Math.min(a.getX(), b.getX());
- final double maxX = Math.max(a.getX(), b.getX());
-
- final double minY = Math.min(a.getY(), b.getY());
- final double maxY = Math.max(a.getY(), b.getY());
-
- if (precision.eq(minX, maxX) || precision.eq(minY, maxY)) {
- throw new IllegalArgumentException("Rectangle has zero size: " + a + ", " + b + ".");
- }
-
- final Vector2D lowerLeft = Vector2D.of(minX, minY);
- final Vector2D upperLeft = Vector2D.of(minX, maxY);
-
- final Vector2D upperRight = Vector2D.of(maxX, maxY);
- final Vector2D lowerRight = Vector2D.of(maxX, minY);
-
- addSegment(lowerLeft, lowerRight);
- addSegment(upperRight, upperLeft);
- addSegment(lowerRight, upperRight);
- addSegment(upperLeft, lowerLeft);
-
- return this;
- }
-
- /** Get the created BSP tree.
- * @return the created BSP tree
- */
- public RegionBSPTree2D build() {
- return tree;
- }
- }
-
/** Class used to project points onto the 2D region boundary.
*/
private static final class BoundaryProjector2D extends BoundaryProjector<Vector2D, RegionNode2D> {
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/DocumentationExamplesTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/DocumentationExamplesTest.java
index f77ba4d..cf6818e 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/DocumentationExamplesTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/DocumentationExamplesTest.java
@@ -28,6 +28,7 @@
import org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D;
import org.apache.commons.geometry.euclidean.oned.Vector1D;
import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
+import org.apache.commons.geometry.euclidean.threed.Boundaries3D;
import org.apache.commons.geometry.euclidean.threed.ConvexSubPlane;
import org.apache.commons.geometry.euclidean.threed.Line3D;
import org.apache.commons.geometry.euclidean.threed.Plane;
@@ -59,9 +60,9 @@
// create a binary space partitioning tree representing the unit cube
// centered on the origin
- RegionBSPTree3D region = RegionBSPTree3D.builder(precision)
- .addRect(Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5))
- .build();
+ RegionBSPTree3D region = Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), precision)
+ .toTree();
// create a rotated copy of the region
Transform3D rotation = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, 0.25 * Math.PI);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Boundaries3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Boundaries3DTest.java
new file mode 100644
index 0000000..469913a
--- /dev/null
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Boundaries3DTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Boundaries3DTest {
+
+ private static final double TEST_EPS = 1e-10;
+
+ private static final DoublePrecisionContext TEST_PRECISION =
+ new EpsilonDoublePrecisionContext(TEST_EPS);
+
+ @Test
+ public void testRect_minFirst() {
+ // act
+ List<ConvexSubPlane> boundaries = Boundaries3D.rect(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION)
+ .boundaryStream()
+ .collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(6, boundaries.size());
+
+ Vector3D b1 = Vector3D.of(1, 2, 3);
+ Vector3D b2 = Vector3D.of(4, 2, 3);
+ Vector3D b3 = Vector3D.of(4, 5, 3);
+ Vector3D b4 = Vector3D.of(1, 5, 3);
+
+ Vector3D t1 = Vector3D.of(1, 2, 6);
+ Vector3D t2 = Vector3D.of(4, 2, 6);
+ Vector3D t3 = Vector3D.of(4, 5, 6);
+ Vector3D t4 = Vector3D.of(1, 5, 6);
+
+ checkVertices(boundaries.get(0), b1, b4, b3, b2, b1);
+ checkVertices(boundaries.get(1), t1, t2, t3, t4, t1);
+
+ checkVertices(boundaries.get(2), b1, t1, t4, b4, b1);
+ checkVertices(boundaries.get(3), t2, b2, b3, t3, t2);
+
+ checkVertices(boundaries.get(4), b1, b2, t2, t1, b1);
+ checkVertices(boundaries.get(5), b4, t4, t3, b3, b4);
+ }
+
+ @Test
+ public void testRect_maxFirst() {
+ // act
+ List<ConvexSubPlane> boundaries = Boundaries3D.rect(Vector3D.of(4, 5, 6), Vector3D.of(1, 2, 3), TEST_PRECISION)
+ .boundaryStream()
+ .collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(6, boundaries.size());
+
+ Vector3D b1 = Vector3D.of(1, 2, 3);
+ Vector3D b2 = Vector3D.of(4, 2, 3);
+ Vector3D b3 = Vector3D.of(4, 5, 3);
+ Vector3D b4 = Vector3D.of(1, 5, 3);
+
+ Vector3D t1 = Vector3D.of(1, 2, 6);
+ Vector3D t2 = Vector3D.of(4, 2, 6);
+ Vector3D t3 = Vector3D.of(4, 5, 6);
+ Vector3D t4 = Vector3D.of(1, 5, 6);
+
+ checkVertices(boundaries.get(0), b1, b4, b3, b2, b1);
+ checkVertices(boundaries.get(1), t1, t2, t3, t4, t1);
+
+ checkVertices(boundaries.get(2), b1, t1, t4, b4, b1);
+ checkVertices(boundaries.get(3), t2, b2, b3, t3, t2);
+
+ checkVertices(boundaries.get(4), b1, b2, t2, t1, b1);
+ checkVertices(boundaries.get(5), b4, t4, t3, b3, b4);
+ }
+
+ @Test
+ public void testRect_toTree() {
+ // arrange
+ BoundarySource3D src = Boundaries3D.rect(Vector3D.of(1, 2, 3), Vector3D.of(4, 5, 6), TEST_PRECISION);
+
+ // act
+ RegionBSPTree3D tree = src.toTree();
+
+ // assert
+ Assert.assertEquals(27, tree.getSize(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(2.5, 3.5, 4.5), tree.getBarycenter(), TEST_EPS);
+ }
+
+ @Test
+ public void testRect_illegalArgs() {
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ Boundaries3D.rect(Vector3D.of(1, 2, 3), Vector3D.of(1, 5, 6), TEST_PRECISION);
+ }, IllegalArgumentException.class);
+
+ GeometryTestUtils.assertThrows(() -> {
+ Boundaries3D.rect(Vector3D.of(1, 2, 3), Vector3D.of(4, 2, 6), TEST_PRECISION);
+ }, IllegalArgumentException.class);
+
+ GeometryTestUtils.assertThrows(() -> {
+ Boundaries3D.rect(Vector3D.of(1, 2, 3), Vector3D.of(1, 5, 3), TEST_PRECISION);
+ }, IllegalArgumentException.class);
+ }
+
+ private static void checkVertices(ConvexSubPlane sp, Vector3D... pts) {
+ List<Vector3D> actual = sp.getPlane().toSpace(
+ sp.getSubspaceRegion().getBoundaryPaths().get(0).getVertices());
+
+ Assert.assertEquals(pts.length, actual.size());
+
+ for (int i = 0; i < pts.length; ++i) {
+ EuclideanTestUtils.assertCoordinatesEqual(pts[i], actual.get(i), TEST_EPS);
+ }
+ }
+}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/ConvexVolumeTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/ConvexVolumeTest.java
index 461e9c4..cd748e6 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/ConvexVolumeTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/ConvexVolumeTest.java
@@ -18,6 +18,7 @@
import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.RegionLocation;
@@ -55,7 +56,36 @@
}
@Test
- public void testTOTree() {
+ public void testBoundaryStream() {
+ // arrange
+ Plane plane = Plane.fromNormal(Vector3D.Unit.PLUS_Z, TEST_PRECISION);
+ ConvexVolume volume = ConvexVolume.fromBounds(plane);
+
+ // act
+ List<ConvexSubPlane> boundaries = volume.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(1, boundaries.size());
+
+ ConvexSubPlane sp = boundaries.get(0);
+ Assert.assertEquals(0, sp.getSubspaceRegion().getBoundaries().size());
+ Assert.assertSame(plane, sp.getPlane());
+ }
+
+ @Test
+ public void testBoundaryStream_noBoundaries() {
+ // arrange
+ ConvexVolume volume = ConvexVolume.full();
+
+ // act
+ List<ConvexSubPlane> boundaries = volume.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(0, boundaries.size());
+ }
+
+ @Test
+ public void testToTree() {
// arrange
ConvexVolume volume = ConvexVolume.fromBounds(
Plane.fromPointAndNormal(Vector3D.ZERO, Vector3D.Unit.MINUS_X, TEST_PRECISION),
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3DTest.java
index 63397b0..8dbcc6b 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3DTest.java
@@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.RegionLocation;
@@ -124,9 +125,7 @@
@Test
public void testBoundaries() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addRect(Vector3D.ZERO, Vector3D.of(1, 1, 1))
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(1, 1, 1), TEST_PRECISION).toTree();
// act
List<ConvexSubPlane> subplanes = new ArrayList<>();
@@ -139,9 +138,7 @@
@Test
public void testGetBoundaries() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addRect(Vector3D.ZERO, Vector3D.of(1, 1, 1))
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(1, 1, 1), TEST_PRECISION).toTree();
// act
List<ConvexSubPlane> subplanes = tree.getBoundaries();
@@ -151,6 +148,46 @@
}
@Test
+ public void testBoundaryStream() {
+ // arrange
+ RegionBSPTree3D tree = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(1, 1, 1), TEST_PRECISION)
+ .toTree();
+
+ // act
+ List<ConvexSubPlane> subplanes = tree.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(6, subplanes.size());
+ }
+
+ @Test
+ public void testBoundaryStream_noBoundaries() {
+ // arrange
+ RegionBSPTree3D tree = RegionBSPTree3D.full();
+
+ // act
+ List<ConvexSubPlane> subplanes = tree.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(0, subplanes.size());
+ }
+
+ @Test
+ public void testToTree_returnsNewTree() {
+ // arrange
+ RegionBSPTree3D tree = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(1, 2, 1), TEST_PRECISION)
+ .toTree();
+
+ // act
+ RegionBSPTree3D result = tree.toTree();
+
+ // assert
+ Assert.assertEquals(6, result.getBoundaries().size());
+ Assert.assertEquals(2, result.getSize(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 1, 0.5), result.getBarycenter(), TEST_EPS);
+ }
+
+ @Test
public void testHalfSpace() {
// act
RegionBSPTree3D tree = RegionBSPTree3D.empty();
@@ -236,9 +273,8 @@
@Test
public void testRaycastFirstFace() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.ZERO, 2)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(Vector3D.of(-1, -1, -1), Vector3D.of(1, 1, 1), TEST_PRECISION)
+ .toTree();
Line3D xPlus = Line3D.fromPoints(Vector3D.ZERO, Vector3D.of(1, 0, 0), TEST_PRECISION);
Line3D xMinus = Line3D.fromPoints(Vector3D.ZERO, Vector3D.of(-1, 0, 0), TEST_PRECISION);
@@ -289,9 +325,7 @@
Vector3D upperCorner = Vector3D.of(1, 1, 1);
Vector3D center = lowerCorner.lerp(upperCorner, 0.5);
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addRect(lowerCorner, upperCorner)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(lowerCorner, upperCorner, TEST_PRECISION).toTree();
Line3D upDiagonal = Line3D.fromPoints(lowerCorner, upperCorner, TEST_PRECISION);
Line3D downDiagonal = upDiagonal.reverse();
@@ -321,9 +355,7 @@
Vector3D lowerCorner = Vector3D.ZERO;
Vector3D upperCorner = Vector3D.of(1, 1, 1);
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addRect(lowerCorner, upperCorner)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(lowerCorner, upperCorner, TEST_PRECISION).toTree();
Vector3D firstPointOnLine = Vector3D.of(0.5, -1.0, 0);
Vector3D secondPointOnLine = Vector3D.of(0.5, 2.0, 0);
@@ -350,9 +382,7 @@
Vector3D lowerCorner = Vector3D.ZERO;
Vector3D upperCorner = Vector3D.of(1, 1, 1);
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addRect(lowerCorner, upperCorner)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(lowerCorner, upperCorner, TEST_PRECISION).toTree();
Vector3D pt = Vector3D.of(0.5, 0.5, 0);
Line3D intoBoxLine = Line3D.fromPoints(pt, pt.add(Vector3D.Unit.PLUS_Z), TEST_PRECISION);
@@ -374,9 +404,7 @@
Vector3D lowerCorner = Vector3D.ZERO;
Vector3D upperCorner = Vector3D.of(1, 1, 1);
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addRect(lowerCorner, upperCorner)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(lowerCorner, upperCorner, TEST_PRECISION).toTree();
Line3D intoBoxLine = Line3D.fromPoints(lowerCorner, upperCorner, TEST_PRECISION);
Line3D outOfBoxLine = intoBoxLine.reverse();
@@ -397,9 +425,7 @@
Vector3D lowerCorner = Vector3D.ZERO;
Vector3D upperCorner = Vector3D.of(1, 1, 1);
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addRect(lowerCorner, upperCorner)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(lowerCorner, upperCorner, TEST_PRECISION).toTree();
Line3D line = Line3D.fromPointAndDirection(Vector3D.of(0.5, 0.5, 0.5), Vector3D.Unit.PLUS_X, TEST_PRECISION);
@@ -421,9 +447,8 @@
@Test
public void testInvertedRegion() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.ZERO, 1)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), TEST_PRECISION).toTree();
// act
tree.complement();
@@ -448,9 +473,8 @@
@Test
public void testUnitBox() {
// act
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.ZERO, 1)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), TEST_PRECISION).toTree();
// assert
Assert.assertFalse(tree.isEmpty());
@@ -511,12 +535,12 @@
public void testTwoBoxes_disjoint() {
// act
RegionBSPTree3D tree = RegionBSPTree3D.empty();
- tree.union(RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.ZERO, 1)
- .build());
- tree.union(RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.of(2, 0, 0), 1)
- .build());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), TEST_PRECISION)
+ .toTree());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(1.5, -0.5, -0.5), Vector3D.of(2.5, 0.5, 0.5), TEST_PRECISION)
+ .toTree());
// assert
Assert.assertFalse(tree.isEmpty());
@@ -540,12 +564,12 @@
public void testTwoBoxes_sharedSide() {
// act
RegionBSPTree3D tree = RegionBSPTree3D.empty();
- tree.union(RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.ZERO, 1)
- .build());
- tree.union(RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.of(1, 0, 0), 1)
- .build());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), TEST_PRECISION)
+ .toTree());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(0.5, -0.5, -0.5), Vector3D.of(1.5, 0.5, 0.5), TEST_PRECISION)
+ .toTree());
// assert
Assert.assertFalse(tree.isEmpty());
@@ -572,12 +596,12 @@
// act
RegionBSPTree3D tree = RegionBSPTree3D.empty();
- tree.union(RegionBSPTree3D.builder(precision)
- .addCenteredCube(Vector3D.ZERO, 1)
- .build());
- tree.union(RegionBSPTree3D.builder(precision)
- .addCenteredCube(Vector3D.of(1 + 1e-7, 0, 0), 1)
- .build());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), precision)
+ .toTree());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(0.5 + 1e-7, -0.5, -0.5), Vector3D.of(1.5 + 1e-7, 0.5, 0.5), precision)
+ .toTree());
// assert
Assert.assertFalse(tree.isEmpty());
@@ -600,12 +624,12 @@
public void testTwoBoxes_sharedEdge() {
// act
RegionBSPTree3D tree = RegionBSPTree3D.empty();
- tree.union(RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.ZERO, 1)
- .build());
- tree.union(RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.of(1, 1, 0), 1)
- .build());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), TEST_PRECISION)
+ .toTree());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(0.5, 0.5, -0.5), Vector3D.of(1.5, 1.5, 0.5), TEST_PRECISION)
+ .toTree());
// assert
Assert.assertFalse(tree.isEmpty());
@@ -631,12 +655,12 @@
public void testTwoBoxes_sharedPoint() {
// act
RegionBSPTree3D tree = RegionBSPTree3D.empty();
- tree.union(RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.ZERO, 1)
- .build());
- tree.union(RegionBSPTree3D.builder(TEST_PRECISION)
- .addCenteredCube(Vector3D.of(1, 1, 1), 1)
- .build());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), TEST_PRECISION)
+ .toTree());
+ tree.union(Boundaries3D.rect(
+ Vector3D.of(0.5, 0.5, 0.5), Vector3D.of(1.5, 1.5, 1.5), TEST_PRECISION)
+ .toTree());
// assert
Assert.assertFalse(tree.isEmpty());
@@ -665,13 +689,16 @@
Vector3D vertex3 = Vector3D.of(2, 3, 3);
Vector3D vertex4 = Vector3D.of(1, 3, 4);
+ List<ConvexSubPlane> boundaries = Arrays.asList(
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertex3, vertex2, vertex1), TEST_PRECISION),
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertex2, vertex3, vertex4), TEST_PRECISION),
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertex4, vertex3, vertex1), TEST_PRECISION),
+ ConvexSubPlane.fromVertexLoop(Arrays.asList(vertex1, vertex2, vertex4), TEST_PRECISION)
+ );
+
// act
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addFacet(vertex3, vertex2, vertex1)
- .addFacet(vertex2, vertex3, vertex4)
- .addFacet(vertex4, vertex3, vertex1)
- .addFacet(vertex1, vertex2, vertex4)
- .build();
+ RegionBSPTree3D tree = RegionBSPTree3D.full();
+ tree.insert(boundaries);
// assert
Assert.assertEquals(1.0 / 3.0, tree.getSize(), TEST_EPS);
@@ -735,9 +762,8 @@
@Test
public void testProjectToBoundary() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.ZERO, 1)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(1, 1, 1), TEST_PRECISION)
+ .toTree();
// act/assert
checkProject(tree, Vector3D.of(0.5, 0.5, 0.5), Vector3D.of(0, 0.5, 0.5));
@@ -749,9 +775,8 @@
@Test
public void testProjectToBoundary_invertedRegion() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.ZERO, 1)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(1, 1, 1), TEST_PRECISION)
+ .toTree();
tree.complement();
@@ -773,9 +798,8 @@
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
- RegionBSPTree3D box = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.ZERO, size)
- .build();
+ RegionBSPTree3D box = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(size, size, size), TEST_PRECISION)
+ .toTree();
RegionBSPTree3D sphere = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
// act
@@ -855,9 +879,8 @@
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
- RegionBSPTree3D box = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.ZERO, size)
- .build();
+ RegionBSPTree3D box = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(size, size, size), TEST_PRECISION)
+ .toTree();
RegionBSPTree3D sphere = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
// act
@@ -933,12 +956,11 @@
public void testBoolean_xor_twoCubes() throws IOException {
// arrange
double size = 1.0;
- RegionBSPTree3D box1 = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.ZERO, size)
- .build();
- RegionBSPTree3D box2 = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.of(0.5, 0.5, 0.5), size)
- .build();
+ RegionBSPTree3D box1 = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(size, size, size), TEST_PRECISION)
+ .toTree();
+ RegionBSPTree3D box2 = Boundaries3D.rect(
+ Vector3D.of(0.5, 0.5, 0.5), Vector3D.of(0.5 + size, 0.5 + size, 0.5 + size), TEST_PRECISION)
+ .toTree();
// act
RegionBSPTree3D result = RegionBSPTree3D.empty();
@@ -975,9 +997,8 @@
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
- RegionBSPTree3D box = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.ZERO, size)
- .build();
+ RegionBSPTree3D box = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(size, size, size), TEST_PRECISION)
+ .toTree();
RegionBSPTree3D sphere = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
// act
@@ -1053,9 +1074,8 @@
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
- RegionBSPTree3D box = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.ZERO, size)
- .build();
+ RegionBSPTree3D box = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(size, size, size), TEST_PRECISION)
+ .toTree();
RegionBSPTree3D sphere = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
// act
@@ -1129,9 +1149,8 @@
double tolerance = 0.05;
double size = 1.0;
double radius = size * 0.5;
- RegionBSPTree3D box = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.ZERO, size)
- .build();
+ RegionBSPTree3D box = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(size, size, size), TEST_PRECISION)
+ .toTree();
RegionBSPTree3D sphereToAdd = createSphere(Vector3D.of(size * 0.5, size * 0.5, size), radius, 8, 16);
RegionBSPTree3D sphereToRemove1 = createSphere(Vector3D.of(size * 0.5, 0, size * 0.5), radius, 8, 16);
RegionBSPTree3D sphereToRemove2 = createSphere(Vector3D.of(size * 0.5, 1, size * 0.5), radius, 8, 16);
@@ -1180,9 +1199,9 @@
@Test
public void testToConvex_singleBox() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.of(1, 2, 3), 1)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(
+ Vector3D.of(1, 2, 3), Vector3D.of(2, 3, 4), TEST_PRECISION)
+ .toTree();
// act
List<ConvexVolume> result = tree.toConvex();
@@ -1198,12 +1217,10 @@
@Test
public void testToConvex_multipleBoxes() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.of(4, 5, 6), 1)
- .build();
- tree.union(RegionBSPTree3D.builder(TEST_PRECISION)
- .addRect(Vector3D.ZERO, 2, 1, 1)
- .build());
+ RegionBSPTree3D tree = Boundaries3D.rect(Vector3D.of(4, 5, 6), Vector3D.of(5, 6, 7), TEST_PRECISION)
+ .toTree();
+ tree.union(Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(2, 1, 1), TEST_PRECISION)
+ .toTree());
// act
List<ConvexVolume> result = tree.toConvex();
@@ -1226,9 +1243,9 @@
@Test
public void testSplit() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.of(-0.5, -0.5, -0.5), 1)
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), TEST_PRECISION)
+ .toTree();
Plane splitter = Plane.fromNormal(Vector3D.Unit.PLUS_X, TEST_PRECISION);
@@ -1250,9 +1267,8 @@
@Test
public void testGetNodeRegion() {
// arrange
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addRect(Vector3D.ZERO, Vector3D.of(1, 1, 1))
- .build();
+ RegionBSPTree3D tree = Boundaries3D.rect(Vector3D.ZERO, Vector3D.of(1, 1, 1), TEST_PRECISION)
+ .toTree();
// act/assert
ConvexVolume rootVol = tree.getRoot().getNodeRegion();
@@ -1268,237 +1284,6 @@
EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 0.5), centerVol.getBarycenter(), TEST_EPS);
}
- @Test
- public void testBuilder_nothingAdded() {
- // act
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION).build();
-
- // assert
- Assert.assertTrue(tree.isEmpty());
- }
-
- @Test
- public void testBuilder_rectMethods() {
- // act
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
-
- .addRect(Vector3D.ZERO, Vector3D.of(2, 1, 1))
- .addRect(Vector3D.of(0, 0, -1), Vector3D.of(-1, -2, -2))
-
- .addRect(Vector3D.of(0, 0, 5), 1, 2, 3)
- .addRect(Vector3D.of(0, 0, 10), -3, -2, -1)
-
- .addCenteredRect(Vector3D.of(0, 0, 15), 2, 3, 4)
- .addCenteredRect(Vector3D.of(0, 0, 20), 4, 3, 2)
-
- .build();
-
- // act
- Assert.assertEquals(2 + 2 + 6 + 6 + 24 + 24, tree.getSize(), TEST_EPS);
-
- EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.INSIDE,
- Vector3D.of(1, 0.5, 0.5),
- Vector3D.of(-0.5, -1, -1.5),
-
- Vector3D.of(0.5, 1, 6.5),
- Vector3D.of(-1.5, -1, 9.5),
-
- Vector3D.of(0, 0, 15),
- Vector3D.of(0, 0, 20));
-
- EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.BOUNDARY,
- Vector3D.of(-1, -1.5, 13),
- Vector3D.of(1, 1.5, 17),
-
- Vector3D.of(-2, -1.5, 19),
- Vector3D.of(2, 1.5, 21));
- }
-
- @Test
- public void testBuilder_rectMethods_invalidDimensions() {
- // act/assert
- GeometryTestUtils.assertThrows(() -> {
- RegionBSPTree3D.builder(TEST_PRECISION).addRect(Vector3D.ZERO, 1e-20, 1, 1);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- RegionBSPTree3D.builder(TEST_PRECISION).addRect(Vector3D.ZERO, 1, 1e-20, 1);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- RegionBSPTree3D.builder(TEST_PRECISION).addRect(Vector3D.ZERO, 1, 1, 1e-20);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- RegionBSPTree3D.builder(TEST_PRECISION).addRect(Vector3D.ZERO, 0, 0, 0);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- RegionBSPTree3D.builder(TEST_PRECISION).addRect(Vector3D.of(1, 2, 3), Vector3D.of(1, 2, 3));
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- RegionBSPTree3D.builder(TEST_PRECISION).addCenteredRect(Vector3D.of(1, 2, 3), 0, 0, 0);
- }, IllegalArgumentException.class);
- }
-
- @Test
- public void testBuilder_cubeMethods() {
- // act
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .addCube(Vector3D.of(1, 0, 0), 2)
- .addCenteredCube(Vector3D.of(-2, -3, -4), 3)
- .build();
-
- // assert
- Assert.assertEquals(8 + 27, tree.getSize(), TEST_EPS);
-
- EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.INSIDE,
- Vector3D.of(2, 1, 1),
- Vector3D.of(-2, -3, -4));
-
- EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.BOUNDARY,
- Vector3D.of(-3.5, -4.5, -5.5),
- Vector3D.of(-0.5, -1.5, -2.5));
- }
-
- @Test
- public void testBuilder_cubeMethods_invalidDimensions() {
- // act/assert
- GeometryTestUtils.assertThrows(() -> {
- RegionBSPTree3D.builder(TEST_PRECISION).addCube(Vector3D.ZERO, 1e-20);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- RegionBSPTree3D.builder(TEST_PRECISION).addCube(Vector3D.of(1, 2, 3), 1e-20);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- RegionBSPTree3D.builder(TEST_PRECISION).addCenteredCube(Vector3D.of(1, 2, 3), 0);
- }, IllegalArgumentException.class);
- }
-
- @Test
- public void testBuilder_addIndexedFacets_triangles() {
- // arrange
- Vector3D[] vertices = {
- Vector3D.ZERO,
- Vector3D.of(1, 0, 0),
- Vector3D.of(1, 1, 0),
- Vector3D.of(0, 1, 0),
-
- Vector3D.of(0, 0, 1),
- Vector3D.of(1, 0, 1),
- Vector3D.of(1, 1, 1),
- Vector3D.of(0, 1, 1)
- };
-
- int[][] facets = {
- {0, 3, 2},
- {0, 2, 1},
-
- {4, 5, 6},
- {4, 6, 7},
-
- {5, 1, 2},
- {5, 2, 6},
-
- {4, 7, 3},
- {4, 3, 0},
-
- {4, 0, 1},
- {4, 1, 5},
-
- {7, 6, 2},
- {7, 2, 3}
- };
-
- // act
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .withVertexList(vertices)
- .addIndexedFacets(facets)
- .build();
-
- // assert
- Assert.assertFalse(tree.isFull());
- Assert.assertFalse(tree.isEmpty());
-
- Assert.assertEquals(1, tree.getSize(), TEST_EPS);
- Assert.assertEquals(6, tree.getBoundarySize(), TEST_EPS);
- EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(0.5, 0.5, 0.5), tree.getBarycenter(), TEST_EPS);
- }
-
- @Test
- public void testBuilder_addIndexedFacets_concaveFacets() {
- // arrange
- Vector3D[] vertices = {
- Vector3D.of(-1, 0, 1),
- Vector3D.of(-1, 0, 0),
-
- Vector3D.of(0, 2, 1),
- Vector3D.of(0, 2, 0),
-
- Vector3D.of(1, 0, 1),
- Vector3D.of(1, 0, 0),
-
- Vector3D.of(0, 1, 1),
- Vector3D.of(0, 1, 0)
- };
-
- int[][] facets = {
- {0, 2, 3, 1},
- {4, 5, 3, 2},
- {0, 1, 7, 6},
- {4, 6, 7, 5},
- {0, 6, 4, 2},
- {1, 3, 5, 7}
- };
-
- // act
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .withVertexList(vertices)
- .addIndexedFacets(facets)
- .build();
-
- // assert
- Assert.assertFalse(tree.isFull());
- Assert.assertFalse(tree.isEmpty());
-
- Assert.assertTrue(Double.isFinite(tree.getSize()));
- Assert.assertTrue(Double.isFinite(tree.getBoundarySize()));
- Assert.assertNotNull(tree.getBarycenter());
-
- EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.INSIDE, Vector3D.of(0, 1.5, 0.5));
- EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.OUTSIDE, Vector3D.of(0, 0.5, 0.5));
- }
-
- @Test
- public void testBuilder_addIndexedFacets_multipleVertexLists() {
- // arrange
- Vector3D p0 = Vector3D.ZERO;
- Vector3D p1 = Vector3D.Unit.PLUS_X;
- Vector3D p2 = Vector3D.Unit.PLUS_Y;
- Vector3D p3 = Vector3D.Unit.PLUS_Z;
-
- // act
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .withVertexList(p1, p2, p3)
- .addIndexedFacet(Arrays.asList(2, 0, 1))
-
- .withVertexList(p0, p1, p2, p3)
- .addIndexedFacet(0, 2, 1)
- .addIndexedFacets(new int[][] {
- {0, 1, 3},
- {0, 3, 2}
- })
- .build();
-
- // assert
- Assert.assertEquals(0.5 / 3.0, tree.getSize(), TEST_EPS);
-
- EuclideanTestUtils.assertRegionLocation(tree, RegionLocation.INSIDE, Vector3D.of(0.25, 0.25, 0.25));
- }
-
// GEOMETRY-59
@Test
public void testSlightlyConcavePrism() {
@@ -1523,11 +1308,11 @@
{3, 0, 4, 7}
};
+ List<ConvexSubPlane> faces = indexedFacetsToBoundaries(vertices, facets);
+
// act
- RegionBSPTree3D tree = RegionBSPTree3D.builder(TEST_PRECISION)
- .withVertexList(vertices)
- .addIndexedFacets(facets)
- .build();
+ RegionBSPTree3D tree = RegionBSPTree3D.full();
+ tree.insert(faces);
// assert
Assert.assertFalse(tree.isFull());
@@ -1540,6 +1325,26 @@
Vector3D.of(-1, 1, 1), Vector3D.of(4, 1, 1));
}
+ private static List<ConvexSubPlane> indexedFacetsToBoundaries(Vector3D[] vertices, int[][] facets) {
+ List<ConvexSubPlane> boundaries = new ArrayList<>();
+
+ List<Vector3D> vertexList = new ArrayList<>();
+
+ for (int i = 0; i < facets.length; ++i) {
+ int[] indices = facets[i];
+
+ for (int j = 0; j < indices.length; ++j) {
+ vertexList.add(vertices[indices[j]]);
+ }
+
+ boundaries.add(ConvexSubPlane.fromVertexLoop(vertexList, TEST_PRECISION));
+
+ vertexList.clear();
+ }
+
+ return boundaries;
+ }
+
private static RegionBSPTree3D createSphere(final Vector3D center, final double radius, final int stacks, final int slices) {
final List<Plane> planes = new ArrayList<>();
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubPlaneTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubPlaneTest.java
index d40811e..99945f9 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubPlaneTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubPlaneTest.java
@@ -32,6 +32,7 @@
import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
+import org.apache.commons.geometry.euclidean.twod.Boundaries2D;
import org.apache.commons.geometry.euclidean.twod.ConvexArea;
import org.apache.commons.geometry.euclidean.twod.Line;
import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
@@ -181,7 +182,7 @@
public void testSplit_both() {
// arrange
SubPlane sp = new SubPlane(XY_PLANE, false);
- sp.getSubspaceRegion().union(RegionBSPTree2D.builder(TEST_PRECISION).addRect(Vector2D.of(-1, -1), Vector2D.of(1, 1)).build());
+ sp.getSubspaceRegion().union(Boundaries2D.rect(Vector2D.of(-1, -1), Vector2D.of(1, 1), TEST_PRECISION).toTree());
Plane splitter = Plane.fromNormal(Vector3D.Unit.PLUS_X, TEST_PRECISION);
@@ -208,7 +209,7 @@
public void testSplit_intersects_plusOnly() {
// arrange
SubPlane sp = new SubPlane(XY_PLANE, false);
- sp.getSubspaceRegion().union(RegionBSPTree2D.builder(TEST_PRECISION).addRect(Vector2D.of(-1, -1), Vector2D.of(1, 1)).build());
+ sp.getSubspaceRegion().union(Boundaries2D.rect(Vector2D.of(-1, -1), Vector2D.of(1, 1), TEST_PRECISION).toTree());
Plane splitter = Plane.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0.1, 0, 1), TEST_PRECISION);
@@ -226,7 +227,7 @@
public void testSplit_intersects_minusOnly() {
// arrange
SubPlane sp = new SubPlane(XY_PLANE, false);
- sp.getSubspaceRegion().union(RegionBSPTree2D.builder(TEST_PRECISION).addRect(Vector2D.of(-1, -1), Vector2D.of(1, 1)).build());
+ sp.getSubspaceRegion().union(Boundaries2D.rect(Vector2D.of(-1, -1), Vector2D.of(1, 1), TEST_PRECISION).toTree());
Plane splitter = Plane.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.of(0.1, 0, -1), TEST_PRECISION);
@@ -244,7 +245,7 @@
public void testSplit_parallel_plusOnly() {
// arrange
SubPlane sp = new SubPlane(XY_PLANE, false);
- sp.getSubspaceRegion().union(RegionBSPTree2D.builder(TEST_PRECISION).addRect(Vector2D.of(-1, -1), Vector2D.of(1, 1)).build());
+ sp.getSubspaceRegion().union(Boundaries2D.rect(Vector2D.of(-1, -1), Vector2D.of(1, 1), TEST_PRECISION).toTree());
Plane splitter = Plane.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_Z, TEST_PRECISION);
@@ -262,7 +263,7 @@
public void testSplit_parallel_minusOnly() {
// arrange
SubPlane sp = new SubPlane(XY_PLANE, false);
- sp.getSubspaceRegion().union(RegionBSPTree2D.builder(TEST_PRECISION).addRect(Vector2D.of(-1, -1), Vector2D.of(1, 1)).build());
+ sp.getSubspaceRegion().union(Boundaries2D.rect(Vector2D.of(-1, -1), Vector2D.of(1, 1), TEST_PRECISION).toTree());
Plane splitter = Plane.fromPointAndNormal(Vector3D.of(0, 0, 1), Vector3D.Unit.MINUS_Z, TEST_PRECISION);
@@ -280,7 +281,7 @@
public void testSplit_coincident() {
// arrange
SubPlane sp = new SubPlane(XY_PLANE, false);
- sp.getSubspaceRegion().union(RegionBSPTree2D.builder(TEST_PRECISION).addRect(Vector2D.of(-1, -1), Vector2D.of(1, 1)).build());
+ sp.getSubspaceRegion().union(Boundaries2D.rect(Vector2D.of(-1, -1), Vector2D.of(1, 1), TEST_PRECISION).toTree());
// act
Split<SubPlane> split = sp.split(sp.getPlane());
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Boundaries2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Boundaries2DTest.java
new file mode 100644
index 0000000..793497b
--- /dev/null
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Boundaries2DTest.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Boundaries2DTest {
+
+ private static final double TEST_EPS = 1e-10;
+
+ private static final DoublePrecisionContext TEST_PRECISION =
+ new EpsilonDoublePrecisionContext(TEST_EPS);
+
+ @Test
+ public void testRect_minFirst() {
+ // act
+ List<Segment> segments = Boundaries2D.rect(Vector2D.of(1, 2), Vector2D.of(3, 4), TEST_PRECISION)
+ .boundaryStream()
+ .collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(4, segments.size());
+
+ assertSegment(segments.get(0), Vector2D.of(1, 2), Vector2D.of(3, 2));
+ assertSegment(segments.get(1), Vector2D.of(3, 4), Vector2D.of(1, 4));
+ assertSegment(segments.get(2), Vector2D.of(3, 2), Vector2D.of(3, 4));
+ assertSegment(segments.get(3), Vector2D.of(1, 4), Vector2D.of(1, 2));
+ }
+
+ @Test
+ public void testRect_maxFirst() {
+ // act
+ List<Segment> segments = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(-1, -2), TEST_PRECISION)
+ .boundaryStream()
+ .collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(4, segments.size());
+
+ assertSegment(segments.get(0), Vector2D.of(-1, -2), Vector2D.of(0, -2));
+ assertSegment(segments.get(1), Vector2D.ZERO, Vector2D.of(-1, 0));
+ assertSegment(segments.get(2), Vector2D.of(0, -2), Vector2D.ZERO);
+ assertSegment(segments.get(3), Vector2D.of(-1, 0), Vector2D.of(-1, -2));
+ }
+
+ @Test
+ public void testRect_toTree() {
+ // act
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(1, 4), TEST_PRECISION).toTree();
+
+ // assert
+ Assert.assertFalse(tree.isFull());
+ Assert.assertFalse(tree.isEmpty());
+
+ Assert.assertEquals(4, tree.getSize(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 2), tree.getBarycenter(), TEST_EPS);
+ }
+
+ @Test
+ public void testRect_illegalArgs() {
+ // act/assert
+ GeometryTestUtils.assertThrows(() -> {
+ Boundaries2D.rect(Vector2D.of(1, 1), Vector2D.of(1, 3), TEST_PRECISION);
+ }, IllegalArgumentException.class);
+
+ GeometryTestUtils.assertThrows(() -> {
+ Boundaries2D.rect(Vector2D.of(1, 1), Vector2D.of(3, 1), TEST_PRECISION);
+ }, IllegalArgumentException.class);
+
+ GeometryTestUtils.assertThrows(() -> {
+ Boundaries2D.rect(Vector2D.of(2, 3), Vector2D.of(2, 3), TEST_PRECISION);
+ }, IllegalArgumentException.class);
+ }
+
+ private static void assertSegment(Segment segment, Vector2D start, Vector2D end) {
+ EuclideanTestUtils.assertCoordinatesEqual(start, segment.getStartPoint(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(end, segment.getEndPoint(), TEST_EPS);
+ }
+}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/ConvexAreaTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/ConvexAreaTest.java
index 339af22..a054329 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/ConvexAreaTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/ConvexAreaTest.java
@@ -20,6 +20,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.RegionLocation;
@@ -54,6 +55,35 @@
}
@Test
+ public void testBoundaryStream() {
+ // arrange
+ Line line = Line.fromPointAndAngle(Vector2D.ZERO, 0, TEST_PRECISION);
+ ConvexArea area = ConvexArea.fromBounds(line);
+
+ // act
+ List<Segment> segments = area.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(1, segments.size());
+ Segment segment = segments.get(0);
+ Assert.assertNull(segment.getStartPoint());
+ Assert.assertNull(segment.getEndPoint());
+ Assert.assertSame(line, segment.getLine());
+ }
+
+ @Test
+ public void testBoundaryStream_full() {
+ // arrange
+ ConvexArea area = ConvexArea.full();
+
+ // act
+ List<Segment> segments = area.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(0, segments.size());
+ }
+
+ @Test
public void testToTree() {
// arrange
ConvexArea area = ConvexArea.fromBounds(
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolylineTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolylineTest.java
index c3d05fd..e4318ec 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolylineTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolylineTest.java
@@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.commons.numbers.angle.PlaneAngleRadians;
import org.apache.commons.geometry.core.GeometryTestUtils;
@@ -544,21 +545,29 @@
}
@Test
- public void testIterable() {
+ public void testBoundaryStream() {
// arrange
- Polyline path = Polyline.builder(TEST_PRECISION)
- .appendVertices(Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.of(1, 1)).build();
+ Segment seg = Segment.fromPoints(Vector2D.ZERO, Vector2D.of(1, 0), TEST_PRECISION);
+ Polyline path = Polyline.fromSegments(Arrays.asList(seg));
// act
- List<Segment> segments = new ArrayList<>();
- for (Segment segment : path) {
- segments.add(segment);
- }
+ List<Segment> segments = path.boundaryStream().collect(Collectors.toList());
// assert
- Assert.assertEquals(2, segments.size());
- assertFiniteSegment(segments.get(0), Vector2D.Unit.ZERO, Vector2D.Unit.PLUS_X);
- assertFiniteSegment(segments.get(1), Vector2D.Unit.PLUS_X, Vector2D.of(1, 1));
+ Assert.assertEquals(1, segments.size());
+ Assert.assertSame(seg, segments.get(0));
+ }
+
+ @Test
+ public void testBoundaryStream_empty() {
+ // arrange
+ Polyline path = Polyline.empty();
+
+ // act
+ List<Segment> segments = path.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(0, segments.size());
}
@Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2DTest.java
index d9e5fef..1fbb863 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2DTest.java
@@ -21,6 +21,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.Region;
@@ -30,7 +31,6 @@
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
-import org.apache.commons.geometry.euclidean.oned.Interval;
import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D.RegionNode2D;
import org.apache.commons.numbers.angle.PlaneAngleRadians;
import org.junit.Assert;
@@ -122,9 +122,8 @@
@Test
public void testBoundaries() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.ZERO, Vector2D.of(1, 1))
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION)
+ .toTree();
// act
List<Segment> segments = new ArrayList<>();
@@ -137,9 +136,8 @@
@Test
public void testGetBoundaries() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.ZERO, Vector2D.of(1, 1))
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION)
+ .toTree();
// act
List<Segment> segments = tree.getBoundaries();
@@ -149,6 +147,31 @@
}
@Test
+ public void testBoundaryStream() {
+ // arrange
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION)
+ .toTree();
+
+ // act
+ List<Segment> segments = tree.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(4, segments.size());
+ }
+
+ @Test
+ public void testBoundaryStream_noBoundaries() {
+ // arrange
+ RegionBSPTree2D tree = RegionBSPTree2D.full();
+
+ // act
+ List<Segment> segments = tree.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(0, segments.size());
+ }
+
+ @Test
public void testGetBoundaryPaths_cachesResult() {
// arrange
RegionBSPTree2D tree = RegionBSPTree2D.empty();
@@ -289,9 +312,8 @@
@Test
public void testToConvex_square() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addSquare(Vector2D.ZERO, 1)
- .build();
+ RegionBSPTree2D tree = RegionBSPTree2D.from(
+ Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(1, 1), TEST_PRECISION));
// act
List<ConvexArea> result = tree.toConvex();
@@ -451,9 +473,8 @@
@Test
public void testSplit_bothSides() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.ZERO, 2, 1)
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(2, 1), TEST_PRECISION)
+ .toTree();
Line splitter = Line.fromPointAndAngle(Vector2D.ZERO, 0.25 * PlaneAngleRadians.PI, TEST_PRECISION);
@@ -477,9 +498,8 @@
@Test
public void testSplit_plusSideOnly() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.ZERO, 2, 1)
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(2, 1), TEST_PRECISION)
+ .toTree();
Line splitter = Line.fromPointAndAngle(Vector2D.of(0, 1), 0.25 * PlaneAngleRadians.PI, TEST_PRECISION);
@@ -500,9 +520,8 @@
@Test
public void testSplit_minusSideOnly() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.ZERO, 2, 1)
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(2, 1), TEST_PRECISION)
+ .toTree();
Line splitter = Line.fromPointAndAngle(Vector2D.of(0, 1), 0.25 * PlaneAngleRadians.PI, TEST_PRECISION)
.reverse();
@@ -748,12 +767,10 @@
@Test
public void testGeometricProperties_regionWithHole() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.ZERO, Vector2D.of(3, 3))
- .build();
- RegionBSPTree2D inner = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.of(1, 1), Vector2D.of(2, 2))
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(3, 3), TEST_PRECISION)
+ .toTree();
+ RegionBSPTree2D inner = Boundaries2D.rect(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION)
+ .toTree();
tree.difference(inner);
@@ -789,12 +806,10 @@
@Test
public void testGeometricProperties_complementedRegionWithHole() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.ZERO, Vector2D.of(3, 3))
- .build();
- RegionBSPTree2D inner = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.of(1, 1), Vector2D.of(2, 2))
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(3, 3), TEST_PRECISION)
+ .toTree();
+ RegionBSPTree2D inner = Boundaries2D.rect(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION)
+ .toTree();
tree.difference(inner);
@@ -830,15 +845,28 @@
}
@Test
+ public void testFrom_boundarySource_noBoundaries() {
+ // arrange
+ BoundarySource2D src = () -> new ArrayList<Segment>().stream();
+
+ // act
+ RegionBSPTree2D tree = RegionBSPTree2D.from(src);
+
+ // assert
+ Assert.assertNull(tree.getBarycenter());
+ Assert.assertTrue(tree.isFull());
+ }
+
+ @Test
public void testFromConvexArea_full() {
// arrange
ConvexArea area = ConvexArea.full();
// act
RegionBSPTree2D tree = RegionBSPTree2D.from(area);
- Assert.assertNull(tree.getBarycenter());
// assert
+ Assert.assertNull(tree.getBarycenter());
Assert.assertTrue(tree.isFull());
}
@@ -885,100 +913,17 @@
}
@Test
- public void testBuilder_rectMethods() {
- // act
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addCenteredRect(Vector2D.ZERO, 1, 2)
- .addCenteredSquare(Vector2D.of(5, 0), -2)
-
- .addRect(Vector2D.of(10, 5), -2, -3)
- .addRect(Vector2D.of(15, 5), Vector2D.of(16, 4))
-
- .addSquare(Vector2D.of(20, 1), 3)
-
- .build();
-
- // assert
- Assert.assertEquals(2 + 4 + 6 + 1 + 9, tree.getSize(), TEST_EPS);
-
- checkClassify(tree, RegionLocation.INSIDE,
- Vector2D.ZERO,
- Vector2D.of(5, 0),
- Vector2D.of(9, 3.5),
- Vector2D.of(15.5, 4.5),
- Vector2D.of(21.5, 2.5));
-
- checkClassify(tree, RegionLocation.BOUNDARY,
- Vector2D.of(-0.5, -1), Vector2D.of(0.5, 1),
- Vector2D.of(4, -1), Vector2D.of(6, 1));
- }
-
- @Test
- public void testBuilder_rectMethods_zeroSize() {
+ public void testToTree_returnsNewTree() {
// arrange
- final RegionBSPTree2D.Builder builder = RegionBSPTree2D.builder(TEST_PRECISION);
-
- // act/assert
- GeometryTestUtils.assertThrows(() -> {
- builder.addRect(Vector2D.of(1, 1), 0, 2);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.addRect(Vector2D.of(1, 1), 2, 0);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.addRect(Vector2D.of(2, 3), 0, 0);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.addRect(Vector2D.of(1, 1), Vector2D.of(1, 3));
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.addRect(Vector2D.of(1, 1), Vector2D.of(3, 1));
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.addRect(Vector2D.of(2, 3), Vector2D.of(2, 3));
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.addCenteredRect(Vector2D.of(2, 3), 0, 1e-20);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.addSquare(Vector2D.of(2, 3), 1e-20);
- }, IllegalArgumentException.class);
-
- GeometryTestUtils.assertThrows(() -> {
- builder.addCenteredSquare(Vector2D.of(2, 3), 0);
- }, IllegalArgumentException.class);
- }
-
- @Test
- public void testBuilder_mixedArguments() {
- // arrange
- Line minusYAxis = Line.fromPointAndAngle(Vector2D.ZERO, -PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION);
-
- Polyline path = Polyline.builder(TEST_PRECISION)
- .append(Vector2D.Unit.PLUS_X)
- .append(Vector2D.of(1, 1))
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(1, 2), TEST_PRECISION).toTree();
// act
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .add(Segment.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_X, TEST_PRECISION))
- .add(path)
- .addSegment(Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y)
- .add(new SubLine(minusYAxis, Interval.of(-1, 0, TEST_PRECISION).toTree()))
- .build();
+ RegionBSPTree2D result = tree.toTree();
// assert
- Assert.assertEquals(1, tree.getSize(), TEST_EPS);
- Assert.assertEquals(4, tree.getBoundarySize(), TEST_EPS);
-
- EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 0.5), tree.getBarycenter(), TEST_EPS);
+ Assert.assertNotSame(tree, result);
+ Assert.assertEquals(2, result.getSize(), TEST_EPS);
+ EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(0.5, 1), result.getBarycenter(), TEST_EPS);
}
@Test
@@ -1006,9 +951,8 @@
@Test
public void testProject_rect() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addSquare(Vector2D.of(1, 1), 1)
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(
+ Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION).toTree();
// act/assert
EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), tree.project(Vector2D.ZERO), TEST_EPS);
@@ -1031,9 +975,8 @@
@Test
public void testTransform() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addRect(Vector2D.of(1, 1), 2, 1)
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.of(1, 1), Vector2D.of(3, 2), TEST_PRECISION)
+ .toTree();
AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(0.5, 2)
.rotate(PlaneAngleRadians.PI_OVER_TWO)
@@ -1101,9 +1044,7 @@
@Test
public void testTransform_reflection() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addSquare(Vector2D.of(1, 1), 1)
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION).toTree();
Transform2D transform = FunctionTransform2D.from(v -> Vector2D.of(-v.getX(), v.getY()));
@@ -1125,9 +1066,8 @@
@Test
public void testTransform_doubleReflection() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addSquare(Vector2D.of(1, 1), 1)
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(
+ Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION).toTree();
Transform2D transform = FunctionTransform2D.from(Vector2D::negate);
@@ -1149,20 +1089,18 @@
@Test
public void testBooleanOperations() {
// arrange
- RegionBSPTree2D tree = RegionBSPTree2D.builder(TEST_PRECISION)
- .addSquare(Vector2D.ZERO, 3)
- .build();
+ RegionBSPTree2D tree = Boundaries2D.rect(Vector2D.ZERO, Vector2D.of(3, 3), TEST_PRECISION).toTree();
RegionBSPTree2D temp;
// act
- temp = RegionBSPTree2D.builder(TEST_PRECISION).addSquare(Vector2D.of(1, 1), 1).build();
+ temp = Boundaries2D.rect(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION).toTree();
temp.complement();
tree.intersection(temp);
- temp = RegionBSPTree2D.builder(TEST_PRECISION).addSquare(Vector2D.of(3, 0), 3).build();
+ temp = Boundaries2D.rect(Vector2D.of(3, 0), Vector2D.of(6, 3), TEST_PRECISION).toTree();
tree.union(temp);
- temp = RegionBSPTree2D.builder(TEST_PRECISION).addRect(Vector2D.of(2, 1), 3, 1).build();
+ temp = Boundaries2D.rect(Vector2D.of(2, 1), Vector2D.of(5, 2), TEST_PRECISION).toTree();
tree.difference(temp);
temp.setFull();
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/RegionBSPTree1S.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/RegionBSPTree1S.java
index 1c6bc06..b056043 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/RegionBSPTree1S.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/RegionBSPTree1S.java
@@ -203,7 +203,7 @@
}
final List<BoundaryPair> insideBoundaryPairs = new ArrayList<>();
- for (RegionNode1S node : this) {
+ for (RegionNode1S node : nodes()) {
if (node.isInside()) {
insideBoundaryPairs.add(getNodeBoundaryPair(node));
}
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/BoundarySource2S.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/BoundarySource2S.java
new file mode 100644
index 0000000..f0173c1
--- /dev/null
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/BoundarySource2S.java
@@ -0,0 +1,35 @@
+/*
+ * 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.spherical.twod;
+
+import org.apache.commons.geometry.core.partitioning.BoundarySource;
+
+/** Extension of the {@link BoundarySource} interface for spherical 2D
+ * space.
+ */
+public interface BoundarySource2S extends BoundarySource<GreatArc> {
+
+ /** Construct a new BSP tree from the boundaries contained in this
+ * instance.
+ * @return a new BSP tree constructed from the boundaries in this
+ * instance
+ * @see RegionBSPTree2S#from(BoundarySource)
+ */
+ default RegionBSPTree2S toTree() {
+ return RegionBSPTree2S.from(this);
+ }
+}
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/ConvexArea2S.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/ConvexArea2S.java
index d77b892..d87dceb 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/ConvexArea2S.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/ConvexArea2S.java
@@ -22,6 +22,7 @@
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.numbers.angle.PlaneAngleRadians;
import org.apache.commons.geometry.core.Transform;
@@ -35,7 +36,8 @@
/** Class representing a convex area in 2D spherical space. The boundaries of this
* area, if any, are composed of convex great circle arcs.
*/
-public final class ConvexArea2S extends AbstractConvexHyperplaneBoundedRegion<Point2S, GreatArc> {
+public final class ConvexArea2S extends AbstractConvexHyperplaneBoundedRegion<Point2S, GreatArc>
+ implements BoundarySource2S {
/** Instance representing the full spherical area. */
private static final ConvexArea2S FULL = new ConvexArea2S(Collections.emptyList());
@@ -54,6 +56,12 @@
super(boundaries);
}
+ /** {@inheritDoc} */
+ @Override
+ public Stream<GreatArc> boundaryStream() {
+ return getBoundaries().stream();
+ }
+
/** Get a path instance representing the boundary of the area. The path is oriented
* so that the minus sides of the arcs lie on the inside of the area.
* @return the boundary path of the area
@@ -166,13 +174,6 @@
return (GreatArc) super.trim(convexSubHyperplane);
}
- /** Return a BSP tree instance representing the same region as the current instance.
- * @return a BSP tree instance representing the same region as the current instance
- */
- public RegionBSPTree2S toTree() {
- return RegionBSPTree2S.from(this);
- }
-
/** Return an instance representing the full spherical 2D space.
* @return an instance representing the full spherical 2D space.
*/
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatArcPath.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatArcPath.java
index 7d3e7d5..cf10762 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatArcPath.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatArcPath.java
@@ -21,14 +21,14 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
+import java.util.stream.Stream;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
/** Class representing a connected sequence of {@link GreatArc} instances.
*/
-public final class GreatArcPath implements Iterable<GreatArc> {
+public final class GreatArcPath implements BoundarySource2S {
/** Instance containing no arcs. */
private static final GreatArcPath EMPTY = new GreatArcPath(Collections.emptyList());
@@ -42,6 +42,12 @@
this.arcs = Collections.unmodifiableList(arcs);
}
+ /** {@inheritDoc} */
+ @Override
+ public Stream<GreatArc> boundaryStream() {
+ return getArcs().stream();
+ }
+
/** Get the arcs in path.
* @return the arcs in the path
*/
@@ -140,22 +146,6 @@
return false;
}
- /** {@inheritDoc} */
- @Override
- public Iterator<GreatArc> iterator() {
- return arcs.iterator();
- }
-
- /** Construct a {@link RegionBSPTree2S} from the arcs in this instance.
- * @return a bsp tree constructed from the arcs in this instance
- */
- public RegionBSPTree2S toTree() {
- RegionBSPTree2S tree = RegionBSPTree2S.empty();
- tree.insert(this);
-
- return tree;
- }
-
/** Return a string representation of this arc path instance.
*
* <p>In order to keep the string representation short but useful, the exact format of the return
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2S.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2S.java
index d519e22..a443c75 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2S.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2S.java
@@ -19,18 +19,22 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
+import org.apache.commons.geometry.core.partitioning.BoundarySource;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.Split;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
/** BSP tree representing regions in 2D spherical space.
*/
-public class RegionBSPTree2S extends AbstractRegionBSPTree<Point2S, RegionBSPTree2S.RegionNode2S> {
+public class RegionBSPTree2S extends AbstractRegionBSPTree<Point2S, RegionBSPTree2S.RegionNode2S>
+ implements BoundarySource2S {
/** Constant containing the area of the full spherical space. */
private static final double FULL_SIZE = 4 * PlaneAngleRadians.PI;
@@ -71,6 +75,12 @@
/** {@inheritDoc} */
@Override
+ public Stream<GreatArc> boundaryStream() {
+ return StreamSupport.stream(boundaries().spliterator(), false);
+ }
+
+ /** {@inheritDoc} */
+ @Override
public List<GreatArc> getBoundaries() {
return createBoundaryList(b -> (GreatArc) b);
}
@@ -211,14 +221,15 @@
return new RegionBSPTree2S(true);
}
- /** Construct a tree from a convex area.
- * @param area the area to construct a tree from
- * @return tree instance representing the same area as the given
- * convex area
+ /** Construct a new tree from the boundaries in the given boundary source. If no boundaries
+ * are present in the given source, their the returned tree contains the full space.
+ * @param boundarySrc boundary source to construct a tree from
+ * @return a new tree instance constructed from the boundaries in the
+ * given source
*/
- public static RegionBSPTree2S from(final ConvexArea2S area) {
+ public static RegionBSPTree2S from(final BoundarySource<GreatArc> boundarySrc) {
final RegionBSPTree2S tree = RegionBSPTree2S.full();
- tree.insert(area.getBoundaries());
+ tree.insert(boundarySrc);
return tree;
}
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/ConvexArea2STest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/ConvexArea2STest.java
index 9380c4e..0307804 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/ConvexArea2STest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/ConvexArea2STest.java
@@ -20,6 +20,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.commons.numbers.angle.PlaneAngleRadians;
import org.apache.commons.geometry.core.GeometryTestUtils;
@@ -540,6 +541,32 @@
}
@Test
+ public void testBoundaryStream() {
+ // arrange
+ GreatCircle circle = GreatCircle.fromPole(Vector3D.Unit.PLUS_X, TEST_PRECISION);
+ ConvexArea2S area = ConvexArea2S.fromBounds(circle);
+
+ // act
+ List<GreatArc> arcs = area.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(1, arcs.size());
+ Assert.assertSame(circle, arcs.get(0).getCircle());
+ }
+
+ @Test
+ public void testBoundaryStream_noBoundaries() {
+ // arrange
+ ConvexArea2S area = ConvexArea2S.full();
+
+ // act
+ List<GreatArc> arcs = area.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(0, arcs.size());
+ }
+
+ @Test
public void testGetInteriorAngles_noAngles() {
// act/assert
Assert.assertEquals(0, ConvexArea2S.full().getInteriorAngles().length);
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatArcPathTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatArcPathTest.java
index 52c13a3..626701b 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatArcPathTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatArcPathTest.java
@@ -16,21 +16,21 @@
*/
package org.apache.commons.geometry.spherical.twod;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
import org.apache.commons.geometry.core.GeometryTestUtils;
import org.apache.commons.geometry.core.RegionLocation;
import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
import org.apache.commons.geometry.euclidean.threed.Vector3D;
import org.apache.commons.geometry.spherical.SphericalTestUtils;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
import org.junit.Assert;
import org.junit.Test;
@@ -284,22 +284,29 @@
}
@Test
- public void testIterator() {
+ public void testBoundaryStream() {
// arrange
- GreatArc a = GreatArc.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION);
- GreatArc b = GreatArc.fromPoints(Point2S.PLUS_J, Point2S.PLUS_K, TEST_PRECISION);
-
- GreatArcPath path = GreatArcPath.fromArcs(Arrays.asList(a, b));
-
- List<GreatArc> arcs = new ArrayList<>();
+ GreatArc fullArc = GreatCircle.fromPole(Vector3D.Unit.PLUS_X, TEST_PRECISION).span();
+ GreatArcPath path = GreatArcPath.fromArcs(fullArc);
// act
- for (GreatArc arc : path) {
- arcs.add(arc);
- }
+ List<GreatArc> arcs = path.boundaryStream().collect(Collectors.toList());
// assert
- Assert.assertEquals(arcs, Arrays.asList(a, b));
+ Assert.assertEquals(1, arcs.size());
+ Assert.assertSame(fullArc, arcs.get(0));
+ }
+
+ @Test
+ public void testBoundaryStream_noBoundaries() {
+ // arrange
+ GreatArcPath path = GreatArcPath.empty();
+
+ // act
+ List<GreatArc> arcs = path.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(0, arcs.size());
}
@Test
@@ -308,8 +315,8 @@
RegionBSPTree2S tree = GreatArcPath.empty().toTree();
// assert
- Assert.assertFalse(tree.isFull());
- Assert.assertTrue(tree.isEmpty());
+ Assert.assertTrue(tree.isFull());
+ Assert.assertFalse(tree.isEmpty());
}
@Test
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2STest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2STest.java
index 4b15a22..0f0b22e 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2STest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2STest.java
@@ -111,7 +111,7 @@
}
@Test
- public void testFromConvexArea() {
+ public void testFromBoundarySource() {
// arrange
ConvexArea2S area = ConvexArea2S.fromVertexLoop(Arrays.asList(
Point2S.of(0.1, 0.1), Point2S.of(0, 0.5),
@@ -131,6 +131,18 @@
}
@Test
+ public void testFromBoundarySource_noBoundaries() {
+ // arrange
+ BoundarySource2S src = () -> new ArrayList<GreatArc>().stream();
+
+ // act
+ RegionBSPTree2S tree = RegionBSPTree2S.from(src);
+
+ // assert
+ Assert.assertTrue(tree.isFull());
+ }
+
+ @Test
public void testCopy() {
// arrange
RegionBSPTree2S tree = new RegionBSPTree2S(true);
@@ -172,6 +184,45 @@
}
@Test
+ public void testBoundaryStream() {
+ // arrange
+ RegionBSPTree2S tree = RegionBSPTree2S.empty();
+ insertPositiveQuadrant(tree);
+
+ // act
+ List<GreatArc> arcs = tree.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(3, arcs.size());
+ }
+
+ @Test
+ public void testBoundaryStream_noBoundaries() {
+ // arrange
+ RegionBSPTree2S tree = RegionBSPTree2S.empty();
+
+ // act
+ List<GreatArc> arcs = tree.boundaryStream().collect(Collectors.toList());
+
+ // assert
+ Assert.assertEquals(0, arcs.size());
+ }
+
+ @Test
+ public void testToTree_returnsNewInstance() {
+ // arrange
+ RegionBSPTree2S tree = RegionBSPTree2S.empty();
+ insertPositiveQuadrant(tree);
+
+ // act
+ RegionBSPTree2S result = tree.toTree();
+
+ // assert
+ Assert.assertNotSame(tree, result);
+ Assert.assertEquals(3, result.getBoundaries().size());
+ }
+
+ @Test
public void testGetBoundaryPaths_cachesResult() {
// arrange
RegionBSPTree2S tree = RegionBSPTree2S.empty();
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
index af2ea39..3edcb1d 100644
--- a/src/site/xdoc/index.xml
+++ b/src/site/xdoc/index.xml
@@ -46,9 +46,9 @@
// create a binary space partitioning tree representing the unit cube
// centered on the origin
-RegionBSPTree3D region = RegionBSPTree3D.builder(precision)
- .addRect(Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5))
- .build();
+RegionBSPTree3D region = Boundaries3D.rect(
+ Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), precision)
+ .toTree();
// create a rotated copy of the region
Transform3D rotation = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, 0.25 * Math.PI);