You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ma...@apache.org on 2020/05/06 01:54:22 UTC

[commons-geometry] branch master updated: GEOMETRY-92: adding specialized segment, ray, and reverse ray classes for 2D and 3D; GEOMETRY-93: major refactor from 'subhyperplane' naming convention to 'hyperplane-subset' naming convention

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

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


The following commit(s) were added to refs/heads/master by this push:
     new eee2589  GEOMETRY-92: adding specialized segment, ray, and reverse ray classes for 2D and 3D; GEOMETRY-93: major refactor from 'subhyperplane' naming convention to 'hyperplane-subset' naming convention
eee2589 is described below

commit eee258978cae8fe4987c2155ddfbd08c73d3c9db
Author: Matt Juntunen <ma...@apache.org>
AuthorDate: Tue May 5 21:50:34 2020 -0400

    GEOMETRY-92: adding specialized segment, ray, and reverse ray classes for 2D and 3D; GEOMETRY-93: major refactor from 'subhyperplane' naming convention to 'hyperplane-subset' naming convention
---
 .../apache/commons/geometry/core/Embedding.java    |  20 +-
 .../org/apache/commons/geometry/core/Region.java   |   9 +-
 .../BoundarySource.java => RegionEmbedding.java}   |  33 +-
 .../BoundarySource.java => Sized.java}             |  33 +-
 .../AbstractConvexHyperplaneBoundedRegion.java     | 106 ++-
 ...> AbstractRegionEmbeddingHyperplaneSubset.java} |  26 +-
 .../geometry/core/partitioning/BoundarySource.java |   6 +-
 .../geometry/core/partitioning/Hyperplane.java     |  14 +-
 .../core/partitioning/HyperplaneBoundedRegion.java |   2 +-
 ...Hyperplane.java => HyperplaneConvexSubset.java} |  20 +-
 .../core/partitioning/HyperplaneSubset.java        | 130 +++
 .../geometry/core/partitioning/SubHyperplane.java  | 153 ----
 .../core/partitioning/bsp/AbstractBSPTree.java     | 131 ++-
 .../partitioning/bsp/AbstractRegionBSPTree.java    | 183 ++--
 .../geometry/core/partitioning/bsp/BSPTree.java    |  33 +-
 .../core/partitioning/bsp/RegionCutBoundary.java   |  44 +-
 .../commons/geometry/core/RegionEmbeddingTest.java | 116 +++
 .../apache/commons/geometry/core/SizedTest.java    |  55 ++
 .../geometry/core/partition/test/TestBSPTree.java  |  15 +-
 .../core/partition/test/TestLineSegment.java       |  16 +-
 .../partition/test/TestLineSegmentCollection.java  |  14 +-
 .../test/TestLineSegmentCollectionBuilder.java     |  14 +-
 .../core/partition/test/TestRegionBSPTree.java     |   4 +-
 .../AbstractConvexHyperplaneBoundedRegionTest.java |   4 +-
 .../core/partitioning/AbstractHyperplaneTest.java  |   2 +-
 ...stractRegionEmbeddingHyperplaneSubsetTest.java} |  39 +-
 .../core/partitioning/bsp/AbstractBSPTreeTest.java |   2 +-
 .../bsp/AbstractRegionBSPTreeTest.java             |  22 +-
 .../partitioning/bsp/RegionCutBoundaryTest.java    |   2 +-
 .../euclidean/threed/SphereGenerator.java          |   3 +-
 .../euclidean/internal/AbstractPathConnector.java  |   8 +-
 .../commons/geometry/euclidean/oned/Interval.java  |  10 +-
 .../geometry/euclidean/oned/OrientedPoint.java     | 159 +---
 .../geometry/euclidean/oned/OrientedPoints.java    | 107 +++
 .../euclidean/threed/AbstractSubLine3D.java        |  56 --
 .../euclidean/threed/BoundarySource3D.java         |  27 +-
 .../threed/BoundarySourceLinecaster3D.java         |  37 +-
 .../geometry/euclidean/threed/ConvexSubPlane.java  | 195 -----
 .../geometry/euclidean/threed/ConvexVolume.java    |  38 +-
 ...{SubPlane.java => EmbeddedTreePlaneSubset.java} | 114 +--
 .../commons/geometry/euclidean/threed/Plane.java   | 184 +---
 .../euclidean/threed/PlaneConvexSubset.java        | 128 +++
 .../{AbstractSubPlane.java => PlaneSubset.java}    |  35 +-
 .../commons/geometry/euclidean/threed/Planes.java  | 264 ++++++
 .../geometry/euclidean/threed/RegionBSPTree3D.java |  62 +-
 .../geometry/euclidean/threed/Segment3D.java       | 267 ------
 .../EmbeddedTreeLineSubset3D.java}                 |  52 +-
 .../euclidean/threed/{ => line}/Line3D.java        | 156 ++--
 .../euclidean/threed/line/LineConvexSubset3D.java  | 115 +++
 .../threed/line/LineSpanningSubset3D.java          | 123 +++
 .../euclidean/threed/line/LineSubset3D.java        |  62 ++
 .../threed/{ => line}/LinecastPoint3D.java         |   3 +-
 .../threed/{ => line}/Linecastable3D.java          |  26 +-
 .../geometry/euclidean/threed/line/Lines3D.java    | 271 ++++++
 .../geometry/euclidean/threed/line/Ray3D.java      | 145 +++
 .../euclidean/threed/line/ReverseRay3D.java        | 139 +++
 .../geometry/euclidean/threed/line/Segment3D.java  | 141 +++
 .../threed/{shapes => line}/package-info.java      |   8 +-
 .../threed/{shapes => shape}/Parallelepiped.java   |  13 +-
 .../euclidean/threed/{shapes => shape}/Sphere.java |  42 +-
 .../threed/{shapes => shape}/package-info.java     |   2 +-
 .../euclidean/twod/AbstractSegmentConnector.java   | 298 -------
 .../geometry/euclidean/twod/AbstractSubLine.java   | 127 ---
 .../geometry/euclidean/twod/BoundarySource2D.java  |  32 +-
 .../euclidean/twod/BoundarySourceLinecaster2D.java |  28 +-
 .../geometry/euclidean/twod/ConvexArea.java        |  83 +-
 .../euclidean/twod/EmbeddedTreeLineSubset.java     | 262 ++++++
 .../commons/geometry/euclidean/twod/Line.java      | 155 ++--
 .../geometry/euclidean/twod/LineConvexSubset.java  | 141 +++
 .../euclidean/twod/LineSpanningSubset.java         | 156 ++++
 .../geometry/euclidean/twod/LineSubset.java        | 156 ++++
 .../geometry/euclidean/twod/Linecastable2D.java    |  20 +-
 .../commons/geometry/euclidean/twod/Lines.java     | 277 ++++++
 .../commons/geometry/euclidean/twod/Ray.java       | 194 ++++
 .../geometry/euclidean/twod/RegionBSPTree2D.java   |  83 +-
 .../geometry/euclidean/twod/ReverseRay.java        | 189 ++++
 .../commons/geometry/euclidean/twod/Segment.java   | 329 +++----
 .../commons/geometry/euclidean/twod/SubLine.java   | 219 -----
 .../twod/path/AbstractLinePathConnector.java       | 299 +++++++
 .../InteriorAngleLinePathConnector.java}           |  68 +-
 .../twod/{Polyline.java => path/LinePath.java}     | 586 +++++++------
 .../{threed/shapes => twod/path}/package-info.java |   6 +-
 .../euclidean/twod/{shapes => shape}/Circle.java   |  17 +-
 .../twod/{shapes => shape}/Parallelogram.java      |  15 +-
 .../twod/{shapes => shape}/package-info.java       |   2 +-
 .../euclidean/DocumentationExamplesTest.java       |  91 +-
 .../geometry/euclidean/EuclideanTestUtils.java     |  36 +-
 .../geometry/euclidean/oned/IntervalTest.java      |  78 +-
 .../geometry/euclidean/oned/OrientedPointTest.java | 237 ++---
 .../euclidean/oned/RegionBSPTree1DTest.java        |  46 +-
 .../threed/AffineTransformMatrix3DTest.java        |   6 +-
 .../euclidean/threed/BoundarySource3DTest.java     |  24 +-
 .../threed/BoundarySourceLinecaster3DTest.java     |  57 +-
 .../euclidean/threed/ConvexVolumeTest.java         |  53 +-
 ...eTest.java => EmbeddedTreePlaneSubsetTest.java} | 147 ++--
 .../euclidean/threed/LinecastChecker3D.java        |   6 +-
 ...ubPlaneTest.java => PlaneConvexSubsetTest.java} | 165 ++--
 .../geometry/euclidean/threed/PlaneTest.java       | 296 +++----
 .../euclidean/threed/RegionBSPTree3DTest.java      | 207 ++---
 .../geometry/euclidean/threed/Segment3DTest.java   | 386 --------
 .../EmbeddedTreeLineSubset3DTest.java}             |  46 +-
 .../euclidean/threed/{ => line}/Line3DTest.java    | 273 ++++--
 .../threed/line/LineConvexSubset3DTest.java        | 293 +++++++
 .../threed/{ => line}/LinecastPoint3DTest.java     |  17 +-
 .../geometry/euclidean/threed/line/Ray3DTest.java  | 221 +++++
 .../euclidean/threed/line/ReverseRay3DTest.java    | 221 +++++
 .../euclidean/threed/line/Segment3DTest.java       | 337 +++++++
 .../threed/rotation/QuaternionRotationTest.java    |   2 +-
 .../{shapes => shape}/ParallelepipedTest.java      |  14 +-
 .../threed/{shapes => shape}/SphereTest.java       |  71 +-
 .../twod/AbstractSegmentConnectorTest.java         | 563 ------------
 .../twod/AffineTransformMatrix2DTest.java          |   6 +-
 .../euclidean/twod/BoundarySource2DTest.java       |  24 +-
 .../twod/BoundarySourceLinecaster2DTest.java       |  56 +-
 .../geometry/euclidean/twod/ConvexAreaTest.java    | 183 ++--
 ...neTest.java => EmbeddedTreeLineSubsetTest.java} | 307 ++++---
 .../euclidean/twod/LineConvexSubsetTest.java       | 481 ++++++++++
 .../geometry/euclidean/twod/LineSpanTest.java      | 220 +++++
 .../commons/geometry/euclidean/twod/LineTest.java  | 462 +++++-----
 .../geometry/euclidean/twod/LinecastChecker2D.java |   4 +-
 .../euclidean/twod/LinecastPoint2DTest.java        |  14 +-
 .../commons/geometry/euclidean/twod/RayTest.java   | 374 ++++++++
 .../euclidean/twod/RegionBSPTree2DTest.java        | 293 +++----
 .../geometry/euclidean/twod/ReverseRayTest.java    | 368 ++++++++
 .../geometry/euclidean/twod/SegmentTest.java       | 971 ++++++---------------
 .../twod/path/AbstractLinePathConnectorTest.java   | 568 ++++++++++++
 .../InteriorAngleLinePathConnectorTest.java}       | 117 +--
 .../{PolylineTest.java => path/LinePathTest.java}  | 564 ++++++------
 .../euclidean/twod/rotation/Rotation2DTest.java    |   5 +-
 .../twod/{shapes => shape}/CircleTest.java         |  61 +-
 .../twod/{shapes => shape}/ParallelogramTest.java  |  16 +-
 .../examples/jmh/euclidean/CirclePerformance.java  |   2 +-
 .../jmh/euclidean/RegionBSPTree2DPerformance.java  | 139 +++
 .../jmh/euclidean/RegionBSPTree3DPerformance.java  | 141 +++
 .../examples/jmh/euclidean/SpherePerformance.java  |   2 +-
 .../geometry/hull/euclidean/twod/ConvexHull2D.java |  12 +-
 .../hull/euclidean/twod/MonotoneChain.java         |   4 +-
 .../hull/euclidean/twod/ConvexHull2DTest.java      |  22 +-
 .../twod/ConvexHullGenerator2DAbstractTest.java    |   8 +-
 .../geometry/spherical/oned/AngularInterval.java   |  12 +-
 .../commons/geometry/spherical/oned/CutAngle.java  | 144 +--
 .../commons/geometry/spherical/oned/CutAngles.java |  92 ++
 .../geometry/spherical/oned/RegionBSPTree1S.java   |  10 +-
 .../spherical/twod/AbstractGreatArcConnector.java  |   8 +-
 .../geometry/spherical/twod/ConvexArea2S.java      |  10 +-
 ...cle.java => EmbeddedTreeGreatCircleSubset.java} |  68 +-
 .../commons/geometry/spherical/twod/GreatArc.java  |  46 +-
 .../geometry/spherical/twod/GreatArcPath.java      |   6 +-
 .../geometry/spherical/twod/GreatCircle.java       |  76 +-
 ...tSubGreatCircle.java => GreatCircleSubset.java} |  16 +-
 .../geometry/spherical/twod/GreatCircles.java      | 130 +++
 .../spherical/DocumentationExamplesTest.java       |   7 +-
 .../spherical/oned/AngularIntervalTest.java        |  56 +-
 .../geometry/spherical/oned/CutAngleTest.java      | 238 +++--
 .../spherical/oned/RegionBSPTree1STest.java        |  62 +-
 .../geometry/spherical/oned/Transform1STest.java   |   2 +-
 .../twod/AbstractGreatArcPathConnectorTest.java    |  44 +-
 .../spherical/twod/BoundarySource2STest.java       |   2 +-
 .../geometry/spherical/twod/ConvexArea2STest.java  |  66 +-
 ...st.java => EmbeddedTreeSubGreatCircleTest.java} | 159 ++--
 .../geometry/spherical/twod/GreatArcPathTest.java  |  36 +-
 .../geometry/spherical/twod/GreatArcTest.java      |  62 +-
 .../geometry/spherical/twod/GreatCircleTest.java   | 162 ++--
 .../twod/InteriorAngleGreatArcConnectorTest.java   |  50 +-
 .../spherical/twod/RegionBSPTree2STest.java        |  26 +-
 .../geometry/spherical/twod/Transform2STest.java   |   2 +-
 .../checkstyle/checkstyle-suppressions.xml         |  15 +-
 src/site/xdoc/userguide/index.xml                  | 186 ++--
 168 files changed, 11587 insertions(+), 7505 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
index cf258c8..9dca186 100644
--- 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
@@ -36,36 +36,36 @@ import java.util.stream.Collectors;
 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
+     * @param pt 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);
+    S toSubspace(P pt);
 
     /** Transform a collection of space points into subspace points.
-     * @param points collection of n-dimension points to transform
+     * @param pts 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());
+    default List<S> toSubspace(final Collection<P> pts) {
+        return pts.stream().map(this::toSubspace).collect(Collectors.toList());
     }
 
     /** Transform a subspace point into a space point.
-     * @param point lower-dimension point of the subspace
+     * @param pt 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);
+    P toSpace(S pt);
 
     /** Transform a collection of subspace points into space points.
-     * @param points collection of lower-dimension points to transform
+     * @param pts 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());
+    default List<P> toSpace(final Collection<S> pts) {
+        return pts.stream().map(this::toSpace).collect(Collectors.toList());
     }
 }
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
index 16da017..f22e00c 100644
--- 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
@@ -20,7 +20,7 @@ package org.apache.commons.geometry.core;
  * into sets of points lying on the inside, outside, and boundary.
  * @param <P> Point implementation type
  */
-public interface Region<P extends Point<P>> {
+public interface Region<P extends Point<P>> extends Sized {
 
     /** 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
@@ -35,13 +35,6 @@ public interface Region<P extends Point<P>> {
      */
     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.
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/RegionEmbedding.java
similarity index 52%
copy from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java
copy to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/RegionEmbedding.java
index 6dffc6b..b82b75a 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/RegionEmbedding.java
@@ -14,21 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.core.partitioning;
+package org.apache.commons.geometry.core;
 
-import java.util.stream.Stream;
-
-import org.apache.commons.geometry.core.Point;
-
-/** Interface representing an object that can produce region boundaries as a stream
- * of convex subhyperplanes.
- * @param <C> Convex subhyperplane implementation type
+/** Interface representing a geometric element that embeds a region in a subspace.
+ * @param <P> Point type defining the embedding space.
+ * @param <S> Point type defining the embedded subspace.
+ * @see Embedding
+ * @see Region
  */
-@FunctionalInterface
-public interface BoundarySource<C extends ConvexSubHyperplane<? extends Point<?>>> {
+public interface RegionEmbedding<P extends Point<P>, S extends Point<S>>
+    extends Embedding<P, S>, Sized {
+
+    /** Get the size of the instance, which by default is the size of the embedded
+     * subspace region.
+     * @return the size of instance
+     */
+    @Override
+    default double getSize() {
+        return getSubspaceRegion().getSize();
+    }
 
-    /** Return a stream containing the boundaries for this instance.
-     * @return a stream containing the boundaries for this instance
+    /** Get the embedded subspace region.
+     * @return the embedded subspace region
      */
-    Stream<C> boundaryStream();
+    Region<S> getSubspaceRegion();
 }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Sized.java
similarity index 50%
copy from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java
copy to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Sized.java
index 6dffc6b..b911160 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Sized.java
@@ -14,21 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.core.partitioning;
+package org.apache.commons.geometry.core;
 
-import java.util.stream.Stream;
+/** Interface representing a geometric element with a size. The exact meaning
+ * of the size will vary between spaces and dimensions. For example, the size
+ * of a line is its length, while the size of a polygon is its area.
+ */
+public interface Sized {
 
-import org.apache.commons.geometry.core.Point;
+    /** Get the size of the instance.
+     * @return the size of the instance
+     */
+    double getSize();
 
-/** Interface representing an object that can produce region boundaries as a stream
- * of convex subhyperplanes.
- * @param <C> Convex subhyperplane implementation type
- */
-@FunctionalInterface
-public interface BoundarySource<C extends ConvexSubHyperplane<? extends Point<?>>> {
+    /** Return true if the size of the instance is finite.
+     * @return true if the size of the instance is finite
+     */
+    default boolean isFinite() {
+        return Double.isFinite(getSize());
+    }
 
-    /** Return a stream containing the boundaries for this instance.
-     * @return a stream containing the boundaries for this instance
+    /** Return true if the size of the instance is infinite.
+     * @return true if the size of the instance is infinite
      */
-    Stream<C> boundaryStream();
+    default boolean isInfinite() {
+        return Double.isInfinite(getSize());
+    }
 }
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
index 82c8bec..5ff18d8 100644
--- 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
@@ -29,15 +29,15 @@ import org.apache.commons.geometry.core.Transform;
 /** 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
+ * @param <S> Hyperplane convex subset implementation type
  */
-public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>, S extends ConvexSubHyperplane<P>>
+public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>, S extends HyperplaneConvexSubset<P>>
     implements HyperplaneBoundedRegion<P> {
     /** 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.
+    /** Simple constructor. Callers are responsible for ensuring that the given list of boundaries
+     * define a convex region. No validation is performed.
      * @param boundaries the boundaries of the convex region
      */
     protected AbstractConvexHyperplaneBoundedRegion(final List<S> boundaries) {
@@ -121,14 +121,13 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
         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.
+    /** Trim the given hyperplane subset to the portion contained inside this instance.
+     * @param sub hyperplane subset to trim. Null is returned if the subset 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;
+    public HyperplaneConvexSubset<P> trim(final HyperplaneConvexSubset<P> sub) {
+        HyperplaneConvexSubset<P> remaining = sub;
         for (final S boundary : boundaries) {
             remaining = remaining.split(boundary.getHyperplane()).getMinus();
             if (remaining == null) {
@@ -154,13 +153,13 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
      * @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 boundaryType the type used for the boundary hyperplane subsets
      * @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 Transform<P> transform, final R thisInstance, final Class<S> boundaryType,
             final Function<List<S>, R> factory) {
 
         if (isFull()) {
@@ -174,7 +173,7 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
 
         // determine if the hyperplanes should be reversed
         final S boundary = origBoundaries.get(0);
-        ConvexSubHyperplane<P> tBoundary = boundary.transform(transform);
+        HyperplaneConvexSubset<P> tBoundary = boundary.transform(transform);
 
         final boolean reverseDirection = swapsInsideOutside(transform);
 
@@ -182,7 +181,7 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
         if (reverseDirection) {
             tBoundary = tBoundary.reverse();
         }
-        tBoundaries.add(subhpType.cast(tBoundary));
+        tBoundaries.add(boundaryType.cast(tBoundary));
 
         for (int i = 1; i < origBoundaries.size(); ++i) {
             tBoundary = origBoundaries.get(i).transform(transform);
@@ -191,7 +190,7 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
                 tBoundary = tBoundary.reverse();
             }
 
-            tBoundaries.add(subhpType.cast(tBoundary));
+            tBoundaries.add(boundaryType.cast(tBoundary));
         }
 
         return factory.apply(tBoundaries);
@@ -216,22 +215,22 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
      * @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 boundaryType the type used for the boundary hyperplane subsets
      * @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 Hyperplane<P> splitter, final R thisInstance, final Class<S> boundaryType,
             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())));
+            final R minus = factory.apply(Arrays.asList(boundaryType.cast(splitter.span())));
+            final R plus = factory.apply(Arrays.asList(boundaryType.cast(splitter.reverse().span())));
 
             return new Split<>(minus, plus);
         } else {
-            final ConvexSubHyperplane<P> trimmedSplitter = trim(splitter.span());
+            final HyperplaneConvexSubset<P> trimmedSplitter = trim(splitter.span());
 
             if (trimmedSplitter == null) {
                 // The splitter lies entirely outside of the region; we need
@@ -246,10 +245,10 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
             final List<S> minusBoundaries = new ArrayList<>();
             final List<S> plusBoundaries = new ArrayList<>();
 
-            splitBoundaries(splitter, subhpType, minusBoundaries, plusBoundaries);
+            splitBoundaries(splitter, boundaryType, minusBoundaries, plusBoundaries);
 
-            minusBoundaries.add(subhpType.cast(trimmedSplitter));
-            plusBoundaries.add(subhpType.cast(trimmedSplitter.reverse()));
+            minusBoundaries.add(boundaryType.cast(trimmedSplitter));
+            plusBoundaries.add(boundaryType.cast(trimmedSplitter.reverse()));
 
             return new Split<>(factory.apply(minusBoundaries), factory.apply(plusBoundaries));
         }
@@ -284,7 +283,7 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
         double minusSize = 0;
         double plusSize = 0;
 
-        Split<? extends ConvexSubHyperplane<P>> split;
+        Split<? extends HyperplaneConvexSubset<P>> split;
         SplitLocation loc;
 
         for (final S boundary : boundaries) {
@@ -309,18 +308,18 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
     /** Split the boundaries of the region by the given hyperplane, adding the split parts into the
      * corresponding lists.
      * @param splitter splitting hyperplane
-     * @param subhpType the type used for the boundary subhyperplanes
+     * @param boundaryType the type used for the boundary hyperplane subsets
      * @param minusBoundaries list that will contain the portions of the boundaries on the minus side
      *      of the splitting hyperplane
      * @param plusBoundaries list that will contain the portions of the boundaries on the plus side of
      *      the splitting hyperplane
      */
-    private void splitBoundaries(final Hyperplane<P> splitter, final Class<S> subhpType,
+    private void splitBoundaries(final Hyperplane<P> splitter, final Class<S> boundaryType,
             final List<S> minusBoundaries, final List<S> plusBoundaries) {
 
-        Split<? extends ConvexSubHyperplane<P>> split;
-        ConvexSubHyperplane<P> minusBoundary;
-        ConvexSubHyperplane<P> plusBoundary;
+        Split<? extends HyperplaneConvexSubset<P>> split;
+        HyperplaneConvexSubset<P> minusBoundary;
+        HyperplaneConvexSubset<P> plusBoundary;
 
         for (final S boundary : boundaries) {
             split = boundary.split(splitter);
@@ -329,37 +328,36 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
             plusBoundary = split.getPlus();
 
             if (minusBoundary != null) {
-                minusBoundaries.add(subhpType.cast(minusBoundary));
+                minusBoundaries.add(boundaryType.cast(minusBoundary));
             }
 
             if (plusBoundary != null) {
-                plusBoundaries.add(subhpType.cast(plusBoundary));
+                plusBoundaries.add(boundaryType.cast(plusBoundary));
             }
         }
     }
 
-    /** Internal class encapsulating the logic for building convex region boundaries from collections of
-     * hyperplanes.
+    /** Internal class encapsulating the logic for building convex region boundaries from collections of hyperplanes.
      * @param <P> Point implementation type
-     * @param <S> ConvexSubHyperplane implementation type
+     * @param <S> Hyperplane convex subset implementation type
      */
-    protected static class ConvexRegionBoundaryBuilder<P extends Point<P>, S extends ConvexSubHyperplane<P>> {
+    protected static class ConvexRegionBoundaryBuilder<P extends Point<P>, S extends HyperplaneConvexSubset<P>> {
 
-        /** Convex subhyperplane implementation type. */
-        private final Class<S> subhyperplaneType;
+        /** Hyperplane convex subset implementation type. */
+        private final Class<S> subsetType;
 
-        /** Construct a new instance for building convex region boundaries with the given convex subhyperplane
-         * implementation type.
-         * @param subhyperplaneType Convex subhyperplane implementation type
+        /** Construct a new instance for building convex region boundaries with the given hyperplane
+         * convex subset implementation type.
+         * @param subsetType Hyperplane convex subset implementation type
          */
-        public ConvexRegionBoundaryBuilder(final Class<S> subhyperplaneType) {
-            this.subhyperplaneType = subhyperplaneType;
+        public ConvexRegionBoundaryBuilder(final Class<S> subsetType) {
+            this.subsetType = subsetType;
         }
 
-        /** Compute a list of convex subhyperplanes representing the boundaries of the convex region
+        /** Compute a list of hyperplane convex subsets 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
+         * @return a list of hyperplane convex subsets representing the boundaries of the convex region
          * @throws IllegalArgumentException if the given hyperplanes do not form a convex region
          */
         public List<S> build(final Iterable<? extends Hyperplane<P>> bounds) {
@@ -368,14 +366,14 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
 
             // cut each hyperplane by every other hyperplane in order to get the region boundaries
             int boundIdx = 0;
-            ConvexSubHyperplane<P> boundary;
+            HyperplaneConvexSubset<P> boundary;
 
             for (final Hyperplane<P> currentBound : bounds) {
                 ++boundIdx;
 
                 boundary = splitBound(currentBound, bounds, boundIdx);
                 if (boundary != null) {
-                    boundaries.add(subhyperplaneType.cast(boundary));
+                    boundaries.add(subsetType.cast(boundary));
                 }
             }
 
@@ -388,18 +386,18 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
         }
 
         /** Split the given bounding hyperplane by all of the other hyperplanes in the given collection, returning the
-         * remaining subhyperplane.
+         * remaining hyperplane subset.
          * @param currentBound the bound to split; this value is assumed to have come from {@code bounds}
          * @param bounds collection of bounds to use to split {@code currentBound}
          * @param currentBoundIdx the index of {@code currentBound} in {@code bounds}
-         * @return the part of {@code currentBound}'s subhyperplane that lies on the minus side of all of the
+         * @return the part of {@code currentBound}'s hyperplane subset that lies on the minus side of all of the
          *      splitting hyperplanes
          * @throws IllegalArgumentException if the hyperplanes do not form a convex region
          */
-        private ConvexSubHyperplane<P> splitBound(final Hyperplane<P> currentBound,
+        private HyperplaneConvexSubset<P> splitBound(final Hyperplane<P> currentBound,
                 final Iterable<? extends Hyperplane<P>> bounds, final int currentBoundIdx) {
 
-            ConvexSubHyperplane<P> boundary = currentBound.span();
+            HyperplaneConvexSubset<P> boundary = currentBound.span();
 
             int splitterIdx = 0;
             for (final Hyperplane<P> splitter : bounds) {
@@ -410,15 +408,15 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
 
                     if (currentBoundIdx > splitterIdx) {
                         // this hyperplane is duplicated in the list; skip all but the
-                        // first insertion of its subhyperplane
+                        // first insertion of its hyperplane subset
                         return null;
                     }
                 } else {
-                    // split the subhyperplane
-                    final Split<? extends ConvexSubHyperplane<P>> split = boundary.split(splitter);
+                    // split the boundary
+                    final Split<? extends HyperplaneConvexSubset<P>> split = boundary.split(splitter);
 
                     if (split.getLocation() == SplitLocation.NEITHER) {
-                        // the subhyperplane lies directly on the splitter
+                        // the boundary lies directly on the splitter
 
                         if (!currentBound.similarOrientation(splitter)) {
                             // two or more splitters are coincident and have opposite
@@ -426,7 +424,7 @@ public abstract class AbstractConvexHyperplaneBoundedRegion<P extends Point<P>,
                             // of both
                             throw nonConvexException(bounds);
                         } else if (currentBoundIdx > splitterIdx) {
-                         // two or more hyperplanes are equivalent; only use the boundary
+                            // two or more hyperplanes are equivalent; only use the boundary
                             // from the first one and return null for this one
                             return null;
                         }
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/AbstractRegionEmbeddingHyperplaneSubset.java
similarity index 81%
rename from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractEmbeddingSubHyperplane.java
rename to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/AbstractRegionEmbeddingHyperplaneSubset.java
index acbce9c..e72071a 100644
--- 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/AbstractRegionEmbeddingHyperplaneSubset.java
@@ -18,18 +18,19 @@ package org.apache.commons.geometry.core.partitioning;
 
 import org.apache.commons.geometry.core.Point;
 import org.apache.commons.geometry.core.Region;
+import org.apache.commons.geometry.core.RegionEmbedding;
 import org.apache.commons.geometry.core.RegionLocation;
 
-/** Abstract base class for subhyperplane implementations that embed a lower-dimension region through
+/** Abstract base class for hyperplane subset 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<
+public abstract class AbstractRegionEmbeddingHyperplaneSubset<
     P extends Point<P>,
     S extends Point<S>,
-    H extends EmbeddingHyperplane<P, S>> implements SubHyperplane<P> {
+    H extends EmbeddingHyperplane<P, S>> implements HyperplaneSubset<P>, RegionEmbedding<P, S> {
 
     /** {@inheritDoc} */
     @Override
@@ -45,20 +46,14 @@ public abstract class AbstractEmbeddingSubHyperplane<
 
     /** {@inheritDoc} */
     @Override
-    public boolean isInfinite() {
-        return Double.isInfinite(getSize());
+    public S toSubspace(final P pt) {
+        return getHyperplane().toSubspace(pt);
     }
 
     /** {@inheritDoc} */
     @Override
-    public boolean isFinite() {
-        return Double.isFinite(getSize());
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public double getSize() {
-        return getSubspaceRegion().getSize();
+    public P toSpace(final S pt) {
+        return getHyperplane().toSpace(pt);
     }
 
     /** {@inheritDoc} */
@@ -98,8 +93,7 @@ public abstract class AbstractEmbeddingSubHyperplane<
     @Override
     public abstract H getHyperplane();
 
-    /** Return the embedded subspace region for this instance.
-     * @return the embedded subspace region for this instance
-     */
+    /** {@inheritDoc} */
+    @Override
     public abstract HyperplaneBoundedRegion<S> getSubspaceRegion();
 }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java
index 6dffc6b..625faf1 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/BoundarySource.java
@@ -21,11 +21,11 @@ import java.util.stream.Stream;
 import org.apache.commons.geometry.core.Point;
 
 /** Interface representing an object that can produce region boundaries as a stream
- * of convex subhyperplanes.
- * @param <C> Convex subhyperplane implementation type
+ * of hyperplane convex subsets.
+ * @param <C> Hyperplane convex subset implementation type
  */
 @FunctionalInterface
-public interface BoundarySource<C extends ConvexSubHyperplane<? extends Point<?>>> {
+public interface BoundarySource<C extends HyperplaneConvexSubset<? extends Point<?>>> {
 
     /** Return a stream containing the boundaries for this instance.
      * @return a stream containing the boundaries for this instance
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 6ae7ff1..aed3b33 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
@@ -19,8 +19,8 @@ package org.apache.commons.geometry.core.partitioning;
 import org.apache.commons.geometry.core.Point;
 import org.apache.commons.geometry.core.Transform;
 
-/** Interface representing a hyperplane, which is a subspace of dimension
- * one less than its surrounding space. (A hyperplane in Euclidean 3D space,
+/** Interface representing a hyperplane, which in a space of dimension {@code n} is
+ * a subspace of dimension {@code n - 1}. (A hyperplane in Euclidean 3D space,
  * for example, is a 2 dimensional plane.)
  *
  * <p>
@@ -36,7 +36,7 @@ import org.apache.commons.geometry.core.Transform;
  *
  * @param <P> Point implementation type
  * @see HyperplaneLocation
- * @see SubHyperplane
+ * @see HyperplaneSubset
  */
 public interface Hyperplane<P extends Point<P>> {
 
@@ -92,9 +92,9 @@ public interface Hyperplane<P extends Point<P>> {
      */
     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
+    /** Return a {@link HyperplaneConvexSubset} spanning this entire hyperplane. The returned
+     * subset contains all points lying in this hyperplane and no more.
+     * @return a {@link HyperplaneConvexSubset} containing all points lying in this hyperplane
      */
-    ConvexSubHyperplane<P> span();
+    HyperplaneConvexSubset<P> span();
 }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneBoundedRegion.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneBoundedRegion.java
index 0718d09..982c60a 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneBoundedRegion.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneBoundedRegion.java
@@ -21,7 +21,7 @@ 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
+ * with finite sizes as well as infinite and empty regions. Regions of this type
  * can be recursively split by hyperplanes into similar regions.
  * @param <P> Point implementation type
  */
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/HyperplaneConvexSubset.java
similarity index 67%
rename from commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/ConvexSubHyperplane.java
rename to commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneConvexSubset.java
index 606e233..96eb987 100644
--- 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/HyperplaneConvexSubset.java
@@ -19,32 +19,32 @@ 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
+/** Extension of the {@link HyperplaneSubset} 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> {
+public interface HyperplaneConvexSubset<P extends Point<P>> extends HyperplaneSubset<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
+    /** Reverse the orientation of the hyperplane for this instance, returning the result as
+     * a new instance. The returned subset contains the same points but has a reversed orientation.
+     * @return a hyperplane convex subset representing the same region but with the
      *      opposite orientation.
      */
-    ConvexSubHyperplane<P> reverse();
+    HyperplaneConvexSubset<P> reverse();
 
     /** {@inheritDoc}
      *
-     * <p>The parts resulting from a split operation with a convex subhyperplane
+     * <p>The parts resulting from a split operation with a convex subset
      * are guaranteed to also be convex.</p>
      */
     @Override
-    Split<? extends ConvexSubHyperplane<P>> split(Hyperplane<P> splitter);
+    Split<? extends HyperplaneConvexSubset<P>> split(Hyperplane<P> splitter);
 
     /** {@inheritDoc}
      *
-     * <p>Convex subhyperplanes subjected to affine transformations remain
+     * <p>Hyperplane convex subsets subjected to affine transformations remain
      * convex.</p>
      */
     @Override
-    ConvexSubHyperplane<P> transform(Transform<P> transform);
+    HyperplaneConvexSubset<P> transform(Transform<P> transform);
 }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneSubset.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneSubset.java
new file mode 100644
index 0000000..8d3f73b
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/HyperplaneSubset.java
@@ -0,0 +1,130 @@
+/*
+ * 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.List;
+
+import org.apache.commons.geometry.core.Point;
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Sized;
+import org.apache.commons.geometry.core.Transform;
+
+/** Interface representing a subset of the points lying on a hyperplane. Examples include
+ * rays and line segments in Euclidean 2D space and triangular facets in Euclidean 3D space.
+ * Hyperplane subsets can have finite or infinite size and can represent contiguous regions
+ * of the hyperplane (as in the examples aboves); multiple, disjoint regions; or the
+ * {@link Hyperplane#span() entire hyperplane}.
+ *
+ * @param <P> Point implementation type
+ * @see Hyperplane
+ */
+public interface HyperplaneSubset<P extends Point<P>> extends Splittable<P, HyperplaneSubset<P>>, Sized {
+
+    /** Get the hyperplane containing this instance.
+     * @return the hyperplane containing this instance
+     */
+    Hyperplane<P> getHyperplane();
+
+    /** 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();
+
+    /** Classify a point with respect to the subset region. The point is classified as follows:
+     * <ul>
+     *  <li>{@link RegionLocation#INSIDE INSIDE} - The point lies on the hyperplane
+     *      and inside of the subset region.</li>
+     *  <li>{@link RegionLocation#BOUNDARY BOUNDARY} - The point lies on the hyperplane
+     *      and is on the boundary of the subset 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
+     *      subset region.</li>
+     * </ul>
+     * @param pt the point to classify
+     * @return classification of the point with respect to the hyperplane
+     *      and subspace region
+     */
+    RegionLocation classify(P pt);
+
+    /** Return true if the hyperplane subset contains the given point, meaning that the point
+     * lies on the hyperplane and is not on the outside of the subset region.
+     * @param pt the point to check
+     * @return true if the point is contained in the hyperplane subset
+     */
+    default boolean contains(P pt) {
+        final RegionLocation loc = classify(pt);
+        return loc != null && loc != RegionLocation.OUTSIDE;
+    }
+
+    /** Return the closest point to the argument that is contained in the subset
+     * (ie, not classified as {@link RegionLocation#OUTSIDE outside}), or null if no
+     * such point exists.
+     * @param pt the reference point
+     * @return the closest point to the reference point that is contained in the subset,
+     *      or null if no such point exists
+     */
+    P closest(P pt);
+
+    /** Return a {@link Builder} instance for joining multiple
+     * hyperplane subsets together.
+     * @return a new builder instance
+     */
+    Builder<P> builder();
+
+    /** Return a new hyperplane subset resulting from the application of the given transform.
+     * The current instance is not modified.
+     * @param transform the transform instance to apply
+     * @return new transformed hyperplane subset
+     */
+    HyperplaneSubset<P> transform(Transform<P> transform);
+
+    /** Convert this instance into a list of convex child subsets.
+     * @return a list of hyperplane convex subsets representing the same subspace
+     *      region as this instance
+     */
+    List<? extends HyperplaneConvexSubset<P>> toConvex();
+
+    /** Interface for joining multiple {@link HyperplaneSubset}s into a single
+     * instance.
+     * @param <P> Point implementation type
+     */
+    interface Builder<P extends Point<P>> {
+
+        /** Add a {@link HyperplaneSubset} instance to the builder.
+         * @param sub subset to add to this instance
+         */
+        void add(HyperplaneSubset<P> sub);
+
+        /** Add a {@link HyperplaneConvexSubset} instance to the builder.
+         * @param sub convex subset to add to this instance
+         */
+        void add(HyperplaneConvexSubset<P> sub);
+
+        /** Get a {@link HyperplaneSubset} representing the union
+         * of all input subsets.
+         * @return subset representing the union of all input subsets
+         */
+        HyperplaneSubset<P> build();
+    }
+}
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
deleted file mode 100644
index 74a4169..0000000
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/SubHyperplane.java
+++ /dev/null
@@ -1,153 +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.List;
-
-import org.apache.commons.geometry.core.Point;
-import org.apache.commons.geometry.core.RegionLocation;
-import org.apache.commons.geometry.core.Transform;
-
-/** Interface representing subhyperplanes.
- *
- * <p>
- * A subhyperplane is a portion of a hyperplane. For example, the triangular
- * facet of a polyhedron in Euclidean 3D space is a subhyperplane because
- * its interior represents a subset of the plane defined by the three points.
- * While hyperplanes always extend through the entire space that surrounds
- * them, subhyperplanes have no such restriction: they can represent a single,
- * small portion of the hyperplane (as in the example above); multiple, disjoint
- * regions; or the entire hyperplane.
- *
- * @param <P> Point implementation type
- * @see Hyperplane
- */
-public interface SubHyperplane<P extends Point<P>> extends Splittable<P, SubHyperplane<P>> {
-
-    /** Get the hyperplane that this instance is embedded in.
-     * @return the hyperplane that this instance is embedded in.
-     */
-    Hyperplane<P> getHyperplane();
-
-    /** 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();
-
-    /** 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();
-
-    /** 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
-     */
-    RegionLocation classify(P point);
-
-    /** 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
-     */
-    default boolean contains(P point) {
-        final RegionLocation loc = classify(point);
-        return loc != null && loc != RegionLocation.OUTSIDE;
-    }
-
-    /** 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
-     */
-    P closest(P point);
-
-    /** Return a {@link Builder} instance for joining multiple
-     * subhyperplanes together.
-     * @return a new builder instance
-     */
-    Builder<P> builder();
-
-    /** 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);
-
-    /** 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();
-
-    /** 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
-         */
-        void add(SubHyperplane<P> sub);
-
-        /** Add a {@link ConvexSubHyperplane} instance to the builder.
-         * @param sub convex subhyperplane to add to this instance
-         */
-        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/bsp/AbstractBSPTree.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTree.java
index a7d7a2b..55761d2 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTree.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTree.java
@@ -23,7 +23,7 @@ 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.HyperplaneConvexSubset;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
 import org.apache.commons.geometry.core.partitioning.Split;
@@ -49,7 +49,7 @@ import org.apache.commons.geometry.core.partitioning.SplitLocation;
  *      <li>Since the methods used to construct and modify trees can vary by use case, no public API is provided
  *      for manipulating the tree. Subclasses are expected to use the protected methods of this class to
  *      create their own. For tree construction, subclasses are expected to pass their own {@link SubtreeInitializer}
- *      instances to {@link #insert(ConvexSubHyperplane, SubtreeInitializer) insert} and
+ *      instances to {@link #insert(HyperplaneConvexSubset, SubtreeInitializer) insert} and
  *      {@link #cutNode(AbstractNode, Hyperplane, SubtreeInitializer) cutNode} in order to set the correct properties on
  *      tree nodes. To support tree copying, subclasses must also override
  *      {@link #copyNodeProperties(AbstractNode, AbstractNode) copyNodeProperties}.</li>
@@ -212,17 +212,15 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
     /** 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.
+     * connections and cuts. Subclasses should override this method to copy additional
+     * properties stored on nodes.
      * @param src source node
      * @param dst destination node
      */
-    protected void copyNodeProperties(final N src, final N dst) {
-        // no-op
-    }
+    protected abstract void copyNodeProperties(N src, N dst);
 
     /** Create a non-structural copy of the given node. Properties such as parent/child
-     * connections and cut subhyperplanes are <em>not</em> copied.
+     * connections and cuts 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)
@@ -250,7 +248,7 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
             copyNodeProperties(src, dst);
 
             // copy the subtree structure
-            ConvexSubHyperplane<P> cut = null;
+            HyperplaneConvexSubset<P> cut = null;
             N minus = null;
             N plus = null;
 
@@ -333,7 +331,7 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
      * @param start the node to begin the search with
      * @param pt the point to check
      * @param cutRule value determining the search behavior when the test point
-     *      lies directly on the cut subhyperplane of an internal node
+     *      lies directly on the cut of an internal node
      * @return the smallest node in the tree containing the point
      */
     protected N findNode(final N start, final P pt, final FindNodeCutRule cutRule) {
@@ -425,7 +423,7 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
      *      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>the remaining portion becomes the cut hyperplane subset for the node,</li>
      *              <li>two new child nodes are created and initialized with
      *              {@code subtreeInitializer}, and</li>
      *              <li>true is returned.</li>
@@ -453,8 +451,8 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
      * @param subtreeInitializer object used to initialize any newly-created subtrees
      * @return true if the node was cut and two new child nodes were created;
      *      otherwise false
-     * @see #trimToNode(AbstractNode, ConvexSubHyperplane)
-     * @see #setNodeCut(AbstractNode, ConvexSubHyperplane, SubtreeInitializer)
+     * @see #trimToNode(AbstractNode, HyperplaneConvexSubset)
+     * @see #setNodeCut(AbstractNode, HyperplaneConvexSubset, SubtreeInitializer)
      * @see #removeNodeCut(AbstractNode)
      * @see #invalidate()
      */
@@ -463,7 +461,7 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
 
         // cut the hyperplane using all hyperplanes from this node up
         // to the root
-        final ConvexSubHyperplane<P> cut = trimToNode(node, cutter.span());
+        final HyperplaneConvexSubset<P> cut = trimToNode(node, cutter.span());
         if (cut == null || cut.isEmpty()) {
             // insertion failed; the node was not cut
             removeNodeCut(node);
@@ -474,18 +472,18 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
         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.
+    /** Trim the given hyperplane convex subset to the region defined by the given node. This method
+     * cuts the subset with the cut hyperplanes (binary partitioners) of all parent nodes up to the
+     * root and returns the trimmed subset or {@code null} if the subset lies outside of the region
+     * defined by the node.
      *
-     * <p>If the subhyperplane is directly coincident with a binary partitioner of a parent node,
+     * <p>If the subset 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
+     *      <li>If the orientations are <strong>similar</strong>, then the subset 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
+     *      <li>If the orientations are <strong>different</strong> (ie, opposite), then the subset
      *      is determined to lie <em>inside</em> of the node's region and the fit operation continues
      *      with the remaining parent nodes.</li>
      * </ul>
@@ -503,24 +501,24 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
      * 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
+     * @param node the node representing the region to fit the hyperplane subset to
+     * @param sub the hyperplane subset to trim to the node's region
+     * @return the trimmed hyperplane subset or null if the given hyperplane subset does not intersect
      *      the node's region
      */
-    protected ConvexSubHyperplane<P> trimToNode(final N node, final ConvexSubHyperplane<P> sub) {
+    protected HyperplaneConvexSubset<P> trimToNode(final N node, final HyperplaneConvexSubset<P> sub) {
 
-        ConvexSubHyperplane<P> result = sub;
+        HyperplaneConvexSubset<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());
+            final Split<? extends HyperplaneConvexSubset<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
+                // we say the subset 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;
@@ -556,19 +554,19 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
         return false;
     }
 
-    /** Set the cut subhyperplane for the given node. Two new child nodes are created for the given
+    /** Set the cut hyperplane subset for the given node. Two new child nodes are created for the
      * node and the new subtree is initialized with {@code subtreeInitializer}.
      *
      * <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>
+     * hyperplane subset. It is the responsibility of the caller to ensure that the
+     * hyperplane subset 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
+     * @param cut the hyperplane convex subset to set as the node cut
      * @param subtreeInitializer object used to initialize the newly-created subtree
      */
-    protected void setNodeCut(final N node, final ConvexSubHyperplane<P> cut,
+    protected void setNodeCut(final N node, final HyperplaneConvexSubset<P> cut,
             final SubtreeInitializer<N> subtreeInitializer) {
 
         node.setSubtree(cut, createNode(), createNode());
@@ -578,35 +576,35 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
         invalidate();
     }
 
-    /** Insert the given convex subhyperplane into the tree, starting at the root node. Any subtrees
+    /** Insert the given hyperplane convex subset into the tree, starting at the root node. Any subtrees
      * created are initialized with {@code subtreeInit}.
-     * @param convexSub convex subhyperplane to insert into the tree
+     * @param convexSub hyperplane convex subset to insert into the tree
      * @param subtreeInit object used to initialize newly created subtrees
      */
-    protected void insert(final ConvexSubHyperplane<P> convexSub, final SubtreeInitializer<N> subtreeInit) {
+    protected void insert(final HyperplaneConvexSubset<P> convexSub, final SubtreeInitializer<N> subtreeInit) {
         insertRecursive(getRoot(), convexSub,
                 convexSub.getHyperplane().span(), subtreeInit);
     }
 
-    /** Recursively insert a subhyperplane into the tree at the given node.
+    /** Recursively insert a hyperplane convex subset 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
+     * @param insert the hyperplane subset to insert
+     * @param trimmed hyperplane subset containing the result of splitting the entire
      *      space with each hyperplane from this node to the root
      * @param subtreeInit object used to initialize newly created subtrees
      */
-    private void insertRecursive(final N node, final ConvexSubHyperplane<P> insert,
-            final ConvexSubHyperplane<P> trimmed, final SubtreeInitializer<N> subtreeInit) {
+    private void insertRecursive(final N node, final HyperplaneConvexSubset<P> insert,
+            final HyperplaneConvexSubset<P> trimmed, final SubtreeInitializer<N> subtreeInit) {
         if (node.isLeaf()) {
             setNodeCut(node, trimmed, subtreeInit);
         } else {
-            final Split<? extends ConvexSubHyperplane<P>> insertSplit = insert.split(node.getCutHyperplane());
+            final Split<? extends HyperplaneConvexSubset<P>> insertSplit = insert.split(node.getCutHyperplane());
 
-            final ConvexSubHyperplane<P> minus = insertSplit.getMinus();
-            final ConvexSubHyperplane<P> plus = insertSplit.getPlus();
+            final HyperplaneConvexSubset<P> minus = insertSplit.getMinus();
+            final HyperplaneConvexSubset<P> plus = insertSplit.getPlus();
 
             if (minus != null || plus != null) {
-                final Split<? extends ConvexSubHyperplane<P>> trimmedSplit = trimmed.split(node.getCutHyperplane());
+                final Split<? extends HyperplaneConvexSubset<P>> trimmedSplit = trimmed.split(node.getCutHyperplane());
 
                 if (minus != null) {
                     insertRecursive(node.getMinus(), minus, trimmedSplit.getMinus(), subtreeInit);
@@ -642,7 +640,7 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
     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);
+            final HyperplaneConvexSubset<P> transformedCut = node.getCut().transform(t);
 
             // transform our children
             transformRecursive(node.getMinus(), t, swapChildren);
@@ -680,29 +678,29 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
         }
     }
 
-    /** Split the subtree rooted at the given node by a partitioning convex subhyperplane defined
+    /** Split the subtree rooted at the given node by a partitioning convex subset 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
+     * @param partitioner partitioning convex subset
      * @return node containing the split subtree
      */
-    protected N splitSubtree(final N node, final ConvexSubHyperplane<P> partitioner) {
+    protected N splitSubtree(final N node, final HyperplaneConvexSubset<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
+    /** Split the given leaf node by a partitioning convex subset defined on the
      * same region and import it into this tree.
      * @param node the leaf node to split
-     * @param partitioner partitioning convex subhyperplane
+     * @param partitioner partitioning convex subset
      * @return node containing the split subtree
      */
-    private N splitLeafNode(final N node, final ConvexSubHyperplane<P> partitioner) {
+    private N splitLeafNode(final N node, final HyperplaneConvexSubset<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();
@@ -711,16 +709,17 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
         return parent;
     }
 
-    /** Split the given internal node by a partitioning convex subhyperplane defined on the same region
+    /** Split the given internal node by a partitioning convex subset 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
+     * @param partitioner partitioning convex subset
      * @return node containing the split subtree
      */
-    private N splitInternalNode(final N node, final ConvexSubHyperplane<P> partitioner) {
+    private N splitInternalNode(final N node, final HyperplaneConvexSubset<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 Split<? extends HyperplaneConvexSubset<P>> partitionerSplit = partitioner.split(node.getCutHyperplane());
+        final Split<? extends HyperplaneConvexSubset<P>> nodeCutSplit =
+                node.getCut().split(partitioner.getHyperplane());
 
         final SplitLocation partitionerSplitSide = partitionerSplit.getLocation();
         final SplitLocation nodeCutSplitSide = nodeCutSplit.getLocation();
@@ -822,15 +821,15 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
         /** 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 hyperplane convex subset cutting the node's region; this will be null for leaf nodes. */
+        private HyperplaneConvexSubset<P> cut;
 
-        /** The node lying on the minus side of the cut subhyperplane; this will be null
+        /** The node lying on the minus side of the cut hyperplane; 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
+        /** The node lying on the plus side of the cut hyperplane; this will be null
          * for leaf nodes.
          */
         private N plus;
@@ -961,7 +960,7 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
 
         /** {@inheritDoc} */
         @Override
-        public ConvexSubHyperplane<P> getCut() {
+        public HyperplaneConvexSubset<P> getCut() {
             return cut;
         }
 
@@ -985,7 +984,7 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
 
         /** {@inheritDoc} */
         @Override
-        public ConvexSubHyperplane<P> trim(final ConvexSubHyperplane<P> sub) {
+        public HyperplaneConvexSubset<P> trim(final HyperplaneConvexSubset<P> sub) {
             return getTree().trimToNode(getSelf(), sub);
         }
 
@@ -1005,14 +1004,14 @@ public abstract class AbstractBSPTree<P extends Point<P>, N extends AbstractBSPT
          * 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
+         * ensuring that any given hyperplane subset 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 newCut the new cut hyperplane subset 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) {
+        protected void setSubtree(final HyperplaneConvexSubset<P> newCut, final N newMinus, final N newPlus) {
             this.cut = newCut;
 
             final N self = getSelf();
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java
index 9a56876..1df224b 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTree.java
@@ -26,13 +26,13 @@ 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.BoundarySource;
-import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
 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.HyperplaneSubset;
 import org.apache.commons.geometry.core.partitioning.bsp.BSPTreeVisitor.ClosestFirstVisitor;
 
 /** Abstract {@link BSPTree} specialized for representing regions of space. For example,
@@ -146,75 +146,75 @@ public abstract class AbstractRegionBSPTree<
         return boundarySize;
     }
 
-    /** Insert a subhyperplane into the tree, using the default {@link RegionCutRule} of
+    /** Insert a hyperplane subset into the tree, using the default {@link RegionCutRule} of
      * {@link RegionCutRule#MINUS_INSIDE MINUS_INSIDE}.
-     * @param sub the subhyperplane to insert into the tree
+     * @param sub the hyperplane subset to insert into the tree
      */
-    public void insert(final SubHyperplane<P> sub) {
+    public void insert(final HyperplaneSubset<P> sub) {
         insert(sub, DEFAULT_REGION_CUT_RULE);
     }
 
-    /** Insert a subhyperplane into the tree.
-     * @param sub the subhyperplane to insert into the tree
+    /** Insert a hyperplane subset into the tree.
+     * @param sub the hyperplane subset to insert into the tree
      * @param cutRule rule used to determine the region locations of new child nodes
      */
-    public void insert(final SubHyperplane<P> sub, final RegionCutRule cutRule) {
+    public void insert(final HyperplaneSubset<P> sub, final RegionCutRule cutRule) {
         insert(sub.toConvex(), cutRule);
     }
 
-    /** Insert a convex subhyperplane into the tree, using the default {@link RegionCutRule} of
+    /** Insert a hyperplane convex subset into the tree, using the default {@link RegionCutRule} of
      * {@link RegionCutRule#MINUS_INSIDE MINUS_INSIDE}.
-     * @param convexSub the convex subhyperplane to insert into the tree
+     * @param convexSub the hyperplane convex subset to insert into the tree
      */
-    public void insert(final ConvexSubHyperplane<P> convexSub) {
+    public void insert(final HyperplaneConvexSubset<P> convexSub) {
         insert(convexSub, DEFAULT_REGION_CUT_RULE);
     }
 
-    /** Insert a convex subhyperplane into the tree.
-     * @param convexSub the convex subhyperplane to insert into the tree
+    /** Insert a hyperplane convex subset into the tree.
+     * @param convexSub the hyperplane convex subset to insert into the tree
      * @param cutRule rule used to determine the region locations of new child nodes
      */
-    public void insert(final ConvexSubHyperplane<P> convexSub, final RegionCutRule cutRule) {
+    public void insert(final HyperplaneConvexSubset<P> convexSub, final RegionCutRule cutRule) {
         insert(convexSub, getSubtreeInitializer(cutRule));
     }
 
-    /** Insert a set of convex subhyperplanes into the tree, using the default {@link RegionCutRule} of
+    /** Insert a set of hyperplane convex subsets into the tree, using the default {@link RegionCutRule} of
      * {@link RegionCutRule#MINUS_INSIDE MINUS_INSIDE}.
-     * @param convexSubs iterable containing a collection of subhyperplanes
+     * @param convexSubs iterable containing a collection of hyperplane convex subsets
      *      to insert into the tree
      */
-    public void insert(final Iterable<? extends ConvexSubHyperplane<P>> convexSubs) {
+    public void insert(final Iterable<? extends HyperplaneConvexSubset<P>> convexSubs) {
         insert(convexSubs, DEFAULT_REGION_CUT_RULE);
     }
 
-    /** Insert a set of convex subhyperplanes into the tree.
-     * @param convexSubs iterable containing a collection of subhyperplanes
+    /** Insert a set of hyperplane convex subsets into the tree.
+     * @param convexSubs iterable containing a collection of hyperplane convex subsets
      *      to insert into the tree
      * @param cutRule rule used to determine the region locations of new child nodes
      */
-    public void insert(final Iterable<? extends ConvexSubHyperplane<P>> convexSubs, final RegionCutRule cutRule) {
-        for (final ConvexSubHyperplane<P> convexSub : convexSubs) {
+    public void insert(final Iterable<? extends HyperplaneConvexSubset<P>> convexSubs, final RegionCutRule cutRule) {
+        for (final HyperplaneConvexSubset<P> convexSub : convexSubs) {
             insert(convexSub, cutRule);
         }
     }
 
-    /** Insert all convex subhyperplanes from the given source into the tree, using the default
+    /** Insert all hyperplane convex subsets from the given source into the tree, using the default
      * {@link RegionCutRule} of {@link RegionCutRule#MINUS_INSIDE MINUS_INSIDE}.
-     * @param boundarySrc source of boundary convex subhyperplanes to insert
+     * @param boundarySrc source of boundary hyperplane subsets to insert
      *      into the tree
      */
-    public void insert(final BoundarySource<? extends ConvexSubHyperplane<P>> boundarySrc) {
+    public void insert(final BoundarySource<? extends HyperplaneConvexSubset<P>> boundarySrc) {
         insert(boundarySrc, DEFAULT_REGION_CUT_RULE);
     }
 
-    /** Insert all convex subhyperplanes from the given source into the tree.
-     * @param boundarySrc source of boundary convex subhyperplanes to insert
+    /** Insert all hyperplane convex subsets from the given source into the tree.
+     * @param boundarySrc source of boundary hyperplane subsets to insert
      *      into the tree
      * @param cutRule rule used to determine the region locations of new child nodes
      */
-    public void insert(final BoundarySource<? extends ConvexSubHyperplane<P>> boundarySrc,
+    public void insert(final BoundarySource<? extends HyperplaneConvexSubset<P>> boundarySrc,
             final RegionCutRule cutRule) {
-        try (Stream<? extends ConvexSubHyperplane<P>> stream = boundarySrc.boundaryStream()) {
+        try (Stream<? extends HyperplaneConvexSubset<P>> stream = boundarySrc.boundaryStream()) {
             stream.forEach(c -> insert(c, cutRule));
         }
     }
@@ -252,18 +252,18 @@ public abstract class AbstractRegionBSPTree<
      * @return an {@link Iterable} for iterating over the boundaries of the region
      * @see #getBoundaries()
      */
-    public Iterable<? extends ConvexSubHyperplane<P>> boundaries() {
+    public Iterable<? extends HyperplaneConvexSubset<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
+     * @param typeConverter function to convert the generic hyperplane subset type into
      *      the type specific for this tree
-     * @param <C> ConvexSubhyperplane implementation type
+     * @param <C> HyperplaneConvexSubset implementation type
      * @return an iterable to iterating the region boundaries
      */
-    protected <C extends ConvexSubHyperplane<P>> Iterable<C> createBoundaryIterable(
-            final Function<ConvexSubHyperplane<P>, C> typeConverter) {
+    protected <C extends HyperplaneConvexSubset<P>> Iterable<C> createBoundaryIterable(
+            final Function<HyperplaneConvexSubset<P>, C> typeConverter) {
 
         return () -> new RegionBoundaryIterator<>(
                 getRoot().nodes().iterator(),
@@ -275,18 +275,18 @@ public abstract class AbstractRegionBSPTree<
      * 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() {
+    public List<? extends HyperplaneConvexSubset<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
+    /** Internal method for creating a list of the region boundaries.
+     * @param typeConverter function to convert the generic convex subset type into
      *      the type specific for this tree
-     * @param <C> ConvexSubhyperplane implementation type
+     * @param <C> HyperplaneConvexSubset implementation type
      * @return a list of the region boundaries
      */
-    protected <C extends ConvexSubHyperplane<P>> List<C> createBoundaryList(
-            final Function<ConvexSubHyperplane<P>, C> typeConverter) {
+    protected <C extends HyperplaneConvexSubset<P>> List<C> createBoundaryList(
+            final Function<HyperplaneConvexSubset<P>, C> typeConverter) {
 
         final List<C> result = new ArrayList<>();
 
@@ -406,7 +406,7 @@ public abstract class AbstractRegionBSPTree<
     }
 
     /** Change this region into its complement. All inside nodes become outside
-     * nodes and vice versa. The orientation of the cut subhyperplanes is not modified.
+     * nodes and vice versa. The orientations of the node cuts are not modified.
      */
     public void complement() {
         complementRecursive(getRoot());
@@ -546,7 +546,7 @@ public abstract class AbstractRegionBSPTree<
         /** 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
+        /** Object representing the part of the node cut hyperplane subset that lies on the
          * region boundary. This is calculated lazily and is only present on internal nodes.
          */
         private RegionCutBoundary<P> cutBoundary;
@@ -625,7 +625,7 @@ public abstract class AbstractRegionBSPTree<
         }
 
         /** 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}
+         * this node's region, then the node's cut is set to the {@link HyperplaneConvexSubset}
          * 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
@@ -676,9 +676,8 @@ public abstract class AbstractRegionBSPTree<
             return getSelf();
         }
 
-        /** 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
+        /** Get the portion of the node's cut that lies on the boundary of the region.
+         * @return the portion of the node's cut that lies on the boundary of
          *      the region
          */
         public RegionCutBoundary<P> getCutBoundary() {
@@ -693,34 +692,33 @@ public abstract class AbstractRegionBSPTree<
             return cutBoundary;
         }
 
-        /** Compute the portion of the node's cut subhyperplane that lies on the boundary of
-         * the region. This method must only be called on internal nodes.
-         * @return object representing the portions of the node's cut subhyperplane that lie
-         *      on the region's boundary
+        /** Compute the portion of the node's cut that lies on the boundary of the region.
+         * This method must only be called on internal nodes.
+         * @return object representing the portions of the node's cut that lie on the region's boundary
          */
         private RegionCutBoundary<P> computeBoundary() {
-            ConvexSubHyperplane<P> sub = getCut();
+            HyperplaneConvexSubset<P> sub = 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();
+            HyperplaneSubset.Builder<P> minusInBuilder = sub.builder();
+            HyperplaneSubset.Builder<P> minusOutBuilder = sub.builder();
 
-            characterizeSubHyperplane(sub, getMinus(), minusInBuilder, minusOutBuilder);
+            characterizeHyperplaneSubset(sub, getMinus(), minusInBuilder, minusOutBuilder);
 
-            List<? extends ConvexSubHyperplane<P>> minusIn = minusInBuilder.build().toConvex();
-            List<? extends ConvexSubHyperplane<P>> minusOut = minusOutBuilder.build().toConvex();
+            List<? extends HyperplaneConvexSubset<P>> minusIn = minusInBuilder.build().toConvex();
+            List<? extends HyperplaneConvexSubset<P>> minusOut = minusOutBuilder.build().toConvex();
 
             // create the result boundary builders
-            SubHyperplane.Builder<P> insideFacing = sub.builder();
-            SubHyperplane.Builder<P> outsideFacing = sub.builder();
+            HyperplaneSubset.Builder<P> insideFacing = sub.builder();
+            HyperplaneSubset.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, getPlus(), null, outsideFacing);
+                for (HyperplaneConvexSubset<P> minusInFragment : minusIn) {
+                    characterizeHyperplaneSubset(minusInFragment, getPlus(), null, outsideFacing);
                 }
             }
 
@@ -728,25 +726,26 @@ public abstract class AbstractRegionBSPTree<
                 // 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, getPlus(), insideFacing, null);
+                for (HyperplaneConvexSubset<P> minusOutFragment : minusOut) {
+                    characterizeHyperplaneSubset(minusOutFragment, getPlus(), insideFacing, null);
                 }
             }
 
             return new RegionCutBoundary<>(insideFacing.build(), outsideFacing.build());
         }
 
-        /** Recursive method to characterize a convex subhyperplane with respect to the region's
+        /** Recursive method to characterize a hyperplane convex subset 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
+         * @param sub the hyperplane convex subset to characterize
+         * @param node the node to characterize the hyperplane convex subset against
+         * @param in the builder that will receive the portions of the subset 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
+         * @param out the builder that will receive the portions of the subset 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) {
+        private void characterizeHyperplaneSubset(final HyperplaneConvexSubset<P> sub,
+                final AbstractRegionNode<P, N> node, final HyperplaneSubset.Builder<P> in,
+                final HyperplaneSubset.Builder<P> out) {
 
             if (sub != null) {
                 if (node.isLeaf()) {
@@ -756,16 +755,16 @@ public abstract class AbstractRegionBSPTree<
                         out.add(sub);
                     }
                 } else {
-                    final Split<? extends ConvexSubHyperplane<P>> split = sub.split(node.getCutHyperplane());
+                    final Split<? extends HyperplaneConvexSubset<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
+                    // Continue further on down the subtree with the same subset if the
+                    // subset 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);
+                        characterizeHyperplaneSubset(sub, node.getPlus(), in, out);
+                        characterizeHyperplaneSubset(sub, node.getMinus(), in, out);
                     } else {
-                        characterizeSubHyperplane(split.getPlus(), node.getPlus(), in, out);
-                        characterizeSubHyperplane(split.getMinus(), node.getMinus(), in, out);
+                        characterizeHyperplaneSubset(split.getPlus(), node.getPlus(), in, out);
+                        characterizeHyperplaneSubset(split.getMinus(), node.getMinus(), in, out);
                     }
                 }
             }
@@ -848,16 +847,16 @@ public abstract class AbstractRegionBSPTree<
             return Result.CONTINUE;
         }
 
-        /** 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
+        /** Return true if the given node cut is a possible candidate for containing the closest region
+         * boundary point to the target.
+         * @param cut the node cut 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
+         * @return true if the cut is a possible candidate for containing the closest region
          *      boundary point to the target
          */
-        protected boolean isPossibleClosestCut(final SubHyperplane<P> cut, final P target,
+        protected boolean isPossibleClosestCut(final HyperplaneSubset<P> cut, final P target,
                 final double currentMinDist) {
             return Math.abs(cut.getHyperplane().offset(target)) <= currentMinDist;
         }
@@ -1093,26 +1092,26 @@ public abstract class AbstractRegionBSPTree<
         }
     }
 
-    /** Class that iterates over the boundary convex subhyperplanes from a set of region nodes.
+    /** Class that iterates over the boundary hyperplane convex subsets from a set of region nodes.
      * @param <P> Point implementation type
-     * @param <C> Boundary convex subhyperplane implementation type
+     * @param <C> Boundary hyperplane convex subset implementation type
      * @param <N> BSP tree node implementation type
      */
     private static final class RegionBoundaryIterator<
             P extends Point<P>,
-            C extends ConvexSubHyperplane<P>,
+            C extends HyperplaneConvexSubset<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;
+        /** Function that converts from the convex subset type to the output type. */
+        private final Function<HyperplaneConvexSubset<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
+         * @param typeConverter function that converts from the convex subset type to the output type
          */
         RegionBoundaryIterator(final Iterator<N> inputIterator,
-                final Function<ConvexSubHyperplane<P>, C> typeConverter) {
+                final Function<HyperplaneConvexSubset<P>, C> typeConverter) {
             super(inputIterator);
 
             this.typeConverter = typeConverter;
@@ -1124,18 +1123,18 @@ public abstract class AbstractRegionBSPTree<
             if (input.isInternal()) {
                 final RegionCutBoundary<P> cutBoundary = input.getCutBoundary();
 
-                final SubHyperplane<P> outsideFacing = cutBoundary.getOutsideFacing();
-                final SubHyperplane<P> insideFacing = cutBoundary.getInsideFacing();
+                final HyperplaneSubset<P> outsideFacing = cutBoundary.getOutsideFacing();
+                final HyperplaneSubset<P> insideFacing = cutBoundary.getInsideFacing();
 
                 if (outsideFacing != null && !outsideFacing.isEmpty()) {
-                    for (ConvexSubHyperplane<P> boundary : outsideFacing.toConvex()) {
+                    for (HyperplaneConvexSubset<P> boundary : outsideFacing.toConvex()) {
 
                         addOutput(typeConverter.apply(boundary));
                     }
                 }
                 if (insideFacing != null && !insideFacing.isEmpty()) {
-                    for (ConvexSubHyperplane<P> boundary : insideFacing.toConvex()) {
-                        ConvexSubHyperplane<P> reversed = boundary.reverse();
+                    for (HyperplaneConvexSubset<P> boundary : insideFacing.toConvex()) {
+                        HyperplaneConvexSubset<P> reversed = boundary.reverse();
 
                         addOutput(typeConverter.apply(reversed));
                     }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java
index c359173..a94c4ca 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/partitioning/bsp/BSPTree.java
@@ -18,7 +18,7 @@ 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.HyperplaneConvexSubset;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 
 /** Interface for Binary Space Partitioning (BSP) trees. BSP trees are spatial data
@@ -42,7 +42,7 @@ 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.
+     * falls directly on the cut of an internal node.
      */
     enum FindNodeCutRule {
 
@@ -81,9 +81,9 @@ public interface BSPTree<P extends Point<P>, N extends BSPTree.Node<P, N>>
         return findNode(pt, FindNodeCutRule.MINUS);
     }
 
-    /** Find a node in this subtree containing the given point on it interior or boundary. The
+    /** Find a node in this subtree containing the given point on its 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
+     * exactly on the cut 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>
@@ -93,7 +93,7 @@ public interface BSPTree<P extends Point<P>, N extends BSPTree.Node<P, N>>
      * </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
+     *      exactly on the cut of an internal node
      * @return node containing the point on its interior or boundary
      * @see #findNode(Point)
      */
@@ -110,7 +110,7 @@ public interface BSPTree<P extends Point<P>, N extends BSPTree.Node<P, N>>
      */
     void extract(N node);
 
-    /** Transform this tree. Each cut subhyperplane in the tree is transformed in place using
+    /** Transform this tree. Each cut in the tree is transformed in place using
      * the given {@link Transform}.
      * @param transform the transform to apply
      */
@@ -139,17 +139,18 @@ public interface BSPTree<P extends Point<P>, N extends BSPTree.Node<P, N>>
          */
         N getParent();
 
-        /** Get the cut for the node. This is a convex subhyperplane that splits
+        /** Get the cut for the node. This is a hyperplane convex subset 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
+         * @return the cut for the cell
+         * @see #getCutHyperplane()
          */
-        ConvexSubHyperplane<P> getCut();
+        HyperplaneConvexSubset<P> getCut();
 
-        /** Get the hyperplane belonging to the node cut, if it exists.
-         * @return the hyperplane belonging to the node cut, or null if
+        /** Get the hyperplane containing the node cut, if it exists.
+         * @return the hyperplane containing the node cut, or null if
          *      the node does not have a cut
          * @see #getCut()
          */
@@ -191,14 +192,14 @@ public interface BSPTree<P extends Point<P>, N extends BSPTree.Node<P, N>>
          */
         boolean isPlus();
 
-        /** Trim the given subhyperplane to the region defined by this node by cutting
+        /** Trim the given hyperplane subset to the region defined by this node by cutting
          * the argument with the cut hyperplanes (binary partitioners) of all parent nodes
-         * up to the root. Null is returned if the subhyperplane lies outside of the region
+         * up to the root. Null is returned if the hyperplane subset lies outside of the region
          * defined by the node.
-         * @param sub the convex subhyperplane to trim
-         * @return the trimmed convex subhyperplane or null if no part of the argument lies
+         * @param sub the hyperplane subset to trim
+         * @return the trimmed hyperplane subset or null if no part of the argument lies
          *      within the node's region
          */
-        ConvexSubHyperplane<P> trim(ConvexSubHyperplane<P> sub);
+        HyperplaneConvexSubset<P> trim(HyperplaneConvexSubset<P> sub);
     }
 }
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
index 3469afa..2a7fb84 100644
--- 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
@@ -17,55 +17,51 @@
 package org.apache.commons.geometry.core.partitioning.bsp;
 
 import org.apache.commons.geometry.core.Point;
-import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 
 /** 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
+ * {@link AbstractRegionBSPTree.AbstractRegionNode AbstractRegionNode}'s cut that
+ * lies on the boundary of the region. Portions of this hyperplane subset
+ * may be oriented so that the plus side of the hyperplane subset 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
+ * of the same hyperplane subset 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>> {
-    /** Portion of the region cut subhyperplane with its plus side facing the
-     * inside of the region.
+    /** Portion of the cut with its plus side facing the inside of the region.
      */
-    private final SubHyperplane<P> insideFacing;
+    private final HyperplaneSubset<P> insideFacing;
 
-    /** Portion of the region cut subhyperplane with its plus side facing the
-     * outside of the region.
+    /** Portion of the cut with its plus side facing the outside of the region.
      */
-    private final SubHyperplane<P> outsideFacing;
+    private final HyperplaneSubset<P> outsideFacing;
 
     /** Simple constructor.
-     * @param insideFacing portion of the region cut subhyperplane with its plus side facing the
+     * @param insideFacing portion of the cut with its plus side facing the
      *      inside of the region
-     * @param outsideFacing portion of the region cut subhyperplane with its plus side facing the
+     * @param outsideFacing portion of the cut with its plus side facing the
      *      outside of the region
      */
-    public RegionCutBoundary(final SubHyperplane<P> insideFacing, final SubHyperplane<P> outsideFacing) {
+    public RegionCutBoundary(final HyperplaneSubset<P> insideFacing, final HyperplaneSubset<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
+    /** Get the portion of the cut with its plus side facing the inside of the region.
+     * @return the portion of the cut with its plus side facing the
      *      inside of the region
      */
-    public SubHyperplane<P> getInsideFacing() {
+    public HyperplaneSubset<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
+    /** Get the portion of the cut with its plus side facing the outside of the region.
+     * @return the portion of the cut with its plus side facing the
      *      outside of the region
      */
-    public SubHyperplane<P> getOutsideFacing() {
+    public HyperplaneSubset<P> getOutsideFacing() {
         return outsideFacing;
     }
 
@@ -73,7 +69,7 @@ public final class RegionCutBoundary<P extends Point<P>> {
      * 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)
+     * @see HyperplaneSubset#closest(Point)
      */
     public P closest(final P pt) {
         final P insideFacingPt = (insideFacing != null) ? insideFacing.closest(pt) : null;
@@ -94,7 +90,7 @@ public final class RegionCutBoundary<P extends Point<P>> {
      * 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)
+     * @see HyperplaneSubset#contains(Point)
      */
     public boolean contains(final P pt) {
         return (insideFacing != null && insideFacing.contains(pt)) ||
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/RegionEmbeddingTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/RegionEmbeddingTest.java
new file mode 100644
index 0000000..667bea3
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/RegionEmbeddingTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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 org.apache.commons.geometry.core.partition.test.TestPoint1D;
+import org.apache.commons.geometry.core.partition.test.TestPoint2D;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class RegionEmbeddingTest {
+
+    private static final double TEST_EPS = 1e-10;
+
+    @Test
+    public void testGetSize() {
+        // arrange
+        StubRegionEmbedding finite = new StubRegionEmbedding(2.0);
+        StubRegionEmbedding infinite = new StubRegionEmbedding(Double.POSITIVE_INFINITY);
+        StubRegionEmbedding nan = new StubRegionEmbedding(Double.NaN);
+
+        // act/assert
+        Assert.assertEquals(2.0, finite.getSize(), TEST_EPS);
+        Assert.assertTrue(finite.isFinite());
+        Assert.assertFalse(finite.isInfinite());
+
+        GeometryTestUtils.assertPositiveInfinity(infinite.getSize());
+        Assert.assertFalse(infinite.isFinite());
+        Assert.assertTrue(infinite.isInfinite());
+
+        Assert.assertTrue(Double.isNaN(nan.getSize()));
+        Assert.assertFalse(nan.isFinite());
+        Assert.assertFalse(nan.isInfinite());
+    }
+
+    private static class StubRegionEmbedding implements RegionEmbedding<TestPoint2D, TestPoint1D> {
+
+        private final StubRegion1D subspaceRegion;
+
+        StubRegionEmbedding(final double size) {
+            subspaceRegion = new StubRegion1D(size);
+        }
+
+        @Override
+        public TestPoint1D toSubspace(TestPoint2D pt) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public TestPoint2D toSpace(TestPoint1D pt) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Region<TestPoint1D> getSubspaceRegion() {
+            return subspaceRegion;
+        }
+    }
+
+    private static class StubRegion1D implements Region<TestPoint1D> {
+
+        private final double size;
+
+        StubRegion1D(final double size) {
+            this.size = size;
+        }
+
+        @Override
+        public double getSize() {
+            return size;
+        }
+
+        @Override
+        public boolean isFull() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isEmpty() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public double getBoundarySize() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public TestPoint1D getBarycenter() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public RegionLocation classify(TestPoint1D pt) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public TestPoint1D project(TestPoint1D pt) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/SizedTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/SizedTest.java
new file mode 100644
index 0000000..d1a0128
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/SizedTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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 org.junit.Assert;
+import org.junit.Test;
+
+public class SizedTest {
+
+    @Test
+    public void testProperties() {
+        // arrange
+        Sized finite = new StubSized(1);
+        Sized infinite = new StubSized(Double.POSITIVE_INFINITY);
+        Sized nan = new StubSized(Double.NaN);
+
+        // act/assert
+        Assert.assertTrue(finite.isFinite());
+        Assert.assertFalse(finite.isInfinite());
+
+        Assert.assertFalse(infinite.isFinite());
+        Assert.assertTrue(infinite.isInfinite());
+
+        Assert.assertFalse(nan.isFinite());
+        Assert.assertFalse(nan.isInfinite());
+    }
+
+    private static class StubSized implements Sized {
+
+        private final double size;
+
+        StubSized(final double size) {
+            this.size = size;
+        }
+
+        @Override
+        public double getSize() {
+            return size;
+        }
+    }
+}
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
index 65cdf29..f976fd1 100644
--- 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
@@ -17,9 +17,9 @@
 package org.apache.commons.geometry.core.partition.test;
 
 import org.apache.commons.geometry.core.partitioning.BoundarySource;
-import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
-import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
 
 /** BSP Tree implementation class for testing purposes.
@@ -32,15 +32,15 @@ public class TestBSPTree extends AbstractBSPTree<TestPoint2D, TestBSPTree.TestNo
         return new TestNode(this);
     }
 
-    public void insert(final SubHyperplane<TestPoint2D> sub) {
+    public void insert(final HyperplaneSubset<TestPoint2D> sub) {
         insert(sub.toConvex());
     }
 
-    public void insert(final ConvexSubHyperplane<TestPoint2D> sub) {
+    public void insert(final HyperplaneConvexSubset<TestPoint2D> sub) {
         insert(sub, root -> { });
     }
 
-    public void insert(final Iterable<? extends ConvexSubHyperplane<TestPoint2D>> subs) {
+    public void insert(final Iterable<? extends HyperplaneConvexSubset<TestPoint2D>> subs) {
         subs.forEach(this::insert);
     }
 
@@ -60,6 +60,11 @@ public class TestBSPTree extends AbstractBSPTree<TestPoint2D, TestBSPTree.TestNo
         super.splitIntoTrees(splitter, minus, plus);
     }
 
+    @Override
+    protected void copyNodeProperties(final TestNode src, final TestNode dst) {
+        // do nothing
+    }
+
     /** BSP Tree node class for {@link TestBSPTree}.
      */
     public static class TestNode extends AbstractBSPTree.AbstractNode<TestPoint2D, TestNode> {
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
index 011e010..896b286 100644
--- 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
@@ -21,15 +21,15 @@ 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.HyperplaneConvexSubset;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.partitioning.Split;
-import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 
 /** 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> {
+public class TestLineSegment implements HyperplaneConvexSubset<TestPoint2D> {
     /** Abscissa of the line segment start point. */
     private final double start;
 
@@ -169,7 +169,7 @@ public class TestLineSegment implements ConvexSubHyperplane<TestPoint2D> {
 
     /** {@inheritDoc} */
     @Override
-    public List<ConvexSubHyperplane<TestPoint2D>> toConvex() {
+    public List<HyperplaneConvexSubset<TestPoint2D>> toConvex() {
         return Arrays.asList(this);
     }
 
@@ -193,13 +193,13 @@ public class TestLineSegment implements ConvexSubHyperplane<TestPoint2D> {
 
     /** {@inheritDoc} */
     @Override
-    public SubHyperplane.Builder<TestPoint2D> builder() {
+    public HyperplaneSubset.Builder<TestPoint2D> builder() {
         return new TestLineSegmentCollectionBuilder(line);
     }
 
     /** {@inheritDoc} */
     @Override
-    public ConvexSubHyperplane<TestPoint2D> transform(Transform<TestPoint2D> transform) {
+    public HyperplaneConvexSubset<TestPoint2D> transform(Transform<TestPoint2D> transform) {
         if (!isInfinite()) {
             // simple case; just transform the points directly
             TestPoint2D p1 = transform.apply(getStartPoint());
@@ -239,7 +239,7 @@ public class TestLineSegment implements ConvexSubHyperplane<TestPoint2D> {
     /** 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
+     * @return the split convex subset
      */
     private Split<TestLineSegment> splitInfinite(TestLine splitter) {
         final TestPoint2D intersection = splitter.intersection(line);
@@ -282,7 +282,7 @@ public class TestLineSegment implements ConvexSubHyperplane<TestPoint2D> {
     /** 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
+     * @return the split convex subset
      */
     private Split<TestLineSegment> splitFinite(TestLine splitter) {
 
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
index 1c9b60e..a036d9d 100644
--- 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
@@ -22,16 +22,16 @@ 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.HyperplaneConvexSubset;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.partitioning.Split;
-import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 
 /** Class containing a collection line segments. This class should only be used for
  * testing purposes.
  */
-public class TestLineSegmentCollection implements SubHyperplane<TestPoint2D> {
-    /** The collection of line-segments making up the subhyperplane.
+public class TestLineSegmentCollection implements HyperplaneSubset<TestPoint2D> {
+    /** The collection of line-segments making up the subset.
      */
     private final List<TestLineSegment> segments;
 
@@ -174,19 +174,19 @@ public class TestLineSegmentCollection implements SubHyperplane<TestPoint2D> {
 
     /** {@inheritDoc} */
     @Override
-    public List<ConvexSubHyperplane<TestPoint2D>> toConvex() {
+    public List<HyperplaneConvexSubset<TestPoint2D>> toConvex() {
         return new ArrayList<>(segments);
     }
 
     /** {@inheritDoc} */
     @Override
-    public SubHyperplane<TestPoint2D> transform(Transform<TestPoint2D> transform) {
+    public HyperplaneSubset<TestPoint2D> transform(Transform<TestPoint2D> transform) {
         throw new UnsupportedOperationException();
     }
 
     /** {@inheritDoc} */
     @Override
-    public SubHyperplane.Builder<TestPoint2D> builder() {
+    public HyperplaneSubset.Builder<TestPoint2D> builder() {
         return new TestLineSegmentCollectionBuilder(segments.get(0).getHyperplane());
     }
 }
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegmentCollectionBuilder.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegmentCollectionBuilder.java
index 4deeb2a..7589574 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegmentCollectionBuilder.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestLineSegmentCollectionBuilder.java
@@ -20,10 +20,10 @@ import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
-import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
-import org.apache.commons.geometry.core.partitioning.SubHyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 
-public class TestLineSegmentCollectionBuilder implements SubHyperplane.Builder<TestPoint2D> {
+public class TestLineSegmentCollectionBuilder implements HyperplaneSubset.Builder<TestPoint2D> {
 
     private final TestLine line;
 
@@ -35,22 +35,22 @@ public class TestLineSegmentCollectionBuilder implements SubHyperplane.Builder<T
 
     /** {@inheritDoc} */
     @Override
-    public void add(SubHyperplane<TestPoint2D> sub) {
-        for (ConvexSubHyperplane<TestPoint2D> convex : sub.toConvex()) {
+    public void add(HyperplaneSubset<TestPoint2D> sub) {
+        for (HyperplaneConvexSubset<TestPoint2D> convex : sub.toConvex()) {
             add(convex);
         }
     }
 
     /** {@inheritDoc} */
     @Override
-    public void add(ConvexSubHyperplane<TestPoint2D> convex) {
+    public void add(HyperplaneConvexSubset<TestPoint2D> convex) {
         TestLineSegment seg = (TestLineSegment) convex;
         addSegment(seg.getStart(), seg.getEnd());
     }
 
     /** {@inheritDoc} */
     @Override
-    public SubHyperplane<TestPoint2D> build() {
+    public HyperplaneSubset<TestPoint2D> build() {
         List<TestLineSegment> segments = new ArrayList<>();
 
         for (SegmentInterval interval : intervals) {
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestRegionBSPTree.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestRegionBSPTree.java
index d4cbf5c..5b46c41 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestRegionBSPTree.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestRegionBSPTree.java
@@ -17,7 +17,7 @@
 package org.apache.commons.geometry.core.partition.test;
 
 import org.apache.commons.geometry.core.RegionLocation;
-import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.partitioning.Split;
 import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
@@ -39,7 +39,7 @@ public final class TestRegionBSPTree extends AbstractRegionBSPTree<TestPoint2D,
     /**
      * Expose the direct node cut method for easier creation of test tree structures.
      */
-    public void cutNode(final TestRegionNode node, final ConvexSubHyperplane<TestPoint2D> cut) {
+    public void cutNode(final TestRegionNode node, final HyperplaneConvexSubset<TestPoint2D> cut) {
         super.setNodeCut(node, cut, getSubtreeInitializer(RegionCutRule.MINUS_INSIDE));
     }
 
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractConvexHyperplaneBoundedRegionTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractConvexHyperplaneBoundedRegionTest.java
index bd73f02..ccebc49 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractConvexHyperplaneBoundedRegionTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractConvexHyperplaneBoundedRegionTest.java
@@ -579,8 +579,8 @@ public class AbstractConvexHyperplaneBoundedRegionTest {
         }
 
         @Override
-        public TestLineSegment trim(ConvexSubHyperplane<TestPoint2D> convexSubHyperplane) {
-            return (TestLineSegment) super.trim(convexSubHyperplane);
+        public TestLineSegment trim(HyperplaneConvexSubset<TestPoint2D> subset) {
+            return (TestLineSegment) super.trim(subset);
         }
 
         @Override
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractHyperplaneTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractHyperplaneTest.java
index 98fe31f..9b41cf0 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractHyperplaneTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractHyperplaneTest.java
@@ -100,7 +100,7 @@ public class AbstractHyperplaneTest {
         }
 
         @Override
-        public ConvexSubHyperplane<TestPoint2D> span() {
+        public HyperplaneConvexSubset<TestPoint2D> span() {
             return null;
         }
     }
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractEmbeddingSubHyperplaneTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractRegionEmbeddingHyperplaneSubsetTest.java
similarity index 80%
rename from commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractEmbeddingSubHyperplaneTest.java
rename to commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractRegionEmbeddingHyperplaneSubsetTest.java
index d91f2f8..4dc0863 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractEmbeddingSubHyperplaneTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/AbstractRegionEmbeddingHyperplaneSubsetTest.java
@@ -27,12 +27,12 @@ import org.apache.commons.geometry.core.partition.test.TestPoint2D;
 import org.junit.Assert;
 import org.junit.Test;
 
-public class AbstractEmbeddingSubHyperplaneTest {
+public class AbstractRegionEmbeddingHyperplaneSubsetTest {
 
     @Test
     public void testSimpleProperties() {
         // arrange
-        StubSubHyperplane sub = new StubSubHyperplane(1.0);
+        StubHyperplaneSubset sub = new StubHyperplaneSubset(1.0);
 
         // act/assert
         Assert.assertTrue(sub.isFull());
@@ -46,9 +46,9 @@ public class AbstractEmbeddingSubHyperplaneTest {
     @Test
     public void testFiniteAndInfinite() {
         // arrange
-        StubSubHyperplane finite = new StubSubHyperplane(1.0);
-        StubSubHyperplane inf = new StubSubHyperplane(Double.POSITIVE_INFINITY);
-        StubSubHyperplane nan = new StubSubHyperplane(Double.NaN);
+        StubHyperplaneSubset finite = new StubHyperplaneSubset(1.0);
+        StubHyperplaneSubset inf = new StubHyperplaneSubset(Double.POSITIVE_INFINITY);
+        StubHyperplaneSubset nan = new StubHyperplaneSubset(Double.NaN);
 
         // act/assert
         Assert.assertTrue(finite.isFinite());
@@ -62,9 +62,19 @@ public class AbstractEmbeddingSubHyperplaneTest {
     }
 
     @Test
+    public void testSpaceConversions() {
+        // arrange
+        StubHyperplaneSubset sub = new StubHyperplaneSubset();
+
+        // act/assert
+        Assert.assertEquals(2.0, sub.toSubspace(new TestPoint2D(2.0, 3.0)).getX(), PartitionTestUtils.EPS);
+        PartitionTestUtils.assertPointsEqual(new TestPoint2D(2.0, 0.0), sub.toSpace(new TestPoint1D(2.0)));
+    }
+
+    @Test
     public void testClassify() {
         // arrange
-        StubSubHyperplane sub = new StubSubHyperplane();
+        StubHyperplaneSubset sub = new StubHyperplaneSubset();
 
         // act/assert
         Assert.assertEquals(RegionLocation.INSIDE, sub.classify(new TestPoint2D(-1, 0)));
@@ -78,7 +88,7 @@ public class AbstractEmbeddingSubHyperplaneTest {
     @Test
     public void testClosest() {
         // arrange
-        StubSubHyperplane sub = new StubSubHyperplane();
+        StubHyperplaneSubset sub = new StubHyperplaneSubset();
 
         // act/assert
         PartitionTestUtils.assertPointsEqual(new TestPoint2D(-1, 0), sub.closest(new TestPoint2D(-1, 0)));
@@ -95,22 +105,23 @@ public class AbstractEmbeddingSubHyperplaneTest {
     @Test
     public void testClosest_nullSubspaceRegionProjection() {
         // arrange
-        StubSubHyperplane sub = new StubSubHyperplane();
+        StubHyperplaneSubset sub = new StubHyperplaneSubset();
         sub.region.projected = null;
 
         // act/assert
         Assert.assertNull(sub.closest(new TestPoint2D(1, 1)));
     }
 
-    private static class StubSubHyperplane extends AbstractEmbeddingSubHyperplane<TestPoint2D, TestPoint1D, TestLine> {
+    private static class StubHyperplaneSubset
+        extends AbstractRegionEmbeddingHyperplaneSubset<TestPoint2D, TestPoint1D, TestLine> {
 
         private final StubRegion1D region;
 
-        StubSubHyperplane() {
+        StubHyperplaneSubset() {
             this(0);
         }
 
-        StubSubHyperplane(final double size) {
+        StubHyperplaneSubset(final double size) {
             this.region = new StubRegion1D(size);
         }
 
@@ -120,7 +131,7 @@ public class AbstractEmbeddingSubHyperplaneTest {
         }
 
         @Override
-        public List<? extends ConvexSubHyperplane<TestPoint2D>> toConvex() {
+        public List<? extends HyperplaneConvexSubset<TestPoint2D>> toConvex() {
             return null;
         }
 
@@ -135,12 +146,12 @@ public class AbstractEmbeddingSubHyperplaneTest {
         }
 
         @Override
-        public Split<StubSubHyperplane> split(Hyperplane<TestPoint2D> splitter) {
+        public Split<StubHyperplaneSubset> split(Hyperplane<TestPoint2D> splitter) {
             return null;
         }
 
         @Override
-        public SubHyperplane<TestPoint2D> transform(Transform<TestPoint2D> transform) {
+        public HyperplaneSubset<TestPoint2D> transform(Transform<TestPoint2D> transform) {
             return null;
         }
     }
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeTest.java
index 8885be3..a79cab1 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractBSPTreeTest.java
@@ -675,7 +675,7 @@ public class AbstractBSPTreeTest {
     }
 
     @Test
-    public void testInsert_subhyperplane_concaveRegion() {
+    public void testInsert_hyperplaneSubset_concaveRegion() {
         // arrange
         TestBSPTree tree = new TestBSPTree();
 
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTreeTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTreeTest.java
index 0dbd048..3bbf64f 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTreeTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/AbstractRegionBSPTreeTest.java
@@ -34,10 +34,10 @@ import org.apache.commons.geometry.core.partition.test.TestRegionBSPTree;
 import org.apache.commons.geometry.core.partition.test.TestRegionBSPTree.TestRegionNode;
 import org.apache.commons.geometry.core.partition.test.TestTransform2D;
 import org.apache.commons.geometry.core.partitioning.BoundarySource;
-import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
 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.HyperplaneSubset;
 import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree.RegionSizeProperties;
 import org.junit.Assert;
 import org.junit.Before;
@@ -109,7 +109,7 @@ public class AbstractRegionBSPTreeTest {
     }
 
     @Test
-    public void testInsert_subhyperplanes_mixedCutRules() {
+    public void testInsert_hyperplaneSubsets_mixedCutRules() {
         // act/assert
         checkMixedCutRuleInsertion(segs -> {
             tree.insert(new TestLineSegmentCollection(Arrays.asList(segs[0])), RegionCutRule.PLUS_INSIDE);
@@ -122,7 +122,7 @@ public class AbstractRegionBSPTreeTest {
     }
 
     @Test
-    public void testInsert_convexSubhyperplanes_mixedCutRules() {
+    public void testInsert_hyperplaneConvexSubsets_mixedCutRules() {
         // act/assert
         checkMixedCutRuleInsertion(segs -> {
             tree.insert(segs[0], RegionCutRule.PLUS_INSIDE);
@@ -134,7 +134,7 @@ public class AbstractRegionBSPTreeTest {
     }
 
     @Test
-    public void testInsert_convexSubhyperplaneList_mixedCutRules() {
+    public void testInsert_hyperplaneConvexSubsetList_mixedCutRules() {
         // act/assert
         checkMixedCutRuleInsertion(segs -> {
             tree.insert(Arrays.asList(segs[0]), RegionCutRule.PLUS_INSIDE);
@@ -162,7 +162,7 @@ public class AbstractRegionBSPTreeTest {
         });
     }
 
-    /** Helper function to check the insertion of subhyperplanes using different region cut rules.
+    /** Helper function to check the insertion of hyperplane subsets using different region cut rules.
      * @param fn
      */
     private void checkMixedCutRuleInsertion(Consumer<TestLineSegment[]> fn) {
@@ -440,7 +440,7 @@ public class AbstractRegionBSPTreeTest {
 
         // act
         List<TestLineSegment> segments = new ArrayList<>();
-        for (ConvexSubHyperplane<TestPoint2D> sub : tree.boundaries()) {
+        for (HyperplaneConvexSubset<TestPoint2D> sub : tree.boundaries()) {
             segments.add((TestLineSegment) sub);
         }
 
@@ -461,7 +461,7 @@ public class AbstractRegionBSPTreeTest {
 
         // act
         List<TestLineSegment> segments = new ArrayList<>();
-        for (ConvexSubHyperplane<TestPoint2D> sub : tree.boundaries()) {
+        for (HyperplaneConvexSubset<TestPoint2D> sub : tree.boundaries()) {
             segments.add((TestLineSegment) sub);
         }
 
@@ -491,7 +491,7 @@ public class AbstractRegionBSPTreeTest {
 
         // act
         List<TestLineSegment> segments = new ArrayList<>();
-        for (ConvexSubHyperplane<TestPoint2D> sub : tree.getBoundaries()) {
+        for (HyperplaneConvexSubset<TestPoint2D> sub : tree.getBoundaries()) {
             segments.add((TestLineSegment) sub);
         }
 
@@ -512,7 +512,7 @@ public class AbstractRegionBSPTreeTest {
 
         // act
         List<TestLineSegment> segments = new ArrayList<>();
-        for (ConvexSubHyperplane<TestPoint2D> sub : tree.getBoundaries()) {
+        for (HyperplaneConvexSubset<TestPoint2D> sub : tree.getBoundaries()) {
             segments.add((TestLineSegment) sub);
         }
 
@@ -1552,7 +1552,7 @@ public class AbstractRegionBSPTreeTest {
                 new TestLineSegment(new TestPoint2D(-4, -5), new TestPoint2D(1, 0))));
     }
 
-    private static void assertCutBoundarySegment(final SubHyperplane<TestPoint2D> boundary, final TestPoint2D start,
+    private static void assertCutBoundarySegment(final HyperplaneSubset<TestPoint2D> boundary, final TestPoint2D start,
             final TestPoint2D end) {
         Assert.assertFalse("Expected boundary to not be empty", boundary.isEmpty());
 
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/RegionCutBoundaryTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/RegionCutBoundaryTest.java
index 45fee2c..5b305ce 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/RegionCutBoundaryTest.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partitioning/bsp/RegionCutBoundaryTest.java
@@ -133,7 +133,7 @@ public class RegionCutBoundaryTest {
     }
 
     @Test
-    public void testContains_nullSubHyperplanes() {
+    public void testContains_nullHyperplaneSubsets() {
         // arrange
         RegionCutBoundary<TestPoint2D> boundary = new RegionCutBoundary<>(null, null);
 
diff --git a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGenerator.java b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGenerator.java
index 0aab6d3..246a579 100644
--- a/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGenerator.java
+++ b/commons-geometry-enclosing/src/main/java/org/apache/commons/geometry/enclosing/euclidean/threed/SphereGenerator.java
@@ -25,6 +25,7 @@ import org.apache.commons.geometry.enclosing.EnclosingBall;
 import org.apache.commons.geometry.enclosing.SupportBallGenerator;
 import org.apache.commons.geometry.enclosing.euclidean.twod.DiskGenerator;
 import org.apache.commons.geometry.euclidean.threed.Plane;
+import org.apache.commons.geometry.euclidean.threed.Planes;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.geometry.euclidean.twod.Vector2D;
 import org.apache.commons.numbers.fraction.BigFraction;
@@ -61,7 +62,7 @@ public class SphereGenerator implements SupportBallGenerator<Vector3D> {
         }
         final Vector3D vC = support.get(2);
         if (support.size() < 4) {
-            final Plane p = Plane.fromPoints(vA, vB, vC, precision);
+            final Plane p = Planes.fromPoints(vA, vB, vC, precision);
             final EnclosingBall<Vector2D> disk =
                     new DiskGenerator().ballOnSupport(Arrays.asList(p.toSubspace(vA),
                                                                     p.toSubspace(vB),
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/AbstractPathConnector.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/AbstractPathConnector.java
index fee4343..2fbe770 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/AbstractPathConnector.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/AbstractPathConnector.java
@@ -95,14 +95,14 @@ public abstract class AbstractPathConnector<E extends AbstractPathConnector.Conn
      * @return a list of root elements for the computed connected paths
      */
     protected List<E> computePathRoots() {
-        for (final E segment : pathElements) {
-            followForwardConnections(segment);
+        for (final E element : pathElements) {
+            followForwardConnections(element);
         }
 
         final List<E> rootEntries = new ArrayList<>();
         E root;
-        for (final E segment : pathElements) {
-            root = segment.exportPath();
+        for (final E element : pathElements) {
+            root = element.exportPath();
             if (root != null) {
                 rootEntries.add(root);
             }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Interval.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Interval.java
index 9d3470e..57a2dd6 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Interval.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Interval.java
@@ -117,6 +117,7 @@ public final class Interval implements HyperplaneBoundedRegion<Vector1D> {
      * does not exist.
      * @return true if the region is infinite
      */
+    @Override
     public boolean isInfinite() {
         return minBoundary == null || maxBoundary == null;
     }
@@ -125,6 +126,7 @@ public final class Interval implements HyperplaneBoundedRegion<Vector1D> {
      * boundaries exist and the region size is finite.
      * @return true if the region is finite
      */
+    @Override
     public boolean isFinite() {
         return !isInfinite();
     }
@@ -312,9 +314,9 @@ public final class Interval implements HyperplaneBoundedRegion<Vector1D> {
                 low = this;
             } else {
                 // the interval is split in two
-                low = new Interval(minBoundary, OrientedPoint.createPositiveFacing(
+                low = new Interval(minBoundary, OrientedPoints.createPositiveFacing(
                         splitPoint, splitOrientedPoint.getPrecision()));
-                high = new Interval(OrientedPoint.createNegativeFacing(
+                high = new Interval(OrientedPoints.createNegativeFacing(
                         splitPoint, splitOrientedPoint.getPrecision()), maxBoundary);
             }
         }
@@ -365,11 +367,11 @@ public final class Interval implements HyperplaneBoundedRegion<Vector1D> {
         final double max = Math.max(a, b);
 
         final OrientedPoint minBoundary = Double.isFinite(min) ?
-                OrientedPoint.fromLocationAndDirection(min, false, precision) :
+                OrientedPoints.fromLocationAndDirection(min, false, precision) :
                 null;
 
         final OrientedPoint maxBoundary = Double.isFinite(max) ?
-                OrientedPoint.fromLocationAndDirection(max, true, precision) :
+                OrientedPoints.fromLocationAndDirection(max, true, precision) :
                 null;
 
         if (minBoundary == null && maxBoundary == null) {
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/OrientedPoint.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/OrientedPoint.java
index 704c0ab..0463139 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/OrientedPoint.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/OrientedPoint.java
@@ -16,18 +16,18 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
 import org.apache.commons.geometry.core.RegionLocation;
 import org.apache.commons.geometry.core.Transform;
 import org.apache.commons.geometry.core.partitioning.AbstractHyperplane;
-import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
 import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 import org.apache.commons.geometry.core.partitioning.Split;
-import org.apache.commons.geometry.core.partitioning.SubHyperplane;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 
 /** This class represents a 1D oriented hyperplane.
@@ -36,6 +36,7 @@ import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
  * boolean indicating if the direction is positive or negative.</p>
  *
  * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @see OrientedPoints
  */
 public final class OrientedPoint extends AbstractHyperplane<Vector1D>
     implements Hyperplane<Vector1D> {
@@ -51,7 +52,7 @@ public final class OrientedPoint extends AbstractHyperplane<Vector1D>
      *      otherwise, it will point toward negative infinity.
      * @param precision precision context used to compare floating point values
      */
-    private OrientedPoint(final Vector1D point, final boolean positiveFacing, final DoublePrecisionContext precision) {
+    OrientedPoint(final Vector1D point, final boolean positiveFacing, final DoublePrecisionContext precision) {
         super(precision);
 
         this.point = point;
@@ -116,7 +117,7 @@ public final class OrientedPoint extends AbstractHyperplane<Vector1D>
             transformedDir = transformedPoint.vectorTo(transformedPointPlusDir);
         }
 
-        return OrientedPoint.fromPointAndDirection(
+        return OrientedPoints.fromPointAndDirection(
                     transformedPoint,
                     transformedDir,
                     getPrecision()
@@ -177,10 +178,15 @@ public final class OrientedPoint extends AbstractHyperplane<Vector1D>
         return this.point;
     }
 
-    /** {@inheritDoc} */
+    /** {@inheritDoc}
+     *
+     * <p>Since there are no subspaces in 1D, this method effectively returns a stub implementation of
+     * {@link HyperplaneConvexSubset}, the main purpose of which is to support the proper functioning
+     * of the partitioning code.</p>
+     */
     @Override
-    public SubOrientedPoint span() {
-        return new SubOrientedPoint(this);
+    public HyperplaneConvexSubset<Vector1D> span() {
+        return new OrientedPointConvexSubset(this);
     }
 
     /** Return true if this instance should be considered equivalent to the argument, using the
@@ -243,97 +249,18 @@ public final class OrientedPoint extends AbstractHyperplane<Vector1D>
         return sb.toString();
     }
 
-    /** Create a new instance from the given location and boolean direction value.
-     * @param location the location of the hyperplane
-     * @param positiveFacing if true, the hyperplane will face toward positive infinity;
-     *      otherwise, it will point toward negative infinity.
-     * @param precision precision context used to compare floating point values
-     * @return a new instance
-     */
-    public static OrientedPoint fromLocationAndDirection(final double location, final boolean positiveFacing,
-            final DoublePrecisionContext precision) {
-        return fromPointAndDirection(Vector1D.of(location), positiveFacing, precision);
-    }
-
-    /** Create a new instance from the given point and boolean direction value.
-     * @param point the location of the hyperplane
-     * @param positiveFacing if true, the hyperplane will face toward positive infinity;
-     *      otherwise, it will point toward negative infinity.
-     * @param precision precision context used to compare floating point values
-     * @return a new instance
-     */
-    public static OrientedPoint fromPointAndDirection(final Vector1D point, final boolean positiveFacing,
-            final DoublePrecisionContext precision) {
-        return new OrientedPoint(point, positiveFacing, precision);
-    }
-
-    /** Create a new instance from the given point and direction.
-     * @param point the location of the hyperplane
-     * @param direction the direction of the plus side of the hyperplane
-     * @param precision precision context used to compare floating point values
-     * @return a new instance oriented in the given direction
-     * @throws IllegalArgumentException if the direction is zero as evaluated by the
-     *      given precision context
-     */
-    public static OrientedPoint fromPointAndDirection(final Vector1D point, final Vector1D direction,
-            final DoublePrecisionContext precision) {
-        if (direction.isZero(precision)) {
-            throw new IllegalArgumentException("Oriented point direction cannot be zero");
-        }
-
-        final boolean positiveFacing = direction.getX() > 0;
-
-        return new OrientedPoint(point, positiveFacing, precision);
-    }
-
-    /** Create a new instance at the given point, oriented so that it is facing positive infinity.
-     * @param point the location of the hyperplane
-     * @param precision precision context used to compare floating point values
-     * @return a new instance oriented toward positive infinity
-     */
-    public static OrientedPoint createPositiveFacing(final Vector1D point, final DoublePrecisionContext precision) {
-        return new OrientedPoint(point, true, precision);
-    }
-
-    /** Create a new instance at the given location, oriented so that it is facing positive infinity.
-     * @param location the location of the hyperplane
-     * @param precision precision context used to compare floating point values
-     * @return a new instance oriented toward positive infinity
-     */
-    public static OrientedPoint createPositiveFacing(final double location, final DoublePrecisionContext precision) {
-        return new OrientedPoint(Vector1D.of(location), true, precision);
-    }
-
-    /** Create a new instance at the given point, oriented so that it is facing negative infinity.
-     * @param point the location of the hyperplane
-     * @param precision precision context used to compare floating point values
-     * @return a new instance oriented toward negative infinity
-     */
-    public static OrientedPoint createNegativeFacing(final Vector1D point, final DoublePrecisionContext precision) {
-        return new OrientedPoint(point, false, precision);
-    }
-
-    /** Create a new instance at the given location, oriented so that it is facing negative infinity.
-     * @param location the location of the hyperplane
-     * @param precision precision context used to compare floating point values
-     * @return a new instance oriented toward negative infinity
-     */
-    public static OrientedPoint createNegativeFacing(final double location, final DoublePrecisionContext precision) {
-        return new OrientedPoint(Vector1D.of(location), false, precision);
-    }
-
-    /** {@link ConvexSubHyperplane} implementation for Euclidean 1D space. Since there are no subspaces in 1D,
+    /** {@link HyperplaneConvexSubset} implementation for Euclidean 1D space. Since there are no subspaces in 1D,
      * this is effectively a stub implementation, its main use being to allow for the correct functioning of
      * partitioning code.
      */
-    public static class SubOrientedPoint implements ConvexSubHyperplane<Vector1D> {
+    private static class OrientedPointConvexSubset implements HyperplaneConvexSubset<Vector1D> {
         /** The underlying hyperplane for this instance. */
         private final OrientedPoint hyperplane;
 
         /** Simple constructor.
          * @param hyperplane underlying hyperplane instance
          */
-        public SubOrientedPoint(final OrientedPoint hyperplane) {
+        OrientedPointConvexSubset(final OrientedPoint hyperplane) {
             this.hyperplane = hyperplane;
         }
 
@@ -411,11 +338,11 @@ public final class OrientedPoint extends AbstractHyperplane<Vector1D>
 
         /** {@inheritDoc} */
         @Override
-        public Split<SubOrientedPoint> split(final Hyperplane<Vector1D> splitter) {
+        public Split<OrientedPointConvexSubset> split(final Hyperplane<Vector1D> splitter) {
             final HyperplaneLocation side = splitter.classify(hyperplane.getPoint());
 
-            SubOrientedPoint minus = null;
-            SubOrientedPoint plus = null;
+            OrientedPointConvexSubset minus = null;
+            OrientedPointConvexSubset plus = null;
 
             if (side == HyperplaneLocation.MINUS) {
                 minus = this;
@@ -428,26 +355,26 @@ public final class OrientedPoint extends AbstractHyperplane<Vector1D>
 
         /** {@inheritDoc} */
         @Override
-        public List<SubOrientedPoint> toConvex() {
-            return Arrays.asList(this);
+        public List<OrientedPointConvexSubset> toConvex() {
+            return Collections.singletonList(this);
         }
 
         /** {@inheritDoc} */
         @Override
-        public SubOrientedPoint transform(final Transform<Vector1D> transform) {
-            return getHyperplane().transform(transform).span();
+        public OrientedPointConvexSubset transform(final Transform<Vector1D> transform) {
+            return new OrientedPointConvexSubset(getHyperplane().transform(transform));
         }
 
         /** {@inheritDoc} */
         @Override
-        public SubOrientedPointBuilder builder() {
-            return new SubOrientedPointBuilder(this);
+        public OrientedPointSubsetBuilder builder() {
+            return new OrientedPointSubsetBuilder(this);
         }
 
         /** {@inheritDoc} */
         @Override
-        public SubOrientedPoint reverse() {
-            return new SubOrientedPoint(hyperplane.reverse());
+        public OrientedPointConvexSubset reverse() {
+            return new OrientedPointConvexSubset(hyperplane.reverse());
         }
 
         /** {@inheritDoc} */
@@ -463,36 +390,36 @@ public final class OrientedPoint extends AbstractHyperplane<Vector1D>
         }
     }
 
-    /** {@link SubHyperplane.Builder} implementation for Euclidean 1D space. Similar to {@link SubOrientedPoint},
-     * this is effectively a stub implementation since there are no subspaces of 1D space. Its primary use is to allow
-     * for the correct functioning of partitioning code.
+    /** {@link HyperplaneSubset.Builder} implementation for Euclidean 1D space. Similar to
+     * {@link OrientedPointConvexSubset}, this is effectively a stub implementation since there are no subspaces
+     * of 1D space. Its primary use is to allow for the correct functioning of partitioning code.
      */
-    public static final class SubOrientedPointBuilder implements SubHyperplane.Builder<Vector1D> {
-        /** Base subhyperplane for the builder. */
-        private final SubOrientedPoint base;
+    private static final class OrientedPointSubsetBuilder implements HyperplaneSubset.Builder<Vector1D> {
+        /** Base hyperplane subset for the builder. */
+        private final OrientedPointConvexSubset base;
 
-        /** Construct a new instance using the given base subhyperplane.
-         * @param base base subhyperplane for the instance
+        /** Construct a new instance using the given base hyperplane subset.
+         * @param base base hyperplane subset for the instance
          */
-        private SubOrientedPointBuilder(final SubOrientedPoint base) {
+        OrientedPointSubsetBuilder(final OrientedPointConvexSubset base) {
             this.base = base;
         }
 
         /** {@inheritDoc} */
         @Override
-        public void add(final SubHyperplane<Vector1D> sub) {
+        public void add(final HyperplaneSubset<Vector1D> sub) {
             validateHyperplane(sub);
         }
 
         /** {@inheritDoc} */
         @Override
-        public void add(final ConvexSubHyperplane<Vector1D> sub) {
+        public void add(final HyperplaneConvexSubset<Vector1D> sub) {
             validateHyperplane(sub);
         }
 
         /** {@inheritDoc} */
         @Override
-        public SubOrientedPoint build() {
+        public OrientedPointConvexSubset build() {
             return base;
         }
 
@@ -508,12 +435,12 @@ public final class OrientedPoint extends AbstractHyperplane<Vector1D>
             return sb.toString();
         }
 
-        /** Validate the given subhyperplane lies on the same hyperplane.
-         * @param sub subhyperplane to validate
+        /** Validate that the given hyperplane subset lies on the same hyperplane as this instance.
+         * @param sub hyperplane subset to validate
          * @throws IllegalArgumentException if the argument does not lie on
          *      the same hyperplane as this instance
          */
-        private void validateHyperplane(final SubHyperplane<Vector1D> sub) {
+        private void validateHyperplane(final HyperplaneSubset<Vector1D> sub) {
             final OrientedPoint baseHyper = base.getHyperplane();
             final OrientedPoint inputHyper = (OrientedPoint) sub.getHyperplane();
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/OrientedPoints.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/OrientedPoints.java
new file mode 100644
index 0000000..8bece35
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/OrientedPoints.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.oned;
+
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+
+/** Class containing factory methods for constructing {@link OrientedPoint} instances.
+ */
+public final class OrientedPoints {
+
+    /** Utility class; no instantiation. */
+    private OrientedPoints() {
+    }
+
+    /** Create a new instance from the given location and boolean direction value.
+     * @param location the location of the hyperplane
+     * @param positiveFacing if true, the hyperplane will face toward positive infinity;
+     *      otherwise, it will point toward negative infinity.
+     * @param precision precision context used to compare floating point values
+     * @return a new instance
+     */
+    public static OrientedPoint fromLocationAndDirection(final double location, final boolean positiveFacing,
+            final DoublePrecisionContext precision) {
+        return fromPointAndDirection(Vector1D.of(location), positiveFacing, precision);
+    }
+
+    /** Create a new instance from the given point and boolean direction value.
+     * @param point the location of the hyperplane
+     * @param positiveFacing if true, the hyperplane will face toward positive infinity;
+     *      otherwise, it will point toward negative infinity.
+     * @param precision precision context used to compare floating point values
+     * @return a new instance
+     */
+    public static OrientedPoint fromPointAndDirection(final Vector1D point, final boolean positiveFacing,
+            final DoublePrecisionContext precision) {
+        return new OrientedPoint(point, positiveFacing, precision);
+    }
+
+    /** Create a new instance from the given point and direction.
+     * @param point the location of the hyperplane
+     * @param direction the direction of the plus side of the hyperplane
+     * @param precision precision context used to compare floating point values
+     * @return a new instance oriented in the given direction
+     * @throws IllegalArgumentException if the direction is zero as evaluated by the
+     *      given precision context
+     */
+    public static OrientedPoint fromPointAndDirection(final Vector1D point, final Vector1D direction,
+            final DoublePrecisionContext precision) {
+        if (direction.isZero(precision)) {
+            throw new IllegalArgumentException("Oriented point direction cannot be zero");
+        }
+
+        final boolean positiveFacing = direction.getX() > 0;
+
+        return new OrientedPoint(point, positiveFacing, precision);
+    }
+
+    /** Create a new instance at the given point, oriented so that it is facing positive infinity.
+     * @param point the location of the hyperplane
+     * @param precision precision context used to compare floating point values
+     * @return a new instance oriented toward positive infinity
+     */
+    public static OrientedPoint createPositiveFacing(final Vector1D point, final DoublePrecisionContext precision) {
+        return new OrientedPoint(point, true, precision);
+    }
+
+    /** Create a new instance at the given location, oriented so that it is facing positive infinity.
+     * @param location the location of the hyperplane
+     * @param precision precision context used to compare floating point values
+     * @return a new instance oriented toward positive infinity
+     */
+    public static OrientedPoint createPositiveFacing(final double location, final DoublePrecisionContext precision) {
+        return new OrientedPoint(Vector1D.of(location), true, precision);
+    }
+
+    /** Create a new instance at the given point, oriented so that it is facing negative infinity.
+     * @param point the location of the hyperplane
+     * @param precision precision context used to compare floating point values
+     * @return a new instance oriented toward negative infinity
+     */
+    public static OrientedPoint createNegativeFacing(final Vector1D point, final DoublePrecisionContext precision) {
+        return new OrientedPoint(point, false, precision);
+    }
+
+    /** Create a new instance at the given location, oriented so that it is facing negative infinity.
+     * @param location the location of the hyperplane
+     * @param precision precision context used to compare floating point values
+     * @return a new instance oriented toward negative infinity
+     */
+    public static OrientedPoint createNegativeFacing(final double location, final DoublePrecisionContext precision) {
+        return new OrientedPoint(Vector1D.of(location), false, precision);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AbstractSubLine3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AbstractSubLine3D.java
deleted file mode 100644
index 35f8631..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AbstractSubLine3D.java
+++ /dev/null
@@ -1,56 +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.euclidean.threed;
-
-import org.apache.commons.geometry.core.Region;
-import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
-import org.apache.commons.geometry.euclidean.oned.Vector1D;
-
-/** Internal base class for 3 dimensional subline implementations.
- * @param <R> 1D subspace region type
- */
-abstract class AbstractSubLine3D<R extends Region<Vector1D>> {
-    /** The line that this instance belongs to. */
-    private final Line3D line;
-
-    /** Construct a new instance belonging to the given line.
-     * @param line line the instance belongs to
-     */
-    protected AbstractSubLine3D(final Line3D line) {
-        this.line = line;
-    }
-
-    /** Get the line that this subline belongs to.
-     * @return the line that this subline belongs to.
-     */
-    public Line3D getLine() {
-        return line;
-    }
-
-    /** Get the precision object used to perform floating point
-     * comparisons for this instance.
-     * @return the precision object for this instance
-     */
-    public DoublePrecisionContext getPrecision() {
-        return line.getPrecision();
-    }
-
-    /** Get the subspace region for the subline.
-     * @return the subspace region for the subline
-     */
-    public abstract R getSubspaceRegion();
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySource3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySource3D.java
index cc912a1..791d7f4 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySource3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySource3D.java
@@ -18,13 +18,16 @@ package org.apache.commons.geometry.euclidean.threed;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 import org.apache.commons.geometry.core.partitioning.BoundarySource;
+import org.apache.commons.geometry.euclidean.threed.line.LineConvexSubset3D;
+import org.apache.commons.geometry.euclidean.threed.line.LinecastPoint3D;
+import org.apache.commons.geometry.euclidean.threed.line.Linecastable3D;
 
-/** Extension of the {@link BoundarySource} interface for Euclidean 3D
- * space.
+/** Extension of the {@link BoundarySource} interface for Euclidean 3D space.
  */
-public interface BoundarySource3D extends BoundarySource<ConvexSubPlane> {
+public interface BoundarySource3D extends BoundarySource<PlaneConvexSubset>, Linecastable3D {
 
     /** Return a BSP tree constructed from the boundaries contained in this instance.
      * The default implementation creates a new, empty tree and inserts the
@@ -38,20 +41,32 @@ public interface BoundarySource3D extends BoundarySource<ConvexSubPlane> {
         return tree;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    default List<LinecastPoint3D> linecast(final LineConvexSubset3D subset) {
+        return new BoundarySourceLinecaster3D(this).linecast(subset);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    default LinecastPoint3D linecastFirst(final LineConvexSubset3D subset) {
+        return new BoundarySourceLinecaster3D(this).linecastFirst(subset);
+    }
+
     /** Return a {@link BoundarySource3D} instance containing the given boundaries.
      * @param boundaries boundaries to include in the boundary source
      * @return a boundary source containing the given boundaries
      */
-    static BoundarySource3D from(final ConvexSubPlane... boundaries) {
+    static BoundarySource3D from(final PlaneConvexSubset... boundaries) {
         return from(Arrays.asList(boundaries));
     }
 
     /** Return a {@link BoundarySource3D} instance containing the given boundaries. The given
-     * collection is used directly as the source of the subplanes; no copy is made.
+     * collection is used directly as the source of the boundaries; no copy is made.
      * @param boundaries boundaries to include in the boundary source
      * @return a boundary source containing the given boundaries
      */
-    static BoundarySource3D from(final Collection<ConvexSubPlane> boundaries) {
+    static BoundarySource3D from(final Collection<PlaneConvexSubset> boundaries) {
         return boundaries::stream;
     }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySourceLinecaster3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySourceLinecaster3D.java
index 73b0448..56caf89 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySourceLinecaster3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/BoundarySourceLinecaster3D.java
@@ -22,9 +22,13 @@ import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.apache.commons.geometry.euclidean.threed.line.LineConvexSubset3D;
+import org.apache.commons.geometry.euclidean.threed.line.LinecastPoint3D;
+import org.apache.commons.geometry.euclidean.threed.line.Linecastable3D;
+
 /** Class that performs linecast operations against arbitrary {@link BoundarySource3D}
  * instances. This class performs a brute-force computation of the intersections of the
- * line or line segment against all boundaries. Some data structures may support more
+ * line or line convex subset against all boundaries. Some data structures may support more
  * efficient algorithms and should therefore prefer those instead.
  */
 final class BoundarySourceLinecaster3D implements Linecastable3D {
@@ -41,8 +45,8 @@ final class BoundarySourceLinecaster3D implements Linecastable3D {
 
     /** {@inheritDoc} */
     @Override
-    public List<LinecastPoint3D> linecast(final Segment3D segment) {
-        final List<LinecastPoint3D> results =  getIntersectionStream(segment)
+    public List<LinecastPoint3D> linecast(final LineConvexSubset3D subset) {
+        final List<LinecastPoint3D> results =  getIntersectionStream(subset)
                 .collect(Collectors.toCollection(ArrayList::new));
 
         LinecastPoint3D.sortAndFilter(results);
@@ -52,37 +56,38 @@ final class BoundarySourceLinecaster3D implements Linecastable3D {
 
     /** {@inheritDoc} */
     @Override
-    public LinecastPoint3D linecastFirst(final Segment3D segment) {
-        return getIntersectionStream(segment)
+    public LinecastPoint3D linecastFirst(final LineConvexSubset3D subset) {
+        return getIntersectionStream(subset)
                 .min(LinecastPoint3D.ABSCISSA_ORDER)
                 .orElse(null);
     }
 
     /** Return a stream containing intersections between the boundary source and the
-     * given line segment.
-     * @param segment segment to intersect
+     * given line convex subset.
+     * @param subset line subset to intersect
      * @return a stream containing linecast intersections
      */
-    private Stream<LinecastPoint3D> getIntersectionStream(final Segment3D segment) {
+    private Stream<LinecastPoint3D> getIntersectionStream(final LineConvexSubset3D subset) {
         return boundarySrc.boundaryStream()
-                .map(boundary -> computeIntersection(boundary, segment))
+                .map(boundary -> computeIntersection(boundary, subset))
                 .filter(Objects::nonNull);
     }
 
-    /** Compute the intersection between a boundary subplane and segment. Null is
+    /** Compute the intersection between a boundary plane subset and line subset. Null is
      * returned if no intersection is discovered.
-     * @param subplane subplane from the boundary source
-     * @param segment linecast segment to intersect with
+     * @param planeSubset plane subset from the boundary source
+     * @param lineSubset line subset to intersect with
      * @return the linecast intersection between the two arguments or null if there is no such
      *      intersection
      */
-    private LinecastPoint3D computeIntersection(final ConvexSubPlane subplane, final Segment3D segment) {
-        final Vector3D intersectionPt = subplane.intersection(segment);
+    private LinecastPoint3D computeIntersection(final PlaneConvexSubset planeSubset,
+            final LineConvexSubset3D lineSubset) {
+        final Vector3D intersectionPt = planeSubset.intersection(lineSubset);
 
         if (intersectionPt != null) {
-            final Vector3D normal = subplane.getPlane().getNormal();
+            final Vector3D normal = planeSubset.getPlane().getNormal();
 
-            return new LinecastPoint3D(intersectionPt, normal, segment.getLine());
+            return new LinecastPoint3D(intersectionPt, normal, lineSubset.getLine());
         }
 
         return null; // no intersection
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexSubPlane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexSubPlane.java
deleted file mode 100644
index ab48979..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexSubPlane.java
+++ /dev/null
@@ -1,195 +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.euclidean.threed;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-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.precision.DoublePrecisionContext;
-import org.apache.commons.geometry.euclidean.threed.Plane.SubspaceTransform;
-import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
-import org.apache.commons.geometry.euclidean.twod.ConvexArea;
-import org.apache.commons.geometry.euclidean.twod.Vector2D;
-
-/** Class representing a convex subhyperplane in 3 dimensional Euclidean space, meaning
- * a 2D convex area embedded in a plane. The subhyperplane may be finite or infinite.
- */
-public final class ConvexSubPlane extends AbstractSubPlane<ConvexArea>
-    implements ConvexSubHyperplane<Vector3D>  {
-    /** The embedded 2D area. */
-    private final ConvexArea area;
-
-    /** Create a new instance from its component parts.
-     * @param plane plane the the convex area is embedded in
-     * @param area the embedded convex area
-     */
-    private ConvexSubPlane(final Plane plane, final ConvexArea area) {
-        super(plane);
-
-        this.area = area;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public List<ConvexSubPlane> toConvex() {
-        return Arrays.asList(this);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public ConvexSubPlane reverse() {
-        final Plane plane = getPlane();
-        final Plane rPlane = plane.reverse();
-
-        final Vector2D rU = rPlane.toSubspace(plane.toSpace(Vector2D.Unit.PLUS_X));
-        final Vector2D rV = rPlane.toSubspace(plane.toSpace(Vector2D.Unit.PLUS_Y));
-
-        final AffineTransformMatrix2D transform =
-                AffineTransformMatrix2D.fromColumnVectors(rU, rV);
-
-        return new ConvexSubPlane(rPlane, area.transform(transform));
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public ConvexSubPlane transform(final Transform<Vector3D> transform) {
-        final SubspaceTransform st = getPlane().subspaceTransform(transform);
-        final ConvexArea tArea = area.transform(st.getTransform());
-
-        return fromConvexArea(st.getPlane(), tArea);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public ConvexArea getSubspaceRegion() {
-        return area;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Split<ConvexSubPlane> split(final Hyperplane<Vector3D> splitter) {
-        return splitInternal(splitter, this, (p, r) -> new ConvexSubPlane(p, (ConvexArea) r));
-    }
-
-    /** Get the unique intersection of this subplane with the given line. Null is
-     * returned if no unique intersection point exists (ie, the line and plane are
-     * parallel or coincident) or the line does not intersect the subplane.
-     * @param line line to intersect with this subplane
-     * @return the unique intersection point between the line and this subplane
-     *      or null if no such point exists.
-     * @see Plane#intersection(Line3D)
-     */
-    public Vector3D intersection(final Line3D line) {
-        final Vector3D pt = getPlane().intersection(line);
-        return (pt != null && contains(pt)) ? pt : null;
-    }
-
-    /** Get the unique intersection of this subplane with the given segment. Null
-     * is returned if the underlying line and plane do not have a unique intersection
-     * point (ie, they are parallel or coincident) or the intersection point is unique
-     * but is not contained in both the segment and subplane.
-     * @param segment segment to intersect with
-     * @return the unique intersection point between this subplane and the argument or
-     *      null if no such point exists.
-     * @see Plane#intersection(Line3D)
-     */
-    public Vector3D intersection(final Segment3D segment) {
-        final Vector3D pt = intersection(segment.getLine());
-        return (pt != null && segment.contains(pt)) ? pt : null;
-    }
-
-    /** Get the vertices for the subplane. The vertices lie at the intersections of the
-     * 2D area bounding lines.
-     * @return the vertices for the subplane
-     */
-    public List<Vector3D> getVertices() {
-        return getPlane().toSpace(area.getVertices());
-    }
-
-    /** Create a new instance from a plane and an embedded convex subspace area.
-     * @param plane embedding plane for the area
-     * @param area area embedded in the plane
-     * @return a new convex sub plane instance
-     */
-    public static ConvexSubPlane fromConvexArea(final Plane plane, final ConvexArea area) {
-        return new ConvexSubPlane(plane, area);
-    }
-
-    /** Create a new instance from the given sequence of points. The points must define a unique plane, meaning that
-    * at least 3 unique vertices must be given. In contrast with the
-    * {@link #fromVertices(Collection, DoublePrecisionContext)} method, the first point in the sequence is included
-    * at the end if needed, in order to form a closed loop.
-    * @param pts collection of points defining the subplane
-    * @param precision precision context used to compare floating point values
-    * @return a new instance defined by the given sequence of vertices
-    * @throws IllegalArgumentException if fewer than 3 vertices are given or the vertices do not define a
-    *       unique plane
-    * @see #fromVertices(Collection, DoublePrecisionContext)
-    * @see #fromVertices(Collection, boolean, DoublePrecisionContext)
-    * @see Plane#fromPoints(Collection, DoublePrecisionContext)
-    */
-    public static ConvexSubPlane fromVertexLoop(final Collection<Vector3D> pts,
-            final DoublePrecisionContext precision) {
-        return fromVertices(pts, true, precision);
-    }
-
-    /** Create a new instance from the given sequence of points. The points must define a unique plane, meaning that
-     * at least 3 unique vertices must be given.
-     * @param pts collection of points defining the subplane
-     * @param precision precision context used to compare floating point values
-     * @return a new instance defined by the given sequence of vertices
-     * @throws IllegalArgumentException if fewer than 3 vertices are given or the vertices do not define a
-     *      unique plane
-     * @see #fromVertexLoop(Collection, DoublePrecisionContext)
-     * @see #fromVertices(Collection, boolean, DoublePrecisionContext)
-     * @see Plane#fromPoints(Collection, DoublePrecisionContext)
-     */
-    public static ConvexSubPlane fromVertices(final Collection<Vector3D> pts,
-            final DoublePrecisionContext precision) {
-        return fromVertices(pts, false, precision);
-    }
-
-    /** Create a new instance from the given sequence of points. The points must define a unique plane, meaning that
-     * at least 3 unique vertices must be given. If {@code close} is true, the vertices are made into a closed loop
-     * by including the start point at the end if needed.
-     * @param pts collection of points
-     * @param close if true, the point sequence will implicitly include the start point again at the end; otherwise
-     *      the vertex sequence is taken as-is
-     * @param precision precision context used to compare floating point values
-     * @return a new subplane instance
-     * @throws IllegalArgumentException if fewer than 3 vertices are given or the vertices do not define a
-     *      unique plane
-     * @see #fromVertexLoop(Collection, DoublePrecisionContext)
-     * @see #fromVertices(Collection, DoublePrecisionContext)
-     * @see Plane#fromPoints(Collection, DoublePrecisionContext)
-     */
-    public static ConvexSubPlane fromVertices(final Collection<Vector3D> pts, final boolean close,
-            final DoublePrecisionContext precision) {
-
-        final Plane plane = Plane.fromPoints(pts, precision);
-
-        final List<Vector2D> subspacePts = plane.toSubspace(pts);
-        final ConvexArea area = ConvexArea.fromVertices(subspacePts, close, precision);
-
-        return new ConvexSubPlane(plane, area);
-    }
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexVolume.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexVolume.java
index 4daedab..57949c3 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexVolume.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/ConvexVolume.java
@@ -23,16 +23,16 @@ import java.util.stream.Stream;
 
 import org.apache.commons.geometry.core.Transform;
 import org.apache.commons.geometry.core.partitioning.AbstractConvexHyperplaneBoundedRegion;
-import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
 import org.apache.commons.geometry.core.partitioning.Split;
 import org.apache.commons.geometry.euclidean.twod.ConvexArea;
 
 /** Class representing a finite or infinite convex volume in Euclidean 3D space.
- * The boundaries of this area, if any, are composed of convex subplanes.
+ * The boundaries of this area, if any, are composed of plane convex subsets.
  */
-public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D, ConvexSubPlane>
-    implements BoundarySource3D, Linecastable3D {
+public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D, PlaneConvexSubset>
+    implements BoundarySource3D {
 
     /** Instance representing the full 3D volume. */
     private static final ConvexVolume FULL = new ConvexVolume(Collections.emptyList());
@@ -41,13 +41,13 @@ public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D
      * represents the boundary of a convex area. No validation is performed.
      * @param boundaries the boundaries of the convex area
      */
-    protected ConvexVolume(final List<ConvexSubPlane> boundaries) {
+    protected ConvexVolume(final List<PlaneConvexSubset> boundaries) {
         super(boundaries);
     }
 
     /** {@inheritDoc} */
     @Override
-    public Stream<ConvexSubPlane> boundaryStream() {
+    public Stream<PlaneConvexSubset> boundaryStream() {
         return getBoundaries().stream();
     }
 
@@ -60,7 +60,7 @@ public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D
 
         double volumeSum = 0.0;
 
-        for (final ConvexSubPlane boundary : getBoundaries()) {
+        for (final PlaneConvexSubset boundary : getBoundaries()) {
             if (boundary.isInfinite()) {
                 return Double.POSITIVE_INFINITY;
             }
@@ -87,7 +87,7 @@ public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D
         double sumY = 0.0;
         double sumZ = 0.0;
 
-        for (final ConvexSubPlane boundary : getBoundaries()) {
+        for (final PlaneConvexSubset boundary : getBoundaries()) {
             if (boundary.isInfinite()) {
                 return null;
             }
@@ -126,7 +126,7 @@ public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D
     /** {@inheritDoc} */
     @Override
     public Split<ConvexVolume> split(final Hyperplane<Vector3D> splitter) {
-        return splitInternal(splitter, this, ConvexSubPlane.class, ConvexVolume::new);
+        return splitInternal(splitter, this, PlaneConvexSubset.class, ConvexVolume::new);
     }
 
     /** Return a BSP tree representing the same region as this instance.
@@ -138,20 +138,8 @@ public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D
 
     /** {@inheritDoc} */
     @Override
-    public List<LinecastPoint3D> linecast(final Segment3D segment) {
-        return new BoundarySourceLinecaster3D(this).linecast(segment);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public LinecastPoint3D linecastFirst(final Segment3D segment) {
-        return new BoundarySourceLinecaster3D(this).linecastFirst(segment);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public ConvexSubPlane trim(final ConvexSubHyperplane<Vector3D> convexSubHyperplane) {
-        return (ConvexSubPlane) super.trim(convexSubHyperplane);
+    public PlaneConvexSubset trim(final HyperplaneConvexSubset<Vector3D> convexSubset) {
+        return (PlaneConvexSubset) super.trim(convexSubset);
     }
 
     /** Return a new instance transformed by the argument.
@@ -159,7 +147,7 @@ public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D
      * @return a new instance transformed by the argument
      */
     public ConvexVolume transform(final Transform<Vector3D> transform) {
-        return transformInternal(transform, this, ConvexSubPlane.class, ConvexVolume::new);
+        return transformInternal(transform, this, PlaneConvexSubset.class, ConvexVolume::new);
     }
 
     /** Return an instance representing the full 3D volume.
@@ -196,7 +184,7 @@ public class ConvexVolume extends AbstractConvexHyperplaneBoundedRegion<Vector3D
      *      meaning that there is no region that is on the minus side of all of the bounding planes.
      */
     public static ConvexVolume fromBounds(final Iterable<Plane> boundingPlanes) {
-        final List<ConvexSubPlane> facets = new ConvexRegionBoundaryBuilder<>(ConvexSubPlane.class)
+        final List<PlaneConvexSubset> facets = new ConvexRegionBoundaryBuilder<>(PlaneConvexSubset.class)
                 .build(boundingPlanes);
         return facets.isEmpty() ? full() : new ConvexVolume(facets);
     }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubPlane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java
similarity index 54%
rename from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubPlane.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java
index e09d1b4..5c49f6f 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubPlane.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java
@@ -20,45 +20,45 @@ import java.util.ArrayList;
 import java.util.List;
 
 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.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 import org.apache.commons.geometry.core.partitioning.Split;
-import org.apache.commons.geometry.core.partitioning.SubHyperplane;
 import org.apache.commons.geometry.euclidean.twod.ConvexArea;
 import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
 
-/** Class representing an arbitrary region of a plane. This class can represent
- * both convex and non-convex regions of its underlying plane.
+/** Class representing an arbitrary subset of a plane using a {@link RegionBSPTree2D}.
+ * This class can represent convex, non-convex, finite, infinite, and empty regions.
  *
  * <p>This class is mutable and <em>not</em> thread safe.</p>
  */
-public final class SubPlane extends AbstractSubPlane<RegionBSPTree2D> {
+public final class EmbeddedTreePlaneSubset extends PlaneSubset {
     /** The 2D region representing the area on the plane. */
     private final RegionBSPTree2D region;
 
-    /** Construct a new, empty subplane for the given plane.
-     * @param plane plane defining the subplane
+    /** Construct a new, empty plane subset for the given plane.
+     * @param plane plane defining the subset
      */
-    public SubPlane(final Plane plane) {
+    public EmbeddedTreePlaneSubset(final Plane plane) {
         this(plane, false);
     }
 
-    /** Construct a new subplane for the given plane. If {@code full}
-     * is true, then the subplane will cover the entire plane; otherwise,
+    /** Construct a new subset for the given plane. If {@code full}
+     * is true, then the subset will cover the entire plane; otherwise,
      * it will be empty.
-     * @param plane plane defining the subplane
-     * @param full if true, the subplane will cover the entire space;
+     * @param plane plane defining the subset
+     * @param full if true, the subset will cover the entire space;
      *      otherwise it will be empty
      */
-    public SubPlane(final Plane plane, boolean full) {
+    public EmbeddedTreePlaneSubset(final Plane plane, boolean full) {
         this(plane, new RegionBSPTree2D(full));
     }
 
     /** Construct a new instance from its defining plane and subspace region.
-     * @param plane plane defining the subplane
-     * @param region subspace region for the subplane
+     * @param plane plane defining the subset
+     * @param region subspace region for the plane subset
      */
-    public SubPlane(final Plane plane, final RegionBSPTree2D region) {
+    public EmbeddedTreePlaneSubset(final Plane plane, final RegionBSPTree2D region) {
         super(plane);
 
         this.region = region;
@@ -66,14 +66,14 @@ public final class SubPlane extends AbstractSubPlane<RegionBSPTree2D> {
 
     /** {@inheritDoc} */
     @Override
-    public List<ConvexSubPlane> toConvex() {
+    public List<PlaneConvexSubset> toConvex() {
         final List<ConvexArea> areas = region.toConvex();
 
         final Plane plane = getPlane();
-        final List<ConvexSubPlane> facets = new ArrayList<>(areas.size());
+        final List<PlaneConvexSubset> facets = new ArrayList<>(areas.size());
 
         for (final ConvexArea area : areas) {
-            facets.add(ConvexSubPlane.fromConvexArea(plane, area));
+            facets.add(Planes.subsetFromConvexArea(plane, area));
         }
 
         return facets;
@@ -84,14 +84,14 @@ public final class SubPlane extends AbstractSubPlane<RegionBSPTree2D> {
      * <p>In all cases, the current instance is not modified. However, In order to avoid
      * unnecessary copying, this method will use the current instance as the split value when
      * the instance lies entirely on the plus or minus side of the splitter. For example, if
-     * this instance lies entirely on the minus side of the splitter, the subplane
+     * this instance lies entirely on the minus side of the splitter, the plane subset
      * returned by {@link Split#getMinus()} will be this instance. Similarly, {@link Split#getPlus()}
      * will return the current instance if it lies entirely on the plus side. Callers need to make
      * special note of this, since this class is mutable.</p>
      */
     @Override
-    public Split<SubPlane> split(final Hyperplane<Vector3D> splitter) {
-        return splitInternal(splitter, this, (p, r) -> new SubPlane(p, (RegionBSPTree2D) r));
+    public Split<EmbeddedTreePlaneSubset> split(final Hyperplane<Vector3D> splitter) {
+        return splitInternal(splitter, this, (p, r) -> new EmbeddedTreePlaneSubset(p, (RegionBSPTree2D) r));
     }
 
     /** {@inheritDoc} */
@@ -102,40 +102,40 @@ public final class SubPlane extends AbstractSubPlane<RegionBSPTree2D> {
 
     /** {@inheritDoc} */
     @Override
-    public SubPlane transform(final Transform<Vector3D> transform) {
+    public EmbeddedTreePlaneSubset transform(final Transform<Vector3D> transform) {
         final Plane.SubspaceTransform subTransform = getPlane().subspaceTransform(transform);
 
         final RegionBSPTree2D tRegion = RegionBSPTree2D.empty();
         tRegion.copy(region);
         tRegion.transform(subTransform.getTransform());
 
-        return new SubPlane(subTransform.getPlane(), tRegion);
+        return new EmbeddedTreePlaneSubset(subTransform.getPlane(), tRegion);
     }
 
-    /** Add a convex subplane to this instance.
-     * @param subplane convex subplane to add
-     * @throws IllegalArgumentException if the given subplane is not from
+    /** Add a plane convex subset to this instance.
+     * @param subset plane convex subset to add
+     * @throws IllegalArgumentException if the given plane subset is not from
      *      a plane equivalent to this instance
      */
-    public void add(final ConvexSubPlane subplane) {
-        validatePlane(subplane.getPlane());
+    public void add(final PlaneConvexSubset subset) {
+        validatePlane(subset.getPlane());
 
-        region.add(subplane.getSubspaceRegion());
+        region.add(subset.getSubspaceRegion());
     }
 
-    /** Add a subplane to this instance.
-     * @param subplane subplane to add
-     * @throws IllegalArgumentException if the given subplane is not from
+    /** Add a plane subset to this instance.
+     * @param subset plane subset to add
+     * @throws IllegalArgumentException if the given plane subset is not from
      *      a plane equivalent to this instance
      */
-    public void add(final SubPlane subplane) {
-        validatePlane(subplane.getPlane());
+    public void add(final EmbeddedTreePlaneSubset subset) {
+        validatePlane(subset.getPlane());
 
-        region.union(subplane.getSubspaceRegion());
+        region.union(subset.getSubspaceRegion());
     }
 
     /** Validate that the given plane is equivalent to the plane
-     * defining this subplane.
+     * defining this instance.
      * @param inputPlane plane to validate
      * @throws IllegalArgumentException if the given plane is not equivalent
      *      to the plane for this instance
@@ -150,48 +150,48 @@ public final class SubPlane extends AbstractSubPlane<RegionBSPTree2D> {
         }
     }
 
-    /** {@link Builder} implementation for sublines.
+    /** {@link HyperplaneSubset.Builder} implementation for plane subsets.
      */
-    public static class SubPlaneBuilder implements SubHyperplane.Builder<Vector3D> {
+    public static class Builder implements HyperplaneSubset.Builder<Vector3D> {
 
-        /** Subplane instance created by this builder. */
-        private final SubPlane subplane;
+        /** Plane subset instance created by this builder. */
+        private final EmbeddedTreePlaneSubset subset;
 
-        /** Construct a new instance for building subplane region for the given plane.
-         * @param plane the underlying plane for the subplane region
+        /** Construct a new instance for building a subset region for the given plane.
+         * @param plane the underlying plane for the subset
          */
-        public SubPlaneBuilder(final Plane plane) {
-            this.subplane = new SubPlane(plane);
+        public Builder(final Plane plane) {
+            this.subset = new EmbeddedTreePlaneSubset(plane);
         }
 
         /** {@inheritDoc} */
         @Override
-        public void add(final SubHyperplane<Vector3D> sub) {
+        public void add(final HyperplaneSubset<Vector3D> sub) {
             addInternal(sub);
         }
 
         /** {@inheritDoc} */
         @Override
-        public void add(final ConvexSubHyperplane<Vector3D> sub) {
+        public void add(final HyperplaneConvexSubset<Vector3D> sub) {
             addInternal(sub);
         }
 
         /** {@inheritDoc} */
         @Override
-        public SubPlane build() {
-            return subplane;
+        public EmbeddedTreePlaneSubset build() {
+            return subset;
         }
 
-        /** Internal method for adding subhyperplanes to this builder.
-         * @param sub the subhyperplane to add; either convex or non-convex
+        /** Internal method for adding hyperplane subsets to this builder.
+         * @param sub the hyperplane subset to add; either convex or non-convex
          */
-        private void addInternal(final SubHyperplane<Vector3D> sub) {
-            if (sub instanceof ConvexSubPlane) {
-                subplane.add((ConvexSubPlane) sub);
-            } else if (sub instanceof SubPlane) {
-                subplane.add((SubPlane) sub);
+        private void addInternal(final HyperplaneSubset<Vector3D> sub) {
+            if (sub instanceof PlaneConvexSubset) {
+                subset.add((PlaneConvexSubset) sub);
+            } else if (sub instanceof EmbeddedTreePlaneSubset) {
+                subset.add((EmbeddedTreePlaneSubset) sub);
             } else {
-                throw new IllegalArgumentException("Unsupported subhyperplane type: " + sub.getClass().getName());
+                throw new IllegalArgumentException("Unsupported plane subset type: " + sub.getClass().getName());
             }
         }
     }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
index 35781b8..4601680 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
@@ -16,9 +16,6 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
 import java.util.Objects;
 
 import org.apache.commons.geometry.core.Transform;
@@ -27,12 +24,15 @@ import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.geometry.euclidean.threed.line.Line3D;
+import org.apache.commons.geometry.euclidean.threed.line.Lines3D;
 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
 import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
 import org.apache.commons.geometry.euclidean.twod.ConvexArea;
 import org.apache.commons.geometry.euclidean.twod.Vector2D;
 
 /** Class representing a plane in 3 dimensional Euclidean space.
+ * @see Planes
  */
 public final class Plane extends AbstractHyperplane<Vector3D>
     implements EmbeddingHyperplane<Vector3D, Vector2D> {
@@ -57,7 +57,7 @@ public final class Plane extends AbstractHyperplane<Vector3D>
      * @param originOffset offset of the origin with respect to the plane.
      * @param precision precision context used to compare floating point values
      */
-    private Plane(final Vector3D u, final Vector3D v, final Vector3D w, double originOffset,
+    Plane(final Vector3D u, final Vector3D v, final Vector3D w, double originOffset,
             final DoublePrecisionContext precision) {
 
         super(precision);
@@ -169,7 +169,7 @@ public final class Plane extends AbstractHyperplane<Vector3D>
         final Vector3D p1 = project(line.getOrigin());
         final Vector3D p2 = p1.add(projectedLineDirection);
 
-        return Line3D.fromPoints(p1, p2, getPrecision());
+        return Lines3D.fromPoints(p1, p2, getPrecision());
     }
 
     /**
@@ -223,7 +223,7 @@ public final class Plane extends AbstractHyperplane<Vector3D>
         final Vector3D p2 = transform.apply(origin.add(u));
         final Vector3D p3 = transform.apply(origin.add(v));
 
-        return fromPoints(p1, p2, p3, getPrecision());
+        return Planes.fromPoints(p1, p2, p3, getPrecision());
     }
 
     /** Get an object containing the current plane transformed by the argument along with a
@@ -255,7 +255,7 @@ public final class Plane extends AbstractHyperplane<Vector3D>
         final Vector3D p2 = transform.apply(origin.add(u));
         final Vector3D p3 = transform.apply(origin.add(v));
 
-        final Plane tPlane = fromPoints(p1, p2, p3, getPrecision());
+        final Plane tPlane = Planes.fromPoints(p1, p2, p3, getPrecision());
 
         final Vector2D tSubspaceOrigin = tPlane.toSubspace(p1);
         final Vector2D tSubspaceU = tSubspaceOrigin.vectorTo(tPlane.toSubspace(p2));
@@ -374,8 +374,8 @@ public final class Plane extends AbstractHyperplane<Vector3D>
         if (getPrecision().eqZero(direction.norm())) {
             return null;
         }
-        final Vector3D point = intersection(this, other, Plane.fromNormal(direction, getPrecision()));
-        return Line3D.fromPointAndDirection(point, direction, getPrecision());
+        final Vector3D point = intersection(this, other, Planes.fromNormal(direction, getPrecision()));
+        return Lines3D.fromPointAndDirection(point, direction, getPrecision());
     }
 
     /**
@@ -426,8 +426,8 @@ public final class Plane extends AbstractHyperplane<Vector3D>
 
     /** {@inheritDoc} */
     @Override
-    public ConvexSubPlane span() {
-        return ConvexSubPlane.fromConvexArea(this, ConvexArea.full());
+    public PlaneConvexSubset span() {
+        return Planes.subsetFromConvexArea(this, ConvexArea.full());
     }
 
     /**
@@ -565,168 +565,6 @@ public final class Plane extends AbstractHyperplane<Vector3D>
         return sb.toString();
     }
 
-    /**
-     * Build a plane from a point and two (on plane) vectors.
-     * @param p the provided point (on plane)
-     * @param u u vector (on plane)
-     * @param v v vector (on plane)
-     * @param precision precision context used to compare floating point values
-     * @return a new plane
-     * @throws IllegalArgumentException if the norm of the given values is zero, NaN, or infinite.
-     */
-    public static Plane fromPointAndPlaneVectors(final Vector3D p, final Vector3D u, final Vector3D v,
-            final DoublePrecisionContext precision) {
-        final Vector3D uNorm = u.normalize();
-        final Vector3D vNorm = uNorm.orthogonal(v);
-        final Vector3D wNorm = uNorm.cross(vNorm).normalize();
-        final double originOffset = -p.dot(wNorm);
-
-        return new Plane(uNorm, vNorm, wNorm, originOffset, precision);
-    }
-
-    /**
-     * Build a plane from a normal.
-     * Chooses origin as point on plane.
-     * @param normal    normal direction to the plane
-     * @param precision precision context used to compare floating point values
-     * @return a new plane
-     * @throws IllegalArgumentException if the norm of the given values is zero, NaN, or infinite.
-     */
-    public static Plane fromNormal(final Vector3D normal, final DoublePrecisionContext precision) {
-        return fromPointAndNormal(Vector3D.ZERO, normal, precision);
-    }
-
-    /**
-     * Build a plane from a point and a normal.
-     *
-     * @param p         point belonging to the plane
-     * @param normal    normal direction to the plane
-     * @param precision precision context used to compare floating point values
-     * @return a new plane
-     * @throws IllegalArgumentException if the norm of the given values is zero, NaN, or infinite.
-     */
-    public static Plane fromPointAndNormal(final Vector3D p, final Vector3D normal,
-            final DoublePrecisionContext precision) {
-        final Vector3D w = normal.normalize();
-        final double originOffset = -p.dot(w);
-
-        final Vector3D u = w.orthogonal();
-        final Vector3D v = w.cross(u);
-
-        return new Plane(u, v, w, originOffset, precision);
-    }
-
-    /**
-     * Build a plane from three points.
-     * <p>
-     * The plane is oriented in the direction of {@code (p2-p1) ^ (p3-p1)}
-     * </p>
-     *
-     * @param p1        first point belonging to the plane
-     * @param p2        second point belonging to the plane
-     * @param p3        third point belonging to the plane
-     * @param precision precision context used to compare floating point values
-     * @return a new plane
-     * @throws IllegalArgumentException if the points do not define a unique plane
-     */
-    public static Plane fromPoints(final Vector3D p1, final Vector3D p2, final Vector3D p3,
-            final DoublePrecisionContext precision) {
-        return Plane.fromPoints(Arrays.asList(p1, p2, p3), precision);
-    }
-
-    /** Construct a plane from a collection of points lying on the plane. The plane orientation is
-     * determined by the overall orientation of the point sequence. For example, if the points wind
-     * around the z-axis in a counter-clockwise direction, then the plane normal will point up the
-     * +z axis. If the points wind in the opposite direction, then the plane normal will point down
-     * the -z axis. The {@code u} vector for the plane is set to the first non-zero vector between
-     * points in the sequence (ie, the first direction in the path).
-     *
-     * @param pts collection of sequenced points lying on the plane
-     * @param precision precision context used to compare floating point values
-     * @return a new plane containing the given points
-     * @throws IllegalArgumentException if the given collection does not contain at least 3 points or the
-     *      points do not define a unique plane
-     */
-    public static Plane fromPoints(final Collection<Vector3D> pts, final DoublePrecisionContext precision) {
-
-        if (pts.size() < 3) {
-            throw new IllegalArgumentException("At least 3 points are required to define a plane; " +
-                    "argument contains only " + pts.size() + ".");
-        }
-
-        final Iterator<Vector3D> it = pts.iterator();
-
-        final Vector3D startPt = it.next();
-
-        Vector3D u = null;
-        Vector3D w = null;
-
-        Vector3D currentPt;
-        Vector3D prevPt = startPt;
-
-        Vector3D currentVector = null;
-        Vector3D prevVector = null;
-
-        Vector3D cross = null;
-        double crossNorm;
-        double crossSumX = 0.0;
-        double crossSumY = 0.0;
-        double crossSumZ = 0.0;
-
-        boolean nonPlanar = false;
-
-        while (it.hasNext()) {
-            currentPt = it.next();
-
-            if (!currentPt.eq(prevPt, precision)) {
-                currentVector = startPt.vectorTo(currentPt);
-
-                if (u == null) {
-                    // save the first non-zero vector as our u vector
-                    u = currentVector.normalize();
-                }
-                if (prevVector != null) {
-                    cross = prevVector.cross(currentVector);
-
-                    crossSumX += cross.getX();
-                    crossSumY += cross.getY();
-                    crossSumZ += cross.getZ();
-
-                    crossNorm = cross.norm();
-
-                    if (!precision.eqZero(crossNorm)) {
-                        // the cross product has non-zero magnitude
-                        if (w == null) {
-                            // save the first non-zero cross product as our normal
-                            w = cross.normalize();
-                        } else if (!precision.eq(1.0, Math.abs(w.dot(cross) / crossNorm))) {
-                            // if the normalized dot product is not either +1 or -1, then
-                            // the points are not coplanar
-                            nonPlanar = true;
-                            break;
-                        }
-                    }
-                }
-
-                prevVector = currentVector;
-                prevPt = currentPt;
-            }
-        }
-
-        if (u == null || w == null || nonPlanar) {
-            throw new IllegalArgumentException("Points do not define a plane: " + pts);
-        }
-
-        if (w.dot(Vector3D.of(crossSumX, crossSumY, crossSumZ)) < 0) {
-            w = w.negate();
-        }
-
-        final Vector3D v = w.cross(u);
-        final double originOffset = -startPt.dot(w);
-
-        return new Plane(u, v, w, originOffset, precision);
-    }
-
     /** Class containing a transformed plane instance along with a subspace (2D) transform. The subspace
      * transform produces the equivalent of the 3D transform in 2D.
      */
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneConvexSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneConvexSubset.java
new file mode 100644
index 0000000..82573a5
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneConvexSubset.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.euclidean.threed.Plane.SubspaceTransform;
+import org.apache.commons.geometry.euclidean.threed.line.Line3D;
+import org.apache.commons.geometry.euclidean.threed.line.LineConvexSubset3D;
+import org.apache.commons.geometry.euclidean.twod.AffineTransformMatrix2D;
+import org.apache.commons.geometry.euclidean.twod.ConvexArea;
+import org.apache.commons.geometry.euclidean.twod.Vector2D;
+
+/** Class representing a convex subset of points in a plane. The subset may be finite
+ * or infinite.
+ * @see Planes
+ */
+public final class PlaneConvexSubset extends PlaneSubset
+    implements HyperplaneConvexSubset<Vector3D>  {
+    /** The embedded 2D area. */
+    private final ConvexArea area;
+
+    /** Create a new instance from its component parts.
+     * @param plane plane the the convex area is embedded in
+     * @param area the embedded convex area
+     */
+    PlaneConvexSubset(final Plane plane, final ConvexArea area) {
+        super(plane);
+
+        this.area = area;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<PlaneConvexSubset> toConvex() {
+        return Collections.singletonList(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PlaneConvexSubset reverse() {
+        final Plane plane = getPlane();
+        final Plane rPlane = plane.reverse();
+
+        final Vector2D rU = rPlane.toSubspace(plane.toSpace(Vector2D.Unit.PLUS_X));
+        final Vector2D rV = rPlane.toSubspace(plane.toSpace(Vector2D.Unit.PLUS_Y));
+
+        final AffineTransformMatrix2D transform =
+                AffineTransformMatrix2D.fromColumnVectors(rU, rV);
+
+        return new PlaneConvexSubset(rPlane, area.transform(transform));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public PlaneConvexSubset transform(final Transform<Vector3D> transform) {
+        final SubspaceTransform st = getPlane().subspaceTransform(transform);
+        final ConvexArea tArea = area.transform(st.getTransform());
+
+        return Planes.subsetFromConvexArea(st.getPlane(), tArea);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ConvexArea getSubspaceRegion() {
+        return area;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Split<PlaneConvexSubset> split(final Hyperplane<Vector3D> splitter) {
+        return splitInternal(splitter, this, (p, r) -> new PlaneConvexSubset(p, (ConvexArea) r));
+    }
+
+    /** Get the unique intersection of this plane subset with the given line. Null is
+     * returned if no unique intersection point exists (ie, the line and plane are
+     * parallel or coincident) or the line does not intersect the plane subset.
+     * @param line line to intersect with this plane subset
+     * @return the unique intersection point between the line and this plane subset
+     *      or null if no such point exists.
+     * @see Plane#intersection(Line3D)
+     */
+    public Vector3D intersection(final Line3D line) {
+        final Vector3D pt = getPlane().intersection(line);
+        return (pt != null && contains(pt)) ? pt : null;
+    }
+
+    /** Get the unique intersection of this plane subset with the given line subset. Null
+     * is returned if the underlying line and plane do not have a unique intersection
+     * point (ie, they are parallel or coincident) or the intersection point is unique
+     * but is not contained in both the line subset and plane subset.
+     * @param lineSubset line subset to intersect with
+     * @return the unique intersection point between this plane subset and the argument or
+     *      null if no such point exists.
+     * @see Plane#intersection(Line3D)
+     */
+    public Vector3D intersection(final LineConvexSubset3D lineSubset) {
+        final Vector3D pt = intersection(lineSubset.getLine());
+        return (pt != null && lineSubset.contains(pt)) ? pt : null;
+    }
+
+    /** Get the vertices for the plane subset. The vertices lie at the intersections of the
+     * 2D area bounding lines.
+     * @return the vertices for the plane subset
+     */
+    public List<Vector3D> getVertices() {
+        return getPlane().toSpace(area.getVertices());
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AbstractSubPlane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java
similarity index 79%
rename from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AbstractSubPlane.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java
index cea56a6..a3a3598 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AbstractSubPlane.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java
@@ -18,34 +18,35 @@ package org.apache.commons.geometry.euclidean.threed;
 
 import java.util.function.BiFunction;
 
-import org.apache.commons.geometry.core.partitioning.AbstractEmbeddingSubHyperplane;
+import org.apache.commons.geometry.core.partitioning.AbstractRegionEmbeddingHyperplaneSubset;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
 import org.apache.commons.geometry.core.partitioning.Split;
 import org.apache.commons.geometry.core.partitioning.SplitLocation;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
-import org.apache.commons.geometry.euclidean.threed.SubPlane.SubPlaneBuilder;
+import org.apache.commons.geometry.euclidean.threed.line.Line3D;
 import org.apache.commons.geometry.euclidean.twod.Line;
+import org.apache.commons.geometry.euclidean.twod.Lines;
 import org.apache.commons.geometry.euclidean.twod.Vector2D;
 
-/** Internal base class for subplane implementations.
- * @param <R> Subspace region type
+/** Class representing a subset of points in a 3D Euclidean space. For example, triangles
+ * and other polygons in 3D are plane subsets. Instances may be finite or infinite.
  */
-abstract class AbstractSubPlane<R extends HyperplaneBoundedRegion<Vector2D>>
-    extends AbstractEmbeddingSubHyperplane<Vector3D, Vector2D, Plane> {
+public abstract class PlaneSubset
+    extends AbstractRegionEmbeddingHyperplaneSubset<Vector3D, Vector2D, Plane> {
     /** The plane defining this instance. */
     private final Plane plane;
 
     /** Construct a new instance based on the given plane.
-     * @param plane the plane defining the subplane
+     * @param plane the plane defining the subset
      */
-    AbstractSubPlane(final Plane plane) {
+    PlaneSubset(final Plane plane) {
         this.plane = plane;
     }
 
-    /** Get the plane that this subplane lies on. This method is an alias
+    /** Get the plane that this subset lies on. This method is an alias
      * for {@link #getHyperplane()}.
-     * @return the plane that this subplane lies on
+     * @return the plane that this subset lies on
      * @see #getHyperplane()
      */
     public Plane getPlane() {
@@ -60,8 +61,8 @@ abstract class AbstractSubPlane<R extends HyperplaneBoundedRegion<Vector2D>>
 
     /** {@inheritDoc} */
     @Override
-    public SubPlaneBuilder builder() {
-        return new SubPlaneBuilder(plane);
+    public EmbeddedTreePlaneSubset.Builder builder() {
+        return new EmbeddedTreePlaneSubset.Builder(plane);
     }
 
     /** Return the object used to perform floating point comparisons, which is the
@@ -92,12 +93,12 @@ abstract class AbstractSubPlane<R extends HyperplaneBoundedRegion<Vector2D>>
      * @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 factory function used to create new subhyperplane instances
-     * @param <T> Subplane implementation type
+     * @param factory function used to create new hyperplane subset instances
+     * @param <T> Plane subset implementation type
      * @return the result of the split operation
      */
-    protected <T extends AbstractSubPlane<R>> Split<T> splitInternal(final Hyperplane<Vector3D> splitter,
-            final T thisInstance, final BiFunction<Plane, HyperplaneBoundedRegion<Vector2D>, T> factory) {
+    protected <T extends PlaneSubset> Split<T> splitInternal(final Hyperplane<Vector3D> splitter,
+                    final T thisInstance, final BiFunction<Plane, HyperplaneBoundedRegion<Vector2D>, T> factory) {
 
         final Plane thisPlane = thisInstance.getPlane();
         final Plane splitterPlane = (Plane) splitter;
@@ -123,7 +124,7 @@ abstract class AbstractSubPlane<R extends HyperplaneBoundedRegion<Vector2D>>
             final Vector2D subspaceP1 = thisPlane.toSubspace(intersectionOrigin);
             final Vector2D subspaceP2 = thisPlane.toSubspace(intersectionOrigin.add(intersection.getDirection()));
 
-            final Line subspaceSplitter = Line.fromPoints(subspaceP1, subspaceP2, getPrecision());
+            final Line subspaceSplitter = Lines.fromPoints(subspaceP1, subspaceP2, getPrecision());
 
             final Split<? extends HyperplaneBoundedRegion<Vector2D>> split =
                     thisInstance.getSubspaceRegion().split(subspaceSplitter);
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java
new file mode 100644
index 0000000..8a4f275
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Planes.java
@@ -0,0 +1,264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.twod.ConvexArea;
+import org.apache.commons.geometry.euclidean.twod.Vector2D;
+
+/** Class containing factory methods for constructing {@link Plane} and {@link PlaneSubset} instances.
+ */
+public final class Planes {
+
+    /** Utility class; no instantiation. */
+    private Planes() {
+    }
+
+    /**
+     * Build a plane from a point and two (on plane) vectors.
+     * @param p the provided point (on plane)
+     * @param u u vector (on plane)
+     * @param v v vector (on plane)
+     * @param precision precision context used to compare floating point values
+     * @return a new plane
+     * @throws IllegalArgumentException if the norm of the given values is zero, NaN, or infinite.
+     */
+    public static Plane fromPointAndPlaneVectors(final Vector3D p, final Vector3D u, final Vector3D v,
+            final DoublePrecisionContext precision) {
+        final Vector3D uNorm = u.normalize();
+        final Vector3D vNorm = uNorm.orthogonal(v);
+        final Vector3D wNorm = uNorm.cross(vNorm).normalize();
+        final double originOffset = -p.dot(wNorm);
+
+        return new Plane(uNorm, vNorm, wNorm, originOffset, precision);
+    }
+
+    /**
+     * Build a plane from a normal.
+     * Chooses origin as point on plane.
+     * @param normal normal direction to the plane
+     * @param precision precision context used to compare floating point values
+     * @return a new plane
+     * @throws IllegalArgumentException if the norm of the given values is zero, NaN, or infinite.
+     */
+    public static Plane fromNormal(final Vector3D normal, final DoublePrecisionContext precision) {
+        return fromPointAndNormal(Vector3D.ZERO, normal, precision);
+    }
+
+    /**
+     * Build a plane from a point and a normal.
+     *
+     * @param p point belonging to the plane
+     * @param normal normal direction to the plane
+     * @param precision precision context used to compare floating point values
+     * @return a new plane
+     * @throws IllegalArgumentException if the norm of the given values is zero, NaN, or infinite.
+     */
+    public static Plane fromPointAndNormal(final Vector3D p, final Vector3D normal,
+            final DoublePrecisionContext precision) {
+        final Vector3D w = normal.normalize();
+        final double originOffset = -p.dot(w);
+
+        final Vector3D u = w.orthogonal();
+        final Vector3D v = w.cross(u);
+
+        return new Plane(u, v, w, originOffset, precision);
+    }
+
+    /**
+     * Build a plane from three points.
+     * <p>
+     * The plane is oriented in the direction of {@code (p2-p1) ^ (p3-p1)}
+     * </p>
+     *
+     * @param p1 first point belonging to the plane
+     * @param p2 second point belonging to the plane
+     * @param p3 third point belonging to the plane
+     * @param precision precision context used to compare floating point values
+     * @return a new plane
+     * @throws IllegalArgumentException if the points do not define a unique plane
+     */
+    public static Plane fromPoints(final Vector3D p1, final Vector3D p2, final Vector3D p3,
+            final DoublePrecisionContext precision) {
+        return fromPoints(Arrays.asList(p1, p2, p3), precision);
+    }
+
+    /** Construct a plane from a collection of points lying on the plane. The plane orientation is
+     * determined by the overall orientation of the point sequence. For example, if the points wind
+     * around the z-axis in a counter-clockwise direction, then the plane normal will point up the
+     * +z axis. If the points wind in the opposite direction, then the plane normal will point down
+     * the -z axis. The {@code u} vector for the plane is set to the first non-zero vector between
+     * points in the sequence (ie, the first direction in the path).
+     *
+     * @param pts collection of sequenced points lying on the plane
+     * @param precision precision context used to compare floating point values
+     * @return a new plane containing the given points
+     * @throws IllegalArgumentException if the given collection does not contain at least 3 points or the
+     *      points do not define a unique plane
+     */
+    public static Plane fromPoints(final Collection<Vector3D> pts, final DoublePrecisionContext precision) {
+
+        if (pts.size() < 3) {
+            throw new IllegalArgumentException("At least 3 points are required to define a plane; " +
+                    "argument contains only " + pts.size() + ".");
+        }
+
+        final Iterator<Vector3D> it = pts.iterator();
+
+        final Vector3D startPt = it.next();
+
+        Vector3D u = null;
+        Vector3D w = null;
+
+        Vector3D currentPt;
+        Vector3D prevPt = startPt;
+
+        Vector3D currentVector = null;
+        Vector3D prevVector = null;
+
+        Vector3D cross = null;
+        double crossNorm;
+        double crossSumX = 0.0;
+        double crossSumY = 0.0;
+        double crossSumZ = 0.0;
+
+        boolean nonPlanar = false;
+
+        while (it.hasNext()) {
+            currentPt = it.next();
+
+            if (!currentPt.eq(prevPt, precision)) {
+                currentVector = startPt.vectorTo(currentPt);
+
+                if (u == null) {
+                    // save the first non-zero vector as our u vector
+                    u = currentVector.normalize();
+                }
+                if (prevVector != null) {
+                    cross = prevVector.cross(currentVector);
+
+                    crossSumX += cross.getX();
+                    crossSumY += cross.getY();
+                    crossSumZ += cross.getZ();
+
+                    crossNorm = cross.norm();
+
+                    if (!precision.eqZero(crossNorm)) {
+                        // the cross product has non-zero magnitude
+                        if (w == null) {
+                            // save the first non-zero cross product as our normal
+                            w = cross.normalize();
+                        } else if (!precision.eq(1.0, Math.abs(w.dot(cross) / crossNorm))) {
+                            // if the normalized dot product is not either +1 or -1, then
+                            // the points are not coplanar
+                            nonPlanar = true;
+                            break;
+                        }
+                    }
+                }
+
+                prevVector = currentVector;
+                prevPt = currentPt;
+            }
+        }
+
+        if (u == null || w == null || nonPlanar) {
+            throw new IllegalArgumentException("Points do not define a plane: " + pts);
+        }
+
+        if (w.dot(Vector3D.of(crossSumX, crossSumY, crossSumZ)) < 0) {
+            w = w.negate();
+        }
+
+        final Vector3D v = w.cross(u);
+        final double originOffset = -startPt.dot(w);
+
+        return new Plane(u, v, w, originOffset, precision);
+    }
+
+    /** Create a new plane subset from a plane and an embedded convex subspace area.
+     * @param plane embedding plane for the area
+     * @param area area embedded in the plane
+     * @return a new convex sub plane instance
+     */
+    public static PlaneConvexSubset subsetFromConvexArea(final Plane plane, final ConvexArea area) {
+        return new PlaneConvexSubset(plane, area);
+    }
+
+    /** Create a new plane subset from the given sequence of points. The points must define a unique plane,
+     * meaning that at least 3 unique vertices must be given. In contrast with the
+     * {@link #subsetFromVertices(Collection, DoublePrecisionContext)} method, the first point in the sequence
+     * is included at the end if needed, in order to form a closed loop.
+     * @param pts collection of points defining the plane subset
+     * @param precision precision context used to compare floating point values
+     * @return a new plane subset defined by the given sequence of vertices
+     * @throws IllegalArgumentException if fewer than 3 vertices are given or the vertices do not define a
+     *       unique plane
+     * @see #subsetFromVertices(Collection, boolean, DoublePrecisionContext)
+     * @see #fromPoints(Collection, DoublePrecisionContext)
+     */
+    public static PlaneConvexSubset subsetFromVertexLoop(final Collection<Vector3D> pts,
+            final DoublePrecisionContext precision) {
+        return subsetFromVertices(pts, true, precision);
+    }
+
+    /** Create a new plane subset from the given sequence of points. The points must define a unique plane,
+     * meaning that at least 3 unique vertices must be given.
+     * @param pts collection of points defining the plane subset
+     * @param precision precision context used to compare floating point values
+     * @return a new plane subset defined by the given sequence of vertices
+     * @throws IllegalArgumentException if fewer than 3 vertices are given or the vertices do not define a
+     *      unique plane
+     * @see #subsetFromVertexLoop(Collection, DoublePrecisionContext)
+     * @see #subsetFromVertices(Collection, boolean, DoublePrecisionContext)
+     * @see #fromPoints(Collection, DoublePrecisionContext)
+     */
+    public static PlaneConvexSubset subsetFromVertices(final Collection<Vector3D> pts,
+            final DoublePrecisionContext precision) {
+        return subsetFromVertices(pts, false, precision);
+    }
+
+    /** Create a new plane subset from the given sequence of points. The points must define a unique plane,
+     * meaning that at least 3 unique vertices must be given. If {@code close} is true, the vertices are made
+     * into a closed loop by including the start point at the end if needed.
+     * @param pts collection of points
+     * @param close if true, the point sequence will implicitly include the start point again at the end; otherwise
+     *      the vertex sequence is taken as-is
+     * @param precision precision context used to compare floating point values
+     * @return a new plane subset instance
+     * @throws IllegalArgumentException if fewer than 3 vertices are given or the vertices do not define a
+     *      unique plane
+     * @see #subsetFromVertexLoop(Collection, DoublePrecisionContext)
+     * @see #subsetFromVertices(Collection, boolean, DoublePrecisionContext)
+     * @see #fromPoints(Collection, DoublePrecisionContext)
+     */
+    public static PlaneConvexSubset subsetFromVertices(final Collection<Vector3D> pts, final boolean close,
+            final DoublePrecisionContext precision) {
+
+        final Plane plane = Planes.fromPoints(pts, precision);
+
+        final List<Vector2D> subspacePts = plane.toSubspace(pts);
+        final ConvexArea area = ConvexArea.fromVertices(subspacePts, close, precision);
+
+        return new PlaneConvexSubset(plane, area);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
index 8e6e9c2..177ded8 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/RegionBSPTree3D.java
@@ -22,12 +22,15 @@ import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 import org.apache.commons.geometry.core.partitioning.Split;
-import org.apache.commons.geometry.core.partitioning.SubHyperplane;
 import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
 import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
 import org.apache.commons.geometry.core.partitioning.bsp.BSPTreeVisitor;
 import org.apache.commons.geometry.core.partitioning.bsp.RegionCutBoundary;
+import org.apache.commons.geometry.euclidean.threed.line.Line3D;
+import org.apache.commons.geometry.euclidean.threed.line.LineConvexSubset3D;
+import org.apache.commons.geometry.euclidean.threed.line.LinecastPoint3D;
 import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
 import org.apache.commons.geometry.euclidean.twod.Vector2D;
 
@@ -35,7 +38,7 @@ import org.apache.commons.geometry.euclidean.twod.Vector2D;
  * Euclidean space.
  */
 public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, RegionBSPTree3D.RegionNode3D>
-    implements BoundarySource3D, Linecastable3D {
+    implements BoundarySource3D {
 
     /** Create a new, empty region. */
     public RegionBSPTree3D() {
@@ -64,20 +67,20 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
 
     /** {@inheritDoc} */
     @Override
-    public Iterable<ConvexSubPlane> boundaries() {
-        return createBoundaryIterable(b -> (ConvexSubPlane) b);
+    public Iterable<PlaneConvexSubset> boundaries() {
+        return createBoundaryIterable(b -> (PlaneConvexSubset) b);
     }
 
     /** {@inheritDoc} */
     @Override
-    public Stream<ConvexSubPlane> boundaryStream() {
+    public Stream<PlaneConvexSubset> boundaryStream() {
         return StreamSupport.stream(boundaries().spliterator(), false);
     }
 
     /** {@inheritDoc} */
     @Override
-    public List<ConvexSubPlane> getBoundaries() {
-        return createBoundaryList(b -> (ConvexSubPlane) b);
+    public List<PlaneConvexSubset> getBoundaries() {
+        return createBoundaryList(b -> (PlaneConvexSubset) b);
     }
 
     /** Return a list of {@link ConvexVolume}s representing the same region
@@ -144,8 +147,8 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
 
     /** {@inheritDoc} */
     @Override
-    public List<LinecastPoint3D> linecast(final Segment3D segment) {
-        final LinecastVisitor visitor = new LinecastVisitor(segment, false);
+    public List<LinecastPoint3D> linecast(final LineConvexSubset3D subset) {
+        final LinecastVisitor visitor = new LinecastVisitor(subset, false);
         accept(visitor);
 
         return visitor.getResults();
@@ -153,8 +156,8 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
 
     /** {@inheritDoc} */
     @Override
-    public LinecastPoint3D linecastFirst(final Segment3D segment) {
-        final LinecastVisitor visitor = new LinecastVisitor(segment, true);
+    public LinecastPoint3D linecastFirst(final LineConvexSubset3D subset) {
+        final LinecastVisitor visitor = new LinecastVisitor(subset, true);
         accept(visitor);
 
         return visitor.getFirstResult();
@@ -202,7 +205,7 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
      * @return a new tree instance constructed from the given boundaries
      * @see #from(Iterable, boolean)
      */
-    public static RegionBSPTree3D from(final Iterable<ConvexSubPlane> boundaries) {
+    public static RegionBSPTree3D from(final Iterable<PlaneConvexSubset> boundaries) {
         return from(boundaries, false);
     }
 
@@ -213,7 +216,7 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
      * @param full if true, the initial tree will contain the entire space
      * @return a new tree instance constructed from the given boundaries
      */
-    public static RegionBSPTree3D from(final Iterable<ConvexSubPlane> boundaries, final boolean full) {
+    public static RegionBSPTree3D from(final Iterable<PlaneConvexSubset> boundaries, final boolean full) {
         final RegionBSPTree3D tree = new RegionBSPTree3D(full);
         tree.insert(boundaries);
 
@@ -349,9 +352,9 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
          * @param boundary node cut boundary
          * @param reverse if true, the boundary contribution is reversed before being added to the total.
          */
-        private void addBoundaryContribution(final SubHyperplane<Vector3D> boundary, boolean reverse) {
-            final SubPlane subplane = (SubPlane) boundary;
-            final RegionBSPTree2D base = subplane.getSubspaceRegion();
+        private void addBoundaryContribution(final HyperplaneSubset<Vector3D> boundary, boolean reverse) {
+            final EmbeddedTreePlaneSubset boundarySubset = (EmbeddedTreePlaneSubset) boundary;
+            final RegionBSPTree2D base = boundarySubset.getSubspaceRegion();
 
             final double area = base.getSize();
             final Vector2D baseBarycenter = base.getBarycenter();
@@ -359,7 +362,7 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
             if (Double.isInfinite(area)) {
                 volumeSum = Double.POSITIVE_INFINITY;
             } else if (baseBarycenter != null) {
-                final Plane plane = subplane.getPlane();
+                final Plane plane = boundarySubset.getPlane();
                 final Vector3D facetBarycenter = plane.toSpace(base.getBarycenter());
 
                 // the volume here is actually 3x the actual pyramid volume; we'll apply
@@ -382,8 +385,8 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
      */
     private static final class LinecastVisitor implements BSPTreeVisitor<Vector3D, RegionNode3D> {
 
-        /** The line segment to intersect with the boundaries of the BSP tree. */
-        private final Segment3D linecastSegment;
+        /** The line subset to intersect with the boundaries of the BSP tree. */
+        private final LineConvexSubset3D linecastSubset;
 
         /** If true, the visitor will stop visiting the tree once the first linecast
          * point is determined.
@@ -396,13 +399,13 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
         /** List of results from the linecast operation. */
         private final List<LinecastPoint3D> results = new ArrayList<>();
 
-        /** Create a new instance with the given intersecting line segment.
-         * @param linecastSegment segment to intersect with the BSP tree region boundary
+        /** Create a new instance with the given intersecting line convex subset.
+         * @param linecastSubset line subset to intersect with the BSP tree region boundary
          * @param firstOnly if true, the visitor will stop visiting the tree once the first
          *      linecast point is determined
          */
-        LinecastVisitor(final Segment3D linecastSegment, final boolean firstOnly) {
-            this.linecastSegment = linecastSegment;
+        LinecastVisitor(final LineConvexSubset3D linecastSubset, final boolean firstOnly) {
+            this.linecastSubset = linecastSubset;
             this.firstOnly = firstOnly;
         }
 
@@ -431,7 +434,7 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
         @Override
         public Order visitOrder(final RegionNode3D internalNode) {
             final Plane cut = (Plane) internalNode.getCutHyperplane();
-            final Line3D line = linecastSegment.getLine();
+            final Line3D line = linecastSubset.getLine();
 
             final boolean plusIsNear = line.getDirection().dot(cut.getNormal()) < 0;
 
@@ -444,8 +447,8 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
         @Override
         public Result visit(final RegionNode3D node) {
             if (node.isInternal()) {
-                // check if the line segment intersects the cut subhyperplane
-                final Line3D line = linecastSegment.getLine();
+                // check if the line subset intersects the node cut hyperplane
+                final Line3D line = linecastSubset.getLine();
                 final Vector3D pt = ((Plane) node.getCutHyperplane()).intersection(line);
 
                 if (pt != null) {
@@ -454,7 +457,7 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
                         // we have results and we are now sure that no other intersection points will be
                         // found that are closer or at the same position on the intersecting line.
                         return Result.TERMINATE;
-                    } else if (linecastSegment.contains(pt)) {
+                    } else if (linecastSubset.contains(pt)) {
                         // we've potentially found a new linecast point; add it to the list of potential
                         // results
                         final LinecastPoint3D potentialResult = computeLinecastPoint(pt, node);
@@ -474,8 +477,7 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
         /** Compute the linecast point for the given intersection point and tree node, returning null
          * if the point does not actually lie on the region boundary.
          * @param pt intersection point
-         * @param node node containing the cut subhyperplane that the linecast line
-         *      intersected with
+         * @param node node containing the cut that the linecast line intersected with
          * @return a new linecast point instance or null if the intersection point does not lie
          *      on the region boundary
          */
@@ -501,7 +503,7 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
                     normal = normal.negate();
                 }
 
-                return new LinecastPoint3D(pt, normal, linecastSegment.getLine());
+                return new LinecastPoint3D(pt, normal, linecastSubset.getLine());
             }
 
             return null;
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Segment3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Segment3D.java
deleted file mode 100644
index e94ceef..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Segment3D.java
+++ /dev/null
@@ -1,267 +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.euclidean.threed;
-
-import org.apache.commons.geometry.core.Transform;
-import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
-import org.apache.commons.geometry.euclidean.oned.Interval;
-import org.apache.commons.geometry.euclidean.oned.Vector1D;
-import org.apache.commons.geometry.euclidean.threed.Line3D.SubspaceTransform;
-
-/** Class representing a line segment in 3 dimensional Euclidean space.
- *
- * <p>This class is guaranteed to be immutable.</p>
- */
-public final class Segment3D extends AbstractSubLine3D<Interval> {
-    /** String used to indicate the start point of the segment in the toString() representation. */
-    private static final String START_STR = "start= ";
-
-    /** String used to indicate the direction the segment in the toString() representation. */
-    private static final String DIR_STR = "direction= ";
-
-    /** String used to indicate the end point of the segment in the toString() representation. */
-    private static final String END_STR = "end= ";
-
-    /** String used as a separator value in the toString() representation. */
-    private static final String SEP_STR = ", ";
-
-    /** The interval representing the region of the line contained in
-     * the line segment.
-     */
-    private final Interval interval;
-
-    /** Construct a line segment from an underlying line and a 1D interval
-     * on it.
-     * @param line the underlying line
-     * @param interval 1D interval on the line defining the line segment
-     */
-    private Segment3D(final Line3D line, final Interval interval) {
-        super(line);
-
-        this.interval = interval;
-    }
-
-    /** Get the start value in the 1D subspace of the line.
-     * @return the start value in the 1D subspace of the line.
-     */
-    public double getSubspaceStart() {
-        return interval.getMin();
-    }
-
-    /** Get the end value in the 1D subspace of the line.
-     * @return the end value in the 1D subspace of the line
-     */
-    public double getSubspaceEnd() {
-        return interval.getMax();
-    }
-
-    /** Get the start point of the line segment or null if no start point
-     * exists (ie, the segment is infinite).
-     * @return the start point of the line segment or null if no start point
-     *      exists
-     */
-    public Vector3D getStartPoint() {
-        return interval.hasMinBoundary() ? getLine().toSpace(interval.getMin()) : null;
-    }
-
-    /** Get the end point of the line segment or null if no end point
-     * exists (ie, the segment is infinite).
-     * @return the end point of the line segment or null if no end point
-     *      exists
-     */
-    public Vector3D getEndPoint() {
-        return interval.hasMaxBoundary() ? getLine().toSpace(interval.getMax()) : null;
-    }
-
-    /** Return true if the segment is infinite.
-     * @return true if the segment is infinite.
-     */
-    public boolean isInfinite() {
-        return interval.isInfinite();
-    }
-
-    /** Return true if the segment is finite.
-     * @return true if the segment is finite.
-     */
-    public boolean isFinite() {
-        return interval.isFinite();
-    }
-
-    /** Return the 1D interval for the line segment.
-     * @return the 1D interval for the line segment
-     * @see #getSubspaceRegion()
-     */
-    public Interval getInterval() {
-        return interval;
-    }
-
-    /** {@inheritDoc}
-     *
-     * <p>This is an alias for {@link #getInterval()}.</p>
-     */
-    @Override
-    public Interval getSubspaceRegion() {
-        return getInterval();
-    }
-
-    /** Return true if the given point lies in the segment.
-     * @param pt point to check
-     * @return true if the point lies in the segment
-     */
-    public boolean contains(final Vector3D pt) {
-        final Line3D line = getLine();
-        return line.contains(pt) && interval.contains(line.toSubspace(pt));
-    }
-
-    /** Transform this instance.
-     * @param transform the transform to apply
-     * @return a new, transformed instance
-     */
-    public Segment3D transform(final Transform<Vector3D> transform) {
-        final SubspaceTransform st = getLine().subspaceTransform(transform);
-
-        return new Segment3D(st.getLine(), interval.transform(st.getTransform()));
-    }
-
-    /** Return a string representation of the segment.
-     *
-     * <p>In order to keep the representation short but informative, the exact format used
-     * depends on the properties of the instance, as demonstrated in the examples
-     * below.
-     * <ul>
-     *      <li>Infinite segment -
-     *          {@code "Segment3D[lineOrigin= (0.0, 0.0, 0.0), lineDirection= (1.0, 0.0, 0.0)]}"</li>
-     *      <li>Start point but no end point -
-     *          {@code "Segment3D[start= (0.0, 0.0, 0.0), direction= (1.0, 0.0, 0.0)]}"</li>
-     *      <li>End point but no start point -
-     *          {@code "Segment3D[direction= (1.0, 0.0, 0.0), end= (0.0, 0.0, 0.0)]}"</li>
-     *      <li>Start point and end point -
-     *          {@code "Segment3D[start= (0.0, 0.0, 0.0), end= (1.0, 0.0, 0.0)]}"</li>
-     * </ul>
-     */
-    @Override
-    public String toString() {
-        final Vector3D startPoint = getStartPoint();
-        final Vector3D endPoint = getEndPoint();
-
-        final StringBuilder sb = new StringBuilder();
-        sb.append(this.getClass().getSimpleName())
-            .append('[');
-
-        if (startPoint != null && endPoint != null) {
-            sb.append(START_STR)
-                .append(startPoint)
-                .append(SEP_STR)
-                .append(END_STR)
-                .append(endPoint);
-        } else if (startPoint != null) {
-            sb.append(START_STR)
-                .append(startPoint)
-                .append(SEP_STR)
-                .append(DIR_STR)
-                .append(getLine().getDirection());
-        } else if (endPoint != null) {
-            sb.append(DIR_STR)
-                .append(getLine().getDirection())
-                .append(SEP_STR)
-                .append(END_STR)
-                .append(endPoint);
-        } else {
-            final Line3D line = getLine();
-
-            sb.append("lineOrigin= ")
-                .append(line.getOrigin())
-                .append(SEP_STR)
-                .append("lineDirection= ")
-                .append(line.getDirection());
-        }
-
-        sb.append(']');
-
-        return sb.toString();
-    }
-
-    /** Create a line segment between two points. The underlying line points in the direction from {@code start}
-     * to {@code end}.
-     * @param start start point for the line segment
-     * @param end end point for the line segment
-     * @param precision precision context used to determine floating point equality
-     * @return a new line segment between {@code start} and {@code end}.
-     */
-    public static Segment3D fromPoints(final Vector3D start, final Vector3D end,
-            final DoublePrecisionContext precision) {
-
-        final Line3D line = Line3D.fromPoints(start, end, precision);
-        return fromPointsOnLine(line, start, end);
-    }
-
-    /** Construct a line segment from a starting point and a direction that the line should extend to
-     * infinity from. This is equivalent to constructing a ray.
-     * @param start start point for the segment
-     * @param direction direction that the line should extend from the segment
-     * @param precision precision context used to determine floating point equality
-     * @return a new line segment starting from the given point and extending to infinity in the
-     *      specified direction
-     */
-    public static Segment3D fromPointAndDirection(final Vector3D start, final Vector3D direction,
-            final DoublePrecisionContext precision) {
-        final Line3D line = Line3D.fromPointAndDirection(start, direction, precision);
-        return fromInterval(line, Interval.min(line.toSubspace(start).getX(), precision));
-    }
-
-    /** Create a line segment from an underlying line and a 1D interval on the line.
-     * @param line the line that the line segment will belong to
-     * @param interval 1D interval on the line
-     * @return a line segment defined by the given line and interval
-     */
-    public static Segment3D fromInterval(final Line3D line, final Interval interval) {
-        return new Segment3D(line, interval);
-    }
-
-    /** Create a line segment from an underlying line and a 1D interval on the line.
-     * @param line the line that the line segment will belong to
-     * @param a first 1D location on the line
-     * @param b second 1D location on the line
-     * @return a line segment defined by the given line and interval
-     */
-    public static Segment3D fromInterval(final Line3D line, final double a, final double b) {
-        return fromInterval(line, Interval.of(a, b, line.getPrecision()));
-    }
-
-    /** Create a line segment from an underlying line and a 1D interval on the line.
-     * @param line the line that the line segment will belong to
-     * @param a first 1D point on the line; must not be null
-     * @param b second 1D point on the line; must not be null
-     * @return a line segment defined by the given line and interval
-     */
-    public static Segment3D fromInterval(final Line3D line, final Vector1D a, final Vector1D b) {
-        return fromInterval(line, a.getX(), b.getX());
-    }
-
-    /** Create a new line segment from a line and points known to lie on the line.
-     * @param line the line that the line segment will belong to
-     * @param start line segment start point known to lie on the line
-     * @param end line segment end poitn known to lie on the line
-     * @return a new line segment created from the line and points
-     */
-    private static Segment3D fromPointsOnLine(final Line3D line, final Vector3D start, final Vector3D end) {
-        final double subspaceStart = line.toSubspace(start).getX();
-        final double subspaceEnd = line.toSubspace(end).getX();
-
-        return fromInterval(line, subspaceStart, subspaceEnd);
-    }
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubLine3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/EmbeddedTreeLineSubset3D.java
similarity index 61%
rename from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubLine3D.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/EmbeddedTreeLineSubset3D.java
index 925da52..8d5188c 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SubLine3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/EmbeddedTreeLineSubset3D.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.euclidean.threed;
+package org.apache.commons.geometry.euclidean.threed.line;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -22,40 +22,42 @@ import java.util.List;
 import org.apache.commons.geometry.core.Transform;
 import org.apache.commons.geometry.euclidean.oned.Interval;
 import org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D;
-import org.apache.commons.geometry.euclidean.threed.Line3D.SubspaceTransform;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.geometry.euclidean.threed.line.Line3D.SubspaceTransform;
 
-/** Class representing an arbitrary region of a 3 dimensional line. This class can represent
- * both convex and non-convex regions of its underlying line.
+/** Class representing an arbitrary subset of a line in 3D Euclidean space using a
+ * {@link RegionBSPTree1D}. This class can represent convex, non-convex, finite,
+ * infinite, and empty regions.
  *
  * <p>This class is mutable and <em>not</em> thread safe.</p>
  */
-public final class SubLine3D extends AbstractSubLine3D<RegionBSPTree1D> {
+public final class EmbeddedTreeLineSubset3D extends LineSubset3D {
     /** The 1D region representing the area on the line. */
     private final RegionBSPTree1D region;
 
-    /** Construct a new, empty subline for the given line.
-     * @param line line defining the subline
+    /** Construct a new, empty subset for the given line.
+     * @param line line defining the subset
      */
-    public SubLine3D(final Line3D line) {
+    public EmbeddedTreeLineSubset3D(final Line3D line) {
         this(line, false);
     }
 
-    /** Construct a new subline for the given line. If {@code full}
-     * is true, then the subline will cover the entire line; otherwise,
+    /** Construct a new subset for the given line. If {@code full}
+     * is true, then the subset will cover the entire line; otherwise,
      * it will be empty.
-     * @param line line defining the subline
-     * @param full if true, the subline will cover the entire space;
+     * @param line line defining the subset
+     * @param full if true, the subset will cover the entire space;
      *      otherwise it will be empty
      */
-    public SubLine3D(final Line3D line, boolean full) {
+    public EmbeddedTreeLineSubset3D(final Line3D line, boolean full) {
         this(line, new RegionBSPTree1D(full));
     }
 
     /** Construct a new instance from its defining line and subspace region.
-     * @param line line defining the subline
-     * @param region subspace region for the subline
+     * @param line line defining the subset
+     * @param region subspace region for the subset
      */
-    public SubLine3D(final Line3D line, final RegionBSPTree1D region) {
+    public EmbeddedTreeLineSubset3D(final Line3D line, final RegionBSPTree1D region) {
         super(line);
 
         this.region = region;
@@ -65,32 +67,32 @@ public final class SubLine3D extends AbstractSubLine3D<RegionBSPTree1D> {
      * @param transform the transform to apply
      * @return a new, transformed instance
      */
-    public SubLine3D transform(final Transform<Vector3D> transform) {
+    public EmbeddedTreeLineSubset3D transform(final Transform<Vector3D> transform) {
         final SubspaceTransform st = getLine().subspaceTransform(transform);
 
         final RegionBSPTree1D tRegion = RegionBSPTree1D.empty();
         tRegion.copy(region);
         tRegion.transform(st.getTransform());
 
-        return new SubLine3D(st.getLine(), tRegion);
+        return new EmbeddedTreeLineSubset3D(st.getLine(), tRegion);
     }
 
-    /** Return a list of {@link Segment3D} instances representing the same region
-     * as this subline.
-     * @return a list of {@link Segment3D} instances representing the same region
+    /** Return a list of {@link LineConvexSubset3D} instances representing the same region
+     * as this instance.
+     * @return a list of {@link LineConvexSubset3D} instances representing the same region
      *      as this instance.
      */
-    public List<Segment3D> toConvex() {
+    public List<LineConvexSubset3D> toConvex() {
         final List<Interval> intervals = region.toIntervals();
 
         final Line3D line = getLine();
-        final List<Segment3D> segments = new ArrayList<>(intervals.size());
+        final List<LineConvexSubset3D> convex = new ArrayList<>(intervals.size());
 
         for (final Interval interval : intervals) {
-            segments.add(Segment3D.fromInterval(line, interval));
+            convex.add(Lines3D.subsetFromInterval(line, interval));
         }
 
-        return segments;
+        return convex;
     }
 
     /** {@inheritDoc} */
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Line3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Line3D.java
similarity index 74%
rename from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Line3D.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Line3D.java
index c44d9ff..12d5743 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Line3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Line3D.java
@@ -14,22 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.euclidean.threed;
+package org.apache.commons.geometry.euclidean.threed.line;
 
+import java.text.MessageFormat;
 import java.util.Objects;
 
 import org.apache.commons.geometry.core.Embedding;
 import org.apache.commons.geometry.core.Transform;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
-import org.apache.commons.geometry.euclidean.oned.Interval;
 import org.apache.commons.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
 
 /** Class representing a line in 3D space.
  *
  * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @see Lines3D
  */
 public final class Line3D implements Embedding<Vector3D, Vector1D> {
+
+    /** Format string for creating line string representations. */
+    static final String TO_STRING_FORMAT = "{0}[origin= {1}, direction= {2}]";
+
     /** Line point closest to the origin. */
     private final Vector3D origin;
 
@@ -45,7 +51,7 @@ public final class Line3D implements Embedding<Vector3D, Vector1D> {
      * @param direction the direction of the line
      * @param precision precision context used to compare floating point numbers
      */
-    private Line3D(final Vector3D origin, final Vector3D direction, final DoublePrecisionContext precision) {
+    Line3D(final Vector3D origin, final Vector3D direction, final DoublePrecisionContext precision) {
         this.origin = origin;
         this.direction = direction;
         this.precision = precision;
@@ -89,7 +95,7 @@ public final class Line3D implements Embedding<Vector3D, Vector1D> {
         final Vector3D p1 = transform.apply(origin);
         final Vector3D p2 = transform.apply(origin.add(direction));
 
-        return fromPoints(p1, p2, precision);
+        return Lines3D.fromPoints(p1, p2, precision);
     }
 
     /** Get an object containing the current line transformed by the argument along with a
@@ -118,7 +124,7 @@ public final class Line3D implements Embedding<Vector3D, Vector1D> {
         final Vector3D p1 = transform.apply(origin);
         final Vector3D p2 = transform.apply(origin.add(direction));
 
-        final Line3D tLine = fromPoints(p1, p2, precision);
+        final Line3D tLine = Lines3D.fromPoints(p1, p2, precision);
 
         final Vector1D tSubspaceOrigin = tLine.toSubspace(p1);
         final Vector1D tSubspaceDirection = tSubspaceOrigin.vectorTo(tLine.toSubspace(p2));
@@ -137,12 +143,12 @@ public final class Line3D implements Embedding<Vector3D, Vector1D> {
      * 2D space). Abscissa values increase in the direction of the line. This method
      * is exactly equivalent to {@link #toSubspace(Vector3D)} except that this method
      * returns a double instead of a {@link Vector1D}.
-     * @param point point to compute the abscissa for
+     * @param pt point to compute the abscissa for
      * @return abscissa value of the point
      * @see #toSubspace(Vector3D)
      */
-    public double abscissa(final Vector3D point) {
-        return point.subtract(origin).dot(direction);
+    public double abscissa(final Vector3D pt) {
+        return pt.subtract(origin).dot(direction);
     }
 
     /** Get one point from the line.
@@ -155,13 +161,13 @@ public final class Line3D implements Embedding<Vector3D, Vector1D> {
 
     /** {@inheritDoc} */
     @Override
-    public Vector1D toSubspace(Vector3D pt) {
+    public Vector1D toSubspace(final Vector3D pt) {
         return Vector1D.of(abscissa(pt));
     }
 
     /** {@inheritDoc} */
     @Override
-    public Vector3D toSpace(Vector1D pt) {
+    public Vector3D toSpace(final Vector1D pt) {
         return toSpace(pt.getX());
     }
 
@@ -258,65 +264,85 @@ public final class Line3D implements Embedding<Vector3D, Vector1D> {
         return line.contains(closestPt) ? closestPt : null;
     }
 
-    /** Return a new infinite segment representing the entire line.
-     * @return a new infinite segment representing the entire line
+    /** Return a new infinite line subset representing the entire line.
+     * @return a new infinite line subset representing the entire line
+     * @see Lines3D#span(Line3D)
      */
-    public Segment3D span() {
-        return Segment3D.fromInterval(this, Interval.full());
+    public LineConvexSubset3D span() {
+        return Lines3D.span(this);
     }
 
-    /** Create a new line segment from the given interval.
-     * @param interval interval representing the 1D region for the line segment
-     * @return a new line segment on this line
-     */
-    public Segment3D segment(final Interval interval) {
-        return Segment3D.fromInterval(this, interval);
-    }
-
-    /** Create a new line segment from the given interval.
+    /** Create a new line segment from the given 1D interval. The returned line
+     * segment consists of all points between the two locations, regardless of the order the
+     * arguments are given.
      * @param a first 1D location for the interval
      * @param b second 1D location for the interval
      * @return a new line segment on this line
+     * @throws IllegalArgumentException if either of the locations is NaN or infinite
+     * @see Lines3D#segmentFromLocations(Line3D, double, double)
      */
     public Segment3D segment(final double a, final double b) {
-        return Segment3D.fromInterval(this, a, b);
+        return Lines3D.segmentFromLocations(this, a, b);
     }
 
-    /** Create a new line segment between the projections of the two
-     * given points onto this line.
+    /** Create a new line segment from two points. The returned segment represents all points on this line
+     * between the projected locations of {@code a} and {@code b}. The points may be given in any order.
      * @param a first point
      * @param b second point
      * @return a new line segment on this line
+     * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values
+     * @see Lines3D#segmentFromPoints(Line3D, Vector3D, Vector3D)
      */
     public Segment3D segment(final Vector3D a, final Vector3D b) {
-        return Segment3D.fromInterval(this, toSubspace(a), toSubspace(b));
+        return Lines3D.segmentFromPoints(this, a, b);
     }
 
-    /** Create a new line segment that starts at infinity and continues along
-     * the line up to the projection of the given point.
-     * @param pt point defining the end point of the line segment; the end point
+    /** Create a new line convex subset that starts at infinity and continues along
+     * the line up to the projection of the given end point.
+     * @param endPoint point defining the end point of the line subset; the end point
      *      is equal to the projection of this point onto the line
-     * @return a new, half-open line segment
+     * @return a new, half-open line subset that ends at the given point
+     * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
+     * @see Lines3D#reverseRayFromPoint(Line3D, Vector3D)
+     */
+    public ReverseRay3D reverseRayTo(final Vector3D endPoint) {
+        return Lines3D.reverseRayFromPoint(this, endPoint);
+    }
+
+    /** Create a new line convex subset that starts at infinity and continues along
+     * the line up to the given 1D location.
+     * @param endLocation the 1D location of the end of the half-line
+     * @return a new, half-open line subset that ends at the given 1D location
+     * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
+     * @see Lines3D#reverseRayFromLocation(Line3D, double)
      */
-    public Segment3D segmentTo(final Vector3D pt) {
-        return segment(Double.NEGATIVE_INFINITY, toSubspace(pt).getX());
+    public ReverseRay3D reverseRayTo(final double endLocation) {
+        return Lines3D.reverseRayFromLocation(this, endLocation);
     }
 
-    /** Create a new line segment that starts at the projection of the given point
-     * and continues in the direction of the line to infinity, similar to a ray.
-     * @param pt point defining the start point of the line segment; the start point
+    /** Create a new ray instance that starts at the projection of the given point
+     * and continues in the direction of the line to infinity.
+     * @param startPoint point defining the start point of the ray; the start point
      *      is equal to the projection of this point onto the line
-     * @return a new, half-open line segment
+     * @return a ray starting at the projected point and extending along this line
+     *      to infinity
+     * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
+     * @see Lines3D#rayFromPoint(Line3D, Vector3D)
      */
-    public Segment3D segmentFrom(final Vector3D pt) {
-        return segment(toSubspace(pt).getX(), Double.POSITIVE_INFINITY);
+    public Ray3D rayFrom(final Vector3D startPoint) {
+        return Lines3D.rayFromPoint(this, startPoint);
     }
 
-    /** Create a new, empty subline based on this line.
-     * @return a new, empty subline based on this line
+    /** Create a new ray instance that starts at the given 1D location and continues in
+     * the direction of the line to infinity.
+     * @param startLocation 1D location defining the start point of the ray
+     * @return a ray starting at the given 1D location and extending along this line
+     *      to infinity
+     * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
+     * @see Lines3D#rayFromLocation(Line3D, double)
      */
-    public SubLine3D subline() {
-        return new SubLine3D(this);
+    public Ray3D rayFrom(final double startLocation) {
+        return Lines3D.rayFromLocation(this, startLocation);
     }
 
     /** Return true if this instance should be considered equivalent to the argument, using the
@@ -356,46 +382,10 @@ public final class Line3D implements Embedding<Vector3D, Vector1D> {
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append(getClass().getSimpleName())
-            .append("[origin= ")
-            .append(origin)
-            .append(", direction= ")
-            .append(direction)
-            .append("]");
-
-        return sb.toString();
-    }
-
-    /** Create a new line instance from two points that lie on the line. The line
-     * direction points from the first point to the second point.
-     * @param p1 first point on the line
-     * @param p2 second point on the line
-     * @param precision floating point precision context
-     * @return a new line instance that contains both of the given point and that has
-     *      a direction going from the first point to the second point
-     * @throws IllegalArgumentException if the points lie too close to reate a non-zero direction vector
-     */
-    public static Line3D fromPoints(final Vector3D p1, final Vector3D p2,
-            final DoublePrecisionContext precision) {
-        return fromPointAndDirection(p1, p1.directionTo(p2), precision);
-    }
-
-    /** Create a new line instance from a point and a direction.
-     * @param pt a point lying on the line
-     * @param direction the direction of the line
-     * @param precision floating point precision context
-     * @return a new line instance that contains the given point and points in the
-     *      given direction
-     * @throws IllegalArgumentException if the direction cannot be normalized
-     */
-    public static Line3D fromPointAndDirection(final Vector3D pt, final Vector3D direction,
-            final DoublePrecisionContext precision) {
-
-        final Vector3D normDirection = direction.normalize();
-        final Vector3D origin = pt.reject(normDirection);
-
-        return new Line3D(origin, normDirection, precision);
+        return MessageFormat.format(TO_STRING_FORMAT,
+                getClass().getSimpleName(),
+                getOrigin(),
+                getDirection());
     }
 
     /** Class containing a transformed line instance along with a subspace (1D) transform. The subspace
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LineConvexSubset3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LineConvexSubset3D.java
new file mode 100644
index 0000000..e205b16
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LineConvexSubset3D.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.euclidean.threed.line;
+
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.euclidean.oned.Interval;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+
+/** Class representing a convex subset of a line in 3D Euclidean space. Instances
+ * need not be finite, in which case the start or end point (or both) will be null.
+ * @see Lines3D
+ */
+public abstract class LineConvexSubset3D extends LineSubset3D {
+
+    /** Construct a new instance for the given line.
+     * @param line line containing this convex subset
+     */
+    LineConvexSubset3D(final Line3D line) {
+        super(line);
+    }
+
+    /** Return true if the line subset is infinite.
+     * @return true if the line subset is infinite.
+     */
+    @Override
+    public abstract boolean isInfinite();
+
+    /** Return true if the line subset is finite.
+     * @return true if the line subset is finite.
+     */
+    @Override
+    public abstract boolean isFinite();
+
+    /** Get the start point for the line subset.
+     * @return the start point for the line subset, or null if no start point exists
+     */
+    public abstract Vector3D getStartPoint();
+
+    /** Get the 1D start location of the line subset or {@link Double#NEGATIVE_INFINITY} if
+     * no start location exists.
+     * @return the 1D start location of the line subset or {@link Double#NEGATIVE_INFINITY} if
+     *      no start location exists.
+     */
+    public abstract double getSubspaceStart();
+
+    /** Get the end point for the line subset.
+     * @return the end point for the line subset, or null if no end point exists.
+     */
+    public abstract Vector3D getEndPoint();
+
+    /** Get the 1D end location of the line subset or {@link Double#POSITIVE_INFINITY} if
+     * no end location exists.
+     * @return the 1D end location of the line subset or {@link Double#POSITIVE_INFINITY} if
+     *      no end location exists
+     */
+    public abstract double getSubspaceEnd();
+
+    /** Get the size (length) of the line subset.
+     * @return the size of the line subset
+     */
+    @Override
+    public abstract double getSize();
+
+    /** {@inheritDoc} */
+    @Override
+    public Interval getSubspaceRegion() {
+        final double start = getSubspaceStart();
+        final double end = getSubspaceEnd();
+
+        return Interval.of(start, end, getLine().getPrecision());
+    }
+
+    /** Get the 1D interval for the line subset. This method is an alias for {@link #getSubspaceRegion()}.
+     * @return the 1D interval for the line subset.
+     */
+    public Interval getInterval() {
+        return getSubspaceRegion();
+    }
+
+    /** Return true if the given point lies in the line subset.
+     * @param pt point to check
+     * @return true if the point lies in the line subset
+     */
+    public boolean contains(final Vector3D pt) {
+        final Line3D line = getLine();
+        return line.contains(pt) && containsAbscissa(line.abscissa(pt));
+    }
+
+    /** Transform this instance.
+     * @param transform the transform to apply
+     * @return a new, transformed instance
+     */
+    public abstract LineConvexSubset3D transform(Transform<Vector3D> transform);
+
+    /** Return true if the given abscissa value is contained in the line subset (ie, in the subspace region
+     * or one of its 1D boundaries).
+     * @param abscissa abscissa to check
+     * @return true if {@code abscissa} lies on the inside or boundary of the subspace region
+     */
+    abstract boolean containsAbscissa(double abscissa);
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LineSpanningSubset3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LineSpanningSubset3D.java
new file mode 100644
index 0000000..373e74b
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LineSpanningSubset3D.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed.line;
+
+import java.text.MessageFormat;
+
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+
+/** Class representing the span of a line in 3D Euclidean space. This is the set of all points
+ * contained by the line.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ */
+final class LineSpanningSubset3D extends LineConvexSubset3D {
+
+    /** Construct a new instance for the given line.
+     * @param line line to construct the span for
+     */
+    LineSpanningSubset3D(final Line3D line) {
+        super(line);
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code true}.</p>
+    */
+    @Override
+    public boolean isInfinite() {
+        return true;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code false}.</p>
+    */
+    @Override
+    public boolean isFinite() {
+        return false;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSize() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code null}.</p>
+    */
+    @Override
+    public Vector3D getStartPoint() {
+        return null;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#NEGATIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSubspaceStart() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code null}.</p>
+    */
+    @Override
+    public Vector3D getEndPoint() {
+        return null;
+    }
+
+    /** {@inheritDoc}
+     *
+     * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+     */
+    @Override
+    public double getSubspaceEnd() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public LineSpanningSubset3D transform(final Transform<Vector3D> transform) {
+        return new LineSpanningSubset3D(getLine().transform(transform));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final Line3D line = getLine();
+
+        return MessageFormat.format(Line3D.TO_STRING_FORMAT,
+                getClass().getSimpleName(),
+                line.getOrigin(),
+                line.getDirection());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    boolean containsAbscissa(final double abscissa) {
+        return true;
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LineSubset3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LineSubset3D.java
new file mode 100644
index 0000000..0a27e98
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LineSubset3D.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed.line;
+
+import org.apache.commons.geometry.core.RegionEmbedding;
+import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
+import org.apache.commons.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+
+/** Class representing a subset of a line in 3D Euclidean space. For example, line segments,
+ * rays, and disjoint combinations of the two are line subsets. Line subsets may be finite or infinite.
+ */
+public abstract class LineSubset3D implements RegionEmbedding<Vector3D, Vector1D> {
+    /** The line containing this instance. */
+    private final Line3D line;
+
+    /** Construct a new instance based on the given line.
+     * @param line line containing the instance
+     */
+    LineSubset3D(final Line3D line) {
+        this.line = line;
+    }
+
+    /** Get the line containing this subset.
+     * @return the line containing this subset
+     */
+    public Line3D getLine() {
+        return line;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D toSpace(final Vector1D pt) {
+        return line.toSpace(pt);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector1D toSubspace(final Vector3D pt) {
+        return line.toSubspace(pt);
+    }
+
+    /** Get the subspace region for the instance.
+     * @return the subspace region for the instance
+     */
+    @Override
+    public abstract HyperplaneBoundedRegion<Vector1D> getSubspaceRegion();
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/LinecastPoint3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LinecastPoint3D.java
similarity index 97%
rename from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/LinecastPoint3D.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LinecastPoint3D.java
index 35f5d66..1e1c46d 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/LinecastPoint3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/LinecastPoint3D.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.euclidean.threed;
+package org.apache.commons.geometry.euclidean.threed.line;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -24,6 +24,7 @@ import java.util.ListIterator;
 
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.euclidean.AbstractLinecastPoint;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
 
 /** Class representing intersections resulting from linecast operations in Euclidean
  * 3D space. This class contains the intersection point along with the boundary normal
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Linecastable3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Linecastable3D.java
similarity index 76%
rename from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Linecastable3D.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Linecastable3D.java
index 43f2cbb..1e9cfc7 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Linecastable3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Linecastable3D.java
@@ -14,18 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.euclidean.threed;
+package org.apache.commons.geometry.euclidean.threed.line;
 
 import java.util.List;
 
 /** Interface for objects that support linecast operations in Euclidean 3D space.
  *
  * <p>
- * Linecasting is a process that takes a line or line segment and intersects it with
+ * Linecasting is a process that takes a line or line convex subset and intersects it with
  * the boundaries of a region. This is similar to
  * <a href="https://en.wikipedia.org/wiki/Ray_casting">raycasting</a> used
  * for collision detection with the exception that the intersecting element can be a
- * line or line segment and not just a ray.
+ * line or line convex subset and not just a ray.
  * </p>
  */
 public interface Linecastable3D {
@@ -41,14 +41,14 @@ public interface Linecastable3D {
         return linecast(line.span());
     }
 
-    /** Intersect the given line segment against the boundaries in this instance, returning
-     * a list of all intersections in order of increasing distance along the line. An empty
-     * list is returned if no intersections are discovered.
-     * @param segment segment to intersect
+    /** Intersect the given line convex subset against the boundaries in this instance, returning
+     * a list of all intersections in order of increasing distance along the line. An empty list is
+     * returned if no intersections are discovered.
+     * @param subset line subset to intersect
      * @return a list of computed intersections in order of increasing distance
      *      along the line
      */
-    List<LinecastPoint3D> linecast(Segment3D segment);
+    List<LinecastPoint3D> linecast(LineConvexSubset3D subset);
 
     /** Intersect the given line against the boundaries in this instance, returning the first
      * intersection found when traveling in the direction of the line from infinity.
@@ -60,12 +60,12 @@ public interface Linecastable3D {
         return linecastFirst(line.span());
     }
 
-    /** Intersect the given line segment against the boundaries in this instance, returning
-     * the first intersection found when traveling in the direction of the line segment
-     * from its start point.
-     * @param segment segment to intersect
+    /** Intersect the given line convex subset against the boundaries in this instance, returning
+     * the first intersection found when traveling in the direction of the line subset from its
+     * start point.
+     * @param subset line subset to intersect
      * @return the first intersection found or null if no intersection
      *      is found
      */
-    LinecastPoint3D linecastFirst(Segment3D segment);
+    LinecastPoint3D linecastFirst(LineConvexSubset3D subset);
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Lines3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Lines3D.java
new file mode 100644
index 0000000..6ed1a2a
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Lines3D.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed.line;
+
+import java.text.MessageFormat;
+
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.oned.Interval;
+import org.apache.commons.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+
+/** Class containing factory methods for constructing {@link Line3D} and {@link LineSubset3D} instances.
+ */
+public final class Lines3D {
+
+    /** Utility class; no instantiation. */
+    private Lines3D() {
+    }
+
+    /** Create a new line instance from two points that lie on the line. The line
+     * direction points from the first point to the second point.
+     * @param p1 first point on the line
+     * @param p2 second point on the line
+     * @param precision floating point precision context
+     * @return a new line instance that contains both of the given point and that has
+     *      a direction going from the first point to the second point
+     * @throws IllegalArgumentException if the points lie too close to create a non-zero direction vector
+     */
+    public static Line3D fromPoints(final Vector3D p1, final Vector3D p2,
+            final DoublePrecisionContext precision) {
+        return fromPointAndDirection(p1, p1.vectorTo(p2), precision);
+    }
+
+    /** Create a new line instance from a point and a direction.
+     * @param pt a point lying on the line
+     * @param dir the direction of the line
+     * @param precision floating point precision context
+     * @return a new line instance that contains the given point and points in the
+     *      given direction
+     * @throws IllegalArgumentException if {@code dir} has zero length, as evaluated by the
+     *      given precision context
+     */
+    public static Line3D fromPointAndDirection(final Vector3D pt, final Vector3D dir,
+            final DoublePrecisionContext precision) {
+        if (dir.isZero(precision)) {
+            throw new IllegalArgumentException("Line direction cannot be zero");
+        }
+
+        final Vector3D normDirection = dir.normalize();
+        final Vector3D origin = pt.reject(normDirection);
+
+        return new Line3D(origin, normDirection, precision);
+    }
+
+    /** Construct a ray from a start point and a direction.
+     * @param startPoint ray start point
+     * @param direction ray direction
+     * @param precision precision context used for floating point comparisons
+     * @return a new ray instance with the given start point and direction
+     * @throws IllegalArgumentException If {@code direction} has zero length, as evaluated by the
+     *      given precision context
+     * @see Lines3D#fromPointAndDirection(Vector3D, Vector3D, DoublePrecisionContext)
+     */
+    public static Ray3D rayFromPointAndDirection(final Vector3D startPoint, final Vector3D direction,
+            final DoublePrecisionContext precision) {
+        final Line3D line = Lines3D.fromPointAndDirection(startPoint, direction, precision);
+
+        return new Ray3D(line, startPoint);
+    }
+
+    /** Construct a ray starting at the given point and continuing to infinity in the direction
+     * of {@code line}. The given point is projected onto the line.
+     * @param line line for the ray
+     * @param startPoint start point for the ray
+     * @return a new ray instance starting at the given point and continuing in the direction of
+     *      {@code line}
+     * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
+     */
+    public static Ray3D rayFromPoint(final Line3D line, final Vector3D startPoint) {
+        return rayFromLocation(line, line.abscissa(startPoint));
+    }
+
+    /** Construct a ray starting at the given 1D location on {@code line} and continuing in the
+     * direction of the line to infinity.
+     * @param line line for the ray
+     * @param startLocation 1D location of the ray start point
+     * @return a new ray instance starting at the given 1D location and continuing to infinity
+     *      along {@code line}
+     * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
+     */
+    public static Ray3D rayFromLocation(final Line3D line, final double startLocation) {
+        if (!Double.isFinite(startLocation)) {
+            throw new IllegalArgumentException("Invalid ray start location: " + Double.toString(startLocation));
+        }
+
+        return new Ray3D(line, startLocation);
+    }
+
+    /** Construct a reverse ray from an end point and a line direction.
+     * @param endPoint instance end point
+     * @param lineDirection line direction
+     * @param precision precision context used for floating point comparisons
+     * @return a new reverse ray with the given end point and line direction
+     * @throws IllegalArgumentException If {@code lineDirection} has zero length, as evaluated by the
+     *      given precision context
+     * @see Lines3D#fromPointAndDirection(Vector3D, Vector3D, DoublePrecisionContext)
+     */
+    public static ReverseRay3D reverseRayFromPointAndDirection(final Vector3D endPoint, final Vector3D lineDirection,
+            final DoublePrecisionContext precision) {
+        final Line3D line = Lines3D.fromPointAndDirection(endPoint, lineDirection, precision);
+
+        return new ReverseRay3D(line, endPoint);
+    }
+
+    /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
+     * to the given end point. The point is projected onto the line.
+     * @param line line for the instance
+     * @param endPoint end point for the instance
+     * @return a new reverse ray starting at infinity and continuing along the line to {@code endPoint}
+     * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
+     */
+    public static ReverseRay3D reverseRayFromPoint(final Line3D line, final Vector3D endPoint) {
+        return reverseRayFromLocation(line, line.abscissa(endPoint));
+    }
+
+    /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
+     * to the given 1D end location.
+     * @param line line for the instance
+     * @param endLocation 1D location of the instance end point
+     * @return a new reverse ray starting infinity and continuing in the direction of {@code line}
+     *      to the given 1D end location
+     * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
+     */
+    public static ReverseRay3D reverseRayFromLocation(final Line3D line, final double endLocation) {
+        if (!Double.isFinite(endLocation)) {
+            throw new IllegalArgumentException("Invalid reverse ray end location: " + Double.toString(endLocation));
+        }
+
+        return new ReverseRay3D(line, endLocation);
+    }
+
+    /** Construct a new line segment from two points. A new line is created for the segment and points in the
+     * direction from {@code startPoint} to {@code endPoint}.
+     * @param startPoint segment start point
+     * @param endPoint segment end point
+     * @param precision precision context to use for floating point comparisons
+     * @return a new line segment instance with the given start and end points
+     * @throws IllegalArgumentException If the vector between {@code startPoint} and {@code endPoint} has zero length,
+     *      as evaluated by the given precision context
+     * @see Lines3D#fromPoints(Vector3D, Vector3D, DoublePrecisionContext)
+     */
+    public static Segment3D segmentFromPoints(final Vector3D startPoint, final Vector3D endPoint,
+            final DoublePrecisionContext precision) {
+        final Line3D line = Lines3D.fromPoints(startPoint, endPoint, precision);
+
+        // we know that the points lie on the line and are in increasing abscissa order
+        // since they were used to create the line
+        return new Segment3D(line, startPoint, endPoint);
+    }
+
+    /** Construct a new line segment from a line and a pair of points. The returned segment represents
+     * all points on the line between the projected locations of {@code a} and {@code b}. The points may
+     * be given in any order.
+     * @param line line forming the base of the segment
+     * @param a first point
+     * @param b second point
+     * @return a new line segment representing the points between the projected locations of {@code a}
+     *      and {@code b} on the given line
+     * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values)
+     */
+    public static Segment3D segmentFromPoints(final Line3D line, final Vector3D a, final Vector3D b) {
+        return segmentFromLocations(line, line.abscissa(a), line.abscissa(b));
+    }
+
+    /** Construct a new line segment from a pair of 1D locations on a line. The returned line
+     * segment consists of all points between the two locations, regardless of the order the
+     * arguments are given.
+     * @param line line forming the base of the segment
+     * @param a first 1D location on the line
+     * @param b second 1D location on the line
+     * @return a new line segment representing the points between {@code a} and {@code b} on
+     *      the given line
+     * @throws IllegalArgumentException if either of the locations is NaN or infinite
+     */
+    public static Segment3D segmentFromLocations(final Line3D line, final double a, final double b) {
+
+        if (Double.isFinite(a) && Double.isFinite(b)) {
+            final double min = Math.min(a, b);
+            final double max = Math.max(a, b);
+
+            return new Segment3D(line, min, max);
+        }
+
+        throw new IllegalArgumentException(
+                MessageFormat.format("Invalid line segment locations: {0}, {1}",
+                        Double.toString(a), Double.toString(b)));
+    }
+
+    /** Create a {@link LineConvexSubset3D} spanning the entire line. In other words, the returned
+     * subset is infinite and contains all points on the given line.
+     * @param line the line to span
+     * @return a convex subset spanning the entire line
+     */
+    public static LineConvexSubset3D span(final Line3D line) {
+        return new LineSpanningSubset3D(line);
+    }
+
+    /** Create a line convex subset from a line and a 1D interval on the line.
+     * @param line the line containing the subset
+     * @param interval 1D interval on the line
+     * @return a line convex subset defined by the given line and interval
+     */
+    public static LineConvexSubset3D subsetFromInterval(final Line3D line, final Interval interval) {
+        return subsetFromInterval(line, interval.getMin(), interval.getMax());
+    }
+
+    /** Create a line convex subset from a line and a 1D interval on the line.
+     * @param line the line containing the subset
+     * @param a first 1D location on the line
+     * @param b second 1D location on the line
+     * @return a line convex subset defined by the given line and interval
+     */
+    public static LineConvexSubset3D subsetFromInterval(final Line3D line, final double a, final double b) {
+        final double min = Math.min(a, b);
+        final double max = Math.max(a, b);
+
+        final boolean hasMin = Double.isFinite(min);
+        final boolean hasMax = Double.isFinite(max);
+
+        if (hasMin) {
+            if (hasMax) {
+                // has both
+                return new Segment3D(line, min, max);
+            }
+            // min only
+            return new Ray3D(line, min);
+        } else if (hasMax) {
+            // max only
+            return new ReverseRay3D(line, max);
+        } else if (Double.isInfinite(min) && Double.isInfinite(max) && Double.compare(min, max) < 0) {
+            return new LineSpanningSubset3D(line);
+        }
+
+        throw new IllegalArgumentException(MessageFormat.format(
+                "Invalid line convex subset interval: {0}, {1}", Double.toString(a), Double.toString(b)));
+    }
+
+    /** Create a line convex subset from a line and a 1D interval on the line.
+     * @param line the line containing the subset
+     * @param a first 1D point on the line; must not be null
+     * @param b second 1D point on the line; must not be null
+     * @return a line convex subset defined by the given line and interval
+     */
+    public static LineConvexSubset3D subsetFromInterval(final Line3D line, final Vector1D a, final Vector1D b) {
+        return subsetFromInterval(line, a.getX(), b.getX());
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Ray3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Ray3D.java
new file mode 100644
index 0000000..4975d50
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Ray3D.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed.line;
+
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+
+/** Class representing a ray in 3D Euclidean space. A ray is a portion of a line consisting of
+ * a single start point and extending to infinity along the direction of the line.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @see ReverseRay3D
+ * @see Lines3D
+ * @see <a href="https://en.wikipedia.org/wiki/Line_(geometry)#Ray">Ray</a>
+ */
+public final class Ray3D extends LineConvexSubset3D {
+
+    /** The start abscissa value for the ray. */
+    private final double start;
+
+    /** Construct a ray from a line and a start point. The start point is projected
+     * onto the line. No validation is performed.
+     * @param line line for the ray
+     * @param startPoint start point for the ray
+     */
+    Ray3D(final Line3D line, final Vector3D startPoint) {
+        this(line, line.abscissa(startPoint));
+    }
+
+    /** Construct a ray from a line and a 1D start location. No validation is performed.
+     * @param line line for the ray
+     * @param start 1D start location
+     */
+    Ray3D(final Line3D line, final double start) {
+        super(line);
+
+        this.start = start;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code true}.</p>
+    */
+    @Override
+    public boolean isInfinite() {
+        return true;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code false}.</p>
+    */
+    @Override
+    public boolean isFinite() {
+        return false;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSize() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    @Override
+    public Vector3D getStartPoint() {
+        return getLine().toSpace(start);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getSubspaceStart() {
+        return start;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code null}.</p>
+    */
+    @Override
+    public Vector3D getEndPoint() {
+        return null;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSubspaceEnd() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** Get the direction of the ray. This is a convenience method for {@code ray.getLine().getDirection()}.
+     * @return the direction of the ray
+     */
+    public Vector3D getDirection() {
+        return getLine().getDirection();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Ray3D transform(final Transform<Vector3D> transform) {
+        final Line3D tLine = getLine().transform(transform);
+        final Vector3D tStart = transform.apply(getStartPoint());
+
+        return new Ray3D(tLine, tStart);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName())
+            .append("[startPoint= ")
+            .append(getStartPoint())
+            .append(", direction= ")
+            .append(getLine().getDirection())
+            .append(']');
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    boolean containsAbscissa(final double abscissa) {
+        return getLine().getPrecision().gte(abscissa, start);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/ReverseRay3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/ReverseRay3D.java
new file mode 100644
index 0000000..480efec
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/ReverseRay3D.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed.line;
+
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+
+/** Class representing a portion of a line in 3D Euclidean space that starts at infinity and
+ * continues in the direction of the line up to a single end point. This is equivalent to taking a
+ * {@link Ray3D} and reversing the line direction.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @see Ray3D
+ * @see Lines3D
+ */
+public final class ReverseRay3D extends LineConvexSubset3D {
+
+    /** The abscissa of the line subset endpoint. */
+    private final double end;
+
+    /** Construct a new instance from the given line and end point. The end point is projected onto
+     * the line. No validation is performed.
+     * @param line line for the instance
+     * @param endPoint end point for the instance
+     */
+    ReverseRay3D(final Line3D line, final Vector3D endPoint) {
+        this(line, line.abscissa(endPoint));
+    }
+
+    /** Construct a new instance from the given line and 1D end location. No validation is performed.
+     * @param line line for the instance
+     * @param end end location for the instance
+     */
+    ReverseRay3D(final Line3D line, final double end) {
+        super(line);
+
+        this.end = end;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code true}.</p>
+    */
+    @Override
+    public boolean isInfinite() {
+        return true;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code false}.</p>
+    */
+    @Override
+    public boolean isFinite() {
+        return false;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSize() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code null}.</p>
+    */
+    @Override
+    public Vector3D getStartPoint() {
+        return null;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#NEGATIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSubspaceStart() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D getEndPoint() {
+        return getLine().toSpace(end);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getSubspaceEnd() {
+        return end;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ReverseRay3D transform(final Transform<Vector3D> transform) {
+        final Line3D tLine = getLine().transform(transform);
+        final Vector3D tEnd = transform.apply(getEndPoint());
+
+        return new ReverseRay3D(tLine, tEnd);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName())
+            .append("[direction= ")
+            .append(getLine().getDirection())
+            .append(", endPoint= ")
+            .append(getEndPoint())
+            .append(']');
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    boolean containsAbscissa(final double abscissa) {
+        return getLine().getPrecision().lte(abscissa, end);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Segment3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Segment3D.java
new file mode 100644
index 0000000..c351c43
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/Segment3D.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed.line;
+
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+
+/** Class representing a line segment in 3D Euclidean space. A line segment is a portion of
+ * a line with finite start and end points.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @see Lines3D
+ * @see <a href="https://en.wikipedia.org/wiki/Line_segment">Line Segment</a>
+ */
+public final class Segment3D extends LineConvexSubset3D {
+
+    /** Start abscissa for the segment. */
+    private final double start;
+
+    /** End abscissa for the segment. */
+    private final double end;
+
+    /** Construct a new instance from a line and two points on the line. The points are projected onto
+     * the line and must be in order of increasing abscissa. No validation is performed.
+     * @param line line for the segment
+     * @param startPoint segment start point
+     * @param endPoint segment end point
+     */
+    Segment3D(final Line3D line, final Vector3D startPoint, final Vector3D endPoint) {
+        this(line, line.abscissa(startPoint), line.abscissa(endPoint));
+    }
+
+    /** Construct a new instance from a line and two abscissa locations on the line.
+     * The abscissa locations must be in increasing order. No validation is performed.
+     * @param line line for the segment
+     * @param start abscissa start location
+     * @param end abscissa end location
+     */
+    Segment3D(final Line3D line, final double start, final double end) {
+        super(line);
+
+        this.start = start;
+        this.end = end;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code false}.</p>
+    */
+    @Override
+    public boolean isInfinite() {
+        return false;
+    }
+
+    /** {@inheritDoc}
+     *
+     * <p>This method always returns {@code true}.</p>
+     */
+    @Override
+    public boolean isFinite() {
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D getStartPoint() {
+        return getLine().toSpace(start);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getSubspaceStart() {
+        return start;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D getEndPoint() {
+        return getLine().toSpace(end);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getSubspaceEnd() {
+        return end;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getSize() {
+        return end - start;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Segment3D transform(final Transform<Vector3D> transform) {
+        final Vector3D t1 = transform.apply(getStartPoint());
+        final Vector3D t2 = transform.apply(getEndPoint());
+
+        final Line3D tLine = getLine().transform(transform);
+
+        return new Segment3D(tLine, t1, t2);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName())
+            .append("[startPoint= ")
+            .append(getStartPoint())
+            .append(", endPoint= ")
+            .append(getEndPoint())
+            .append(']');
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    boolean containsAbscissa(final double abscissa) {
+        final DoublePrecisionContext precision = getLine().getPrecision();
+        return precision.gte(abscissa, start) &&
+                precision.lte(abscissa, end);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/package-info.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/package-info.java
similarity index 83%
copy from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/package-info.java
copy to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/package-info.java
index 5009b79..028a331 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/package-info.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/line/package-info.java
@@ -15,6 +15,10 @@
  * limitations under the License.
  */
 /**
- * This package provides utilities for constructing basic 3D shapes.
+ *
+ * <p>
+ * This package provides classes and utilities for lines in 3D Euclidean space.
+ * </p>
+ *
  */
-package org.apache.commons.geometry.euclidean.threed.shapes;
+package org.apache.commons.geometry.euclidean.threed.line;
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/Parallelepiped.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shape/Parallelepiped.java
similarity index 96%
rename from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/Parallelepiped.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shape/Parallelepiped.java
index 4266be1..fc99ec2 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/Parallelepiped.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shape/Parallelepiped.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.euclidean.threed.shapes;
+package org.apache.commons.geometry.euclidean.threed.shape;
 
 import java.text.MessageFormat;
 import java.util.Arrays;
@@ -24,8 +24,9 @@ import java.util.stream.Collectors;
 import org.apache.commons.geometry.core.Transform;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.euclidean.threed.AffineTransformMatrix3D;
-import org.apache.commons.geometry.euclidean.threed.ConvexSubPlane;
 import org.apache.commons.geometry.euclidean.threed.ConvexVolume;
+import org.apache.commons.geometry.euclidean.threed.PlaneConvexSubset;
+import org.apache.commons.geometry.euclidean.threed.Planes;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
 
@@ -53,7 +54,7 @@ public final class Parallelepiped extends ConvexVolume {
      * @param boundaries the boundaries of the parallelepiped; this must be a list
      *      with 6 elements
      */
-    private Parallelepiped(final List<ConvexSubPlane> boundaries) {
+    private Parallelepiped(final List<PlaneConvexSubset> boundaries) {
         super(boundaries);
     }
 
@@ -152,7 +153,7 @@ public final class Parallelepiped extends ConvexVolume {
         ensureNonZeroSideLength(vertices.get(1), vertices.get(2), precision);
         ensureNonZeroSideLength(vertices.get(0), vertices.get(4), precision);
 
-        final List<ConvexSubPlane> boundaries = Arrays.asList(
+        final List<PlaneConvexSubset> boundaries = Arrays.asList(
                     // planes orthogonal to x
                     createFace(0, 4, 7, 3, vertices, reverse, precision),
                     createFace(1, 2, 6, 5, vertices, reverse, precision),
@@ -187,7 +188,7 @@ public final class Parallelepiped extends ConvexVolume {
      * @param precision precision context used to create the face
      * @return a parallelepiped face created from the indexed vertices
      */
-    private static ConvexSubPlane createFace(final int a, final int b, final int c, final int d,
+    private static PlaneConvexSubset createFace(final int a, final int b, final int c, final int d,
             final List<Vector3D> vertices, final boolean reverse, final DoublePrecisionContext precision) {
 
         final Vector3D pa = vertices.get(a);
@@ -199,7 +200,7 @@ public final class Parallelepiped extends ConvexVolume {
                 Arrays.asList(pd, pc, pb, pa) :
                 Arrays.asList(pa, pb, pc, pd);
 
-        return ConvexSubPlane.fromVertexLoop(loop, precision);
+        return Planes.subsetFromVertexLoop(loop, precision);
     }
 
     /** Ensure that the given points defining one side of a parallelepiped face are separated by a non-zero
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/Sphere.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shape/Sphere.java
similarity index 91%
rename from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/Sphere.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shape/Sphere.java
index b6e2253..8c0d789 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/Sphere.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shape/Sphere.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.commons.geometry.euclidean.threed.shapes;
+package org.apache.commons.geometry.euclidean.threed.shape;
 
 import java.text.MessageFormat;
 import java.util.List;
@@ -24,14 +24,14 @@ import java.util.stream.Stream;
 import org.apache.commons.geometry.core.partitioning.bsp.RegionCutRule;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.euclidean.AbstractNSphere;
-import org.apache.commons.geometry.euclidean.threed.Line3D;
-import org.apache.commons.geometry.euclidean.threed.LinecastPoint3D;
-import org.apache.commons.geometry.euclidean.threed.Linecastable3D;
-import org.apache.commons.geometry.euclidean.threed.Plane;
+import org.apache.commons.geometry.euclidean.threed.Planes;
 import org.apache.commons.geometry.euclidean.threed.RegionBSPTree3D;
-import org.apache.commons.geometry.euclidean.threed.Segment3D;
 import org.apache.commons.geometry.euclidean.threed.SphericalCoordinates;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.geometry.euclidean.threed.line.Line3D;
+import org.apache.commons.geometry.euclidean.threed.line.LineConvexSubset3D;
+import org.apache.commons.geometry.euclidean.threed.line.LinecastPoint3D;
+import org.apache.commons.geometry.euclidean.threed.line.Linecastable3D;
 import org.apache.commons.numbers.angle.PlaneAngleRadians;
 
 /** Class representing a 3 dimensional sphere in Euclidean space.
@@ -137,28 +137,28 @@ public final class Sphere extends AbstractNSphere<Vector3D> implements Linecasta
 
     /** {@inheritDoc} */
     @Override
-    public List<LinecastPoint3D> linecast(final Segment3D segment) {
-        return getLinecastStream(segment)
+    public List<LinecastPoint3D> linecast(final LineConvexSubset3D subset) {
+        return getLinecastStream(subset)
                 .collect(Collectors.toList());
     }
 
     /** {@inheritDoc} */
     @Override
-    public LinecastPoint3D linecastFirst(final Segment3D segment) {
-        return getLinecastStream(segment)
+    public LinecastPoint3D linecastFirst(final LineConvexSubset3D subset) {
+        return getLinecastStream(subset)
                 .findFirst()
                 .orElse(null);
     }
 
     /** Get a stream containing the linecast intersection points of the given
-     * segment with this instance.
-     * @param segment segment to intersect against this instance
+     * line subset with this instance.
+     * @param subset line subset to intersect against this instance
      * @return a stream containing linecast intersection points
      */
-    private Stream<LinecastPoint3D> getLinecastStream(final Segment3D segment) {
-        return intersections(segment.getLine()).stream()
-            .filter(segment::contains)
-            .map(pt -> new LinecastPoint3D(pt, getCenter().directionTo(pt), segment.getLine()));
+    private Stream<LinecastPoint3D> getLinecastStream(final LineConvexSubset3D subset) {
+        return intersections(subset.getLine()).stream()
+            .filter(subset::contains)
+            .map(pt -> new LinecastPoint3D(pt, getCenter().directionTo(pt), subset.getLine()));
     }
 
     /** Construct a sphere from a center point and radius.
@@ -205,7 +205,7 @@ public final class Sphere extends AbstractNSphere<Vector3D> implements Linecasta
         /** Create a new instance for approximating the given sphere.
          * @param sphere sphere to approximate
          * @param stacks number of "stacks" (sections parallel to the x-y plane) in the approximation
-         * @param slices number of "slices" (boundary subplanes per stack) in the approximation
+         * @param slices number of "slices" (boundary plane subsets per stack) in the approximation
          * @throws IllegalArgumentException if {@code stacks} is less than 2 or {@code slices} is less than 3
          */
         SphereApproximationBuilder(final Sphere sphere, final int stacks, final int slices) {
@@ -258,7 +258,7 @@ public final class Sphere extends AbstractNSphere<Vector3D> implements Linecasta
                 final int splitIdx = (indexDiff / 2) + polarTopIdx;
                 final Vector3D splitPt = pointAt(splitIdx, 0);
 
-                node.cut(Plane.fromPointAndNormal(splitPt, Vector3D.Unit.PLUS_Z, sphere.getPrecision()),
+                node.cut(Planes.fromPointAndNormal(splitPt, Vector3D.Unit.PLUS_Z, sphere.getPrecision()),
                         RegionCutRule.INHERIT);
 
                 splitAndInsertStacks(node.getPlus(), polarTopIdx, splitIdx);
@@ -288,7 +288,7 @@ public final class Sphere extends AbstractNSphere<Vector3D> implements Linecasta
                 final Vector3D p1 = pointAt(polarBottomIdx, splitIdx);
                 final Vector3D p2 = pointAt(polarBottomIdx - 1, splitIdx);
 
-                splitNode.cut(Plane.fromPoints(p0, p1, p2, sphere.getPrecision()), RegionCutRule.INHERIT);
+                splitNode.cut(Planes.fromPoints(p0, p1, p2, sphere.getPrecision()), RegionCutRule.INHERIT);
 
                 splitAndInsertSlices(splitNode.getPlus(), polarBottomIdx, 0, splitIdx);
                 splitAndInsertSlices(splitNode.getMinus(), polarBottomIdx, splitIdx, sliceEndIdx);
@@ -314,7 +314,7 @@ public final class Sphere extends AbstractNSphere<Vector3D> implements Linecasta
                 final Vector3D p1 = pointAt(polarBottomIdx, splitIdx);
                 final Vector3D p2 = pointAt(polarBottomIdx - 1, splitIdx);
 
-                node.cut(Plane.fromPoints(p0, p1, p2, sphere.getPrecision()), RegionCutRule.INHERIT);
+                node.cut(Planes.fromPoints(p0, p1, p2, sphere.getPrecision()), RegionCutRule.INHERIT);
 
                 splitAndInsertSlices(node.getPlus(), polarBottomIdx, azimuthStartIdx, splitIdx);
                 splitAndInsertSlices(node.getMinus(), polarBottomIdx, splitIdx, azimuthStopIdx);
@@ -349,7 +349,7 @@ public final class Sphere extends AbstractNSphere<Vector3D> implements Linecasta
                         pointAt(polarBottomIdx - 1, i - 1) :
                         pointAt(polarBottomIdx, i - 1);
 
-                currNode = currNode.cut(Plane.fromPoints(p0, p1, p2, sphere.getPrecision()))
+                currNode = currNode.cut(Planes.fromPoints(p0, p1, p2, sphere.getPrecision()))
                         .getMinus();
             }
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/package-info.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shape/package-info.java
similarity index 93%
copy from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/package-info.java
copy to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shape/package-info.java
index 5009b79..0d21e5f 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shapes/package-info.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/shape/package-info.java
@@ -17,4 +17,4 @@
 /**
  * This package provides utilities for constructing basic 3D shapes.
  */
-package org.apache.commons.geometry.euclidean.threed.shapes;
+package org.apache.commons.geometry.euclidean.threed.shape;
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AbstractSegmentConnector.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AbstractSegmentConnector.java
deleted file mode 100644
index e6d21b3..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AbstractSegmentConnector.java
+++ /dev/null
@@ -1,298 +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.euclidean.twod;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import org.apache.commons.geometry.euclidean.internal.AbstractPathConnector;
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
-
-/** Abstract class for joining collections of line segments into connected
- * paths. This class is not thread-safe.
- */
-public abstract class AbstractSegmentConnector
-    extends AbstractPathConnector<AbstractSegmentConnector.ConnectableSegment> {
-    /** Add a line segment to the connector, leaving it unconnected until a later call to
-     * to {@link #connect(Iterable)} or {@link #connectAll()}.
-     * @param segment line segment to add
-     * @see #connect(Iterable)
-     * @see #connectAll()
-     */
-    public void add(final Segment segment) {
-        addPathElement(new ConnectableSegment(segment));
-    }
-
-    /** Add a collection of line segments to the connector, leaving them unconnected
-     * until a later call to {@link #connect(Iterable)} or
-     * {@link #connectAll()}.
-     * @param segments line segments to add
-     * @see #connect(Iterable)
-     * @see #connectAll()
-     * @see #add(Segment)
-     */
-    public void add(final Iterable<Segment> segments) {
-        for (final Segment segment : segments) {
-            add(segment);
-        }
-    }
-
-    /** Add a collection of line segments to the connector and attempt to connect each new
-     * segment with existing segments. Connections made at this time will not be
-     * overwritten by subsequent calls to this or other connection methods.
-     * (eg, {@link #connectAll()}).
-     *
-     * <p>The connector is not reset by this call. Additional segments can still be added
-     * to the current set of paths.</p>
-     * @param segments line segments to connect
-     * @see #connectAll()
-     */
-    public void connect(final Iterable<Segment> segments) {
-        final List<ConnectableSegment> newEntries = new ArrayList<>();
-
-        for (final Segment segment : segments) {
-            newEntries.add(new ConnectableSegment(segment));
-        }
-
-        connectPathElements(newEntries);
-    }
-
-    /** Add the given line segments to this instance and connect all current
-     * segments into polylines (ie, line segment paths). This call is equivalent to
-     * <pre>
-     *      connector.add(segments);
-     *      List&lt;Polyline&gt; result = connector.connectAll();
-     * </pre>
-     *
-     * <p>The connector is reset after this call. Further calls to
-     * add or connect line segments will result in new paths being
-     * generated.</p>
-     * @param segments line segments to add
-     * @return the connected line segment paths
-     * @see #add(Iterable)
-     * @see #connectAll()
-     */
-    public List<Polyline> connectAll(final Iterable<Segment> segments) {
-        add(segments);
-        return connectAll();
-    }
-
-    /** Connect all current segments into connected paths, returning the result as a
-     * list of polylines.
-     *
-     * <p>The connector is reset after this call. Further calls to
-     * add or connect line segments will result in new paths being
-     * generated.</p>
-     * @return the connected line segments paths
-     */
-    public List<Polyline> connectAll() {
-        final List<ConnectableSegment> roots = computePathRoots();
-        final List<Polyline> paths = new ArrayList<>(roots.size());
-
-        for (final ConnectableSegment root : roots) {
-            paths.add(toPolyline(root));
-        }
-
-        return paths;
-    }
-
-    /** Convert the linked list of path elements starting at the argument
-     * into a {@link Polyline}.
-     * @param root root of a connected path linked list
-     * @return a polyline representing the linked list path
-     */
-    private Polyline toPolyline(final ConnectableSegment root) {
-        final Polyline.Builder builder = Polyline.builder(null);
-
-        builder.append(root.getSegment());
-
-        ConnectableSegment current = root.getNext();
-
-        while (current != null && current != root) {
-            builder.append(current.getSegment());
-            current = current.getNext();
-        }
-
-        return builder.build();
-    }
-
-    /** Internal class used to connect line segments together.
-     */
-    protected static class ConnectableSegment extends AbstractPathConnector.ConnectableElement<ConnectableSegment> {
-        /** Segment start point. This will be used to connect to other path elements. */
-        private final Vector2D start;
-
-        /** Line segment for the entry. */
-        private final Segment segment;
-
-        /** Create a new instance with the given start point. This constructor is
-         * intended only for performing searches for other path elements.
-         * @param start start point
-         */
-        public ConnectableSegment(final Vector2D start) {
-            this(start, null);
-        }
-
-        /** Create a new instance from the given line segment.
-         * @param segment line segment
-         */
-        public ConnectableSegment(final Segment segment) {
-            this(segment.getStartPoint(), segment);
-        }
-
-        /** Create a new instance with the given start point and line segment.
-         * @param start start point
-         * @param segment line segment
-         */
-        private ConnectableSegment(final Vector2D start, final Segment segment) {
-            this.start = start;
-            this.segment = segment;
-        }
-
-        /** Get the line segment for this instance.
-         * @return the line segment for this instance
-         */
-        public Segment getSegment() {
-            return segment;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean hasStart() {
-            return start != null;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean hasEnd() {
-            return segment != null && segment.getEndPoint() != null;
-        }
-
-        /** Return true if this instance has a size equivalent to zero.
-         * @return true if this instance has a size equivalent to zero.
-         */
-        public boolean hasZeroSize() {
-            return segment != null && segment.getPrecision().eqZero(segment.getSize());
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean endPointsEq(final ConnectableSegment other) {
-            if (hasEnd() && other.hasEnd()) {
-                return segment.getEndPoint()
-                        .eq(other.segment.getEndPoint(), segment.getPrecision());
-            }
-
-            return false;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean canConnectTo(final ConnectableSegment next) {
-            final Vector2D end = segment.getEndPoint();
-            final Vector2D nextStart = next.start;
-
-            return end != null && nextStart != null &&
-                    end.eq(nextStart, segment.getPrecision());
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public double getRelativeAngle(final ConnectableSegment next) {
-            return segment.getLine().angle(next.getSegment().getLine());
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public ConnectableSegment getConnectionSearchKey() {
-            return new ConnectableSegment(segment.getEndPoint());
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean shouldContinueConnectionSearch(final ConnectableSegment candidate, final boolean ascending) {
-
-            if (candidate.hasStart()) {
-                final double candidateX = candidate.getSegment().getStartPoint().getX();
-                final double thisX = segment.getEndPoint().getX();
-                final int cmp = segment.getPrecision().compare(candidateX, thisX);
-
-                return ascending ? cmp <= 0 : cmp >= 0;
-            }
-
-            return true;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public int compareTo(ConnectableSegment other) {
-            // sort by coordinates
-            int cmp = Vector2D.COORDINATE_ASCENDING_ORDER.compare(start, other.start);
-            if (cmp == 0) {
-                // sort entries without segments before ones with segments
-                final boolean thisHasSegment = segment != null;
-                final boolean otherHasSegment = other.segment != null;
-
-                cmp = Boolean.compare(thisHasSegment, otherHasSegment);
-
-                if (cmp == 0 && thisHasSegment) {
-                    // place point-like segments before ones with non-zero length
-                    cmp = Boolean.compare(this.hasZeroSize(), other.hasZeroSize());
-
-                    if (cmp == 0) {
-                        // sort by line angle
-                        final double aAngle = PlaneAngleRadians.normalizeBetweenMinusPiAndPi(
-                                this.getSegment().getLine().getAngle());
-                        final double bAngle = PlaneAngleRadians.normalizeBetweenMinusPiAndPi(
-                                other.getSegment().getLine().getAngle());
-
-                        cmp = Double.compare(aAngle, bAngle);
-                    }
-                }
-            }
-            return cmp;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public int hashCode() {
-            return Objects.hash(start, segment);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean equals(final Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null || !this.getClass().equals(obj.getClass())) {
-                return false;
-            }
-
-            final ConnectableSegment other = (ConnectableSegment) obj;
-            return Objects.equals(this.start, other.start) &&
-                    Objects.equals(this.segment, other.segment);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        protected ConnectableSegment getSelf() {
-            return this;
-        }
-    }
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AbstractSubLine.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AbstractSubLine.java
deleted file mode 100644
index 94ea6ad..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AbstractSubLine.java
+++ /dev/null
@@ -1,127 +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.euclidean.twod;
-
-import java.util.function.BiFunction;
-
-import org.apache.commons.geometry.core.partitioning.AbstractEmbeddingSubHyperplane;
-import org.apache.commons.geometry.core.partitioning.Hyperplane;
-import org.apache.commons.geometry.core.partitioning.HyperplaneBoundedRegion;
-import org.apache.commons.geometry.core.partitioning.Split;
-import org.apache.commons.geometry.core.partitioning.SplitLocation;
-import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
-import org.apache.commons.geometry.euclidean.oned.OrientedPoint;
-import org.apache.commons.geometry.euclidean.oned.Vector1D;
-import org.apache.commons.geometry.euclidean.twod.SubLine.SubLineBuilder;
-
-/** Internal base class for subline implementations.
- */
-abstract class AbstractSubLine extends AbstractEmbeddingSubHyperplane<Vector2D, Vector1D, Line> {
-    /** The line defining this instance. */
-    private final Line line;
-
-    /** Construct a new instance based on the given line.
-     * @param line line forming the base of the instance
-     */
-    AbstractSubLine(final Line line) {
-        this.line = line;
-    }
-
-    /** Get the line that this segment lies on. This method is an alias
-     * for {@link #getHyperplane()}.
-     * @return the line that this segment lies on
-     * @see #getHyperplane()
-     */
-    public Line getLine() {
-        return getHyperplane();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Line getHyperplane() {
-        return line;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public SubLineBuilder builder() {
-        return new SubLineBuilder(line);
-    }
-
-    /** Return the object used to perform floating point comparisons, which is the
-     * same object used by the underlying {@link Line}).
-     * @return precision object used to perform floating point comparisons.
-     */
-    public DoublePrecisionContext getPrecision() {
-        return line.getPrecision();
-    }
-
-    /** 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 factory function used to create new subhyperplane instances
-     * @param <T> Subline implementation type
-     * @return the result of the split operation
-     */
-    protected <T extends AbstractSubLine> Split<T> splitInternal(final Hyperplane<Vector2D> splitter,
-            final T thisInstance, final BiFunction<Line, HyperplaneBoundedRegion<Vector1D>, T> factory) {
-
-        final Line thisLine = getLine();
-        final Line splitterLine = (Line) splitter;
-        final DoublePrecisionContext precision = getPrecision();
-
-        final Vector2D intersection = splitterLine.intersection(thisLine);
-        if (intersection == null) {
-            // the lines are parallel or coincident; check which side of
-            // the splitter we lie on
-            final double offset = splitterLine.offset(thisLine);
-            final int comp = precision.compare(offset, 0.0);
-
-            if (comp < 0) {
-                return new Split<>(thisInstance, null);
-            } else if (comp > 0) {
-                return new Split<>(null, thisInstance);
-            } else {
-                return new Split<>(null, null);
-            }
-        } else {
-            // the lines intersect; split the subregion
-            final Vector1D splitPt = thisLine.toSubspace(intersection);
-            final boolean positiveFacing = thisLine.angle(splitterLine) > 0.0;
-
-            final OrientedPoint subspaceSplitter = OrientedPoint.fromPointAndDirection(splitPt,
-                    positiveFacing, getPrecision());
-
-            final Split<? extends HyperplaneBoundedRegion<Vector1D>> split =
-                    thisInstance.getSubspaceRegion().split(subspaceSplitter);
-            final SplitLocation subspaceSplitLoc = split.getLocation();
-
-            if (SplitLocation.MINUS == subspaceSplitLoc) {
-                return new Split<>(thisInstance, null);
-            } else if (SplitLocation.PLUS == subspaceSplitLoc) {
-                return new Split<>(null, thisInstance);
-            }
-
-            final T minus = (split.getMinus() != null) ? factory.apply(thisLine, split.getMinus()) : null;
-            final T plus = (split.getPlus() != null) ? factory.apply(thisLine, split.getPlus()) : null;
-
-            return new Split<>(minus, plus);
-        }
-    }
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySource2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySource2D.java
index ec0fe30..45cfe7e 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySource2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySource2D.java
@@ -18,13 +18,13 @@ package org.apache.commons.geometry.euclidean.twod;
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 import org.apache.commons.geometry.core.partitioning.BoundarySource;
 
-/** Extension of the {@link BoundarySource} interface for Euclidean 2D
- * space.
+/** Extension of the {@link BoundarySource} interface for Euclidean 2D space.
  */
-public interface BoundarySource2D extends BoundarySource<Segment> {
+public interface BoundarySource2D extends BoundarySource<LineConvexSubset>, Linecastable2D {
 
     /** Return a BSP tree constructed from the boundaries contained in this
      * instance. The default implementation creates a new, empty tree
@@ -38,20 +38,32 @@ public interface BoundarySource2D extends BoundarySource<Segment> {
         return tree;
     }
 
-    /** Return a {@link BoundarySource2D} instance containing the given segments.
-     * @param boundaries segments to include in the boundary source
+    /** {@inheritDoc} */
+    @Override
+    default List<LinecastPoint2D> linecast(final LineConvexSubset subset) {
+        return new BoundarySourceLinecaster2D(this).linecast(subset);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    default LinecastPoint2D linecastFirst(final LineConvexSubset subset) {
+        return new BoundarySourceLinecaster2D(this).linecastFirst(subset);
+    }
+
+    /** Return a {@link BoundarySource2D} instance containing the given boundaries.
+     * @param boundaries line subsets to include in the boundary source
      * @return a boundary source containing the given boundaries
      */
-    static BoundarySource2D from(final Segment... boundaries) {
+    static BoundarySource2D from(final LineConvexSubset... boundaries) {
         return from(Arrays.asList(boundaries));
     }
 
-    /** Return a {@link BoundarySource2D} instance containing the given segments. The given
-     * collection is used directly as the source of the segments; no copy is made.
-     * @param boundaries segments to include in the boundary source
+    /** Return a {@link BoundarySource2D} instance containing the given boundaries. The given
+     * collection is used directly as the source of the line subsets; no copy is made.
+     * @param boundaries line subsets to include in the boundary source
      * @return a boundary source containing the given boundaries
      */
-    static BoundarySource2D from(final Collection<Segment> boundaries) {
+    static BoundarySource2D from(final Collection<LineConvexSubset> boundaries) {
         return boundaries::stream;
     }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySourceLinecaster2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySourceLinecaster2D.java
index 612c6a1..69a265c 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySourceLinecaster2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/BoundarySourceLinecaster2D.java
@@ -24,7 +24,7 @@ import java.util.stream.Stream;
 
 /** Class that performs linecast operations against arbitrary {@link BoundarySource2D}
  * instances. This class performs a brute-force computation of the intersections of the
- * line or line segment against all boundaries. Some data structures may support more
+ * line or line subset against all boundaries. Some data structures may support more
  * efficient algorithms and should therefore prefer those instead.
  */
 final class BoundarySourceLinecaster2D implements Linecastable2D {
@@ -41,8 +41,8 @@ final class BoundarySourceLinecaster2D implements Linecastable2D {
 
     /** {@inheritDoc} */
     @Override
-    public List<LinecastPoint2D> linecast(final Segment segment) {
-        final List<LinecastPoint2D> results = getIntersectionStream(segment)
+    public List<LinecastPoint2D> linecast(final LineConvexSubset subset) {
+        final List<LinecastPoint2D> results = getIntersectionStream(subset)
                 .collect(Collectors.toCollection(ArrayList::new));
 
         LinecastPoint2D.sortAndFilter(results);
@@ -52,37 +52,37 @@ final class BoundarySourceLinecaster2D implements Linecastable2D {
 
     /** {@inheritDoc} */
     @Override
-    public LinecastPoint2D linecastFirst(final Segment segment) {
-        return getIntersectionStream(segment)
+    public LinecastPoint2D linecastFirst(final LineConvexSubset subset) {
+        return getIntersectionStream(subset)
                 .min(LinecastPoint2D.ABSCISSA_ORDER)
                 .orElse(null);
     }
 
     /** Return a stream containing intersections between the boundary source and the
-     * given line segment.
-     * @param segment segment to intersect
+     * given line subset.
+     * @param subset line subset to intersect
      * @return a stream containing linecast intersections
      */
-    private Stream<LinecastPoint2D> getIntersectionStream(final Segment segment) {
+    private Stream<LinecastPoint2D> getIntersectionStream(final LineConvexSubset subset) {
         return boundarySrc.boundaryStream()
-                .map(boundary -> computeIntersection(boundary, segment))
+                .map(boundary -> computeIntersection(boundary, subset))
                 .filter(Objects::nonNull);
     }
 
-    /** Compute the intersection between a boundary segment and linecast intersecting segment. Null is
+    /** Compute the intersection between a boundary line subset and linecast intersecting line subset. Null is
      * returned if no intersection is discovered.
      * @param boundary boundary from the boundary source
-     * @param segment linecast segment to intersect with
+     * @param subset line subset to intersect with
      * @return the linecast intersection between the two arguments or null if there is no such
      *      intersection
      */
-    private LinecastPoint2D computeIntersection(final Segment boundary, final Segment segment) {
-        final Vector2D intersectionPt = boundary.intersection(segment);
+    private LinecastPoint2D computeIntersection(final LineConvexSubset boundary, final LineConvexSubset subset) {
+        final Vector2D intersectionPt = boundary.intersection(subset);
 
         if (intersectionPt != null) {
             final Vector2D normal = boundary.getLine().getOffsetDirection();
 
-            return new LinecastPoint2D(intersectionPt, normal, segment.getLine());
+            return new LinecastPoint2D(intersectionPt, normal, subset.getLine());
         }
 
         return null; // no intersection
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ConvexArea.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ConvexArea.java
index 9db3fd6..67d0566 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ConvexArea.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ConvexArea.java
@@ -26,16 +26,18 @@ import java.util.stream.Stream;
 
 import org.apache.commons.geometry.core.Transform;
 import org.apache.commons.geometry.core.partitioning.AbstractConvexHyperplaneBoundedRegion;
-import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
 import org.apache.commons.geometry.core.partitioning.Split;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.twod.path.InteriorAngleLinePathConnector;
+import org.apache.commons.geometry.euclidean.twod.path.LinePath;
 
 /** Class representing a finite or infinite convex area in Euclidean 2D space.
- * The boundaries of this area, if any, are composed of convex sublines.
+ * The boundaries of this area, if any, are composed of convex line subsets.
  */
-public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D, Segment>
-    implements BoundarySource2D, Linecastable2D {
+public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D, LineConvexSubset>
+    implements BoundarySource2D {
 
     /** Instance representing the full 2D plane. */
     private static final ConvexArea FULL = new ConvexArea(Collections.emptyList());
@@ -44,32 +46,32 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
      * represents the boundary of a convex area. No validation is performed.
      * @param boundaries the boundaries of the convex area
      */
-    protected ConvexArea(final List<Segment> boundaries) {
+    protected ConvexArea(final List<LineConvexSubset> boundaries) {
         super(boundaries);
     }
 
     /** {@inheritDoc} */
     @Override
-    public Stream<Segment> boundaryStream() {
+    public Stream<LineConvexSubset> boundaryStream() {
         return getBoundaries().stream();
     }
 
-    /** Get the connected line segment paths comprising the boundary of the area. The
-     * segments are oriented so that their minus sides point toward the interior of the
+    /** Get the connected line subset paths comprising the boundary of the area. The
+     * line subsets are oriented so that their minus sides point toward the interior of the
      * region. The size of the returned list is
      * <ul>
      *      <li><strong>0</strong> if the convex area is full,</li>
      *      <li><strong>1</strong> if at least one boundary is present and
-     *          a single path can connect all segments (this will be the case
+     *          a single path can connect all line subsets (this will be the case
      *          for most instances), and</li>
      *      <li><strong>2</strong> if only two boundaries exist and they are
      *          parallel to each other (in which case they cannot be connected
      *          as a single path).</li>
      * </ul>
-     * @return the line segment paths comprising the boundary of the area.
+     * @return the line subset paths comprising the boundary of the area.
      */
-    public List<Polyline> getBoundaryPaths() {
-        return InteriorAngleSegmentConnector.connectMinimized(getBoundaries());
+    public List<LinePath> getBoundaryPaths() {
+        return InteriorAngleLinePathConnector.connectMinimized(getBoundaries());
     }
 
     /** Get the vertices defining the area. The vertices lie at the intersections of the
@@ -80,12 +82,12 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
      * @return the vertices defining the area
      */
     public List<Vector2D> getVertices() {
-        final List<Polyline> paths = getBoundaryPaths();
+        final List<LinePath> paths = getBoundaryPaths();
 
         // we will only have vertices if we have a single path; otherwise, we have a full
-        // area or two non-intersecting infinite segments
+        // area or two non-intersecting infinite line subsets
         if (paths.size() == 1) {
-            final Polyline path = paths.get(0);
+            final LinePath path = paths.get(0);
             final List<Vector2D> vertices = path.getVertexSequence();
 
             if (path.isClosed()) {
@@ -103,13 +105,13 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
      * @return a new instance transformed by the argument
      */
     public ConvexArea transform(final Transform<Vector2D> transform) {
-        return transformInternal(transform, this, Segment.class, ConvexArea::new);
+        return transformInternal(transform, this, LineConvexSubset.class, ConvexArea::new);
     }
 
     /** {@inheritDoc} */
     @Override
-    public Segment trim(final ConvexSubHyperplane<Vector2D> convexSubHyperplane) {
-        return (Segment) super.trim(convexSubHyperplane);
+    public LineConvexSubset trim(final HyperplaneConvexSubset<Vector2D> convexSubset) {
+        return (LineConvexSubset) super.trim(convexSubset);
     }
 
     /** {@inheritDoc} */
@@ -121,12 +123,12 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
 
         double quadrilateralAreaSum = 0.0;
 
-        for (final Segment segment : getBoundaries()) {
-            if (segment.isInfinite()) {
+        for (final LineConvexSubset boundary : getBoundaries()) {
+            if (boundary.isInfinite()) {
                 return Double.POSITIVE_INFINITY;
             }
 
-            quadrilateralAreaSum += segment.getStartPoint().signedArea(segment.getEndPoint());
+            quadrilateralAreaSum += boundary.getStartPoint().signedArea(boundary.getEndPoint());
         }
 
         return 0.5 * quadrilateralAreaSum;
@@ -135,7 +137,7 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
     /** {@inheritDoc} */
     @Override
     public Vector2D getBarycenter() {
-        final List<Segment> boundaries = getBoundaries();
+        final List<LineConvexSubset> boundaries = getBoundaries();
 
         double quadrilateralAreaSum = 0.0;
         double scaledSumX = 0.0;
@@ -145,7 +147,7 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
         Vector2D startPoint;
         Vector2D endPoint;
 
-        for (final Segment seg : boundaries) {
+        for (final LineConvexSubset seg : boundaries) {
             if (seg.isInfinite()) {
                 // infinite => no barycenter
                 return null;
@@ -172,7 +174,7 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
     /** {@inheritDoc} */
     @Override
     public Split<ConvexArea> split(final Hyperplane<Vector2D> splitter) {
-        return splitInternal(splitter, this, Segment.class, ConvexArea::new);
+        return splitInternal(splitter, this, LineConvexSubset.class, ConvexArea::new);
     }
 
     /** Return a BSP tree representing the same region as this instance.
@@ -182,18 +184,6 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
         return RegionBSPTree2D.from(getBoundaries(), true);
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public List<LinecastPoint2D> linecast(final Segment segment) {
-        return new BoundarySourceLinecaster2D(this).linecast(segment);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public LinecastPoint2D linecastFirst(final Segment segment) {
-        return new BoundarySourceLinecaster2D(this).linecastFirst(segment);
-    }
-
     /** Return an instance representing the full 2D area.
      * @return an instance representing the full 2D area.
      */
@@ -255,14 +245,14 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
             }
 
             if (prev != null && !cur.eq(prev, precision)) {
-                lines.add(Line.fromPoints(prev, cur, precision));
+                lines.add(Lines.fromPoints(prev, cur, precision));
             }
 
             prev = cur;
         }
 
         if (close && cur != null && !cur.eq(first, precision)) {
-            lines.add(Line.fromPoints(cur, first, precision));
+            lines.add(Lines.fromPoints(cur, first, precision));
         }
 
         if (!vertices.isEmpty() && lines.isEmpty()) {
@@ -272,15 +262,15 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
         return fromBounds(lines);
     }
 
-    /** Construct a convex area from a line segment path. The area represents the intersection of all of the negative
-     * half-spaces of the lines in the path. The boundaries of the returned area may therefore not match the line
-     * segments in the path.
+    /** Construct a convex area from a line subset path. The area represents the intersection of all of the
+     * negative half-spaces of the lines in the path. The boundaries of the returned area may therefore not
+     * match the line subsets in the path.
      * @param path path to construct the area from
-     * @return a convex area constructed from the lines in the given path
+     * @return a convex area constructed from the line subsets in the given path
      */
-    public static ConvexArea fromPath(final Polyline path) {
+    public static ConvexArea fromPath(final LinePath path) {
         final List<Line> lines = path.boundaryStream()
-                .map(Segment::getLine)
+                .map(LineConvexSubset::getLine)
                 .collect(Collectors.toList());
 
         return fromBounds(lines);
@@ -313,7 +303,8 @@ public class ConvexArea extends AbstractConvexHyperplaneBoundedRegion<Vector2D,
      *      meaning that there is no region that is on the minus side of all of the bounding lines.
      */
     public static ConvexArea fromBounds(final Iterable<Line> bounds) {
-        final List<Segment> segments = new ConvexRegionBoundaryBuilder<>(Segment.class).build(bounds);
-        return segments.isEmpty() ? full() : new ConvexArea(segments);
+        final List<LineConvexSubset> subsets =
+                new ConvexRegionBoundaryBuilder<>(LineConvexSubset.class).build(bounds);
+        return subsets.isEmpty() ? full() : new ConvexArea(subsets);
     }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubset.java
new file mode 100644
index 0000000..9bfb2f8
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubset.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import java.util.ArrayList;
+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.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.core.partitioning.SplitLocation;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.oned.Interval;
+import org.apache.commons.geometry.euclidean.oned.OrientedPoint;
+import org.apache.commons.geometry.euclidean.oned.OrientedPoints;
+import org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D;
+import org.apache.commons.geometry.euclidean.twod.Line.SubspaceTransform;
+
+/** Class representing an arbitrary subset of a line using a {@link RegionBSPTree1D}.
+ * This class can represent convex, non-convex, finite, infinite, and empty regions.
+ *
+ * <p>This class is mutable and <em>not</em> thread safe.</p>
+ */
+public final class EmbeddedTreeLineSubset extends LineSubset {
+    /** The 1D region representing the area on the line. */
+    private final RegionBSPTree1D region;
+
+    /** Construct a new, empty subset for the given line.
+     * @param line line defining the subset
+     */
+    public EmbeddedTreeLineSubset(final Line line) {
+        this(line, false);
+    }
+
+    /** Construct a new subset for the given line. If {@code full}
+     * is true, then the subset will cover the entire line; otherwise,
+     * it will be empty.
+     * @param line line defining the subset
+     * @param full if true, the subset will cover the entire space;
+     *      otherwise it will be empty
+     */
+    public EmbeddedTreeLineSubset(final Line line, boolean full) {
+        this(line, new RegionBSPTree1D(full));
+    }
+
+    /** Construct a new instance from its defining line and subspace region. The give
+     * BSP tree is used directly by this instance; it is not copied.
+     * @param line line defining the subset
+     * @param region subspace region for the instance
+     */
+    public EmbeddedTreeLineSubset(final Line line, final RegionBSPTree1D region) {
+        super(line);
+
+        this.region = region;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public EmbeddedTreeLineSubset transform(final Transform<Vector2D> transform) {
+        final SubspaceTransform st = getLine().subspaceTransform(transform);
+
+        final RegionBSPTree1D tRegion = RegionBSPTree1D.empty();
+        tRegion.copy(region);
+        tRegion.transform(st.getTransform());
+
+        return new EmbeddedTreeLineSubset(st.getLine(), tRegion);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<LineConvexSubset> toConvex() {
+        final List<Interval> intervals = region.toIntervals();
+
+        final Line line = getLine();
+        final List<LineConvexSubset> convexSubsets = new ArrayList<>(intervals.size());
+
+        for (final Interval interval : intervals) {
+            convexSubsets.add(Lines.subsetFromInterval(line, interval));
+        }
+
+        return convexSubsets;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RegionBSPTree1D getSubspaceRegion() {
+        return region;
+    }
+
+    /** {@inheritDoc}
+     *
+     * <p>In all cases, the current instance is not modified. However, In order to avoid
+     * unnecessary copying, this method will use the current instance as the split value when
+     * the instance lies entirely on the plus or minus side of the splitter. For example, if
+     * this instance lies entirely on the minus side of the splitter, the subplane
+     * returned by {@link Split#getMinus()} will be this instance. Similarly, {@link Split#getPlus()}
+     * will return the current instance if it lies entirely on the plus side. Callers need to make
+     * special note of this, since this class is mutable.</p>
+     */
+    @Override
+    public Split<EmbeddedTreeLineSubset> split(final Hyperplane<Vector2D> splitter) {
+        final Line thisLine = getLine();
+        final Line splitterLine = (Line) splitter;
+        final DoublePrecisionContext precision = getPrecision();
+
+        final Vector2D intersection = splitterLine.intersection(thisLine);
+        if (intersection == null) {
+            return getNonIntersectingSplitResult(splitterLine, this);
+        }
+
+        final double abscissa = thisLine.abscissa(intersection);
+        final OrientedPoint subspaceSplitter = OrientedPoints.fromLocationAndDirection(
+                abscissa,
+                splitterPlusIsPositiveFacing(splitterLine),
+                precision);
+
+        final Split<RegionBSPTree1D> subspaceSplit = region.split(subspaceSplitter);
+        final SplitLocation subspaceSplitLoc = subspaceSplit.getLocation();
+
+        if (SplitLocation.MINUS == subspaceSplitLoc) {
+            return new Split<>(this, null);
+        } else if (SplitLocation.PLUS == subspaceSplitLoc) {
+            return new Split<>(null, this);
+        }
+
+        final EmbeddedTreeLineSubset minus = (subspaceSplit.getMinus() != null) ?
+                new EmbeddedTreeLineSubset(thisLine, subspaceSplit.getMinus()) :
+                null;
+        final EmbeddedTreeLineSubset plus = (subspaceSplit.getPlus() != null) ?
+                new EmbeddedTreeLineSubset(thisLine, subspaceSplit.getPlus()) :
+                null;
+
+        return new Split<>(minus, plus);
+    }
+
+    /** Add a line subset to this instance.
+     * @param subset the line subset to add
+     * @throws IllegalArgumentException if the given line subset is not from
+     *      a line equivalent to this instance
+     */
+    public void add(final LineConvexSubset subset) {
+        validateLine(subset.getLine());
+
+        region.add(subset.getInterval());
+    }
+
+    /** Add the region represented by the given line subset to this instance.
+     * The argument is not modified.
+     * @param subset line subset to add
+     * @throws IllegalArgumentException if the given line subset is not from
+     *      a line equivalent to this instance
+     */
+    public void add(final EmbeddedTreeLineSubset subset) {
+        validateLine(subset.getLine());
+
+        region.union(subset.getSubspaceRegion());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final Line line = getLine();
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append(this.getClass().getSimpleName())
+            .append('[')
+            .append("lineOrigin= ")
+            .append(line.getOrigin())
+            .append(", lineDirection= ")
+            .append(line.getDirection())
+            .append(", region= ")
+            .append(region)
+            .append(']');
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    RegionLocation classifyAbscissa(final double abscissa) {
+        return region.classify(abscissa);
+    }
+
+    /** Validate that the given line is equivalent to the line
+     * defining this instance.
+     * @param inputLine the line to validate
+     * @throws IllegalArgumentException if the given line is not equivalent
+     *      to the line for this instance
+     */
+    private void validateLine(final Line inputLine) {
+        final Line line = getLine();
+
+        if (!line.eq(inputLine, line.getPrecision())) {
+            throw new IllegalArgumentException("Argument is not on the same " +
+                    "line. Expected " + line + " but was " +
+                    inputLine);
+        }
+    }
+
+    /** {@link HyperplaneSubset.Builder} implementation for line subsets.
+     */
+    public static final class Builder implements HyperplaneSubset.Builder<Vector2D> {
+
+        /** Line subset instance created by this builder. */
+        private final EmbeddedTreeLineSubset lineSubset;
+
+        /** Construct a new instance for building a line subset for the given line.
+         * @param line the underlying line for the subset
+         */
+        public Builder(final Line line) {
+            this.lineSubset = new EmbeddedTreeLineSubset(line);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void add(final HyperplaneSubset<Vector2D> sub) {
+            addInternal(sub);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void add(final HyperplaneConvexSubset<Vector2D> sub) {
+            addInternal(sub);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public EmbeddedTreeLineSubset build() {
+            return lineSubset;
+        }
+
+        /** Internal method for adding hyperplane subsets to this builder.
+         * @param sub the hyperplane subset to add; either convex or non-convex
+         */
+        private void addInternal(final HyperplaneSubset<Vector2D> sub) {
+            if (sub instanceof LineConvexSubset) {
+                lineSubset.add((LineConvexSubset) sub);
+            } else if (sub instanceof EmbeddedTreeLineSubset) {
+                lineSubset.add((EmbeddedTreeLineSubset) sub);
+            } else {
+                throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
+            }
+        }
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java
index 9ca8807..b7a0d93 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Line.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import java.text.MessageFormat;
 import java.util.Objects;
 
 import org.apache.commons.geometry.core.Transform;
@@ -24,7 +25,6 @@ import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
-import org.apache.commons.geometry.euclidean.oned.Interval;
 import org.apache.commons.geometry.euclidean.oned.Vector1D;
 import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.apache.commons.numbers.arrays.LinearCombination;
@@ -54,9 +54,14 @@ import org.apache.commons.numbers.arrays.LinearCombination;
  * the set of points at zero offset, the left half plane is the set of
  * points with negative offsets and the right half plane is the set of
  * points with positive offsets.</p>
+ * @see Lines
  */
 public final class Line extends AbstractHyperplane<Vector2D>
     implements EmbeddingHyperplane<Vector2D, Vector1D> {
+
+    /** Format string for creating line string representations. */
+    static final String TO_STRING_FORMAT = "{0}[origin= {1}, direction= {2}]";
+
     /** The direction of the line as a normalized vector. */
     private final Vector2D direction;
 
@@ -68,7 +73,7 @@ public final class Line extends AbstractHyperplane<Vector2D>
      * @param originOffset The signed distance between the line and the origin.
      * @param precision Precision context used to compare floating point numbers.
      */
-    private Line(final Vector2D direction, final double originOffset, final DoublePrecisionContext precision) {
+    Line(final Vector2D direction, final double originOffset, final DoublePrecisionContext precision) {
         super(precision);
 
         this.direction = direction;
@@ -132,7 +137,7 @@ public final class Line extends AbstractHyperplane<Vector2D>
         final Vector2D tOrigin = transform.apply(origin);
         final Vector2D tOriginPlusDir = transform.apply(origin.add(getDirection()));
 
-        return fromPoints(tOrigin, tOriginPlusDir, getPrecision());
+        return Lines.fromPoints(tOrigin, tOriginPlusDir, getPrecision());
     }
 
     /** Get an object containing the current line transformed by the argument along with a
@@ -163,7 +168,7 @@ public final class Line extends AbstractHyperplane<Vector2D>
         final Vector2D p1 = transform.apply(origin);
         final Vector2D p2 = transform.apply(origin.add(direction));
 
-        final Line tLine = Line.fromPoints(p1, p2, getPrecision());
+        final Line tLine = Lines.fromPoints(p1, p2, getPrecision());
 
         final Vector1D tSubspaceOrigin = tLine.toSubspace(p1);
         final Vector1D tSubspaceDirection = tSubspaceOrigin.vectorTo(tLine.toSubspace(p2));
@@ -178,62 +183,81 @@ public final class Line extends AbstractHyperplane<Vector2D>
 
     /** {@inheritDoc} */
     @Override
-    public Segment span() {
-        return segment(Interval.full());
-    }
-
-    /** Create a new line segment from the given interval.
-     * @param interval interval representing the 1D region for the line segment
-     * @return a new line segment on this line
-     */
-    public Segment segment(final Interval interval) {
-        return Segment.fromInterval(this, interval);
+    public LineConvexSubset span() {
+        return Lines.span(this);
     }
 
-    /** Create a new line segment from the given interval.
+    /** Create a new line segment from the given 1D interval. The returned line
+     * segment consists of all points between the two locations, regardless of the order the
+     * arguments are given.
      * @param a first 1D location for the interval
      * @param b second 1D location for the interval
      * @return a new line segment on this line
+     * @throws IllegalArgumentException if either of the locations is NaN or infinite
+     * @see Lines#segmentFromLocations(Line, double, double)
      */
     public Segment segment(final double a, final double b) {
-        return Segment.fromInterval(this, a, b);
+        return Lines.segmentFromLocations(this, a, b);
     }
 
-    /** Create a new line segment between the projections of the two
-     * given points onto this line.
+    /** Create a new line segment from two points. The returned segment represents all points on this line
+     * between the projected locations of {@code a} and {@code b}. The points may be given in any order.
      * @param a first point
      * @param b second point
      * @return a new line segment on this line
+     * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values
+     * @see Lines#segmentFromPoints(Line, Vector2D, Vector2D)
      */
     public Segment segment(final Vector2D a, final Vector2D b) {
-        return Segment.fromInterval(this, toSubspace(a), toSubspace(b));
+        return Lines.segmentFromPoints(this, a, b);
     }
 
-    /** Create a new line segment that starts at infinity and continues along
-     * the line up to the projection of the given point.
-     * @param pt point defining the end point of the line segment; the end point
+    /** Create a new convex line subset that starts at infinity and continues along
+     * the line up to the projection of the given end point.
+     * @param endPoint point defining the end point of the line subset; the end point
      *      is equal to the projection of this point onto the line
-     * @return a new, half-open line segment
+     * @return a new, half-open line subset that ends at the given point
+     * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
+     * @see Lines#reverseRayFromPoint(Line, Vector2D)
      */
-    public Segment segmentTo(final Vector2D pt) {
-        return segment(Double.NEGATIVE_INFINITY, toSubspace(pt).getX());
+    public ReverseRay reverseRayTo(final Vector2D endPoint) {
+        return Lines.reverseRayFromPoint(this, endPoint);
     }
 
-    /** Create a new line segment that starts at the projection of the given point
-     * and continues in the direction of the line to infinity, similar to a ray.
-     * @param pt point defining the start point of the line segment; the start point
+    /** Create a new convex line subset that starts at infinity and continues along
+     * the line up to the given 1D location.
+     * @param endLocation the 1D location of the end of the half-line
+     * @return a new, half-open line subset that ends at the given 1D location
+     * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
+     * @see Lines#reverseRayFromLocation(Line, double)
+     */
+    public ReverseRay reverseRayTo(final double endLocation) {
+        return Lines.reverseRayFromLocation(this, endLocation);
+    }
+
+    /** Create a new ray instance that starts at the projection of the given point
+     * and continues in the direction of the line to infinity.
+     * @param startPoint point defining the start point of the ray; the start point
      *      is equal to the projection of this point onto the line
-     * @return a new, half-open line segment
+     * @return a ray starting at the projected point and extending along this line
+     *      to infinity
+     * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
+     * @see Lines#rayFromPoint(Line, Vector2D)
      */
-    public Segment segmentFrom(final Vector2D pt) {
-        return segment(toSubspace(pt).getX(), Double.POSITIVE_INFINITY);
+    public Ray rayFrom(final Vector2D startPoint) {
+        return Lines.rayFromPoint(this, startPoint);
     }
 
-    /** Create a new, empty subline based on this line.
-     * @return a new, empty subline based on this line
+    /** Create a new ray instance that starts at the given 1D location and continues in
+     * the direction of the line to infinity.
+     * @param startLocation 1D location defining the start point of the ray
+     * @return a ray starting at the given 1D location and extending along this line
+     *      to infinity
+     * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
+     * @see Lines#rayFromLocation(Line, double)
      */
-    public SubLine subline() {
-        return new SubLine(this);
+    public Ray rayFrom(final double startLocation) {
+        return Lines.rayFromLocation(this, startLocation);
     }
 
     /** Get the abscissa of the given point on the line. The abscissa represents
@@ -478,63 +502,10 @@ public final class Line extends AbstractHyperplane<Vector2D>
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append(getClass().getSimpleName())
-            .append("[origin= ")
-            .append(getOrigin())
-            .append(", direction= ")
-            .append(direction)
-            .append(']');
-
-        return sb.toString();
-    }
-
-    /** Create a line from two points lying on the line. The line points in the direction
-     * from {@code p1} to {@code p2}.
-     * @param p1 first point
-     * @param p2 second point
-     * @param precision precision context used to compare floating point values
-     * @return new line containing {@code p1} and {@code p2} and pointing in the direction
-     *      from {@code p1} to {@code p2}
-     * @throws IllegalArgumentException If the vector between {@code p1} and {@code p2} has zero length,
-     *      as evaluated by the given precision context
-     */
-    public static Line fromPoints(final Vector2D p1, final Vector2D p2, final DoublePrecisionContext precision) {
-        return fromPointAndDirection(p1, p1.vectorTo(p2), precision);
-    }
-
-    /** Create a line from a point and direction.
-     * @param pt point belonging to the line
-     * @param dir the direction of the line
-     * @param precision precision context used to compare floating point values
-     * @return new line containing {@code pt} and pointing in direction {@code dir}
-     * @throws IllegalArgumentException If {@code dir} has zero length, as evaluated by the
-     *      given precision context
-     */
-    public static Line fromPointAndDirection(final Vector2D pt, final Vector2D dir,
-            final DoublePrecisionContext precision) {
-        if (dir.isZero(precision)) {
-            throw new IllegalArgumentException("Line direction cannot be zero");
-        }
-
-        final Vector2D normalizedDir = dir.normalize();
-        final double originOffset = normalizedDir.signedArea(pt);
-
-        return new Line(normalizedDir, originOffset, precision);
-    }
-
-    /** Create a line from a point lying on the line and an angle relative to the abscissa (x) axis. Note that the
-     * line does not need to intersect the x-axis; the given angle is simply relative to it.
-     * @param pt point belonging to the line
-     * @param angle angle of the line with respect to abscissa (x) axis, in radians
-     * @param precision precision context used to compare floating point values
-     * @return new line containing {@code pt} and forming the given angle with the
-     *      abscissa (x) axis.
-     */
-    public static Line fromPointAndAngle(final Vector2D pt, final double angle,
-            final DoublePrecisionContext precision) {
-        final Vector2D.Unit dir = Vector2D.Unit.from(Math.cos(angle), Math.sin(angle));
-        return fromPointAndDirection(pt, dir, precision);
+        return MessageFormat.format(TO_STRING_FORMAT,
+                getClass().getSimpleName(),
+                getOrigin(),
+                getDirection());
     }
 
     /** Class containing a transformed line instance along with a subspace (1D) transform. The subspace
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineConvexSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineConvexSubset.java
new file mode 100644
index 0000000..11a8db1
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineConvexSubset.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.Hyperplane;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.euclidean.oned.Interval;
+
+/** Class representing a convex subset of a line in 2D Euclidean space. Instances
+ * need not be finite, in which case the start or end point (or both) will be null.
+ * Line segments and rays are examples of convex line subsets.
+ * @see Lines
+ */
+public abstract class LineConvexSubset extends LineSubset implements HyperplaneConvexSubset<Vector2D> {
+
+    /** Construct a new instance for the given line.
+     * @param line line containing this line subset
+     */
+    LineConvexSubset(final Line line) {
+        super(line);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<LineConvexSubset> toConvex() {
+        return Collections.singletonList(this);
+    }
+
+    /** {@inheritDoc}
+     *
+     * <p>This method always returns {@code false}.</p>
+     */
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+
+    /** Get the start point for the subset.
+     * @return the start point for the subset, or null if no start point exists
+     */
+    public abstract Vector2D getStartPoint();
+
+    /** Get the 1D start location of the subset or {@link Double#NEGATIVE_INFINITY} if
+     * no start location exists.
+     * @return the 1D start location of the subset or {@link Double#NEGATIVE_INFINITY} if
+     *      no start location exists.
+     */
+    public abstract double getSubspaceStart();
+
+    /** Get the end point for the subset.
+     * @return the end point for the subset, or null if no end point exists.
+     */
+    public abstract Vector2D getEndPoint();
+
+    /** Get the 1D end location of the subset or {@link Double#POSITIVE_INFINITY} if
+     * no end location exists.
+     * @return the 1D end location of the subset or {@link Double#POSITIVE_INFINITY} if
+     *      no end location exists
+     */
+    public abstract double getSubspaceEnd();
+
+    /** {@inheritDoc} */
+    @Override
+    public Interval getSubspaceRegion() {
+        final double start = getSubspaceStart();
+        final double end = getSubspaceEnd();
+
+        return Interval.of(start, end, getPrecision());
+    }
+
+    /** Get the 1D interval for the region. This method is an alias for {@link #getSubspaceRegion()}.
+     * @return the 1D interval for the region.
+     */
+    public Interval getInterval() {
+        return getSubspaceRegion();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Split<LineConvexSubset> split(final Hyperplane<Vector2D> splitter) {
+        final Line thisLine = getLine();
+        final Line splitterLine = (Line) splitter;
+
+        final Vector2D intersection = splitterLine.intersection(thisLine);
+        if (intersection == null) {
+            return getNonIntersectingSplitResult(splitterLine, this);
+        }
+        return splitOnIntersection(splitterLine, intersection);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D closest(final Vector2D pt) {
+        final Line line = getLine();
+        final double abscissa = line.abscissa(pt);
+
+        return line.toSpace(closestAbscissa(abscissa));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public abstract LineConvexSubset transform(Transform<Vector2D> transform);
+
+    /** {@inheritDoc} */
+    @Override
+    public abstract LineConvexSubset reverse();
+
+    /** Get the closest value in the subspace region to the given abscissa.
+     * @param abscissa input abscissa
+     * @return the closest value in the subspace region to the given abscissa
+     */
+    abstract double closestAbscissa(double abscissa);
+
+    /** Split this instance using the given splitter line and intersection point.
+     * @param splitter splitter line
+     * @param intersection intersection point between the splitter line and the line
+     *      for this instance
+     * @return the result of splitting this instance with the given splitter line and intersection
+     *      point
+     */
+    abstract Split<LineConvexSubset> splitOnIntersection(Line splitter, Vector2D intersection);
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSpanningSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSpanningSubset.java
new file mode 100644
index 0000000..ab16ae3
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSpanningSubset.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import java.text.MessageFormat;
+
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.Split;
+
+/** Class representing the span of a line in 2D Euclidean space. This is the set of all points
+ * contained by the line.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ */
+final class LineSpanningSubset extends LineConvexSubset {
+
+    /** Construct a new instance for the given line.
+     * @param line line to construct the span for
+     */
+    LineSpanningSubset(final Line line) {
+        super(line);
+    }
+
+    /** {@inheritDoc}
+     *
+     * <p>This method always returns {@code true}.</p>
+     */
+    @Override
+    public boolean isFull() {
+        return true;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code true}.</p>
+    */
+    @Override
+    public boolean isInfinite() {
+        return true;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code false}.</p>
+    */
+    @Override
+    public boolean isFinite() {
+        return false;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSize() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code null}.</p>
+    */
+    @Override
+    public Vector2D getStartPoint() {
+        return null;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#NEGATIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSubspaceStart() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code null}.</p>
+    */
+    @Override
+    public Vector2D getEndPoint() {
+        return null;
+    }
+
+    /** {@inheritDoc}
+     *
+     * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+     */
+    @Override
+    public double getSubspaceEnd() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public LineSpanningSubset transform(final Transform<Vector2D> transform) {
+        return new LineSpanningSubset(getLine().transform(transform));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public LineSpanningSubset reverse() {
+        return new LineSpanningSubset(getLine().reverse());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final Line line = getLine();
+
+        return MessageFormat.format(Line.TO_STRING_FORMAT,
+                getClass().getSimpleName(),
+                line.getOrigin(),
+                line.getDirection());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    RegionLocation classifyAbscissa(double abscissa) {
+        return RegionLocation.INSIDE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    double closestAbscissa(double abscissa) {
+        return abscissa;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    Split<LineConvexSubset> splitOnIntersection(final Line splitter, final Vector2D intersection) {
+        final Line line = getLine();
+
+        final ReverseRay low = new ReverseRay(line, intersection);
+        final Ray high = new Ray(line, intersection);
+
+        return createSplitResult(splitter, low, high);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSubset.java
new file mode 100644
index 0000000..e8c28aa
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSubset.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.partitioning.AbstractRegionEmbeddingHyperplaneSubset;
+import org.apache.commons.geometry.core.partitioning.Split;
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.oned.Vector1D;
+
+/** Class representing a subset of points on a line in 2D Euclidean space. For example, line segments
+ * and rays are line subsets. Line subsets may be finite or infinite.
+ */
+public abstract class LineSubset extends AbstractRegionEmbeddingHyperplaneSubset<Vector2D, Vector1D, Line> {
+    /** The line containing this instance. */
+    private final Line line;
+
+    /** Construct a new instance based on the given line.
+     * @param line line forming the base of the instance
+     */
+    LineSubset(final Line line) {
+        this.line = line;
+    }
+
+    /** Get the line containing this subset. This method is an alias
+     * for {@link #getHyperplane()}.
+     * @return the line containing this subset
+     * @see #getHyperplane()
+     */
+    public Line getLine() {
+        return getHyperplane();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Line getHyperplane() {
+        return line;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public EmbeddedTreeLineSubset.Builder builder() {
+        return new EmbeddedTreeLineSubset.Builder(line);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RegionLocation classify(final Vector2D pt) {
+        if (line.contains(pt)) {
+            return classifyAbscissa(line.abscissa(pt));
+        }
+
+        return RegionLocation.OUTSIDE;
+    }
+
+    /** Get the unique intersection of this subset with the given line. Null is
+     * returned if no unique intersection point exists (ie, the lines are
+     * parallel or coincident) or the line does not intersect this instance.
+     * @param inputLine line to intersect with this line subset
+     * @return the unique intersection point between the line and this line subset
+     *      or null if no such point exists.
+     * @see Line#intersection(Line)
+     */
+    public Vector2D intersection(final Line inputLine) {
+        final Vector2D pt = line.intersection(inputLine);
+        return (pt != null && contains(pt)) ? pt : null;
+    }
+
+    /** Get the unique intersection of this instance with the given line subset. Null
+     * is returned if the lines containing the line subsets do not have a unique intersection
+     * point (ie, they are parallel or coincident) or the intersection point is unique
+     * but is not contained in both line subsets.
+     * @param subset line subset to intersect with
+     * @return the unique intersection point between this line subset and the argument or
+     *      null if no such point exists.
+     * @see Line#intersection(Line)
+     */
+    public Vector2D intersection(final LineSubset subset) {
+        final Vector2D pt = intersection(subset.getLine());
+        return (pt != null && subset.contains(pt)) ? pt : null;
+    }
+
+    /** Return the object used to perform floating point comparisons, which is the
+     * same object used by the underlying {@link Line}).
+     * @return precision object used to perform floating point comparisons.
+     */
+    public DoublePrecisionContext getPrecision() {
+        return line.getPrecision();
+    }
+
+    /** Classify the given line abscissa value with respect to the subspace region.
+     * @param abscissa the abscissa value to classify
+     * @return the region location of the line abscissa value
+     */
+    abstract RegionLocation classifyAbscissa(double abscissa);
+
+    /** Get a split result for cases where no intersection exists between the splitting line and the
+     * line underlying the given line subset. This occurs when the two lines are parallel or coincident.
+     * @param <T> Line subset type
+     * @param splitter splitting line
+     * @param subset line subset instance being split
+     * @return return result of the non-intersecting split operation
+     */
+    <T extends LineSubset> Split<T> getNonIntersectingSplitResult(final Line splitter, final T subset) {
+        // check which side of the splitter we lie on
+        final double offset = splitter.offset(subset.getLine());
+        final int comp = getPrecision().compare(offset, 0.0);
+
+        if (comp < 0) {
+            return new Split<>(subset, null);
+        } else if (comp > 0) {
+            return new Split<>(null, subset);
+        } else {
+            return new Split<>(null, null);
+        }
+    }
+
+    /** Return true if the plus side of the given splitter line is facing in the positive direction
+     * of this line.
+     * @param splitterLine line splitting this instance
+     * @return true if the plus side of the given line is facing in the positive direction of this
+     *      line
+     */
+    boolean splitterPlusIsPositiveFacing(final Line splitterLine) {
+        return line.getOffsetDirection().dot(splitterLine.getDirection()) <= 0;
+    }
+
+    /** Create a split result for the given splitter line, given the low and high split portion of this
+     * instance. The arguments are assigned to the split result's minus and plus properties based on the
+     * relative orientation of the splitter line.
+     * @param <T> Line subset type
+     * @param splitter splitter line
+     * @param low portion of the split result closest to negative infinity on this line
+     * @param high portion of th split result closest to positive infinity on this line
+     * @return a split result for the given splitter line.
+     */
+    <T extends LineSubset> Split<T> createSplitResult(final Line splitter, final T low, final T high) {
+        return splitterPlusIsPositiveFacing(splitter) ?
+                new Split<>(low, high) :
+                new Split<>(high, low);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Linecastable2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Linecastable2D.java
index a9ee49e..8575477 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Linecastable2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Linecastable2D.java
@@ -21,11 +21,11 @@ import java.util.List;
 /** Interface for objects that support linecast operations in Euclidean 2D space.
  *
  * <p>
- * Linecasting is a process that takes a line or line segment and intersects it with
+ * Linecasting is a process that takes a line or convex line subset and intersects it with
  * the boundaries of a region. This is similar to
  * <a href="https://en.wikipedia.org/wiki/Ray_casting">raycasting</a> used
  * for collision detection with the exception that the intersecting element can be a
- * line or line segment and not just a ray.
+ * line or convex line subset and not just a ray.
  * </p>
  */
 public interface Linecastable2D {
@@ -41,14 +41,14 @@ public interface Linecastable2D {
         return linecast(line.span());
     }
 
-    /** Intersect the given line segment against the boundaries in this instance, returning
+    /** Intersect the given line subset against the boundaries in this instance, returning
      * a list of all intersections in order of increasing position along the line. An empty
      * list is returned if no intersections are discovered.
-     * @param segment segment to intersect
+     * @param subset line subset to intersect
      * @return a list of computed intersections in order of increasing position
      *      along the line
      */
-    List<LinecastPoint2D> linecast(Segment segment);
+    List<LinecastPoint2D> linecast(LineConvexSubset subset);
 
     /** Intersect the given line against the boundaries in this instance, returning
      * the first intersection found when traveling in the direction of the line from
@@ -61,12 +61,12 @@ public interface Linecastable2D {
         return linecastFirst(line.span());
     }
 
-    /** Intersect the given line segment against the boundaries in this instance, returning
-     * the first intersection found when traveling in the direction of the line segment
-     * from its start point.
-     * @param segment segment to intersect
+    /** Intersect the given line subset against the boundaries in this instance, returning
+     * the first intersection found when traveling in the direction of the line subset
+     * from its start location.
+     * @param subset line subset to intersect
      * @return the first intersection found or null if no intersection
      *      is found
      */
-    LinecastPoint2D linecastFirst(Segment segment);
+    LinecastPoint2D linecastFirst(LineConvexSubset subset);
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Lines.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Lines.java
new file mode 100644
index 0000000..5915fe0
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Lines.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import java.text.MessageFormat;
+
+import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.oned.Interval;
+
+/** Class containing factory methods for constructing {@link Line} and {@link LineSubset} instances.
+ */
+public final class Lines {
+
+    /** Utility class; no instantiation. */
+    private Lines() {
+    }
+
+    /** Create a line from two points lying on the line. The line points in the direction
+     * from {@code p1} to {@code p2}.
+     * @param p1 first point
+     * @param p2 second point
+     * @param precision precision context used to compare floating point values
+     * @return new line containing {@code p1} and {@code p2} and pointing in the direction
+     *      from {@code p1} to {@code p2}
+     * @throws IllegalArgumentException If the vector between {@code p1} and {@code p2} has zero length,
+     *      as evaluated by the given precision context
+     */
+    public static Line fromPoints(final Vector2D p1, final Vector2D p2, final DoublePrecisionContext precision) {
+        return fromPointAndDirection(p1, p1.vectorTo(p2), precision);
+    }
+
+    /** Create a line from a point and direction.
+     * @param pt point belonging to the line
+     * @param dir the direction of the line
+     * @param precision precision context used to compare floating point values
+     * @return new line containing {@code pt} and pointing in direction {@code dir}
+     * @throws IllegalArgumentException If {@code dir} has zero length, as evaluated by the
+     *      given precision context
+     */
+    public static Line fromPointAndDirection(final Vector2D pt, final Vector2D dir,
+            final DoublePrecisionContext precision) {
+        if (dir.isZero(precision)) {
+            throw new IllegalArgumentException("Line direction cannot be zero");
+        }
+
+        final Vector2D normalizedDir = dir.normalize();
+        final double originOffset = normalizedDir.signedArea(pt);
+
+        return new Line(normalizedDir, originOffset, precision);
+    }
+
+    /** Create a line from a point lying on the line and an angle relative to the abscissa (x) axis. Note that the
+     * line does not need to intersect the x-axis; the given angle is simply relative to it.
+     * @param pt point belonging to the line
+     * @param angle angle of the line with respect to abscissa (x) axis, in radians
+     * @param precision precision context used to compare floating point values
+     * @return new line containing {@code pt} and forming the given angle with the
+     *      abscissa (x) axis.
+     */
+    public static Line fromPointAndAngle(final Vector2D pt, final double angle,
+            final DoublePrecisionContext precision) {
+        final Vector2D.Unit dir = Vector2D.Unit.from(Math.cos(angle), Math.sin(angle));
+        return fromPointAndDirection(pt, dir, precision);
+    }
+
+    /** Construct a ray from a start point and a direction.
+     * @param startPoint ray start point
+     * @param direction ray direction
+     * @param precision precision context used for floating point comparisons
+     * @return a new ray instance with the given start point and direction
+     * @throws IllegalArgumentException If {@code direction} has zero length, as evaluated by the
+     *      given precision context
+     * @see #fromPointAndDirection(Vector2D, Vector2D, DoublePrecisionContext)
+     */
+    public static Ray rayFromPointAndDirection(final Vector2D startPoint, final Vector2D direction,
+            final DoublePrecisionContext precision) {
+        final Line line = Lines.fromPointAndDirection(startPoint, direction, precision);
+
+        return new Ray(line, startPoint);
+    }
+
+    /** Construct a ray starting at the given point and continuing to infinity in the direction
+     * of {@code line}. The given point is projected onto the line.
+     * @param line line for the ray
+     * @param startPoint start point for the ray
+     * @return a new ray instance starting at the given point and continuing in the direction of
+     *      {@code line}
+     * @throws IllegalArgumentException if any coordinate in {@code startPoint} is NaN or infinite
+     */
+    public static Ray rayFromPoint(final Line line, final Vector2D startPoint) {
+        return rayFromLocation(line, line.abscissa(startPoint));
+    }
+
+    /** Construct a ray starting at the given 1D location on {@code line} and continuing in the
+     * direction of the line to infinity.
+     * @param line line for the ray
+     * @param startLocation 1D location of the ray start point
+     * @return a new ray instance starting at the given 1D location and continuing to infinity
+     *      along {@code line}
+     * @throws IllegalArgumentException if {@code startLocation} is NaN or infinite
+     */
+    public static Ray rayFromLocation(final Line line, final double startLocation) {
+        if (!Double.isFinite(startLocation)) {
+            throw new IllegalArgumentException("Invalid ray start location: " + Double.toString(startLocation));
+        }
+
+        return new Ray(line, startLocation);
+    }
+
+    /** Construct a reverse ray from an end point and a line direction.
+     * @param endPoint instance end point
+     * @param lineDirection line direction
+     * @param precision precision context used for floating point comparisons
+     * @return a new instance with the given end point and line direction
+     * @throws IllegalArgumentException If {@code lineDirection} has zero length, as evaluated by the
+     *      given precision context
+     * @see #fromPointAndDirection(Vector2D, Vector2D, DoublePrecisionContext)
+     */
+    public static ReverseRay reverseRayFromPointAndDirection(final Vector2D endPoint, final Vector2D lineDirection,
+            final DoublePrecisionContext precision) {
+        final Line line = Lines.fromPointAndDirection(endPoint, lineDirection, precision);
+
+        return new ReverseRay(line, endPoint);
+    }
+
+    /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
+     * to the given end point. The point is projected onto the line.
+     * @param line line for the instance
+     * @param endPoint end point for the instance
+     * @return a new instance starting at infinity and continuing along the line to {@code endPoint}
+     * @throws IllegalArgumentException if any coordinate in {@code endPoint} is NaN or infinite
+     */
+    public static ReverseRay reverseRayFromPoint(final Line line, final Vector2D endPoint) {
+        return reverseRayFromLocation(line, line.abscissa(endPoint));
+    }
+
+    /** Construct a reverse ray starting at infinity and continuing in the direction of {@code line}
+     * to the given 1D end location.
+     * @param line line for the instance
+     * @param endLocation 1D location of the instance end point
+     * @return a new instance starting infinity and continuing in the direction of {@code line}
+     *      to the given 1D end location
+     * @throws IllegalArgumentException if {@code endLocation} is NaN or infinite
+     */
+    public static ReverseRay reverseRayFromLocation(final Line line, final double endLocation) {
+        if (!Double.isFinite(endLocation)) {
+            throw new IllegalArgumentException("Invalid reverse ray end location: " + Double.toString(endLocation));
+        }
+
+        return new ReverseRay(line, endLocation);
+    }
+
+    /** Construct a new line segment from two points. A new line is created for the segment and points in the
+     * direction from {@code startPoint} to {@code endPoint}.
+     * @param startPoint segment start point
+     * @param endPoint segment end point
+     * @param precision precision context to use for floating point comparisons
+     * @return a new line segment instance with the given start and end points
+     * @throws IllegalArgumentException If the vector between {@code startPoint} and {@code endPoint} has zero length,
+     *      as evaluated by the given precision context
+     */
+    public static Segment segmentFromPoints(final Vector2D startPoint, final Vector2D endPoint,
+            final DoublePrecisionContext precision) {
+        final Line line = Lines.fromPoints(startPoint, endPoint, precision);
+
+        // we know that the points lie on the line and are in increasing abscissa order
+        // since they were used to create the line
+        return new Segment(line, startPoint, endPoint);
+    }
+
+    /** Construct a new line segment from a line and a pair of points. The returned segment represents
+     * all points on the line between the projected locations of {@code a} and {@code b}. The points may
+     * be given in any order.
+     * @param line line forming the base of the segment
+     * @param a first point
+     * @param b second point
+     * @return a new line segment representing the points between the projected locations of {@code a}
+     *      and {@code b} on the given line
+     * @throws IllegalArgumentException if either point contains NaN or infinite coordinate values
+     */
+    public static Segment segmentFromPoints(final Line line, final Vector2D a, final Vector2D b) {
+        return segmentFromLocations(line, line.abscissa(a), line.abscissa(b));
+    }
+
+    /** Construct a new line segment from a pair of 1D locations on a line. The returned line
+     * segment consists of all points between the two locations, regardless of the order the
+     * arguments are given.
+     * @param line line forming the base of the segment
+     * @param a first 1D location on the line
+     * @param b second 1D location on the line
+     * @return a new line segment representing the points between {@code a} and {@code b} on
+     *      the given line
+     * @throws IllegalArgumentException if either of the locations is NaN or infinite
+     */
+    public static Segment segmentFromLocations(final Line line, final double a, final double b) {
+
+        if (Double.isFinite(a) && Double.isFinite(b)) {
+            final double min = Math.min(a, b);
+            final double max = Math.max(a, b);
+
+            return new Segment(line, min, max);
+        }
+
+        throw new IllegalArgumentException(
+                MessageFormat.format("Invalid line segment locations: {0}, {1}",
+                        Double.toString(a), Double.toString(b)));
+    }
+
+    /** Create a {@link LineConvexSubset} spanning the entire line. In other words, the returned
+     * subset is infinite and contains all points on the given line.
+     * @param line the line to span
+     * @return a convex subset spanning the entire line
+     */
+    public static LineConvexSubset span(final Line line) {
+        return new LineSpanningSubset(line);
+    }
+
+    /** Create a line subset from a line and a 1D interval on the line. The returned subset
+     * uses the precision context from the line and not any precision contexts referenced by the interval.
+     * @param line the line containing the subset
+     * @param interval 1D interval on the line
+     * @return a convex subset defined by the given line and interval
+     */
+    public static LineConvexSubset subsetFromInterval(final Line line, final Interval interval) {
+        return subsetFromInterval(line, interval.getMin(), interval.getMax());
+    }
+
+    /** Create a line subset from a line and a 1D interval on the line. The double values may be given in any
+     * order and support the use of infinite values. For example, the call
+     * {@code Lines.subsetFromInterval(line, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)} will return
+     * an instance representing the full span of the line.
+     * @param line the line containing the subset
+     * @param a first 1D location on the line
+     * @param b second 1D location on the line
+     * @return a line subset defined by the given line and interval
+     * @throws IllegalArgumentException if either double value is NaN or both are infinite with the same sign
+     *      (eg, both positive infinity or both negative infinity)
+     */
+    public static LineConvexSubset subsetFromInterval(final Line line, final double a, final double b) {
+        final double min = Math.min(a, b);
+        final double max = Math.max(a, b);
+
+        final boolean hasMin = Double.isFinite(min);
+        final boolean hasMax = Double.isFinite(max);
+
+        if (hasMin) {
+            if (hasMax) {
+                // has both
+                return new Segment(line, min, max);
+            }
+            // min only
+            return new Ray(line, min);
+        } else if (hasMax) {
+            // max only
+            return new ReverseRay(line, max);
+        } else if (Double.isInfinite(min) && Double.isInfinite(max) && Double.compare(min, max) < 0) {
+            return new LineSpanningSubset(line);
+        }
+
+        throw new IllegalArgumentException(MessageFormat.format(
+                "Invalid line subset interval: {0}, {1}", Double.toString(a), Double.toString(b)));
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Ray.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Ray.java
new file mode 100644
index 0000000..c817727
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Ray.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.Split;
+
+/** Class representing a ray in 2D Euclidean space. A ray is a portion of a line consisting of
+ * a single start point and extending to infinity along the direction of the line.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @see ReverseRay
+ * @see Lines
+ */
+public final class Ray extends LineConvexSubset {
+
+    /** The start abscissa value for the ray. */
+    private final double start;
+
+    /** Construct a ray from a line and a start point. The start point is projected
+     * onto the line. No validation is performed.
+     * @param line line for the ray
+     * @param startPoint start point for the ray
+     */
+    Ray(final Line line, final Vector2D startPoint) {
+        this(line, line.abscissa(startPoint));
+    }
+
+    /** Construct a ray from a line and a 1D start location. No validation is performed.
+     * @param line line for the ray
+     * @param start 1D start location
+     */
+    Ray(final Line line, final double start) {
+        super(line);
+
+        this.start = start;
+    }
+
+    /** {@inheritDoc}
+     *
+     * <p>This method always returns {@code false}.</p>
+     */
+    @Override
+    public boolean isFull() {
+        return false;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code true}.</p>
+    */
+    @Override
+    public boolean isInfinite() {
+        return true;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code false}.</p>
+    */
+    @Override
+    public boolean isFinite() {
+        return false;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSize() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    @Override
+    public Vector2D getStartPoint() {
+        return getLine().toSpace(start);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getSubspaceStart() {
+        return start;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code null}.</p>
+    */
+    @Override
+    public Vector2D getEndPoint() {
+        return null;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSubspaceEnd() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** Get the direction of the ray. This is a convenience method for {@code ray.getLine().getDirection()}.
+     * @return the direction of the ray
+     */
+    public Vector2D getDirection() {
+        return getLine().getDirection();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Ray transform(final Transform<Vector2D> transform) {
+        final Line tLine = getLine().transform(transform);
+        final Vector2D tStart = transform.apply(getStartPoint());
+
+        return new Ray(tLine, tStart);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ReverseRay reverse() {
+        return new ReverseRay(getLine().reverse(), -start);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName())
+            .append("[startPoint= ")
+            .append(getStartPoint())
+            .append(", direction= ")
+            .append(getLine().getDirection())
+            .append(']');
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    RegionLocation classifyAbscissa(double abscissa) {
+        int cmp = getPrecision().compare(abscissa, start);
+        if (cmp > 0) {
+            return RegionLocation.INSIDE;
+        } else if (cmp == 0) {
+            return RegionLocation.BOUNDARY;
+        }
+
+        return RegionLocation.OUTSIDE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    double closestAbscissa(double abscissa) {
+        return Math.max(start, abscissa);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    Split<LineConvexSubset> splitOnIntersection(final Line splitter, final Vector2D intersection) {
+
+        final Line line = getLine();
+        final double splitAbscissa = line.abscissa(intersection);
+
+        LineConvexSubset low = null;
+        LineConvexSubset high = null;
+
+        int cmp = getPrecision().compare(splitAbscissa, start);
+        if (cmp > 0) {
+            low = new Segment(line, start, splitAbscissa);
+            high = new Ray(line, splitAbscissa);
+        } else {
+            high = this;
+        }
+
+        return createSplitResult(splitter, low, high);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2D.java
index 3e3c12a..004b369 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2D.java
@@ -29,15 +29,17 @@ import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
 import org.apache.commons.geometry.core.partitioning.bsp.AbstractRegionBSPTree;
 import org.apache.commons.geometry.core.partitioning.bsp.BSPTreeVisitor;
 import org.apache.commons.geometry.core.partitioning.bsp.RegionCutBoundary;
+import org.apache.commons.geometry.euclidean.twod.path.InteriorAngleLinePathConnector;
+import org.apache.commons.geometry.euclidean.twod.path.LinePath;
 
 /** Binary space partitioning (BSP) tree representing a region in two dimensional
  * Euclidean space.
  */
 public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, RegionBSPTree2D.RegionNode2D>
-    implements BoundarySource2D, Linecastable2D {
+    implements BoundarySource2D {
 
-    /** List of line segment paths comprising the region boundary. */
-    private List<Polyline> boundaryPaths;
+    /** List of line subset paths comprising the region boundary. */
+    private List<LinePath> boundaryPaths;
 
     /** Create a new, empty region.
      */
@@ -67,28 +69,28 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
 
     /** {@inheritDoc} */
     @Override
-    public Iterable<Segment> boundaries() {
-        return createBoundaryIterable(b -> (Segment) b);
+    public Iterable<LineConvexSubset> boundaries() {
+        return createBoundaryIterable(b -> (LineConvexSubset) b);
     }
 
     /** {@inheritDoc} */
     @Override
-    public Stream<Segment> boundaryStream() {
+    public Stream<LineConvexSubset> boundaryStream() {
         return StreamSupport.stream(boundaries().spliterator(), false);
     }
 
     /** {@inheritDoc} */
     @Override
-    public List<Segment> getBoundaries() {
-        return createBoundaryList(b -> (Segment) b);
+    public List<LineConvexSubset> getBoundaries() {
+        return createBoundaryList(b -> (LineConvexSubset) b);
     }
 
-    /** Get the boundary of the region as a list of connected line segment paths. The
-     * line segments are oriented such that their minus (left) side lies on the
+    /** Get the boundary of the region as a list of connected line subset paths.
+     * The line subset are oriented such that their minus (left) side lies on the
      * interior of the region.
-     * @return line segment paths representing the region boundary
+     * @return line subset paths representing the region boundary
      */
-    public List<Polyline> getBoundaryPaths() {
+    public List<LinePath> getBoundaryPaths() {
         if (boundaryPaths == null) {
             boundaryPaths = Collections.unmodifiableList(computeBoundaryPaths());
         }
@@ -165,8 +167,8 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
 
     /** {@inheritDoc} */
     @Override
-    public List<LinecastPoint2D> linecast(final Segment segment) {
-        final LinecastVisitor visitor = new LinecastVisitor(segment, false);
+    public List<LinecastPoint2D> linecast(final LineConvexSubset subset) {
+        final LinecastVisitor visitor = new LinecastVisitor(subset, false);
         accept(visitor);
 
         return visitor.getResults();
@@ -174,22 +176,22 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
 
     /** {@inheritDoc} */
     @Override
-    public LinecastPoint2D linecastFirst(final Segment segment) {
-        final LinecastVisitor visitor = new LinecastVisitor(segment, true);
+    public LinecastPoint2D linecastFirst(final LineConvexSubset subset) {
+        final LinecastVisitor visitor = new LinecastVisitor(subset, true);
         accept(visitor);
 
         return visitor.getFirstResult();
     }
 
-    /** Compute the line segment paths comprising the region boundary.
-     * @return the line segment paths comprising the region boundary
+    /** Compute the line subset paths comprising the region boundary.
+     * @return the line subset paths comprising the region boundary
      */
-    private List<Polyline> computeBoundaryPaths() {
-        final InteriorAngleSegmentConnector connector = new InteriorAngleSegmentConnector.Minimize();
+    private List<LinePath> computeBoundaryPaths() {
+        final InteriorAngleLinePathConnector connector = new InteriorAngleLinePathConnector.Minimize();
         connector.connect(boundaries());
 
         return connector.connectAll().stream()
-                .map(Polyline::simplify).collect(Collectors.toList());
+                .map(LinePath::simplify).collect(Collectors.toList());
     }
 
     /** {@inheritDoc} */
@@ -202,7 +204,7 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
             return new RegionSizeProperties<>(0, null);
         }
 
-        // compute the size based on the boundary segments
+        // compute the size based on the boundary line subsets
         double quadrilateralAreaSum = 0.0;
 
         double scaledSumX = 0.0;
@@ -212,9 +214,9 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
         Vector2D endPoint;
         double signedArea;
 
-        for (final Segment segment : boundaries()) {
+        for (final LineConvexSubset boundary : boundaries()) {
 
-            if (segment.isInfinite()) {
+            if (boundary.isInfinite()) {
                 // at least on boundary is infinite, meaning that
                 // the size is also infinite
                 quadrilateralAreaSum = Double.POSITIVE_INFINITY;
@@ -222,8 +224,8 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
                 break;
             }
 
-            startPoint = segment.getStartPoint();
-            endPoint = segment.getEndPoint();
+            startPoint = boundary.getStartPoint();
+            endPoint = boundary.getEndPoint();
 
             // compute the area
             signedArea = startPoint.signedArea(endPoint);
@@ -286,7 +288,7 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
      * @return a new tree instance constructed from the given boundaries
      * @see #from(Iterable, boolean)
      */
-    public static RegionBSPTree2D from(final Iterable<Segment> boundaries) {
+    public static RegionBSPTree2D from(final Iterable<LineConvexSubset> boundaries) {
         return from(boundaries, false);
     }
 
@@ -297,7 +299,7 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
      * @param full if true, the initial tree will contain the entire space
      * @return a new tree instance constructed from the given boundaries
      */
-    public static RegionBSPTree2D from(final Iterable<Segment> boundaries, final boolean full) {
+    public static RegionBSPTree2D from(final Iterable<LineConvexSubset> boundaries, final boolean full) {
         final RegionBSPTree2D tree = new RegionBSPTree2D(full);
         tree.insert(boundaries);
 
@@ -366,8 +368,8 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
      */
     private static final class LinecastVisitor implements BSPTreeVisitor<Vector2D, RegionNode2D> {
 
-        /** The line segment to intersect with the boundaries of the BSP tree. */
-        private final Segment linecastSegment;
+        /** The line subset to intersect with the boundaries of the BSP tree. */
+        private final LineConvexSubset linecastSubset;
 
         /** If true, the visitor will stop visiting the tree once the first linecast
          * point is determined.
@@ -380,13 +382,13 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
         /** List of results from the linecast operation. */
         private final List<LinecastPoint2D> results = new ArrayList<>();
 
-        /** Create a new instance with the given intersecting line segment.
-         * @param linecastSegment segment to intersect with the BSP tree region boundary
+        /** Create a new instance with the given intersecting line subset.
+         * @param linecastSubset line subset to intersect with the BSP tree region boundary
          * @param firstOnly if true, the visitor will stop visiting the tree once the first
          *      linecast point is determined
          */
-        LinecastVisitor(final Segment linecastSegment, final boolean firstOnly) {
-            this.linecastSegment = linecastSegment;
+        LinecastVisitor(final LineConvexSubset linecastSubset, final boolean firstOnly) {
+            this.linecastSubset = linecastSubset;
             this.firstOnly = firstOnly;
         }
 
@@ -415,7 +417,7 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
         @Override
         public Order visitOrder(final RegionNode2D internalNode) {
             final Line cut = (Line) internalNode.getCutHyperplane();
-            final Line line = linecastSegment.getLine();
+            final Line line = linecastSubset.getLine();
 
             final boolean plusIsNear = line.getDirection().dot(cut.getOffsetDirection()) < 0;
 
@@ -428,8 +430,8 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
         @Override
         public Result visit(final RegionNode2D node) {
             if (node.isInternal()) {
-                // check if the line segment intersects the cut subhyperplane
-                final Line line = linecastSegment.getLine();
+                // check if the line subset intersects the node cut
+                final Line line = linecastSubset.getLine();
                 final Vector2D pt = ((Line) node.getCutHyperplane()).intersection(line);
 
                 if (pt != null) {
@@ -438,7 +440,7 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
                         // we have results and we are now sure that no other intersection points will be
                         // found that are closer or at the same position on the intersecting line.
                         return Result.TERMINATE;
-                    } else if (linecastSegment.contains(pt)) {
+                    } else if (linecastSubset.contains(pt)) {
                         // we've potentially found a new linecast point; add it to the list of potential
                         // results
                         final LinecastPoint2D potentialResult = computeLinecastPoint(pt, node);
@@ -458,8 +460,7 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
         /** Compute the linecast point for the given intersection point and tree node, returning null
          * if the point does not actually lie on the region boundary.
          * @param pt intersection point
-         * @param node node containing the cut subhyperplane that the linecast line
-         *      intersected with
+         * @param node node containing the cut that the linecast line intersected with
          * @return a new linecast point instance or null if the intersection point does not lie
          *      on the region boundary
          */
@@ -485,7 +486,7 @@ public final class RegionBSPTree2D extends AbstractRegionBSPTree<Vector2D, Regio
                     normal = normal.negate();
                 }
 
-                return new LinecastPoint2D(pt, normal, linecastSegment.getLine());
+                return new LinecastPoint2D(pt, normal, linecastSubset.getLine());
             }
 
             return null;
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ReverseRay.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ReverseRay.java
new file mode 100644
index 0000000..766b912
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/ReverseRay.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import org.apache.commons.geometry.core.RegionLocation;
+import org.apache.commons.geometry.core.Transform;
+import org.apache.commons.geometry.core.partitioning.Split;
+
+/** Class representing a portion of a line in 2D Euclidean space that starts at infinity and
+ * continues in the direction of the line up to a single end point. This is equivalent to taking a
+ * {@link Ray} and reversing the line direction.
+ *
+ * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @see Ray
+ * @see Lines
+ */
+public final class ReverseRay extends LineConvexSubset {
+
+    /** The abscissa of the endpoint. */
+    private final double end;
+
+    /** Construct a new instance from the given line and end point. The end point is projected onto
+     * the line. No validation is performed.
+     * @param line line for the instance
+     * @param endPoint end point for the instance
+     */
+    ReverseRay(final Line line, final Vector2D endPoint) {
+        this(line, line.abscissa(endPoint));
+    }
+
+    /** Construct a new instance from the given line and 1D end location. No validation is performed.
+     * @param line line for the instance
+     * @param end end location for the instance
+     */
+    ReverseRay(final Line line, final double end) {
+        super(line);
+
+        this.end = end;
+    }
+
+    /** {@inheritDoc}
+     *
+     * <p>This method always returns {@code false}.</p>
+     */
+    @Override
+    public boolean isFull() {
+        return false;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code true}.</p>
+    */
+    @Override
+    public boolean isInfinite() {
+        return true;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code false}.</p>
+    */
+    @Override
+    public boolean isFinite() {
+        return false;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#POSITIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSize() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code null}.</p>
+    */
+    @Override
+    public Vector2D getStartPoint() {
+        return null;
+    }
+
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@link Double#NEGATIVE_INFINITY}.</p>
+    */
+    @Override
+    public double getSubspaceStart() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D getEndPoint() {
+        return getLine().toSpace(end);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getSubspaceEnd() {
+        return end;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public ReverseRay transform(final Transform<Vector2D> transform) {
+        final Line tLine = getLine().transform(transform);
+        final Vector2D tEnd = transform.apply(getEndPoint());
+
+        return new ReverseRay(tLine, tEnd);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Ray reverse() {
+        return new Ray(getLine().reverse(), -end);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName())
+            .append("[direction= ")
+            .append(getLine().getDirection())
+            .append(", endPoint= ")
+            .append(getEndPoint())
+            .append(']');
+
+        return sb.toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    RegionLocation classifyAbscissa(double abscissa) {
+        int cmp = getPrecision().compare(abscissa, end);
+        if (cmp < 0) {
+            return RegionLocation.INSIDE;
+        } else if (cmp == 0) {
+            return RegionLocation.BOUNDARY;
+        }
+
+        return RegionLocation.OUTSIDE;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    double closestAbscissa(double abscissa) {
+        return Math.min(end, abscissa);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected Split<LineConvexSubset> splitOnIntersection(final Line splitter, final Vector2D intersection) {
+
+        final Line line = getLine();
+        final double splitAbscissa = line.abscissa(intersection);
+
+        LineConvexSubset low = null;
+        LineConvexSubset high = null;
+
+        int cmp = getPrecision().compare(splitAbscissa, end);
+        if (cmp < 0) {
+            low = new ReverseRay(line, splitAbscissa);
+            high = new Segment(line, splitAbscissa, end);
+        } else {
+            low = this;
+        }
+
+        return createSplitResult(splitter, low, high);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java
index 2084714..0464ccd 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java
@@ -16,290 +16,185 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
-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.precision.DoublePrecisionContext;
-import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
-import org.apache.commons.geometry.euclidean.oned.Interval;
-import org.apache.commons.geometry.euclidean.oned.Vector1D;
-import org.apache.commons.geometry.euclidean.twod.Line.SubspaceTransform;
 
-/** <p>Class representing a line segment in 2D Euclidean space. Segments
- * need not be finite, in which case the start or end point (or both)
- * will be null.</p>
+/** Class representing a line segment in 2D Euclidean space. A line segment is a portion of
+ * a line with finite start and end points.
  *
  * <p>Instances of this class are guaranteed to be immutable.</p>
+ * @see Lines
+ * @see <a href="https://en.wikipedia.org/wiki/Line_segment">Line Segment</a>
  */
-public final class Segment extends AbstractSubLine
-    implements ConvexSubHyperplane<Vector2D> {
-    /** String used to indicate the start point of the segment in the toString() representation. */
-    private static final String START_STR = "start= ";
-
-    /** String used to indicate the direction the segment in the toString() representation. */
-    private static final String DIR_STR = "direction= ";
+public final class Segment extends LineConvexSubset {
 
-    /** String used to indicate the end point of the segment in the toString() representation. */
-    private static final String END_STR = "end= ";
+    /** Start abscissa for the segment. */
+    private final double start;
 
-    /** String used as a separator value in the toString() representation. */
-    private static final String SEP_STR = ", ";
-
-    /** The interval representing the region of the line contained in
-     * the line segment.
-     */
-    private final Interval interval;
+    /** End abscissa for the segment. */
+    private final double end;
 
-    /** Construct a line segment from an underlying line and a 1D interval
-     * on it.
-     * @param line the underlying line
-     * @param interval 1D interval on the line defining the line segment
+    /** Construct a new instance from a line and two points on the line. The points are projected onto
+     * the line and must be in order of increasing abscissa. No validation is performed.
+     * @param line line for the segment
+     * @param startPoint segment start point
+     * @param endPoint segment end point
      */
-    private Segment(final Line line, final Interval interval) {
-        super(line);
-
-        this.interval = interval;
+    Segment(final Line line, final Vector2D startPoint, final Vector2D endPoint) {
+        this(line, line.abscissa(startPoint), line.abscissa(endPoint));
     }
 
-    /** Get the start value in the 1D subspace of the line.
-     * @return the start value in the 1D subspace of the line.
+    /** Construct a new instance from a line and two abscissa locations on the line.
+     * The abscissa locations must be in increasing order. No validation is performed.
+     * @param line line for the segment
+     * @param start abscissa start location
+     * @param end abscissa end location
      */
-    public double getSubspaceStart() {
-        return interval.getMin();
-    }
+    Segment(final Line line, final double start, final double end) {
+        super(line);
 
-    /** Get the end value in the 1D subspace of the line.
-     * @return the end value in the 1D subspace of the line
-     */
-    public double getSubspaceEnd() {
-        return interval.getMax();
+        this.start = start;
+        this.end = end;
     }
 
-    /** Get the start point of the line segment or null if no start point
-     * exists (ie, the segment is infinite).
-     * @return the start point of the line segment or null if no start point
-     *      exists
-     */
-    public Vector2D getStartPoint() {
-        return interval.hasMinBoundary() ? getLine().toSpace(interval.getMin()) : null;
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code false}.</p>
+    */
+    @Override
+    public boolean isFull() {
+        return false;
     }
 
-    /** Get the end point of the line segment or null if no end point
-     * exists (ie, the segment is infinite).
-     * @return the end point of the line segment or null if no end point
-     *      exists
-     */
-    public Vector2D getEndPoint() {
-        return interval.hasMaxBoundary() ? getLine().toSpace(interval.getMax()) : null;
+    /** {@inheritDoc}
+    *
+    * <p>This method always returns {@code false}.</p>
+    */
+    @Override
+    public boolean isInfinite() {
+        return false;
     }
 
-    /** Return the 1D interval for the line segment.
-     * @return the 1D interval for the line segment
-     * @see #getSubspaceRegion()
+    /** {@inheritDoc}
+     *
+     * <p>This method always returns {@code true}.</p>
      */
-    public Interval getInterval() {
-        return interval;
+    @Override
+    public boolean isFinite() {
+        return true;
     }
 
     /** {@inheritDoc} */
     @Override
-    public boolean isInfinite() {
-        return interval.isInfinite();
+    public double getSize() {
+        return end - start;
     }
 
     /** {@inheritDoc} */
     @Override
-    public boolean isFinite() {
-        return interval.isFinite();
+    public Vector2D getStartPoint() {
+        return getLine().toSpace(start);
     }
 
     /** {@inheritDoc} */
     @Override
-    public Interval getSubspaceRegion() {
-        return getInterval();
+    public double getSubspaceStart() {
+        return start;
     }
 
     /** {@inheritDoc} */
     @Override
-    public List<Segment> toConvex() {
-        return Arrays.asList(this);
+    public Vector2D getEndPoint() {
+        return getLine().toSpace(end);
     }
 
     /** {@inheritDoc} */
     @Override
-    public Split<Segment> split(final Hyperplane<Vector2D> splitter) {
-        return splitInternal(splitter, this, (line, region) -> new Segment(line, (Interval) region));
+    public double getSubspaceEnd() {
+        return end;
     }
 
     /** {@inheritDoc} */
     @Override
-    public Segment transform(Transform<Vector2D> transform) {
-        final Line line = getLine();
-        final SubspaceTransform st = line.subspaceTransform(transform);
-
-        return fromInterval(st.getLine(), interval.transform(st.getTransform()));
-    }
+    public Segment transform(final Transform<Vector2D> transform) {
+        final Vector2D t1 = transform.apply(getStartPoint());
+        final Vector2D t2 = transform.apply(getEndPoint());
 
-    /** Get the unique intersection of this segment with the given line. Null is
-     * returned if no unique intersection point exists (ie, the lines are
-     * parallel or coincident) or the line does not intersect the segment.
-     * @param line line to intersect with this segment
-     * @return the unique intersection point between the line and this segment
-     *      or null if no such point exists.
-     * @see Line#intersection(Line)
-     */
-    public Vector2D intersection(final Line line) {
-        final Vector2D pt = getLine().intersection(line);
-        return (pt != null && contains(pt)) ? pt : null;
-    }
+        final Line tLine = getLine().transform(transform);
 
-    /** Get the unique intersection of this instance with the given segment. Null
-     * is returned if the lines containing the segments do not have a unique intersection
-     * point (ie, they are parallel or coincident) or the intersection point is unique
-     * but in not contained in both segments.
-     * @param segment segment to intersect with
-     * @return the unique intersection point between this segment and the argument or
-     *      null if no such point exists.
-     * @see Line#intersection(Line)
-     */
-    public Vector2D intersection(final Segment segment) {
-        final Vector2D pt = intersection(segment.getLine());
-        return (pt != null && segment.contains(pt)) ? pt : null;
+        return new Segment(tLine, t1, t2);
     }
 
     /** {@inheritDoc} */
     @Override
     public Segment reverse() {
-        final Interval reversedInterval = interval.transform(AffineTransformMatrix1D.createScale(-1));
-        return fromInterval(getLine().reverse(), reversedInterval);
+        return new Segment(getLine().reverse(), -end, -start);
     }
 
-    /** Return a string representation of the segment.
-    *
-    * <p>In order to keep the representation short but informative, the exact format used
-    * depends on the properties of the instance, as demonstrated in the examples
-    * below.
-    * <ul>
-    *      <li>Infinite segment -
-    *          {@code "Segment[lineOrigin= (0.0, 0.0), lineDirection= (1.0, 0.0)]"}</li>
-    *      <li>Start point but no end point -
-    *          {@code "Segment[start= (0.0, 0.0), direction= (1.0, 0.0)]"}</li>
-    *      <li>End point but no start point -
-    *          {@code "Segment[direction= (1.0, 0.0), end= (0.0, 0.0)]"}</li>
-    *      <li>Start point and end point -
-    *          {@code "Segment[start= (0.0, 0.0), end= (1.0, 0.0)]"}</li>
-    * </ul>
-    */
+    /** {@inheritDoc} */
     @Override
     public String toString() {
-        final Vector2D startPoint = getStartPoint();
-        final Vector2D endPoint = getEndPoint();
-
         final StringBuilder sb = new StringBuilder();
-        sb.append(this.getClass().getSimpleName())
-            .append('[');
-
-        if (startPoint != null && endPoint != null) {
-            sb.append(START_STR)
-                .append(startPoint)
-                .append(SEP_STR)
-                .append(END_STR)
-                .append(endPoint);
-        } else if (startPoint != null) {
-            sb.append(START_STR)
-                .append(startPoint)
-                .append(SEP_STR)
-                .append(DIR_STR)
-                .append(getLine().getDirection());
-        } else if (endPoint != null) {
-            sb.append(DIR_STR)
-                .append(getLine().getDirection())
-                .append(SEP_STR)
-                .append(END_STR)
-                .append(endPoint);
-        } else {
-            final Line line = getLine();
-
-            sb.append("lineOrigin= ")
-                .append(line.getOrigin())
-                .append(", lineDirection= ")
-                .append(line.getDirection());
-        }
-
-        sb.append(']');
+        sb.append(getClass().getSimpleName())
+            .append("[startPoint= ")
+            .append(getStartPoint())
+            .append(", endPoint= ")
+            .append(getEndPoint())
+            .append(']');
 
         return sb.toString();
     }
 
-    /** Create a line segment between two points. The underlying line points in the direction from {@code start}
-     * to {@code end}.
-     * @param start start point for the line segment
-     * @param end end point for the line segment
-     * @param precision precision context used to determine floating point equality
-     * @return a new line segment between {@code start} and {@code end}.
-     */
-    public static Segment fromPoints(final Vector2D start, final Vector2D end, final DoublePrecisionContext precision) {
-        final Line line = Line.fromPoints(start, end, precision);
-        return fromPointsOnLine(line, start, end);
-    }
-
-    /** Construct a line segment from a starting point and a direction that the line should extend to
-     * infinity from. This is equivalent to constructing a ray.
-     * @param start start point for the segment
-     * @param direction direction that the line should extend from the segment
-     * @param precision precision context used to determine floating point equality
-     * @return a new line segment starting from the given point and extending to infinity in the
-     *      specified direction
-     */
-    public static Segment fromPointAndDirection(final Vector2D start, final Vector2D direction,
-            final DoublePrecisionContext precision) {
-        final Line line = Line.fromPointAndDirection(start, direction, precision);
-        return fromInterval(line, Interval.min(line.toSubspace(start).getX(), precision));
-    }
-
-    /** Create a line segment from an underlying line and a 1D interval on the line.
-     * @param line the line that the line segment will belong to
-     * @param interval 1D interval on the line
-     * @return a line segment defined by the given line and interval
-     */
-    public static Segment fromInterval(final Line line, final Interval interval) {
-        return new Segment(line, interval);
-    }
+    /** {@inheritDoc} */
+    @Override
+    RegionLocation classifyAbscissa(final double abscissa) {
+        final DoublePrecisionContext precision = getPrecision();
+        int startCmp = precision.compare(abscissa, start);
+        if (startCmp > 0) {
+            int endCmp = precision.compare(abscissa, end);
+            if (endCmp < 0) {
+                return RegionLocation.INSIDE;
+            } else if (endCmp == 0) {
+                return RegionLocation.BOUNDARY;
+            }
+        } else if (startCmp == 0) {
+            return RegionLocation.BOUNDARY;
+        }
 
-    /** Create a line segment from an underlying line and a 1D interval on the line.
-     * @param line the line that the line segment will belong to
-     * @param a first 1D location on the line
-     * @param b second 1D location on the line
-     * @return a line segment defined by the given line and interval
-     */
-    public static Segment fromInterval(final Line line, final double a, final double b) {
-        return fromInterval(line, Interval.of(a, b, line.getPrecision()));
... 22625 lines suppressed ...