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 21:25:19 UTC

[commons-geometry] branch master updated: GEOMETRY-96: optimizing HyperplaneSubset.Builder implementations to only create internal BSP trees when needed

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 7ccde75  GEOMETRY-96: optimizing HyperplaneSubset.Builder implementations to only create internal BSP trees when needed
7ccde75 is described below

commit 7ccde75624c83b9d0e41e7a2c81ef0d96639dcae
Author: Matt Juntunen <ma...@apache.org>
AuthorDate: Wed May 6 10:20:20 2020 -0400

    GEOMETRY-96: optimizing HyperplaneSubset.Builder implementations to only create internal BSP trees when needed
---
 .../euclidean/threed/EmbeddedTreePlaneSubset.java  |  68 +----
 .../geometry/euclidean/threed/PlaneSubset.java     | 111 +++++++-
 .../commons/geometry/euclidean/threed/Planes.java  |  12 +
 .../geometry/euclidean/threed/RegionBSPTree3D.java |   6 +-
 .../euclidean/twod/EmbeddedTreeLineSubset.java     |  68 +----
 .../geometry/euclidean/twod/LineSubset.java        | 112 +++++++-
 .../commons/geometry/euclidean/twod/Lines.java     |  12 +
 .../threed/EmbeddedTreePlaneSubsetTest.java        |  82 +-----
 .../geometry/euclidean/threed/PlaneSubsetTest.java | 281 +++++++++++++++++++
 .../euclidean/twod/EmbeddedTreeLineSubsetTest.java | 207 --------------
 ...neSpanTest.java => LineSpanningSubsetTest.java} |   2 +-
 .../geometry/euclidean/twod/LineSubsetTest.java    | 312 +++++++++++++++++++++
 .../commons/geometry/spherical/oned/CutAngle.java  |   4 +-
 .../twod/EmbeddedTreeGreatCircleSubset.java        |  68 +----
 .../geometry/spherical/twod/GreatCircleSubset.java | 112 +++++++-
 .../geometry/spherical/twod/GreatCircles.java      |  14 +
 .../twod/EmbeddedTreeSubGreatCircleTest.java       | 117 --------
 .../spherical/twod/GreatCircleSubsetTest.java      | 284 +++++++++++++++++++
 18 files changed, 1258 insertions(+), 614 deletions(-)

diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java
index 5c49f6f..7b15ef6 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubset.java
@@ -21,8 +21,6 @@ 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.HyperplaneSubset;
 import org.apache.commons.geometry.core.partitioning.Split;
 import org.apache.commons.geometry.euclidean.twod.ConvexArea;
 import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
@@ -118,7 +116,7 @@ public final class EmbeddedTreePlaneSubset extends PlaneSubset {
      *      a plane equivalent to this instance
      */
     public void add(final PlaneConvexSubset subset) {
-        validatePlane(subset.getPlane());
+        Planes.validatePlanesEquivalent(getPlane(), subset.getPlane());
 
         region.add(subset.getSubspaceRegion());
     }
@@ -129,70 +127,8 @@ public final class EmbeddedTreePlaneSubset extends PlaneSubset {
      *      a plane equivalent to this instance
      */
     public void add(final EmbeddedTreePlaneSubset subset) {
-        validatePlane(subset.getPlane());
+        Planes.validatePlanesEquivalent(getPlane(), subset.getPlane());
 
         region.union(subset.getSubspaceRegion());
     }
-
-    /** Validate that the given plane is equivalent to the plane
-     * defining this instance.
-     * @param inputPlane plane to validate
-     * @throws IllegalArgumentException if the given plane is not equivalent
-     *      to the plane for this instance
-     */
-    private void validatePlane(final Plane inputPlane) {
-        final Plane plane = getPlane();
-
-        if (!plane.eq(inputPlane, plane.getPrecision())) {
-            throw new IllegalArgumentException("Argument is not on the same " +
-                    "plane. Expected " + plane + " but was " +
-                    inputPlane);
-        }
-    }
-
-    /** {@link HyperplaneSubset.Builder} implementation for plane subsets.
-     */
-    public static class Builder implements HyperplaneSubset.Builder<Vector3D> {
-
-        /** Plane subset instance created by this builder. */
-        private final EmbeddedTreePlaneSubset subset;
-
-        /** Construct a new instance for building a subset region for the given plane.
-         * @param plane the underlying plane for the subset
-         */
-        public Builder(final Plane plane) {
-            this.subset = new EmbeddedTreePlaneSubset(plane);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public void add(final HyperplaneSubset<Vector3D> sub) {
-            addInternal(sub);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public void add(final HyperplaneConvexSubset<Vector3D> sub) {
-            addInternal(sub);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public EmbeddedTreePlaneSubset build() {
-            return subset;
-        }
-
-        /** 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<Vector3D> sub) {
-            if (sub instanceof PlaneConvexSubset) {
-                subset.add((PlaneConvexSubset) sub);
-            } else if (sub instanceof EmbeddedTreePlaneSubset) {
-                subset.add((EmbeddedTreePlaneSubset) sub);
-            } else {
-                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/PlaneSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java
index a3a3598..945f87d 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/PlaneSubset.java
@@ -16,11 +16,15 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import java.util.List;
+import java.util.Objects;
 import java.util.function.BiFunction;
 
 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.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;
@@ -61,8 +65,12 @@ public abstract class PlaneSubset
 
     /** {@inheritDoc} */
     @Override
-    public EmbeddedTreePlaneSubset.Builder builder() {
-        return new EmbeddedTreePlaneSubset.Builder(plane);
+    public abstract List<PlaneConvexSubset> toConvex();
+
+    /** {@inheritDoc} */
+    @Override
+    public HyperplaneSubset.Builder<Vector3D> builder() {
+        return new Builder(plane);
     }
 
     /** Return the object used to perform floating point comparisons, which is the
@@ -142,4 +150,103 @@ public abstract class PlaneSubset
             return new Split<>(minus, plus);
         }
     }
+
+    /** Internal implementation of the {@link HyperplaneSubset.Builder} interface. In cases where only a single
+     * convex subset is given to the builder, this class returns the convex subset instance directly. In all other
+     * cases, an {@link EmbeddedTreePlaneSubset} is used to construct the final subset.
+     */
+    private static final class Builder implements HyperplaneSubset.Builder<Vector3D> {
+        /** Plane that a subset is being constructed for. */
+        private final Plane plane;
+
+        /** Embedded tree subset. */
+        private EmbeddedTreePlaneSubset treeSubset;
+
+        /** Convex subset added as the first subset to the builder. This is returned directly if
+         * no other subsets are added.
+         */
+        private PlaneConvexSubset convexSubset;
+
+        /** Create a new subset builder for the given plane.
+         * @param plane plane to build a subset for
+         */
+        Builder(final Plane plane) {
+            this.plane = plane;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void add(final HyperplaneSubset<Vector3D> sub) {
+            addInternal(sub);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void add(final HyperplaneConvexSubset<Vector3D> sub) {
+            addInternal(sub);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public PlaneSubset build() {
+            // return the convex subset directly if that was all we were given
+            if (convexSubset != null) {
+                return convexSubset;
+            }
+            return getTreeSubset();
+        }
+
+        /** Internal method for adding hyperplane subsets to this builder.
+         * @param sub the hyperplane subset to add; may be either convex or non-convex
+         */
+        private void addInternal(final HyperplaneSubset<Vector3D> sub) {
+            Objects.requireNonNull(sub, "Hyperplane subset must not be null");
+
+            if (sub instanceof PlaneConvexSubset) {
+                addConvexSubset((PlaneConvexSubset) sub);
+            } else if (sub instanceof EmbeddedTreePlaneSubset) {
+                addTreeSubset((EmbeddedTreePlaneSubset) sub);
+            } else {
+                throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
+            }
+        }
+
+        /** Add a convex subset to the builder.
+         * @param convex convex subset to add
+         */
+        private void addConvexSubset(final PlaneConvexSubset convex) {
+            Planes.validatePlanesEquivalent(plane, convex.getPlane());
+
+            if (treeSubset == null && convexSubset == null) {
+                convexSubset = convex;
+            } else {
+                getTreeSubset().add(convex);
+            }
+        }
+
+        /** Add an embedded tree subset to the builder.
+         * @param tree embedded tree subset to add
+         */
+        private void addTreeSubset(final EmbeddedTreePlaneSubset tree) {
+            // no need to validate the line here since the add() method does that for us
+            getTreeSubset().add(tree);
+        }
+
+        /** Get the tree subset for the builder, creating it if needed.
+         * @return the tree subset for the builder
+         */
+        private EmbeddedTreePlaneSubset getTreeSubset() {
+            if (treeSubset == null) {
+                treeSubset = new EmbeddedTreePlaneSubset(plane);
+
+                if (convexSubset != null) {
+                    treeSubset.add(convexSubset);
+
+                    convexSubset = null;
+                }
+            }
+
+            return treeSubset;
+        }
+    }
 }
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
index 8a4f275..fcba291 100644
--- 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
@@ -261,4 +261,16 @@ public final class Planes {
 
         return new PlaneConvexSubset(plane, area);
     }
+
+    /** Validate that the actual plane is equivalent to the expected plane, throwing an exception if not.
+     * @param expected the expected plane
+     * @param actual the actual plane
+     * @throws IllegalArgumentException if the actual plane is not equivalent to the expected plane
+     */
+    static void validatePlanesEquivalent(final Plane expected, final Plane actual) {
+        if (!expected.eq(actual, expected.getPrecision())) {
+            throw new IllegalArgumentException("Arguments do not represent the same plane. Expected " +
+                    expected + " but was " + actual + ".");
+        }
+    }
 }
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 177ded8..0739d67 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,6 +22,7 @@ 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.HyperplaneBoundedRegion;
 import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 import org.apache.commons.geometry.core.partitioning.Split;
 import org.apache.commons.geometry.core.partitioning.bsp.AbstractBSPTree;
@@ -31,7 +32,6 @@ 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;
 
 /** Binary space partitioning (BSP) tree representing a region in three dimensional
@@ -353,8 +353,8 @@ public final class RegionBSPTree3D extends AbstractRegionBSPTree<Vector3D, Regio
          * @param reverse if true, the boundary contribution is reversed before being added to the total.
          */
         private void addBoundaryContribution(final HyperplaneSubset<Vector3D> boundary, boolean reverse) {
-            final EmbeddedTreePlaneSubset boundarySubset = (EmbeddedTreePlaneSubset) boundary;
-            final RegionBSPTree2D base = boundarySubset.getSubspaceRegion();
+            final PlaneSubset boundarySubset = (PlaneSubset) boundary;
+            final HyperplaneBoundedRegion<Vector2D> base = boundarySubset.getSubspaceRegion();
 
             final double area = base.getSize();
             final Vector2D baseBarycenter = base.getBarycenter();
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
index 9bfb2f8..ab557dd 100644
--- 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
@@ -22,8 +22,6 @@ 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;
@@ -156,7 +154,7 @@ public final class EmbeddedTreeLineSubset extends LineSubset {
      *      a line equivalent to this instance
      */
     public void add(final LineConvexSubset subset) {
-        validateLine(subset.getLine());
+        Lines.validateLinesEquivalent(getLine(), subset.getLine());
 
         region.add(subset.getInterval());
     }
@@ -168,7 +166,7 @@ public final class EmbeddedTreeLineSubset extends LineSubset {
      *      a line equivalent to this instance
      */
     public void add(final EmbeddedTreeLineSubset subset) {
-        validateLine(subset.getLine());
+        Lines.validateLinesEquivalent(getLine(), subset.getLine());
 
         region.union(subset.getSubspaceRegion());
     }
@@ -197,66 +195,4 @@ public final class EmbeddedTreeLineSubset extends LineSubset {
     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/LineSubset.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/LineSubset.java
index e8c28aa..ba05313 100644
--- 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
@@ -16,8 +16,13 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import java.util.List;
+import java.util.Objects;
+
 import org.apache.commons.geometry.core.RegionLocation;
 import org.apache.commons.geometry.core.partitioning.AbstractRegionEmbeddingHyperplaneSubset;
+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.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.euclidean.oned.Vector1D;
@@ -53,8 +58,12 @@ public abstract class LineSubset extends AbstractRegionEmbeddingHyperplaneSubset
 
     /** {@inheritDoc} */
     @Override
-    public EmbeddedTreeLineSubset.Builder builder() {
-        return new EmbeddedTreeLineSubset.Builder(line);
+    public abstract List<LineConvexSubset> toConvex();
+
+    /** {@inheritDoc} */
+    @Override
+    public HyperplaneSubset.Builder<Vector2D> builder() {
+        return new Builder(line);
     }
 
     /** {@inheritDoc} */
@@ -153,4 +162,103 @@ public abstract class LineSubset extends AbstractRegionEmbeddingHyperplaneSubset
                 new Split<>(low, high) :
                 new Split<>(high, low);
     }
+
+    /** Internal implementation of the {@link HyperplaneSubset.Builder} interface. In cases where only a single
+     * convex subset is given to the builder, this class returns the convex subset instance directly. In all other
+     * cases, an {@link EmbeddedTreeLineSubset} is used to construct the final subset.
+     */
+    private static final class Builder implements HyperplaneSubset.Builder<Vector2D> {
+        /** Line that a subset is being constructed for. */
+        private final Line line;
+
+        /** Embedded tree subset. */
+        private EmbeddedTreeLineSubset treeSubset;
+
+        /** Convex subset added as the first subset to the builder. This is returned directly if
+         * no other subsets are added.
+         */
+        private LineConvexSubset convexSubset;
+
+        /** Create a new subset builder for the given line.
+         * @param line line to build a subset for
+         */
+        Builder(final Line line) {
+            this.line = 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 LineSubset build() {
+            // return the convex subset directly if that was all we were given
+            if (convexSubset != null) {
+                return convexSubset;
+            }
+            return getTreeSubset();
+        }
+
+        /** Internal method for adding hyperplane subsets to this builder.
+         * @param sub the hyperplane subset to add; may be either convex or non-convex
+         */
+        private void addInternal(final HyperplaneSubset<Vector2D> sub) {
+            Objects.requireNonNull(sub, "Hyperplane subset must not be null");
+
+            if (sub instanceof LineConvexSubset) {
+                addConvexSubset((LineConvexSubset) sub);
+            } else if (sub instanceof EmbeddedTreeLineSubset) {
+                addTreeSubset((EmbeddedTreeLineSubset) sub);
+            } else {
+                throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
+            }
+        }
+
+        /** Add a convex subset to the builder.
+         * @param convex convex subset to add
+         */
+        private void addConvexSubset(final LineConvexSubset convex) {
+            Lines.validateLinesEquivalent(line, convex.getLine());
+
+            if (treeSubset == null && convexSubset == null) {
+                convexSubset = convex;
+            } else {
+                getTreeSubset().add(convex);
+            }
+        }
+
+        /** Add an embedded tree subset to the builder.
+         * @param tree embedded tree subset to add
+         */
+        private void addTreeSubset(final EmbeddedTreeLineSubset tree) {
+            // no need to validate the line here since the add() method does that for us
+            getTreeSubset().add(tree);
+        }
+
+        /** Get the tree subset for the builder, creating it if needed.
+         * @return the tree subset for the builder
+         */
+        private EmbeddedTreeLineSubset getTreeSubset() {
+            if (treeSubset == null) {
+                treeSubset = new EmbeddedTreeLineSubset(line);
+
+                if (convexSubset != null) {
+                    treeSubset.add(convexSubset);
+
+                    convexSubset = null;
+                }
+            }
+
+            return treeSubset;
+        }
+    }
 }
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
index 5915fe0..1ac2ebe 100644
--- 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
@@ -274,4 +274,16 @@ public final class Lines {
         throw new IllegalArgumentException(MessageFormat.format(
                 "Invalid line subset interval: {0}, {1}", Double.toString(a), Double.toString(b)));
     }
+
+    /** Validate that the actual line is equivalent to the expected line, throwing an exception if not.
+     * @param expected the expected line
+     * @param actual the actual line
+     * @throws IllegalArgumentException if the actual line is not equivalent to the expected line
+     */
+    static void validateLinesEquivalent(final Line expected, final Line actual) {
+        if (!expected.eq(actual, expected.getPrecision())) {
+            throw new IllegalArgumentException("Arguments do not represent the same line. Expected " +
+                    expected + " but was " + actual + ".");
+        }
+    }
 }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubsetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubsetTest.java
index 3a323bb..5860ac3 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubsetTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/EmbeddedTreePlaneSubsetTest.java
@@ -22,10 +22,6 @@ import java.util.List;
 import org.apache.commons.geometry.core.GeometryTestUtils;
 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.HyperplaneBoundedRegion;
-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;
@@ -412,42 +408,6 @@ public class EmbeddedTreePlaneSubsetTest {
     }
 
     @Test
-    public void testBuilder() {
-        // arrange
-        Plane mainPlane = Planes.fromPointAndPlaneVectors(
-                Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
-        EmbeddedTreePlaneSubset.Builder builder = new EmbeddedTreePlaneSubset.Builder(mainPlane);
-
-        ConvexArea a = ConvexArea.fromVertexLoop(
-                Arrays.asList(Vector2D.ZERO, Vector2D.Unit.PLUS_X, Vector2D.Unit.PLUS_Y), TEST_PRECISION);
-        ConvexArea b = ConvexArea.fromVertexLoop(
-                Arrays.asList(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y), TEST_PRECISION);
-
-        Plane closePlane = Planes.fromPointAndPlaneVectors(
-                Vector3D.of(1e-16, 0, 1), Vector3D.of(1, 1e-16, 0), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
-
-        // act
-        builder.add(Planes.subsetFromConvexArea(closePlane, a));
-        builder.add(new EmbeddedTreePlaneSubset(closePlane, b.toTree()));
-
-        EmbeddedTreePlaneSubset result = builder.build();
-
-        // assert
-        Assert.assertFalse(result.isFull());
-        Assert.assertFalse(result.isEmpty());
-        Assert.assertTrue(result.isFinite());
-        Assert.assertFalse(result.isInfinite());
-
-        checkPoints(result, RegionLocation.INSIDE, Vector3D.of(0.5, 0.5, 1));
-        checkPoints(result, RegionLocation.OUTSIDE,
-                Vector3D.of(-1, 0.5, 1), Vector3D.of(2, 0.5, 1),
-                Vector3D.of(0.5, -1, 1), Vector3D.of(0.5, 2, 1));
-        checkPoints(result, RegionLocation.BOUNDARY,
-                Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1),
-                Vector3D.of(1, 1, 1), Vector3D.of(0, 1, 1));
-    }
-
-    @Test
     public void testSubPlaneAddMethods_validatesPlane() {
         // arrange
         EmbeddedTreePlaneSubset sp = new EmbeddedTreePlaneSubset(XY_PLANE, false);
@@ -466,47 +426,9 @@ public class EmbeddedTreePlaneSubsetTest {
         }, IllegalArgumentException.class);
     }
 
-    @Test
-    public void testBuilder_addUnknownType() {
-        // arrange
-        EmbeddedTreePlaneSubset.Builder sp = new EmbeddedTreePlaneSubset.Builder(XY_PLANE);
-
-        // act/assert
-        GeometryTestUtils.assertThrows(() -> {
-            sp.add(new StubSubPlane(XY_PLANE));
-        }, IllegalArgumentException.class);
-    }
-
-    private static void checkPoints(EmbeddedTreePlaneSubset sp, RegionLocation loc, Vector3D... pts) {
+    private static void checkPoints(EmbeddedTreePlaneSubset ps, RegionLocation loc, Vector3D... pts) {
         for (Vector3D pt : pts) {
-            Assert.assertEquals("Unexpected subplane location for point " + pt, loc, sp.classify(pt));
-        }
-    }
-
-    private static class StubSubPlane extends PlaneSubset implements HyperplaneSubset<Vector3D> {
-
-        StubSubPlane(Plane plane) {
-            super(plane);
-        }
-
-        @Override
-        public Split<? extends HyperplaneSubset<Vector3D>> split(Hyperplane<Vector3D> splitter) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public HyperplaneSubset<Vector3D> transform(Transform<Vector3D> transform) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public List<? extends HyperplaneConvexSubset<Vector3D>> toConvex() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public HyperplaneBoundedRegion<Vector2D> getSubspaceRegion() {
-            throw new UnsupportedOperationException();
+            Assert.assertEquals("Unexpected location for point " + pt, loc, ps.classify(pt));
         }
     }
 }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneSubsetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneSubsetTest.java
new file mode 100644
index 0000000..12308fe
--- /dev/null
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneSubsetTest.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.threed;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+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.HyperplaneBoundedRegion;
+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.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.twod.ConvexArea;
+import org.apache.commons.geometry.euclidean.twod.Vector2D;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PlaneSubsetTest {
+
+    private static final double TEST_EPS = 1e-10;
+
+    private static final DoublePrecisionContext TEST_PRECISION =
+            new EpsilonDoublePrecisionContext(TEST_EPS);
+
+    private static final Plane XY_PLANE = Planes.fromPointAndPlaneVectors(Vector3D.ZERO,
+            Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+    @Test
+    public void testBuilder_empty() {
+        // act
+        HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+        PlaneSubset result = (PlaneSubset) builder.build();
+
+        // assert
+        Assert.assertSame(XY_PLANE, result.getPlane());
+
+        Assert.assertFalse(result.isFull());
+        Assert.assertTrue(result.isEmpty());
+        Assert.assertTrue(result.isFinite());
+        Assert.assertFalse(result.isInfinite());
+
+        Assert.assertEquals(0, result.getSize(), TEST_EPS);
+
+        checkPoints(result, RegionLocation.OUTSIDE, Vector3D.ZERO);
+    }
+
+    @Test
+    public void testBuilder_addSingleConvex_returnsSameInstance() {
+        // arrange
+        PlaneConvexSubset convex = Planes.subsetFromVertexLoop(Arrays.asList(
+                    Vector3D.ZERO,
+                    Vector3D.of(1, 0, 0),
+                    Vector3D.of(0, 1, 0)
+                ), TEST_PRECISION);
+
+        // act
+        HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+        builder.add(convex);
+
+        PlaneSubset result = (PlaneSubset) builder.build();
+
+        // assert
+        Assert.assertSame(convex, result);
+    }
+
+    @Test
+    public void testBuilder_addSingleTreeSubset() {
+        // arrange
+        ConvexArea area = ConvexArea.fromVertexLoop(Arrays.asList(
+                    Vector2D.ZERO,
+                    Vector2D.of(1, 0),
+                    Vector2D.of(0, 1)
+                ), TEST_PRECISION);
+        EmbeddedTreePlaneSubset treeSubset = new EmbeddedTreePlaneSubset(XY_PLANE, area.toTree());
+
+        // act
+        HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+        builder.add(treeSubset);
+
+        PlaneSubset result = (PlaneSubset) builder.build();
+
+        // assert
+        Assert.assertNotSame(treeSubset, result);
+
+        Assert.assertFalse(result.isFull());
+        Assert.assertFalse(result.isEmpty());
+        Assert.assertTrue(result.isFinite());
+        Assert.assertFalse(result.isInfinite());
+
+        Assert.assertEquals(0.5, result.getSize(), TEST_EPS);
+
+        checkPoints(result, RegionLocation.INSIDE, Vector3D.of(0.25, 0.25, 0));
+        checkPoints(result, RegionLocation.OUTSIDE,
+                Vector3D.of(0.25, 0.25, 1), Vector3D.of(0.25, 0.25, -1),
+                Vector3D.of(1, 0.25, 0), Vector3D.of(-1, 0.25, 0),
+                Vector3D.of(0.25, 1, 0), Vector3D.of(0.25, -1, 0));
+        checkPoints(result, RegionLocation.BOUNDARY, Vector3D.of(0, 0, 0));
+    }
+
+    @Test
+    public void testBuilder_addMixed_convexFirst() {
+        // arrange
+        Plane mainPlane = Planes.fromPointAndPlaneVectors(
+                Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+        ConvexArea a = ConvexArea.fromVertexLoop(
+                Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(1, 1).normalize()), TEST_PRECISION);
+        ConvexArea b = ConvexArea.fromVertexLoop(
+                Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 1).normalize(), Vector2D.of(0, 1)), TEST_PRECISION);
+        ConvexArea c = ConvexArea.fromVertexLoop(
+                Arrays.asList(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y), TEST_PRECISION);
+
+        Plane closePlane = Planes.fromPointAndPlaneVectors(
+                Vector3D.of(1e-16, 0, 1), Vector3D.of(1, 1e-16, 0), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+        // act
+        HyperplaneSubset.Builder<Vector3D> builder = mainPlane.span().builder();
+
+        builder.add(Planes.subsetFromConvexArea(closePlane, a));
+        builder.add(Planes.subsetFromConvexArea(closePlane, b));
+        builder.add(new EmbeddedTreePlaneSubset(closePlane, c.toTree()));
+
+        PlaneSubset result = (PlaneSubset) builder.build();
+
+        // assert
+        Assert.assertFalse(result.isFull());
+        Assert.assertFalse(result.isEmpty());
+        Assert.assertTrue(result.isFinite());
+        Assert.assertFalse(result.isInfinite());
+
+        checkPoints(result, RegionLocation.INSIDE, Vector3D.of(0.5, 0.5, 1));
+        checkPoints(result, RegionLocation.OUTSIDE,
+                Vector3D.of(-1, 0.5, 1), Vector3D.of(2, 0.5, 1),
+                Vector3D.of(0.5, -1, 1), Vector3D.of(0.5, 2, 1));
+        checkPoints(result, RegionLocation.BOUNDARY,
+                Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1),
+                Vector3D.of(1, 1, 1), Vector3D.of(0, 1, 1));
+    }
+
+    @Test
+    public void testBuilder_addMixed_treeSubsetFirst() {
+     // arrange
+        Plane mainPlane = Planes.fromPointAndPlaneVectors(
+                Vector3D.of(0, 0, 1), Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+        ConvexArea a = ConvexArea.fromVertexLoop(
+                Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 0), Vector2D.of(1, 1).normalize()), TEST_PRECISION);
+        ConvexArea b = ConvexArea.fromVertexLoop(
+                Arrays.asList(Vector2D.ZERO, Vector2D.of(1, 1).normalize(), Vector2D.of(0, 1)), TEST_PRECISION);
+        ConvexArea c = ConvexArea.fromVertexLoop(
+                Arrays.asList(Vector2D.Unit.PLUS_X, Vector2D.of(1, 1), Vector2D.Unit.PLUS_Y), TEST_PRECISION);
+
+        Plane closePlane = Planes.fromPointAndPlaneVectors(
+                Vector3D.of(1e-16, 0, 1), Vector3D.of(1, 1e-16, 0), Vector3D.Unit.PLUS_Y, TEST_PRECISION);
+
+        // act
+        HyperplaneSubset.Builder<Vector3D> builder = mainPlane.span().builder();
+
+        builder.add(new EmbeddedTreePlaneSubset(closePlane, c.toTree()));
+        builder.add(Planes.subsetFromConvexArea(closePlane, a));
+        builder.add(Planes.subsetFromConvexArea(closePlane, b));
+
+        PlaneSubset result = (PlaneSubset) builder.build();
+
+        // assert
+        Assert.assertFalse(result.isFull());
+        Assert.assertFalse(result.isEmpty());
+        Assert.assertTrue(result.isFinite());
+        Assert.assertFalse(result.isInfinite());
+
+        checkPoints(result, RegionLocation.INSIDE, Vector3D.of(0.5, 0.5, 1));
+        checkPoints(result, RegionLocation.OUTSIDE,
+                Vector3D.of(-1, 0.5, 1), Vector3D.of(2, 0.5, 1),
+                Vector3D.of(0.5, -1, 1), Vector3D.of(0.5, 2, 1));
+        checkPoints(result, RegionLocation.BOUNDARY,
+                Vector3D.of(0, 0, 1), Vector3D.of(1, 0, 1),
+                Vector3D.of(1, 1, 1), Vector3D.of(0, 1, 1));
+    }
+
+    @Test
+    public void testBuilder_nullArguments() {
+        // arrange
+        HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add((HyperplaneSubset<Vector3D>) null);
+        }, NullPointerException.class, "Hyperplane subset must not be null");
+
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add((HyperplaneConvexSubset<Vector3D>) null);
+        }, NullPointerException.class, "Hyperplane subset must not be null");
+    }
+
+    @Test
+    public void testBuilder_argumentsFromDifferentPlanes() {
+        // arrange
+        PlaneConvexSubset convex = Planes.subsetFromVertexLoop(Arrays.asList(
+                Vector3D.ZERO,
+                Vector3D.of(1, 0, 1),
+                Vector3D.of(0, 1, 1)
+            ), TEST_PRECISION);
+
+        HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(convex);
+        }, IllegalArgumentException.class);
+
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(new EmbeddedTreePlaneSubset(convex.getPlane(), convex.getSubspaceRegion().toTree()));
+        }, IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testBuilder_addUnknownType() {
+        // arrange
+        HyperplaneSubset.Builder<Vector3D> builder = XY_PLANE.span().builder();
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(new StubSubPlane(Planes.fromNormal(Vector3D.Unit.PLUS_Y, TEST_PRECISION)));
+        }, IllegalArgumentException.class);
+    }
+
+    private static void checkPoints(PlaneSubset ps, RegionLocation loc, Vector3D... pts) {
+        for (Vector3D pt : pts) {
+            Assert.assertEquals("Unexpected location for point " + pt, loc, ps.classify(pt));
+        }
+    }
+
+    private static class StubSubPlane extends PlaneSubset implements HyperplaneSubset<Vector3D> {
+
+        StubSubPlane(Plane plane) {
+            super(plane);
+        }
+
+        @Override
+        public Split<? extends HyperplaneSubset<Vector3D>> split(Hyperplane<Vector3D> splitter) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public HyperplaneSubset<Vector3D> transform(Transform<Vector3D> transform) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public List<PlaneConvexSubset> toConvex() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public HyperplaneBoundedRegion<Vector2D> getSubspaceRegion() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubsetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubsetTest.java
index 2143274..d01a0e3 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubsetTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/EmbeddedTreeLineSubsetTest.java
@@ -19,12 +19,6 @@ package org.apache.commons.geometry.euclidean.twod;
 import java.util.List;
 
 import org.apache.commons.geometry.core.GeometryTestUtils;
-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.HyperplaneBoundedRegion;
-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;
@@ -32,8 +26,6 @@ import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.geometry.euclidean.oned.Interval;
 import org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D;
-import org.apache.commons.geometry.euclidean.oned.Vector1D;
-import org.apache.commons.geometry.euclidean.twod.EmbeddedTreeLineSubset.Builder;
 import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.junit.Assert;
 import org.junit.Test;
@@ -474,205 +466,6 @@ public class EmbeddedTreeLineSubsetTest {
     }
 
     @Test
-    public void testBuilder_instanceMethod() {
-        // arrange
-        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
-        Builder builder = new EmbeddedTreeLineSubset(line).builder();
-
-        // act
-        EmbeddedTreeLineSubset subset = builder.build();
-
-        // assert
-        Assert.assertFalse(subset.isFull());
-        Assert.assertTrue(subset.isEmpty());
-
-        List<LineConvexSubset> segments = subset.toConvex();
-        Assert.assertEquals(0, segments.size());
-
-        Assert.assertSame(line, subset.getLine());
-        Assert.assertSame(line, subset.getHyperplane());
-        Assert.assertSame(TEST_PRECISION, subset.getPrecision());
-    }
-
-    @Test
-    public void testBuilder_createEmpty() {
-        // arrange
-        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
-
-        Builder builder = new Builder(line);
-
-        // act
-        EmbeddedTreeLineSubset subset = builder.build();
-
-        // assert
-        Assert.assertFalse(subset.isFull());
-        Assert.assertTrue(subset.isEmpty());
-
-        List<LineConvexSubset> segments = subset.toConvex();
-        Assert.assertEquals(0, segments.size());
-    }
-
-    @Test
-    public void testBuilder_addConvex() {
-        // arrange
-        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
-        Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-11, TEST_PRECISION);
-
-        Builder builder = new Builder(line);
-
-        // act
-        builder.add(Lines.subsetFromInterval(line, 2, 4));
-        builder.add(Lines.subsetFromInterval(otherLine, 1, 3));
-        builder.add(Lines.segmentFromPoints(Vector2D.of(-4, 1), Vector2D.of(-1, 1), TEST_PRECISION));
-
-        EmbeddedTreeLineSubset subset = builder.build();
-
-        // assert
-        Assert.assertFalse(subset.isFull());
-        Assert.assertFalse(subset.isEmpty());
-
-        List<LineConvexSubset> segments = subset.toConvex();
-
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 1), segments.get(0).getStartPoint(), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1), segments.get(0).getEndPoint(), TEST_EPS);
-
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(1).getStartPoint(), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(1).getEndPoint(), TEST_EPS);
-    }
-
-    @Test
-    public void testBuilder_addNonConvex() {
-        // arrange
-        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
-
-        EmbeddedTreeLineSubset a = new EmbeddedTreeLineSubset(line);
-        RegionBSPTree1D aTree = a.getSubspaceRegion();
-        aTree.add(Interval.max(-3, TEST_PRECISION));
-        aTree.add(Interval.of(1, 2, TEST_PRECISION));
-
-        EmbeddedTreeLineSubset b = new EmbeddedTreeLineSubset(line);
-        RegionBSPTree1D bTree = b.getSubspaceRegion();
-        bTree.add(Interval.of(2, 4, TEST_PRECISION));
-        bTree.add(Interval.of(-4, -2, TEST_PRECISION));
-
-        Builder builder = new Builder(line);
-
-        int aTreeCount = aTree.count();
-        int bTreeCount = bTree.count();
-
-        // act
-        builder.add(a);
-        builder.add(b);
-
-        EmbeddedTreeLineSubset subset = builder.build();
-
-        // assert
-        Assert.assertFalse(subset.isFull());
-        Assert.assertFalse(subset.isEmpty());
-
-        List<LineConvexSubset> segments = subset.toConvex();
-
-        Assert.assertEquals(2, segments.size());
-
-        Assert.assertNull(segments.get(0).getStartPoint());
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 1), segments.get(0).getEndPoint(), TEST_EPS);
-
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(1).getStartPoint(), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(1).getEndPoint(), TEST_EPS);
-
-        Assert.assertEquals(aTreeCount, aTree.count());
-        Assert.assertEquals(bTreeCount, bTree.count());
-    }
-
-    @Test
-    public void testBuilder_argumentsFromDifferentLine() {
-        // arrange
-        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
-        Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-2, TEST_PRECISION);
-
-        Builder builder = new Builder(line);
-
-        // act/assert
-        GeometryTestUtils.assertThrows(() -> {
-            builder.add(Lines.subsetFromInterval(otherLine, 0, 1));
-        }, IllegalArgumentException.class);
-
-        GeometryTestUtils.assertThrows(() -> {
-            builder.add(new EmbeddedTreeLineSubset(otherLine));
-        }, IllegalArgumentException.class);
-    }
-
-    @Test
-    public void testBuilder_unknownSubsetType() {
-        // arrange
-        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
-
-        LineSubset unknownType = new LineSubset(line) {
-            @Override
-            public boolean isInfinite() {
-                return false;
-            }
-
-            @Override
-            public boolean isFinite() {
-                return true;
-            }
-
-            @Override
-            public List<? extends HyperplaneConvexSubset<Vector2D>> toConvex() {
-                return null;
-            }
-
-            @Override
-            public HyperplaneBoundedRegion<Vector1D> getSubspaceRegion() {
-                return null;
-            }
-
-            @Override
-            public Split<? extends HyperplaneSubset<Vector2D>> split(Hyperplane<Vector2D> splitter) {
-                return null;
-            }
-
-            @Override
-            public HyperplaneSubset<Vector2D> transform(Transform<Vector2D> transform) {
-                return null;
-            }
-
-            @Override
-            public Vector2D closest(Vector2D point) {
-                return null;
-            }
-
-            @Override
-            public boolean isFull() {
-                return false;
-            }
-
-            @Override
-            public boolean isEmpty() {
-                return false;
-            }
-
-            @Override
-            public double getSize() {
-                return 0;
-            }
-
-            @Override
-            RegionLocation classifyAbscissa(double abscissa) {
-                return null;
-            }
-        };
-
-        Builder builder = new Builder(line);
-
-        // act/assert
-        GeometryTestUtils.assertThrows(() -> {
-            builder.add(unknownType);
-        }, IllegalArgumentException.class);
-    }
-
-    @Test
     public void testToString() {
         // arrange
         EmbeddedTreeLineSubset sub = new EmbeddedTreeLineSubset(DEFAULT_TEST_LINE);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanningSubsetTest.java
similarity index 99%
rename from commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanTest.java
rename to commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanningSubsetTest.java
index 4c1dd4a..b24a330 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSpanningSubsetTest.java
@@ -26,7 +26,7 @@ import org.apache.commons.geometry.euclidean.oned.Interval;
 import org.junit.Assert;
 import org.junit.Test;
 
-public class LineSpanTest {
+public class LineSpanningSubsetTest {
 
     private static final double TEST_EPS = 1e-10;
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSubsetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSubsetTest.java
new file mode 100644
index 0000000..1b313a6
--- /dev/null
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/LineSubsetTest.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.twod;
+
+import java.util.List;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+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.HyperplaneBoundedRegion;
+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.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
+import org.apache.commons.geometry.euclidean.oned.Interval;
+import org.apache.commons.geometry.euclidean.oned.RegionBSPTree1D;
+import org.apache.commons.geometry.euclidean.oned.Vector1D;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class LineSubsetTest {
+
+    private static final double TEST_EPS = 1e-10;
+
+    private static final DoublePrecisionContext TEST_PRECISION =
+            new EpsilonDoublePrecisionContext(TEST_EPS);
+
+    @Test
+    public void testBuilder_empty() {
+        // arrange
+        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+
+        HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+        // act
+        LineSubset subset = (LineSubset) builder.build();
+
+        // assert
+        Assert.assertFalse(subset.isFull());
+        Assert.assertTrue(subset.isEmpty());
+
+        List<LineConvexSubset> segments = subset.toConvex();
+        Assert.assertEquals(0, segments.size());
+    }
+
+    @Test
+    public void testBuilder_addSingleConvex_usesSameInstance() {
+        // arrange
+        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+
+        HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+        LineConvexSubset convex = Lines.subsetFromInterval(line, 2, 4);
+
+        // act
+        builder.add(convex);
+
+        LineSubset subset = (LineSubset) builder.build();
+
+        // assert
+        Assert.assertSame(convex, subset);
+    }
+
+    @Test
+    public void testBuilder_addConvex() {
+        // arrange
+        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+        Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-11, TEST_PRECISION);
+
+        HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+        // act
+        builder.add(Lines.subsetFromInterval(line, 2, 4));
+        builder.add(Lines.subsetFromInterval(otherLine, 1, 3));
+        builder.add(Lines.segmentFromPoints(Vector2D.of(-4, 1), Vector2D.of(-1, 1), TEST_PRECISION));
+
+        LineSubset subset = (LineSubset) builder.build();
+
+        // assert
+        Assert.assertFalse(subset.isFull());
+        Assert.assertFalse(subset.isEmpty());
+
+        List<LineConvexSubset> segments = subset.toConvex();
+        Assert.assertEquals(2, segments.size());
+
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, 1), segments.get(0).getStartPoint(), TEST_EPS);
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 1), segments.get(0).getEndPoint(), TEST_EPS);
+
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(1).getStartPoint(), TEST_EPS);
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(1).getEndPoint(), TEST_EPS);
+    }
+
+    @Test
+    public void testBuilder_addTreeSubset() {
+        // arrange
+        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+
+        EmbeddedTreeLineSubset a = new EmbeddedTreeLineSubset(line);
+        RegionBSPTree1D aTree = a.getSubspaceRegion();
+        aTree.add(Interval.max(-3, TEST_PRECISION));
+        aTree.add(Interval.of(1, 2, TEST_PRECISION));
+
+        EmbeddedTreeLineSubset b = new EmbeddedTreeLineSubset(line);
+        RegionBSPTree1D bTree = b.getSubspaceRegion();
+        bTree.add(Interval.of(2, 4, TEST_PRECISION));
+        bTree.add(Interval.of(-4, -2, TEST_PRECISION));
+
+        HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+        int aTreeCount = aTree.count();
+        int bTreeCount = bTree.count();
+
+        // act
+        builder.add(a);
+        builder.add(b);
+
+        LineSubset subset = (LineSubset) builder.build();
+
+        // assert
+        Assert.assertFalse(subset.isFull());
+        Assert.assertFalse(subset.isEmpty());
+
+        List<LineConvexSubset> segments = subset.toConvex();
+
+        Assert.assertEquals(2, segments.size());
+
+        Assert.assertNull(segments.get(0).getStartPoint());
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 1), segments.get(0).getEndPoint(), TEST_EPS);
+
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(1).getStartPoint(), TEST_EPS);
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(1).getEndPoint(), TEST_EPS);
+
+        Assert.assertEquals(aTreeCount, aTree.count());
+        Assert.assertEquals(bTreeCount, bTree.count());
+    }
+
+    @Test
+    public void testBuilder_addMixed_convexFirst() {
+        // arrange
+        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+        Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-11, TEST_PRECISION);
+
+        HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+        // act
+        builder.add(Lines.subsetFromInterval(line, 2, 4));
+
+        EmbeddedTreeLineSubset treeSubset = new EmbeddedTreeLineSubset(otherLine);
+        treeSubset.add(Lines.subsetFromInterval(otherLine, 1, 3));
+        builder.add(treeSubset);
+
+        LineSubset subset = (LineSubset) builder.build();
+
+        // assert
+        Assert.assertFalse(subset.isFull());
+        Assert.assertFalse(subset.isEmpty());
+
+        List<LineConvexSubset> segments = subset.toConvex();
+        Assert.assertEquals(1, segments.size());
+
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(0).getStartPoint(), TEST_EPS);
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(0).getEndPoint(), TEST_EPS);
+    }
+
+    @Test
+    public void testBuilder_addMixed_treeSubsetFirst() {
+        // arrange
+        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+        Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-11, TEST_PRECISION);
+
+        HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+        // act
+        EmbeddedTreeLineSubset treeSubset = new EmbeddedTreeLineSubset(otherLine);
+        treeSubset.add(Lines.subsetFromInterval(otherLine, 1, 3));
+        builder.add(treeSubset);
+
+        builder.add(Lines.subsetFromInterval(line, 2, 4));
+
+        LineSubset subset = (LineSubset) builder.build();
+
+        // assert
+        Assert.assertFalse(subset.isFull());
+        Assert.assertFalse(subset.isEmpty());
+
+        List<LineConvexSubset> segments = subset.toConvex();
+        Assert.assertEquals(1, segments.size());
+
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 1), segments.get(0).getStartPoint(), TEST_EPS);
+        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 1), segments.get(0).getEndPoint(), TEST_EPS);
+    }
+
+    @Test
+    public void testBuilder_nullArgs() {
+        // arrange
+        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+        HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add((HyperplaneSubset<Vector2D>) null);
+        }, NullPointerException.class, "Hyperplane subset must not be null");
+
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add((HyperplaneConvexSubset<Vector2D>) null);
+        }, NullPointerException.class, "Hyperplane subset must not be null");
+    }
+
+    @Test
+    public void testBuilder_argumentsFromDifferentLine() {
+        // arrange
+        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+        Line otherLine = Lines.fromPointAndAngle(Vector2D.of(0, 1), 1e-2, TEST_PRECISION);
+
+        HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(Lines.subsetFromInterval(otherLine, 0, 1));
+        }, IllegalArgumentException.class);
+
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(new EmbeddedTreeLineSubset(otherLine));
+        }, IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testBuilder_unknownSubsetType() {
+        // arrange
+        Line line = Lines.fromPointAndAngle(Vector2D.of(0, 1), 0.0, TEST_PRECISION);
+
+        LineSubset unknownType = new LineSubset(line) {
+            @Override
+            public boolean isInfinite() {
+                return false;
+            }
+
+            @Override
+            public boolean isFinite() {
+                return true;
+            }
+
+            @Override
+            public List<LineConvexSubset> toConvex() {
+                return null;
+            }
+
+            @Override
+            public HyperplaneBoundedRegion<Vector1D> getSubspaceRegion() {
+                return null;
+            }
+
+            @Override
+            public Split<? extends HyperplaneSubset<Vector2D>> split(Hyperplane<Vector2D> splitter) {
+                return null;
+            }
+
+            @Override
+            public HyperplaneSubset<Vector2D> transform(Transform<Vector2D> transform) {
+                return null;
+            }
+
+            @Override
+            public Vector2D closest(Vector2D point) {
+                return null;
+            }
+
+            @Override
+            public boolean isFull() {
+                return false;
+            }
+
+            @Override
+            public boolean isEmpty() {
+                return false;
+            }
+
+            @Override
+            public double getSize() {
+                return 0;
+            }
+
+            @Override
+            RegionLocation classifyAbscissa(double abscissa) {
+                return null;
+            }
+        };
+
+        HyperplaneSubset.Builder<Vector2D> builder = line.span().builder();
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(unknownType);
+        }, IllegalArgumentException.class);
+    }
+}
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/CutAngle.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/CutAngle.java
index 81e5a9b..ec3e5dc 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/CutAngle.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/CutAngle.java
@@ -239,7 +239,7 @@ public final class CutAngle extends AbstractHyperplane<Point1S> {
      * this is effectively a stub implementation, its main use being to allow for the correct functioning of
      * partitioning code.
      */
-    private static class CutAngleConvexSubset implements HyperplaneConvexSubset<Point1S> {
+    private static final class CutAngleConvexSubset implements HyperplaneConvexSubset<Point1S> {
         /** The hyperplane containing for this instance. */
         private final CutAngle hyperplane;
 
@@ -380,7 +380,7 @@ public final class CutAngle extends AbstractHyperplane<Point1S> {
      * 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 CutAngleSubsetBuilder implements HyperplaneSubset.Builder<Point1S> {
+    private static final class CutAngleSubsetBuilder implements HyperplaneSubset.Builder<Point1S> {
         /** Base hyperplane subset for the builder. */
         private final CutAngleConvexSubset base;
 
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeGreatCircleSubset.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeGreatCircleSubset.java
index 71c7b99..b9054af 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeGreatCircleSubset.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeGreatCircleSubset.java
@@ -21,8 +21,6 @@ import java.util.stream.Collectors;
 
 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.spherical.oned.CutAngle;
@@ -136,7 +134,7 @@ public final class EmbeddedTreeGreatCircleSubset extends GreatCircleSubset {
      *      a great circle equivalent to this instance
      */
     public void add(final GreatArc arc) {
-        validateGreatCircle(arc.getCircle());
+        GreatCircles.validateGreatCirclesEquivalent(getCircle(), arc.getCircle());
 
         region.add(arc.getSubspaceRegion());
     }
@@ -148,7 +146,7 @@ public final class EmbeddedTreeGreatCircleSubset extends GreatCircleSubset {
      *      a great circle equivalent to this instance
      */
     public void add(final EmbeddedTreeGreatCircleSubset subcircle) {
-        validateGreatCircle(subcircle.getCircle());
+        GreatCircles.validateGreatCirclesEquivalent(getCircle(), subcircle.getCircle());
 
         region.union(subcircle.getSubspaceRegion());
     }
@@ -167,66 +165,4 @@ public final class EmbeddedTreeGreatCircleSubset extends GreatCircleSubset {
 
         return sb.toString();
     }
-
-    /** Validate that the given great circle is equivalent to the circle
-     * defining this instance.
-     * @param inputCircle the great circle to validate
-     * @throws IllegalArgumentException if the argument is not equivalent
-     *      to the great circle for this instance
-     */
-    private void validateGreatCircle(final GreatCircle inputCircle) {
-        final GreatCircle circle = getCircle();
-
-        if (!circle.eq(inputCircle, circle.getPrecision())) {
-            throw new IllegalArgumentException("Argument is not on the same " +
-                    "great circle. Expected " + circle + " but was " +
-                    inputCircle);
-        }
-    }
-
-    /** {@link HyperplaneSubset.Builder} implementation for great circle subsets.
-     */
-    public static final class Builder implements HyperplaneSubset.Builder<Point2S> {
-
-        /** SubGreatCircle instance created by this builder. */
-        private final EmbeddedTreeGreatCircleSubset subcircle;
-
-        /** Construct a new instance for building regions for the given great circle.
-         * @param circle the underlying great circle for the region
-         */
-        public Builder(final GreatCircle circle) {
-            this.subcircle = new EmbeddedTreeGreatCircleSubset(circle);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public void add(final HyperplaneSubset<Point2S> sub) {
-            addInternal(sub);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public void add(final HyperplaneConvexSubset<Point2S> sub) {
-            addInternal(sub);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public EmbeddedTreeGreatCircleSubset build() {
-            return subcircle;
-        }
-
-        /** 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<Point2S> sub) {
-            if (sub instanceof GreatArc) {
-                subcircle.add((GreatArc) sub);
-            } else if (sub instanceof EmbeddedTreeGreatCircleSubset) {
-                subcircle.add((EmbeddedTreeGreatCircleSubset) sub);
-            } else {
-                throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
-            }
-        }
-    }
 }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubset.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubset.java
index ab8a76b..5fbf0f5 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubset.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubset.java
@@ -16,7 +16,12 @@
  */
 package org.apache.commons.geometry.spherical.twod;
 
+import java.util.List;
+import java.util.Objects;
+
 import org.apache.commons.geometry.core.partitioning.AbstractRegionEmbeddingHyperplaneSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;
+import org.apache.commons.geometry.core.partitioning.HyperplaneSubset;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.spherical.oned.Point1S;
 
@@ -51,8 +56,12 @@ public abstract class GreatCircleSubset
 
     /** {@inheritDoc} */
     @Override
-    public EmbeddedTreeGreatCircleSubset.Builder builder() {
-        return new EmbeddedTreeGreatCircleSubset.Builder(circle);
+    public abstract List<GreatArc> toConvex();
+
+    /** {@inheritDoc} */
+    @Override
+    public HyperplaneSubset.Builder<Point2S> builder() {
+        return new Builder(circle);
     }
 
     /** Return the object used to perform floating point comparisons, which is the
@@ -62,4 +71,103 @@ public abstract class GreatCircleSubset
     public DoublePrecisionContext getPrecision() {
         return circle.getPrecision();
     }
+
+    /** Internal implementation of the {@link HyperplaneSubset.Builder} interface. In cases where only a single
+     * convex subset is given to the builder, this class returns the convex subset instance directly. In all other
+     * cases, an {@link EmbeddedTreeGreatCircleSubset} is used to construct the final subset.
+     */
+    private static final class Builder implements HyperplaneSubset.Builder<Point2S> {
+        /** Great circle that a subset is being constructed for. */
+        private final GreatCircle circle;
+
+        /** Embedded tree subset. */
+        private EmbeddedTreeGreatCircleSubset treeSubset;
+
+        /** Convex subset added as the first subset to the builder. This is returned directly if
+         * no other subsets are added.
+         */
+        private GreatArc convexSubset;
+
+        /** Create a new subset builder for the given great circle.
+         * @param circle great circle to build a subset for
+         */
+        Builder(final GreatCircle circle) {
+            this.circle = circle;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void add(final HyperplaneSubset<Point2S> sub) {
+            addInternal(sub);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void add(final HyperplaneConvexSubset<Point2S> sub) {
+            addInternal(sub);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public GreatCircleSubset build() {
+            // return the convex subset directly if that was all we were given
+            if (convexSubset != null) {
+                return convexSubset;
+            }
+            return getTreeSubset();
+        }
+
+        /** Internal method for adding hyperplane subsets to this builder.
+         * @param sub the hyperplane subset to add; may be either convex or non-convex
+         */
+        private void addInternal(final HyperplaneSubset<Point2S> sub) {
+            Objects.requireNonNull(sub, "Hyperplane subset must not be null");
+
+            if (sub instanceof GreatArc) {
+                addConvexSubset((GreatArc) sub);
+            } else if (sub instanceof EmbeddedTreeGreatCircleSubset) {
+                addTreeSubset((EmbeddedTreeGreatCircleSubset) sub);
+            } else {
+                throw new IllegalArgumentException("Unsupported hyperplane subset type: " + sub.getClass().getName());
+            }
+        }
+
+        /** Add a convex subset to the builder.
+         * @param convex convex subset to add
+         */
+        private void addConvexSubset(final GreatArc convex) {
+            GreatCircles.validateGreatCirclesEquivalent(circle, convex.getCircle());
+
+            if (treeSubset == null && convexSubset == null) {
+                convexSubset = convex;
+            } else {
+                getTreeSubset().add(convex);
+            }
+        }
+
+        /** Add an embedded tree subset to the builder.
+         * @param tree embedded tree subset to add
+         */
+        private void addTreeSubset(final EmbeddedTreeGreatCircleSubset tree) {
+            // no need to validate the line here since the add() method does that for us
+            getTreeSubset().add(tree);
+        }
+
+        /** Get the tree subset for the builder, creating it if needed.
+         * @return the tree subset for the builder
+         */
+        private EmbeddedTreeGreatCircleSubset getTreeSubset() {
+            if (treeSubset == null) {
+                treeSubset = new EmbeddedTreeGreatCircleSubset(circle);
+
+                if (convexSubset != null) {
+                    treeSubset.add(convexSubset);
+
+                    convexSubset = null;
+                }
+            }
+
+            return treeSubset;
+        }
+    }
 }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircles.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircles.java
index 1bec679..44ce6f9 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircles.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/GreatCircles.java
@@ -127,4 +127,18 @@ public final class GreatCircles {
     public static GreatArc arcFromInterval(final GreatCircle circle, final AngularInterval.Convex interval) {
         return new GreatArc(circle, interval);
     }
+
+    /** Validate that the actual great circle is equivalent to the expected great circle,
+     * throwing an exception if not.
+     * @param expected the expected great circle
+     * @param actual the actual great circle
+     * @throws IllegalArgumentException if the actual great circle is not equivalent to the
+     *      expected great circle
+     */
+    static void validateGreatCirclesEquivalent(final GreatCircle expected, final GreatCircle actual) {
+        if (!expected.eq(actual, expected.getPrecision())) {
+            throw new IllegalArgumentException("Arguments do not represent the same great circle. Expected " +
+                    expected + " but was " + actual + ".");
+        }
+    }
 }
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeSubGreatCircleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeSubGreatCircleTest.java
index a05c729..742f2fe 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeSubGreatCircleTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/EmbeddedTreeSubGreatCircleTest.java
@@ -20,9 +20,6 @@ import java.util.List;
 
 import org.apache.commons.geometry.core.GeometryTestUtils;
 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;
@@ -386,57 +383,6 @@ public class EmbeddedTreeSubGreatCircleTest {
     }
 
     @Test
-    public void testBuilder() {
-        // arrange
-        GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
-
-        EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle);
-
-        RegionBSPTree1S region = RegionBSPTree1S.empty();
-        region.add(AngularInterval.of(PlaneAngleRadians.PI, 1.25 * PlaneAngleRadians.PI, TEST_PRECISION));
-        region.add(AngularInterval.of(0.25 * PlaneAngleRadians.PI, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION));
-
-        // act
-        EmbeddedTreeGreatCircleSubset.Builder builder = sub.builder();
-
-        builder.add(new EmbeddedTreeGreatCircleSubset(circle, region));
-        builder.add(circle.arc(1.5 * PlaneAngleRadians.PI, 0.25 * PlaneAngleRadians.PI));
-
-        EmbeddedTreeGreatCircleSubset result = builder.build();
-
-        // assert
-        List<GreatArc> arcs = result.toConvex();
-
-        Assert.assertEquals(2, arcs.size());
-        checkArc(arcs.get(0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0.25 * PlaneAngleRadians.PI));
-        checkArc(arcs.get(1), Point2S.PLUS_J, Point2S.MINUS_J);
-    }
-
-    @Test
-    public void testBuilder_invalidArgs() {
-        // arrange
-        GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
-        GreatCircle otherCircle = GreatCircles.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION);
-
-        EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle);
-
-        EmbeddedTreeGreatCircleSubset.Builder builder = sub.builder();
-
-        // act/assert
-        GeometryTestUtils.assertThrows(() -> {
-            builder.add(otherCircle.span());
-        }, IllegalArgumentException.class);
-
-        GeometryTestUtils.assertThrows(() -> {
-            builder.add(new EmbeddedTreeGreatCircleSubset(otherCircle));
-        }, IllegalArgumentException.class);
-
-        GeometryTestUtils.assertThrows(() -> {
-            builder.add(new UnknownHyperplaneSubset());
-        }, IllegalArgumentException.class);
-    }
-
-    @Test
     public void testToString() {
         // arrange
         GreatCircle circle = GreatCircles.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION);
@@ -461,67 +407,4 @@ public class EmbeddedTreeSubGreatCircleTest {
         SphericalTestUtils.assertPointsEq(start, arc.getStartPoint(), TEST_EPS);
         SphericalTestUtils.assertPointsEq(end, arc.getEndPoint(), TEST_EPS);
     }
-
-    private static class UnknownHyperplaneSubset implements HyperplaneSubset<Point2S> {
-
-        @Override
-        public Split<? extends HyperplaneSubset<Point2S>> split(Hyperplane<Point2S> splitter) {
-            return null;
-        }
-
-        @Override
-        public Hyperplane<Point2S> getHyperplane() {
-            return null;
-        }
-
-        @Override
-        public boolean isFull() {
-            return false;
-        }
-
-        @Override
-        public boolean isEmpty() {
-            return false;
-        }
-
-        @Override
-        public boolean isInfinite() {
-            return false;
-        }
-
-        @Override
-        public boolean isFinite() {
-            return false;
-        }
-
-        @Override
-        public double getSize() {
-            return 0;
-        }
-
-        @Override
-        public RegionLocation classify(Point2S point) {
-            return null;
-        }
-
-        @Override
-        public Point2S closest(Point2S point) {
-            return null;
-        }
-
-        @Override
-        public Builder<Point2S> builder() {
-            return null;
-        }
-
-        @Override
-        public HyperplaneSubset<Point2S> transform(Transform<Point2S> transform) {
-            return null;
-        }
-
-        @Override
-        public List<? extends HyperplaneConvexSubset<Point2S>> toConvex() {
-            return null;
-        }
-    }
 }
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubsetTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubsetTest.java
new file mode 100644
index 0000000..3f99b10
--- /dev/null
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/GreatCircleSubsetTest.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.spherical.twod;
+
+import java.util.List;
+
+import org.apache.commons.geometry.core.GeometryTestUtils;
+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.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
+import org.apache.commons.geometry.spherical.SphericalTestUtils;
+import org.apache.commons.geometry.spherical.oned.AngularInterval;
+import org.apache.commons.geometry.spherical.oned.RegionBSPTree1S;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GreatCircleSubsetTest {
+
+    private static final double TEST_EPS = 1e-10;
+
+    private static final DoublePrecisionContext TEST_PRECISION =
+            new EpsilonDoublePrecisionContext(TEST_EPS);
+
+    private static final GreatCircle XY_CIRCLE = GreatCircles.fromPoleAndU(
+            Vector3D.Unit.PLUS_Z, Vector3D.Unit.PLUS_X, TEST_PRECISION);
+
+    @Test
+    public void testBuilder_empty() {
+        // act
+        HyperplaneSubset.Builder<Point2S> builder = XY_CIRCLE.span().builder();
+
+        GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+        // assert
+        Assert.assertFalse(result.isFull());
+        Assert.assertTrue(result.isEmpty());
+        Assert.assertFalse(result.isInfinite());
+        Assert.assertTrue(result.isFinite());
+
+        Assert.assertEquals(0, result.getSize(), TEST_EPS);
+    }
+
+    @Test
+    public void testBuilder_addSingleConvex_returnsSameInstance() {
+        // arrange
+        GreatArc convex = XY_CIRCLE.arc(0, 1);
+
+        // act
+        HyperplaneSubset.Builder<Point2S> builder = XY_CIRCLE.span().builder();
+
+        builder.add(convex);
+
+        GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+        // assert
+        Assert.assertSame(convex, result);
+    }
+
+    @Test
+    public void testBuilder_addSingleTreeSubset() {
+        // arrange
+        GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.PLUS_J, TEST_PRECISION);
+
+        RegionBSPTree1S region = RegionBSPTree1S.empty();
+        region.add(AngularInterval.of(PlaneAngleRadians.PI, 1.5 * PlaneAngleRadians.PI, TEST_PRECISION));
+        region.add(AngularInterval.of(0, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION));
+
+        EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle, region);
+
+        // act
+        HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+        builder.add(sub);
+
+        GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+        // assert
+        Assert.assertNotSame(sub, result);
+
+        List<GreatArc> arcs = result.toConvex();
+
+        Assert.assertEquals(2, arcs.size());
+        checkArc(arcs.get(0), Point2S.MINUS_K, Point2S.PLUS_J);
+        checkArc(arcs.get(1), Point2S.PLUS_K, Point2S.MINUS_J);
+    }
+
+    @Test
+    public void testBuilder_addMixed_convexFirst() {
+        // arrange
+        GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+
+        RegionBSPTree1S region = RegionBSPTree1S.empty();
+        region.add(AngularInterval.of(PlaneAngleRadians.PI, 1.25 * PlaneAngleRadians.PI, TEST_PRECISION));
+        region.add(AngularInterval.of(0.25 * PlaneAngleRadians.PI, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION));
+
+        EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle, region);
+
+        // act
+        HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+        builder.add(circle.arc(1.5 * PlaneAngleRadians.PI, 0.25 * PlaneAngleRadians.PI));
+        builder.add(circle.arc(1.6 * PlaneAngleRadians.PI, 0.2 * PlaneAngleRadians.PI));
+        builder.add(sub);
+
+        GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+        // assert
+        List<GreatArc> arcs = result.toConvex();
+
+        Assert.assertEquals(2, arcs.size());
+        checkArc(arcs.get(0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0.25 * PlaneAngleRadians.PI));
+        checkArc(arcs.get(1), Point2S.PLUS_J, Point2S.MINUS_J);
+    }
+
+    @Test
+    public void testBuilder_addMixed_treeSubsetFirst() {
+        // arrange
+        GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+
+        RegionBSPTree1S region = RegionBSPTree1S.empty();
+        region.add(AngularInterval.of(PlaneAngleRadians.PI, 1.25 * PlaneAngleRadians.PI, TEST_PRECISION));
+        region.add(AngularInterval.of(0.25 * PlaneAngleRadians.PI, PlaneAngleRadians.PI_OVER_TWO, TEST_PRECISION));
+
+        EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle, region);
+
+        // act
+        HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+        builder.add(sub);
+        builder.add(circle.arc(1.5 * PlaneAngleRadians.PI, 0.25 * PlaneAngleRadians.PI));
+
+        GreatCircleSubset result = (GreatCircleSubset) builder.build();
+
+        // assert
+        List<GreatArc> arcs = result.toConvex();
+
+        Assert.assertEquals(2, arcs.size());
+        checkArc(arcs.get(0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0), Point2S.of(PlaneAngleRadians.PI_OVER_TWO, 0.25 * PlaneAngleRadians.PI));
+        checkArc(arcs.get(1), Point2S.PLUS_J, Point2S.MINUS_J);
+    }
+
+    @Test
+    public void testBuilder_nullArguments() {
+     // arrange
+        GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+
+        HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add((HyperplaneSubset<Point2S>) null);
+        }, NullPointerException.class);
+
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add((HyperplaneConvexSubset<Point2S>) null);
+        }, NullPointerException.class, "Hyperplane subset must not be null");
+    }
+
+    @Test
+    public void testBuilder_argumentsFromDifferentGreatCircle() {
+        // arrange
+        GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+        GreatCircle otherCircle = GreatCircles.fromPoints(Point2S.PLUS_I, Point2S.PLUS_J, TEST_PRECISION);
+
+        EmbeddedTreeGreatCircleSubset sub = new EmbeddedTreeGreatCircleSubset(circle);
+
+        HyperplaneSubset.Builder<Point2S> builder = sub.builder();
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(otherCircle.span());
+        }, IllegalArgumentException.class);
+
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(new EmbeddedTreeGreatCircleSubset(otherCircle));
+        }, IllegalArgumentException.class);
+
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(new UnknownHyperplaneSubset());
+        }, IllegalArgumentException.class);
+    }
+
+    @Test
+    public void testBuilder_unknownSubsetType() {
+     // arrange
+        GreatCircle circle = GreatCircles.fromPoints(Point2S.MINUS_K, Point2S.MINUS_J, TEST_PRECISION);
+
+        HyperplaneSubset.Builder<Point2S> builder = circle.span().builder();
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            builder.add(new UnknownHyperplaneSubset());
+        }, IllegalArgumentException.class);
+    }
+
+    private static void checkArc(GreatArc arc, Point2S start, Point2S end) {
+        SphericalTestUtils.assertPointsEq(start, arc.getStartPoint(), TEST_EPS);
+        SphericalTestUtils.assertPointsEq(end, arc.getEndPoint(), TEST_EPS);
+    }
+
+    private static class UnknownHyperplaneSubset implements HyperplaneSubset<Point2S> {
+
+        @Override
+        public Split<? extends HyperplaneSubset<Point2S>> split(Hyperplane<Point2S> splitter) {
+            return null;
+        }
+
+        @Override
+        public Hyperplane<Point2S> getHyperplane() {
+            return null;
+        }
+
+        @Override
+        public boolean isFull() {
+            return false;
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return false;
+        }
+
+        @Override
+        public boolean isInfinite() {
+            return false;
+        }
+
+        @Override
+        public boolean isFinite() {
+            return false;
+        }
+
+        @Override
+        public double getSize() {
+            return 0;
+        }
+
+        @Override
+        public RegionLocation classify(Point2S point) {
+            return null;
+        }
+
+        @Override
+        public Point2S closest(Point2S point) {
+            return null;
+        }
+
+        @Override
+        public Builder<Point2S> builder() {
+            return null;
+        }
+
+        @Override
+        public HyperplaneSubset<Point2S> transform(Transform<Point2S> transform) {
+            return null;
+        }
+
+        @Override
+        public List<? extends HyperplaneConvexSubset<Point2S>> toConvex() {
+            return null;
+        }
+    }
+}