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:51 UTC

[commons-geometry] branch master updated (443057b -> d4a619d)

This is an automated email from the ASF dual-hosted git repository.

erans pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-geometry.git.


    from 443057b  Badge for "SonarQube".
     new 77a2792  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)
     new 5b5ea79  adding documentation to checkstyle suppression rules
     new 3ec2963  fixing VectorXD.linearCombination() docs
     new d4a619d  Merge branch 'GEOMETRY-32__Matt'

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/commons/geometry/core/Embedding.java    |   71 +
 .../org/apache/commons/geometry/core/Geometry.java |    8 +-
 .../org/apache/commons/geometry/core/Region.java   |   82 +
 .../{package-info.java => RegionLocation.java}     |   30 +-
 .../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 +-
 .../{DoubleFunction1N.java => Equivalency.java}    |   20 +-
 .../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 +
 .../bsp}/package-info.java                         |    5 +-
 .../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 -
 .../oned/{package-info.java => Transform1D.java}   |   21 +-
 .../commons/geometry/euclidean/oned/Vector1D.java  |  152 +-
 .../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 +-
 .../threed/{package-info.java => Transform3D.java} |   21 +-
 .../geometry/euclidean/threed/Vector3D.java        |  199 +-
 .../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 +--
 .../twod/{package-info.java => Transform2D.java}   |   21 +-
 .../commons/geometry/euclidean/twod/Vector2D.java  |  185 +-
 .../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         |   13 +
 248 files changed, 48946 insertions(+), 21005 deletions(-)
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Embedding.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Region.java
 copy commons-geometry-core/src/main/java/org/apache/commons/geometry/core/{package-info.java => RegionLocation.java} (66%)
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Transform.java
 copy commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/{DoubleFunction1N.java => Equivalency.java} (55%)
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/IteratorTransform.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractConvexHyperplaneBoundedRegion.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractEmbeddingSubHyperplane.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractHyperplane.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractRegion.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractSubHyperplane.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BSPTree.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BSPTreeVisitor.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryAttribute.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryBuilder.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryProjection.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundaryProjector.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySizeVisitor.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Characterization.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/ConvexSubHyperplane.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Embedding.java
 copy commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/{Side.java => EmbeddingHyperplane.java} (68%)
 copy commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/{Side.java => HyperplaneBoundedRegion.java} (60%)
 copy commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/{Side.java => HyperplaneLocation.java} (68%)
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/InsideFinder.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/NodesSet.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Region.java
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/RegionFactory.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Split.java
 copy commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/{Side.java => SplitLocation.java} (53%)
 rename commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/{Side.java => Splittable.java} (64%)
 delete mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/Transform.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTree.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeMergeOperator.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AttributeBSPTree.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPSubtree.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTreePrinter.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTreeVisitor.java
 create mode 100644 commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/RegionCutBoundary.java
 copy commons-geometry-core/src/main/java/org/apache/commons/geometry/core/{precision => partitioning/bsp}/package-info.java (77%)
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/EmbeddingTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/IteratorTransformTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/PartitionTestUtils.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestBSPTree.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLine.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegment.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegmentCollection.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegmentCollectionBuilder.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestPoint1D.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestPoint2D.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestTransform2D.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractConvexHyperplaneBoundedRegionTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractEmbeddingSubHyperplaneTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractHyperplaneTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/SplitTest.java
 delete mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/TreeBuilder.java
 delete mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/TreeDumper.java
 delete mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/TreePrinter.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeMergeOperatorTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTreeTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AttributeBSPTreeTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTreeVisitorTest.java
 create mode 100644 commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/RegionCutBoundaryTest.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/AbstractAffineTransformMatrix.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/AffineTransformMatrix.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanTransform.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/AbstractPathConnector.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/FunctionTransform1D.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/IntervalsSet.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1D.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/SubOrientedPoint.java
 copy commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/{package-info.java => Transform1D.java} (65%)
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AbstractSubLine3D.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AbstractSubPlane.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexSubPlane.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexVolume.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/FunctionTransform3D.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Line.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Line3D.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/OutlineExtractor.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSet.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Segment.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Segment3D.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubLine.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubLine3D.java
 copy commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/{package-info.java => Transform3D.java} (65%)
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AbstractSegmentConnector.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AbstractSubLine.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ConvexArea.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/FunctionTransform2D.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/InteriorAngleSegmentConnector.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/NestedLoops.java
 delete mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolygonsSet.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Polyline.java
 create mode 100644 commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2D.java
 copy commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/{package-info.java => Transform2D.java} (65%)
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/core/partitioning/CharacterizationTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/FunctionTransform1DTest.java
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/IntervalsSetTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1DTest.java
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/SubOrientedPointTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/ConvexSubPlaneTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/ConvexVolumeTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/FunctionTransform3DTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Line3DTest.java
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/LineTest.java
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/OBJWriter.java
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PLYParser.java
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PolyhedronsSetTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3DTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Segment3DTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLine3DTest.java
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLineTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubPlaneTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/AbstractSegmentConnectorTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/ConvexAreaTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/FunctionTransform2DTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/InteriorAngleSegmentConnectorTest.java
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/NestedLoopsTest.java
 delete mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolygonsSetTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolylineTest.java
 create mode 100644 commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2DTest.java
 delete mode 100644 commons-geometry-euclidean/src/test/resources/org/apache/commons/geometry/euclidean/threed/issue-1211.bsp
 delete mode 100644 commons-geometry-euclidean/src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-bad-orientation.ply
 delete mode 100644 commons-geometry-euclidean/src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-hole.ply
 delete mode 100644 commons-geometry-euclidean/src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-out-of-plane.ply
 delete mode 100644 commons-geometry-euclidean/src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N-too-close.ply
 delete mode 100644 commons-geometry-euclidean/src/test/resources/org/apache/commons/geometry/euclidean/threed/pentomino-N.ply
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/AngularInterval.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/Arc.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/ArcsSet.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/CutAngle.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/LimitAngle.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/Point1S.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/RegionBSPTree1S.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/SubLimitAngle.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/Transform1S.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/AbstractGreatArcConnector.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/AbstractSubGreatCircle.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/ConvexArea2S.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatArc.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatArcPath.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircle.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/InteriorAngleGreatArcConnector.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Point2S.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2S.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SubCircle.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SubGreatCircle.java
 create mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Transform2S.java
 delete mode 100644 commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Vertex.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/AngularIntervalTest.java
 delete mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcTest.java
 delete mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/ArcsSetTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/CutAngleTest.java
 delete mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/LimitAngleTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/Point1STest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/RegionBSPTree1STest.java
 delete mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/S1PointTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/oned/Transform1STest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/AbstractGreatArcPathConnectorTest.java
 delete mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/ConvexArea2STest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatArcPathTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatArcTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatCircleTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/InteriorAngleGreatArcConnectorTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/Point2STest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/RegionBSPTree2STest.java
 delete mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/S2PointTest.java
 delete mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
 delete mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SubCircleTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SubGreatCircleTest.java
 create mode 100644 commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/Transform2STest.java


[commons-geometry] 02/04: adding documentation to checkstyle suppression rules

Posted by er...@apache.org.
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 5b5ea797a61d3988b7b2943bc4acccaad9298216
Author: Matt Juntunen <ma...@hotmail.com>
AuthorDate: Sun Nov 24 12:11:45 2019 -0500

    adding documentation to checkstyle suppression rules
---
 src/main/resources/checkstyle/checkstyle-suppressions.xml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/main/resources/checkstyle/checkstyle-suppressions.xml b/src/main/resources/checkstyle/checkstyle-suppressions.xml
index f352d2a..bc75f02 100644
--- a/src/main/resources/checkstyle/checkstyle-suppressions.xml
+++ b/src/main/resources/checkstyle/checkstyle-suppressions.xml
@@ -19,11 +19,17 @@
     "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
     "https://checkstyle.org/dtds/suppressions_1_2.dtd">
 <suppressions>
+  <!-- allow internal Matrices.determinant() method for 3x3 matrices -->
   <suppress checks="ParameterNumber" files=".*/internal/Matrices" />
+  <!-- allow Vector1D.linearCombination() methods -->
   <suppress checks="ParameterNumber" files=".*/oned/Vector1D" />
+  <!-- allow internal, non-array constructor -->
   <suppress checks="ParameterNumber" files=".*/threed/AffineTransformMatrix3D" />
+  <!-- allow Vector3D.linearCombination() methods -->
   <suppress checks="ParameterNumber" files=".*/threed/Vector3D" />
+  <!-- allow internal, non-array QuaterionRotation.orthogonalRotationMatrixToQuaternion() method -->
   <suppress checks="ParameterNumber" files=".*/threed/rotation/QuaternionRotation" />
+  <!-- allow Vector2D.linearCombination() methods -->
   <suppress checks="ParameterNumber" files=".*/twod/Vector2D" />
 
   <!-- Be more lenient on tests. -->


[commons-geometry] 03/04: fixing VectorXD.linearCombination() docs

Posted by er...@apache.org.
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 3ec296329122ab64a10d5bef5b850629a5bc62d9
Author: Matt Juntunen <ma...@hotmail.com>
AuthorDate: Sun Nov 24 15:02:14 2019 -0500

    fixing VectorXD.linearCombination() docs
---
 .../commons/geometry/euclidean/oned/Vector1D.java  | 49 +++++++++++-----------
 .../geometry/euclidean/threed/Vector3D.java        | 48 ++++++++++-----------
 .../commons/geometry/euclidean/twod/Vector2D.java  | 48 ++++++++++-----------
 3 files changed, 72 insertions(+), 73 deletions(-)

diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
index 57ff97d..d62e0a6 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
@@ -326,9 +326,9 @@ public class Vector1D extends EuclideanVector<Vector1D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a scale factor for first coordinate
-     * @param c first coordinate
-     * @return vector with coordinates calculated by {@code a * c}
+     * @param a scale factor for first vector
+     * @param c first vector
+     * @return vector calculated by {@code a * c}
      */
     public static Vector1D linearCombination(final double a, final Vector1D c) {
         return new Vector1D(a * c.x);
@@ -340,11 +340,11 @@ public class Vector1D extends EuclideanVector<Vector1D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a1 scale factor for first coordinate
-     * @param v1 first coordinate
-     * @param a2 scale factor for second coordinate
-     * @param v2 second coordinate
-     * @return vector with coordinates calculated by {@code (a1 * v1) + (a2 * v2)}
+     * @param a1 scale factor for first vector
+     * @param v1 first vector
+     * @param a2 scale factor for second vector
+     * @param v2 second vector
+     * @return vector calculated by {@code (a1 * v1) + (a2 * v2)}
      */
     public static Vector1D linearCombination(final double a1, final Vector1D v1,
             final double a2, final Vector1D v2) {
@@ -359,13 +359,13 @@ public class Vector1D extends EuclideanVector<Vector1D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a1 scale factor for first coordinate
-     * @param v1 first coordinate
-     * @param a2 scale factor for second coordinate
-     * @param v2 second coordinate
-     * @param a3 scale factor for third coordinate
-     * @param v3 third coordinate
-     * @return vector with coordinates calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
+     * @param a1 scale factor for first vector
+     * @param v1 first vector
+     * @param a2 scale factor for second vector
+     * @param v2 second vector
+     * @param a3 scale factor for third vector
+     * @param v3 third vector
+     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
      */
     public static Vector1D linearCombination(final double a1, final Vector1D v1,
             final double a2, final Vector1D v2,
@@ -381,15 +381,15 @@ public class Vector1D extends EuclideanVector<Vector1D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a1 scale factor for first coordinate
-     * @param v1 first coordinate
-     * @param a2 scale factor for second coordinate
-     * @param v2 second coordinate
-     * @param a3 scale factor for third coordinate
-     * @param v3 third coordinate
-     * @param a4 scale factor for fourth coordinate
-     * @param v4 fourth coordinate
-     * @return point with coordinates calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
+     * @param a1 scale factor for first vector
+     * @param v1 first vector
+     * @param a2 scale factor for second vector
+     * @param v2 second vector
+     * @param a3 scale factor for third vector
+     * @param v3 third vector
+     * @param a4 scale factor for fourth vector
+     * @param v4 fourth vector
+     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
      */
     public static Vector1D linearCombination(final double a1, final Vector1D v1,
             final double a2, final Vector1D v2,
@@ -405,7 +405,6 @@ public class Vector1D extends EuclideanVector<Vector1D> {
      * This allows optimizations to be performed for certain operations.
      */
     public static final class Unit extends Vector1D {
-
         /** Unit vector (coordinates: 1). */
         public static final Unit PLUS  = new Unit(1d);
         /** Negation of unit vector (coordinates: -1). */
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
index a8b8d47..da6f7a6 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
@@ -521,9 +521,9 @@ public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a scale factor for first coordinate
-     * @param c first coordinate
-     * @return vector with coordinates calculated by {@code a * c}
+     * @param a scale factor for first vector
+     * @param c first vector
+     * @return vector calculated by {@code a * c}
      */
     public static Vector3D linearCombination(final double a, final Vector3D c) {
         return c.multiply(a);
@@ -535,11 +535,11 @@ public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a1 scale factor for first coordinate
-     * @param v1 first coordinate
-     * @param a2 scale factor for second coordinate
-     * @param v2 second coordinate
-     * @return vector with coordinates calculated by {@code (a1 * v1) + (a2 * v2)}
+     * @param a1 scale factor for first vector
+     * @param v1 first vector
+     * @param a2 scale factor for second vector
+     * @param v2 second vector
+     * @return vector calculated by {@code (a1 * v1) + (a2 * v2)}
      */
     public static Vector3D linearCombination(final double a1, final Vector3D v1,
             final double a2, final Vector3D v2) {
@@ -555,13 +555,13 @@ public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a1 scale factor for first coordinate
-     * @param v1 first coordinate
-     * @param a2 scale factor for second coordinate
-     * @param v2 second coordinate
-     * @param a3 scale factor for third coordinate
-     * @param v3 third coordinate
-     * @return vector with coordinates calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
+     * @param a1 scale factor for first vector
+     * @param v1 first vector
+     * @param a2 scale factor for second vector
+     * @param v2 second vector
+     * @param a3 scale factor for third vector
+     * @param v3 third vector
+     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
      */
     public static Vector3D linearCombination(final double a1, final Vector3D v1,
             final double a2, final Vector3D v2,
@@ -578,15 +578,15 @@ public class Vector3D extends MultiDimensionalEuclideanVector<Vector3D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a1 scale factor for first coordinate
-     * @param v1 first coordinate
-     * @param a2 scale factor for second coordinate
-     * @param v2 second coordinate
-     * @param a3 scale factor for third coordinate
-     * @param v3 third coordinate
-     * @param a4 scale factor for fourth coordinate
-     * @param v4 fourth coordinate
-     * @return point with coordinates calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
+     * @param a1 scale factor for first vector
+     * @param v1 first vector
+     * @param a2 scale factor for second vector
+     * @param v2 second vector
+     * @param a3 scale factor for third vector
+     * @param v3 third vector
+     * @param a4 scale factor for fourth vector
+     * @param v4 fourth vector
+     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
      */
     public static Vector3D linearCombination(final double a1, final Vector3D v1,
             final double a2, final Vector3D v2,
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
index 15ab254..6e60fcf 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
@@ -464,9 +464,9 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a scale factor for first coordinate
-     * @param c first coordinate
-     * @return vector with coordinates calculated by {@code a * c}
+     * @param a scale factor for first vector
+     * @param c first vector
+     * @return vector calculated by {@code a * c}
      */
     public static Vector2D linearCombination(final double a, final Vector2D c) {
         return new Vector2D(a * c.x, a * c.y);
@@ -478,11 +478,11 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a1 scale factor for first coordinate
-     * @param v1 first coordinate
-     * @param a2 scale factor for second coordinate
-     * @param v2 second coordinate
-     * @return vector with coordinates calculated by {@code (a1 * v1) + (a2 * v2)}
+     * @param a1 scale factor for first vector
+     * @param v1 first vector
+     * @param a2 scale factor for second vector
+     * @param v2 second vector
+     * @return vector calculated by {@code (a1 * v1) + (a2 * v2)}
      */
     public static Vector2D linearCombination(final double a1, final Vector2D v1,
             final double a2, final Vector2D v2) {
@@ -497,13 +497,13 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a1 scale factor for first coordinate
-     * @param v1 first coordinate
-     * @param a2 scale factor for second coordinate
-     * @param v2 second coordinate
-     * @param a3 scale factor for third coordinate
-     * @param v3 third coordinate
-     * @return vector with coordinates calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
+     * @param a1 scale factor for first vector
+     * @param v1 first vector
+     * @param a2 scale factor for second vector
+     * @param v2 second vector
+     * @param a3 scale factor for third vector
+     * @param v3 third vector
+     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3)}
      */
     public static Vector2D linearCombination(final double a1, final Vector2D v1,
             final double a2, final Vector2D v2,
@@ -519,15 +519,15 @@ public class Vector2D extends MultiDimensionalEuclideanVector<Vector2D> {
      * corresponding scale factors.
      * </p>
      *
-     * @param a1 scale factor for first coordinate
-     * @param v1 first coordinate
-     * @param a2 scale factor for second coordinate
-     * @param v2 second coordinate
-     * @param a3 scale factor for third coordinate
-     * @param v3 third coordinate
-     * @param a4 scale factor for fourth coordinate
-     * @param v4 fourth coordinate
-     * @return point with coordinates calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
+     * @param a1 scale factor for first vector
+     * @param v1 first vector
+     * @param a2 scale factor for second vector
+     * @param v2 second vector
+     * @param a3 scale factor for third vector
+     * @param v3 third vector
+     * @param a4 scale factor for fourth vector
+     * @param v4 fourth vector
+     * @return vector calculated by {@code (a1 * v1) + (a2 * v2) + (a3 * v3) + (a4 * v4)}
      */
     public static Vector2D linearCombination(final double a1, Vector2D v1,
             final double a2, final Vector2D v2,


[commons-geometry] 04/04: Merge branch 'GEOMETRY-32__Matt'

Posted by er...@apache.org.
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 d4a619d0bd98444e6a244971d5fc99a8fd7217d4
Merge: 443057b 3ec2963
Author: Gilles Sadowski <gi...@harfang.homelinux.org>
AuthorDate: Mon Nov 25 00:04:13 2019 +0100

    Merge branch 'GEOMETRY-32__Matt'
    
    Closes #40.

 .../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  |  152 +-
 .../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        |  199 +-
 .../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  |  185 +-
 .../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         |   13 +
 248 files changed, 48934 insertions(+), 21050 deletions(-)


[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)

Posted by er...@apache.org.
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 &lt;op&gt;
-     * 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 &lt;op&gt;
-     * 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 ...