You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2019/11/24 23:09:52 UTC
[commons-geometry] 01/04: GEOMETRY-32: refactoring BSP and related
classes;
also addresses issues GEOMETRY-32 (simplify Transform interface),
GEOMETRY-33 (Region API), and GEOMETRY-34 (SubHyperplane optimized
implementations)
This is an automated email from the ASF dual-hosted git repository.
erans pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-geometry.git
commit 77a2792463b091a37eea3859032793648a9b525d
Author: Matt Juntunen <ma...@hotmail.com>
AuthorDate: Sun Nov 24 01:04:02 2019 -0500
GEOMETRY-32: refactoring BSP and related classes; also addresses issues GEOMETRY-32 (simplify Transform interface), GEOMETRY-33 (Region API), and GEOMETRY-34 (SubHyperplane optimized implementations)
---
.../apache/commons/geometry/core/Embedding.java | 71 +
.../org/apache/commons/geometry/core/Geometry.java | 8 +-
.../org/apache/commons/geometry/core/Region.java | 82 +
...etryValueException.java => RegionLocation.java} | 28 +-
.../org/apache/commons/geometry/core/Spatial.java | 6 +
.../apache/commons/geometry/core/Transform.java | 56 +
.../org/apache/commons/geometry/core/Vector.java | 8 +-
.../geometry/core/exception/GeometryException.java | 2 +-
.../core/exception/GeometryValueException.java | 2 +-
.../core/exception/IllegalNormException.java | 2 +-
.../Equivalency.java} | 31 +-
.../core/internal/GeometryInternalError.java | 2 +-
.../geometry/core/internal/IteratorTransform.java | 92 +
.../geometry/core/internal/SimpleTupleFormat.java | 17 +-
.../AbstractConvexHyperplaneBoundedRegion.java | 379 ++++
.../AbstractEmbeddingSubHyperplane.java | 110 +
.../core/partitioning/AbstractHyperplane.java | 70 +
.../geometry/core/partitioning/AbstractRegion.java | 516 -----
.../core/partitioning/AbstractSubHyperplane.java | 189 --
.../geometry/core/partitioning/BSPTree.java | 781 -------
.../geometry/core/partitioning/BSPTreeVisitor.java | 112 -
.../core/partitioning/BoundaryAttribute.java | 100 -
.../core/partitioning/BoundaryBuilder.java | 99 -
.../core/partitioning/BoundaryProjection.java | 84 -
.../core/partitioning/BoundaryProjector.java | 200 --
.../core/partitioning/BoundarySizeVisitor.java | 67 -
.../core/partitioning/Characterization.java | 196 --
.../core/partitioning/ConvexSubHyperplane.java | 50 +
.../geometry/core/partitioning/Embedding.java | 62 -
.../{Side.java => EmbeddingHyperplane.java} | 26 +-
.../geometry/core/partitioning/Hyperplane.java | 105 +-
.../{Side.java => HyperplaneBoundedRegion.java} | 26 +-
.../{Side.java => HyperplaneLocation.java} | 26 +-
.../geometry/core/partitioning/InsideFinder.java | 149 --
.../geometry/core/partitioning/NodesSet.java | 72 -
.../commons/geometry/core/partitioning/Region.java | 204 --
.../geometry/core/partitioning/RegionFactory.java | 383 ----
.../commons/geometry/core/partitioning/Split.java | 97 +
.../partitioning/{Side.java => SplitLocation.java} | 27 +-
.../partitioning/{Side.java => Splittable.java} | 26 +-
.../geometry/core/partitioning/SubHyperplane.java | 194 +-
.../geometry/core/partitioning/Transform.java | 77 -
.../core/partitioning/bsp/AbstractBSPTree.java | 1108 ++++++++++
.../bsp/AbstractBSPTreeMergeOperator.java | 147 ++
.../partitioning/bsp/AbstractRegionBSPTree.java | 966 +++++++++
.../core/partitioning/bsp/AttributeBSPTree.java | 144 ++
.../geometry/core/partitioning/bsp/BSPSubtree.java | 53 +
.../geometry/core/partitioning/bsp/BSPTree.java | 225 ++
.../core/partitioning/bsp/BSPTreePrinter.java | 118 +
.../core/partitioning/bsp/BSPTreeVisitor.java | 173 ++
.../core/partitioning/bsp/RegionCutBoundary.java | 109 +
.../{Side.java => bsp/package-info.java} | 26 +-
.../geometry/core/partitioning/package-info.java | 93 +-
.../core/precision/DoublePrecisionContext.java | 20 +-
.../precision/EpsilonDoublePrecisionContext.java | 2 +-
.../commons/geometry/core/EmbeddingTest.java | 98 +
.../commons/geometry/core/GeometryTestUtils.java | 39 +-
.../core/internal/IteratorTransformTest.java | 90 +
.../core/partition/test/PartitionTestUtils.java | 115 +
.../geometry/core/partition/test/TestBSPTree.java | 64 +
.../geometry/core/partition/test/TestLine.java | 280 +++
.../core/partition/test/TestLineSegment.java | 340 +++
.../partition/test/TestLineSegmentCollection.java | 197 ++
.../test/TestLineSegmentCollectionBuilder.java | 101 +
.../geometry/core/partition/test/TestPoint1D.java | 83 +
.../geometry/core/partition/test/TestPoint2D.java | 107 +
.../core/partition/test/TestTransform2D.java | 60 +
.../AbstractConvexHyperplaneBoundedRegionTest.java | 546 +++++
.../AbstractEmbeddingSubHyperplaneTest.java | 187 ++
.../core/partitioning/AbstractHyperplaneTest.java | 113 +
.../geometry/core/partitioning/SplitTest.java | 64 +
.../geometry/core/partitioning/TreeBuilder.java | 168 --
.../geometry/core/partitioning/TreeDumper.java | 101 -
.../geometry/core/partitioning/TreePrinter.java | 135 --
.../bsp/AbstractBSPTreeMergeOperatorTest.java | 561 +++++
.../core/partitioning/bsp/AbstractBSPTreeTest.java | 1806 ++++++++++++++++
.../bsp/AbstractRegionBSPTreeTest.java | 2257 ++++++++++++++++++++
.../partitioning/bsp/AttributeBSPTreeTest.java | 220 ++
.../core/partitioning/bsp/BSPTreeVisitorTest.java | 142 ++
.../partitioning/bsp/RegionCutBoundaryTest.java | 146 ++
.../core/precision/DoublePrecisionContextTest.java | 13 +
.../EpsilonDoublePrecisionContextTest.java | 22 +
.../commons/geometry/enclosing/EnclosingBall.java | 2 +-
.../threed/enclosing/SphereGenerator.java | 11 +-
.../euclidean/AbstractAffineTransformMatrix.java | 51 +
.../geometry/euclidean/AffineTransformMatrix.java | 75 -
.../geometry/euclidean/EuclideanTransform.java | 40 +
.../geometry/euclidean/EuclideanVector.java | 21 +-
.../euclidean/MultiDimensionalEuclideanVector.java | 18 +-
.../exception/NonInvertibleTransformException.java | 2 +-
.../euclidean/internal/AbstractPathConnector.java | 460 ++++
.../geometry/euclidean/internal/Matrices.java | 2 +-
.../euclidean/oned/AffineTransformMatrix1D.java | 50 +-
.../euclidean/oned/FunctionTransform1D.java | 95 +
.../commons/geometry/euclidean/oned/Interval.java | 513 ++++-
.../geometry/euclidean/oned/IntervalsSet.java | 619 ------
.../geometry/euclidean/oned/OrientedPoint.java | 435 +++-
.../geometry/euclidean/oned/RegionBSPTree1D.java | 578 +++++
.../geometry/euclidean/oned/SubOrientedPoint.java | 79 -
.../geometry/euclidean/oned/Transform1D.java | 22 +-
.../commons/geometry/euclidean/oned/Vector1D.java | 105 +-
.../euclidean/threed/AbstractSubLine3D.java | 62 +
.../euclidean/threed/AbstractSubPlane.java | 147 ++
.../euclidean/threed/AffineTransformMatrix3D.java | 153 +-
.../geometry/euclidean/threed/ConvexSubPlane.java | 172 ++
.../geometry/euclidean/threed/ConvexVolume.java | 188 ++
.../euclidean/threed/FunctionTransform3D.java | 107 +
.../commons/geometry/euclidean/threed/Line.java | 269 ---
.../commons/geometry/euclidean/threed/Line3D.java | 432 ++++
.../euclidean/threed/OutlineExtractor.java | 263 ---
.../commons/geometry/euclidean/threed/Plane.java | 804 +++----
.../geometry/euclidean/threed/PolyhedronsSet.java | 704 ------
.../geometry/euclidean/threed/RegionBSPTree3D.java | 698 ++++++
.../commons/geometry/euclidean/threed/Segment.java | 65 -
.../geometry/euclidean/threed/Segment3D.java | 271 +++
.../euclidean/threed/SphericalCoordinates.java | 9 +-
.../commons/geometry/euclidean/threed/SubLine.java | 147 --
.../geometry/euclidean/threed/SubLine3D.java | 124 ++
.../geometry/euclidean/threed/SubPlane.java | 229 +-
.../geometry/euclidean/threed/Transform3D.java | 22 +-
.../geometry/euclidean/threed/Vector3D.java | 151 +-
.../threed/rotation/AxisAngleSequence.java | 8 +-
.../euclidean/threed/rotation/AxisSequence.java | 4 +-
.../threed/rotation/QuaternionRotation.java | 147 +-
.../euclidean/threed/rotation/Rotation3D.java | 24 +-
.../euclidean/twod/AbstractSegmentConnector.java | 306 +++
.../geometry/euclidean/twod/AbstractSubLine.java | 131 ++
.../euclidean/twod/AffineTransformMatrix2D.java | 74 +-
.../geometry/euclidean/twod/ConvexArea.java | 294 +++
.../euclidean/twod/FunctionTransform2D.java | 103 +
.../twod/InteriorAngleSegmentConnector.java | 125 ++
.../commons/geometry/euclidean/twod/Line.java | 337 ++-
.../geometry/euclidean/twod/NestedLoops.java | 195 --
.../geometry/euclidean/twod/PolarCoordinates.java | 14 +-
.../geometry/euclidean/twod/PolygonsSet.java | 1101 ----------
.../commons/geometry/euclidean/twod/Polyline.java | 861 ++++++++
.../geometry/euclidean/twod/RegionBSPTree2D.java | 506 +++++
.../commons/geometry/euclidean/twod/Segment.java | 332 ++-
.../commons/geometry/euclidean/twod/SubLine.java | 300 +--
.../geometry/euclidean/twod/Transform2D.java | 22 +-
.../commons/geometry/euclidean/twod/Vector2D.java | 137 +-
.../core/partitioning/CharacterizationTest.java | 427 ----
.../geometry/euclidean/EuclideanTestUtils.java | 314 +--
.../oned/AffineTransformMatrix1DTest.java | 45 +
.../euclidean/oned/FunctionTransform1DTest.java | 177 ++
.../geometry/euclidean/oned/IntervalTest.java | 940 +++++++-
.../geometry/euclidean/oned/IntervalsSetTest.java | 592 -----
.../geometry/euclidean/oned/OrientedPointTest.java | 466 +++-
.../euclidean/oned/RegionBSPTree1DTest.java | 1231 +++++++++++
.../euclidean/oned/SubOrientedPointTest.java | 169 --
.../geometry/euclidean/oned/Vector1DTest.java | 48 +-
.../threed/AffineTransformMatrix3DTest.java | 121 ++
.../euclidean/threed/ConvexSubPlaneTest.java | 641 ++++++
.../euclidean/threed/ConvexVolumeTest.java | 228 ++
.../euclidean/threed/FunctionTransform3DTest.java | 197 ++
.../geometry/euclidean/threed/Line3DTest.java | 459 ++++
.../geometry/euclidean/threed/LineTest.java | 151 --
.../geometry/euclidean/threed/OBJWriter.java | 318 ---
.../geometry/euclidean/threed/PLYParser.java | 289 ---
.../geometry/euclidean/threed/PlaneTest.java | 890 ++++++--
.../euclidean/threed/PolyhedronsSetTest.java | 1655 --------------
.../euclidean/threed/RegionBSPTree3DTest.java | 1633 ++++++++++++++
.../geometry/euclidean/threed/Segment3DTest.java | 387 ++++
.../euclidean/threed/SphericalCoordinatesTest.java | 19 +
.../geometry/euclidean/threed/SubLine3DTest.java | 199 ++
.../geometry/euclidean/threed/SubLineTest.java | 170 --
.../geometry/euclidean/threed/SubPlaneTest.java | 496 +++++
.../geometry/euclidean/threed/Vector3DTest.java | 82 +-
.../threed/rotation/QuaternionRotationTest.java | 77 +-
.../twod/AbstractSegmentConnectorTest.java | 525 +++++
.../twod/AffineTransformMatrix2DTest.java | 54 +
.../geometry/euclidean/twod/ConvexAreaTest.java | 1210 +++++++++++
.../euclidean/twod/FunctionTransform2DTest.java | 190 ++
.../twod/InteriorAngleSegmentConnectorTest.java | 342 +++
.../commons/geometry/euclidean/twod/LineTest.java | 420 +++-
.../geometry/euclidean/twod/NestedLoopsTest.java | 73 -
.../euclidean/twod/PolarCoordinatesTest.java | 20 +
.../geometry/euclidean/twod/PolygonsSetTest.java | 1849 ----------------
.../geometry/euclidean/twod/PolylineTest.java | 1306 +++++++++++
.../euclidean/twod/RegionBSPTree2DTest.java | 1237 +++++++++++
.../geometry/euclidean/twod/SegmentTest.java | 875 +++++++-
.../geometry/euclidean/twod/SubLineTest.java | 682 +++++-
.../geometry/euclidean/twod/Vector2DTest.java | 60 +-
.../geometry/euclidean/threed/issue-1211.bsp | 14 -
.../threed/pentomino-N-bad-orientation.ply | 40 -
.../geometry/euclidean/threed/pentomino-N-hole.ply | 39 -
.../euclidean/threed/pentomino-N-out-of-plane.ply | 40 -
.../euclidean/threed/pentomino-N-too-close.ply | 86 -
.../geometry/euclidean/threed/pentomino-N.ply | 39 -
.../twod/hull/AbstractConvexHullGenerator2D.java | 5 +-
.../geometry/euclidean/twod/hull/ConvexHull2D.java | 31 +-
.../euclidean/twod/hull/MonotoneChain.java | 4 +-
.../apache/commons/geometry/hull/ConvexHull.java | 2 +-
.../hull/ConvexHullGenerator2DAbstractTest.java | 12 +-
.../geometry/spherical/oned/AngularInterval.java | 630 ++++++
.../commons/geometry/spherical/oned/Arc.java | 133 --
.../commons/geometry/spherical/oned/ArcsSet.java | 925 --------
.../commons/geometry/spherical/oned/CutAngle.java | 518 +++++
.../geometry/spherical/oned/LimitAngle.java | 133 --
.../commons/geometry/spherical/oned/Point1S.java | 393 ++++
.../geometry/spherical/oned/RegionBSPTree1S.java | 511 +++++
.../commons/geometry/spherical/oned/S1Point.java | 177 --
.../geometry/spherical/oned/SubLimitAngle.java | 65 -
.../geometry/spherical/oned/Transform1S.java | 236 ++
.../spherical/twod/AbstractGreatArcConnector.java | 303 +++
.../spherical/twod/AbstractSubGreatCircle.java | 69 +
.../commons/geometry/spherical/twod/Circle.java | 336 ---
.../geometry/spherical/twod/ConvexArea2S.java | 305 +++
.../commons/geometry/spherical/twod/Edge.java | 222 --
.../geometry/spherical/twod/EdgesBuilder.java | 170 --
.../commons/geometry/spherical/twod/GreatArc.java | 227 ++
.../geometry/spherical/twod/GreatArcPath.java | 688 ++++++
.../geometry/spherical/twod/GreatCircle.java | 446 ++++
.../twod/InteriorAngleGreatArcConnector.java | 127 ++
.../commons/geometry/spherical/twod/Point2S.java | 317 +++
.../spherical/twod/PropertiesComputer.java | 175 --
.../geometry/spherical/twod/RegionBSPTree2S.java | 303 +++
.../commons/geometry/spherical/twod/S2Point.java | 228 --
.../spherical/twod/SphericalPolygonsSet.java | 558 -----
.../commons/geometry/spherical/twod/SubCircle.java | 71 -
.../geometry/spherical/twod/SubGreatCircle.java | 233 ++
.../geometry/spherical/twod/Transform2S.java | 264 +++
.../commons/geometry/spherical/twod/Vertex.java | 123 --
.../geometry/spherical/SphericalTestUtils.java | 132 +-
.../spherical/oned/AngularIntervalTest.java | 894 ++++++++
.../commons/geometry/spherical/oned/ArcTest.java | 93 -
.../geometry/spherical/oned/ArcsSetTest.java | 599 ------
.../geometry/spherical/oned/CutAngleTest.java | 596 ++++++
.../geometry/spherical/oned/LimitAngleTest.java | 45 -
.../geometry/spherical/oned/Point1STest.java | 481 +++++
.../spherical/oned/RegionBSPTree1STest.java | 923 ++++++++
.../geometry/spherical/oned/S1PointTest.java | 86 -
.../geometry/spherical/oned/Transform1STest.java | 254 +++
.../twod/AbstractGreatArcPathConnectorTest.java | 305 +++
.../geometry/spherical/twod/CircleTest.java | 191 --
.../geometry/spherical/twod/ConvexArea2STest.java | 795 +++++++
.../geometry/spherical/twod/GreatArcPathTest.java | 641 ++++++
.../geometry/spherical/twod/GreatArcTest.java | 383 ++++
.../geometry/spherical/twod/GreatCircleTest.java | 753 +++++++
.../twod/InteriorAngleGreatArcConnectorTest.java | 221 ++
.../geometry/spherical/twod/Point2STest.java | 346 +++
.../spherical/twod/RegionBSPTree2STest.java | 714 +++++++
.../geometry/spherical/twod/S2PointTest.java | 93 -
.../spherical/twod/SphericalPolygonsSetTest.java | 570 -----
.../geometry/spherical/twod/SubCircleTest.java | 146 --
.../spherical/twod/SubGreatCircleTest.java | 529 +++++
.../geometry/spherical/twod/Transform2STest.java | 287 +++
.../checkstyle/checkstyle-suppressions.xml | 7 +
248 files changed, 48857 insertions(+), 20978 deletions(-)
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Embedding.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Embedding.java
new file mode 100644
index 0000000..cf258c8
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Embedding.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.core;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** This interface defines mappings between a space and one of its subspaces.
+
+ * <p>Subspaces are the lower-dimension subsets of a space. For example,
+ * in an n-dimension space, the subspaces are the (n-1) dimension space,
+ * the (n-2) dimension space, and so on. This interface can be used regardless
+ * of the difference in number of dimensions between the space and the target
+ * subspace. For example, a line in 3D Euclidean space can use this interface
+ * to map directly from 3D Euclidean space to 1D Euclidean space (ie, the location
+ * along the line).</p>
+ *
+ * @param <P> Point type defining the embedding space.
+ * @param <S> Point type defining the embedded subspace.
+ */
+public interface Embedding<P extends Point<P>, S extends Point<S>> {
+
+ /** Transform a space point into a subspace point.
+ * @param point n-dimension point of the space
+ * @return lower-dimension point of the subspace corresponding to
+ * the specified space point
+ * @see #toSpace
+ */
+ S toSubspace(P point);
+
+ /** Transform a collection of space points into subspace points.
+ * @param points collection of n-dimension points to transform
+ * @return collection of transformed lower-dimension points.
+ * @see #toSubspace(Point)
+ */
+ default List<S> toSubspace(final Collection<P> points) {
+ return points.stream().map(this::toSubspace).collect(Collectors.toList());
+ }
+
+ /** Transform a subspace point into a space point.
+ * @param point lower-dimension point of the subspace
+ * @return n-dimension point of the space corresponding to the
+ * specified subspace point
+ * @see #toSubspace(Point)
+ */
+ P toSpace(S point);
+
+ /** Transform a collection of subspace points into space points.
+ * @param points collection of lower-dimension points to transform
+ * @return collection of transformed n-dimension points.
+ * @see #toSpace(Point)
+ */
+ default List<P> toSpace(final Collection<S> points) {
+ return points.stream().map(this::toSpace).collect(Collectors.toList());
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Geometry.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Geometry.java
index 819a9e5..37c40aa 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Geometry.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Geometry.java
@@ -23,8 +23,8 @@ public final class Geometry {
/** Alias for {@link Math#PI}, placed here for completeness. */
public static final double PI = Math.PI;
- /** Constant value for {@code -pi} */
- public static final double MINUS_PI = - Math.PI;
+ /** Constant value for {@code -pi}. */
+ public static final double MINUS_PI = -Math.PI;
/** Constant value for {@code 2*pi}. */
public static final double TWO_PI = 2.0 * Math.PI;
@@ -36,7 +36,7 @@ public final class Geometry {
public static final double HALF_PI = 0.5 * Math.PI;
/** Constant value for {@code - pi/2}. */
- public static final double MINUS_HALF_PI = - 0.5 * Math.PI;
+ public static final double MINUS_HALF_PI = -0.5 * Math.PI;
/** Constant value for {@code 3*pi/2}. */
public static final double THREE_HALVES_PI = 1.5 * Math.PI;
@@ -46,6 +46,6 @@ public final class Geometry {
*/
public static final double ZERO_PI = 0.0;
- /** Private constructor */
+ /** Private constructor. */
private Geometry() {}
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Region.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Region.java
new file mode 100644
index 0000000..233cd4b
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Region.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+/** Interface representing a region in a space. A region partitions a space
+ * into sets of points lying on the inside, outside, and boundary.
+ * @param <P> Point implementation type
+ */
+public interface Region<P extends Point<P>> {
+
+ /** Return true if the region spans the entire space. In other words,
+ * a region is full if no points in the space are classified as
+ * {@link RegionLocation#OUTSIDE outside}.
+ * @return true if the region spans the entire space
+ */
+ boolean isFull();
+
+ /** Return true if the region is completely empty, ie all points in
+ * the space are classified as {@link RegionLocation#OUTSIDE outside}.
+ * @return true if the region is empty
+ */
+ boolean isEmpty();
+
+ /** Get the size of the region. The meaning of this will vary depending on
+ * the space and dimension of the region. For example, in Euclidean space,
+ * this will be a length in 1D, an area in 2D, and a volume in 3D.
+ * @return the size of the region
+ */
+ double getSize();
+
+ /** Get the size of the boundary of the region. The size is a value in
+ * the {@code d-1} dimension space. For example, in Euclidean space,
+ * this will be a length in 2D and an area in 3D.
+ * @return the size of the boundary of the region
+ */
+ double getBoundarySize();
+
+ /** Get the barycenter of the region or null if none exists. A barycenter
+ * will not exist for empty or infinite regions.
+ * @return the barycenter of the region or null if none exists
+ */
+ P getBarycenter();
+
+ /** Classify the given point with respect to the region.
+ * @param pt the point to classify
+ * @return the location of the point with respect to the region
+ */
+ RegionLocation classify(P pt);
+
+ /** Return true if the given point is on the inside or boundary
+ * of the region.
+ * @param pt the point to test
+ * @return true if the point is on the inside or boundary of the region
+ */
+ default boolean contains(P pt) {
+ final RegionLocation location = classify(pt);
+ return location != null && location != RegionLocation.OUTSIDE;
+ }
+
+ /** Project a point onto the boundary of the region. Null is returned if
+ * the region contains no boundaries (ie, is either {@link #isFull() full}
+ * or {@link #isEmpty() empty}).
+ * @param pt pt to project
+ * @return projection of the point on the boundary of the region or null
+ * if the region does not contain any boundaries
+ */
+ P project(P pt);
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryValueException.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/RegionLocation.java
similarity index 63%
copy from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryValueException.java
copy to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/RegionLocation.java
index a5f7d3f..19f5068 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryValueException.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/RegionLocation.java
@@ -14,20 +14,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.commons.geometry.core.exception;
+package org.apache.commons.geometry.core;
-/** Exception thrown to indicate that a value used in a geometric operation
- * is not valid.
+/** Enumeration containing the possible locations of a point with
+ * respect to a region.
+ * @see Region
*/
-public class GeometryValueException extends GeometryException {
+public enum RegionLocation {
- /** Serializable version identifier */
- private static final long serialVersionUID = 20190210L;
+ /** Value indicating that a point lies on the inside of
+ * a region.
+ */
+ INSIDE,
+
+ /** Value indicating that a point lies on the outside of
+ * a region.
+ */
+ OUTSIDE,
- /** Simple constructor with error message.
- * @param msg exception message string
+ /** Value indicating that a point lies on the boundary of
+ * a region.
*/
- public GeometryValueException(String msg) {
- super(msg);
- }
+ BOUNDARY
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Spatial.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Spatial.java
index c6f76c1..ae5e277 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Spatial.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Spatial.java
@@ -38,4 +38,10 @@ public interface Spatial {
* are NaN
*/
boolean isInfinite();
+
+ /** Returns true if all values in this element are finite, meaning
+ * they are not NaN or infinite.
+ * @return true if all values in this element are finite
+ */
+ boolean isFinite();
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Transform.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Transform.java
new file mode 100644
index 0000000..7eedd67
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Transform.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import java.util.function.Function;
+
+/** This interface represents an <em>inversible affine transform</em> in a space.
+ * Common examples of this type of transform in Euclidean space include
+ * scalings, translations, and rotations.
+ *
+ * <h2>Implementation Note</h2>
+ * <p>Implementations are responsible for ensuring that they meet the geometric
+ * requirements outlined above. These are:
+ * <ol>
+ * <li>The transform must be <a href="https://en.wikipedia.org/wiki/Affine_transformation">affine</a>.
+ * This means that points and parallel lines must be preserved by the transformation. For example,
+ * a translation or rotation in Euclidean 3D space meets this requirement because a mapping exists for
+ * all points and lines that are parallel before the transform remain parallel afterwards.
+ * However, a projective transform that causes parallel lines to meet at a point in infinity does not.
+ * </li>
+ * <li>The transform must be <em>inversible</em>. An inverse transform must exist that will return
+ * the original point if given the transformed point. In other words, for a transform {@code t}, there
+ * must exist an inverse {@code inv} such that {@code inv.apply(t.apply(pt))} returns a point equal to
+ * the input point {@code pt}.
+ * </li>
+ * </ol>
+ * Implementations that do not meet these requirements cannot be expected to produce correct results in
+ * algorithms that use this interface.
+ *
+ * @param <P> Point implementation type
+ * @see <a href="https://en.wikipedia.org/wiki/Affine_transformation">Affine Space</a>
+ */
+public interface Transform<P extends Point<P>> extends Function<P, P> {
+
+ /** Return true if the transform preserves the orientation of the space.
+ * For example, in Euclidean 2D space, this will be true for translations,
+ * rotations, and scalings but will be false for reflections.
+ * @return true if the transform preserves the orientation of the space
+ * @see <a href="https://en.wikipedia.org/wiki/Orientation_(vector_space)">Orientation</a>
+ */
+ boolean preservesOrientation();
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java
index 873e24e..c62abe7 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java
@@ -16,8 +16,6 @@
*/
package org.apache.commons.geometry.core;
-import org.apache.commons.geometry.core.exception.IllegalNormException;
-
/** Interface representing a vector in a vector space or displacement vectors
* in an affine space.
*
@@ -96,7 +94,8 @@ public interface Vector<V extends Vector<V>> extends Spatial {
/** Get a normalized vector aligned with the instance. The returned
* vector has a magnitude of 1.
* @return a new normalized vector
- * @exception IllegalNormException if the norm is zero, NaN, or infinite
+ * @throws org.apache.commons.geometry.core.exception.IllegalNormException if the norm is
+ * zero, NaN, or infinite
*/
V normalize();
@@ -131,7 +130,8 @@ public interface Vector<V extends Vector<V>> extends Spatial {
/** Compute the angular separation between two vectors in radians.
* @param v other vector
* @return angular separation between this instance and v in radians
- * @exception IllegalNormException if either vector has a zero, NaN, or infinite norm
+ * @throws org.apache.commons.geometry.core.exception.IllegalNormException if either
+ * vector has a zero, NaN, or infinite norm
*/
double angle(V v);
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryException.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryException.java
index 57462be..d37bef8 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryException.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryException.java
@@ -22,7 +22,7 @@ package org.apache.commons.geometry.core.exception;
*/
public class GeometryException extends RuntimeException {
- /** Serializable version identifier */
+ /** Serializable version identifier. */
private static final long serialVersionUID = 20180909L;
/** Simple constructor with error message.
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryValueException.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryValueException.java
index a5f7d3f..592045b 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryValueException.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/GeometryValueException.java
@@ -21,7 +21,7 @@ package org.apache.commons.geometry.core.exception;
*/
public class GeometryValueException extends GeometryException {
- /** Serializable version identifier */
+ /** Serializable version identifier. */
private static final long serialVersionUID = 20190210L;
/** Simple constructor with error message.
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java
index 6993704..ad65322 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java
@@ -21,7 +21,7 @@ package org.apache.commons.geometry.core.exception;
*/
public class IllegalNormException extends GeometryValueException {
- /** Serializable version identifier */
+ /** Serializable version identifier. */
private static final long serialVersionUID = 20180909L;
/** Simple constructor accepting the illegal norm value.
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/Equivalency.java
similarity index 52%
copy from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java
copy to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/Equivalency.java
index 6993704..cd52a73 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/exception/IllegalNormException.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/Equivalency.java
@@ -14,27 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.commons.geometry.core.exception;
+package org.apache.commons.geometry.core.internal;
-/** Exception thrown when an illegal vector norm value is encountered
- * in an operation.
+/** Interface for determining equivalency, not exact equality, between
+ * two objects. This is performs a function similar to {@link Object#equals(Object)}
+ * but allows fuzzy comparisons to occur instead of strict equality. This is
+ * especially useful when comparing objects with floating point values that
+ * may not be exact but are operationally equivalent.
+ * @param <T> The type being compared
*/
-public class IllegalNormException extends GeometryValueException {
+public interface Equivalency<T> {
- /** Serializable version identifier */
- private static final long serialVersionUID = 20180909L;
-
- /** Simple constructor accepting the illegal norm value.
- * @param norm the illegal norm value
- */
- public IllegalNormException(double norm) {
- super("Illegal norm: " + norm);
- }
-
- /** Constructor accepting an error message.
- * @param msg the error message
+ /** Determine if this object is equivalent (effectively equal) to the argument.
+ * @param other the object to compare for equivalency
+ * @return true if this object is equivalent to the argument; false otherwise
*/
- public IllegalNormException(String msg) {
- super(msg);
- }
+ boolean eq(T other);
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/GeometryInternalError.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/GeometryInternalError.java
index 219ccdb..bc52f62 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/GeometryInternalError.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/GeometryInternalError.java
@@ -27,7 +27,7 @@ public class GeometryInternalError extends IllegalStateException {
"error in the algorithm implementation than in the calling code or data. Please file a bug report " +
"with the developers.";
- /** Serializable version identifier */
+ /** Serializable version identifier. */
private static final long serialVersionUID = 20180913L;
/** Simple constructor with a default error message.
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/IteratorTransform.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/IteratorTransform.java
new file mode 100644
index 0000000..b60d40d
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/IteratorTransform.java
@@ -0,0 +1,92 @@
+/*
+ * 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.internal;
+
+import java.util.Collection;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+
+/** Class that wraps another iterator, converting each input iterator value into
+ * one or more output iterator values.
+ * @param <I> Input iterator type
+ * @param <T> Output iterator type
+ */
+public abstract class IteratorTransform<I, T> implements Iterator<T> {
+
+ /** Input iterator instance that supplies the input values for this instance. */
+ private final Iterator<I> inputIterator;
+
+ /** Output value queue. */
+ private final Deque<T> outputQueue = new LinkedList<>();
+
+ /** Create a new instance that uses the given iterator as the input source.
+ * @param inputIterator iterator supplying input values
+ */
+ public IteratorTransform(final Iterator<I> inputIterator) {
+ this.inputIterator = inputIterator;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean hasNext() {
+ return loadNextOutput();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public T next() {
+ if (outputQueue.isEmpty()) {
+ throw new NoSuchElementException();
+ }
+
+ return outputQueue.removeFirst();
+ }
+
+ /** Load the next output values into the output queue. Returns true if the output queue
+ * contains more entries.
+ * @return true if more output values are available
+ */
+ private boolean loadNextOutput() {
+ while (outputQueue.isEmpty() && inputIterator.hasNext()) {
+ acceptInput(inputIterator.next());
+ }
+
+ return !outputQueue.isEmpty();
+ }
+
+ /** Add a value to the output queue.
+ * @param value value to add to the output queue
+ */
+ protected void addOutput(final T value) {
+ outputQueue.add(value);
+ }
+
+ /** Add multiple values to the output queue.
+ * @param values values to add to the output queue
+ */
+ protected void addAllOutput(final Collection<T> values) {
+ outputQueue.addAll(values);
+ }
+
+ /** Accept a value from the input iterator. This method should take
+ * the input value and add one or more values to the output queue.
+ * @param input value from the input iterator
+ */
+ protected abstract void acceptInput(I input);
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java
index 15637e0..ce0c9ac 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java
@@ -25,7 +25,7 @@ public class SimpleTupleFormat {
/** Default value separator string. */
private static final String DEFAULT_SEPARATOR = ",";
- /** Space character */
+ /** Space character. */
private static final String SPACE = " ";
/** Static instance configured with default values. Tuples in this format
@@ -34,13 +34,13 @@ public class SimpleTupleFormat {
private static final SimpleTupleFormat DEFAULT_INSTANCE =
new SimpleTupleFormat(",", "(", ")");
- /** String separating tuple values */
+ /** String separating tuple values. */
private final String separator;
- /** String used to signal the start of a tuple; may be null */
+ /** String used to signal the start of a tuple; may be null. */
private final String prefix;
- /** String used to signal the end of a tuple; may be null */
+ /** String used to signal the end of a tuple; may be null. */
private final String suffix;
/** Constructs a new instance with the default string separator (a comma)
@@ -298,8 +298,7 @@ public class SimpleTupleFormat {
matchSequence(str, separator, pos);
return value;
- }
- catch (NumberFormatException exc) {
+ } catch (NumberFormatException exc) {
fail(String.format("unable to parse number from string \"%s\"", substr), str, pos, exc);
return 0.0; // for the compiler
}
@@ -342,7 +341,7 @@ public class SimpleTupleFormat {
int idx = pos.getIndex();
final int len = str.length();
- for (; idx<len; ++idx) {
+ for (; idx < len; ++idx) {
if (!Character.isWhitespace(str.codePointAt(idx))) {
break;
}
@@ -369,7 +368,7 @@ public class SimpleTupleFormat {
int i = idx;
int s = 0;
- for (; i<inputLength && s<seqLength; ++i, ++s) {
+ for (; i < inputLength && s < seqLength; ++i, ++s) {
if (str.codePointAt(i) != seq.codePointAt(s)) {
break;
}
@@ -444,7 +443,7 @@ public class SimpleTupleFormat {
*/
private static class TupleParseException extends IllegalArgumentException {
- /** Serializable version identifier */
+ /** Serializable version identifier. */
private static final long serialVersionUID = 20180629;
/** Simple constructor.
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractConvexHyperplaneBoundedRegion.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractConvexHyperplaneBoundedRegion.java
new file mode 100644
index 0000000..d920335
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractConvexHyperplaneBoundedRegion.java
@@ -0,0 +1,379 @@
+/*
+ * 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.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.exception.GeometryException;
+
+/** Base class for convex hyperplane-bounded regions. This class provides generic implementations of many
+ * algorithms related to convex regions.
+ * @param <P> Point implementation type
+ * @param <S> Convex subhyperplane implementation type
+ */
+public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>, S extends ConvexSubHyperplane<P>>
+ implements HyperplaneBoundedRegion<P>, Serializable {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190812L;
+
+ /** List of boundaries for the region. */
+ private final List<S> boundaries;
+
+ /** Simple constructor. Callers are responsible for ensuring that the given list of subhyperplanes
+ * represent a valid convex region boundary. No validation is performed.
+ * @param boundaries the boundaries of the convex region
+ */
+ protected AbstractConvexHyperplaneBoundedRegion(final List<S> boundaries) {
+ this.boundaries = Collections.unmodifiableList(boundaries);
+ }
+
+ /** Get the boundaries of the convex region. The exact ordering of the boundaries
+ * is not guaranteed.
+ * @return the boundaries of the convex region
+ */
+ public List<S> getBoundaries() {
+ return boundaries;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isFull() {
+ // no boundaries => no outside
+ return boundaries.isEmpty();
+ }
+
+ /** {@inheritDoc}
+ *
+ * <p>This method always returns false.</p>
+ */
+ @Override
+ public boolean isEmpty() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getBoundarySize() {
+ double sum = 0.0;
+ for (final S boundary : boundaries) {
+ sum += boundary.getSize();
+ }
+
+ return sum;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public RegionLocation classify(P pt) {
+ boolean isOn = false;
+
+ HyperplaneLocation loc;
+ for (final S boundary : boundaries) {
+ loc = boundary.getHyperplane().classify(pt);
+
+ if (loc == HyperplaneLocation.PLUS) {
+ return RegionLocation.OUTSIDE;
+ } else if (loc == HyperplaneLocation.ON) {
+ isOn = true;
+ }
+ }
+
+ return isOn ? RegionLocation.BOUNDARY : RegionLocation.INSIDE;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public P project(P pt) {
+
+ P projected;
+ double dist;
+
+ P closestPt = null;
+ double closestDist = Double.POSITIVE_INFINITY;
+
+ for (final S boundary : boundaries) {
+ projected = boundary.closest(pt);
+ dist = pt.distance(projected);
+
+ if (projected != null && (closestPt == null || dist < closestDist)) {
+ closestPt = projected;
+ closestDist = dist;
+ }
+ }
+
+ return closestPt;
+ }
+
+ /** Trim the given convex subhyperplane to the portion contained inside this instance.
+ * @param convexSubHyperplane convex subhyperplane to trim. Null is returned if the subhyperplane
+ * does not intersect the instance.
+ * @return portion of the argument that lies entirely inside the region represented by
+ * this instance, or null if it does not intersect.
+ */
+ public ConvexSubHyperplane<P> trim(final ConvexSubHyperplane<P> convexSubHyperplane) {
+ ConvexSubHyperplane<P> remaining = convexSubHyperplane;
+ for (final S boundary : boundaries) {
+ remaining = remaining.split(boundary.getHyperplane()).getMinus();
+ if (remaining == null) {
+ break;
+ }
+ }
+
+ return remaining;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(this.getClass().getSimpleName())
+ .append("[boundaries= ")
+ .append(boundaries);
+
+ return sb.toString();
+ }
+
+ /** Generic, internal transform method. Subclasses should use this to implement their own transform methods.
+ * @param transform the transform to apply to the instance
+ * @param thisInstance a reference to the current instance; this is passed as
+ * an argument in order to allow it to be a generic type
+ * @param subhpType the type used for the boundary subhyperplanes
+ * @param factory function used to create new convex region instances
+ * @param <R> Region implementation type
+ * @return the result of the transform operation
+ */
+ protected <R extends AbstractConvexHyperplaneBoundedRegion<P, S>> R transformInternal(
+ final Transform<P> transform, final R thisInstance, final Class<S> subhpType,
+ final Function<List<S>, R> factory) {
+
+ if (isFull()) {
+ return thisInstance;
+ }
+
+ final List<S> origBoundaries = getBoundaries();
+
+ final int size = origBoundaries.size();
+ final List<S> tBoundaries = new ArrayList<>(size);
+
+ // determine if the hyperplanes should be reversed
+ final S boundary = origBoundaries.get(0);
+ ConvexSubHyperplane<P> tBoundary = boundary.transform(transform);
+
+ final boolean reverseDirection = swapsInsideOutside(transform);
+
+ // transform all of the segments
+ if (reverseDirection) {
+ tBoundary = tBoundary.reverse();
+ }
+ tBoundaries.add(subhpType.cast(tBoundary));
+
+ for (int i = 1; i < origBoundaries.size(); ++i) {
+ tBoundary = origBoundaries.get(i).transform(transform);
+
+ if (reverseDirection) {
+ tBoundary = tBoundary.reverse();
+ }
+
+ tBoundaries.add(subhpType.cast(tBoundary));
+ }
+
+ return factory.apply(tBoundaries);
+ }
+
+ /** Return true if the given transform swaps the inside and outside of
+ * the region.
+ *
+ * <p>The default behavior of this method is to return true if the transform
+ * does not preserve spatial orientation (ie, {@link Transform#preservesOrientation()}
+ * is false). Subclasses may need to override this method to implement the correct
+ * behavior for their space and dimension.</p>
+ * @param transform transform to check
+ * @return true if the given transform swaps the interior and exterior of
+ * the region
+ */
+ protected boolean swapsInsideOutside(final Transform<P> transform) {
+ return !transform.preservesOrientation();
+ }
+
+ /** Generic, internal split method. Subclasses should call this from their {@link #split(Hyperplane)} methods.
+ * @param splitter splitting hyperplane
+ * @param thisInstance a reference to the current instance; this is passed as
+ * an argument in order to allow it to be a generic type
+ * @param subhpType the type used for the boundary subhyperplanes
+ * @param factory function used to create new convex region instances
+ * @param <R> Region implementation type
+ * @return the result of the split operation
+ */
+ protected <R extends AbstractConvexHyperplaneBoundedRegion<P, S>> Split<R> splitInternal(
+ final Hyperplane<P> splitter, final R thisInstance, final Class<S> subhpType,
+ final Function<List<S>, R> factory) {
+
+ if (isFull()) {
+ final R minus = factory.apply(Arrays.asList(subhpType.cast(splitter.span())));
+ final R plus = factory.apply(Arrays.asList(subhpType.cast(splitter.reverse().span())));
+
+ return new Split<>(minus, plus);
+ } else {
+ final ConvexSubHyperplane<P> trimmedSplitter = trim(splitter.span());
+
+ if (trimmedSplitter == null) {
+ // The splitter lies entirely outside of the region; we need
+ // to determine whether we lie on the plus or minus side of the splitter.
+ // We can use the first boundary to determine this. If the boundary is entirely
+ // on the minus side of the splitter or lies directly on the splitter and has
+ // the same orientation, then the area lies on the minus side of the splitter.
+ // Otherwise, it lies on the plus side.
+ final ConvexSubHyperplane<P> testSegment = boundaries.get(0);
+ final SplitLocation testLocation = testSegment.split(splitter).getLocation();
+
+ if (SplitLocation.MINUS == testLocation ||
+ (SplitLocation.NEITHER == testLocation &&
+ splitter.similarOrientation(testSegment.getHyperplane()))) {
+ return new Split<>(thisInstance, null);
+ }
+
+ return new Split<>(null, thisInstance);
+ }
+
+ final List<S> minusBoundaries = new ArrayList<>();
+ final List<S> plusBoundaries = new ArrayList<>();
+
+ Split<? extends ConvexSubHyperplane<P>> split;
+ ConvexSubHyperplane<P> minusBoundary;
+ ConvexSubHyperplane<P> plusBoundary;
+
+ for (final S boundary : boundaries) {
+ split = boundary.split(splitter);
+
+ minusBoundary = split.getMinus();
+ plusBoundary = split.getPlus();
+
+ if (minusBoundary != null) {
+ minusBoundaries.add(subhpType.cast(minusBoundary));
+ }
+
+ if (plusBoundary != null) {
+ plusBoundaries.add(subhpType.cast(plusBoundary));
+ }
+ }
+
+ minusBoundaries.add(subhpType.cast(trimmedSplitter));
+ plusBoundaries.add(subhpType.cast(trimmedSplitter.reverse()));
+
+ return new Split<>(factory.apply(minusBoundaries), factory.apply(plusBoundaries));
+ }
+ }
+
+ /** Internal class encapsulating the logic for building convex region boundaries from collections of
+ * hyperplanes.
+ * @param <P> Point implementation type
+ * @param <S> ConvexSubHyperplane implementation type
+ */
+ protected static class ConvexRegionBoundaryBuilder<P extends Point<P>, S extends ConvexSubHyperplane<P>> {
+
+ /** Convex subhyperplane implementation type. */
+ private final Class<S> subhyperplaneType;
+
+ /** Construct a new instance for building convex region boundaries with the given convex subhyperplane
+ * implementation type.
+ * @param subhyperplaneType Convex subhyperplane implementation type
+ */
+ public ConvexRegionBoundaryBuilder(final Class<S> subhyperplaneType) {
+ this.subhyperplaneType = subhyperplaneType;
+ }
+
+ /** Compute a list of convex subhyperplanes representing the boundaries of the convex region
+ * bounded by the given collection of hyperplanes.
+ * @param bounds hyperplanes defining the convex region
+ * @return a list of convex subhyperplanes representing the boundaries of the convex region
+ * @throws GeometryException if the given hyperplanes do not form a convex region
+ */
+ public List<S> build(final Iterable<? extends Hyperplane<P>> bounds) {
+
+ final List<S> boundaries = new ArrayList<>();
+
+ // cut each hyperplane by every other hyperplane in order to get the subplane boundaries
+ boolean notConvex = false;
+ int outerIdx = 0;
+ ConvexSubHyperplane<P> subhp;
+
+ for (final Hyperplane<P> hp : bounds) {
+ ++outerIdx;
+ subhp = hp.span();
+
+ int innerIdx = 0;
+ for (final Hyperplane<P> splitter : bounds) {
+ ++innerIdx;
+
+ if (hp != splitter) {
+ final Split<? extends ConvexSubHyperplane<P>> split = subhp.split(splitter);
+
+ if (split.getLocation() == SplitLocation.NEITHER) {
+ if (hp.similarOrientation(splitter)) {
+ // two or more splitters are the equivalent; only
+ // use the segment from the first one
+ if (outerIdx > innerIdx) {
+ subhp = null;
+ }
+ } else {
+ // two or more splitters are coincident and have opposite
+ // orientations, meaning that no area is on the minus side
+ // of both
+ notConvex = true;
+ break;
+ }
+ } else {
+ subhp = subhp.split(splitter).getMinus();
+ }
+
+ if (subhp == null) {
+ break;
+ }
+ } else if (outerIdx > innerIdx) {
+ // this hyperplane is duplicated in the list; skip all but the
+ // first insertion of its subhyperplane
+ subhp = null;
+ break;
+ }
+ }
+
+ if (notConvex) {
+ break;
+ }
+
+ if (subhp != null) {
+ boundaries.add(subhyperplaneType.cast(subhp));
+ }
+ }
+
+ if (notConvex || (outerIdx > 0 && boundaries.isEmpty())) {
+ throw new GeometryException("Bounding hyperplanes do not produce a convex region: " + bounds);
+ }
+
+ return boundaries;
+ }
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractEmbeddingSubHyperplane.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractEmbeddingSubHyperplane.java
new file mode 100644
index 0000000..6bfb2b8
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractEmbeddingSubHyperplane.java
@@ -0,0 +1,110 @@
+/*
+ * 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.io.Serializable;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.Region;
+import org.apache.commons.geometry.core.RegionLocation;
+
+/** Abstract base class for subhyperplane implementations that embed a lower-dimension region through
+ * an embedding hyperplane.
+ * @param <P> Point implementation type
+ * @param <S> Subspace point implementation type
+ * @param <H> Hyperplane containing the embedded subspace
+ */
+public abstract class AbstractEmbeddingSubHyperplane<
+ P extends Point<P>,
+ S extends Point<S>,
+ H extends EmbeddingHyperplane<P, S>> implements SubHyperplane<P>, Serializable {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190729L;
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isFull() {
+ return getSubspaceRegion().isFull();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isEmpty() {
+ return getSubspaceRegion().isEmpty();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isInfinite() {
+ return Double.isInfinite(getSize());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isFinite() {
+ return !isInfinite();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getSize() {
+ return getSubspaceRegion().getSize();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public RegionLocation classify(P point) {
+ final H hyperplane = getHyperplane();
+
+ if (hyperplane.contains(point)) {
+ final S subPoint = hyperplane.toSubspace(point);
+
+ return getSubspaceRegion().classify(subPoint);
+ }
+ return RegionLocation.OUTSIDE;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public P closest(P point) {
+ final H hyperplane = getHyperplane();
+
+ final P projected = hyperplane.project(point);
+ final S subProjected = hyperplane.toSubspace(projected);
+
+ final Region<S> region = getSubspaceRegion();
+ if (region.contains(subProjected)) {
+ return projected;
+ }
+
+ final S subRegionBoundary = region.project(subProjected);
+ if (subRegionBoundary != null) {
+ return hyperplane.toSpace(subRegionBoundary);
+ }
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public abstract H getHyperplane();
+
+ /** Return the embedded subspace region for this instance.
+ * @return the embedded subspace region for this instance
+ */
+ public abstract HyperplaneBoundedRegion<S> getSubspaceRegion();
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractHyperplane.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractHyperplane.java
new file mode 100644
index 0000000..03d1723
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractHyperplane.java
@@ -0,0 +1,70 @@
+/*
+ * 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.io.Serializable;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+
+/** Base class for hyperplane implementations.
+ * @param <P> Point implementation type
+ */
+public abstract class AbstractHyperplane<P extends Point<P>> implements Hyperplane<P>, Serializable {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 1L;
+
+ /** Precision object used to perform floating point comparisons. */
+ private final DoublePrecisionContext precision;
+
+ /** Construct an instance using the given precision context.
+ * @param precision object used to perform floating point comparisons
+ */
+ protected AbstractHyperplane(final DoublePrecisionContext precision) {
+ this.precision = precision;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public HyperplaneLocation classify(final P point) {
+ final double offsetValue = offset(point);
+
+ final int cmp = precision.sign(offsetValue);
+ if (cmp > 0) {
+ return HyperplaneLocation.PLUS;
+ } else if (cmp < 0) {
+ return HyperplaneLocation.MINUS;
+ }
+ return HyperplaneLocation.ON;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean contains(final P point) {
+ final HyperplaneLocation loc = classify(point);
+ return loc == HyperplaneLocation.ON;
+ }
+
+ /** Get the precision object used to perform floating point
+ * comparisons for this instance.
+ * @return the precision object for this instance
+ */
+ public DoublePrecisionContext getPrecision() {
+ return precision;
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractRegion.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractRegion.java
deleted file mode 100644
index d3047c8..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractRegion.java
+++ /dev/null
@@ -1,516 +0,0 @@
-/*
- * 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.ArrayList;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.TreeSet;
-
-import org.apache.commons.geometry.core.Point;
-import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
-
-/** Abstract class for all regions, independent of geometry type or dimension.
-
- * @param <P> Point type defining the space
- * @param <S> Point type defining the sub-space
- */
-public abstract class AbstractRegion<P extends Point<P>, S extends Point<S>> implements Region<P> {
-
- /** Inside/Outside BSP tree. */
- private BSPTree<P> tree;
-
- /** Precision context used to determine floating point equality. */
- private final DoublePrecisionContext precision;
-
- /** Size of the instance. */
- private double size;
-
- /** Barycenter. */
- private P barycenter;
-
- /** Build a region representing the whole space.
- * @param precision precision context used to compare floating point numbers
- */
- protected AbstractRegion(final DoublePrecisionContext precision) {
- this.tree = new BSPTree<>(Boolean.TRUE);
- this.precision = precision;
- }
-
- /** Build a region from an inside/outside BSP tree.
- * <p>The leaf nodes of the BSP tree <em>must</em> have a
- * {@code Boolean} attribute representing the inside status of
- * the corresponding cell (true for inside cells, false for outside
- * cells). In order to avoid building too many small objects, it is
- * recommended to use the predefined constants
- * {@code Boolean.TRUE} and {@code Boolean.FALSE}. The
- * tree also <em>must</em> have either null internal nodes or
- * internal nodes representing the boundary as specified in the
- * {@link #getTree getTree} method).</p>
- * @param tree inside/outside BSP tree representing the region
- * @param precision precision context used to compare floating point values
- */
- protected AbstractRegion(final BSPTree<P> tree, final DoublePrecisionContext precision) {
- this.tree = tree;
- this.precision = precision;
- }
-
- /** Build a Region from a Boundary REPresentation (B-rep).
- * <p>The boundary is provided as a collection of {@link
- * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
- * interior part of the region on its minus side and the exterior on
- * its plus side.</p>
- * <p>The boundary elements can be in any order, and can form
- * several non-connected sets (like for example polygons with holes
- * or a set of disjoints polyhedrons considered as a whole). In
- * fact, the elements do not even need to be connected together
- * (their topological connections are not used here). However, if the
- * boundary does not really separate an inside open from an outside
- * open (open having here its topological meaning), then subsequent
- * calls to the {@link #checkPoint(Point) checkPoint} method will not be
- * meaningful anymore.</p>
- * <p>If the boundary is empty, the region will represent the whole
- * space.</p>
- * @param boundary collection of boundary elements, as a
- * collection of {@link SubHyperplane SubHyperplane} objects
- * @param precision precision context used to compare floating point values
- */
- protected AbstractRegion(final Collection<SubHyperplane<P>> boundary, final DoublePrecisionContext precision) {
-
- this.precision = precision;
-
- if (boundary.size() == 0) {
-
- // the tree represents the whole space
- tree = new BSPTree<>(Boolean.TRUE);
-
- } else {
-
- // sort the boundary elements in decreasing size order
- // (we don't want equal size elements to be removed, so
- // we use a trick to fool the TreeSet)
- final TreeSet<SubHyperplane<P>> ordered = new TreeSet<>(new Comparator<SubHyperplane<P>>() {
- /** {@inheritDoc} */
- @Override
- public int compare(final SubHyperplane<P> o1, final SubHyperplane<P> o2) {
- final double size1 = o1.getSize();
- final double size2 = o2.getSize();
- return (size2 < size1) ? -1 : ((o1 == o2) ? 0 : +1);
- }
- });
- ordered.addAll(boundary);
-
- // build the tree top-down
- tree = new BSPTree<>();
- insertCuts(tree, ordered);
-
- // set up the inside/outside flags
- tree.visit(new BSPTreeVisitor<P>() {
-
- /** {@inheritDoc} */
- @Override
- public Order visitOrder(final BSPTree<P> node) {
- return Order.PLUS_SUB_MINUS;
- }
-
- /** {@inheritDoc} */
- @Override
- public void visitInternalNode(final BSPTree<P> node) {
- }
-
- /** {@inheritDoc} */
- @Override
- public void visitLeafNode(final BSPTree<P> node) {
- if (node.getParent() == null || node == node.getParent().getMinus()) {
- node.setAttribute(Boolean.TRUE);
- } else {
- node.setAttribute(Boolean.FALSE);
- }
- }
- });
-
- }
-
- }
-
- /** Build a convex region from an array of bounding hyperplanes.
- * @param hyperplanes array of bounding hyperplanes (if null, an
- * empty region will be built)
- * @param precision precision context used to compare floating point values
- */
- public AbstractRegion(final Hyperplane<P>[] hyperplanes, final DoublePrecisionContext precision) {
- this.precision = precision;
- if ((hyperplanes == null) || (hyperplanes.length == 0)) {
- tree = new BSPTree<>(Boolean.FALSE);
- } else {
-
- // use the first hyperplane to build the right class
- tree = hyperplanes[0].wholeSpace().getTree(false);
-
- // chop off parts of the space
- BSPTree<P> node = tree;
- node.setAttribute(Boolean.TRUE);
- for (final Hyperplane<P> hyperplane : hyperplanes) {
- if (node.insertCut(hyperplane)) {
- node.setAttribute(null);
- node.getPlus().setAttribute(Boolean.FALSE);
- node = node.getMinus();
- node.setAttribute(Boolean.TRUE);
- }
- }
-
- }
-
- }
-
- /** {@inheritDoc} */
- @Override
- public abstract AbstractRegion<P, S> buildNew(BSPTree<P> newTree);
-
- /** Get the object used to determine floating point equality for this region.
- * @return the floating point precision context for the instance
- */
- public DoublePrecisionContext getPrecision() {
- return precision;
- }
-
- /** Recursively build a tree by inserting cut sub-hyperplanes.
- * @param node current tree node (it is a leaf node at the beginning
- * of the call)
- * @param boundary collection of edges belonging to the cell defined
- * by the node
- */
- private void insertCuts(final BSPTree<P> node, final Collection<SubHyperplane<P>> boundary) {
-
- final Iterator<SubHyperplane<P>> iterator = boundary.iterator();
-
- // build the current level
- Hyperplane<P> inserted = null;
- while ((inserted == null) && iterator.hasNext()) {
- inserted = iterator.next().getHyperplane();
- if (!node.insertCut(inserted.copySelf())) {
- inserted = null;
- }
- }
-
- if (!iterator.hasNext()) {
- return;
- }
-
- // distribute the remaining edges in the two sub-trees
- final ArrayList<SubHyperplane<P>> plusList = new ArrayList<>();
- final ArrayList<SubHyperplane<P>> minusList = new ArrayList<>();
- while (iterator.hasNext()) {
- final SubHyperplane<P> other = iterator.next();
- final SubHyperplane.SplitSubHyperplane<P> split = other.split(inserted);
- switch (split.getSide()) {
- case PLUS:
- plusList.add(other);
- break;
- case MINUS:
- minusList.add(other);
- break;
- case BOTH:
- plusList.add(split.getPlus());
- minusList.add(split.getMinus());
- break;
- default:
- // ignore the sub-hyperplanes belonging to the cut hyperplane
- }
- }
-
- // recurse through lower levels
- insertCuts(node.getPlus(), plusList);
- insertCuts(node.getMinus(), minusList);
-
- }
-
- /** {@inheritDoc} */
- @Override
- public AbstractRegion<P, S> copySelf() {
- return buildNew(tree.copySelf());
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isEmpty() {
- return isEmpty(tree);
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isEmpty(final BSPTree<P> node) {
-
- // we use a recursive function rather than the BSPTreeVisitor
- // interface because we can stop visiting the tree as soon as we
- // have found an inside cell
-
- if (node.getCut() == null) {
- // if we find an inside node, the region is not empty
- return !((Boolean) node.getAttribute());
- }
-
- // check both sides of the sub-tree
- return isEmpty(node.getMinus()) && isEmpty(node.getPlus());
-
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isFull() {
- return isFull(tree);
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean isFull(final BSPTree<P> node) {
-
- // we use a recursive function rather than the BSPTreeVisitor
- // interface because we can stop visiting the tree as soon as we
- // have found an outside cell
-
- if (node.getCut() == null) {
- // if we find an outside node, the region does not cover full space
- return (Boolean) node.getAttribute();
- }
-
- // check both sides of the sub-tree
- return isFull(node.getMinus()) && isFull(node.getPlus());
-
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean contains(final Region<P> region) {
- return new RegionFactory<P>().difference(region, this).isEmpty();
- }
-
- /** {@inheritDoc}
- */
- @Override
- public BoundaryProjection<P> projectToBoundary(final P point) {
- final BoundaryProjector<P, S> projector = new BoundaryProjector<>(point);
- getTree(true).visit(projector);
- return projector.getProjection();
- }
-
- /** {@inheritDoc} */
- @Override
- public Location checkPoint(final P point) {
- return checkPoint(tree, point);
- }
-
- /** Check a point with respect to the region starting at a given node.
- * @param node root node of the region
- * @param point point to check
- * @return a code representing the point status: either {@link
- * Region.Location#INSIDE INSIDE}, {@link Region.Location#OUTSIDE
- * OUTSIDE} or {@link Region.Location#BOUNDARY BOUNDARY}
- */
- protected Location checkPoint(final BSPTree<P> node, final P point) {
- final BSPTree<P> cell = node.getCell(point, precision);
- if (cell.getCut() == null) {
- // the point is in the interior of a cell, just check the attribute
- return ((Boolean) cell.getAttribute()) ? Location.INSIDE : Location.OUTSIDE;
- }
-
- // the point is on a cut-sub-hyperplane, is it on a boundary ?
- final Location minusCode = checkPoint(cell.getMinus(), point);
- final Location plusCode = checkPoint(cell.getPlus(), point);
- return (minusCode == plusCode) ? minusCode : Location.BOUNDARY;
-
- }
-
- /** {@inheritDoc} */
- @Override
- public BSPTree<P> getTree(final boolean includeBoundaryAttributes) {
- if (includeBoundaryAttributes && (tree.getCut() != null) && (tree.getAttribute() == null)) {
- // compute the boundary attributes
- tree.visit(new BoundaryBuilder<P>());
- }
- return tree;
- }
-
- /** {@inheritDoc} */
- @Override
- public double getBoundarySize() {
- final BoundarySizeVisitor<P> visitor = new BoundarySizeVisitor<>();
- getTree(true).visit(visitor);
- return visitor.getSize();
- }
-
- /** {@inheritDoc} */
- @Override
- public double getSize() {
- if (barycenter == null) {
- computeGeometricalProperties();
- }
- return size;
- }
-
- /** Set the size of the instance.
- * @param size size of the instance
- */
- protected void setSize(final double size) {
- this.size = size;
- }
-
- /** {@inheritDoc} */
- @Override
- public P getBarycenter() {
- if (barycenter == null) {
- computeGeometricalProperties();
- }
- return barycenter;
- }
-
- /** Set the barycenter of the instance.
- * @param barycenter barycenter of the instance
- */
- protected void setBarycenter(final P barycenter) {
- this.barycenter = barycenter;
- }
-
- /** Compute some geometrical properties.
- * <p>The properties to compute are the barycenter and the size.</p>
- */
- protected abstract void computeGeometricalProperties();
-
- /** {@inheritDoc} */
- @Override
- public SubHyperplane<P> intersection(final SubHyperplane<P> sub) {
- return recurseIntersection(tree, sub);
- }
-
- /** Recursively compute the parts of a sub-hyperplane that are
- * contained in the region.
- * @param node current BSP tree node
- * @param sub sub-hyperplane traversing the region
- * @return filtered sub-hyperplane
- */
- private SubHyperplane<P> recurseIntersection(final BSPTree<P> node, final SubHyperplane<P> sub) {
-
- if (node.getCut() == null) {
- return (Boolean) node.getAttribute() ? sub.copySelf() : null;
- }
-
- final Hyperplane<P> hyperplane = node.getCut().getHyperplane();
- final SubHyperplane.SplitSubHyperplane<P> split = sub.split(hyperplane);
- if (split.getPlus() != null) {
- if (split.getMinus() != null) {
- // both sides
- final SubHyperplane<P> plus = recurseIntersection(node.getPlus(), split.getPlus());
- final SubHyperplane<P> minus = recurseIntersection(node.getMinus(), split.getMinus());
- if (plus == null) {
- return minus;
- } else if (minus == null) {
- return plus;
- } else {
- return plus.reunite(minus);
- }
- } else {
- // only on plus side
- return recurseIntersection(node.getPlus(), sub);
- }
- } else if (split.getMinus() != null) {
- // only on minus side
- return recurseIntersection(node.getMinus(), sub);
- } else {
- // on hyperplane
- return recurseIntersection(node.getPlus(),
- recurseIntersection(node.getMinus(), sub));
- }
-
- }
-
- /** Transform a region.
- * <p>Applying a transform to a region consist in applying the
- * transform to all the hyperplanes of the underlying BSP tree and
- * of the boundary (and also to the sub-hyperplanes embedded in
- * these hyperplanes) and to the barycenter. The instance is not
- * modified, a new instance is built.</p>
- * @param transform transform to apply
- * @return a new region, resulting from the application of the
- * transform to the instance
- */
- public AbstractRegion<P, S> applyTransform(final Transform<P, S> transform) {
-
- // transform the tree, except for boundary attribute splitters
- final Map<BSPTree<P>, BSPTree<P>> map = new HashMap<>();
- final BSPTree<P> transformedTree = recurseTransform(getTree(false), transform, map);
-
- // set up the boundary attributes splitters
- for (final Map.Entry<BSPTree<P>, BSPTree<P>> entry : map.entrySet()) {
- if (entry.getKey().getCut() != null) {
- @SuppressWarnings("unchecked")
- BoundaryAttribute<P> original = (BoundaryAttribute<P>) entry.getKey().getAttribute();
- if (original != null) {
- @SuppressWarnings("unchecked")
- BoundaryAttribute<P> transformed = (BoundaryAttribute<P>) entry.getValue().getAttribute();
- for (final BSPTree<P> splitter : original.getSplitters()) {
- transformed.getSplitters().add(map.get(splitter));
- }
- }
- }
- }
-
- return buildNew(transformedTree);
-
- }
-
- /** Recursively transform an inside/outside BSP-tree.
- * @param node current BSP tree node
- * @param transform transform to apply
- * @param map transformed nodes map
- * @return a new tree
- */
- @SuppressWarnings("unchecked")
- private BSPTree<P> recurseTransform(final BSPTree<P> node, final Transform<P, S> transform,
- final Map<BSPTree<P>, BSPTree<P>> map) {
-
- final BSPTree<P> transformedNode;
- if (node.getCut() == null) {
- transformedNode = new BSPTree<>(node.getAttribute());
- } else {
-
- final SubHyperplane<P> sub = node.getCut();
- final SubHyperplane<P> tSub = ((AbstractSubHyperplane<P, S>) sub).applyTransform(transform);
- BoundaryAttribute<P> attribute = (BoundaryAttribute<P>) node.getAttribute();
- if (attribute != null) {
- final SubHyperplane<P> tPO = (attribute.getPlusOutside() == null) ?
- null : ((AbstractSubHyperplane<P, S>) attribute.getPlusOutside()).applyTransform(transform);
- final SubHyperplane<P> tPI = (attribute.getPlusInside() == null) ?
- null : ((AbstractSubHyperplane<P, S>) attribute.getPlusInside()).applyTransform(transform);
- // we start with an empty list of splitters, it will be filled in out of recursion
- attribute = new BoundaryAttribute<>(tPO, tPI, new NodesSet<P>());
- }
-
- transformedNode = new BSPTree<>(tSub,
- recurseTransform(node.getPlus(), transform, map),
- recurseTransform(node.getMinus(), transform, map),
- attribute);
- }
-
- map.put(node, transformedNode);
- return transformedNode;
-
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractSubHyperplane.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractSubHyperplane.java
deleted file mode 100644
index c8dfad1..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractSubHyperplane.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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.HashMap;
-import java.util.Map;
-
-import org.apache.commons.geometry.core.Point;
-
-/** This class implements the dimension-independent parts of {@link SubHyperplane}.
-
- * <p>sub-hyperplanes are obtained when parts of an {@link
- * Hyperplane hyperplane} are chopped off by other hyperplanes that
- * intersect it. The remaining part is a convex region. Such objects
- * appear in {@link BSPTree BSP trees} as the intersection of a cut
- * hyperplane with the convex region which it splits, the chopping
- * hyperplanes are the cut hyperplanes closer to the tree root.</p>
-
- * @param <P> Point type defining the space
- * @param <S> Point type defining the sub-space
- */
-public abstract class AbstractSubHyperplane<P extends Point<P>, S extends Point<S>>
- implements SubHyperplane<P> {
-
- /** Underlying hyperplane. */
- private final Hyperplane<P> hyperplane;
-
- /** Remaining region of the hyperplane. */
- private final Region<S> remainingRegion;
-
- /** Build a sub-hyperplane from an hyperplane and a region.
- * @param hyperplane underlying hyperplane
- * @param remainingRegion remaining region of the hyperplane
- */
- protected AbstractSubHyperplane(final Hyperplane<P> hyperplane,
- final Region<S> remainingRegion) {
- this.hyperplane = hyperplane;
- this.remainingRegion = remainingRegion;
- }
-
- /** Build a sub-hyperplane from an hyperplane and a region.
- * @param hyper underlying hyperplane
- * @param remaining remaining region of the hyperplane
- * @return a new sub-hyperplane
- */
- protected abstract AbstractSubHyperplane<P, S> buildNew(final Hyperplane<P> hyper,
- final Region<S> remaining);
-
- /** {@inheritDoc} */
- @Override
- public AbstractSubHyperplane<P, S> copySelf() {
- return buildNew(hyperplane.copySelf(), remainingRegion);
- }
-
- /** Get the underlying hyperplane.
- * @return underlying hyperplane
- */
- @Override
- public Hyperplane<P> getHyperplane() {
- return hyperplane;
- }
-
- /** Get the remaining region of the hyperplane.
- * <p>The returned region is expressed in the canonical hyperplane
- * frame and has the hyperplane dimension. For example a chopped
- * hyperplane in the 3D Euclidean is a 2D plane and the
- * corresponding region is a convex 2D polygon.</p>
- * @return remaining region of the hyperplane
- */
- public Region<S> getRemainingRegion() {
- return remainingRegion;
- }
-
- /** {@inheritDoc} */
- @Override
- public double getSize() {
- return remainingRegion.getSize();
- }
-
- /** {@inheritDoc} */
- @Override
- public AbstractSubHyperplane<P, S> reunite(final SubHyperplane<P> other) {
- @SuppressWarnings("unchecked")
- AbstractSubHyperplane<P, S> o = (AbstractSubHyperplane<P, S>) other;
- return buildNew(hyperplane,
- new RegionFactory<S>().union(remainingRegion, o.remainingRegion));
- }
-
- /** Apply a transform to the instance.
- * <p>The instance must be a (D-1)-dimension sub-hyperplane with
- * respect to the transform <em>not</em> a (D-2)-dimension
- * sub-hyperplane the transform knows how to transform by
- * itself. The transform will consist in transforming first the
- * hyperplane and then the all region using the various methods
- * provided by the transform.</p>
- * @param transform D-dimension transform to apply
- * @return the transformed instance
- */
- public AbstractSubHyperplane<P, S> applyTransform(final Transform<P, S> transform) {
- final Hyperplane<P> tHyperplane = transform.apply(hyperplane);
-
- // transform the tree, except for boundary attribute splitters
- final Map<BSPTree<S>, BSPTree<S>> map = new HashMap<>();
- final BSPTree<S> tTree =
- recurseTransform(remainingRegion.getTree(false), tHyperplane, transform, map);
-
- // set up the boundary attributes splitters
- for (final Map.Entry<BSPTree<S>, BSPTree<S>> entry : map.entrySet()) {
- if (entry.getKey().getCut() != null) {
- @SuppressWarnings("unchecked")
- BoundaryAttribute<S> original = (BoundaryAttribute<S>) entry.getKey().getAttribute();
- if (original != null) {
- @SuppressWarnings("unchecked")
- BoundaryAttribute<S> transformed = (BoundaryAttribute<S>) entry.getValue().getAttribute();
- for (final BSPTree<S> splitter : original.getSplitters()) {
- transformed.getSplitters().add(map.get(splitter));
- }
- }
- }
- }
-
- return buildNew(tHyperplane, remainingRegion.buildNew(tTree));
-
- }
-
- /** Recursively transform a BSP-tree from a sub-hyperplane.
- * @param node current BSP tree node
- * @param transformed image of the instance hyperplane by the transform
- * @param transform transform to apply
- * @param map transformed nodes map
- * @return a new tree
- */
- private BSPTree<S> recurseTransform(final BSPTree<S> node,
- final Hyperplane<P> transformed,
- final Transform<P, S> transform,
- final Map<BSPTree<S>, BSPTree<S>> map) {
-
- final BSPTree<S> transformedNode;
- if (node.getCut() == null) {
- transformedNode = new BSPTree<>(node.getAttribute());
- } else {
-
- @SuppressWarnings("unchecked")
- BoundaryAttribute<S> attribute = (BoundaryAttribute<S>) node.getAttribute();
- if (attribute != null) {
- final SubHyperplane<S> tPO = (attribute.getPlusOutside() == null) ?
- null : transform.apply(attribute.getPlusOutside(), hyperplane, transformed);
- final SubHyperplane<S> tPI = (attribute.getPlusInside() == null) ?
- null : transform.apply(attribute.getPlusInside(), hyperplane, transformed);
- // we start with an empty list of splitters, it will be filled in out of recursion
- attribute = new BoundaryAttribute<>(tPO, tPI, new NodesSet<S>());
- }
-
- transformedNode = new BSPTree<>(transform.apply(node.getCut(), hyperplane, transformed),
- recurseTransform(node.getPlus(), transformed, transform, map),
- recurseTransform(node.getMinus(), transformed, transform, map),
- attribute);
- }
-
- map.put(node, transformedNode);
- return transformedNode;
-
- }
-
- /** {@inheritDoc} */
- @Override
- public abstract SplitSubHyperplane<P> split(Hyperplane<P> hyper);
-
- /** {@inheritDoc} */
- @Override
- public boolean isEmpty() {
- return remainingRegion.isEmpty();
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BSPTree.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BSPTree.java
deleted file mode 100644
index 9a00a89..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BSPTree.java
+++ /dev/null
@@ -1,781 +0,0 @@
-/*
- * 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.ArrayList;
-import java.util.List;
-
-import org.apache.commons.geometry.core.Point;
-import org.apache.commons.geometry.core.partitioning.BSPTreeVisitor.Order;
-import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
-
-/** This class represent a Binary Space Partition tree.
-
- * <p>BSP trees are an efficient way to represent space partitions and
- * to associate attributes with each cell. Each node in a BSP tree
- * represents a convex region which is partitioned in two convex
- * sub-regions at each side of a cut hyperplane. The root tree
- * contains the complete space.</p>
-
- * <p>The main use of such partitions is to use a boolean attribute to
- * define an inside/outside property, hence representing arbitrary
- * polytopes (line segments in 1D, polygons in 2D and polyhedrons in
- * 3D) and to operate on them.</p>
-
- * <p>Another example would be to represent Voronoi tesselations, the
- * attribute of each cell holding the defining point of the cell.</p>
-
- * <p>The application-defined attributes are shared among copied
- * instances and propagated to split parts. These attributes are not
- * used by the BSP-tree algorithms themselves, so the application can
- * use them for any purpose. Since the tree visiting method holds
- * internal and leaf nodes differently, it is possible to use
- * different classes for internal nodes attributes and leaf nodes
- * attributes. This should be used with care, though, because if the
- * tree is modified in any way after attributes have been set, some
- * internal nodes may become leaf nodes and some leaf nodes may become
- * internal nodes.</p>
-
- * <p>One of the main sources for the development of this package was
- * Bruce Naylor, John Amanatides and William Thibault paper <a
- * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
- * BSP Trees Yields Polyhedral Set Operations</a> Proc. Siggraph '90,
- * Computer Graphics 24(4), August 1990, pp 115-124, published by the
- * Association for Computing Machinery (ACM).</p>
-
- * @param <P> Point type defining the space
- */
-public class BSPTree<P extends Point<P>> {
-
- /** Cut sub-hyperplane. */
- private SubHyperplane<P> cut;
-
- /** Tree at the plus side of the cut hyperplane. */
- private BSPTree<P> plus;
-
- /** Tree at the minus side of the cut hyperplane. */
- private BSPTree<P> minus;
-
- /** Parent tree. */
- private BSPTree<P> parent;
-
- /** Application-defined attribute. */
- private Object attribute;
-
- /** Build a tree having only one root cell representing the whole space.
- */
- public BSPTree() {
- cut = null;
- plus = null;
- minus = null;
- parent = null;
- attribute = null;
- }
-
- /** Build a tree having only one root cell representing the whole space.
- * @param attribute attribute of the tree (may be null)
- */
- public BSPTree(final Object attribute) {
- cut = null;
- plus = null;
- minus = null;
- parent = null;
- this.attribute = attribute;
- }
-
- /** Build a BSPTree from its underlying elements.
- * <p>This method does <em>not</em> perform any verification on
- * consistency of its arguments, it should therefore be used only
- * when then caller knows what it is doing.</p>
- * <p>This method is mainly useful to build trees
- * bottom-up. Building trees top-down is realized with the help of
- * method {@link #insertCut insertCut}.</p>
- * @param cut cut sub-hyperplane for the tree
- * @param plus plus side sub-tree
- * @param minus minus side sub-tree
- * @param attribute attribute associated with the node (may be null)
- * @see #insertCut
- */
- public BSPTree(final SubHyperplane<P> cut, final BSPTree<P> plus, final BSPTree<P> minus,
- final Object attribute) {
- this.cut = cut;
- this.plus = plus;
- this.minus = minus;
- this.parent = null;
- this.attribute = attribute;
- plus.parent = this;
- minus.parent = this;
- }
-
- /** Insert a cut sub-hyperplane in a node.
- * <p>The sub-tree starting at this node will be completely
- * overwritten. The new cut sub-hyperplane will be built from the
- * intersection of the provided hyperplane with the cell. If the
- * hyperplane does intersect the cell, the cell will have two
- * children cells with {@code null} attributes on each side of
- * the inserted cut sub-hyperplane. If the hyperplane does not
- * intersect the cell then <em>no</em> cut hyperplane will be
- * inserted and the cell will be changed to a leaf cell. The
- * attribute of the node is never changed.</p>
- * <p>This method is mainly useful when called on leaf nodes
- * (i.e. nodes for which {@link #getCut getCut} returns
- * {@code null}), in this case it provides a way to build a
- * tree top-down (whereas the {@link #BSPTree(SubHyperplane,
- * BSPTree, BSPTree, Object) 4 arguments constructor} is devoted to
- * build trees bottom-up).</p>
- * @param hyperplane hyperplane to insert, it will be chopped in
- * order to fit in the cell defined by the parent nodes of the
- * instance
- * @return true if a cut sub-hyperplane has been inserted (i.e. if
- * the cell now has two leaf child nodes)
- * @see #BSPTree(SubHyperplane, BSPTree, BSPTree, Object)
- */
- public boolean insertCut(final Hyperplane<P> hyperplane) {
-
- if (cut != null) {
- plus.parent = null;
- minus.parent = null;
- }
-
- final SubHyperplane<P> chopped = fitToCell(hyperplane.wholeHyperplane());
- if (chopped == null || chopped.isEmpty()) {
- cut = null;
- plus = null;
- minus = null;
- return false;
- }
-
- cut = chopped;
- plus = new BSPTree<>();
- plus.parent = this;
- minus = new BSPTree<>();
- minus.parent = this;
- return true;
-
- }
-
- /** Copy the instance.
- * <p>The instance created is completely independent of the original
- * one. A deep copy is used, none of the underlying objects are
- * shared (except for the nodes attributes and immutable
- * objects).</p>
- * @return a new tree, copy of the instance
- */
- public BSPTree<P> copySelf() {
-
- if (cut == null) {
- return new BSPTree<>(attribute);
- }
-
- return new BSPTree<>(cut.copySelf(), plus.copySelf(), minus.copySelf(),
- attribute);
-
- }
-
- /** Get the cut sub-hyperplane.
- * @return cut sub-hyperplane, null if this is a leaf tree
- */
- public SubHyperplane<P> getCut() {
- return cut;
- }
-
- /** Get the tree on the plus side of the cut hyperplane.
- * @return tree on the plus side of the cut hyperplane, null if this
- * is a leaf tree
- */
- public BSPTree<P> getPlus() {
- return plus;
- }
-
- /** Get the tree on the minus side of the cut hyperplane.
- * @return tree on the minus side of the cut hyperplane, null if this
- * is a leaf tree
- */
- public BSPTree<P> getMinus() {
- return minus;
- }
-
- /** Get the parent node.
- * @return parent node, null if the node has no parents
- */
- public BSPTree<P> getParent() {
- return parent;
- }
-
- /** Associate an attribute with the instance.
- * @param attribute attribute to associate with the node
- * @see #getAttribute
- */
- public void setAttribute(final Object attribute) {
- this.attribute = attribute;
- }
-
- /** Get the attribute associated with the instance.
- * @return attribute associated with the node or null if no
- * attribute has been explicitly set using the {@link #setAttribute
- * setAttribute} method
- * @see #setAttribute
- */
- public Object getAttribute() {
- return attribute;
- }
-
- /** Visit the BSP tree nodes.
- * @param visitor object visiting the tree nodes
- */
- public void visit(final BSPTreeVisitor<P> visitor) {
- if (cut == null) {
- visitor.visitLeafNode(this);
- } else {
- Order order = visitor.visitOrder(this);
- switch (order) {
- case PLUS_MINUS_SUB:
- plus.visit(visitor);
- minus.visit(visitor);
- visitor.visitInternalNode(this);
- break;
- case PLUS_SUB_MINUS:
- plus.visit(visitor);
- visitor.visitInternalNode(this);
- minus.visit(visitor);
- break;
- case MINUS_PLUS_SUB:
- minus.visit(visitor);
- plus.visit(visitor);
- visitor.visitInternalNode(this);
- break;
- case MINUS_SUB_PLUS:
- minus.visit(visitor);
- visitor.visitInternalNode(this);
- plus.visit(visitor);
- break;
- case SUB_PLUS_MINUS:
- visitor.visitInternalNode(this);
- plus.visit(visitor);
- minus.visit(visitor);
- break;
- case SUB_MINUS_PLUS:
- visitor.visitInternalNode(this);
- minus.visit(visitor);
- plus.visit(visitor);
- break;
- default:
- // we shouldn't end up here since all possibilities are
- // covered above
- throw new IllegalStateException("Invalid node visit order: " + order);
- }
- }
- }
-
- /** Fit a sub-hyperplane inside the cell defined by the instance.
- * <p>Fitting is done by chopping off the parts of the
- * sub-hyperplane that lie outside of the cell using the
- * cut-hyperplanes of the parent nodes of the instance.</p>
- * @param sub sub-hyperplane to fit
- * @return a new sub-hyperplane, guaranteed to have no part outside
- * of the instance cell
- */
- private SubHyperplane<P> fitToCell(final SubHyperplane<P> sub) {
- SubHyperplane<P> s = sub;
- for (BSPTree<P> tree = this; tree.parent != null && s != null; tree = tree.parent) {
- if (tree == tree.parent.plus) {
- s = s.split(tree.parent.cut.getHyperplane()).getPlus();
- } else {
- s = s.split(tree.parent.cut.getHyperplane()).getMinus();
- }
- }
- return s;
- }
-
- /** Get the cell to which a point belongs.
- * <p>If the returned cell is a leaf node the points belongs to the
- * interior of the node, if the cell is an internal node the points
- * belongs to the node cut sub-hyperplane.</p>
- * @param point point to check
- * @param precision precision context used to determine which points
- * close to a cut hyperplane are considered to belong to the hyperplane itself
- * @return the tree cell to which the point belongs
- */
- public BSPTree<P> getCell(final P point, final DoublePrecisionContext precision) {
-
- if (cut == null) {
- return this;
- }
-
- // position of the point with respect to the cut hyperplane
- final double offset = cut.getHyperplane().getOffset(point);
-
- final int comparison = precision.compare(offset, 0.0);
- if (comparison == 0) {
- return this;
- } else if (comparison < 0) {
- // point is on the minus side of the cut hyperplane
- return minus.getCell(point, precision);
- } else {
- // point is on the plus side of the cut hyperplane
- return plus.getCell(point, precision);
- }
-
- }
-
- /** Get the cells whose cut sub-hyperplanes are close to the point.
- * @param point point to check
- * @param maxOffset offset below which a cut sub-hyperplane is considered
- * close to the point (in absolute value)
- * @return close cells (may be empty if all cut sub-hyperplanes are farther
- * than maxOffset from the point)
- */
- public List<BSPTree<P>> getCloseCuts(final P point, final double maxOffset) {
- final List<BSPTree<P>> close = new ArrayList<>();
- recurseCloseCuts(point, maxOffset, close);
- return close;
- }
-
- /** Get the cells whose cut sub-hyperplanes are close to the point.
- * @param point point to check
- * @param maxOffset offset below which a cut sub-hyperplane is considered
- * close to the point (in absolute value)
- * @param close list to fill
- */
- private void recurseCloseCuts(final P point, final double maxOffset,
- final List<BSPTree<P>> close) {
- if (cut != null) {
-
- // position of the point with respect to the cut hyperplane
- final double offset = cut.getHyperplane().getOffset(point);
-
- if (offset < -maxOffset) {
- // point is on the minus side of the cut hyperplane
- minus.recurseCloseCuts(point, maxOffset, close);
- } else if (offset > maxOffset) {
- // point is on the plus side of the cut hyperplane
- plus.recurseCloseCuts(point, maxOffset, close);
- } else {
- // point is close to the cut hyperplane
- close.add(this);
- minus.recurseCloseCuts(point, maxOffset, close);
- plus.recurseCloseCuts(point, maxOffset, close);
- }
-
- }
- }
-
- /** Perform condensation on a tree.
- * <p>The condensation operation is not recursive, it must be called
- * explicitly from leaves to root.</p>
- */
- private void condense() {
- if ((cut != null) && (plus.cut == null) && (minus.cut == null) &&
- (((plus.attribute == null) && (minus.attribute == null)) ||
- ((plus.attribute != null) && plus.attribute.equals(minus.attribute)))) {
- attribute = (plus.attribute == null) ? minus.attribute : plus.attribute;
- cut = null;
- plus = null;
- minus = null;
- }
- }
-
- /** Merge a BSP tree with the instance.
- * <p>All trees are modified (parts of them are reused in the new
- * tree), it is the responsibility of the caller to ensure a copy
- * has been done before if any of the former tree should be
- * preserved, <em>no</em> such copy is done here!</p>
- * <p>The algorithm used here is directly derived from the one
- * described in the Naylor, Amanatides and Thibault paper (section
- * III, Binary Partitioning of a BSP Tree).</p>
- * @param tree other tree to merge with the instance (will be
- * <em>unusable</em> after the operation, as well as the
- * instance itself)
- * @param leafMerger object implementing the final merging phase
- * (this is where the semantic of the operation occurs, generally
- * depending on the attribute of the leaf node)
- * @return a new tree, result of <code>instance <op>
- * tree</code>, this value can be ignored if parentTree is not null
- * since all connections have already been established
- */
- public BSPTree<P> merge(final BSPTree<P> tree, final LeafMerger<P> leafMerger) {
- return merge(tree, leafMerger, null, false);
- }
-
- /** Merge a BSP tree with the instance.
- * @param tree other tree to merge with the instance (will be
- * <em>unusable</em> after the operation, as well as the
- * instance itself)
- * @param leafMerger object implementing the final merging phase
- * (this is where the semantic of the operation occurs, generally
- * depending on the attribute of the leaf node)
- * @param parentTree parent tree to connect to (may be null)
- * @param isPlusChild if true and if parentTree is not null, the
- * resulting tree should be the plus child of its parent, ignored if
- * parentTree is null
- * @return a new tree, result of <code>instance <op>
- * tree</code>, this value can be ignored if parentTree is not null
- * since all connections have already been established
- */
- private BSPTree<P> merge(final BSPTree<P> tree, final LeafMerger<P> leafMerger,
- final BSPTree<P> parentTree, final boolean isPlusChild) {
- if (cut == null) {
- // cell/tree operation
- return leafMerger.merge(this, tree, parentTree, isPlusChild, true);
- } else if (tree.cut == null) {
- // tree/cell operation
- return leafMerger.merge(tree, this, parentTree, isPlusChild, false);
- } else {
- // tree/tree operation
- final BSPTree<P> merged = tree.split(cut);
- if (parentTree != null) {
- merged.parent = parentTree;
- if (isPlusChild) {
- parentTree.plus = merged;
- } else {
- parentTree.minus = merged;
- }
- }
-
- // merging phase
- plus.merge(merged.plus, leafMerger, merged, true);
- minus.merge(merged.minus, leafMerger, merged, false);
- merged.condense();
- if (merged.cut != null) {
- merged.cut = merged.fitToCell(merged.cut.getHyperplane().wholeHyperplane());
- }
-
- return merged;
-
- }
- }
-
- /** This interface gather the merging operations between a BSP tree
- * leaf and another BSP tree.
- * <p>As explained in Bruce Naylor, John Amanatides and William
- * Thibault paper <a
- * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
- * BSP Trees Yields Polyhedral Set Operations</a>,
- * the operations on {@link BSPTree BSP trees} can be expressed as a
- * generic recursive merging operation where only the final part,
- * when one of the operand is a leaf, is specific to the real
- * operation semantics. For example, a tree representing a region
- * using a boolean attribute to identify inside cells and outside
- * cells would use four different objects to implement the final
- * merging phase of the four set operations union, intersection,
- * difference and symmetric difference (exclusive or).</p>
- * @param <S> Type of the space.
- */
- public interface LeafMerger<S extends Point<S>> {
-
- /** Merge a leaf node and a tree node.
- * <p>This method is called at the end of a recursive merging
- * resulting from a {@code tree1.merge(tree2, leafMerger)}
- * call, when one of the sub-trees involved is a leaf (i.e. when
- * its cut-hyperplane is null). This is the only place where the
- * precise semantics of the operation are required. For all upper
- * level nodes in the tree, the merging operation is only a
- * generic partitioning algorithm.</p>
- * <p>Since the final operation may be non-commutative, it is
- * important to know if the leaf node comes from the instance tree
- * ({@code tree1}) or the argument tree
- * ({@code tree2}). The third argument of the method is
- * devoted to this. It can be ignored for commutative
- * operations.</p>
- * <p>The {@link BSPTree#insertInTree BSPTree.insertInTree} method
- * may be useful to implement this method.</p>
- * @param leaf leaf node (its cut hyperplane is guaranteed to be
- * null)
- * @param tree tree node (its cut hyperplane may be null or not)
- * @param parentTree parent tree to connect to (may be null)
- * @param isPlusChild if true and if parentTree is not null, the
- * resulting tree should be the plus child of its parent, ignored if
- * parentTree is null
- * @param leafFromInstance if true, the leaf node comes from the
- * instance tree ({@code tree1}) and the tree node comes from
- * the argument tree ({@code tree2})
- * @return the BSP tree resulting from the merging (may be one of
- * the arguments)
- */
- BSPTree<S> merge(BSPTree<S> leaf, BSPTree<S> tree, BSPTree<S> parentTree,
- boolean isPlusChild, boolean leafFromInstance);
-
- }
-
- /** This interface handles the corner cases when an internal node cut sub-hyperplane vanishes.
- * <p>
- * Such cases happens for example when a cut sub-hyperplane is inserted into
- * another tree (during a merge operation), and is split in several parts,
- * some of which becomes smaller than the tolerance. The corresponding node
- * as then no cut sub-hyperplane anymore, but does have children. This interface
- * specifies how to handle this situation.
- * setting
- * </p>
- * @param <S> Type of the space.
- */
- public interface VanishingCutHandler<S extends Point<S>> {
-
- /** Fix a node with both vanished cut and children.
- * @param node node to fix
- * @return fixed node
- */
- BSPTree<S> fixNode(BSPTree<S> node);
-
- }
-
- /** Split a BSP tree by an external sub-hyperplane.
- * <p>Split a tree in two halves, on each side of the
- * sub-hyperplane. The instance is not modified.</p>
- * <p>The tree returned is not upward-consistent: despite all of its
- * sub-trees cut sub-hyperplanes (including its own cut
- * sub-hyperplane) are bounded to the current cell, it is <em>not</em>
- * attached to any parent tree yet. This tree is intended to be
- * later inserted into an higher level tree.</p>
- * <p>The algorithm used here is the one given in Naylor, Amanatides
- * and Thibault paper (section III, Binary Partitioning of a BSP
- * Tree).</p>
- * @param sub partitioning sub-hyperplane, must be already clipped
- * to the convex region represented by the instance, will be used as
- * the cut sub-hyperplane of the returned tree
- * @return a tree having the specified sub-hyperplane as its cut
- * sub-hyperplane, the two parts of the split instance as its two
- * sub-trees and a null parent
- */
- public BSPTree<P> split(final SubHyperplane<P> sub) {
-
- if (cut == null) {
- return new BSPTree<>(sub, copySelf(), new BSPTree<P>(attribute), null);
- }
-
- final Hyperplane<P> cHyperplane = cut.getHyperplane();
- final Hyperplane<P> sHyperplane = sub.getHyperplane();
- final SubHyperplane.SplitSubHyperplane<P> subParts = sub.split(cHyperplane);
- switch (subParts.getSide()) {
- case PLUS :
- { // the partitioning sub-hyperplane is entirely in the plus sub-tree
- final BSPTree<P> split = plus.split(sub);
- if (cut.split(sHyperplane).getSide() == Side.PLUS) {
- split.plus =
- new BSPTree<>(cut.copySelf(), split.plus, minus.copySelf(), attribute);
- split.plus.condense();
- split.plus.parent = split;
- } else {
- split.minus =
- new BSPTree<>(cut.copySelf(), split.minus, minus.copySelf(), attribute);
- split.minus.condense();
- split.minus.parent = split;
- }
- return split;
- }
- case MINUS :
- { // the partitioning sub-hyperplane is entirely in the minus sub-tree
- final BSPTree<P> split = minus.split(sub);
- if (cut.split(sHyperplane).getSide() == Side.PLUS) {
- split.plus =
- new BSPTree<>(cut.copySelf(), plus.copySelf(), split.plus, attribute);
- split.plus.condense();
- split.plus.parent = split;
- } else {
- split.minus =
- new BSPTree<>(cut.copySelf(), plus.copySelf(), split.minus, attribute);
- split.minus.condense();
- split.minus.parent = split;
- }
- return split;
- }
- case BOTH :
- {
- final SubHyperplane.SplitSubHyperplane<P> cutParts = cut.split(sHyperplane);
- final BSPTree<P> split =
- new BSPTree<>(sub, plus.split(subParts.getPlus()), minus.split(subParts.getMinus()),
- null);
- split.plus.cut = cutParts.getPlus();
- split.minus.cut = cutParts.getMinus();
- final BSPTree<P> tmp = split.plus.minus;
- split.plus.minus = split.minus.plus;
- split.plus.minus.parent = split.plus;
- split.minus.plus = tmp;
- split.minus.plus.parent = split.minus;
- split.plus.condense();
- split.minus.condense();
- return split;
- }
- default :
- return cHyperplane.sameOrientationAs(sHyperplane) ?
- new BSPTree<>(sub, plus.copySelf(), minus.copySelf(), attribute) :
- new BSPTree<>(sub, minus.copySelf(), plus.copySelf(), attribute);
- }
-
- }
-
- /** Insert the instance into another tree.
- * <p>The instance itself is modified so its former parent should
- * not be used anymore.</p>
- * @param parentTree parent tree to connect to (may be null)
- * @param isPlusChild if true and if parentTree is not null, the
- * resulting tree should be the plus child of its parent, ignored if
- * parentTree is null
- * @param vanishingHandler handler to use for handling very rare corner
- * cases of vanishing cut sub-hyperplanes in internal nodes during merging
- * @see LeafMerger
- */
- public void insertInTree(final BSPTree<P> parentTree, final boolean isPlusChild,
- final VanishingCutHandler<P> vanishingHandler) {
-
- // set up parent/child links
- parent = parentTree;
- if (parentTree != null) {
- if (isPlusChild) {
- parentTree.plus = this;
- } else {
- parentTree.minus = this;
- }
- }
-
- // make sure the inserted tree lies in the cell defined by its parent nodes
- if (cut != null) {
-
- // explore the parent nodes from here towards tree root
- for (BSPTree<P> tree = this; tree.parent != null; tree = tree.parent) {
-
- // this is an hyperplane of some parent node
- final Hyperplane<P> hyperplane = tree.parent.cut.getHyperplane();
-
- // chop off the parts of the inserted tree that extend
- // on the wrong side of this parent hyperplane
- if (tree == tree.parent.plus) {
- cut = cut.split(hyperplane).getPlus();
- plus.chopOffMinus(hyperplane, vanishingHandler);
- minus.chopOffMinus(hyperplane, vanishingHandler);
- } else {
- cut = cut.split(hyperplane).getMinus();
- plus.chopOffPlus(hyperplane, vanishingHandler);
- minus.chopOffPlus(hyperplane, vanishingHandler);
- }
-
- if (cut == null) {
- // the cut sub-hyperplane has vanished
- final BSPTree<P> fixed = vanishingHandler.fixNode(this);
- cut = fixed.cut;
- plus = fixed.plus;
- minus = fixed.minus;
- attribute = fixed.attribute;
- if (cut == null) {
- break;
- }
- }
-
- }
-
- // since we may have drop some parts of the inserted tree,
- // perform a condensation pass to keep the tree structure simple
- condense();
-
- }
-
- }
-
- /** Prune a tree around a cell.
- * <p>
- * This method can be used to extract a convex cell from a tree.
- * The original cell may either be a leaf node or an internal node.
- * If it is an internal node, it's subtree will be ignored (i.e. the
- * extracted cell will be a leaf node in all cases). The original
- * tree to which the original cell belongs is not touched at all,
- * a new independent tree will be built.
- * </p>
- * @param cellAttribute attribute to set for the leaf node
- * corresponding to the initial instance cell
- * @param otherLeafsAttributes attribute to set for the other leaf
- * nodes
- * @param internalAttributes attribute to set for the internal nodes
- * @return a new tree (the original tree is left untouched) containing
- * a single branch with the cell as a leaf node, and other leaf nodes
- * as the remnants of the pruned branches
- */
- public BSPTree<P> pruneAroundConvexCell(final Object cellAttribute,
- final Object otherLeafsAttributes,
- final Object internalAttributes) {
-
- // build the current cell leaf
- BSPTree<P> tree = new BSPTree<>(cellAttribute);
-
- // build the pruned tree bottom-up
- for (BSPTree<P> current = this; current.parent != null; current = current.parent) {
- final SubHyperplane<P> parentCut = current.parent.cut.copySelf();
- final BSPTree<P> sibling = new BSPTree<>(otherLeafsAttributes);
- if (current == current.parent.plus) {
- tree = new BSPTree<>(parentCut, tree, sibling, internalAttributes);
- } else {
- tree = new BSPTree<>(parentCut, sibling, tree, internalAttributes);
- }
- }
-
- return tree;
-
- }
-
- /** Chop off parts of the tree.
- * <p>The instance is modified in place, all the parts that are on
- * the minus side of the chopping hyperplane are discarded, only the
- * parts on the plus side remain.</p>
- * @param hyperplane chopping hyperplane
- * @param vanishingHandler handler to use for handling very rare corner
- * cases of vanishing cut sub-hyperplanes in internal nodes during merging
- */
- private void chopOffMinus(final Hyperplane<P> hyperplane, final VanishingCutHandler<P> vanishingHandler) {
- if (cut != null) {
-
- cut = cut.split(hyperplane).getPlus();
- plus.chopOffMinus(hyperplane, vanishingHandler);
- minus.chopOffMinus(hyperplane, vanishingHandler);
-
- if (cut == null) {
- // the cut sub-hyperplane has vanished
- final BSPTree<P> fixed = vanishingHandler.fixNode(this);
- cut = fixed.cut;
- plus = fixed.plus;
- minus = fixed.minus;
- attribute = fixed.attribute;
- }
-
- }
- }
-
- /** Chop off parts of the tree.
- * <p>The instance is modified in place, all the parts that are on
- * the plus side of the chopping hyperplane are discarded, only the
- * parts on the minus side remain.</p>
- * @param hyperplane chopping hyperplane
- * @param vanishingHandler handler to use for handling very rare corner
- * cases of vanishing cut sub-hyperplanes in internal nodes during merging
- */
- private void chopOffPlus(final Hyperplane<P> hyperplane, final VanishingCutHandler<P> vanishingHandler) {
- if (cut != null) {
-
- cut = cut.split(hyperplane).getMinus();
- plus.chopOffPlus(hyperplane, vanishingHandler);
- minus.chopOffPlus(hyperplane, vanishingHandler);
-
- if (cut == null) {
- // the cut sub-hyperplane has vanished
- final BSPTree<P> fixed = vanishingHandler.fixNode(this);
- cut = fixed.cut;
- plus = fixed.plus;
- minus = fixed.minus;
- attribute = fixed.attribute;
- }
-
- }
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BSPTreeVisitor.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BSPTreeVisitor.java
deleted file mode 100644
index 52d0eee..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BSPTreeVisitor.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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 org.apache.commons.geometry.core.Point;
-
-/** This interface is used to visit {@link BSPTree BSP tree} nodes.
-
- * <p>Navigation through {@link BSPTree BSP trees} can be done using
- * two different point of views:</p>
- * <ul>
- * <li>
- * the first one is in a node-oriented way using the {@link
- * BSPTree#getPlus}, {@link BSPTree#getMinus} and {@link
- * BSPTree#getParent} methods. Terminal nodes without associated
- * {@link SubHyperplane sub-hyperplanes} can be visited this way,
- * there is no constraint in the visit order, and it is possible
- * to visit either all nodes or only a subset of the nodes
- * </li>
- * <li>
- * the second one is in a sub-hyperplane-oriented way using
- * classes implementing this interface which obeys the visitor
- * design pattern. The visit order is provided by the visitor as
- * each node is first encountered. Each node is visited exactly
- * once.
- * </li>
- * </ul>
-
- * @param <P> Point type defining the space
-
- * @see BSPTree
- * @see SubHyperplane
- */
-public interface BSPTreeVisitor<P extends Point<P>> {
-
- /** Enumerate for visit order with respect to plus sub-tree, minus sub-tree and cut sub-hyperplane. */
- enum Order {
- /** Indicator for visit order plus sub-tree, then minus sub-tree,
- * and last cut sub-hyperplane.
- */
- PLUS_MINUS_SUB,
-
- /** Indicator for visit order plus sub-tree, then cut sub-hyperplane,
- * and last minus sub-tree.
- */
- PLUS_SUB_MINUS,
-
- /** Indicator for visit order minus sub-tree, then plus sub-tree,
- * and last cut sub-hyperplane.
- */
- MINUS_PLUS_SUB,
-
- /** Indicator for visit order minus sub-tree, then cut sub-hyperplane,
- * and last plus sub-tree.
- */
- MINUS_SUB_PLUS,
-
- /** Indicator for visit order cut sub-hyperplane, then plus sub-tree,
- * and last minus sub-tree.
- */
- SUB_PLUS_MINUS,
-
- /** Indicator for visit order cut sub-hyperplane, then minus sub-tree,
- * and last plus sub-tree.
- */
- SUB_MINUS_PLUS;
- }
-
- /** Determine the visit order for this node.
- * <p>Before attempting to visit an internal node, this method is
- * called to determine the desired ordering of the visit. It is
- * guaranteed that this method will be called before {@link
- * #visitInternalNode visitInternalNode} for a given node, it will be
- * called exactly once for each internal node.</p>
- * @param node BSP node guaranteed to have a non null cut sub-hyperplane
- * @return desired visit order, must be one of
- * {@link Order#PLUS_MINUS_SUB}, {@link Order#PLUS_SUB_MINUS},
- * {@link Order#MINUS_PLUS_SUB}, {@link Order#MINUS_SUB_PLUS},
- * {@link Order#SUB_PLUS_MINUS}, {@link Order#SUB_MINUS_PLUS}
- */
- Order visitOrder(BSPTree<P> node);
-
- /** Visit a BSP tree node node having a non-null sub-hyperplane.
- * <p>It is guaranteed that this method will be called after {@link
- * #visitOrder visitOrder} has been called for a given node,
- * it wil be called exactly once for each internal node.</p>
- * @param node BSP node guaranteed to have a non null cut sub-hyperplane
- * @see #visitLeafNode
- */
- void visitInternalNode(BSPTree<P> node);
-
- /** Visit a leaf BSP tree node node having a null sub-hyperplane.
- * @param node leaf BSP node having a null sub-hyperplane
- * @see #visitInternalNode
- */
- void visitLeafNode(BSPTree<P> node);
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryAttribute.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryAttribute.java
deleted file mode 100644
index 0476c34..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryAttribute.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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 org.apache.commons.geometry.core.Point;
-
-/** Class holding boundary attributes.
- *
- * <p>This class is used for the attributes associated with the
- * nodes of region boundary shell trees returned by the {@link
- * Region#getTree(boolean) Region.getTree(includeBoundaryAttributes)}
- * when the boolean {@code includeBoundaryAttributes} parameter is
- * set to {@code true}. It contains the parts of the node cut
- * sub-hyperplane that belong to the boundary.</p>
- *
- * <p>This class is a simple placeholder, it does not provide any
- * processing methods.</p>
- *
- * @param <P> Point type defining the space
- * @see Region#getTree
- */
-public class BoundaryAttribute<P extends Point<P>> {
-
- /** Part of the node cut sub-hyperplane that belongs to the
- * boundary and has the outside of the region on the plus side of
- * its underlying hyperplane (may be null).
- */
- private final SubHyperplane<P> plusOutside;
-
- /** Part of the node cut sub-hyperplane that belongs to the
- * boundary and has the inside of the region on the plus side of
- * its underlying hyperplane (may be null).
- */
- private final SubHyperplane<P> plusInside;
-
- /** Sub-hyperplanes that were used to split the boundary part. */
- private final NodesSet<P> splitters;
-
- /** Simple constructor.
- * @param plusOutside part of the node cut sub-hyperplane that
- * belongs to the boundary and has the outside of the region on
- * the plus side of its underlying hyperplane (may be null)
- * @param plusInside part of the node cut sub-hyperplane that
- * belongs to the boundary and has the inside of the region on the
- * plus side of its underlying hyperplane (may be null)
- * @param splitters sub-hyperplanes that were used to
- * split the boundary part (may be null)
- */
- BoundaryAttribute(final SubHyperplane<P> plusOutside,
- final SubHyperplane<P> plusInside,
- final NodesSet<P> splitters) {
- this.plusOutside = plusOutside;
- this.plusInside = plusInside;
- this.splitters = splitters;
- }
-
- /** Get the part of the node cut sub-hyperplane that belongs to the
- * boundary and has the outside of the region on the plus side of
- * its underlying hyperplane.
- * @return part of the node cut sub-hyperplane that belongs to the
- * boundary and has the outside of the region on the plus side of
- * its underlying hyperplane
- */
- public SubHyperplane<P> getPlusOutside() {
- return plusOutside;
- }
-
- /** Get the part of the node cut sub-hyperplane that belongs to the
- * boundary and has the inside of the region on the plus side of
- * its underlying hyperplane.
- * @return part of the node cut sub-hyperplane that belongs to the
- * boundary and has the inside of the region on the plus side of
- * its underlying hyperplane
- */
- public SubHyperplane<P> getPlusInside() {
- return plusInside;
- }
-
- /** Get the sub-hyperplanes that were used to split the boundary part.
- * @return sub-hyperplanes that were used to split the boundary part
- */
- public NodesSet<P> getSplitters() {
- return splitters;
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryBuilder.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryBuilder.java
deleted file mode 100644
index 63d19b8..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryBuilder.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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 org.apache.commons.geometry.core.Point;
-
-/** Visitor building boundary shell tree.
- *
- * <p>
- * The boundary shell is represented as {@link BoundaryAttribute boundary attributes}
- * at each internal node.
- * </p>
- *
- * @param <P> Point type defining the space.
- */
-class BoundaryBuilder<P extends Point<P>> implements BSPTreeVisitor<P> {
-
- /** {@inheritDoc} */
- @Override
- public Order visitOrder(BSPTree<P> node) {
- return Order.PLUS_MINUS_SUB;
- }
-
- /** {@inheritDoc} */
- @Override
- public void visitInternalNode(BSPTree<P> node) {
-
- SubHyperplane<P> plusOutside = null;
- SubHyperplane<P> plusInside = null;
- NodesSet<P> splitters = null;
-
- // characterize the cut sub-hyperplane,
- // first with respect to the plus sub-tree
- final Characterization<P> plusChar = new Characterization<>(node.getPlus(), node.getCut().copySelf());
-
- if (plusChar.touchOutside()) {
- // plusChar.outsideTouching() corresponds to a subset of the cut sub-hyperplane
- // known to have outside cells on its plus side, we want to check if parts
- // of this subset do have inside cells on their minus side
- final Characterization<P> minusChar = new Characterization<>(node.getMinus(), plusChar.outsideTouching());
- if (minusChar.touchInside()) {
- // this part belongs to the boundary,
- // it has the outside on its plus side and the inside on its minus side
- plusOutside = minusChar.insideTouching();
- splitters = new NodesSet<>();
- splitters.addAll(minusChar.getInsideSplitters());
- splitters.addAll(plusChar.getOutsideSplitters());
- }
- }
-
- if (plusChar.touchInside()) {
- // plusChar.insideTouching() corresponds to a subset of the cut sub-hyperplane
- // known to have inside cells on its plus side, we want to check if parts
- // of this subset do have outside cells on their minus side
- final Characterization<P> minusChar = new Characterization<>(node.getMinus(), plusChar.insideTouching());
- if (minusChar.touchOutside()) {
- // this part belongs to the boundary,
- // it has the inside on its plus side and the outside on its minus side
- plusInside = minusChar.outsideTouching();
- if (splitters == null) {
- splitters = new NodesSet<>();
- }
- splitters.addAll(minusChar.getOutsideSplitters());
- splitters.addAll(plusChar.getInsideSplitters());
- }
- }
-
- if (splitters != null) {
- // the parent nodes are natural splitters for boundary sub-hyperplanes
- for (BSPTree<P> up = node.getParent(); up != null; up = up.getParent()) {
- splitters.add(up);
- }
- }
-
- // set the boundary attribute at non-leaf nodes
- node.setAttribute(new BoundaryAttribute<>(plusOutside, plusInside, splitters));
-
- }
-
- /** {@inheritDoc} */
- @Override
- public void visitLeafNode(BSPTree<P> node) {
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryProjection.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryProjection.java
deleted file mode 100644
index 27709c2..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryProjection.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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 org.apache.commons.geometry.core.Point;
-
-/** Class holding the result of point projection on region boundary.
- *
- * <p>This class is a simple placeholder, it does not provide any
- * processing methods.</p>
- *
- * <p>Instances of this class are guaranteed to be immutable</p>
- *
- * @param <P> Point type defining the space
- * @see AbstractRegion#projectToBoundary(Point)
- */
-public class BoundaryProjection<P extends Point<P>> {
-
- /** Original point. */
- private final P original;
-
- /** Projected point. */
- private final P projected;
-
- /** Offset of the point with respect to the boundary it is projected on. */
- private final double offset;
-
- /** Constructor from raw elements.
- * @param original original point
- * @param projected projected point
- * @param offset offset of the point with respect to the boundary it is projected on
- */
- public BoundaryProjection(final P original, final P projected, final double offset) {
- this.original = original;
- this.projected = projected;
- this.offset = offset;
- }
-
- /** Get the original point.
- * @return original point
- */
- public P getOriginal() {
- return original;
- }
-
- /** Projected point.
- * @return projected point, or null if there are no boundary
- */
- public P getProjected() {
- return projected;
- }
-
- /** Offset of the point with respect to the boundary it is projected on.
- * <p>
- * The offset with respect to the boundary is negative if the {@link
- * #getOriginal() original point} is inside the region, and positive otherwise.
- * </p>
- * <p>
- * If there are no boundary, the value is set to either {@code
- * Double.POSITIVE_INFINITY} if the region is empty (i.e. all points are
- * outside of the region) or {@code Double.NEGATIVE_INFINITY} if the region
- * covers the whole space (i.e. all points are inside of the region).
- * </p>
- * @return offset of the point with respect to the boundary it is projected on
- */
- public double getOffset() {
- return offset;
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryProjector.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryProjector.java
deleted file mode 100644
index f694a89..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryProjector.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * 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.ArrayList;
-import java.util.List;
-
-import org.apache.commons.geometry.core.Point;
-import org.apache.commons.geometry.core.partitioning.Region.Location;
-
-/** Local tree visitor to compute projection on boundary.
- * @param <P> Point type defining the space
- * @param <S> Point type defining the sub-space
- */
-class BoundaryProjector<P extends Point<P>, S extends Point<S>> implements BSPTreeVisitor<P> {
-
- /** Original point. */
- private final P original;
-
- /** Current best projected point. */
- private P projected;
-
- /** Leaf node closest to the test point. */
- private BSPTree<P> leaf;
-
- /** Current offset. */
- private double offset;
-
- /** Simple constructor.
- * @param original original point
- */
- BoundaryProjector(final P original) {
- this.original = original;
- this.projected = null;
- this.leaf = null;
- this.offset = Double.POSITIVE_INFINITY;
- }
-
- /** {@inheritDoc} */
- @Override
- public Order visitOrder(final BSPTree<P> node) {
- // we want to visit the tree so that the first encountered
- // leaf is the one closest to the test point
- if (node.getCut().getHyperplane().getOffset(original) <= 0) {
- return Order.MINUS_SUB_PLUS;
- } else {
- return Order.PLUS_SUB_MINUS;
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public void visitInternalNode(final BSPTree<P> node) {
-
- // project the point on the cut sub-hyperplane
- final Hyperplane<P> hyperplane = node.getCut().getHyperplane();
- final double signedOffset = hyperplane.getOffset(original);
- if (Math.abs(signedOffset) < offset) {
-
- // project point
- final P regular = hyperplane.project(original);
-
- // get boundary parts
- final List<Region<S>> boundaryParts = boundaryRegions(node);
-
- // check if regular projection really belongs to the boundary
- boolean regularFound = false;
- for (final Region<S> part : boundaryParts) {
- if (!regularFound && belongsToPart(regular, hyperplane, part)) {
- // the projected point lies in the boundary
- projected = regular;
- offset = Math.abs(signedOffset);
- regularFound = true;
- }
- }
-
- if (!regularFound) {
- // the regular projected point is not on boundary,
- // so we have to check further if a singular point
- // (i.e. a vertex in 2D case) is a possible projection
- for (final Region<S> part : boundaryParts) {
- final P spI = singularProjection(regular, hyperplane, part);
- if (spI != null) {
- final double distance = original.distance(spI);
- if (distance < offset) {
- projected = spI;
- offset = distance;
- }
- }
- }
-
- }
-
- }
-
- }
-
- /** {@inheritDoc} */
- @Override
- public void visitLeafNode(final BSPTree<P> node) {
- if (leaf == null) {
- // this is the first leaf we visit,
- // it is the closest one to the original point
- leaf = node;
- }
- }
-
- /** Get the projection.
- * @return projection
- */
- public BoundaryProjection<P> getProjection() {
-
- // fix offset sign
- offset = Math.copySign(offset, (Boolean) leaf.getAttribute() ? -1 : +1);
-
- return new BoundaryProjection<>(original, projected, offset);
-
- }
-
- /** Extract the regions of the boundary on an internal node.
- * @param node internal node
- * @return regions in the node sub-hyperplane
- */
- private List<Region<S>> boundaryRegions(final BSPTree<P> node) {
-
- final List<Region<S>> regions = new ArrayList<>(2);
-
- @SuppressWarnings("unchecked")
- final BoundaryAttribute<P> ba = (BoundaryAttribute<P>) node.getAttribute();
- addRegion(ba.getPlusInside(), regions);
- addRegion(ba.getPlusOutside(), regions);
-
- return regions;
-
- }
-
- /** Add a boundary region to a list.
- * @param sub sub-hyperplane defining the region
- * @param list to fill up
- */
- private void addRegion(final SubHyperplane<P> sub, final List<Region<S>> list) {
- if (sub != null) {
- @SuppressWarnings("unchecked")
- final Region<S> region = ((AbstractSubHyperplane<P, S>) sub).getRemainingRegion();
- if (region != null) {
- list.add(region);
- }
- }
- }
-
- /** Check if a projected point lies on a boundary part.
- * @param point projected point to check
- * @param hyperplane hyperplane into which the point was projected
- * @param part boundary part
- * @return true if point lies on the boundary part
- */
- private boolean belongsToPart(final P point, final Hyperplane<P> hyperplane,
- final Region<S> part) {
-
- // there is a non-null sub-space, we can dive into smaller dimensions
- @SuppressWarnings("unchecked")
- final Embedding<P, S> embedding = (Embedding<P, S>) hyperplane;
- return part.checkPoint(embedding.toSubSpace(point)) != Location.OUTSIDE;
-
- }
-
- /** Get the projection to the closest boundary singular point.
- * @param point projected point to check
- * @param hyperplane hyperplane into which the point was projected
- * @param part boundary part
- * @return projection to a singular point of boundary part (may be null)
- */
- private P singularProjection(final P point, final Hyperplane<P> hyperplane,
- final Region<S> part) {
-
- // there is a non-null sub-space, we can dive into smaller dimensions
- @SuppressWarnings("unchecked")
- final Embedding<P, S> embedding = (Embedding<P, S>) hyperplane;
- final BoundaryProjection<S> bp = part.projectToBoundary(embedding.toSubSpace(point));
-
- // back to initial dimension
- return (bp.getProjected() == null) ? null : embedding.toSpace(bp.getProjected());
-
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySizeVisitor.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySizeVisitor.java
deleted file mode 100644
index e84c70a..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySizeVisitor.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 org.apache.commons.geometry.core.Point;
-
-/** Visitor computing the boundary size.
- * @param <P> Point type defining the space
- */
-class BoundarySizeVisitor<P extends Point<P>> implements BSPTreeVisitor<P> {
-
- /** Size of the boundary. */
- private double boundarySize;
-
- /** Simple constructor.
- */
- BoundarySizeVisitor() {
- boundarySize = 0;
- }
-
- /** {@inheritDoc}*/
- @Override
- public Order visitOrder(final BSPTree<P> node) {
- return Order.MINUS_SUB_PLUS;
- }
-
- /** {@inheritDoc}*/
- @Override
- public void visitInternalNode(final BSPTree<P> node) {
- @SuppressWarnings("unchecked")
- final BoundaryAttribute<P> attribute =
- (BoundaryAttribute<P>) node.getAttribute();
- if (attribute.getPlusOutside() != null) {
- boundarySize += attribute.getPlusOutside().getSize();
- }
- if (attribute.getPlusInside() != null) {
- boundarySize += attribute.getPlusInside().getSize();
- }
- }
-
- /** {@inheritDoc}*/
- @Override
- public void visitLeafNode(final BSPTree<P> node) {
- }
-
- /** Get the size of the boundary.
- * @return size of the boundary
- */
- public double getSize() {
- return boundarySize;
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Characterization.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Characterization.java
deleted file mode 100644
index d9ec6e7..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Characterization.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * 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.ArrayList;
-import java.util.List;
-
-import org.apache.commons.geometry.core.Point;
-
-/** Cut sub-hyperplanes characterization with respect to inside/outside cells.
- * @see BoundaryBuilder
- * @param <P> Point type defining the space
- */
-class Characterization<P extends Point<P>> {
-
- /** Part of the cut sub-hyperplane that touch outside cells. */
- private SubHyperplane<P> outsideTouching;
-
- /** Part of the cut sub-hyperplane that touch inside cells. */
- private SubHyperplane<P> insideTouching;
-
- /** Nodes that were used to split the outside touching part. */
- private final NodesSet<P> outsideSplitters;
-
- /** Nodes that were used to split the outside touching part. */
- private final NodesSet<P> insideSplitters;
-
- /** Simple constructor.
- * <p>Characterization consists in splitting the specified
- * sub-hyperplane into several parts lying in inside and outside
- * cells of the tree. The principle is to compute characterization
- * twice for each cut sub-hyperplane in the tree, once on the plus
- * node and once on the minus node. The parts that have the same flag
- * (inside/inside or outside/outside) do not belong to the boundary
- * while parts that have different flags (inside/outside or
- * outside/inside) do belong to the boundary.</p>
- * @param node current BSP tree node
- * @param sub sub-hyperplane to characterize
- */
- Characterization(final BSPTree<P> node, final SubHyperplane<P> sub) {
- outsideTouching = null;
- insideTouching = null;
- outsideSplitters = new NodesSet<>();
- insideSplitters = new NodesSet<>();
- characterize(node, sub, new ArrayList<BSPTree<P>>());
- }
-
- /** Filter the parts of an hyperplane belonging to the boundary.
- * <p>The filtering consist in splitting the specified
- * sub-hyperplane into several parts lying in inside and outside
- * cells of the tree. The principle is to call this method twice for
- * each cut sub-hyperplane in the tree, once on the plus node and
- * once on the minus node. The parts that have the same flag
- * (inside/inside or outside/outside) do not belong to the boundary
- * while parts that have different flags (inside/outside or
- * outside/inside) do belong to the boundary.</p>
- * @param node current BSP tree node
- * @param sub sub-hyperplane to characterize
- * @param splitters nodes that did split the current one
- */
- private void characterize(final BSPTree<P> node, final SubHyperplane<P> sub,
- final List<BSPTree<P>> splitters) {
- if (node.getCut() == null) {
- // we have reached a leaf node
- final boolean inside = (Boolean) node.getAttribute();
- if (inside) {
- addInsideTouching(sub, splitters);
- } else {
- addOutsideTouching(sub, splitters);
- }
- } else {
- final Hyperplane<P> hyperplane = node.getCut().getHyperplane();
- final SubHyperplane.SplitSubHyperplane<P> split = sub.split(hyperplane);
- switch (split.getSide()) {
- case PLUS:
- characterize(node.getPlus(), sub, splitters);
- break;
- case MINUS:
- characterize(node.getMinus(), sub, splitters);
- break;
- case BOTH:
- splitters.add(node);
- characterize(node.getPlus(), split.getPlus(), splitters);
- characterize(node.getMinus(), split.getMinus(), splitters);
- splitters.remove(splitters.size() - 1);
- break;
- default:
- // If we reach this point, then the sub-hyperplane we're
- // testing lies directly on this node's hyperplane. In theory,
- // this shouldn't ever happen with correctly-formed trees. However,
- // this does actually occur in practice, especially with manually
- // built trees or very complex models. Rather than throwing an
- // exception, we'll attempt to handle this situation gracefully
- // by treating these sub-hyperplanes as if they lie on the minus
- // side of the cut hyperplane.
- characterize(node.getMinus(), sub, splitters);
- break;
- }
- }
- }
-
- /** Add a part of the cut sub-hyperplane known to touch an outside cell.
- * @param sub part of the cut sub-hyperplane known to touch an outside cell
- * @param splitters sub-hyperplanes that did split the current one
- */
- private void addOutsideTouching(final SubHyperplane<P> sub,
- final List<BSPTree<P>> splitters) {
- if (outsideTouching == null) {
- outsideTouching = sub;
- } else {
- outsideTouching = outsideTouching.reunite(sub);
- }
- outsideSplitters.addAll(splitters);
- }
-
- /** Add a part of the cut sub-hyperplane known to touch an inside cell.
- * @param sub part of the cut sub-hyperplane known to touch an inside cell
- * @param splitters sub-hyperplanes that did split the current one
- */
- private void addInsideTouching(final SubHyperplane<P> sub,
- final List<BSPTree<P>> splitters) {
- if (insideTouching == null) {
- insideTouching = sub;
- } else {
- insideTouching = insideTouching.reunite(sub);
- }
- insideSplitters.addAll(splitters);
- }
-
- /** Check if the cut sub-hyperplane touches outside cells.
- * @return true if the cut sub-hyperplane touches outside cells
- */
- public boolean touchOutside() {
- return outsideTouching != null && !outsideTouching.isEmpty();
- }
-
- /** Get all the parts of the cut sub-hyperplane known to touch outside cells.
- * @return parts of the cut sub-hyperplane known to touch outside cells
- * (may be null or empty)
- */
- public SubHyperplane<P> outsideTouching() {
- return outsideTouching;
- }
-
- /** Get the nodes that were used to split the outside touching part.
- * <p>
- * Splitting nodes are internal nodes (i.e. they have a non-null
- * cut sub-hyperplane).
- * </p>
- * @return nodes that were used to split the outside touching part
- */
- public NodesSet<P> getOutsideSplitters() {
- return outsideSplitters;
- }
-
- /** Check if the cut sub-hyperplane touches inside cells.
- * @return true if the cut sub-hyperplane touches inside cells
- */
- public boolean touchInside() {
- return insideTouching != null && !insideTouching.isEmpty();
- }
-
- /** Get all the parts of the cut sub-hyperplane known to touch inside cells.
- * @return parts of the cut sub-hyperplane known to touch inside cells
- * (may be null or empty)
- */
- public SubHyperplane<P> insideTouching() {
- return insideTouching;
- }
-
- /** Get the nodes that were used to split the inside touching part.
- * <p>
- * Splitting nodes are internal nodes (i.e. they have a non-null
- * cut sub-hyperplane).
- * </p>
- * @return nodes that were used to split the inside touching part
- */
- public NodesSet<P> getInsideSplitters() {
- return insideSplitters;
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/ConvexSubHyperplane.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/ConvexSubHyperplane.java
new file mode 100644
index 0000000..606e233
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/ConvexSubHyperplane.java
@@ -0,0 +1,50 @@
+/*
+ * 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 org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.Transform;
+
+/** Extension of the {@link SubHyperplane} interface with the additional restriction
+ * that instances represent convex regions of space.
+ * @param <P> Point implementation type
+ */
+public interface ConvexSubHyperplane<P extends Point<P>> extends SubHyperplane<P> {
+
+ /** Reverse the orientation of the hyperplane for this instance. The subhyperplane
+ * occupies the same locations in space but with a reversed orientation.
+ * @return a convex subhyperplane representing the same region but with the
+ * opposite orientation.
+ */
+ ConvexSubHyperplane<P> reverse();
+
+ /** {@inheritDoc}
+ *
+ * <p>The parts resulting from a split operation with a convex subhyperplane
+ * are guaranteed to also be convex.</p>
+ */
+ @Override
+ Split<? extends ConvexSubHyperplane<P>> split(Hyperplane<P> splitter);
+
+ /** {@inheritDoc}
+ *
+ * <p>Convex subhyperplanes subjected to affine transformations remain
+ * convex.</p>
+ */
+ @Override
+ ConvexSubHyperplane<P> transform(Transform<P> transform);
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Embedding.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Embedding.java
deleted file mode 100644
index dfc40bf..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Embedding.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 org.apache.commons.geometry.core.Point;
-
-/** This interface defines mappers between a space and one of its sub-spaces.
-
- * <p>Sub-spaces are the lower dimensions subsets of a n-dimensions
- * space. The (n-1)-dimension sub-spaces are specific sub-spaces known
- * as {@link Hyperplane hyperplanes}. This interface can be used regardless
- * of the dimensions differences. For example, a line in 3D Euclidean space
- * can map directly from 3 dimensions to 1.</p>
-
- * <p>In the 3D Euclidean space, hyperplanes are 2D planes, and the 1D
- * sub-spaces are lines.</p>
-
- * <p>
- * Note that this interface is <em>not</em> intended to be implemented
- * by Apache Commons Geometry users, it is only intended to be implemented
- * within the library itself. New methods may be added even for minor
- * versions, which breaks compatibility for external implementations.
- * </p>
-
- * @param <P> Point type defining the embedding space.
- * @param <S> Point type defining the embedded sub-space.
-
- * @see Hyperplane
- */
-public interface Embedding<P extends Point<P>, S extends Point<S>> {
-
- /** Transform a space point into a sub-space point.
- * @param point n-dimension point of the space
- * @return (n-1)-dimension point of the sub-space corresponding to
- * the specified space point
- * @see #toSpace
- */
- S toSubSpace(P point);
-
- /** Transform a sub-space point into a space point.
- * @param point (n-1)-dimension point of the sub-space
- * @return n-dimension point of the space corresponding to the
- * specified sub-space point
- * @see #toSubSpace
- */
- P toSpace(S point);
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/EmbeddingHyperplane.java
similarity index 68%
copy from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
copy to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/EmbeddingHyperplane.java
index 046defe..71b1297 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/EmbeddingHyperplane.java
@@ -16,21 +16,15 @@
*/
package org.apache.commons.geometry.core.partitioning;
-/** Enumerate representing the location of an element with respect to an
- * {@link Hyperplane hyperplane} of a space.
- */
-public enum Side {
-
- /** Code for the plus side of the hyperplane. */
- PLUS,
-
- /** Code for the minus side of the hyperplane. */
- MINUS,
-
- /** Code for elements crossing the hyperplane from plus to minus side. */
- BOTH,
-
- /** Code for the hyperplane itself. */
- HYPER;
+import org.apache.commons.geometry.core.Embedding;
+import org.apache.commons.geometry.core.Point;
+/** Hyperplane that also embeds a subspace.
+ * @param <P> Point implementation type
+ * @param <S> Subspace point implementation type
+ * @see Hyperplane
+ * @see Embedding
+ */
+public interface EmbeddingHyperplane<P extends Point<P>, S extends Point<S>>
+ extends Hyperplane<P>, Embedding<P, S> {
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Hyperplane.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Hyperplane.java
index 28d9d12..d7bd09f 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Hyperplane.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Hyperplane.java
@@ -17,80 +17,69 @@
package org.apache.commons.geometry.core.partitioning;
import org.apache.commons.geometry.core.Point;
-import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.Transform;
-/** This interface represents an hyperplane of a space.
-
- * <p>The most prominent place where hyperplane appears in space
- * partitioning is as cutters. Each partitioning node in a {@link
- * BSPTree BSP tree} has a cut {@link SubHyperplane sub-hyperplane}
- * which is either an hyperplane or a part of an hyperplane. In an
- * n-dimensions Euclidean space, an hyperplane is an (n-1)-dimensions
- * hyperplane (for example a traditional plane in the 3D Euclidean
- * space). They can be more exotic objects in specific fields, for
- * example a circle on the surface of the unit sphere.</p>
-
- * <p>
- * Note that this interface is <em>not</em> intended to be implemented
- * by Apache Commons Geometry users, it is only intended to be implemented
- * within the library itself. New methods may be added even for minor
- * versions, which breaks compatibility for external implementations.
- * </p>
-
- * @param <P> Point type defining the space
+/** Interface representing a hyperplane, which is a subspace of degree
+ * one less than the space it is embedded in.
+ * @param <P> Point implementation type
*/
public interface Hyperplane<P extends Point<P>> {
- /** Copy the instance.
- * <p>The instance created is completely independant of the original
- * one. A deep copy is used, none of the underlying objects are
- * shared (except for immutable objects).</p>
- * @return a new hyperplane, copy of the instance
+ /** Get the offset (oriented distance) of a point with respect
+ * to this instance. Points with an offset of zero lie on the
+ * hyperplane itself.
+ * @param point the point to compute the offset for
+ * @return the offset of the point
*/
- Hyperplane<P> copySelf();
+ double offset(P point);
- /** Get the offset (oriented distance) of a point.
- * <p>The offset is 0 if the point is on the underlying hyperplane,
- * it is positive if the point is on one particular side of the
- * hyperplane, and it is negative if the point is on the other side,
- * according to the hyperplane natural orientation.</p>
- * @param point point to check
- * @return offset of the point
+ /** Classify a point with respect to this hyperplane.
+ * @param point the point to classify
+ * @return the relative location of the point with
+ * respect to this instance
*/
- double getOffset(P point);
+ HyperplaneLocation classify(P point);
- /** Project a point to the hyperplane.
- * @param point point to project
- * @return projected point
+ /** Return true if the given point lies on the hyperplane.
+ * @param point the point to test
+ * @return true if the point lies on the hyperplane
*/
- P project(P point);
+ boolean contains(P point);
- /** Get the object used to determine floating point equality for this hyperplane.
- * This determines which points belong to the hyperplane and which do not, or pictured
- * another way, the "thickness" of the hyperplane.
- * @return the floating point precision context for the instance
+ /** Project a point onto this instance.
+ * @param point the point to project
+ * @return the projection of the point onto this instance. The returned
+ * point lies on the hyperplane.
*/
- DoublePrecisionContext getPrecision();
+ P project(P point);
- /** Check if the instance has the same orientation as another hyperplane.
- * <p>This method is expected to be called on parallel hyperplanes. The
- * method should <em>not</em> re-check for parallelism, only for
- * orientation, typically by testing something like the sign of the
- * dot-products of normals.</p>
- * @param other other hyperplane to check against the instance
- * @return true if the instance and the other hyperplane have
- * the same orientation
+ /** Return a hyperplane that has the opposite orientation as this instance.
+ * That is, the plus side of this instance is the minus side of the returned
+ * instance and vice versa.
+ * @return a hyperplane with the opposite orientation
*/
- boolean sameOrientationAs(Hyperplane<P> other);
+ Hyperplane<P> reverse();
- /** Build a sub-hyperplane covering the whole hyperplane.
- * @return a sub-hyperplane covering the whole hyperplane
+ /** Transform this instance using the given {@link Transform}.
+ * @param transform object to transform this instance with
+ * @return a new, transformed hyperplane
*/
- SubHyperplane<P> wholeHyperplane();
+ Hyperplane<P> transform(Transform<P> transform);
- /** Build a region covering the whole space.
- * @return a region containing the instance
+ /** Return true if this instance has a similar orientation to the given hyperplane,
+ * meaning that they point in generally the same direction. This method is not
+ * used to determine exact equality of hyperplanes, but rather to determine whether
+ * two hyperplanes that contain the same points are parallel (point in the same direction)
+ * or anti-parallel (point in opposite directions).
+ * @param other the hyperplane to compare with
+ * @return true if the hyperplanes point in generally the same direction and could
+ * possibly be parallel
*/
- Region<P> wholeSpace();
+ boolean similarOrientation(Hyperplane<P> other);
+ /** Return a {@link ConvexSubHyperplane} spanning this entire hyperplane. The returned
+ * subhyperplane contains all points lying in this hyperplane and no more.
+ * @return a {@link ConvexSubHyperplane} containing all points lying in this hyperplane
+ */
+ ConvexSubHyperplane<P> span();
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneBoundedRegion.java
similarity index 60%
copy from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
copy to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneBoundedRegion.java
index 046defe..0718d09 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneBoundedRegion.java
@@ -16,21 +16,15 @@
*/
package org.apache.commons.geometry.core.partitioning;
-/** Enumerate representing the location of an element with respect to an
- * {@link Hyperplane hyperplane} of a space.
- */
-public enum Side {
-
- /** Code for the plus side of the hyperplane. */
- PLUS,
-
- /** Code for the minus side of the hyperplane. */
- MINUS,
-
- /** Code for elements crossing the hyperplane from plus to minus side. */
- BOTH,
-
- /** Code for the hyperplane itself. */
- HYPER;
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.Region;
+/** Interface representing regions with boundaries defined by hyperplanes or
+ * portions of hyperplanes. This interface is intended to represent closed regions
+ * with finite sizes as well as infinite and empty spaces. Regions of this type
+ * can be recursively split by hyperplanes into similar regions.
+ * @param <P> Point implementation type
+ */
+public interface HyperplaneBoundedRegion<P extends Point<P>>
+ extends Region<P>, Splittable<P, HyperplaneBoundedRegion<P>> {
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneLocation.java
similarity index 68%
copy from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
copy to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneLocation.java
index 046defe..ce45f0f 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneLocation.java
@@ -16,21 +16,23 @@
*/
package org.apache.commons.geometry.core.partitioning;
-/** Enumerate representing the location of an element with respect to an
- * {@link Hyperplane hyperplane} of a space.
+/** Enumeration containing possible locations of a point with respect to
+ * a hyperplane.
+ * @see Hyperplane
*/
-public enum Side {
+public enum HyperplaneLocation {
- /** Code for the plus side of the hyperplane. */
- PLUS,
-
- /** Code for the minus side of the hyperplane. */
+ /** Value indicating that a point lies on the minus side of
+ * a hyperplane.
+ */
MINUS,
- /** Code for elements crossing the hyperplane from plus to minus side. */
- BOTH,
-
- /** Code for the hyperplane itself. */
- HYPER;
+ /** Value indicating that a point lies on the plus side of
+ * a hyperplane.
+ */
+ PLUS,
+ /** Value indicating that a point lies directly on a hyperplane.
+ */
+ ON
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/InsideFinder.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/InsideFinder.java
deleted file mode 100644
index cb384d9..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/InsideFinder.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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 org.apache.commons.geometry.core.Point;
-
-/** Utility class checking if inside nodes can be found
- * on the plus and minus sides of an hyperplane.
- * @param <P> Point type defining the space
- */
-class InsideFinder<P extends Point<P>> {
-
- /** Region on which to operate. */
- private final Region<P> region;
-
- /** Indicator of inside leaf nodes found on the plus side. */
- private boolean plusFound;
-
- /** Indicator of inside leaf nodes found on the plus side. */
- private boolean minusFound;
-
- /** Simple constructor.
- * @param region region on which to operate
- */
- InsideFinder(final Region<P> region) {
- this.region = region;
- plusFound = false;
- minusFound = false;
- }
-
- /** Search recursively for inside leaf nodes on each side of the given hyperplane.
-
- * <p>The algorithm used here is directly derived from the one
- * described in section III (<i>Binary Partitioning of a BSP
- * Tree</i>) of the Bruce Naylor, John Amanatides and William
- * Thibault paper <a
- * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
- * BSP Trees Yields Polyhedral Set Operations</a> Proc. Siggraph
- * '90, Computer Graphics 24(4), August 1990, pp 115-124, published
- * by the Association for Computing Machinery (ACM)..</p>
-
- * @param node current BSP tree node
- * @param sub sub-hyperplane
- */
- public void recurseSides(final BSPTree<P> node, final SubHyperplane<P> sub) {
-
- if (node.getCut() == null) {
- if ((Boolean) node.getAttribute()) {
- // this is an inside cell expanding across the hyperplane
- plusFound = true;
- minusFound = true;
- }
- return;
- }
-
- final Hyperplane<P> hyperplane = node.getCut().getHyperplane();
- final SubHyperplane.SplitSubHyperplane<P> split = sub.split(hyperplane);
- switch (split.getSide()) {
- case PLUS :
- // the sub-hyperplane is entirely in the plus sub-tree
- if (node.getCut().split(sub.getHyperplane()).getSide() == Side.PLUS) {
- if (!region.isEmpty(node.getMinus())) {
- plusFound = true;
- }
- } else {
- if (!region.isEmpty(node.getMinus())) {
- minusFound = true;
- }
- }
- if (!(plusFound && minusFound)) {
- recurseSides(node.getPlus(), sub);
- }
- break;
- case MINUS :
- // the sub-hyperplane is entirely in the minus sub-tree
- if (node.getCut().split(sub.getHyperplane()).getSide() == Side.PLUS) {
- if (!region.isEmpty(node.getPlus())) {
- plusFound = true;
- }
- } else {
- if (!region.isEmpty(node.getPlus())) {
- minusFound = true;
- }
- }
- if (!(plusFound && minusFound)) {
- recurseSides(node.getMinus(), sub);
- }
- break;
- case BOTH :
- // the sub-hyperplane extends in both sub-trees
-
- // explore first the plus sub-tree
- recurseSides(node.getPlus(), split.getPlus());
-
- // if needed, explore the minus sub-tree
- if (!(plusFound && minusFound)) {
- recurseSides(node.getMinus(), split.getMinus());
- }
- break;
- default :
- // the sub-hyperplane and the cut sub-hyperplane share the same hyperplane
- if (node.getCut().getHyperplane().sameOrientationAs(sub.getHyperplane())) {
- if ((node.getPlus().getCut() != null) || ((Boolean) node.getPlus().getAttribute())) {
- plusFound = true;
- }
- if ((node.getMinus().getCut() != null) || ((Boolean) node.getMinus().getAttribute())) {
- minusFound = true;
- }
- } else {
- if ((node.getPlus().getCut() != null) || ((Boolean) node.getPlus().getAttribute())) {
- minusFound = true;
- }
- if ((node.getMinus().getCut() != null) || ((Boolean) node.getMinus().getAttribute())) {
- plusFound = true;
- }
- }
- }
-
- }
-
- /** Check if inside leaf nodes have been found on the plus side.
- * @return true if inside leaf nodes have been found on the plus side
- */
- public boolean plusFound() {
- return plusFound;
- }
-
- /** Check if inside leaf nodes have been found on the minus side.
- * @return true if inside leaf nodes have been found on the minus side
- */
- public boolean minusFound() {
- return minusFound;
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/NodesSet.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/NodesSet.java
deleted file mode 100644
index 54e0c3d..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/NodesSet.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.commons.geometry.core.Point;
-
-/** Set of {@link BSPTree BSP tree} nodes.
- * @see BoundaryAttribute
- * @param <P> Point type defining the space
- */
-public class NodesSet<P extends Point<P>> implements Iterable<BSPTree<P>> {
-
- /** List of sub-hyperplanes. */
- private final List<BSPTree<P>> list;
-
- /** Simple constructor.
- */
- public NodesSet() {
- list = new ArrayList<>();
- }
-
- /** Add a node if not already known.
- * @param node node to add
- */
- public void add(final BSPTree<P> node) {
-
- for (final BSPTree<P> existing : list) {
- if (node == existing) {
- // the node is already known, don't add it
- return;
- }
- }
-
- // the node was not known, add it
- list.add(node);
-
- }
-
- /** Add nodes if they are not already known.
- * @param iterator nodes iterator
- */
- public void addAll(final Iterable<BSPTree<P>> iterator) {
- for (final BSPTree<P> node : iterator) {
- add(node);
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public Iterator<BSPTree<P>> iterator() {
- return list.iterator();
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Region.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Region.java
deleted file mode 100644
index 4fef7e3..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Region.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * 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 org.apache.commons.geometry.core.Point;
-
-/** This interface represents a region of a space as a partition.
-
- * <p>Region are subsets of a space, they can be infinite (whole
- * space, half space, infinite stripe ...) or finite (polygons in 2D,
- * polyhedrons in 3D ...). Their main characteristic is to separate
- * points that are considered to be <em>inside</em> the region from
- * points considered to be <em>outside</em> of it. In between, there
- * may be points on the <em>boundary</em> of the region.</p>
-
- * <p>This implementation is limited to regions for which the boundary
- * is composed of several {@link SubHyperplane sub-hyperplanes},
- * including regions with no boundary at all: the whole space and the
- * empty region. They are not necessarily finite and not necessarily
- * path-connected. They can contain holes.</p>
-
- * <p>Regions can be combined using the traditional sets operations :
- * union, intersection, difference and symetric difference (exclusive
- * or) for the binary operations, complement for the unary
- * operation.</p>
-
- * <p>
- * Note that this interface is <em>not</em> intended to be implemented
- * by Apache Commons Math users, it is only intended to be implemented
- * within the library itself. New methods may be added even for minor
- * versions, which breaks compatibility for external implementations.
- * </p>
-
- * @param <P> Point type defining the space
- */
-public interface Region<P extends Point<P>> {
-
- /** Enumerate for the location of a point with respect to the region. */
- enum Location {
- /** Code for points inside the partition. */
- INSIDE,
-
- /** Code for points outside of the partition. */
- OUTSIDE,
-
- /** Code for points on the partition boundary. */
- BOUNDARY;
- }
-
- /** Build a region using the instance as a prototype.
- * <p>This method allow to create new instances without knowing
- * exactly the type of the region. It is an application of the
- * prototype design pattern.</p>
- * <p>The leaf nodes of the BSP tree <em>must</em> have a
- * {@code Boolean} attribute representing the inside status of
- * the corresponding cell (true for inside cells, false for outside
- * cells). In order to avoid building too many small objects, it is
- * recommended to use the predefined constants
- * {@code Boolean.TRUE} and {@code Boolean.FALSE}. The
- * tree also <em>must</em> have either null internal nodes or
- * internal nodes representing the boundary as specified in the
- * {@link #getTree getTree} method).</p>
- * @param newTree inside/outside BSP tree representing the new region
- * @return the built region
- */
- Region<P> buildNew(BSPTree<P> newTree);
-
- /** Copy the instance.
- * <p>The instance created is completely independant of the original
- * one. A deep copy is used, none of the underlying objects are
- * shared (except for the underlying tree {@code Boolean}
- * attributes and immutable objects).</p>
- * @return a new region, copy of the instance
- */
- Region<P> copySelf();
-
- /** Check if the instance is empty.
- * @return true if the instance is empty
- */
- boolean isEmpty();
-
- /** Check if the sub-tree starting at a given node is empty.
- * @param node root node of the sub-tree (<em>must</em> have {@link
- * Region Region} tree semantics, i.e. the leaf nodes must have
- * {@code Boolean} attributes representing an inside/outside
- * property)
- * @return true if the sub-tree starting at the given node is empty
- */
- boolean isEmpty(final BSPTree<P> node);
-
- /** Check if the instance covers the full space.
- * @return true if the instance covers the full space
- */
- boolean isFull();
-
- /** Check if the sub-tree starting at a given node covers the full space.
- * @param node root node of the sub-tree (<em>must</em> have {@link
- * Region Region} tree semantics, i.e. the leaf nodes must have
- * {@code Boolean} attributes representing an inside/outside
- * property)
- * @return true if the sub-tree starting at the given node covers the full space
- */
- boolean isFull(final BSPTree<P> node);
-
- /** Check if the instance entirely contains another region.
- * @param region region to check against the instance
- * @return true if the instance contains the specified tree
- */
- boolean contains(final Region<P> region);
-
- /** Check a point with respect to the region.
- * @param point point to check
- * @return a code representing the point status: either {@link
- * Location#INSIDE}, {@link Location#OUTSIDE} or {@link Location#BOUNDARY}
- */
- Location checkPoint(final P point);
-
- /** Project a point on the boundary of the region.
- * @param point point to check
- * @return projection of the point on the boundary
- */
- BoundaryProjection<P> projectToBoundary(final P point);
-
- /** Get the underlying BSP tree.
-
- * <p>Regions are represented by an underlying inside/outside BSP
- * tree whose leaf attributes are {@code Boolean} instances
- * representing inside leaf cells if the attribute value is
- * {@code true} and outside leaf cells if the attribute is
- * {@code false}. These leaf attributes are always present and
- * guaranteed to be non null.</p>
-
- * <p>In addition to the leaf attributes, the internal nodes which
- * correspond to cells split by cut sub-hyperplanes may contain
- * {@link BoundaryAttribute BoundaryAttribute} objects representing
- * the parts of the corresponding cut sub-hyperplane that belong to
- * the boundary. When the boundary attributes have been computed,
- * all internal nodes are guaranteed to have non-null
- * attributes, however some {@link BoundaryAttribute
- * BoundaryAttribute} instances may have their {@link
- * BoundaryAttribute#getPlusInside() getPlusInside} and {@link
- * BoundaryAttribute#getPlusOutside() getPlusOutside} methods both
- * returning null if the corresponding cut sub-hyperplane does not
- * have any parts belonging to the boundary.</p>
-
- * <p>Since computing the boundary is not always required and can be
- * time-consuming for large trees, these internal nodes attributes
- * are computed using lazy evaluation only when required by setting
- * the {@code includeBoundaryAttributes} argument to
- * {@code true}. Once computed, these attributes remain in the
- * tree, which implies that in this case, further calls to the
- * method for the same region will always include these attributes
- * regardless of the value of the
- * {@code includeBoundaryAttributes} argument.</p>
-
- * @param includeBoundaryAttributes if true, the boundary attributes
- * at internal nodes are guaranteed to be included (they may be
- * included even if the argument is false, if they have already been
- * computed due to a previous call)
- * @return underlying BSP tree
- * @see BoundaryAttribute
- */
- BSPTree<P> getTree(final boolean includeBoundaryAttributes);
-
- /** Get the size of the boundary.
- * @return the size of the boundary (this is 0 in 1D, a length in
- * 2D, an area in 3D ...)
- */
- double getBoundarySize();
-
- /** Get the size of the instance.
- * @return the size of the instance (this is a length in 1D, an area
- * in 2D, a volume in 3D ...)
- */
- double getSize();
-
- /** Get the barycenter of the instance.
- * @return an object representing the barycenter
- */
- P getBarycenter();
-
- /** Get the parts of a sub-hyperplane that are contained in the region.
- * <p>The parts of the sub-hyperplane that belong to the boundary are
- * <em>not</em> included in the resulting parts.</p>
- * @param sub sub-hyperplane traversing the region
- * @return filtered sub-hyperplane
- */
- SubHyperplane<P> intersection(final SubHyperplane<P> sub);
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/RegionFactory.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/RegionFactory.java
deleted file mode 100644
index c15676c..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/RegionFactory.java
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * 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.HashMap;
-import java.util.Map;
-
-import org.apache.commons.geometry.core.Point;
-import org.apache.commons.geometry.core.partitioning.BSPTree.VanishingCutHandler;
-import org.apache.commons.geometry.core.partitioning.Region.Location;
-import org.apache.commons.geometry.core.partitioning.SubHyperplane.SplitSubHyperplane;
-
-/** This class is a factory for {@link Region}.
-
- * @param <P> Point type defining the space
- */
-public class RegionFactory<P extends Point<P>> {
-
- /** Visitor removing internal nodes attributes. */
- private final NodesCleaner nodeCleaner;
-
- /** Simple constructor.
- */
- public RegionFactory() {
- nodeCleaner = new NodesCleaner();
- }
-
- /** Build a convex region from a collection of bounding hyperplanes.
- * @param hyperplanes collection of bounding hyperplanes
- * @return a new convex region, or null if the collection is empty
- */
- @SafeVarargs
- public final Region<P> buildConvex(final Hyperplane<P> ... hyperplanes) {
- if ((hyperplanes == null) || (hyperplanes.length == 0)) {
- return null;
- }
-
- // use the first hyperplane to build the right class
- final Region<P> region = hyperplanes[0].wholeSpace();
-
- // chop off parts of the space
- BSPTree<P> node = region.getTree(false);
- node.setAttribute(Boolean.TRUE);
- for (final Hyperplane<P> hyperplane : hyperplanes) {
- if (node.insertCut(hyperplane)) {
- node.setAttribute(null);
- node.getPlus().setAttribute(Boolean.FALSE);
- node = node.getMinus();
- node.setAttribute(Boolean.TRUE);
- } else {
- // the hyperplane could not be inserted in the current leaf node
- // either it is completely outside (which means the input hyperplanes
- // are wrong), or it is parallel to a previous hyperplane
- SubHyperplane<P> s = hyperplane.wholeHyperplane();
- for (BSPTree<P> tree = node; tree.getParent() != null && s != null; tree = tree.getParent()) {
- final Hyperplane<P> other = tree.getParent().getCut().getHyperplane();
- final SplitSubHyperplane<P> split = s.split(other);
- switch (split.getSide()) {
- case HYPER :
- // the hyperplane is parallel to a previous hyperplane
- if (!hyperplane.sameOrientationAs(other)) {
- // this hyperplane is opposite to the other one,
- // the region is thinner than the tolerance, we consider it empty
- return getComplement(hyperplanes[0].wholeSpace());
- }
- // the hyperplane is an extension of an already known hyperplane, we just ignore it
- break;
- case PLUS :
- // the hyperplane is outside of the current convex zone,
- // the input hyperplanes are inconsistent
- throw new IllegalArgumentException("Hyperplanes do not define a convex region");
- default :
- s = split.getMinus();
- }
- }
- }
- }
-
- return region;
-
- }
-
- /** Compute the union of two regions.
- * @param region1 first region (will be unusable after the operation as
- * parts of it will be reused in the new region)
- * @param region2 second region (will be unusable after the operation as
- * parts of it will be reused in the new region)
- * @return a new region, result of {@code region1 union region2}
- */
- public Region<P> union(final Region<P> region1, final Region<P> region2) {
- final BSPTree<P> tree =
- region1.getTree(false).merge(region2.getTree(false), new UnionMerger());
- tree.visit(nodeCleaner);
- return region1.buildNew(tree);
- }
-
- /** Compute the intersection of two regions.
- * @param region1 first region (will be unusable after the operation as
- * parts of it will be reused in the new region)
- * @param region2 second region (will be unusable after the operation as
- * parts of it will be reused in the new region)
- * @return a new region, result of {@code region1 intersection region2}
- */
- public Region<P> intersection(final Region<P> region1, final Region<P> region2) {
- final BSPTree<P> tree =
- region1.getTree(false).merge(region2.getTree(false), new IntersectionMerger());
- tree.visit(nodeCleaner);
- return region1.buildNew(tree);
- }
-
- /** Compute the symmetric difference (exclusive or) of two regions.
- * @param region1 first region (will be unusable after the operation as
- * parts of it will be reused in the new region)
- * @param region2 second region (will be unusable after the operation as
- * parts of it will be reused in the new region)
- * @return a new region, result of {@code region1 xor region2}
- */
- public Region<P> xor(final Region<P> region1, final Region<P> region2) {
- final BSPTree<P> tree =
- region1.getTree(false).merge(region2.getTree(false), new XorMerger());
- tree.visit(nodeCleaner);
- return region1.buildNew(tree);
- }
-
- /** Compute the difference of two regions.
- * @param region1 first region (will be unusable after the operation as
- * parts of it will be reused in the new region)
- * @param region2 second region (will be unusable after the operation as
- * parts of it will be reused in the new region)
- * @return a new region, result of {@code region1 minus region2}
- */
- public Region<P> difference(final Region<P> region1, final Region<P> region2) {
- final BSPTree<P> tree =
- region1.getTree(false).merge(region2.getTree(false), new DifferenceMerger(region1, region2));
- tree.visit(nodeCleaner);
- return region1.buildNew(tree);
- }
-
- /** Get the complement of the region (exchanged interior/exterior).
- * @param region region to complement, it will not modified, a new
- * region independent region will be built
- * @return a new region, complement of the specified one
- */
- /** Get the complement of the region (exchanged interior/exterior).
- * @param region region to complement, it will not modified, a new
- * region independent region will be built
- * @return a new region, complement of the specified one
- */
- public Region<P> getComplement(final Region<P> region) {
- return region.buildNew(recurseComplement(region.getTree(false)));
- }
-
- /** Recursively build the complement of a BSP tree.
- * @param node current node of the original tree
- * @return new tree, complement of the node
- */
- private BSPTree<P> recurseComplement(final BSPTree<P> node) {
-
- // transform the tree, except for boundary attribute splitters
- final Map<BSPTree<P>, BSPTree<P>> map = new HashMap<>();
- final BSPTree<P> transformedTree = recurseComplement(node, map);
-
- // set up the boundary attributes splitters
- for (final Map.Entry<BSPTree<P>, BSPTree<P>> entry : map.entrySet()) {
- if (entry.getKey().getCut() != null) {
- @SuppressWarnings("unchecked")
- BoundaryAttribute<P> original = (BoundaryAttribute<P>) entry.getKey().getAttribute();
- if (original != null) {
- @SuppressWarnings("unchecked")
- BoundaryAttribute<P> transformed = (BoundaryAttribute<P>) entry.getValue().getAttribute();
- for (final BSPTree<P> splitter : original.getSplitters()) {
- transformed.getSplitters().add(map.get(splitter));
- }
- }
- }
- }
-
- return transformedTree;
-
- }
-
- /** Recursively build the complement of a BSP tree.
- * @param node current node of the original tree
- * @param map transformed nodes map
- * @return new tree, complement of the node
- */
- private BSPTree<P> recurseComplement(final BSPTree<P> node,
- final Map<BSPTree<P>, BSPTree<P>> map) {
-
- final BSPTree<P> transformedNode;
- if (node.getCut() == null) {
- transformedNode = new BSPTree<>(((Boolean) node.getAttribute()) ? Boolean.FALSE : Boolean.TRUE);
- } else {
-
- @SuppressWarnings("unchecked")
- BoundaryAttribute<P> attribute = (BoundaryAttribute<P>) node.getAttribute();
- if (attribute != null) {
- final SubHyperplane<P> plusOutside =
- (attribute.getPlusInside() == null) ? null : attribute.getPlusInside().copySelf();
- final SubHyperplane<P> plusInside =
- (attribute.getPlusOutside() == null) ? null : attribute.getPlusOutside().copySelf();
- // we start with an empty list of splitters, it will be filled in out of recursion
- attribute = new BoundaryAttribute<>(plusOutside, plusInside, new NodesSet<P>());
- }
-
- transformedNode = new BSPTree<>(node.getCut().copySelf(),
- recurseComplement(node.getPlus(), map),
- recurseComplement(node.getMinus(), map),
- attribute);
- }
-
- map.put(node, transformedNode);
- return transformedNode;
-
- }
-
- /** BSP tree leaf merger computing union of two regions. */
- private class UnionMerger implements BSPTree.LeafMerger<P> {
- /** {@inheritDoc} */
- @Override
- public BSPTree<P> merge(final BSPTree<P> leaf, final BSPTree<P> tree,
- final BSPTree<P> parentTree,
- final boolean isPlusChild, final boolean leafFromInstance) {
- if ((Boolean) leaf.getAttribute()) {
- // the leaf node represents an inside cell
- leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
- return leaf;
- }
- // the leaf node represents an outside cell
- tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false));
- return tree;
- }
- }
-
- /** BSP tree leaf merger computing intersection of two regions. */
- private class IntersectionMerger implements BSPTree.LeafMerger<P> {
- /** {@inheritDoc} */
- @Override
- public BSPTree<P> merge(final BSPTree<P> leaf, final BSPTree<P> tree,
- final BSPTree<P> parentTree,
- final boolean isPlusChild, final boolean leafFromInstance) {
- if ((Boolean) leaf.getAttribute()) {
- // the leaf node represents an inside cell
- tree.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
- return tree;
- }
- // the leaf node represents an outside cell
- leaf.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(false));
- return leaf;
- }
- }
-
- /** BSP tree leaf merger computing symmetric difference (exclusive or) of two regions. */
- private class XorMerger implements BSPTree.LeafMerger<P> {
- /** {@inheritDoc} */
- @Override
- public BSPTree<P> merge(final BSPTree<P> leaf, final BSPTree<P> tree,
- final BSPTree<P> parentTree, final boolean isPlusChild,
- final boolean leafFromInstance) {
- BSPTree<P> t = tree;
- if ((Boolean) leaf.getAttribute()) {
- // the leaf node represents an inside cell
- t = recurseComplement(t);
- }
- t.insertInTree(parentTree, isPlusChild, new VanishingToLeaf(true));
- return t;
- }
- }
-
- /** BSP tree leaf merger computing difference of two regions. */
- private class DifferenceMerger implements BSPTree.LeafMerger<P>, VanishingCutHandler<P> {
-
- /** Region to subtract from. */
- private final Region<P> region1;
-
- /** Region to subtract. */
- private final Region<P> region2;
-
- /** Simple constructor.
- * @param region1 region to subtract from
- * @param region2 region to subtract
- */
- DifferenceMerger(final Region<P> region1, final Region<P> region2) {
- this.region1 = region1.copySelf();
- this.region2 = region2.copySelf();
- }
-
- /** {@inheritDoc} */
- @Override
- public BSPTree<P> merge(final BSPTree<P> leaf, final BSPTree<P> tree,
- final BSPTree<P> parentTree, final boolean isPlusChild,
- final boolean leafFromInstance) {
- if ((Boolean) leaf.getAttribute()) {
- // the leaf node represents an inside cell
- final BSPTree<P> argTree =
- recurseComplement(leafFromInstance ? tree : leaf);
- argTree.insertInTree(parentTree, isPlusChild, this);
- return argTree;
- }
- // the leaf node represents an outside cell
- final BSPTree<P> instanceTree =
- leafFromInstance ? leaf : tree;
- instanceTree.insertInTree(parentTree, isPlusChild, this);
- return instanceTree;
- }
-
- /** {@inheritDoc} */
- @Override
- public BSPTree<P> fixNode(final BSPTree<P> node) {
- // get a representative point in the degenerate cell
- final BSPTree<P> cell = node.pruneAroundConvexCell(Boolean.TRUE, Boolean.FALSE, null);
- final Region<P> r = region1.buildNew(cell);
- final P p = r.getBarycenter();
- return new BSPTree<>(region1.checkPoint(p) == Location.INSIDE &&
- region2.checkPoint(p) == Location.OUTSIDE);
- }
-
- }
-
- /** Visitor removing internal nodes attributes. */
- private class NodesCleaner implements BSPTreeVisitor<P> {
-
- /** {@inheritDoc} */
- @Override
- public Order visitOrder(final BSPTree<P> node) {
- return Order.PLUS_SUB_MINUS;
- }
-
- /** {@inheritDoc} */
- @Override
- public void visitInternalNode(final BSPTree<P> node) {
- node.setAttribute(null);
- }
-
- /** {@inheritDoc} */
- @Override
- public void visitLeafNode(final BSPTree<P> node) {
- }
-
- }
-
- /** Handler replacing nodes with vanishing cuts with leaf nodes. */
- private class VanishingToLeaf implements VanishingCutHandler<P> {
-
- /** Inside/outside indocator to use for ambiguous nodes. */
- private final boolean inside;
-
- /** Simple constructor.
- * @param inside inside/outside indicator to use for ambiguous nodes
- */
- VanishingToLeaf(final boolean inside) {
- this.inside = inside;
- }
-
- /** {@inheritDoc} */
- @Override
- public BSPTree<P> fixNode(final BSPTree<P> node) {
- if (node.getPlus().getAttribute().equals(node.getMinus().getAttribute())) {
- // no ambiguity
- return new BSPTree<>(node.getPlus().getAttribute());
- } else {
- // ambiguous node
- return new BSPTree<>(inside);
- }
- }
-
- }
-
-}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Split.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Split.java
new file mode 100644
index 0000000..20307e3
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Split.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+/** Class containing the result of splitting an object with a hyperplane.
+ * @param <T> Split type
+ */
+public final class Split<T> {
+
+ /** Part of the object lying on the minus side of the splitting hyperplane.
+ */
+ private final T minus;
+
+ /** Part of the object lying on the plus side of the splitting hyperplane.
+ */
+ private final T plus;
+
+ /** Build a new instance from its parts.
+ * @param minus part of the object lying on the minus side of the
+ * splitting hyperplane or null if no such part exists
+ * @param plus part of the object lying on the plus side of the
+ * splitting hyperplane or null if no such part exists.
+ */
+ public Split(final T minus, final T plus) {
+ this.minus = minus;
+ this.plus = plus;
+ }
+
+ /** Get the part of the object lying on the minus side of the splitting
+ * hyperplane or null if no such part exists.
+ * @return part of the object lying on the minus side of the splitting
+ * hyperplane
+ */
+ public T getMinus() {
+ return minus;
+ }
+
+ /** Get the part of the object lying on the plus side of the splitting
+ * hyperplane or null if no such part exists.
+ * @return part of the object lying on the plus side of the splitting
+ * hyperplane
+ */
+ public T getPlus() {
+ return plus;
+ }
+
+ /** Get the location of the object with respect to its splitting
+ * hyperplane.
+ * @return
+ * <ul>
+ * <li>{@link SplitLocation#PLUS} - if only {@link #getPlus()} is not null</li>
+ * <li>{@link SplitLocation#MINUS} - if only {@link #getMinus()} is not null</li>
+ * <li>{@link SplitLocation#BOTH} - if both {@link #getPlus()} and {@link #getMinus()}
+ * are not null</li>
+ * <li>{@link SplitLocation#NEITHER} - if both {@link #getPlus()} and {@link #getMinus()}
+ * are null</li>
+ * </ul>
+ */
+ public SplitLocation getLocation() {
+ if (minus != null) {
+ return plus != null ? SplitLocation.BOTH : SplitLocation.MINUS;
+ } else if (plus != null) {
+ return SplitLocation.PLUS;
+ }
+ return SplitLocation.NEITHER;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(this.getClass().getSimpleName())
+ .append("[location= ")
+ .append(getLocation())
+ .append(", minus= ")
+ .append(minus)
+ .append(", plus= ")
+ .append(plus)
+ .append(']');
+
+ return sb.toString();
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/SplitLocation.java
similarity index 53%
copy from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
copy to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/SplitLocation.java
index 046defe..cbf2445 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/SplitLocation.java
@@ -16,21 +16,30 @@
*/
package org.apache.commons.geometry.core.partitioning;
-/** Enumerate representing the location of an element with respect to an
- * {@link Hyperplane hyperplane} of a space.
+/** Enumeration representing the location of a split object with respect
+ * to its splitting {@link Hyperplane hyperplane}.
*/
-public enum Side {
+public enum SplitLocation {
- /** Code for the plus side of the hyperplane. */
+ /** Value indicating that the split object lies entirely on the
+ * plus side of the splitting hyperplane.
+ */
PLUS,
- /** Code for the minus side of the hyperplane. */
+ /** Value indicating that the split object lies entirely on the
+ * minus side of the splitting hyperplane.
+ */
MINUS,
- /** Code for elements crossing the hyperplane from plus to minus side. */
+ /** Value indicating that the split object lies in both the plus
+ * and minus sides of the splitting hyperplane.
+ */
BOTH,
- /** Code for the hyperplane itself. */
- HYPER;
-
+ /** Value indicating that the split object lies neither on the plus
+ * or minus sides of the splitting hyperplane. This is the case when
+ * the object lies entirely on the hyperplane or is empty (and
+ * therefore "lies" nowhere).
+ */
+ NEITHER;
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Splittable.java
similarity index 64%
copy from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
copy to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Splittable.java
index 046defe..60cef06 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Splittable.java
@@ -16,21 +16,17 @@
*/
package org.apache.commons.geometry.core.partitioning;
-/** Enumerate representing the location of an element with respect to an
- * {@link Hyperplane hyperplane} of a space.
- */
-public enum Side {
-
- /** Code for the plus side of the hyperplane. */
- PLUS,
-
- /** Code for the minus side of the hyperplane. */
- MINUS,
+import org.apache.commons.geometry.core.Point;
- /** Code for elements crossing the hyperplane from plus to minus side. */
- BOTH,
-
- /** Code for the hyperplane itself. */
- HYPER;
+/** Interface representing objects that can be split by hyperplanes.
+ * @param <P> Point implementation type
+ * @param <S> Split type
+ */
+public interface Splittable<P extends Point<P>, S extends Splittable<P, S>> {
+ /** Split this instance with the given hyperplane.
+ * @param splitter the hyperplane to split this object with.
+ * @return result of the split operation
+ */
+ Split<? extends S> split(Hyperplane<P> splitter);
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/SubHyperplane.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/SubHyperplane.java
index 7237bb7..887f2a8 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/SubHyperplane.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/SubHyperplane.java
@@ -16,127 +16,129 @@
*/
package org.apache.commons.geometry.core.partitioning;
-import org.apache.commons.geometry.core.Point;
-
-/** This interface represents the remaining parts of an hyperplane after
- * other parts have been chopped off.
+import java.util.List;
- * <p>sub-hyperplanes are obtained when parts of an {@link
- * Hyperplane hyperplane} are chopped off by other hyperplanes that
- * intersect it. The remaining part is a convex region. Such objects
- * appear in {@link BSPTree BSP trees} as the intersection of a cut
- * hyperplane with the convex region which it splits, the chopping
- * hyperplanes are the cut hyperplanes closer to the tree root.</p>
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
- * <p>
- * Note that this interface is <em>not</em> intended to be implemented
- * by Apache Commons Math users, it is only intended to be implemented
- * within the library itself. New methods may be added even for minor
- * versions, which breaks compatibility for external implementations.
- * </p>
+/** Interface representing subhyperplanes, which are regions
+ * embedded in a hyperplane.
- * @param <P> Point type defining the embedding space.
+ * @param <P> Point implementation type
*/
-public interface SubHyperplane<P extends Point<P>> {
-
- /** Copy the instance.
- * <p>The instance created is completely independent of the original
- * one. A deep copy is used, none of the underlying objects are
- * shared (except for the nodes attributes and immutable
- * objects).</p>
- * @return a new sub-hyperplane, copy of the instance
- */
- SubHyperplane<P> copySelf();
+public interface SubHyperplane<P extends Point<P>> extends Splittable<P, SubHyperplane<P>> {
- /** Get the underlying hyperplane.
- * @return underlying hyperplane
+ /** Get the hyperplane that this instance is embedded in.
+ * @return the hyperplane that this instance is embedded in.
*/
Hyperplane<P> getHyperplane();
- /** Check if the instance is empty.
- * @return true if the instance is empty
+ /** Return true if this instance contains all points in the
+ * hyperplane.
+ * @return true if this instance contains all points in the
+ * hyperplane
+ */
+ boolean isFull();
+
+ /** Return true if this instance does not contain any points.
+ * @return true if this instance does not contain any points
*/
boolean isEmpty();
- /** Get the size of the instance.
- * @return the size of the instance (this is a length in 1D, an area
- * in 2D, a volume in 3D ...)
+ /** Return true if this instance has infinite size.
+ * @return true if this instance has infinite size
+ */
+ boolean isInfinite();
+
+ /** Return true if this instance has finite size.
+ * @return true if this instance has finite size
+ */
+ boolean isFinite();
+
+ /** Return the size of this instance. This will have different
+ * meanings in different spaces and dimensions. For example, in
+ * Euclidean space, this will be length in 2D and area in 3D.
+ * @return the size of this instance
*/
double getSize();
- /** Split the instance in two parts by an hyperplane.
- * @param hyperplane splitting hyperplane
- * @return an object containing both the part of the instance
- * on the plus side of the hyperplane and the part of the
- * instance on the minus side of the hyperplane
+ /** Classify a point with respect to the subhyperplane's region. The point is
+ * classified as follows:
+ * <ul>
+ * <li>{@link RegionLocation#INSIDE INSIDE} - The point lies on the hyperplane
+ * and inside of the subhyperplane's region.</li>
+ * <li>{@link RegionLocation#BOUNDARY BOUNDARY} - The point lies on the hyperplane
+ * and is on the boundary of the subhyperplane's region.</li>
+ * <li>{@link RegionLocation#OUTSIDE OUTSIDE} - The point does not lie on
+ * the hyperplane or it does lie on the hyperplane but is outside of the
+ * subhyperplane's region.</li>
+ * </ul>
+ * @param point the point to classify
+ * @return classification of the point with respect to the subhyperplane's hyperplane
+ * and region
*/
- SplitSubHyperplane<P> split(Hyperplane<P> hyperplane);
+ RegionLocation classify(P point);
- /** Compute the union of the instance and another sub-hyperplane.
- * @param other other sub-hyperplane to union (<em>must</em> be in the
- * same hyperplane as the instance)
- * @return a new sub-hyperplane, union of the instance and other
+ /** Return true if the subhyperplane contains the given point, meaning that the point
+ * lies on the hyperplane and is not on the outside of the subhyperplane's region.
+ * @param point the point to check
+ * @return true if the point is contained in the subhyperplane
*/
- SubHyperplane<P> reunite(SubHyperplane<P> other);
+ default boolean contains(P point) {
+ final RegionLocation loc = classify(point);
+ return loc != null && loc != RegionLocation.OUTSIDE;
+ }
- /** Class holding the results of the {@link #split split} method.
- * @param <U> Type of the embedding space.
+ /** Return the closest point to the argument that is contained in the subhyperplane
+ * (ie, not classified as {@link RegionLocation#OUTSIDE outside}), or null if no
+ * such point exists.
+ * @param point the reference point
+ * @return the closest point to the reference point that is contained in the subhyperplane,
+ * or null if no such point exists
*/
- class SplitSubHyperplane<U extends Point<U>> {
+ P closest(P point);
- /** Part of the sub-hyperplane on the plus side of the splitting hyperplane. */
- private final SubHyperplane<U> plus;
+ /** Return a {@link Builder} instance for joining multiple
+ * subhyperplanes together.
+ * @return a new builder instance
+ */
+ Builder<P> builder();
- /** Part of the sub-hyperplane on the minus side of the splitting hyperplane. */
- private final SubHyperplane<U> minus;
+ /** Return a new subhyperplane instance resulting from the application
+ * of the given transform. The current instance is not modified.
+ * @param transform the transform instance to apply
+ * @return new transformed subhyperplane instance
+ */
+ SubHyperplane<P> transform(Transform<P> transform);
- /** Build a SplitSubHyperplane from its parts.
- * @param plus part of the sub-hyperplane on the plus side of the
- * splitting hyperplane
- * @param minus part of the sub-hyperplane on the minus side of the
- * splitting hyperplane
- */
- public SplitSubHyperplane(final SubHyperplane<U> plus,
- final SubHyperplane<U> minus) {
- this.plus = plus;
- this.minus = minus;
- }
-
- /** Get the part of the sub-hyperplane on the plus side of the splitting hyperplane.
- * @return part of the sub-hyperplane on the plus side of the splitting hyperplane
- */
- public SubHyperplane<U> getPlus() {
- return plus;
- }
+ /** Convert this instance into a list of convex child subhyperplanes.
+ * @return a list of convex subhyperplanes representing the same subspace
+ * region as this instance
+ */
+ List<? extends ConvexSubHyperplane<P>> toConvex();
- /** Get the part of the sub-hyperplane on the minus side of the splitting hyperplane.
- * @return part of the sub-hyperplane on the minus side of the splitting hyperplane
+ /** Interface for joining multiple {@link SubHyperplane}s into a single
+ * instance.
+ * @param <P> Point implementation type
+ */
+ interface Builder<P extends Point<P>> {
+
+ /** Add a {@link SubHyperplane} instance to the builder.
+ * @param sub subhyperplane to add to this instance
*/
- public SubHyperplane<U> getMinus() {
- return minus;
- }
-
- /** Get the side of the split sub-hyperplane with respect to its splitter.
- * @return {@link Side#PLUS} if only {@link #getPlus()} is neither null nor empty,
- * {@link Side#MINUS} if only {@link #getMinus()} is neither null nor empty,
- * {@link Side#BOTH} if both {@link #getPlus()} and {@link #getMinus()}
- * are neither null nor empty or {@link Side#HYPER} if both {@link #getPlus()} and
- * {@link #getMinus()} are either null or empty
+ void add(SubHyperplane<P> sub);
+
+ /** Add a {@link ConvexSubHyperplane} instance to the builder.
+ * @param sub convex subhyperplane to add to this instance
*/
- public Side getSide() {
- if (plus != null && !plus.isEmpty()) {
- if (minus != null && !minus.isEmpty()) {
- return Side.BOTH;
- } else {
- return Side.PLUS;
- }
- } else if (minus != null && !minus.isEmpty()) {
- return Side.MINUS;
- } else {
- return Side.HYPER;
- }
- }
+ void add(ConvexSubHyperplane<P> sub);
+ /** Get a {@link SubHyperplane} representing the union
+ * of all input subhyperplanes.
+ * @return subhyperplane representing the union of all input
+ * subhyperplanes
+ */
+ SubHyperplane<P> build();
}
-
}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Transform.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Transform.java
deleted file mode 100644
index 53cb056..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Transform.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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 org.apache.commons.geometry.core.Point;
-
-
-/** This interface represents an inversible affine transform in a space.
- * <p>Inversible affine transform include for example scalings,
- * translations, rotations.</p>
-
- * <p>Transforms are dimension-specific. The consistency rules between
- * the three {@code apply} methods are the following ones for a
- * transformed defined for dimension D:</p>
- * <ul>
- * <li>
- * the transform can be applied to a point in the
- * D-dimension space using its {@link #apply(Point)}
- * method
- * </li>
- * <li>
- * the transform can be applied to a (D-1)-dimension
- * hyperplane in the D-dimension space using its
- * {@link #apply(Hyperplane)} method
- * </li>
- * <li>
- * the transform can be applied to a (D-2)-dimension
- * sub-hyperplane in a (D-1)-dimension hyperplane using
- * its {@link #apply(SubHyperplane, Hyperplane, Hyperplane)}
- * method
- * </li>
- * </ul>
-
- * @param <P> Point type defining the embedding space.
- * @param <S> Point type defining the embedded sub-space.
- */
-public interface Transform<P extends Point<P>, S extends Point<S>> {
-
- /** Transform a point of a space.
- * @param point point to transform
- * @return a new object representing the transformed point
- */
- P apply(P point);
-
- /** Transform an hyperplane of a space.
- * @param hyperplane hyperplane to transform
- * @return a new object representing the transformed hyperplane
- */
- Hyperplane<P> apply(Hyperplane<P> hyperplane);
-
- /** Transform a sub-hyperplane embedded in an hyperplane.
- * @param sub sub-hyperplane to transform
- * @param original hyperplane in which the sub-hyperplane is
- * defined (this is the original hyperplane, the transform has
- * <em>not</em> been applied to it)
- * @param transformed hyperplane in which the sub-hyperplane is
- * defined (this is the transformed hyperplane, the transform
- * <em>has</em> been applied to it)
- * @return a new object representing the transformed sub-hyperplane
- */
- SubHyperplane<S> apply(SubHyperplane<S> sub, Hyperplane<P> original, Hyperplane<P> transformed);
-
-}
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
new file mode 100644
index 0000000..157eb16
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTree.java
@@ -0,0 +1,1108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.partitioning.bsp;
+
+import java.io.Serializable;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.core.partitioning.SplitLocation;
+import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+import org.apache.commons.geometry.core.partitioning.bsp.BSPTreeVisitor.Order;
+
+/** Abstract class for Binary Space Partitioning (BSP) tree implementations.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPTree.AbstractNode<P, N>>
+ implements BSPTree<P, N>, Serializable {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190330L;
+
+ /** The default number of levels to print when creating a string representation of the tree. */
+ private static final int DEFAULT_TREE_STRING_MAX_DEPTH = 8;
+
+ /** Integer value set on various node fields when a value is unknown. */
+ private static final int UNKNOWN_VALUE = -1;
+
+ /** The root node for the tree. */
+ private N root;
+
+ /** The current modification version for the tree structure. This is incremented each time
+ * a structural change occurs in the tree and is used to determine when cached values
+ * must be recomputed.
+ */
+ private int version = 0;
+
+ /** {@inheritDoc} */
+ @Override
+ public N getRoot() {
+ if (root == null) {
+ setRoot(createNode());
+ }
+ return root;
+ }
+
+ /** Set the root node for the tree. Cached tree properties are invalidated
+ * with {@link #invalidate()}.
+ * @param root new root node for the tree
+ */
+ protected void setRoot(final N root) {
+ this.root = root;
+
+ this.root.makeRoot();
+
+ invalidate();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int count() {
+ return getRoot().count();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int height() {
+ return getRoot().height();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void accept(final BSPTreeVisitor<P, N> visitor) {
+ acceptVisitor(getRoot(), visitor);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public N findNode(final P pt, final NodeCutRule cutBehavior) {
+ return findNode(getRoot(), pt, cutBehavior);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void insert(final SubHyperplane<P> sub) {
+ insert(sub.toConvex());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void insert(final ConvexSubHyperplane<P> convexSub) {
+ insertRecursive(getRoot(), convexSub,
+ convexSub.getHyperplane().span());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void insert(final Iterable<? extends ConvexSubHyperplane<P>> convexSubs) {
+ for (final ConvexSubHyperplane<P> convexSub : convexSubs) {
+ insert(convexSub);
+ }
+ }
+
+ /** Return an iterator over the nodes in the tree. */
+ @Override
+ public Iterator<N> iterator() {
+ return new NodeIterator<>(getRoot());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void copy(final BSPTree<P, N> src) {
+ copySubtree(src.getRoot(), getRoot());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void extract(final N node) {
+ // copy downward
+ final N extracted = importSubtree(node);
+
+ // extract upward
+ final N newRoot = extractParentPath(node, extracted);
+
+ // set the root of this tree
+ setRoot(newRoot);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void transform(final Transform<P> transform) {
+ final boolean swapChildren = swapsInsideOutside(transform);
+ transformRecursive(getRoot(), transform, swapChildren);
+
+ invalidate();
+ }
+
+ /** Get a simple string representation of the tree structure. The returned string contains
+ * the tree structure down to the default max depth of {@value #DEFAULT_TREE_STRING_MAX_DEPTH}.
+ * @return a string representation of the tree
+ */
+ public String treeString() {
+ return treeString(DEFAULT_TREE_STRING_MAX_DEPTH);
+ }
+
+ /** Get a simple string representation of the tree structure. The returned string contains
+ * the tree structure down to {@code maxDepth}.
+ * @param maxDepth the maximum depth in the tree to print; nodes below this depth are skipped
+ * @return a string representation of the tree
+ */
+ public String treeString(final int maxDepth) {
+ BSPTreePrinter<P, N> printer = new BSPTreePrinter<>(maxDepth);
+ accept(printer);
+
+ return printer.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append(getClass().getSimpleName())
+ .append("[count= ")
+ .append(count())
+ .append(", height= ")
+ .append(height())
+ .append("]")
+ .toString();
+ }
+
+ /** Create a new node for this tree.
+ * @return a new node for this tree
+ */
+ protected abstract N createNode();
+
+ /** Copy non-structural node properties from {@code src} to {@code dst}.
+ * Non-structural properties are those properties not directly related
+ * to the structure of the BSP tree, i.e. properties other than parent/child
+ * connections and cut subhyperplanes. Subclasses should override this method
+ * when additional properties are stored on nodes.
+ * @param src source node
+ * @param dst destination node
+ */
+ protected void copyNodeProperties(final N src, final N dst) {
+ // no-op
+ }
+
+ /** Method called to initialize a new child node. Subclasses can use this method to
+ * set initial attributes on the node.
+ * @param parent the parent node
+ * @param child the new child node
+ * @param isPlus true if the child will be assigned as the parent's plus child;
+ * false if it will be the parent's minus child
+ */
+ protected void initChildNode(final N parent, final N child, final boolean isPlus) {
+ // no-op
+ }
+
+ /** Create a non-structural copy of the given node. Properties such as parent/child
+ * connections and cut subhyperplanes are <em>not</em> copied.
+ * @param src the node to copy; does not need to belong to the current tree
+ * @return the copied node
+ * @see AbstractBSPTree#copyNodeProperties(AbstractNode, AbstractNode)
+ */
+ protected N copyNode(final N src) {
+ final N copy = createNode();
+ copyNodeProperties(src, copy);
+
+ return copy;
+ }
+
+ /** Recursively copy a subtree. The returned node is not attached to the current tree.
+ * Structural <em>and</em> non-structural properties are copied from the source subtree
+ * to the destination subtree. This method does nothing if {@code src} and {@code dst}
+ * reference the same node.
+ * @param src the node representing the source subtree; does not need to belong to the
+ * current tree
+ * @param dst the node representing the destination subtree
+ * @return the copied node, ie {@code dst}
+ */
+ protected N copySubtree(final N src, final N dst) {
+ // only copy if we're actually switching nodes
+ if (src != dst) {
+ // copy non-structural properties
+ copyNodeProperties(src, dst);
+
+ // copy the subtree structure
+ ConvexSubHyperplane<P> cut = null;
+ N minus = null;
+ N plus = null;
+
+ if (!src.isLeaf()) {
+ final AbstractBSPTree<P, N> dstTree = dst.getTree();
+
+ cut = src.getCut();
+ minus = copySubtree(src.getMinus(), dstTree.createNode());
+ plus = copySubtree(src.getPlus(), dstTree.createNode());
+ }
+
+ dst.setSubtree(cut, minus, plus);
+ }
+
+ return dst;
+ }
+
+ /** Import the subtree represented by the given node into this tree. If the given node
+ * already belongs to this tree, then the node is returned directly without modification.
+ * If the node does <em>not</em> belong to this tree, a new node is created and the src node
+ * subtree is copied into it.
+ *
+ * <p>This method does not modify the current structure of the tree.</p>
+ * @param src node to import
+ * @return the given node if it belongs to this tree, otherwise a new node containing
+ * a copy of the given node's subtree
+ * @see #copySubtree(AbstractNode, AbstractNode)
+ */
+ protected N importSubtree(final N src) {
+ // create a copy of the node if it's not already in this tree
+ if (src.getTree() != this) {
+ return copySubtree(src, createNode());
+ }
+
+ return src;
+ }
+
+ /** Extract the path from {@code src} to the root of its tree and
+ * set it as the parent path of {@code dst}. Leaf nodes created during
+ * the extraction are given the same node properties as their counterparts
+ * in the source tree but without the cuts and child nodes. The properties
+ * of {@code dst} are not modified, with the exception of its parent node
+ * reference.
+ * @param src the source node to copy the parent path from
+ * @param dst the destination node to place under the extracted path
+ * @return the root node of the extracted path
+ */
+ protected N extractParentPath(final N src, final N dst) {
+ N dstParent = dst;
+ N dstChild;
+
+ N srcChild = src;
+ N srcParent = srcChild.getParent();
+
+ while (srcParent != null) {
+ dstChild = dstParent;
+ dstParent = copyNode(srcParent);
+
+ if (srcChild.isMinus()) {
+ dstParent.setSubtree(
+ srcParent.getCut(),
+ dstChild,
+ copyNode(srcParent.getPlus()));
+ } else {
+ dstParent.setSubtree(
+ srcParent.getCut(),
+ copyNode(srcParent.getMinus()),
+ dstChild);
+ }
+
+ srcChild = srcParent;
+ srcParent = srcChild.getParent();
+ }
+
+ return dstParent;
+ }
+
+ /** Find the smallest node in the tree containing the point, starting
+ * at the given node.
+ * @param start the node to begin the search with
+ * @param pt the point to check
+ * @param cutBehavior value determining the search behavior when the test point
+ * lies directly on the cut subhyperplane of an internal node
+ * @return the smallest node in the tree containing the point
+ */
+ protected N findNode(final N start, final P pt, final NodeCutRule cutBehavior) {
+ final Hyperplane<P> cutHyper = start.getCutHyperplane();
+ if (cutHyper != null) {
+ final HyperplaneLocation cutLoc = cutHyper.classify(pt);
+
+ final boolean onPlusSide = cutLoc == HyperplaneLocation.PLUS;
+ final boolean onMinusSide = cutLoc == HyperplaneLocation.MINUS;
+ final boolean onCut = !onPlusSide && !onMinusSide;
+
+ if (onMinusSide || (onCut && cutBehavior == NodeCutRule.MINUS)) {
+ return findNode(start.getMinus(), pt, cutBehavior);
+ } else if (onPlusSide || (onCut && cutBehavior == NodeCutRule.PLUS)) {
+ return findNode(start.getPlus(), pt, cutBehavior);
+ }
+ }
+ return start;
+ }
+
+ /** Visit the nodes in a subtree.
+ * @param node the node to begin the visit process
+ * @param visitor the visitor to pass nodes to
+ */
+ protected void acceptVisitor(final N node, BSPTreeVisitor<P, N> visitor) {
+ if (node.isLeaf()) {
+ visitor.visit(node);
+ } else {
+ final Order order = visitor.visitOrder(node);
+
+ if (order != null) {
+
+ switch (order) {
+ case PLUS_MINUS_NODE:
+ acceptVisitor(node.getPlus(), visitor);
+ acceptVisitor(node.getMinus(), visitor);
+ visitor.visit(node);
+ break;
+ case PLUS_NODE_MINUS:
+ acceptVisitor(node.getPlus(), visitor);
+ visitor.visit(node);
+ acceptVisitor(node.getMinus(), visitor);
+ break;
+ case MINUS_PLUS_NODE:
+ acceptVisitor(node.getMinus(), visitor);
+ acceptVisitor(node.getPlus(), visitor);
+ visitor.visit(node);
+ break;
+ case MINUS_NODE_PLUS:
+ acceptVisitor(node.getMinus(), visitor);
+ visitor.visit(node);
+ acceptVisitor(node.getPlus(), visitor);
+ break;
+ case NODE_PLUS_MINUS:
+ visitor.visit(node);
+ acceptVisitor(node.getPlus(), visitor);
+ acceptVisitor(node.getMinus(), visitor);
+ break;
+ default: // NODE_MINUS_PLUS:
+ visitor.visit(node);
+ acceptVisitor(node.getMinus(), visitor);
+ acceptVisitor(node.getPlus(), visitor);
+ break;
+ }
+ }
+ }
+ }
+
+ /** Cut a node with a hyperplane. The algorithm proceeds are follows:
+ * <ol>
+ * <li>The hyperplane is trimmed by splitting it with each cut hyperplane on the
+ * path from the given node to the root of the tree.</li>
+ * <li>If the remaining portion of the hyperplane is <em>not</em> empty, then
+ * <ul>
+ * <li>the remaining portion becomes the cut subhyperplane for the node,</li>
+ * <li>two new child nodes are created and initialized with
+ * {@link #initChildNode(AbstractNode, AbstractNode, boolean)}, and</li>
+ * <li>true is returned.</li>
+ * </ul>
+ * </li>
+ * <li>If the remaining portion of the hyperplane <em>is</em> empty (ie, the
+ * cutting hyperplane does not intersect the node's region), then
+ * <ul>
+ * <li>the node is converted to a leaf node (meaning that previous
+ * child nodes are lost), and</li>
+ * <li>false is returned.</li>
+ * </ul>
+ * </li>
+ * </ol>
+ *
+ * <p>It is important to note that since this method uses the path from given node
+ * to the tree root, it must only be used on nodes that are already inserted into
+ * the tree.</p>
+ *
+ * <p>This method always calls {@link #invalidate()} to invalidate cached tree properties.</p>
+ *
+ * @param node the node to cut
+ * @param cutter the hyperplane to cut the node with
+ * @return true if the node was cut and two new child nodes were created;
+ * otherwise false
+ * @see #trimToNode(AbstractNode, ConvexSubHyperplane)
+ * @see #cutNode(AbstractNode, ConvexSubHyperplane)
+ * @see #invalidate()
+ */
+ protected boolean insertNodeCut(final N node, final Hyperplane<P> cutter) {
+ // cut the hyperplane using all hyperplanes from this node up
+ // to the root
+ final ConvexSubHyperplane<P> cut = trimToNode(node, cutter.span());
+ if (cut == null || cut.isEmpty()) {
+ // insertion failed; the node was not cut
+ cutNode(node, null);
+ return false;
+ }
+
+ cutNode(node, cut);
+ return true;
+ }
+
+ /** Trim the given subhyperplane to the region defined by the given node. This method cuts the
+ * subhyperplane with the cut hyperplanes (binary partitioners) of all parent nodes up to
+ * the root and returns the trimmed subhyperplane or {@code null} if the subhyperplane lies
+ * outside of the region defined by the node.
+ *
+ * <p>If the subhyperplane is directly coincident with a binary partitioner of a parent node,
+ * then the relative orientations of the associated hyperplanes are used to determine the behavior,
+ * as described below.
+ * <ul>
+ * <li>If the orientations are <strong>similar</strong>, then the subhyperplane is determined to
+ * lie <em>outside</em> of the node's region and {@code null} is returned.</li>
+ * <li>If the orientations are <strong>different</strong> (ie, opposite), then the subhyperplane
+ * is determined to lie <em>inside</em> of the node's region and the fit operation continues
+ * with the remaining parent nodes.</li>
+ * </ul>
+ * These rules are designed to allow the creation of trees with node regions that are the thickness
+ * of a single hyperplane. For example, in two dimensions, a tree could be constructed with an internal
+ * node containing a cut along the x-axis in the positive direction and with a child node containing a
+ * cut along the x-axis in the opposite direction. If the nodes in the tree are given inside and outside
+ * attributes, then this tree could be used to represent a region consisting of a single line or a region
+ * consisting of the entire space except for the single line. This would not be possible if nodes were not
+ * able to have cut hyperplanes that were coincident with parent cuts but in opposite directions.
+ *
+ * <p>
+ * Another way of looking at the rules above is that inserting a hyperplane into the tree that exactly
+ * matches the hyperplane of a parent node does not add any information to the tree. However, adding a
+ * hyperplane to the tree that is coincident with a parent node but with the opposite orientation,
+ * <em>does</em> add information to the tree.
+ *
+ * @param node the node representing the region to fit the subhyperplane to
+ * @param sub the subhyperplane to trim to the node's region
+ * @return the trimmed subhyperplane or null if the given subhyperplane does not intersect
+ * the node's region
+ */
+ protected ConvexSubHyperplane<P> trimToNode(final N node, final ConvexSubHyperplane<P> sub) {
+
+ ConvexSubHyperplane<P> result = sub;
+
+ N parentNode = node.getParent();
+ N currentNode = node;
+
+ while (parentNode != null && result != null) {
+ final Split<? extends ConvexSubHyperplane<P>> split = result.split(parentNode.getCutHyperplane());
+
+ if (split.getLocation() == SplitLocation.NEITHER) {
+ // if we're directly on the splitter and have the same orientation, then
+ // we say the subhyperplane does not lie in the node's region (no new information
+ // is added to the tree in this case)
+ if (result.getHyperplane().similarOrientation(parentNode.getCutHyperplane())) {
+ result = null;
+ }
+ } else {
+ result = currentNode.isPlus() ? split.getPlus() : split.getMinus();
+ }
+
+ currentNode = parentNode;
+ parentNode = parentNode.getParent();
+ }
+
+ return result;
+ }
+
+ /** Remove the cut from the given node. Returns true if the node had a cut before
+ * the call to this method. Any previous child nodes are lost.
+ * @param node the node to remove the cut from
+ * @return true if the node previously had a cut
+ */
+ protected boolean removeNodeCut(final N node) {
+ boolean hadCut = node.getCut() != null;
+ cutNode(node, null);
+
+ return hadCut;
+ }
+
+ /** Set the cut subhyperplane for the given node. If {@code cut} is {@code null} then any
+ * existing child nodes are removed. If {@code cut} is not {@code null}, two new child
+ * nodes are created and initialized with
+ * {@link AbstractBSPTree#initChildNode(AbstractNode, AbstractNode, boolean)}.
+ *
+ * <p>This method performs absolutely <em>no</em> validation on the given cut
+ * subhyperplane. It is the responsibility of the caller to ensure that the
+ * subhyperplane fits the region represented by the node.</p>
+ *
+ * <p>This method always calls {@link #invalidate()} to invalidate cached tree properties.</p>
+ * @param node the node to cut
+ * @param cut the convex subhyperplane to set as the node cut
+ */
+ protected void cutNode(final N node, final ConvexSubHyperplane<P> cut) {
+ N plus = null;
+ N minus = null;
+
+ if (cut != null) {
+ minus = createNode();
+ initChildNode(node, minus, false);
+
+ plus = createNode();
+ initChildNode(node, plus, true);
+ }
+
+ node.setSubtree(cut, minus, plus);
+
+ invalidate();
+ }
+
+ /** Return true if the given transform swaps the inside and outside of
+ * the region.
+ *
+ * <p>The default behavior of this method is to return true if the transform
+ * does not preserve spatial orientation (ie, {@link Transform#preservesOrientation()}
+ * is false). Subclasses may need to override this method to implement the correct
+ * behavior for their space and dimension.</p>
+ * @param transform transform to check
+ * @return true if the given transform swaps the interior and exterior of
+ * the region
+ */
+ protected boolean swapsInsideOutside(final Transform<P> transform) {
+ return !transform.preservesOrientation();
+ }
+
+ /** Recursively insert a subhyperplane into the tree at the given node.
+ * @param node the node to begin insertion with
+ * @param insert the subhyperplane to insert
+ * @param trimmed subhyperplane containing the result of splitting the entire
+ * space with each hyperplane from this node to the root
+ */
+ private void insertRecursive(final N node, final ConvexSubHyperplane<P> insert,
+ final ConvexSubHyperplane<P> trimmed) {
+ if (node.isLeaf()) {
+ cutNode(node, trimmed);
+ } else {
+ final Split<? extends ConvexSubHyperplane<P>> insertSplit = insert.split(node.getCutHyperplane());
+
+ final ConvexSubHyperplane<P> minus = insertSplit.getMinus();
+ final ConvexSubHyperplane<P> plus = insertSplit.getPlus();
+
+ if (minus != null || plus != null) {
+ final Split<? extends ConvexSubHyperplane<P>> trimmedSplit = trimmed.split(node.getCutHyperplane());
+
+ if (minus != null) {
+ insertRecursive(node.getMinus(), minus, trimmedSplit.getMinus());
+ }
+ if (plus != null) {
+ insertRecursive(node.getPlus(), plus, trimmedSplit.getPlus());
+ }
+ }
+ }
+ }
+
+ /** Transform the subtree rooted as {@code node} recursively.
+ * @param node the root node of the subtree to transform
+ * @param t the transform to apply
+ * @param swapChildren if true, the plus and minus child nodes of each internal node
+ * will be swapped; this should be the case when the transform is a reflection
+ */
+ private void transformRecursive(final N node, final Transform<P> t, final boolean swapChildren) {
+ if (node.isInternal()) {
+ // transform our cut
+ final ConvexSubHyperplane<P> transformedCut = node.getCut().transform(t);
+
+ // transform our children
+ transformRecursive(node.getMinus(), t, swapChildren);
+ transformRecursive(node.getPlus(), t, swapChildren);
+
+ final N transformedMinus = swapChildren ? node.getPlus() : node.getMinus();
+ final N transformedPlus = swapChildren ? node.getMinus() : node.getPlus();
+
+ // set our new state
+ node.setSubtree(transformedCut, transformedMinus, transformedPlus);
+ }
+ }
+
+ /** Split this tree with the given hyperplane, placing the split contents into the given
+ * target trees. One of the given trees may be null, in which case that portion of the split
+ * will not be exported. The current tree is not modified.
+ * @param splitter splitting hyperplane
+ * @param minus tree that will contain the portion of the tree on the minus side of the splitter
+ * @param plus tree that will contain the portion of the tree on the plus side of the splitter
+ */
+ protected void splitIntoTrees(final Hyperplane<P> splitter,
+ final AbstractBSPTree<P, N> minus, final AbstractBSPTree<P, N> plus) {
+
+ AbstractBSPTree<P, N> temp = (minus != null) ? minus : plus;
+
+ N splitRoot = temp.splitSubtree(this.getRoot(), splitter.span());
+
+ if (minus != null) {
+ if (plus != null) {
+ plus.extract(splitRoot.getPlus());
+ }
+ minus.extract(splitRoot.getMinus());
+ } else {
+ plus.extract(splitRoot.getPlus());
+ }
+ }
+
+ /** Split the subtree rooted at the given node by a partitioning convex subhyperplane defined
+ * on the same region as the node. The subtree rooted at {@code node} is imported into
+ * this tree, meaning that if it comes from a different tree, the other tree is not
+ * modified.
+ * @param node the root node of the subtree to split; may come from a different tree,
+ * in which case the other tree is not modified
+ * @param partitioner partitioning convex subhyperplane
+ * @return node containing the split subtree
+ */
+ protected N splitSubtree(final N node, final ConvexSubHyperplane<P> partitioner) {
+ if (node.isLeaf()) {
+ return splitLeafNode(node, partitioner);
+ }
+ return splitInternalNode(node, partitioner);
+ }
+
+ /** Split the given leaf node by a partitioning convex subhyperplane defined on the
+ * same region and import it into this tree.
+ * @param node the leaf node to split
+ * @param partitioner partitioning convex subhyperplane
+ * @return node containing the split subtree
+ */
+ private N splitLeafNode(final N node, final ConvexSubHyperplane<P> partitioner) {
+ // in this case, we just create a new parent node with the partitioner as its
+ // cut and two copies of the original node as children
+ final N parent = createNode();
+ parent.setSubtree(partitioner, copyNode(node), copyNode(node));
+
+ return parent;
+ }
+
+ /** Split the given internal node by a partitioning convex subhyperplane defined on the same region
+ * as the node and import it into this tree.
+ * @param node the internal node to split
+ * @param partitioner partitioning convex subhyperplane
+ * @return node containing the split subtree
+ */
+ private N splitInternalNode(final N node, final ConvexSubHyperplane<P> partitioner) {
+ // split the partitioner and node cut with each other's hyperplanes to determine their relative positions
+ final Split<? extends ConvexSubHyperplane<P>> partitionerSplit = partitioner.split(node.getCutHyperplane());
+ final Split<? extends ConvexSubHyperplane<P>> nodeCutSplit = node.getCut().split(partitioner.getHyperplane());
+
+ final SplitLocation partitionerSplitSide = partitionerSplit.getLocation();
+ final SplitLocation nodeCutSplitSide = nodeCutSplit.getLocation();
+
+ final N result = createNode();
+
+ N resultMinus;
+ N resultPlus;
+
+ if (partitionerSplitSide == SplitLocation.PLUS) {
+ if (nodeCutSplitSide == SplitLocation.PLUS) {
+ // partitioner is on node cut plus side, node cut is on partitioner plus side
+ final N nodePlusSplit = splitSubtree(node.getPlus(), partitioner);
+
+ resultMinus = nodePlusSplit.getMinus();
+
+ resultPlus = copyNode(node);
+ resultPlus.setSubtree(node.getCut(), importSubtree(node.getMinus()), nodePlusSplit.getPlus());
+ } else {
+ // partitioner is on node cut plus side, node cut is on partitioner minus side
+ final N nodePlusSplit = splitSubtree(node.getPlus(), partitioner);
+
+ resultMinus = copyNode(node);
+ resultMinus.setSubtree(node.getCut(), importSubtree(node.getMinus()), nodePlusSplit.getMinus());
+
+ resultPlus = nodePlusSplit.getPlus();
+ }
+ } else if (partitionerSplitSide == SplitLocation.MINUS) {
+ if (nodeCutSplitSide == SplitLocation.MINUS) {
+ // partitioner is on node cut minus side, node cut is on partitioner minus side
+ final N nodeMinusSplit = splitSubtree(node.getMinus(), partitioner);
+
+ resultMinus = copyNode(node);
+ resultMinus.setSubtree(node.getCut(), nodeMinusSplit.getMinus(), importSubtree(node.getPlus()));
+
+ resultPlus = nodeMinusSplit.getPlus();
+ } else {
+ // partitioner is on node cut minus side, node cut is on partitioner plus side
+ final N nodeMinusSplit = splitSubtree(node.getMinus(), partitioner);
+
+ resultMinus = nodeMinusSplit.getMinus();
+
+ resultPlus = copyNode(node);
+ resultPlus.setSubtree(node.getCut(), nodeMinusSplit.getPlus(), importSubtree(node.getPlus()));
+ }
+ } else if (partitionerSplitSide == SplitLocation.BOTH) {
+ // partitioner and node cut split each other
+ final N nodeMinusSplit = splitSubtree(node.getMinus(), partitionerSplit.getMinus());
+ final N nodePlusSplit = splitSubtree(node.getPlus(), partitionerSplit.getPlus());
+
+ resultMinus = copyNode(node);
+ resultMinus.setSubtree(nodeCutSplit.getMinus(), nodeMinusSplit.getMinus(), nodePlusSplit.getMinus());
+
+ resultPlus = copyNode(node);
+ resultPlus.setSubtree(nodeCutSplit.getPlus(), nodeMinusSplit.getPlus(), nodePlusSplit.getPlus());
+ } else {
+ // partitioner and node cut are parallel or anti-parallel
+ final boolean sameOrientation = partitioner.getHyperplane().similarOrientation(node.getCutHyperplane());
+
+ resultMinus = importSubtree(sameOrientation ? node.getMinus() : node.getPlus());
+ resultPlus = importSubtree(sameOrientation ? node.getPlus() : node.getMinus());
+ }
+
+ result.setSubtree(partitioner, resultMinus, resultPlus);
+
+ return result;
+ }
+
+ /** Invalidate any previously computed properties that rely on the internal structure of the tree.
+ * This method must be called any time the tree's internal structure changes in order to force cacheable
+ * tree and node properties to be recomputed the next time they are requested.
+ *
+ * <p>This method increments the tree's {@link #version} property.</p>
+ * @see #getVersion()
+ */
+ protected void invalidate() {
+ version = Math.max(0, version + 1); // positive values only
+ }
+
+ /** Get the current structural version of the tree. This is incremented each time the
+ * tree structure is changes and can be used by nodes to allow caching of computed values.
+ * @return the current version of the tree structure
+ * @see #invalidate()
+ */
+ protected int getVersion() {
+ return version;
+ }
+
+ /** Abstract implementation of {@link BSPTree.Node}. This class is intended for use with
+ * {@link AbstractBSPTree} and delegates tree mutation methods back to the parent tree object.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ public abstract static class AbstractNode<P extends Point<P>, N extends AbstractNode<P, N>>
+ implements BSPTree.Node<P, N>, Serializable {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190225L;
+
+ /** The owning tree instance. */
+ private final AbstractBSPTree<P, N> tree;
+
+ /** The parent node; this will be null for the tree root node. */
+ private N parent;
+
+ /** The subhyperplane cutting the node's region; this will be null for leaf nodes. */
+ private ConvexSubHyperplane<P> cut;
+
+ /** The node lying on the minus side of the cut subhyperplane; this will be null
+ * for leaf nodes.
+ */
+ private N minus;
+
+ /** The node lying on the plus side of the cut subhyperplane; this will be null
+ * for leaf nodes.
+ */
+ private N plus;
+
+ /** The current version of the node. This is set to track the tree's version
+ * and is used to detect when certain values need to be recomputed due to
+ * structural changes in the tree.
+ */
+ private int nodeVersion = -1;
+
+ /** The depth of this node in the tree. This will be zero for the root node and
+ * {@link AbstractBSPTree#UNKNOWN_VALUE} when the value needs to be computed.
+ */
+ private int depth = UNKNOWN_VALUE;
+
+ /** The total number of nodes in the subtree rooted at this node. This will be
+ * set to {@link AbstractBSPTree#UNKNOWN_VALUE} when the value needs
+ * to be computed.
+ */
+ private int count = UNKNOWN_VALUE;
+
+ /** The height of the subtree rooted at this node. This will
+ * be set to {@link AbstractBSPTree#UNKNOWN_VALUE} when the value needs
+ * to be computed.
+ */
+ private int height = UNKNOWN_VALUE;
+
+ /** Simple constructor.
+ * @param tree the tree instance that owns this node
+ */
+ protected AbstractNode(final AbstractBSPTree<P, N> tree) {
+ this.tree = tree;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AbstractBSPTree<P, N> getTree() {
+ return tree;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int depth() {
+ if (depth == UNKNOWN_VALUE) {
+ // calculate our depth based on our parent's depth, if
+ // possible
+ if (parent != null) {
+ final int parentDepth = parent.depth();
+ if (parentDepth != UNKNOWN_VALUE) {
+ depth = parentDepth + 1;
+ }
+ }
+ }
+ return depth;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int height() {
+ checkValid();
+
+ if (height == UNKNOWN_VALUE) {
+ if (isLeaf()) {
+ height = 0;
+ } else {
+ height = Math.max(getMinus().height(), getPlus().height()) + 1;
+ }
+ }
+
+ return height;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int count() {
+ checkValid();
+
+ if (count == UNKNOWN_VALUE) {
+ count = 1;
+
+ if (!isLeaf()) {
+ count += minus.count() + plus.count();
+ }
+ }
+
+ return count;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Iterator<N> iterator() {
+ return new NodeIterator<P, N>(getSelf());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void accept(final BSPTreeVisitor<P, N> visitor) {
+ tree.acceptVisitor(getSelf(), visitor);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public N getParent() {
+ return parent;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isLeaf() {
+ return cut == null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isInternal() {
+ return cut != null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isPlus() {
+ return parent != null && parent.getPlus() == this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMinus() {
+ return parent != null && parent.getMinus() == this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ConvexSubHyperplane<P> getCut() {
+ return cut;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Hyperplane<P> getCutHyperplane() {
+ return (cut != null) ? cut.getHyperplane() : null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public N getPlus() {
+ return plus;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public N getMinus() {
+ return minus;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean insertCut(final Hyperplane<P> cutter) {
+ return tree.insertNodeCut(getSelf(), cutter);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean clearCut() {
+ return tree.removeNodeCut(getSelf());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public N cut(final Hyperplane<P> cutter) {
+ this.insertCut(cutter);
+
+ return getSelf();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(this.getClass().getSimpleName())
+ .append("[cut= ")
+ .append(getCut())
+ .append(']');
+
+ return sb.toString();
+ }
+
+ /** Set the parameters for the subtree rooted at this node. The arguments should either be
+ * all null (representing a leaf node) or all non-null (representing an internal node).
+ *
+ * <p>Absolutely no validation is performed on the arguments. Callers are responsible for
+ * ensuring that any given subhyperplane fits the region defined by the node and that
+ * any child nodes belong to this tree and are correctly initialized.</p>
+ *
+ * @param newCut the new cut subhyperplane for the node
+ * @param newMinus the new minus child for the node
+ * @param newPlus the new plus child for the node
+ */
+ protected void setSubtree(final ConvexSubHyperplane<P> newCut, final N newMinus, final N newPlus) {
+ this.cut = newCut;
+
+ final N self = getSelf();
+
+ // cast for access to private member
+ AbstractNode<P, N> minusNode = newMinus;
+ AbstractNode<P, N> plusNode = newPlus;
+
+ // get the child depth now if we know it offhand, otherwise set it to the unknown value
+ // and have the child pull it when needed
+ final int childDepth = (depth != UNKNOWN_VALUE) ? depth + 1 : UNKNOWN_VALUE;
+
+ if (newMinus != null) {
+ minusNode.parent = self;
+ minusNode.depth = childDepth;
+ }
+ this.minus = newMinus;
+
+ if (newPlus != null) {
+ plusNode.parent = self;
+ plusNode.depth = childDepth;
+ }
+ this.plus = newPlus;
+ }
+
+ /**
+ * Make this node a root node, detaching it from its parent and settings its depth to zero.
+ * Any previous parent node will be left in an invalid state since one of its children now
+ * does not have a reference back to it.
+ */
+ protected void makeRoot() {
+ parent = null;
+ depth = 0;
+ }
+
+ /** Check if cached node properties are valid, meaning that no structural updates have
+ * occurred in the tree since the last call to this method. If updates have occurred, the
+ * {@link #nodeInvalidated()} method is called to clear the cached properties. This method
+ * should be called at the beginning of any method that fetches cacheable properties
+ * to ensure that no stale values are returned.
+ */
+ protected void checkValid() {
+ final int treeVersion = tree.getVersion();
+
+ if (nodeVersion != treeVersion) {
+ // the tree structure changed somewhere
+ nodeInvalidated();
+
+ // store the current version
+ nodeVersion = treeVersion;
+ }
+ }
+
+ /** Method called from {@link #checkValid()} when updates
+ * are detected in the tree. This method should clear out any
+ * computed properties that rely on the structure of the tree
+ * and prepare them for recalculation.
+ */
+ protected void nodeInvalidated() {
+ count = UNKNOWN_VALUE;
+ height = UNKNOWN_VALUE;
+ }
+
+ /** Get a reference to the current instance, cast to type N.
+ * @return a reference to the current instance, as type N.
+ */
+ protected abstract N getSelf();
+ }
+
+ /** Class for iterating through the nodes in a BSP subtree.
+ * @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> {
+
+ /** The current node stack. */
+ private final Deque<N> stack = new LinkedList<>();
+
+ /** 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) {
+ stack.push(subtreeRoot);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean hasNext() {
+ return !stack.isEmpty();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public N next() {
+ if (stack.isEmpty()) {
+ throw new NoSuchElementException();
+ }
+
+ final N result = stack.pop();
+
+ if (result != null && !result.isLeaf()) {
+ stack.push(result.getPlus());
+ stack.push(result.getMinus());
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeMergeOperator.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeMergeOperator.java
new file mode 100644
index 0000000..8cfaa46
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeMergeOperator.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.partitioning.bsp;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree.AbstractNode;
+
+/** Class containing the basic algorithm for merging two {@link AbstractBSPTree}
+ * instances. Subclasses must override the
+ * {@link #mergeLeaf(AbstractBSPTree.AbstractNode, AbstractBSPTree.AbstractNode)} method
+ * to implement the merging logic for their particular use case. The remainder of the
+ * algorithm is independent of the use case.
+ *
+ * <p>This class does not expose any public methods so that subclasses can present their own
+ * public API, tailored to the specific types being worked with. In particular, most subclasses
+ * will want to restrict the tree types used with the algorithm, which is difficult to implement
+ * cleanly at this level.</p>
+ *
+ * <p>This class maintains state during the merging process and is therefore
+ * <em>not</em> thread-safe.</p>
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+public abstract class AbstractBSPTreeMergeOperator<P extends Point<P>, N extends AbstractNode<P, N>> {
+
+ /** The tree that the merge operation output will be written to. All existing content
+ * in this tree is overwritten.
+ */
+ private AbstractBSPTree<P, N> outputTree;
+
+ /** Set the tree used as output for this instance.
+ * @param outputTree the tree used as output for this instance
+ */
+ protected void setOutputTree(final AbstractBSPTree<P, N> outputTree) {
+ this.outputTree = outputTree;
+ }
+
+ /** Get the tree used as output for this instance.
+ * @return the tree used as output for this instance
+ */
+ protected AbstractBSPTree<P, N> getOutputTree() {
+ return outputTree;
+ }
+
+ /** Perform a merge operation with the two input trees and store the result in the output tree. The
+ * output tree may be one of the input trees, in which case, the tree is modified in place.
+ * @param input1 first input tree
+ * @param input2 second input tree
+ * @param output output tree all previous content in this tree is overwritten
+ */
+ protected void performMerge(final AbstractBSPTree<P, N> input1, final AbstractBSPTree<P, N> input2,
+ final AbstractBSPTree<P, N> output) {
+
+ setOutputTree(output);
+
+ final N root1 = input1.getRoot();
+ final N root2 = input2.getRoot();
+
+ final N outputRoot = performMergeRecursive(root1, root2);
+
+ getOutputTree().setRoot(outputRoot);
+ }
+
+ /** Recursively merge two nodes.
+ * @param node1 node from the first input tree
+ * @param node2 node from the second input tree
+ * @return a merged node
+ */
+ private N performMergeRecursive(final N node1, final N node2) {
+
+ if (node1.isLeaf() || node2.isLeaf()) {
+ // delegate to the mergeLeaf method if we can no longer continue
+ // merging recursively
+ final N merged = mergeLeaf(node1, node2);
+
+ // copy the merged node to the output if needed (in case mergeLeaf
+ // returned one of the input nodes directly)
+ return outputTree.importSubtree(merged);
+ } else {
+ final N partitioned = outputTree.splitSubtree(node2, node1.getCut());
+
+ final N minus = performMergeRecursive(node1.getMinus(), partitioned.getMinus());
+
+ final N plus = performMergeRecursive(node1.getPlus(), partitioned.getPlus());
+
+ final N outputNode = outputTree.copyNode(node1);
+ outputNode.setSubtree(node1.getCut(), minus, plus);
+
+ return outputNode;
+ }
+ }
+
+ /** Create a new node in the output tree. The node is associated with the output tree but
+ * is not attached to a parent node.
+ * @return a new node associated with the output tree but not yet attached to a parent
+ */
+ protected N outputNode() {
+ return outputTree.createNode();
+ }
+
+ /** Create a new node in the output tree with the same non-structural properties as the given
+ * node. Non-structural properties are properties other than parent, children, or cut. The
+ * returned node is associated with the output tree but is not attached to a parent node.
+ * Note that this method only copies the given node and <strong>not</strong> any of its children.
+ * @param node the input node to copy properties from
+ * @return a new node in the output tree
+ */
+ protected N outputNode(final N node) {
+ return outputTree.copyNode(node);
+ }
+
+ /** Place the subtree rooted at the given input node into the output tree. The subtree
+ * is copied if needed.
+ * @param node the root of the subtree to copy
+ * @return a subtree in the output tree
+ */
+ protected N outputSubtree(final N node) {
+ return outputTree.importSubtree(node);
+ }
+
+ /** Merge a leaf node from one input with a subtree from another.
+ * <p>When this method is called, one or both of the given nodes will be a leaf node.
+ * This method is expected to return a node representing the merger of the two given
+ * nodes. The way that the returned node is determined defines the overall behavior of
+ * the merge operation.
+ * </p>
+ * <p>The return value can be one of the two input nodes or a completely different one.</p>
+ * @param node1 node from the first input tree
+ * @param node2 node from the second input tree
+ * @return node representing the merger of the two input nodes
+ */
+ protected abstract N mergeLeaf(N node1, N node2);
+}
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
new file mode 100644
index 0000000..9a867a8
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java
@@ -0,0 +1,966 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.partitioning.bsp;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.internal.IteratorTransform;
+import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
+import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.core.partitioning.SplitLocation;
+import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+import org.apache.commons.geometry.core.partitioning.bsp.BSPTreeVisitor.ClosestFirstVisitor;
+
+/** {@link BSPTree} specialized for representing regions of space. For example, this
+ * class can be used to represent polygons in Euclidean 2D space and polyhedrons
+ * in Euclidean 3D space.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+public abstract class AbstractRegionBSPTree<
+ P extends Point<P>,
+ N extends AbstractRegionBSPTree.AbstractRegionNode<P, N>>
+ extends AbstractBSPTree<P, N> implements HyperplaneBoundedRegion<P> {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 1L;
+
+ /** Value used to indicate an unknown size. */
+ private static final double UNKNOWN_SIZE = -1.0;
+
+ /** The region boundary size; this is computed when requested and then cached. */
+ private double boundarySize = UNKNOWN_SIZE;
+
+ /** The current size properties for the region. */
+ private RegionSizeProperties<P> regionSizeProperties;
+
+ /** Construct a new region will the given boolean determining whether or not the
+ * region will be full (including the entire space) or empty (excluding the entire
+ * space).
+ * @param full if true, the region will cover the entire space, otherwise it will
+ * be empty
+ */
+ protected AbstractRegionBSPTree(final boolean full) {
+ getRoot().setLocation(full ? RegionLocation.INSIDE : RegionLocation.OUTSIDE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isEmpty() {
+ return !hasNodeWithLocationRecursive(getRoot(), RegionLocation.INSIDE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isFull() {
+ return !hasNodeWithLocationRecursive(getRoot(), RegionLocation.OUTSIDE);
+ }
+
+ /** Return true if any node in the subtree rooted at the given node has a location with the
+ * given value.
+ * @param node the node at the root of the subtree to search
+ * @param location the location to find
+ * @return true if any node in the subtree has the given location
+ */
+ private boolean hasNodeWithLocationRecursive(final AbstractRegionNode<P, N> node, final RegionLocation location) {
+ if (node == null) {
+ return false;
+ }
+
+ return node.getLocation() == location ||
+ hasNodeWithLocationRecursive(node.getMinus(), location) ||
+ hasNodeWithLocationRecursive(node.getPlus(), location);
+ }
+
+ /** Modify this instance so that it contains the entire space.
+ * @see #isFull()
+ */
+ public void setFull() {
+ final N root = getRoot();
+
+ root.clearCut();
+ root.setLocation(RegionLocation.INSIDE);
+ }
+
+ /** Modify this instance so that is is completely empty.
+ * @see #isEmpty()
+ */
+ public void setEmpty() {
+ final N root = getRoot();
+
+ root.clearCut();
+ root.setLocation(RegionLocation.OUTSIDE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getSize() {
+ return getRegionSizeProperties().getSize();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getBoundarySize() {
+ if (boundarySize < 0.0) {
+ double sum = 0.0;
+
+ RegionCutBoundary<P> boundary;
+ for (final AbstractRegionNode<P, N> node : this) {
+ boundary = node.getCutBoundary();
+ if (boundary != null) {
+ sum += boundary.getInsideFacing().getSize();
+ sum += boundary.getOutsideFacing().getSize();
+ }
+ }
+
+ boundarySize = sum;
+ }
+
+ return boundarySize;
+ }
+
+ /** Return an {@link Iterable} for iterating over the boundaries of the region.
+ * Each boundary is oriented such that its plus side points to the outside of the
+ * region. The exact ordering of the boundaries is determined by the internal structure
+ * of the tree.
+ * @return an {@link Iterable} for iterating over the boundaries of the region
+ * @see #getBoundaries()
+ */
+ public Iterable<? extends ConvexSubHyperplane<P>> boundaries() {
+ return createBoundaryIterable(Function.identity());
+ }
+
+ /** Internal method for creating the iterable instances used to iterate the region boundaries.
+ * @param typeConverter function to convert the generic convex subhyperplane type into
+ * the type specific for this tree
+ * @param <C> ConvexSubhyperplane implementation type
+ * @return an iterable to iterating the region boundaries
+ */
+ protected <C extends ConvexSubHyperplane<P>> Iterable<C> createBoundaryIterable(
+ final Function<ConvexSubHyperplane<P>, C> typeConverter) {
+
+ return new Iterable<C>() {
+
+ @Override
+ public Iterator<C> iterator() {
+ final NodeIterator<P, N> nodeIterator = new NodeIterator<>(getRoot());
+ return new RegionBoundaryIterator<>(nodeIterator, typeConverter);
+ }
+ };
+ }
+
+ /** Return a list containing the boundaries of the region. Each boundary is oriented such
+ * that its plus side points to the outside of the region. The exact ordering of
+ * the boundaries is determined by the internal structure of the tree.
+ * @return a list of the boundaries of the region
+ */
+ public List<? extends ConvexSubHyperplane<P>> getBoundaries() {
+ return createBoundaryList(Function.identity());
+ }
+
+ /** Iternal method for creating a list of the region boundaries.
+ * @param typeConverter function to convert the generic convex subhyperplane type into
+ * the type specific for this tree
+ * @param <C> ConvexSubhyperplane implementation type
+ * @return a list of the region boundaries
+ */
+ protected <C extends ConvexSubHyperplane<P>> List<C> createBoundaryList(
+ final Function<ConvexSubHyperplane<P>, C> typeConverter) {
+
+ final List<C> result = new ArrayList<>();
+
+ final RegionBoundaryIterator<P, C, N> it = new RegionBoundaryIterator<>(iterator(), typeConverter);
+ it.forEachRemaining(result::add);
+
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public P project(P pt) {
+ final BoundaryProjector<P, N> projector = new BoundaryProjector<>(pt);
+ accept(projector);
+
+ return projector.getProjected();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public P getBarycenter() {
+ return getRegionSizeProperties().getBarycenter();
+ }
+
+ /** Helper method implementing the algorithm for splitting a tree by a hyperplane. Subclasses
+ * should call this method with two instantiated trees of the correct type.
+ * @param splitter splitting hyperplane
+ * @param minus tree that will contain the minus side of the split result
+ * @param plus tree that will contain the plus side of the split result
+ * @param <T> Tree implementation type
+ * @return result of splitting this tree with the given hyperplane
+ */
+ protected <T extends AbstractRegionBSPTree<P, N>> Split<T> split(final Hyperplane<P> splitter,
+ final T minus, final T plus) {
+
+ splitIntoTrees(splitter, minus, plus);
+
+ T splitMinus = null;
+ T splitPlus = null;
+
+ if (minus != null) {
+ minus.getRoot().getPlus().setLocation(RegionLocation.OUTSIDE);
+ minus.condense();
+
+ splitMinus = minus.isEmpty() ? null : minus;
+ }
+ if (plus != null) {
+ plus.getRoot().getMinus().setLocation(RegionLocation.OUTSIDE);
+ plus.condense();
+
+ splitPlus = plus.isEmpty() ? null : plus;
+ }
+
+ return new Split<T>(splitMinus, splitPlus);
+ }
+
+ /** Get the size-related properties for the region. The value is computed
+ * lazily and cached.
+ * @return the size-related properties for the region
+ */
+ protected RegionSizeProperties<P> getRegionSizeProperties() {
+ if (regionSizeProperties == null) {
+ regionSizeProperties = computeRegionSizeProperties();
+ }
+
+ return regionSizeProperties;
+ }
+
+ /** Compute the size-related properties of the region.
+ * @return object containing size properties for the region
+ */
+ protected abstract RegionSizeProperties<P> computeRegionSizeProperties();
+
+ /** {@inheritDoc}
+ *
+ * <p>If the point is {@link org.apache.commons.geometry.core.Spatial#isNaN() NaN}, then
+ * {@link RegionLocation#OUTSIDE} is returned.</p>
+ */
+ @Override
+ public RegionLocation classify(final P point) {
+ if (point.isNaN()) {
+ return RegionLocation.OUTSIDE;
+ }
+
+ return classifyRecursive(getRoot(), point);
+ }
+
+ /** Recursively classify a point with respect to the region.
+ * @param node the node to classify against
+ * @param point the point to classify
+ * @return the classification of the point with respect to the region rooted
+ * at the given node
+ */
+ private RegionLocation classifyRecursive(final AbstractRegionNode<P, N> node, final P point) {
+ if (node.isLeaf()) {
+ // the point is in a leaf, so the classification is just the leaf location
+ return node.getLocation();
+ } else {
+ final HyperplaneLocation cutLoc = node.getCutHyperplane().classify(point);
+
+ if (cutLoc == HyperplaneLocation.MINUS) {
+ return classifyRecursive(node.getMinus(), point);
+ } else if (cutLoc == HyperplaneLocation.PLUS) {
+ return classifyRecursive(node.getPlus(), point);
+ } else {
+ // the point is on the cut boundary; classify against both child
+ // subtrees and see if we end up with the same result or not
+ RegionLocation minusLoc = classifyRecursive(node.getMinus(), point);
+ RegionLocation plusLoc = classifyRecursive(node.getPlus(), point);
+
+ if (minusLoc == plusLoc) {
+ return minusLoc;
+ }
+ return RegionLocation.BOUNDARY;
+ }
+ }
+ }
+
+ /** Change this region into its complement. All inside nodes become outside
+ * nodes and vice versa. The orientation of the cut subhyperplanes is not modified.
+ */
+ public void complement() {
+ complementRecursive(getRoot());
+ }
+
+ /** Set this instance to be the complement of the given tree. The argument
+ * is not modified.
+ * @param tree the tree to become the complement of
+ */
+ public void complement(final AbstractRegionBSPTree<P, N> tree) {
+ copySubtree(tree.getRoot(), getRoot());
+ complementRecursive(getRoot());
+ }
+
+ /** Recursively switch all inside nodes to outside nodes and vice versa.
+ * @param node the node at the root of the subtree to switch
+ */
+ private void complementRecursive(final AbstractRegionNode<P, N> node) {
+ if (node != null) {
+ final RegionLocation newLoc = (node.getLocationValue() == RegionLocation.INSIDE) ?
+ RegionLocation.OUTSIDE :
+ RegionLocation.INSIDE;
+
+ node.setLocation(newLoc);
+
+ complementRecursive(node.getMinus());
+ complementRecursive(node.getPlus());
+ }
+ }
+
+ /** Compute the union of this instance and the given region, storing the result back in
+ * this instance. The argument is not modified.
+ * @param other the tree to compute the union with
+ */
+ public void union(final AbstractRegionBSPTree<P, N> other) {
+ new UnionOperator<P, N>().apply(this, other, this);
+ }
+
+ /** Compute the union of the two regions passed as arguments and store the result in
+ * this instance. Any nodes currently existing in this instance are removed.
+ * @param a first argument to the union operation
+ * @param b second argument to the union operation
+ */
+ public void union(final AbstractRegionBSPTree<P, N> a, final AbstractRegionBSPTree<P, N> b) {
+ new UnionOperator<P, N>().apply(a, b, this);
+ }
+
+ /** Compute the intersection of this instance and the given region, storing the result back in
+ * this instance. The argument is not modified.
+ * @param other the tree to compute the intersection with
+ */
+ public void intersection(final AbstractRegionBSPTree<P, N> other) {
+ new IntersectionOperator<P, N>().apply(this, other, this);
+ }
+
+ /** Compute the intersection of the two regions passed as arguments and store the result in
+ * this instance. Any nodes currently existing in this instance are removed.
+ * @param a first argument to the intersection operation
+ * @param b second argument to the intersection operation
+ */
+ public void intersection(final AbstractRegionBSPTree<P, N> a, final AbstractRegionBSPTree<P, N> b) {
+ new IntersectionOperator<P, N>().apply(a, b, this);
+ }
+
+ /** Compute the difference of this instance and the given region, storing the result back in
+ * this instance. The argument is not modified.
+ * @param other the tree to compute the difference with
+ */
+ public void difference(final AbstractRegionBSPTree<P, N> other) {
+ new DifferenceOperator<P, N>().apply(this, other, this);
+ }
+
+ /** Compute the difference of the two regions passed as arguments and store the result in
+ * this instance. Any nodes currently existing in this instance are removed.
+ * @param a first argument to the difference operation
+ * @param b second argument to the difference operation
+ */
+ public void difference(final AbstractRegionBSPTree<P, N> a, final AbstractRegionBSPTree<P, N> b) {
+ new DifferenceOperator<P, N>().apply(a, b, this);
+ }
+
+ /** Compute the symmetric difference (xor) of this instance and the given region, storing the result back in
+ * this instance. The argument is not modified.
+ * @param other the tree to compute the symmetric difference with
+ */
+ public void xor(final AbstractRegionBSPTree<P, N> other) {
+ new XorOperator<P, N>().apply(this, other, this);
+ }
+
+ /** Compute the symmetric difference (xor) of the two regions passed as arguments and store the result in
+ * this instance. Any nodes currently existing in this instance are removed.
+ * @param a first argument to the symmetric difference operation
+ * @param b second argument to the symmetric difference operation
+ */
+ public void xor(final AbstractRegionBSPTree<P, N> a, final AbstractRegionBSPTree<P, N> b) {
+ new XorOperator<P, N>().apply(a, b, this);
+ }
+
+ /** Condense this tree by removing redundant subtrees.
+ *
+ * <p>This operation can be used to reduce the total number of nodes in the
+ * tree after performing node manipulations. For example, if two sibling leaf
+ * nodes both represent the same {@link RegionLocation}, then there is no reason
+ * from the perspective of the geometric region to retain both nodes. They are
+ * therefore both merged into their parent node. This method performs this
+ * simplification process.
+ * </p>
+ */
+ protected void condense() {
+ condenseRecursive(getRoot());
+ }
+
+ /** Recursively condense nodes that have children with homogenous location attributes
+ * (eg, both inside, both outside) into single nodes.
+ * @param node the root of the subtree to condense
+ * @return the location of the successfully condensed subtree or null if no condensing was
+ * able to be performed
+ */
+ private RegionLocation condenseRecursive(final N node) {
+ if (node.isLeaf()) {
+ return node.getLocation();
+ }
+
+ final RegionLocation minusLocation = condenseRecursive(node.getMinus());
+ final RegionLocation plusLocation = condenseRecursive(node.getPlus());
+
+ if (minusLocation != null && plusLocation != null && minusLocation == plusLocation) {
+ node.setLocation(minusLocation);
+ node.clearCut();
+
+ return minusLocation;
+ }
+
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void copyNodeProperties(final N src, final N dst) {
+ dst.setLocation(src.getLocationValue());
+ }
+
+ /** Compute the portion of the node's cut subhyperplane that lies on the boundary of
+ * the region.
+ * @param node the node to compute the cut subhyperplane boundary of
+ * @return object representing the portions of the node's cut subhyperplane that lie
+ * on the region's boundary
+ */
+ private RegionCutBoundary<P> computeBoundary(final N node) {
+ if (node.isLeaf()) {
+ // no boundary for leaf nodes; they are either entirely in or
+ // entirely out
+ return null;
+ }
+
+ ConvexSubHyperplane<P> sub = node.getCut();
+
+ // find the portions of the node cut sub-hyperplane that touch inside and
+ // outside cells in the minus sub-tree
+ SubHyperplane.Builder<P> minusInBuilder = sub.builder();
+ SubHyperplane.Builder<P> minusOutBuilder = sub.builder();
+
+ characterizeSubHyperplane(sub, node.getMinus(), minusInBuilder, minusOutBuilder);
+
+ List<? extends ConvexSubHyperplane<P>> minusIn = minusInBuilder.build().toConvex();
+ List<? extends ConvexSubHyperplane<P>> minusOut = minusOutBuilder.build().toConvex();
+
+ // create the result boundary builders
+ SubHyperplane.Builder<P> insideFacing = sub.builder();
+ SubHyperplane.Builder<P> outsideFacing = sub.builder();
+
+ if (!minusIn.isEmpty()) {
+ // Add to the boundary anything that touches an inside cell in the minus sub-tree
+ // and an outside cell in the plus sub-tree. These portions are oriented with their
+ // plus side pointing to the outside of the region.
+ for (ConvexSubHyperplane<P> minusInFragment : minusIn) {
+ characterizeSubHyperplane(minusInFragment, node.getPlus(), null, outsideFacing);
+ }
+ }
+
+ if (!minusOut.isEmpty()) {
+ // Add to the boundary anything that touches an outside cell in the minus sub-tree
+ // and an inside cell in the plus sub-tree. These portions are oriented with their
+ // plus side pointing to the inside of the region.
+ for (ConvexSubHyperplane<P> minusOutFragment : minusOut) {
+ characterizeSubHyperplane(minusOutFragment, node.getPlus(), insideFacing, null);
+ }
+ }
+
+ return new RegionCutBoundary<P>(insideFacing.build(), outsideFacing.build());
+ }
+
+ /** Recursive method to characterize a convex subhyperplane with respect to the region's
+ * boundaries.
+ * @param sub the subhyperplane to characterize
+ * @param node the node to characterize the subhyperplane against
+ * @param in the builder that will receive the portions of the subhyperplane that lie in the inside
+ * of the region; may be null
+ * @param out the builder that will receive the portions of the subhyperplane that lie on the outside
+ * of the region; may be null
+ */
+ private void characterizeSubHyperplane(final ConvexSubHyperplane<P> sub, final AbstractRegionNode<P, N> node,
+ final SubHyperplane.Builder<P> in, final SubHyperplane.Builder<P> out) {
+
+ if (sub != null) {
+ if (node.isLeaf()) {
+ if (node.isInside() && in != null) {
+ in.add(sub);
+ } else if (node.isOutside() && out != null) {
+ out.add(sub);
+ }
+ } else {
+ final Split<? extends ConvexSubHyperplane<P>> split = sub.split(node.getCutHyperplane());
+
+ // Continue further on down the subtree with the same subhyperplane if the
+ // subhyperplane lies directly on the current node's cut
+ if (split.getLocation() == SplitLocation.NEITHER) {
+ characterizeSubHyperplane(sub, node.getPlus(), in, out);
+ characterizeSubHyperplane(sub, node.getMinus(), in, out);
+ } else {
+ characterizeSubHyperplane(split.getPlus(), node.getPlus(), in, out);
+ characterizeSubHyperplane(split.getMinus(), node.getMinus(), in, out);
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void initChildNode(final N parent, final N child, final boolean isPlus) {
+ super.initChildNode(parent, child, isPlus);
+
+ child.setLocation(isPlus ? RegionLocation.OUTSIDE : RegionLocation.INSIDE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void invalidate() {
+ super.invalidate();
+
+ // clear cached region properties
+ boundarySize = UNKNOWN_SIZE;
+ regionSizeProperties = null;
+ }
+
+ /** {@link BSPTree.Node} implementation for use with {@link AbstractRegionBSPTree}s.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ public abstract static class AbstractRegionNode<P extends Point<P>, N extends AbstractRegionNode<P, N>>
+ extends AbstractBSPTree.AbstractNode<P, N> {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 1L;
+
+ /** The location for the node. This will only be set on leaf nodes. */
+ private RegionLocation location;
+
+ /** Object representing the part of the node cut subhyperplane that lies on the
+ * region boundary. This is calculated lazily and is only present on internal nodes.
+ */
+ private RegionCutBoundary<P> cutBoundary;
+
+ /** Simple constructor.
+ * @param tree owning tree instance
+ */
+ protected AbstractRegionNode(AbstractBSPTree<P, N> tree) {
+ super(tree);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AbstractRegionBSPTree<P, N> getTree() {
+ // cast to our parent tree type
+ return (AbstractRegionBSPTree<P, N>) super.getTree();
+ }
+
+ /** Get the location of the node. This value will only be non-null for
+ * leaf nodes.
+ * @return the location of the node; will be null for internal nodes
+ */
+ public RegionLocation getLocation() {
+ return isLeaf() ? location : null;
+ }
+
+ /** True if the node is a leaf node and has a location of {@link RegionLocation#INSIDE}.
+ * @return true if the node is a leaf node and has a location of
+ * {@link RegionLocation#INSIDE}
+ */
+ public boolean isInside() {
+ return getLocation() == RegionLocation.INSIDE;
+ }
+
+ /** True if the node is a leaf node and has a location of {@link RegionLocation#OUTSIDE}.
+ * @return true if the node is a leaf node and has a location of
+ * {@link RegionLocation#OUTSIDE}
+ */
+ public boolean isOutside() {
+ return getLocation() == RegionLocation.OUTSIDE;
+ }
+
+ /** Get the portion of the node's cut subhyperplane that lies on the boundary of the
+ * region.
+ * @return the portion of the node's cut subhyperplane that lies on the boundary of
+ * the region
+ */
+ public RegionCutBoundary<P> getCutBoundary() {
+ if (!isLeaf()) {
+ checkValid();
+
+ if (cutBoundary == null) {
+ cutBoundary = getTree().computeBoundary(getSelf());
+ }
+ }
+
+ return cutBoundary;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(this.getClass().getSimpleName())
+ .append("[cut= ")
+ .append(getCut())
+ .append(", location= ")
+ .append(getLocation())
+ .append("]");
+
+ return sb.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void nodeInvalidated() {
+ super.nodeInvalidated();
+
+ // null any computed boundary value since it is no longer valid
+ cutBoundary = null;
+ }
+
+ /** Set the location attribute for the node.
+ * @param location the location attribute for the node
+ */
+ protected void setLocation(final RegionLocation location) {
+ this.location = location;
+ }
+
+ /** Get the value of the location property, unmodified based on the
+ * node's leaf state.
+ * @return the value of the location property
+ */
+ protected RegionLocation getLocationValue() {
+ return location;
+ }
+ }
+
+ /** 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
+ */
+ protected static class BoundaryProjector<P extends Point<P>, N extends AbstractRegionNode<P, N>>
+ extends ClosestFirstVisitor<P, N> {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190504L;
+
+ /** The projected point. */
+ private P projected;
+
+ /** The current closest distance to the boundary found. */
+ private double minDist = -1.0;
+
+ /** Simple constructor.
+ * @param point the point to project onto the region's boundary
+ */
+ public BoundaryProjector(final P point) {
+ super(point);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void visit(final N node) {
+ final P point = getTarget();
+
+ if (node.isInternal() && (minDist < 0.0 || isPossibleClosestCut(node.getCut(), point, minDist))) {
+ final RegionCutBoundary<P> boundary = node.getCutBoundary();
+ final P boundaryPt = boundary.closest(point);
+
+ final double dist = boundaryPt.distance(point);
+ final int cmp = Double.compare(dist, minDist);
+
+ if (minDist < 0.0 || cmp < 0) {
+ projected = boundaryPt;
+ minDist = dist;
+ } else if (cmp == 0) {
+ // the two points are the _exact_ same distance from the reference point, so use
+ // a separate method to disambiguate them
+ projected = disambiguateClosestPoint(point, projected, boundaryPt);
+ }
+ }
+ }
+
+ /** Return true if the given node cut subhyperplane is a possible candidate for containing the
+ * closest region boundary point to the target.
+ * @param cut the node cut subhyperplane to test
+ * @param target the target point being projected
+ * @param currentMinDist the smallest distance found so far to a region boundary; this value is guaranteed
+ * to be non-negative
+ * @return true if the subhyperplane is a possible candidate for containing the closest region
+ * boundary point to the target
+ */
+ protected boolean isPossibleClosestCut(final SubHyperplane<P> cut, final P target,
+ final double currentMinDist) {
+ return Math.abs(cut.getHyperplane().offset(target)) <= currentMinDist;
+ }
+
+ /** Method used to determine which of points {@code a} and {@code b} should be considered
+ * as the "closest" point to {@code target} when the points are exactly equidistant.
+ * @param target the target point
+ * @param a first point to consider
+ * @param b second point to consider
+ * @return which of {@code a} or {@code b} should be considered as the one closest to
+ * {@code target}
+ */
+ protected P disambiguateClosestPoint(final P target, final P a, final P b) {
+ return a;
+ }
+
+ /** Get the projected point on the region's boundary, or null if no point could be found.
+ * @return the projected point on the region's boundary
+ */
+ public P getProjected() {
+ return projected;
+ }
+ }
+
+ /** Class containing the primary size-related properties of a region. These properties
+ * are typically computed at the same time, so this class serves to encapsulate the result
+ * of the combined computation.
+ * @param <P> Point implementation type
+ */
+ protected static class RegionSizeProperties<P extends Point<P>> implements Serializable {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190428L;
+
+ /** The size of the region. */
+ private final double size;
+
+ /** The barycenter of the region. */
+ private final P barycenter;
+
+ /** Simple constructor.
+ * @param size the region size
+ * @param barycenter the region barycenter
+ */
+ public RegionSizeProperties(final double size, final P barycenter) {
+ this.size = size;
+ this.barycenter = barycenter;
+ }
+
+ /** Get the size of the region.
+ * @return the size of the region
+ */
+ public double getSize() {
+ return size;
+ }
+
+ /** Get the barycenter of the region.
+ * @return the barycenter of the region
+ */
+ public P getBarycenter() {
+ return barycenter;
+ }
+ }
+
+ /** 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<
+ P extends Point<P>,
+ C extends ConvexSubHyperplane<P>,
+ N extends AbstractRegionNode<P, N>>
+ extends IteratorTransform<N, C> {
+
+ /** Function that converts from the convex subhyperplane type to the output type. */
+ private final Function<ConvexSubHyperplane<P>, C> typeConverter;
+
+ /** Simple constructor.
+ * @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,
+ final Function<ConvexSubHyperplane<P>, C> typeConverter) {
+ super(inputIterator);
+
+ this.typeConverter = typeConverter;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void acceptInput(final N input) {
+ if (input.isInternal()) {
+ final RegionCutBoundary<P> cutBoundary = input.getCutBoundary();
+
+ final SubHyperplane<P> outsideFacing = cutBoundary.getOutsideFacing();
+ final SubHyperplane<P> insideFacing = cutBoundary.getInsideFacing();
+
+ if (outsideFacing != null && !outsideFacing.isEmpty()) {
+ for (ConvexSubHyperplane<P> boundary : outsideFacing.toConvex()) {
+
+ addOutput(typeConverter.apply(boundary));
+ }
+ }
+ if (insideFacing != null && !insideFacing.isEmpty()) {
+ for (ConvexSubHyperplane<P> boundary : insideFacing.toConvex()) {
+ ConvexSubHyperplane<P> reversed = boundary.reverse();
+
+ addOutput(typeConverter.apply(reversed));
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AttributeBSPTree.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AttributeBSPTree.java
new file mode 100644
index 0000000..d7fecd6
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AttributeBSPTree.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.partitioning.bsp;
+
+import org.apache.commons.geometry.core.Point;
+
+/** Simple {@link BSPTree} implementation allowing arbitrary values to be
+ * associated with each node.
+ * @param <P> Point implementation type
+ * @param <T> Tree node attribute type
+ */
+public class AttributeBSPTree<P extends Point<P>, T>
+ extends AbstractBSPTree<P, AttributeBSPTree.AttributeNode<P, T>> {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190306L;
+
+ /** The initial attribute value to use for newly created nodes. */
+ private final T initialNodeAttribute;
+
+ /** Create a new tree instance. New nodes in the tree are given an attribute
+ * of null.
+ */
+ public AttributeBSPTree() {
+ this(null);
+ }
+
+ /** Create a new tree instance. New nodes in the tree are assigned the given
+ * initial attribute value.
+ * @param initialNodeAttribute The attribute value to assign to newly created nodes.
+ */
+ public AttributeBSPTree(T initialNodeAttribute) {
+ this.initialNodeAttribute = initialNodeAttribute;
+
+ this.getRoot().setAttribute(initialNodeAttribute);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected AttributeNode<P, T> createNode() {
+ return new AttributeNode<P, T>(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void initChildNode(final AttributeNode<P, T> parent, final AttributeNode<P, T> child,
+ final boolean isPlus) {
+ super.initChildNode(parent, child, isPlus);
+
+ child.setAttribute(initialNodeAttribute);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void copyNodeProperties(final AttributeNode<P, T> src, final AttributeNode<P, T> dst) {
+ dst.setAttribute(src.getAttribute());
+ }
+
+ /** {@link BSPTree.Node} implementation for use with {@link AttributeBSPTree}s.
+ * @param <P> Point implementation type
+ * @param <T> Tree node attribute type
+ */
+ public static class AttributeNode<P extends Point<P>, T>
+ extends AbstractBSPTree.AbstractNode<P, AttributeNode<P, T>> {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 1L;
+
+ /** The node attribute. */
+ private T attribute;
+
+ /** Simple constructor.
+ * @param tree the owning tree; this must be an instance of {@link AttributeBSPTree}
+ */
+ protected AttributeNode(AbstractBSPTree<P, AttributeNode<P, T>> tree) {
+ super(tree);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public AttributeBSPTree<P, T> getTree() {
+ // cast to our parent tree type
+ return (AttributeBSPTree<P, T>) super.getTree();
+ }
+
+ /** Get the attribute associated with this node.
+ * @return the attribute associated with this node
+ */
+ public T getAttribute() {
+ return attribute;
+ }
+
+ /** Set the attribute associated with this node.
+ * @param attribute the attribute to associate with this node
+ */
+ public void setAttribute(T attribute) {
+ this.attribute = attribute;
+ }
+
+ /** Set the attribute for this node. The node is returned.
+ * @param attributeValue attribute to set for the node
+ * @return the node instance
+ */
+ public AttributeNode<P, T> attr(final T attributeValue) {
+ setAttribute(attributeValue);
+
+ return this;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(this.getClass().getSimpleName())
+ .append("[cut= ")
+ .append(getCut())
+ .append(", attribute= ")
+ .append(attribute)
+ .append("]");
+
+ return sb.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected AttributeNode<P, T> getSelf() {
+ return this;
+ }
+ }
+}
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
new file mode 100644
index 0000000..5943765
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPSubtree.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.partitioning.bsp;
+
+import java.util.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
+ * themselves as well as each node in a tree.
+ * @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> {
+
+ /** Return the total number of nodes in the subtree.
+ * @return the total number of nodes in the subtree.
+ */
+ int count();
+
+ /** The height of the subtree, ie the length of the longest downward path from
+ * the subtree root to a leaf node. A leaf node has a height of 0.
+ * @return the height of the subtree.
+ */
+ int height();
+
+ /** Accept a visitor instance, calling it with each node from the subtree.
+ * @param visitor visitor called with each subtree node
+ */
+ 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
+ */
+ default Stream<N> stream() {
+ return StreamSupport.stream(spliterator(), false);
+ }
+}
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
new file mode 100644
index 0000000..758ca90
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java
@@ -0,0 +1,225 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.partitioning.bsp;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+
+/** Interface for Binary Space Partitioning (BSP) trees.
+ * @param <P> Point implementation type
+ * @param <N> Node implementation type
+ */
+public interface BSPTree<P extends Point<P>, N extends BSPTree.Node<P, N>>
+ extends BSPSubtree<P, N> {
+
+ /** Enum specifying possible behaviors when a point used to locate a node
+ * falls directly on the cut subhyperplane of an internal node.
+ */
+ enum NodeCutRule {
+
+ /** Choose the minus child of the internal node and continue searching.
+ * This behavior will result in a leaf node always being returned by the
+ * node search.
+ */
+ MINUS,
+
+ /** Choose the plus child of the internal node and continue searching.
+ * This behavior will result in a leaf node always being returned by the
+ * node search.
+ */
+ PLUS,
+
+ /** Choose the internal node and stop searching. This behavior may result
+ * in non-leaf nodes being returned by the node search.
+ */
+ NODE
+ }
+
+ /** Get the root node of the tree.
+ * @return the root node of the tree
+ */
+ N getRoot();
+
+ /** Find a node in this subtree containing the given point or its interior or boundary.
+ * When a point lies directly on the cut of an internal node, the minus child of the
+ * cut is chosen. This is equivalent to {@code subtree.findNode(pt, NodeCutRule.MINUS)}
+ * and always returns a leaf node.
+ * @param pt test point used to locate a node in the tree
+ * @return leaf node containing the point on its interior or boundary
+ * @see #findNode(Point, NodeCutRule)
+ */
+ default N findNode(P pt) {
+ return findNode(pt, NodeCutRule.MINUS);
+ }
+
+ /** Find a node in this subtree containing the given point on it interior or boundary. The
+ * search should always return a leaf node except in the cases where the given point lies
+ * exactly on the cut subhyperplane of an internal node. In this case, it is unclear whether
+ * the search should continue with the minus child, the plus child, or end with the internal
+ * node. The {@code cutRule} argument specifies what should happen in this case.
+ * <ul>
+ * <li>{@link NodeCutRule#MINUS} - continue the search in the minus subtree</li>
+ * <li>{@link NodeCutRule#PLUS} - continue the search in the plus subtree</li>
+ * <li>{@link NodeCutRule#NODE} - stop the search and return the internal node</li>
+ * </ul>
+ * @param pt test point used to locate a node in the tree
+ * @param cutRule value used to determine the search behavior when the test point lies
+ * exactly on the cut subhyperplane of an internal node
+ * @return node containing the point on its interior or boundary
+ * @see #findNode(Point)
+ */
+ N findNode(P pt, NodeCutRule cutRule);
+
+ /** Insert a subhyperplane into the tree.
+ * @param sub the subhyperplane to insert into the tree
+ */
+ void insert(SubHyperplane<P> sub);
+
+ /** Insert a convex subhyperplane into the tree.
+ * @param convexSub the convex subhyperplane to insert into the tree
+ */
+ void insert(ConvexSubHyperplane<P> convexSub);
+
+ /** Insert a set of convex subhyperplanes into the tree.
+ * @param convexSubs iterable containing a collection of subhyperplanes
+ * to insert into the tree
+ */
+ void insert(Iterable<? extends ConvexSubHyperplane<P>> convexSubs);
+
+ /** Make the current instance a deep copy of the argument.
+ * @param src the tree to copy
+ */
+ void copy(BSPTree<P, N> src);
+
+ /** Set this instance to the region represented by the given node. The
+ * given node could have come from this tree or a different tree.
+ * @param node the node to extract
+ */
+ void extract(N node);
+
+ /** Transform this tree. Each cut subhyperplane in the tree is transformed in place using
+ * the given {@link Transform}.
+ * @param transform the transform to apply
+ */
+ void transform(Transform<P> transform);
+
+ /** Interface for Binary Space Partitioning (BSP) tree nodes.
+ * @param <P> Point type
+ * @param <N> BSP tree node implementation type
+ */
+ interface Node<P extends Point<P>, N extends Node<P, N>> extends BSPSubtree<P, N> {
+
+ /** Get the {@link BSPTree} that owns the node.
+ * @return the owning tree
+ */
+ BSPTree<P, N> getTree();
+
+ /** Get the depth of the node in the tree. The root node of the tree
+ * has a depth of 0.
+ * @return the depth of the node in the tree
+ */
+ int depth();
+
+ /** Get the parent of the node. This will be null if the node is the
+ * root of the tree.
+ * @return the parent node for this instance
+ */
+ N getParent();
+
+ /** Get the cut for the node. This is a convex subhyperplane that splits
+ * the region for the cell into two disjoint regions, namely the plus and
+ * minus regions. This will be null for leaf nodes.
+ * @see #getPlus()
+ * @see #getMinus()
+ * @return the cut subhyperplane for the cell
+ */
+ ConvexSubHyperplane<P> getCut();
+
+ /** Get the hyperplane belonging to the node cut, if it exists.
+ * @return the hyperplane belonging to the node cut, or null if
+ * the node does not have a cut
+ * @see #getCut()
+ */
+ Hyperplane<P> getCutHyperplane();
+
+ /** Get the node for the minus region of the cell. This will be null if the
+ * node has not been cut, ie if it is a leaf node.
+ * @return the node for the minus region of the cell
+ */
+ N getMinus();
+
+ /** Get the node for the plus region of the cell. This will be null if the
+ * node has not been cut, ie if it is a leaf node.
+ * @return the node for the plus region of the cell
+ */
+ N getPlus();
+
+ /** Return true if the node is a leaf node, meaning that it has no
+ * binary partitioner (aka "cut") and therefore no child nodes.
+ * @return true if the node is a leaf node
+ */
+ boolean isLeaf();
+
+ /** Return true if the node is an internal node, meaning that is
+ * has a binary partitioner (aka "cut") and therefore two child nodes.
+ * @return true if the node is an internal node
+ */
+ boolean isInternal();
+
+ /** Return true if the node has a parent and is the parent's minus
+ * child.
+ * @return true if the node is the minus child of its parent
+ */
+ boolean isMinus();
+
+ /** Return true if the node has a parent and is the parent's plus
+ * child.
+ * @return true if the node is the plus child of its parent
+ */
+ boolean isPlus();
+
+ /** Insert a cut into this node. If the given hyperplane intersects
+ * this node's region, then the node's cut is set to the {@link ConvexSubHyperplane}
+ * representing the intersection, new plus and minus child leaf nodes
+ * are assigned, and true is returned. If the hyperplane does not intersect
+ * the node's region, then the node's cut and plus and minus child references
+ * are all set to null (ie, it becomes a leaf node) and false is returned. In
+ * either case, any existing cut and/or child nodes are removed by this method.
+ * @param cutter the hyperplane to cut the node's region with
+ * @return true if the cutting hyperplane intersected the node's region, resulting
+ * in the creation of new child nodes
+ */
+ boolean insertCut(Hyperplane<P> cutter);
+
+ /** Remove the cut from this node. Returns true if the node previously had a cut.
+ * @return true if the node had a cut before the call to this method
+ */
+ boolean clearCut();
+
+ /** Cut this node with the given hyperplane. The same node is returned, regardless of
+ * the outcome of the cut operation. If the operation succeeded, then the node will
+ * have plus and minus child nodes.
+ * @param cutter the hyperplane to cut the node's region with
+ * @return this node
+ * @see #insertCut(Hyperplane)
+ */
+ N cut(Hyperplane<P> cutter);
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTreePrinter.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTreePrinter.java
new file mode 100644
index 0000000..b5df252
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTreePrinter.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.partitioning.bsp;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.partitioning.bsp.BSPTree.Node;
+
+/** Internal class for creating simple string representations of BSP trees.
+ * @param <P> Point implementation type
+ * @param <N> Node implementation type
+ */
+final class BSPTreePrinter<P extends Point<P>, N extends Node<P, N>>
+ implements BSPTreeVisitor<P, N> {
+
+ /** Line indent string. */
+ private static final String INDENT = " ";
+
+ /** New line character. */
+ private static final String NEW_LINE = "\n";
+
+ /** Entry prefix for nodes on the minus side of their parent. */
+ private static final String MINUS_CHILD = "[-] ";
+
+ /** Entry prefix for nodes on the plus side of their parent. */
+ private static final String PLUS_CHILD = "[+] ";
+
+ /** Ellipsis for truncated representations. */
+ private static final String ELLIPSIS = "...";
+
+ /** Maximum depth of nodes that will be printed. */
+ private final int maxDepth;
+
+ /** Contains the string output. */
+ private final StringBuilder output = new StringBuilder();
+
+ /** Simple constructor.
+ * @param maxDepth maximum depth of nodes to be printed
+ */
+ BSPTreePrinter(final int maxDepth) {
+ this.maxDepth = maxDepth;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void visit(final N node) {
+ final int depth = node.depth();
+
+ if (depth <= maxDepth) {
+ startLine(node);
+ writeNode(node);
+ } else if (depth == maxDepth + 1 && node.isPlus()) {
+ startLine(node);
+ write(ELLIPSIS);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Order visitOrder(final N node) {
+ return Order.NODE_MINUS_PLUS;
+ }
+
+ /** Start a line for the given node.
+ * @param node the node to begin a line for
+ */
+ private void startLine(final N node) {
+ if (node.getParent() != null) {
+ write(NEW_LINE);
+ }
+
+ final int depth = node.depth();
+ for (int i = 0; i < depth; ++i) {
+ write(INDENT);
+ }
+ }
+
+ /** Writes the given node to the output.
+ * @param node the node to write
+ */
+ private void writeNode(final N node) {
+ if (node.getParent() != null) {
+ if (node.isMinus()) {
+ write(MINUS_CHILD);
+ } else {
+ write(PLUS_CHILD);
+ }
+ }
+
+ write(node.toString());
+ }
+
+ /** Add the given string to the output.
+ * @param str the string to add
+ */
+ private void write(String str) {
+ output.append(str);
+ }
+
+ /** Return the string representation of the visited tree. */
+ @Override
+ public String toString() {
+ return output.toString();
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTreeVisitor.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTreeVisitor.java
new file mode 100644
index 0000000..8fc0ffd
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTreeVisitor.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.partitioning.bsp;
+
+import java.io.Serializable;
+
+import org.apache.commons.geometry.core.Point;
+
+/** Interface for visiting the nodes in a {@link BSPTree} or {@link BSPSubtree}.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+@FunctionalInterface
+public interface BSPTreeVisitor<P extends Point<P>, N extends BSPTree.Node<P, N>> {
+
+ /** Enum used to specify the order in which visitors should visit the nodes
+ * in the tree.
+ */
+ enum Order {
+
+ /** Indicates that the visitor should first visit the plus sub-tree, then
+ * the minus sub-tree and then the current node.
+ */
+ PLUS_MINUS_NODE,
+
+ /** Indicates that the visitor should first visit the plus sub-tree, then
+ * the current node, and then the minus sub-tree.
+ */
+ PLUS_NODE_MINUS,
+
+ /** Indicates that the visitor should first visit the minus sub-tree, then
+ * the plus sub-tree, and then the current node.
+ */
+ MINUS_PLUS_NODE,
+
+ /** Indicates that the visitor should first visit the minus sub-tree, then the
+ * current node, and then the plus sub-tree.
+ */
+ MINUS_NODE_PLUS,
+
+ /** Indicates that the visitor should first visit the current node, then the
+ * plus sub-tree, and then the minus sub-tree.
+ */
+ NODE_PLUS_MINUS,
+
+ /** Indicates that the visitor should first visit the current node, then the
+ * minus sub-tree, and then the plus sub-tree.
+ */
+ NODE_MINUS_PLUS;
+ }
+
+ /** Visit a node in a BSP tree. This method is called for both internal nodes and
+ * leaf nodes.
+ * @param node the node being visited
+ */
+ void visit(N node);
+
+ /** Determine the visit order for the given internal node. This is called for each
+ * internal node before {@link #visit(BSPTree.Node)} is called. Returning null from
+ * this method skips the subtree rooted at the given node. This method is not called
+ * on leaf nodes.
+ * @param internalNode the internal node to determine the visit order for
+ * @return the order that the subtree rooted at the given node should be visited
+ */
+ default Order visitOrder(final N internalNode) {
+ return Order.NODE_MINUS_PLUS;
+ }
+
+ /** Abstract class for {@link BSPTreeVisitor} implementations that base their visit
+ * ordering on a target point.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ abstract class TargetPointVisitor<P extends Point<P>, N extends BSPTree.Node<P, N>>
+ implements BSPTreeVisitor<P, N>, Serializable {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190504L;
+
+ /** Point serving as the target of the traversal. */
+ private final P target;
+
+ /** Simple constructor.
+ * @param target the point serving as the target for the tree traversal
+ */
+ public TargetPointVisitor(final P target) {
+ this.target = target;
+ }
+
+ /** Get the target point for the tree traversal.
+ * @return the target point for the tree traversal
+ */
+ public P getTarget() {
+ return target;
+ }
+ }
+
+ /** {@link BSPTreeVisitor} base class that orders tree nodes so that nodes closest to the target point are
+ * visited first. This is done by choosing {@link Order#MINUS_NODE_PLUS}
+ * when the target point lies on the minus side of the node's cut hyperplane and {@link Order#PLUS_NODE_MINUS}
+ * when it lies on the plus side. The order {@link Order#MINUS_NODE_PLUS} order is used when
+ * the target point lies directly on the node's cut hyerplane and no child node is closer than the other.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ abstract class ClosestFirstVisitor<P extends Point<P>, N extends BSPTree.Node<P, N>>
+ extends TargetPointVisitor<P, N> {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190504L;
+
+ /** Simple constructor.
+ * @param target the point serving as the target for the traversal
+ */
+ public ClosestFirstVisitor(final P target) {
+ super(target);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Order visitOrder(N node) {
+ if (node.getCutHyperplane().offset(getTarget()) > 0.0) {
+ return Order.PLUS_NODE_MINUS;
+ }
+ return Order.MINUS_NODE_PLUS;
+ }
+ }
+
+ /** {@link BSPTreeVisitor} base class that orders tree nodes so that nodes farthest from the target point
+ * are traversed first. This is done by choosing {@link Order#PLUS_NODE_MINUS}
+ * when the target point lies on the minus side of the node's cut hyperplane and {@link Order#MINUS_NODE_PLUS}
+ * when it lies on the plus side. The order {@link Order#MINUS_NODE_PLUS} order is used when
+ * the target point lies directly on the node's cut hyerplane and no child node is closer than the other.
+ * @param <P> Point implementation type
+ * @param <N> BSP tree node implementation type
+ */
+ abstract class FarthestFirstVisitor<P extends Point<P>, N extends BSPTree.Node<P, N>>
+ extends TargetPointVisitor<P, N> {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190504L;
+
+ /** Simple constructor.
+ * @param target the point serving as the target for the traversal
+ */
+ public FarthestFirstVisitor(final P target) {
+ super(target);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Order visitOrder(N node) {
+ if (node.getCutHyperplane().offset(getTarget()) < 0.0) {
+ return Order.PLUS_NODE_MINUS;
+ }
+ return Order.MINUS_NODE_PLUS;
+ }
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/RegionCutBoundary.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/RegionCutBoundary.java
new file mode 100644
index 0000000..a165381
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/RegionCutBoundary.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.partitioning.bsp;
+
+import java.io.Serializable;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+
+/** Class representing the portion of an
+ * {@link AbstractRegionBSPTree.AbstractRegionNode AbstractRegionNode}'s cut subhyperplane that
+ * lies on the boundary of the region. Portions of this subhyperplane
+ * may be oriented so that the plus side of the subhyperplane points toward
+ * the outside of the region ({@link #getOutsideFacing()}) and other portions
+ * of the same subhyperplane may be oriented so that the plus side points
+ * toward the inside of the region ({@link #getInsideFacing()}).
+ *
+ * @param <P> Point implementation type
+ */
+public final class RegionCutBoundary<P extends Point<P>> implements Serializable {
+
+ /** Serializable UID. */
+ private static final long serialVersionUID = 20190310L;
+
+ /** Portion of the region cut subhyperplane with its plus side facing the
+ * inside of the region.
+ */
+ private final SubHyperplane<P> insideFacing;
+
+ /** Portion of the region cut subhyperplane with its plus side facing the
+ * outside of the region.
+ */
+ private final SubHyperplane<P> outsideFacing;
+
+ /** Simple constructor.
+ * @param insideFacing portion of the region cut subhyperplane with its plus side facing the
+ * inside of the region
+ * @param outsideFacing portion of the region cut subhyperplane with its plus side facing the
+ * outside of the region
+ */
+ public RegionCutBoundary(final SubHyperplane<P> insideFacing, final SubHyperplane<P> outsideFacing) {
+ this.insideFacing = insideFacing;
+ this.outsideFacing = outsideFacing;
+ }
+
+ /** Get the portion of the region cut subhyperplane with its plus side facing the
+ * inside of the region.
+ * @return the portion of the region cut subhyperplane with its plus side facing the
+ * inside of the region
+ */
+ public SubHyperplane<P> getInsideFacing() {
+ return insideFacing;
+ }
+
+ /** Get the portion of the region cut subhyperplane with its plus side facing the
+ * outside of the region.
+ * @return the portion of the region cut subhyperplane with its plus side facing the
+ * outside of the region
+ */
+ public SubHyperplane<P> getOutsideFacing() {
+ return outsideFacing;
+ }
+
+ /** Return the closest point to the argument in the inside and outside facing
+ * portions of the cut boundary.
+ * @param pt the reference point
+ * @return the point in the cut boundary closest to the reference point
+ * @see SubHyperplane#closest(Point)
+ */
+ public P closest(final P pt) {
+ final P insideFacingPt = (insideFacing != null) ? insideFacing.closest(pt) : null;
+ final P outsideFacingPt = (outsideFacing != null) ? outsideFacing.closest(pt) : null;
+
+ if (insideFacingPt != null && outsideFacingPt != null) {
+ if (pt.distance(insideFacingPt) < pt.distance(outsideFacingPt)) {
+ return insideFacingPt;
+ }
+ return outsideFacingPt;
+ } else if (insideFacingPt != null) {
+ return insideFacingPt;
+ }
+ return outsideFacingPt;
+ }
+
+ /** Return true if the given point is contained in the boundary, in either the
+ * inside facing portion or the outside facing portion.
+ * @param pt point to test
+ * @return true if the point is contained in the boundary
+ * @see SubHyperplane#contains(Point)
+ */
+ public boolean contains(final P pt) {
+ return (insideFacing != null && insideFacing.contains(pt)) ||
+ (outsideFacing != null && outsideFacing.contains(pt));
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/package-info.java
similarity index 63%
rename from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
rename to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/package-info.java
index 046defe..b94442a 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Side.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/package-info.java
@@ -14,23 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.commons.geometry.core.partitioning;
-
-/** Enumerate representing the location of an element with respect to an
- * {@link Hyperplane hyperplane} of a space.
+/**
+ *
+ * <p>
+ * This package contains classes related to Binary Space Partitioning (BSP) trees. BSP
+ * tree are data structures that allow arbitrary partitioning of spaces using hyperplanes.
+ * </p>
*/
-public enum Side {
-
- /** Code for the plus side of the hyperplane. */
- PLUS,
-
- /** Code for the minus side of the hyperplane. */
- MINUS,
-
- /** Code for elements crossing the hyperplane from plus to minus side. */
- BOTH,
-
- /** Code for the hyperplane itself. */
- HYPER;
-
-}
+package org.apache.commons.geometry.core.partitioning.bsp;
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/package-info.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/package-info.java
index cfb55c0..b0e4967 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/package-info.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/package-info.java
@@ -16,99 +16,8 @@
*/
/**
*
- * This package provides classes to implement Binary Space Partition trees.
- *
- * <p>
- * {@link org.apache.commons.geometry.core.partitioning.BSPTree BSP trees}
- * are an efficient way to represent parts of space and in particular
- * polytopes (line segments in 1D, polygons in 2D and polyhedrons in 3D)
- * and to operate on them. The main principle is to recursively subdivide
- * the space using simple hyperplanes (points in 1D, lines in 2D, planes
- * in 3D).
- * </p>
- *
- * <p>
- * We start with a tree composed of a single node without any cut
- * hyperplane: it represents the complete space, which is a convex
- * part. If we add a cut hyperplane to this node, this represents a
- * partition with the hyperplane at the node level and two half spaces at
- * each side of the cut hyperplane. These half-spaces are represented by
- * two child nodes without any cut hyperplanes associated, the plus child
- * which represents the half space on the plus side of the cut hyperplane
- * and the minus child on the other side. Continuing the subdivisions, we
- * end up with a tree having internal nodes that are associated with a
- * cut hyperplane and leaf nodes without any hyperplane which correspond
- * to convex parts.
- * </p>
- *
- * <p>
- * When BSP trees are used to represent polytopes, the convex parts are
- * known to be completely inside or outside the polytope as long as there
- * is no facet in the part (which is obviously the case if the cut
- * hyperplanes have been chosen as the underlying hyperplanes of the
- * facets (this is called an autopartition) and if the subdivision
- * process has been continued until all facets have been processed. It is
- * important to note that the polytope is <em>not</em> defined by a
- * single part, but by several convex ones. This is the property that
- * allows BSP-trees to represent non-convex polytopes despites all parts
- * are convex. The {@link
- * org.apache.commons.geometry.core.partitioning.Region Region} class is
- * devoted to this representation, it is build on top of the {@link
- * org.apache.commons.geometry.core.partitioning.BSPTree BSPTree} class using
- * boolean objects as the leaf nodes attributes to represent the
- * inside/outside property of each leaf part, and also adds various
- * methods dealing with boundaries (i.e. the separation between the
- * inside and the outside parts).
- * </p>
- *
* <p>
- * Rather than simply associating the internal nodes with an hyperplane,
- * we consider <em>sub-hyperplanes</em> which correspond to the part of
- * the hyperplane that is inside the convex part defined by all the
- * parent nodes (this implies that the sub-hyperplane at root node is in
- * fact a complete hyperplane, because there is no parent to bound
- * it). Since the parts are convex, the sub-hyperplanes are convex, in
- * 3D the convex parts are convex polyhedrons, and the sub-hyperplanes
- * are convex polygons that cut these polyhedrons in two
- * sub-polyhedrons. Using this definition, a BSP tree completely
- * partitions the space. Each point either belongs to one of the
- * sub-hyperplanes in an internal node or belongs to one of the leaf
- * convex parts.
+ * This package contains code related to partitioning of spaces by hyperplanes.
* </p>
- *
- * <p>
- * In order to determine where a point is, it is sufficient to check its
- * position with respect to the root cut hyperplane, to select the
- * corresponding child tree and to repeat the procedure recursively,
- * until either the point appears to be exactly on one of the hyperplanes
- * in the middle of the tree or to be in one of the leaf parts. For
- * this operation, it is sufficient to consider the complete hyperplanes,
- * there is no need to check the points with the boundary of the
- * sub-hyperplanes, because this check has in fact already been realized
- * by the recursive descent in the tree. This is very easy to do and very
- * efficient, especially if the tree is well balanced (the cost is
- * <code>O(log(n))</code> where <code>n</code> is the number of facets)
- * or if the first tree levels close to the root discriminate large parts
- * of the total space.
- * </p>
- *
- * <p>
- * One of the main sources for the development of this package was Bruce
- * Naylor, John Amanatides and William Thibault paper <a
- * href="http://www.cs.yorku.ca/~amana/research/bsptSetOp.pdf">Merging
- * BSP Trees Yields Polyhedral Set Operations</a> Proc. Siggraph '90,
- * Computer Graphics 24(4), August 1990, pp 115-124, published by the
- * Association for Computing Machinery (ACM). The same paper can also be
- * found <a
- * href="http://www.cs.utexas.edu/users/fussell/courses/cs384g/bsp_treemerge.pdf">here</a>.
- * </p>
- *
- * <p>
- * Note that the interfaces defined in this package are <em>not</em> intended to
- * be implemented by Apache Commons Math users, they are only intended to be
- * implemented within the library itself. New methods may be added even for
- * minor versions, which breaks compatibility for external implementations.
- * </p>
- *
*/
package org.apache.commons.geometry.core.partitioning;
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/precision/DoublePrecisionContext.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/precision/DoublePrecisionContext.java
index 6773837..7ca33ab 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/precision/DoublePrecisionContext.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/precision/DoublePrecisionContext.java
@@ -23,7 +23,7 @@ import java.util.Comparator;
*/
public abstract class DoublePrecisionContext implements Comparator<Double>, Serializable {
- /** Serializable identifier */
+ /** Serializable identifier. */
private static final long serialVersionUID = 20190121L;
/** Return true if the given values are considered equal to each other.
@@ -85,6 +85,22 @@ public abstract class DoublePrecisionContext implements Comparator<Double>, Seri
return compare(a, b) >= 0;
}
+ /** Return the sign of the argument: 0 if the value is considered equal to
+ * zero, -1 if less than 0, and +1 if greater than 0.
+ * @param a number to determine the sign of
+ * @return 0 if the number is considered equal to 0, -1 if less than
+ * 0, and +1 if greater than 0
+ */
+ public int sign(final double a) {
+ final int cmp = compare(a, 0.0);
+ if (cmp < 0) {
+ return -1;
+ } else if (cmp > 0) {
+ return 1;
+ }
+ return 0;
+ }
+
/** {@inheritDoc} */
@Override
public int compare(final Double a, final Double b) {
@@ -110,7 +126,7 @@ public abstract class DoublePrecisionContext implements Comparator<Double>, Seri
* first is smaller than the second, {@code 1} is the first is larger
* than the second or either value is NaN.
*/
- public abstract int compare(final double a, final double b);
+ public abstract int compare(double a, double b);
/** Get the largest positive double value that is still considered equal
* to zero by this instance.
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/precision/EpsilonDoublePrecisionContext.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/precision/EpsilonDoublePrecisionContext.java
index c26f502..0d69c8b 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/precision/EpsilonDoublePrecisionContext.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/precision/EpsilonDoublePrecisionContext.java
@@ -30,7 +30,7 @@ import org.apache.commons.numbers.core.Precision;
*/
public class EpsilonDoublePrecisionContext extends DoublePrecisionContext {
- /** Serializable identifier */
+ /** Serializable identifier. */
private static final long serialVersionUID = 20190119L;
/** Epsilon value. */
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/EmbeddingTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/EmbeddingTest.java
new file mode 100644
index 0000000..f15fcc8
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/EmbeddingTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+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.TestPoint1D;
+import org.apache.commons.geometry.core.partition.test.TestPoint2D;
+import org.junit.Test;
+
+import org.junit.Assert;
+
+public class EmbeddingTest {
+
+ @Test
+ public void testToSubspace_collection_emptyInput() {
+ // arrange
+ TestLine line = TestLine.Y_AXIS;
+
+ // act
+ List<TestPoint1D> result = line.toSubspace(new ArrayList<>());
+
+ // assert
+ Assert.assertEquals(0, result.size());
+ }
+
+ @Test
+ public void testToSubspace_collection() {
+ // arrange
+ List<TestPoint2D> pts = Arrays.asList(
+ new TestPoint2D(0, 0),
+ new TestPoint2D(1, 0.25),
+ new TestPoint2D(0.5, 1)
+ );
+
+ TestLine line = TestLine.Y_AXIS;
+
+ // act
+ List<TestPoint1D> result = line.toSubspace(pts);
+
+ // assert
+ Assert.assertEquals(3, result.size());
+ Assert.assertEquals(0, result.get(0).getX(), PartitionTestUtils.EPS);
+ Assert.assertEquals(0.25, result.get(1).getX(), PartitionTestUtils.EPS);
+ Assert.assertEquals(1, result.get(2).getX(), PartitionTestUtils.EPS);
+ }
+
+ @Test
+ public void testToSpace_collection_emptyInput() {
+ // arrange
+ TestLine line = TestLine.Y_AXIS;
+
+ // act
+ List<TestPoint2D> result = line.toSpace(new ArrayList<>());
+
+ // assert
+ Assert.assertEquals(0, result.size());
+ }
+
+ @Test
+ public void testToSpace_collection() {
+ // arrange
+ List<TestPoint1D> pts = Arrays.asList(
+ new TestPoint1D(0),
+ new TestPoint1D(1),
+ new TestPoint1D(0.5)
+ );
+
+ TestLine line = TestLine.Y_AXIS;
+
+ // act
+ List<TestPoint2D> result = line.toSpace(pts);
+
+ // assert
+ Assert.assertEquals(3, result.size());
+ PartitionTestUtils.assertPointsEqual(new TestPoint2D(0, 0), result.get(0));
+ PartitionTestUtils.assertPointsEqual(new TestPoint2D(0, 1), result.get(1));
+ PartitionTestUtils.assertPointsEqual(new TestPoint2D(0, 0.5), result.get(2));
+ }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/GeometryTestUtils.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/GeometryTestUtils.java
index 60bdeea..242e2d9 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/GeometryTestUtils.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/GeometryTestUtils.java
@@ -20,6 +20,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
+import java.util.regex.Pattern;
import org.junit.Assert;
@@ -50,7 +51,7 @@ public class GeometryTestUtils {
* @param exceptionType the expected exception type
*/
public static void assertThrows(Runnable r, Class<?> exceptionType) {
- assertThrows(r, exceptionType, null);
+ assertThrows(r, exceptionType, (String) null);
}
/** Asserts that the given Runnable throws an exception of the given type. If
@@ -77,6 +78,42 @@ public class GeometryTestUtils {
}
}
+ /** Asserts that the given Runnable throws an exception of the given type. If
+ * {@code pattern} is not null, the exception message is asserted to match the
+ * given regex.
+ * @param r the Runnable instance
+ * @param exceptionType the expected exception type
+ * @param pattern regex pattern to match; ignored if null
+ */
+ public static void assertThrows(Runnable r, Class<?> exceptionType, Pattern pattern) {
+ try {
+ r.run();
+ Assert.fail("Operation should have thrown an exception");
+ }
+ catch (Exception exc) {
+ Class<?> actualType = exc.getClass();
+
+ Assert.assertTrue("Expected exception of type " + exceptionType.getName() + " but was " + actualType.getName(),
+ exceptionType.isAssignableFrom(actualType));
+
+ if (pattern != null) {
+ String message = exc.getMessage();
+
+ String err = "Expected exception message to match /" + pattern + "/ but was [" + message + "]";
+ Assert.assertTrue(err, pattern.matcher(message).matches());
+ }
+ }
+ }
+
+ /** Assert that a string contains a given substring value.
+ * @param substr
+ * @param actual
+ */
+ public static void assertContains(String substr, String actual) {
+ String msg = "Expected string to contain [" + substr + "] but was [" + actual + "]";
+ Assert.assertTrue(msg, actual.contains(substr));
+ }
+
/**
* Serializes and then recovers an object from a byte array. Returns the deserialized object.
*
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/IteratorTransformTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/IteratorTransformTest.java
new file mode 100644
index 0000000..8eaab17
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/IteratorTransformTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class IteratorTransformTest {
+
+ @Test
+ public void testIteration() {
+ // arrange
+ List<Integer> input = Arrays.asList(1, 2, 3, 4, 12, 13);
+
+ // act
+ List<String> result = toList(new EvenCharIterator(input.iterator()));
+
+ // assert
+ Assert.assertEquals(Arrays.asList("2", "4", "1", "2"), result);
+ }
+
+ @Test(expected = NoSuchElementException.class)
+ public void testThrowsNoSuchElement() {
+ // arrange
+ List<Integer> input = Arrays.asList();
+ EvenCharIterator it = new EvenCharIterator(input.iterator());
+
+ // act/assert
+ Assert.assertFalse(it.hasNext());
+ it.next();
+ }
+
+ private static <T> List<T> toList(Iterator<T> it) {
+ List<T> result = new ArrayList<>();
+ while (it.hasNext()) {
+ result.add(it.next());
+ }
+
+ return result;
+ }
+
+ private static class EvenCharIterator extends IteratorTransform<Integer, String>{
+
+ public EvenCharIterator(final Iterator<Integer> inputIterator) {
+ super(inputIterator);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void acceptInput(Integer input) {
+ // filter out odd integers
+ int value = input.intValue();
+ if (value % 2 == 0) {
+ char[] chars = (value + "").toCharArray();
+
+ if (chars.length > 1) {
+ List<String> strs = new ArrayList<>();
+ for (char ch : chars) {
+ strs.add(ch + "");
+ }
+
+ addAllOutput(strs);
+ }
+ else if (chars.length == 1) {
+ addOutput(chars[0] + "");
+ }
+ }
+ }
+ }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/PartitionTestUtils.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/PartitionTestUtils.java
new file mode 100644
index 0000000..107e5a7
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/PartitionTestUtils.java
@@ -0,0 +1,115 @@
+/*
+ * 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.partition.test;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.partitioning.bsp.BSPTree;
+import org.apache.commons.geometry.core.partitioning.bsp.BSPTree.Node;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.junit.Assert;
+
+/** Class containing utility methods for tests related to the
+ * partition package.
+ */
+public class PartitionTestUtils {
+
+ public static final double EPS = 1e-6;
+
+ public static final DoublePrecisionContext PRECISION =
+ new EpsilonDoublePrecisionContext(EPS);
+
+ /**
+ * Asserts that corresponding values in the given points are equal.
+ * @param expected
+ * @param actual
+ */
+ public static void assertPointsEqual(TestPoint2D expected, TestPoint2D actual) {
+ String msg = "Expected points to equal " + expected + " but was " + actual + ";";
+ Assert.assertEquals(msg, expected.getX(), actual.getX(), EPS);
+ Assert.assertEquals(msg, expected.getY(), actual.getY(), EPS);
+ }
+
+ public static void assertSegmentsEqual(TestLineSegment expected, TestLineSegment actual) {
+ String msg = "Expected line segment to equal " + expected + " but was " + actual;
+
+ Assert.assertEquals(msg, expected.getStartPoint().getX(),
+ actual.getStartPoint().getX(), EPS);
+ Assert.assertEquals(msg, expected.getStartPoint().getY(),
+ actual.getStartPoint().getY(), EPS);
+
+ Assert.assertEquals(msg, expected.getEndPoint().getX(),
+ actual.getEndPoint().getX(), EPS);
+ Assert.assertEquals(msg, expected.getEndPoint().getY(),
+ actual.getEndPoint().getY(), EPS);
+ }
+
+ public static void assertIsInternalNode(Node<?, ?> node) {
+ Assert.assertNotNull(node.getCut());
+ Assert.assertNotNull(node.getMinus());
+ Assert.assertNotNull(node.getPlus());
+
+ Assert.assertTrue(node.isInternal());
+ Assert.assertFalse(node.isLeaf());
+ }
+
+ public static void assertIsLeafNode(Node<?, ?> node) {
+ Assert.assertNull(node.getCut());
+ Assert.assertNull(node.getMinus());
+ Assert.assertNull(node.getPlus());
+
+ Assert.assertFalse(node.isInternal());
+ Assert.assertTrue(node.isLeaf());
+ }
+
+ /** Assert that the given tree for has a valid, consistent internal structure. This checks that all nodes
+ * in the tree are owned by the tree, that the node depth values are correct, and the cut nodes have children
+ * and non-cut nodes do not.
+ * @param tree tree to check
+ */
+ public static <P extends Point<P>, N extends BSPTree.Node<P, N>> void assertTreeStructure(final BSPTree<P, N> tree) {
+ assertTreeStructureRecursive(tree, tree.getRoot(), 0);
+ }
+
+ /** Recursive method to assert that a tree has a valid internal structure.
+ * @param tree tree to check
+ * @param node node to check
+ * @param expectedDepth the expected depth of the node in the tree
+ */
+ private static <P extends Point<P>, N extends BSPTree.Node<P, N>> void assertTreeStructureRecursive(
+ final BSPTree<P, N> tree, final BSPTree.Node<P, N> node, final int expectedDepth) {
+
+ Assert.assertSame("Node has an incorrect owning tree", tree, node.getTree());
+ Assert.assertEquals("Node has an incorrect depth property", node.depth(), expectedDepth);
+
+ if (node.getCut() == null) {
+ String msg = "Node without cut cannot have children";
+
+ Assert.assertNull(msg, node.getMinus());
+ Assert.assertNull(msg, node.getPlus());
+ }
+ else {
+ String msg = "Node with cut must have children";
+
+ Assert.assertNotNull(msg, node.getMinus());
+ Assert.assertNotNull(msg, node.getPlus());
+
+ assertTreeStructureRecursive(tree, node.getPlus(), expectedDepth + 1);
+ assertTreeStructureRecursive(tree, node.getMinus(), expectedDepth + 1);
+ }
+ }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestBSPTree.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestBSPTree.java
new file mode 100644
index 0000000..b04ca59
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestBSPTree.java
@@ -0,0 +1,64 @@
+/*
+ * 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.partition.test;
+
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
+
+/** BSP Tree implementation class for testing purposes.
+ */
+public class TestBSPTree extends AbstractBSPTree<TestPoint2D, TestBSPTree.TestNode> {
+
+ /** Serializable UID */
+ private static final long serialVersionUID = 20190225L;
+
+ /** {@inheritDoc} */
+ @Override
+ protected TestNode createNode() {
+ return new TestNode(this);
+ }
+
+ /** {@inheritDoc}
+ *
+ * <p>Exposed as public for testing.</p>
+ */
+ @Override
+ public void splitIntoTrees(Hyperplane<TestPoint2D> splitter,
+ final AbstractBSPTree<TestPoint2D, TestBSPTree.TestNode> minus,
+ final AbstractBSPTree<TestPoint2D, TestBSPTree.TestNode> plus) {
+
+ super.splitIntoTrees(splitter, minus, plus);
+ }
+
+ /** BSP Tree node class for {@link TestBSPTree}.
+ */
+ public static class TestNode extends AbstractBSPTree.AbstractNode<TestPoint2D,TestNode> {
+
+ /** Serializable UID */
+ private static final long serialVersionUID = 20190225L;
+
+ public TestNode(AbstractBSPTree<TestPoint2D, TestNode> tree) {
+ super(tree);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected TestNode getSelf() {
+ return this;
+ }
+ }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLine.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLine.java
new file mode 100644
index 0000000..8a19ed5
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLine.java
@@ -0,0 +1,280 @@
+/*
+ * 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.partition.test;
+
+import java.io.Serializable;
+
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
+
+/** Class representing a line in two dimensional Euclidean space. This
+ * class should only be used for testing purposes.
+ */
+public class TestLine implements EmbeddingHyperplane<TestPoint2D, TestPoint1D>, Serializable {
+
+ /** Line pointing along the positive x-axis. */
+ public static final TestLine X_AXIS = new TestLine(0, 0, 1, 0);
+
+ /** Line pointing along the positive y-axis. */
+ public static final TestLine Y_AXIS = new TestLine(0, 0, 0, 1);
+
+ /** Serializable UID */
+ private static final long serialVersionUID = 20190224L;
+
+ /** X value of the normalized line direction. */
+ private final double directionX;
+
+ /** Y value of the normalized line direction. */
+ private final double directionY;
+
+ /** The distance between the origin and the line. */
+ private final double originOffset;
+
+ /** Construct a line from two points. The line points in the direction from
+ * {@code p1} to {@code p2}.
+ * @param p1 first point
+ * @param p2 second point
+ */
+ public TestLine(final TestPoint2D p1, final TestPoint2D p2) {
+ this(p1.getX(), p1.getY(), p2.getX(), p2.getY());
+ }
+
+ /** Construct a line from two point, given by their components.
+ * @param x1 x coordinate of first point
+ * @param y1 y coordinate of first point
+ * @param x2 x coordinate of second point
+ * @param y2 y coordinate of second point
+ */
+ public TestLine(final double x1, final double y1, final double x2, final double y2) {
+ double vecX = x2 - x1;
+ double vecY = y2 - y1;
+
+ double norm = norm(vecX, vecY);
+
+ vecX /= norm;
+ vecY /= norm;
+
+ if (!Double.isFinite(vecX) || !Double.isFinite(vecY)) {
+ throw new IllegalStateException("Unable to create line between points: (" +
+ x1 + ", " + y1 + "), (" + x2 + ", " + y2 + ")");
+ }
+
+ this.directionX = vecX;
+ this.directionY = vecY;
+
+ this.originOffset = signedArea(vecX, vecY, x1, y1);
+ }
+
+ /** Get the line origin, meaning the projection of the 2D origin onto the line.
+ * @return line origin
+ */
+ public TestPoint2D getOrigin() {
+ return toSpace(0);
+ }
+
+ /** Get the x component of the line direction.
+ * @return x component of the line direction.
+ */
+ public double getDirectionX() {
+ return directionX;
+ }
+
+ /** Get the y component of the line direction.
+ * @return y component of the line direction.
+ */
+ public double getDirectionY() {
+ return directionY;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double offset(TestPoint2D point) {
+ return originOffset - signedArea(directionX, directionY, point.getX(), point.getY());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public HyperplaneLocation classify(TestPoint2D point) {
+ final double offset = offset(point);
+ final double cmp = PartitionTestUtils.PRECISION.compare(offset, 0.0);
+ if (cmp == 0) {
+ return HyperplaneLocation.ON;
+ }
+ return cmp < 0 ? HyperplaneLocation.MINUS : HyperplaneLocation.PLUS;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean contains(TestPoint2D point) {
+ return classify(point) == HyperplaneLocation.ON;
+ }
+
+ /** Get the location of the given 2D point in the 1D space of the line.
+ * @param point point to project into the line's 1D space
+ * @return location of the point in the line's 1D space
+ */
+ public double toSubspaceValue(TestPoint2D point) {
+ return (directionX * point.getX()) + (directionY * point.getY());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestPoint1D toSubspace(TestPoint2D point) {
+ return new TestPoint1D(toSubspaceValue(point));
+ }
+
+ /** Get the 2D location of the given 1D location in the line's 1D space.
+ * @param abscissa location in the line's 1D space.
+ * @return the location of the given 1D point in 2D space
+ */
+ public TestPoint2D toSpace(final double abscissa) {
+ if (Double.isInfinite(abscissa)) {
+ int dirXCmp = PartitionTestUtils.PRECISION.sign(directionX);
+ int dirYCmp = PartitionTestUtils.PRECISION.sign(directionY);
+
+ double x;
+ if (dirXCmp == 0) {
+ // vertical line
+ x = getOrigin().getX();
+ }
+ else {
+ x = (dirXCmp < 0 ^ abscissa < 0) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
+ }
+
+ double y;
+ if (dirYCmp == 0) {
+ // horizontal line
+ y = getOrigin().getY();
+ }
+ else {
+ y = (dirYCmp < 0 ^ abscissa < 0) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
+ }
+
+ return new TestPoint2D(x, y);
+ }
+
+ final double ptX = (abscissa * directionX) + (-originOffset * directionY);
+ final double ptY = (abscissa * directionY) + (originOffset * directionX);
+
+ return new TestPoint2D(ptX, ptY);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestPoint2D toSpace(TestPoint1D point) {
+ return toSpace(point.getX());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestPoint2D project(final TestPoint2D point) {
+ return toSpace(toSubspaceValue(point));
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestLine reverse() {
+ TestPoint2D pt = getOrigin();
+ return new TestLine(pt.getX(), pt.getY(), pt.getX() - directionX, pt.getY() - directionY);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestLine transform(Transform<TestPoint2D> transform) {
+ TestPoint2D p1 = transform.apply(toSpace(0));
+ TestPoint2D p2 = transform.apply(toSpace(1));
+
+ return new TestLine(p1, p2);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean similarOrientation(Hyperplane<TestPoint2D> other) {
+ final TestLine otherLine = (TestLine) other;
+ final double dot = (directionX * otherLine.directionX) + (directionY * otherLine.directionY);
+ return dot >= 0.0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestLineSegment span() {
+ return new TestLineSegment(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, this);
+ }
+
+ /** Get the intersection point of the instance and another line.
+ * @param other other line
+ * @return intersection point of the instance and the other line
+ * or null if there is no unique intersection point (ie, the lines
+ * are parallel or coincident)
+ */
+ public TestPoint2D intersection(final TestLine other) {
+ final double area = signedArea(directionX, directionY, other.directionX, other.directionY);
+ if (PartitionTestUtils.PRECISION.eqZero(area)) {
+ // lines are parallel
+ return null;
+ }
+
+ final double x = ((other.directionX * originOffset) +
+ (-directionX * other.originOffset)) / area;
+
+ final double y = ((other.directionY * originOffset) +
+ (-directionY * other.originOffset)) / area;
+
+ return new TestPoint2D(x, y);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(this.getClass().getSimpleName())
+ .append("[origin= ")
+ .append(getOrigin())
+ .append(", direction= (")
+ .append(directionX)
+ .append(", ")
+ .append(directionY)
+ .append(")]");
+
+ return sb.toString();
+ }
+
+ /** Compute the signed area of the parallelogram with sides defined by the given
+ * vectors.
+ * @param x1 x coordinate of first vector
+ * @param y1 y coordinate of first vector
+ * @param x2 x coordinate of second vector
+ * @param y2 y coordinate of second vector
+ * @return the signed are of the parallelogram with side defined by the given
+ * vectors
+ */
+ private static double signedArea(final double x1, final double y1,
+ final double x2, final double y2) {
+ return (x1 * y2) + (-y1 * x2);
+ }
+
+ /** Compute the Euclidean norm.
+ * @param x x coordinate value
+ * @param y y coordinate value
+ * @return Euclidean norm
+ */
+ public static double norm(final double x, final double y) {
+ return Math.sqrt((x * x) + (y * y));
+ }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegment.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegment.java
new file mode 100644
index 0000000..aa20dd7
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegment.java
@@ -0,0 +1,340 @@
+/*
+ * 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.partition.test;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+
+/** Class representing a line segment in two dimensional Euclidean space. This
+ * class should only be used for testing purposes.
+ */
+public class TestLineSegment implements ConvexSubHyperplane<TestPoint2D>, Serializable {
+
+ /** Serializable UID */
+ private static final long serialVersionUID = 20190224L;
+
+ /** Abscissa of the line segment start point. */
+ private final double start;
+
+ /** Abscissa of the line segment end point. */
+ private final double end;
+
+ /** The underlying line for the line segment. */
+ private final TestLine line;
+
+ /** Construct a line segment between two points.
+ * @param start start point
+ * @param end end point
+ */
+ public TestLineSegment(final TestPoint2D start, final TestPoint2D end) {
+ this.line = new TestLine(start, end);
+
+ final double startValue = line.toSubspaceValue(start);
+ final double endValue = line.toSubspaceValue(end);
+
+ this.start = Math.min(startValue, endValue);
+ this.end = Math.max(startValue, endValue);
+ }
+
+ /** Construct a line segment between two points.
+ * @param x1 x coordinate of first point
+ * @param y1 y coordinate of first point
+ * @param x2 x coordinate of second point
+ * @param y2 y coordinate of second point
+ */
+ public TestLineSegment(final double x1, final double y1, final double x2, final double y2) {
+ this(new TestPoint2D(x1, y1), new TestPoint2D(x2, y2));
+ }
+
+ /** Construct a line segment based on an existing line.
+ * @param start abscissa of the line segment start point
+ * @param end abscissa of the line segment end point
+ * @param line the underyling line
+ */
+ public TestLineSegment(final double start, final double end, final TestLine line) {
+ this.start = Math.min(start, end);
+ this.end = Math.max(start, end);
+ this.line = line;
+ }
+
+ /** Get the start abscissa value.
+ * @return
+ */
+ public double getStart() {
+ return start;
+ }
+
+ /** Get the end abscissa value.
+ * @return
+ */
+ public double getEnd() {
+ return end;
+ }
+
+ /** Get the start point of the line segment.
+ * @return the start point of the line segment
+ */
+ public TestPoint2D getStartPoint() {
+ return line.toSpace(start);
+ }
+
+ /** Get the end point of the line segment.
+ * @return the end point of the line segment
+ */
+ public TestPoint2D getEndPoint() {
+ return line.toSpace(end);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestLine getHyperplane() {
+ return line;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isFull() {
+ return start < end && Double.isInfinite(start) && Double.isInfinite(end);
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isEmpty() {
+ return PartitionTestUtils.PRECISION.eqZero(getSize());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isInfinite() {
+ return Double.isInfinite(getSize());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isFinite() {
+ return !isInfinite();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public double getSize() {
+ return Math.abs(start - end);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public RegionLocation classify(TestPoint2D point) {
+ if (line.contains(point)) {
+ final double value = line.toSubspaceValue(point);
+
+ final int startCmp = PartitionTestUtils.PRECISION.compare(value, start);
+ final int endCmp = PartitionTestUtils.PRECISION.compare(value, end);
+
+ if (startCmp == 0 || endCmp == 0) {
+ return RegionLocation.BOUNDARY;
+ }
+ else if (startCmp > 0 && endCmp < 0) {
+ return RegionLocation.INSIDE;
+ }
+ }
+
+ return RegionLocation.OUTSIDE;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestPoint2D closest(TestPoint2D point) {
+ double value = line.toSubspaceValue(point);
+ value = Math.max(Math.min(value, end), start);
+
+ return line.toSpace(value);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List<ConvexSubHyperplane<TestPoint2D>> toConvex() {
+ return Arrays.asList(this);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public TestLineSegment reverse() {
+ TestLine rLine = line.reverse();
+ return new TestLineSegment(-end, -start, rLine);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Split<TestLineSegment> split(Hyperplane<TestPoint2D> splitter) {
+ final TestLine splitterLine = (TestLine) splitter;
+
+ if (isInfinite()) {
+ return splitInfinite(splitterLine);
+ }
+ return splitFinite(splitterLine);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public SubHyperplane.Builder<TestPoint2D> builder() {
+ return new TestLineSegmentCollectionBuilder(line);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ConvexSubHyperplane<TestPoint2D> transform(Transform<TestPoint2D> transform) {
+ if (!isInfinite()) {
+ // simple case; just transform the points directly
+ TestPoint2D p1 = transform.apply(getStartPoint());
+ TestPoint2D p2 = transform.apply(getEndPoint());
+
+ return new TestLineSegment(p1, p2);
+ }
+
+ // determine how the line has transformed
+ TestPoint2D p0 = transform.apply(line.toSpace(0));
+ TestPoint2D p1 = transform.apply(line.toSpace(1));
+
+ TestLine tLine = new TestLine(p0, p1);
+ double translation = tLine.toSubspaceValue(p0);
+ double scale = tLine.toSubspaceValue(p1);
+
+ double tStart = (start * scale) + translation;
+ double tEnd = (end * scale) + translation;
+
+ return new TestLineSegment(tStart, tEnd, tLine);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(this.getClass().getSimpleName())
+ .append("[start= ")
+ .append(getStartPoint())
+ .append(", end= ")
+ .append(getEndPoint())
+ .append("]");
+
+ return sb.toString();
+ }
+
+ /** Method used to split the instance with the given line when the instance has
+ * infinite size.
+ * @param splitter the splitter line
+ * @return the split convex subhyperplane
+ */
+ private Split<TestLineSegment> splitInfinite(TestLine splitter) {
+ final TestPoint2D intersection = splitter.intersection(line);
+
+ if (intersection == null) {
+ // the lines are parallel
+ final double originOffset = splitter.offset(line.getOrigin());
+
+ final int sign = PartitionTestUtils.PRECISION.sign(originOffset);
+ if (sign < 0) {
+ return new Split<TestLineSegment>(this, null);
+ }
+ else if (sign > 0) {
+ return new Split<TestLineSegment>(null, this);
+ }
+ return new Split<TestLineSegment>(null, null);
+ }
+ else {
+ // the lines intersect
+ final double intersectionAbscissa = line.toSubspaceValue(intersection);
+
+ TestLineSegment startSegment = null;
+ TestLineSegment endSegment = null;
+
+ if (start < intersectionAbscissa) {
+ startSegment = new TestLineSegment(start, intersectionAbscissa, line);
+ }
+ if (intersectionAbscissa < end) {
+ endSegment = new TestLineSegment(intersectionAbscissa, end, line);
+ }
+
+ final double startOffset = splitter.offset(line.toSpace(intersectionAbscissa - 1));
+ final double startCmp = PartitionTestUtils.PRECISION.sign(startOffset);
+
+ final TestLineSegment minus = (startCmp > 0) ? endSegment: startSegment;
+ final TestLineSegment plus = (startCmp > 0) ? startSegment : endSegment;
+
+ return new Split<TestLineSegment>(minus, plus);
+ }
+ }
+
+ /** Method used to split the instance with the given line when the instance has
+ * finite size.
+ * @param splitter the splitter line
+ * @return the split convex subhyperplane
+ */
+ private Split<TestLineSegment> splitFinite(TestLine splitter) {
+
+ final double startOffset = splitter.offset(line.toSpace(start));
+ final double endOffset = splitter.offset(line.toSpace(end));
+
+ final int startCmp = PartitionTestUtils.PRECISION.sign(startOffset);
+ final int endCmp = PartitionTestUtils.PRECISION.sign(endOffset);
+
+ // startCmp | endCmp | result
+ // --------------------------------
+ // 0 | 0 | hyper
+ // 0 | < 0 | minus
+ // 0 | > 0 | plus
+ // < 0 | 0 | minus
+ // < 0 | < 0 | minus
+ // < 0 | > 0 | SPLIT
+ // > 0 | 0 | plus
+ // > 0 | < 0 | SPLIT
+ // > 0 | > 0 | plus
+
+ if (startCmp == 0 && endCmp == 0) {
+ // the entire line segment is directly on the splitter line
+ return new Split<TestLineSegment>(null, null);
+ }
+ else if (startCmp < 1 && endCmp < 1) {
+ // the entire line segment is on the minus side
+ return new Split<TestLineSegment>(this, null);
+ }
+ else if (startCmp > -1 && endCmp > -1) {
+ // the entire line segment is on the plus side
+ return new Split<TestLineSegment>(null, this);
+ }
+
+ // we need to split the line
+ final TestPoint2D intersection = splitter.intersection(line);
+ final double intersectionAbscissa = line.toSubspaceValue(intersection);
+
+ final TestLineSegment startSegment = new TestLineSegment(start, intersectionAbscissa, line);
+ final TestLineSegment endSegment = new TestLineSegment(intersectionAbscissa, end, line);
+
+ final TestLineSegment minus = (startCmp > 0) ? endSegment: startSegment;
+ final TestLineSegment plus = (startCmp > 0) ? startSegment : endSegment;
+
+ return new Split<TestLineSegment>(minus, plus);
+ }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegmentCollection.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegmentCollection.java
new file mode 100644
index 0000000..a541a5d
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegmentCollection.java
@@ -0,0 +1,197 @@
+/*
+ * 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.partition.test;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+
+/** Class containing a collection line segments. This class should only be used for
+ * testing purposes.
+ */
+public class TestLineSegmentCollection implements SubHyperplane<TestPoint2D>, Serializable {
+
+ /** Serializable UID */
+ private static final long serialVersionUID = 20190303L;
+
+ /** The collection of line-segments making up the subhyperplane.
+ */
+ private final List<TestLineSegment> segments;
+
+ /** Create a new instance with the given line segments. The segments
+ * are all assumed to lie on the same hyperplane.
... 64761 lines suppressed ...