You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2020/01/21 09:32:07 UTC

[commons-geometry] branch master updated: GEOMETRY-80: simplifying Transform class hierarchy; improving documentation

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 56df5b5  GEOMETRY-80: simplifying Transform class hierarchy; improving documentation
     new f8127fc  Merge branch 'GEOMETRY-80__Matt'
56df5b5 is described below

commit 56df5b50dfd7eba1ba2377f9a7a92242a082be98
Author: Matt Juntunen <ma...@hotmail.com>
AuthorDate: Mon Jan 20 10:44:14 2020 -0500

    GEOMETRY-80: simplifying Transform class hierarchy; improving documentation
---
 .../org/apache/commons/geometry/core/Region.java   |   5 +-
 .../apache/commons/geometry/core/Transform.java    |  53 +++---
 .../core/partition/test/TestTransform2D.java       |   6 +
 .../euclidean/AbstractAffineTransformMatrix.java   |  38 ----
 .../geometry/euclidean/EuclideanTransform.java     |   6 +-
 .../geometry/euclidean/internal/Matrices.java      |  39 +++++
 .../euclidean/oned/AffineTransformMatrix1D.java    |  54 ++++--
 .../euclidean/oned/FunctionTransform1D.java        |  95 ----------
 .../geometry/euclidean/oned/Transform1D.java       |  50 ------
 .../commons/geometry/euclidean/oned/Vector1D.java  |   2 +-
 .../euclidean/threed/AffineTransformMatrix3D.java  |  59 +++++--
 .../euclidean/threed/FunctionTransform3D.java      | 107 ------------
 .../euclidean/threed/SphericalCoordinates.java     |   2 +-
 .../geometry/euclidean/threed/Transform3D.java     |  50 ------
 .../threed/rotation/QuaternionRotation.java        |   6 +-
 .../euclidean/threed/rotation/Rotation3D.java      |  15 +-
 .../euclidean/twod/AffineTransformMatrix2D.java    |  55 ++++--
 .../euclidean/twod/FunctionTransform2D.java        | 103 -----------
 .../commons/geometry/euclidean/twod/Segment.java   |   4 +-
 .../geometry/euclidean/twod/Transform2D.java       |  50 ------
 .../euclidean/DocumentationExamplesTest.java       |   9 +-
 .../geometry/euclidean/internal/MatricesTest.java  |  53 ++++++
 .../oned/AffineTransformMatrix1DTest.java          |  47 +++--
 .../euclidean/oned/FunctionTransform1DTest.java    | 177 -------------------
 .../geometry/euclidean/oned/IntervalTest.java      |   4 +-
 .../geometry/euclidean/oned/OrientedPointTest.java |   2 +-
 .../euclidean/oned/RegionBSPTree1DTest.java        |   8 +-
 .../threed/AffineTransformMatrix3DTest.java        |  58 +++++--
 .../geometry/euclidean/threed/FacetTest.java       |   2 +-
 .../euclidean/threed/FunctionTransform3DTest.java  | 193 ---------------------
 .../geometry/euclidean/threed/Line3DTest.java      |   6 +-
 .../geometry/euclidean/threed/PlaneTest.java       |   6 +-
 .../geometry/euclidean/threed/SubLine3DTest.java   |   2 +-
 .../twod/AbstractSegmentConnectorTest.java         |   2 +-
 .../twod/AffineTransformMatrix2DTest.java          |  54 ++++--
 .../geometry/euclidean/twod/ConvexAreaTest.java    |   2 +-
 .../euclidean/twod/FunctionTransform2DTest.java    | 186 --------------------
 .../twod/InteriorAngleSegmentConnectorTest.java    |   2 +-
 .../geometry/euclidean/twod/PolylineTest.java      |   6 +-
 .../euclidean/twod/RegionBSPTree2DTest.java        |   4 +-
 .../geometry/euclidean/twod/SegmentTest.java       |  41 ++---
 .../geometry/spherical/oned/Transform1S.java       |   6 +-
 .../geometry/spherical/twod/Transform2S.java       |   6 +-
 src/site/xdoc/index.xml                            |   4 +-
 src/site/xdoc/userguide/index.xml                  |  69 ++++----
 45 files changed, 460 insertions(+), 1288 deletions(-)

diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Region.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Region.java
index 2c15111..16da017 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Region.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Region.java
@@ -49,8 +49,9 @@ public interface Region<P extends Point<P>> {
      */
     double getBoundarySize();
 
-    /** Get the barycenter of the region or null if no single, unique barycenter exists.
-     * A barycenter will not exist for empty or infinite regions.
+    /** Get the barycenter of the region or null if no barycenter exists or
+     * one exists but is not unique. A barycenter will not exist for empty or
+     * infinite regions.
      * @return the barycenter of the region or null if none exists
      */
     P getBarycenter();
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Transform.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Transform.java
index 0dc1d12..a34a69a 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Transform.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Transform.java
@@ -18,35 +18,48 @@ package org.apache.commons.geometry.core;
 
 import java.util.function.UnaryOperator;
 
-/** This interface represents an <em>inversible affine transform</em> in a space.
- * Common examples of this type of transform in Euclidean space include
- * scalings, translations, and rotations.
+/** Interface representing geometric transforms in a space, i.e. mappings from points to points.
+ * Implementations <em>must</em> fulfill a set of requirements, listed below, that preserve the
+ * consistency of partitionings on the space. Transforms that do not meet these requirements, while
+ * potentially valid mathematically, cannot be expected to produce correct results with algorithms
+ * that use this interface.
  *
- * <h2>Implementation Note</h2>
- * <p>Implementations are responsible for ensuring that they meet the geometric
- * requirements outlined above. These are:
  * <ol>
- *      <li>The transform must be <a href="https://en.wikipedia.org/wiki/Affine_transformation">affine</a>.
- *      In basic terms, this means that the transform must retain the "straightness" and "parallelness" of
- *      lines and planes (or whatever is an equivalent concept for the space). For example, a translation or
- *      rotation in Euclidean 3D space meets this requirement because all lines that are parallel before the
- *      transform remain parallel afterwards. However, a projective transform that causes previously parallel
- *      lines to meet at a single point does not.
- *      </li>
- *      <li>The transform must be <em>inversible</em>. An inverse transform must exist that will return
- *      the original point if given the transformed point. In other words, for a transform {@code t}, there
- *      must exist an inverse {@code inv} such that {@code inv.apply(t.apply(pt))} returns a point equal to
- *      the input point {@code pt}.
+ *      <li>Transforms must represent functions that are <em>one-to-one</em> and <em>onto</em> (i.e.
+ *      <a href="https://en.wikipedia.org/wiki/Bijection">bijections</a>). This means that every point
+ *      in the space must be mapped to exactly one other point in the space. This also implies that the
+ *      function is invertible.</li>
+ *      <li>Transforms must preserve <a href="https://en.wikipedia.org/wiki/Collinearity">collinearity</a>.
+ *      This means that if a set of points lie on a common hyperplane before the transform, then they must
+ *      also lie on a common hyperplane after the transform. For example, if the Euclidean 2D points {@code a},
+ *      {@code b}, and {@code c} lie on line {@code L}, then the transformed points {@code a'}, {@code b'}, and
+ *      {@code c'} must lie on line {@code L'}, where {@code L'} is the transformed form of the line.</li>
+ *      <li>Transforms must preserve the concept of
+ *      <a href="https://en.wikipedia.org/wiki/Parallel_(geometry)">parallelism</a> defined for the space.
+ *      This means that hyperplanes that are parallel before the transformation must remain parallel afterwards,
+ *      and hyperplanes that intersect must also intersect afterwards. For example, a transform that causes parallel
+ *      lines to converge to a single point in Euclidean space (such as the projective transforms used to create
+ *      perspective viewpoints in 3D graphics) would not meet this requirement. However, a transform that turns
+ *      a square into a rhombus with no right angles would fulfill the requirement, since the two pairs of parallel
+ *      lines forming the square remain parallel after the transformation.
  *      </li>
  * </ol>
- * Implementations that do not meet these requirements cannot be expected to produce correct results in
- * algorithms that use this interface.
+ *
+ * <p>Transforms that meet the above requirements in Euclidean space (and other affine spaces) are known as
+ * <a href="https://en.wikipedia.org/wiki/Affine_transformation">affine transforms</a>. Common affine transforms
+ * include translation, scaling, rotation, reflection, and any compositions thereof.
+ * </p>
  *
  * @param <P> Point implementation type
- * @see <a href="https://en.wikipedia.org/wiki/Affine_transformation">Affine Space</a>
+ * @see <a href="https://en.wikipedia.org/wiki/Geometric_transformation">Geometric Transformation</a>
  */
 public interface Transform<P extends Point<P>> extends UnaryOperator<P> {
 
+    /** Get an instance representing the inverse transform.
+     * @return an instance representing the inverse transform
+     */
+    Transform<P> inverse();
+
     /** Return true if the transform preserves the orientation of the space.
      * For example, in Euclidean 2D space, this will be true for translations,
      * rotations, and scalings but will be false for reflections.
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestTransform2D.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestTransform2D.java
index 0321da2..738e90b 100644
--- a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestTransform2D.java
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/partition/test/TestTransform2D.java
@@ -57,4 +57,10 @@ public class TestTransform2D implements Transform<TestPoint2D> {
     public boolean preservesOrientation() {
         return preservesHandedness;
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public Transform<TestPoint2D> inverse() {
+        throw new UnsupportedOperationException();
+    }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/AbstractAffineTransformMatrix.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/AbstractAffineTransformMatrix.java
index 476d11a..9674517 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/AbstractAffineTransformMatrix.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/AbstractAffineTransformMatrix.java
@@ -16,8 +16,6 @@
  */
 package org.apache.commons.geometry.euclidean;
 
-import org.apache.commons.geometry.euclidean.internal.Vectors;
-
 /** Base class for affine transform matrices in Euclidean space.
  *
  * @param <V> Vector/point implementation type defining the space.
@@ -49,40 +47,4 @@ public abstract class AbstractAffineTransformMatrix<V extends EuclideanVector<V>
     public boolean preservesOrientation() {
         return determinant() > 0.0;
     }
-
-    /** Get the determinant of the instance for use in calculating the matrix
-     * inverse. An {@link IllegalStateException} is thrown if the determinant is
-     * NaN, infinite, or zero.
-     * @return the determinant of the matrix
-     * @throws IllegalStateException if the matrix determinant value is NaN, infinite,
-     *      or zero
-     */
-    protected double getDeterminantForInverse() {
-        final double det = determinant();
-        if (!Vectors.isRealNonZero(det)) {
-            throw nonInvertibleTransform("matrix determinant is " + det);
-        }
-        return det;
-    }
-
-    /** Check that the given matrix element is valid for use in calculation of
-     * a matrix inverse, throwing an {@link IllegalStateException} if not.
-     * @param element matrix entry to check
-     * @throws IllegalStateException if the element is not valid for use
-     *      in calculating a matrix inverse, ie if it is NaN or infinite.
-     */
-    protected void validateElementForInverse(final double element) {
-        if (!Double.isFinite(element)) {
-            throw nonInvertibleTransform("invalid matrix element: " + element);
-        }
-    }
-
-    /** Create an exception indicating that the instance is not able to be inverted.
-     * @param msg message containing the specific reason that the matrix cannot
-     *      be inverted
-     * @return IllegalStateException containing the given error message
-     */
-    protected IllegalStateException nonInvertibleTransform(final String msg) {
-        return new IllegalStateException("Transform is not invertible; " + msg);
-    }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanTransform.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanTransform.java
index 0a76f37..b9ab17c 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanTransform.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanTransform.java
@@ -18,10 +18,12 @@ package org.apache.commons.geometry.euclidean;
 
 import org.apache.commons.geometry.core.Transform;
 
-/** Extension transform interface for Euclidean space. This interface provides an additional method
- * for transforming vectors as opposed to points.
+/** Interface representing affine transforms in Euclidean space. An affine transform is one that preserves
+ * points, straight lines, planes, and sets of parallel lines. Common affine transforms include translation,
+ * rotation, scaling, reflection and any compositions thereof.
  *
  * @param <V> Vector implementation type
+ * @see <a href="https://en.wikipedia.org/wiki/Affine_transformation">Affine Transformation</a>
  */
 public interface EuclideanTransform<V extends EuclideanVector<V>> extends Transform<V> {
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Matrices.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Matrices.java
index 51ff088..6e09e7e 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Matrices.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Matrices.java
@@ -59,4 +59,43 @@ public final class Matrices {
         return ((a00 * a11 * a22) + (a01 * a12 * a20) + (a02 * a10 * a21)) -
                 ((a00 * a12 * a21) + (a01 * a10 * a22) + (a02 * a11 * a20));
     }
+
+    /** Check that the given determinant is valid for use in calculating a matrix
+     * inverse. An {@link IllegalStateException} is thrown if the determinant is
+     * NaN, infinite, or zero.
+     * @param det the determinant to check
+     * @return the checked determinant
+     * @throws IllegalStateException if the matrix determinant value is NaN, infinite,
+     *      or zero
+     */
+    public static double checkDeterminantForInverse(double det) {
+        if (!Vectors.isRealNonZero(det)) {
+            throw nonInvertibleTransform("matrix determinant is " + det);
+        }
+        return det;
+    }
+
+    /** Check that the given matrix element is valid for use in calculation of
+     * a matrix inverse, throwing an {@link IllegalStateException} if not.
+     * @param element matrix entry to check
+     * @return the checked element
+     * @throws IllegalStateException if the element is not valid for use
+     *      in calculating a matrix inverse, ie if it is NaN or infinite.
+     */
+    public static double checkElementForInverse(final double element) {
+        if (!Double.isFinite(element)) {
+            throw nonInvertibleTransform("invalid matrix element: " + element);
+        }
+
+        return element;
+    }
+
+    /** Create an exception indicating that a matrix is not able to be inverted.
+     * @param msg message containing the specific reason that the matrix cannot
+     *      be inverted
+     * @return IllegalStateException containing the given error message
+     */
+    private static IllegalStateException nonInvertibleTransform(final String msg) {
+        return new IllegalStateException("Matrix is not invertible; " + msg);
+    }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/AffineTransformMatrix1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/AffineTransformMatrix1D.java
index 13d9255..66cfd36 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/AffineTransformMatrix1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/AffineTransformMatrix1D.java
@@ -16,9 +16,12 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
+import java.util.function.UnaryOperator;
+
 import org.apache.commons.geometry.core.internal.DoubleFunction1N;
 import org.apache.commons.geometry.euclidean.AbstractAffineTransformMatrix;
-import org.apache.commons.numbers.core.Precision;
+import org.apache.commons.geometry.euclidean.internal.Matrices;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 
 /** Class using a matrix to represent affine transformations in 1 dimensional Euclidean space.
 *
@@ -28,8 +31,7 @@ import org.apache.commons.numbers.core.Precision;
 * use arrays containing 2 elements, instead of 4.
 * </p>
 */
-public final class AffineTransformMatrix1D extends AbstractAffineTransformMatrix<Vector1D>
-    implements Transform1D {
+public final class AffineTransformMatrix1D extends AbstractAffineTransformMatrix<Vector1D> {
     /** The number of internal matrix elements. */
     private static final int NUM_ELEMENTS = 2;
 
@@ -110,15 +112,6 @@ public final class AffineTransformMatrix1D extends AbstractAffineTransformMatrix
         return m00;
     }
 
-    /** {@inheritDoc}
-     *
-     * <p>This simply returns the current instance.</p>
-     */
-    @Override
-    public AffineTransformMatrix1D toMatrix() {
-        return this;
-    }
-
     /** Get a new transform containing the result of applying a translation logically after
      * the transformation represented by the current instance. This is achieved by
      * creating a new translation transform and pre-multiplying it with the current
@@ -205,15 +198,16 @@ public final class AffineTransformMatrix1D extends AbstractAffineTransformMatrix
         return multiply(m, this);
     }
 
-    /** Get a new transform representing the inverse of the current instance.
-     * @return inverse transform
+    /** {@inheritDoc}
+     *
      * @throws IllegalStateException if the transform matrix cannot be inverted
      */
+    @Override
     public AffineTransformMatrix1D inverse() {
 
-        final double det = getDeterminantForInverse();
+        final double det = Matrices.checkDeterminantForInverse(determinant());
 
-        validateElementForInverse(m01);
+        Matrices.checkElementForInverse(m01);
 
         final double invDet = 1.0 / det;
 
@@ -251,8 +245,8 @@ public final class AffineTransformMatrix1D extends AbstractAffineTransformMatrix
         }
         final AffineTransformMatrix1D other = (AffineTransformMatrix1D) obj;
 
-        return Precision.equals(this.m00, other.m00) &&
-                Precision.equals(this.m01, other.m01);
+        return Double.compare(this.m00, other.m00) == 0 &&
+                Double.compare(this.m01, other.m01) == 0;
     }
 
     /** {@inheritDoc} */
@@ -300,6 +294,30 @@ public final class AffineTransformMatrix1D extends AbstractAffineTransformMatrix
         return new AffineTransformMatrix1D(arr[0], arr[1]);
     }
 
+    /** Construct a new transform representing the given function. The function is sampled at
+     * the points zero and one and a matrix is created to perform the transformation.
+     * @param fn function to create a transform matrix from
+     * @return a transform matrix representing the given function
+     * @throws IllegalArgumentException if the given function does not represent a valid
+     *      affine transform
+     */
+    public static AffineTransformMatrix1D from(final UnaryOperator<Vector1D> fn) {
+        final Vector1D tOne = fn.apply(Vector1D.Unit.PLUS);
+        final Vector1D tZero = fn.apply(Vector1D.ZERO);
+
+        final double scale = tOne.subtract(tZero).getX();
+        final double translate = tZero.getX();
+
+        final AffineTransformMatrix1D mat =  AffineTransformMatrix1D.of(scale, translate);
+
+        final double det = mat.determinant();
+        if (!Vectors.isRealNonZero(det)) {
+            throw new IllegalArgumentException("Transform function is invalid: matrix determinant is " + det);
+        }
+
+        return mat;
+    }
+
     /** Get the transform representing the identity matrix. This transform does not
      * modify point or vector values when applied.
      * @return transform representing the identity matrix
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/FunctionTransform1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/FunctionTransform1D.java
deleted file mode 100644
index e63cc43..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/FunctionTransform1D.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.oned;
-
-import java.util.function.UnaryOperator;
-
-/** Class that wraps a {@link UnaryOperator} with the {@link Transform1D} interface.
- */
-final class FunctionTransform1D implements Transform1D {
-
-    /** Static instance representing the identity transform. */
-    private static final FunctionTransform1D IDENTITY =
-            new FunctionTransform1D(UnaryOperator.identity(), true, Vector1D.ZERO);
-
-    /** The underlying function for the transform. */
-    private final UnaryOperator<Vector1D> fn;
-
-    /** True if the transform preserves spatial orientation. */
-    private final boolean preservesOrientation;
-
-    /** The translation component of the transform. */
-    private final Vector1D translation;
-
-    /** Construct a new instance from its component parts. No validation of the input is performed.
-     * @param fn the underlying function for the transform
-     * @param preservesOrientation true if the transform preserves spatial orientation
-     * @param translation the translation component of the transform
-     */
-    private FunctionTransform1D(final UnaryOperator<Vector1D> fn, final boolean preservesOrientation,
-            final Vector1D translation) {
-        this.fn = fn;
-        this.preservesOrientation = preservesOrientation;
-        this.translation = translation;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Vector1D apply(final Vector1D pt) {
-        return fn.apply(pt);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Vector1D applyVector(final Vector1D vec) {
-        return apply(vec).subtract(translation);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean preservesOrientation() {
-        return preservesOrientation;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public AffineTransformMatrix1D toMatrix() {
-        final Vector1D tOne = applyVector(Vector1D.Unit.PLUS);
-
-        return AffineTransformMatrix1D.of(tOne.getX(), translation.getX());
-    }
-
-    /** Return an instance representing the identity transform.
-     * @return an instance representing the identity transform
-     */
-    static FunctionTransform1D identity() {
-        return IDENTITY;
-    }
-
-    /** Construct a new transform instance from the given function.
-     * @param fn the function to use for the transform
-     * @return a new transform instance using the given function
-     */
-    static FunctionTransform1D from(final UnaryOperator<Vector1D> fn) {
-        final Vector1D tOne = fn.apply(Vector1D.Unit.PLUS);
-        final Vector1D tZero = fn.apply(Vector1D.ZERO);
-
-        final boolean preservesOrientation = tOne.getX() > 0.0;
-
-        return new FunctionTransform1D(fn, preservesOrientation, tZero);
-    }
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Transform1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Transform1D.java
deleted file mode 100644
index a85b4ca..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Transform1D.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.oned;
-
-import java.util.function.UnaryOperator;
-
-import org.apache.commons.geometry.euclidean.EuclideanTransform;
-
-/** Extension of the {@link EuclideanTransform} interface for 1D space.
- */
-public interface Transform1D extends EuclideanTransform<Vector1D> {
-
-    /** Return an affine transform matrix representing the same transform
-     * as this instance.
-     * @return an affine tranform matrix representing the same transform
-     *      as this instance
-     */
-    AffineTransformMatrix1D toMatrix();
-
-    /** Return a transform representing the identity transform.
-     * @return a transform representing the identity transform
-     */
-    static Transform1D identity() {
-        return FunctionTransform1D.identity();
-    }
-
-    /** Construct a transform instance from the given function. Callers are responsible for
-     * ensuring that the given function meets all the requirements for
-     * {@link org.apache.commons.geometry.core.Transform Transform} instances.
-     * @param fn the function to use for the transform
-     * @return a new transform instance using the given function
-     */
-    static Transform1D from(final UnaryOperator<Vector1D> fn) {
-        return FunctionTransform1D.from(fn);
-    }
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
index ba05189..0206f7d 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
@@ -19,11 +19,11 @@ package org.apache.commons.geometry.euclidean.oned;
 import java.util.Comparator;
 import java.util.function.UnaryOperator;
 
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents vectors and points in one-dimensional Euclidean space.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3D.java
index bbcef53..a0fcf21 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3D.java
@@ -16,9 +16,12 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import java.util.function.UnaryOperator;
+
 import org.apache.commons.geometry.core.internal.DoubleFunction3N;
 import org.apache.commons.geometry.euclidean.AbstractAffineTransformMatrix;
 import org.apache.commons.geometry.euclidean.internal.Matrices;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
 import org.apache.commons.numbers.arrays.LinearCombination;
 import org.apache.commons.numbers.core.Precision;
@@ -31,8 +34,7 @@ import org.apache.commons.numbers.core.Precision;
  * use arrays containing 12 elements, instead of 16.
  * </p>
  */
-public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix<Vector3D>
-    implements Transform3D {
+public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix<Vector3D> {
     /** The number of internal matrix elements. */
     private static final int NUM_ELEMENTS = 12;
 
@@ -204,15 +206,6 @@ public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix
             );
     }
 
-    /** {@inheritDoc}
-    *
-    * <p>This simply returns the current instance.</p>
-    */
-    @Override
-    public AffineTransformMatrix3D toMatrix() {
-        return this;
-    }
-
     /** Apply a translation to the current instance, returning the result as a new transform.
      * @param translation vector containing the translation values for each axis
      * @return a new transform containing the result of applying a translation to
@@ -323,21 +316,22 @@ public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix
         return multiply(m, this);
     }
 
-    /** Get a new transform representing the inverse of the current instance.
-     * @return inverse transform
-     * @throws IllegalStateException if the transform matrix cannot be inverted
-     */
+    /** {@inheritDoc}
+    *
+    * @throws IllegalStateException if the transform matrix cannot be inverted
+    */
+    @Override
     public AffineTransformMatrix3D inverse() {
 
         // Our full matrix is 4x4 but we can significantly reduce the amount of computations
         // needed here since we know that our last row is [0 0 0 1].
 
-        final double det = getDeterminantForInverse();
+        final double det = Matrices.checkDeterminantForInverse(determinant());
 
         // validate the remaining matrix elements that were not part of the determinant
-        validateElementForInverse(m03);
-        validateElementForInverse(m13);
-        validateElementForInverse(m23);
+        Matrices.checkElementForInverse(m03);
+        Matrices.checkElementForInverse(m13);
+        Matrices.checkElementForInverse(m23);
 
         // compute the necessary elements of the cofactor matrix
         // (we need all but the last column)
@@ -505,6 +499,33 @@ public final class AffineTransformMatrix3D extends AbstractAffineTransformMatrix
                 );
     }
 
+    /** Construct a new transform representing the given function. The function is sampled at
+     * the origin and along each axis and a matrix is created to perform the transformation.
+     * @param fn function to create a transform matrix from
+     * @return a transform matrix representing the given function
+     * @throws IllegalArgumentException if the given function does not represent a valid
+     *      affine transform
+     */
+    public static AffineTransformMatrix3D from(final UnaryOperator<Vector3D> fn) {
+        final Vector3D tPlusX = fn.apply(Vector3D.Unit.PLUS_X);
+        final Vector3D tPlusY = fn.apply(Vector3D.Unit.PLUS_Y);
+        final Vector3D tPlusZ = fn.apply(Vector3D.Unit.PLUS_Z);
+        final Vector3D tZero = fn.apply(Vector3D.ZERO);
+
+        final Vector3D u = tPlusX.subtract(tZero);
+        final Vector3D v = tPlusY.subtract(tZero);
+        final Vector3D w = tPlusZ.subtract(tZero);
+
+        final AffineTransformMatrix3D mat =  AffineTransformMatrix3D.fromColumnVectors(u, v, w, tZero);
+
+        final double det = mat.determinant();
+        if (!Vectors.isRealNonZero(det)) {
+            throw new IllegalArgumentException("Transform function is invalid: matrix determinant is " + det);
+        }
+
+        return mat;
+    }
+
     /** Get a new transform create from the given column vectors. The returned transform
      * does not include any translation component.
      * @param u first column vector; this corresponds to the first basis vector
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/FunctionTransform3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/FunctionTransform3D.java
deleted file mode 100644
index 2cc52cd..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/FunctionTransform3D.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.threed;
-
-import java.util.function.UnaryOperator;
-
-import org.apache.commons.geometry.euclidean.internal.Matrices;
-
-/** Class that wraps a {@link UnaryOperator} with the {@link Transform3D} interface.
- */
-final class FunctionTransform3D implements Transform3D {
-
-    /** Static instance representing the identity transform. */
-    private static final FunctionTransform3D IDENTITY =
-            new FunctionTransform3D(UnaryOperator.identity(), true, Vector3D.ZERO);
-
-    /** The underlying function for the transform. */
-    private final UnaryOperator<Vector3D> fn;
-
-    /** True if the transform preserves spatial orientation. */
-    private final boolean preservesOrientation;
-
-    /** The translation component of the transform. */
-    private final Vector3D translation;
-
-    /** Construct a new instance from its component parts. No validation of the input is performed.
-     * @param fn the underlying function for the transform
-     * @param preservesOrientation true if the transform preserves spatial orientation
-     * @param translation the translation component of the transform
-     */
-    private FunctionTransform3D(final UnaryOperator<Vector3D> fn, final boolean preservesOrientation,
-            final Vector3D translation) {
-        this.fn = fn;
-        this.preservesOrientation = preservesOrientation;
-        this.translation = translation;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Vector3D apply(final Vector3D pt) {
-        return fn.apply(pt);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Vector3D applyVector(final Vector3D vec) {
-        return apply(vec).subtract(translation);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean preservesOrientation() {
-        return preservesOrientation;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public AffineTransformMatrix3D toMatrix() {
-        final Vector3D u = applyVector(Vector3D.Unit.PLUS_X);
-        final Vector3D v = applyVector(Vector3D.Unit.PLUS_Y);
-        final Vector3D w = applyVector(Vector3D.Unit.PLUS_Z);
-
-        return AffineTransformMatrix3D.fromColumnVectors(u, v, w, translation);
-    }
-
-    /** Return an instance representing the identity transform.
-     * @return an instance representing the identity transform
-     */
-    static FunctionTransform3D identity() {
-        return IDENTITY;
-    }
-
-    /** Construct a new transform instance from the given function.
-     * @param fn the function to use for the transform
-     * @return a new transform instance using the given function
-     */
-    static FunctionTransform3D from(final UnaryOperator<Vector3D> fn) {
-        final Vector3D tPlusX = fn.apply(Vector3D.Unit.PLUS_X);
-        final Vector3D tPlusY = fn.apply(Vector3D.Unit.PLUS_Y);
-        final Vector3D tPlusZ = fn.apply(Vector3D.Unit.PLUS_Z);
-
-        final Vector3D tZero = fn.apply(Vector3D.ZERO);
-
-        final double det = Matrices.determinant(
-                tPlusX.getX(), tPlusY.getX(), tPlusZ.getX(),
-                tPlusX.getY(), tPlusY.getY(), tPlusZ.getY(),
-                tPlusX.getZ(), tPlusY.getZ(), tPlusZ.getZ()
-            );
-        final boolean preservesOrientation = det > 0;
-
-        return new FunctionTransform3D(fn, preservesOrientation, tZero);
-    }
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates.java
index 39acef9..cac6182 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates.java
@@ -16,11 +16,11 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.apache.commons.geometry.core.Spatial;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.geometry.euclidean.twod.PolarCoordinates;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
 
 /** Class representing <a href="https://en.wikipedia.org/wiki/Spherical_coordinate_system">spherical coordinates</a>
  * in 3 dimensional Euclidean space.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Transform3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Transform3D.java
deleted file mode 100644
index 20bbcdb..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Transform3D.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.threed;
-
-import java.util.function.UnaryOperator;
-
-import org.apache.commons.geometry.euclidean.EuclideanTransform;
-
-/** Extension of the {@link EuclideanTransform} interface for 3D points.
- */
-public interface Transform3D extends EuclideanTransform<Vector3D> {
-
-    /** Return an affine transform matrix representing the same transform
-     * as this instance.
-     * @return an affine tranform matrix representing the same transform
-     *      as this instance
-     */
-    AffineTransformMatrix3D toMatrix();
-
-    /** Return a transform representing the identity transform.
-     * @return a transform representing the identity transform
-     */
-    static Transform3D identity() {
-        return FunctionTransform3D.identity();
-    }
-
-    /** Construct a transform instance from the given function. Callers are responsible for
-     * ensuring that the given function meets all the requirements for
-     * {@link org.apache.commons.geometry.core.Transform Transform} instances.
-     * @param fn the function to use for the transform
-     * @return a new transform instance using the given function
-     */
-    static Transform3D from(final UnaryOperator<Vector3D> fn) {
-        return FunctionTransform3D.from(fn);
-    }
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java
index 460852e..dc351ed 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.java
@@ -165,8 +165,10 @@ public final class QuaternionRotation implements Rotation3D {
         return true;
     }
 
-    /** {@inheritDoc} */
-    @Override
+    /** Return an {@link AffineTransformMatrix3D} representing the same rotation as this
+     * instance.
+     * @return a transform matrix representing the same rotation as this instance
+     */
     public AffineTransformMatrix3D toMatrix() {
 
         final double qw = quat.getW();
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/Rotation3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/Rotation3D.java
index 395f9f0..6b56212 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/Rotation3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/rotation/Rotation3D.java
@@ -16,13 +16,13 @@
  */
 package org.apache.commons.geometry.euclidean.threed.rotation;
 
-import org.apache.commons.geometry.euclidean.threed.Transform3D;
+import org.apache.commons.geometry.euclidean.EuclideanTransform;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 
 /** Interface representing a generic rotation in 3-dimensional Euclidean
  * space.
  */
-public interface Rotation3D extends Transform3D {
+public interface Rotation3D extends EuclideanTransform<Vector3D> {
 
     /** Apply this rotation to the given argument. Since rotations do
      * not affect vector magnitudes, this method can be applied to
@@ -33,6 +33,12 @@ public interface Rotation3D extends Transform3D {
     @Override
     Vector3D apply(Vector3D vec);
 
+    /** Get the inverse rotation.
+     * @return the inverse rotation.
+     */
+    @Override
+    Rotation3D inverse();
+
     /** Get the axis of rotation as a normalized {@link Vector3D}.
      *
      * <p>All 3-dimensional rotations and sequences of rotations can be reduced
@@ -53,9 +59,4 @@ public interface Rotation3D extends Transform3D {
      * @see #getAxis()
      */
     double getAngle();
-
-    /** Get the inverse rotation.
-     * @return the inverse rotation.
-     */
-    Rotation3D inverse();
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2D.java
index df0ad11..a311fa2 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2D.java
@@ -16,9 +16,12 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import java.util.function.UnaryOperator;
+
 import org.apache.commons.geometry.core.internal.DoubleFunction2N;
 import org.apache.commons.geometry.euclidean.AbstractAffineTransformMatrix;
 import org.apache.commons.geometry.euclidean.internal.Matrices;
+import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.numbers.arrays.LinearCombination;
 import org.apache.commons.numbers.core.Precision;
 
@@ -30,8 +33,7 @@ import org.apache.commons.numbers.core.Precision;
 * use arrays containing 6 elements, instead of 9.
 * </p>
 */
-public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix<Vector2D>
-    implements Transform2D {
+public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix<Vector2D> {
     /** The number of internal matrix elements. */
     private static final int NUM_ELEMENTS = 6;
 
@@ -168,15 +170,6 @@ public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix
             );
     }
 
-    /** {@inheritDoc}
-    *
-    * <p>This simply returns the current instance.</p>
-    */
-    @Override
-    public AffineTransformMatrix2D toMatrix() {
-        return this;
-    }
-
     /** Apply a translation to the current instance, returning the result as a new transform.
      * @param translation vector containing the translation values for each axis
      * @return a new transform containing the result of applying a translation to
@@ -282,20 +275,21 @@ public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix
         return multiply(m, this);
     }
 
-    /** Get a new transform representing the inverse of the current instance.
-     * @return inverse transform
-     * @throws IllegalStateException if the transform matrix cannot be inverted
-     */
+    /** {@inheritDoc}
+    *
+    * @throws IllegalStateException if the transform matrix cannot be inverted
+    */
+    @Override
     public AffineTransformMatrix2D inverse() {
 
         // Our full matrix is 3x3 but we can significantly reduce the amount of computations
         // needed here since we know that our last row is [0 0 1].
 
-        final double det = getDeterminantForInverse();
+        final double det = Matrices.checkDeterminantForInverse(determinant());
 
         // validate the remaining matrix elements that were not part of the determinant
-        validateElementForInverse(m02);
-        validateElementForInverse(m12);
+        Matrices.checkElementForInverse(m02);
+        Matrices.checkElementForInverse(m12);
 
         // compute the necessary elements of the cofactor matrix
         // (we need all but the last column)
@@ -416,6 +410,31 @@ public final class AffineTransformMatrix2D extends AbstractAffineTransformMatrix
                 );
     }
 
+    /** Construct a new transform representing the given function. The function is sampled at
+     * the origin and along each axis and a matrix is created to perform the transformation.
+     * @param fn function to create a transform matrix from
+     * @return a transform matrix representing the given function
+     * @throws IllegalArgumentException if the given function does not represent a valid
+     *      affine transform
+     */
+    public static AffineTransformMatrix2D from(final UnaryOperator<Vector2D> fn) {
+        final Vector2D tPlusX = fn.apply(Vector2D.Unit.PLUS_X);
+        final Vector2D tPlusY = fn.apply(Vector2D.Unit.PLUS_Y);
+        final Vector2D tZero = fn.apply(Vector2D.ZERO);
+
+        final Vector2D u = tPlusX.subtract(tZero);
+        final Vector2D v = tPlusY.subtract(tZero);
+
+        final AffineTransformMatrix2D mat =  AffineTransformMatrix2D.fromColumnVectors(u, v, tZero);
+
+        final double det = mat.determinant();
+        if (!Vectors.isRealNonZero(det)) {
+            throw new IllegalArgumentException("Transform function is invalid: matrix determinant is " + det);
+        }
+
+        return mat;
+    }
+
     /** Get a new transform create from the given column vectors. The returned transform
      * does not include any translation component.
      * @param u first column vector; this corresponds to the first basis vector
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/FunctionTransform2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/FunctionTransform2D.java
deleted file mode 100644
index 03ff639..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/FunctionTransform2D.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.twod;
-
-import java.util.function.UnaryOperator;
-
-import org.apache.commons.geometry.euclidean.internal.Matrices;
-
-/** Class that wraps a {@link UnaryOperator} with the {@link Transform2D} interface.
- */
-final class FunctionTransform2D implements Transform2D {
-
-    /** Static instance representing the identity transform. */
-    private static final FunctionTransform2D IDENTITY =
-            new FunctionTransform2D(UnaryOperator.identity(), true, Vector2D.ZERO);
-
-    /** The underlying function for the transform. */
-    private final UnaryOperator<Vector2D> fn;
-
-    /** True if the transform preserves spatial orientation. */
-    private final boolean preservesOrientation;
-
-    /** The translation component of the transform. */
-    private final Vector2D translation;
-
-    /** Construct a new instance from its component parts. No validation of the input is performed.
-     * @param fn the underlying function for the transform
-     * @param preservesOrientation true if the transform preserves spatial orientation
-     * @param translation the translation component of the transform
-     */
-    private FunctionTransform2D(final UnaryOperator<Vector2D> fn, final boolean preservesOrientation,
-            final Vector2D translation) {
-        this.fn = fn;
-        this.preservesOrientation = preservesOrientation;
-        this.translation = translation;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Vector2D apply(final Vector2D pt) {
-        return fn.apply(pt);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Vector2D applyVector(final Vector2D vec) {
-        return apply(vec).subtract(translation);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean preservesOrientation() {
-        return preservesOrientation;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public AffineTransformMatrix2D toMatrix() {
-        final Vector2D u = applyVector(Vector2D.Unit.PLUS_X);
-        final Vector2D v = applyVector(Vector2D.Unit.PLUS_Y);
-
-        return AffineTransformMatrix2D.fromColumnVectors(u, v, translation);
-    }
-
-    /** Return an instance representing the identity transform.
-     * @return an instance representing the identity transform
-     */
-    static FunctionTransform2D identity() {
-        return IDENTITY;
-    }
-
-    /** Construct a new transform instance from the given function.
-     * @param fn the function to use for the transform
-     * @return a new transform instance using the given function
-     */
-    static FunctionTransform2D from(final UnaryOperator<Vector2D> fn) {
-        final Vector2D tPlusX = fn.apply(Vector2D.Unit.PLUS_X);
-        final Vector2D tPlusY = fn.apply(Vector2D.Unit.PLUS_Y);
-        final Vector2D tZero = fn.apply(Vector2D.ZERO);
-
-        final double det = Matrices.determinant(
-                tPlusX.getX(), tPlusY.getX(),
-                tPlusX.getY(), tPlusY.getY()
-            );
-        final boolean preservesOrientation = det > 0;
-
-        return new FunctionTransform2D(fn, preservesOrientation, tZero);
-    }
-}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java
index 756bdfe..2084714 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Segment.java
@@ -24,8 +24,8 @@ import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.partitioning.Split;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
+import org.apache.commons.geometry.euclidean.oned.AffineTransformMatrix1D;
 import org.apache.commons.geometry.euclidean.oned.Interval;
-import org.apache.commons.geometry.euclidean.oned.Transform1D;
 import org.apache.commons.geometry.euclidean.oned.Vector1D;
 import org.apache.commons.geometry.euclidean.twod.Line.SubspaceTransform;
 
@@ -174,7 +174,7 @@ public final class Segment extends AbstractSubLine
     /** {@inheritDoc} */
     @Override
     public Segment reverse() {
-        final Interval reversedInterval = interval.transform(Transform1D.from(Vector1D::negate));
+        final Interval reversedInterval = interval.transform(AffineTransformMatrix1D.createScale(-1));
         return fromInterval(getLine().reverse(), reversedInterval);
     }
 
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Transform2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Transform2D.java
deleted file mode 100644
index b6c5da6..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Transform2D.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.twod;
-
-import java.util.function.UnaryOperator;
-
-import org.apache.commons.geometry.euclidean.EuclideanTransform;
-
-/** Extension of the {@link EuclideanTransform} interface for 2D space.
- */
-public interface Transform2D extends EuclideanTransform<Vector2D> {
-
-    /** Return an affine transform matrix representing the same transform
-     * as this instance.
-     * @return an affine tranform matrix representing the same transform
-     *      as this instance
-     */
-    AffineTransformMatrix2D toMatrix();
-
-    /** Return a transform representing the identity transform.
-     * @return a transform representing the identity transform
-     */
-    static Transform2D identity() {
-        return FunctionTransform2D.identity();
-    }
-
-    /** Construct a transform instance from the given function. Callers are responsible for
-     * ensuring that the given function meets all the requirements for
-     * {@link org.apache.commons.geometry.core.Transform Transform} instances.
-     * @param fn the function to use for the transform
-     * @return a new transform instance using the given function
-     */
-    static Transform2D from(final UnaryOperator<Vector2D> fn) {
-        return FunctionTransform2D.from(fn);
-    }
-}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/DocumentationExamplesTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/DocumentationExamplesTest.java
index 42f9c98..31eb242 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/DocumentationExamplesTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/DocumentationExamplesTest.java
@@ -33,7 +33,6 @@ import org.apache.commons.geometry.euclidean.threed.Line3D;
 import org.apache.commons.geometry.euclidean.threed.LinecastPoint3D;
 import org.apache.commons.geometry.euclidean.threed.Plane;
 import org.apache.commons.geometry.euclidean.threed.RegionBSPTree3D;
-import org.apache.commons.geometry.euclidean.threed.Transform3D;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
 import org.apache.commons.geometry.euclidean.threed.shapes.Parallelepiped;
@@ -43,7 +42,6 @@ import org.apache.commons.geometry.euclidean.twod.LinecastPoint2D;
 import org.apache.commons.geometry.euclidean.twod.Polyline;
 import org.apache.commons.geometry.euclidean.twod.RegionBSPTree2D;
 import org.apache.commons.geometry.euclidean.twod.Segment;
-import org.apache.commons.geometry.euclidean.twod.Transform2D;
 import org.apache.commons.geometry.euclidean.twod.Vector2D;
 import org.apache.commons.geometry.euclidean.twod.shapes.Parallelogram;
 import org.apache.commons.numbers.angle.PlaneAngleRadians;
@@ -68,10 +66,8 @@ public class DocumentationExamplesTest {
                 Parallelepiped.axisAligned(Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), precision));
 
         // create a rotated copy of the region
-        Transform3D rotation = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, 0.25 * Math.PI);
-
         RegionBSPTree3D copy = region.copy();
-        copy.transform(rotation);
+        copy.transform(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, 0.25 * Math.PI));
 
         // compute the intersection of the regions, storing the result back into the caller
         // (the result could also have been placed into a third region)
@@ -290,8 +286,7 @@ public class DocumentationExamplesTest {
         RegionBSPTree2D copy = tree.copy();
 
         // translate the copy
-        Vector2D translation = Vector2D.of(0.5, 0.5);
-        copy.transform(Transform2D.from(v -> v.add(translation)));
+        copy.transform(AffineTransformMatrix2D.createTranslation(Vector2D.of(0.5, 0.5)));
 
         // compute the union of the regions, storing the result back into the
         // first tree
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/MatricesTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/MatricesTest.java
index a60a1a3..6acb10a 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/MatricesTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/MatricesTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.geometry.euclidean.internal;
 
+import org.apache.commons.geometry.core.GeometryTestUtils;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -80,4 +81,56 @@ public class MatricesTest {
                 -3, 4, 1
                 ), EPS);
     }
+
+    @Test
+    public void testCheckDeterminantForInverse() {
+        // act/assert
+        Assert.assertEquals(1.0, Matrices.checkDeterminantForInverse(1.0), EPS);
+        Assert.assertEquals(-1.0, Matrices.checkDeterminantForInverse(-1.0), EPS);
+    }
+
+    @Test
+    public void testCheckDeterminantForInverse_invalid() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            Matrices.checkDeterminantForInverse(0);
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is 0.0");
+
+        GeometryTestUtils.assertThrows(() -> {
+            Matrices.checkDeterminantForInverse(Double.NaN);
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is NaN");
+
+        GeometryTestUtils.assertThrows(() -> {
+            Matrices.checkDeterminantForInverse(Double.POSITIVE_INFINITY);
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is Infinity");
+
+        GeometryTestUtils.assertThrows(() -> {
+            Matrices.checkDeterminantForInverse(Double.NEGATIVE_INFINITY);
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is -Infinity");
+    }
+
+    @Test
+    public void testCheckElementForInverse() {
+        // act/assert
+        Assert.assertEquals(0.0, Matrices.checkElementForInverse(0.0), EPS);
+
+        Assert.assertEquals(1.0, Matrices.checkElementForInverse(1.0), EPS);
+        Assert.assertEquals(-1.0, Matrices.checkElementForInverse(-1.0), EPS);
+    }
+
+    @Test
+    public void testCheckElementForInverse_invalid() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            Matrices.checkElementForInverse(Double.NaN);
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: NaN");
+
+        GeometryTestUtils.assertThrows(() -> {
+            Matrices.checkElementForInverse(Double.POSITIVE_INFINITY);
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: Infinity");
+
+        GeometryTestUtils.assertThrows(() -> {
+            Matrices.checkElementForInverse(Double.NEGATIVE_INFINITY);
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: -Infinity");
+    }
 }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/AffineTransformMatrix1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/AffineTransformMatrix1DTest.java
index 7b9087e..28b6e98 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/AffineTransformMatrix1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/AffineTransformMatrix1DTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
+import java.util.function.UnaryOperator;
+
 import org.apache.commons.geometry.core.GeometryTestUtils;
 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.numbers.angle.PlaneAngleRadians;
@@ -38,7 +40,6 @@ public class AffineTransformMatrix1DTest {
         Assert.assertArrayEquals(new double[] {1, 2}, result, 0.0);
     }
 
-
     @Test
     public void testOf_invalidDimensions() {
         // act/assert
@@ -47,6 +48,27 @@ public class AffineTransformMatrix1DTest {
     }
 
     @Test
+    public void testFrom() {
+        // act/assert
+        Assert.assertArrayEquals(new double[] {1, 0},
+                AffineTransformMatrix1D.from(UnaryOperator.identity()).toArray(), EPS);
+        Assert.assertArrayEquals(new double[] {1, 2},
+                AffineTransformMatrix1D.from(v -> v.add(Vector1D.of(2))).toArray(), EPS);
+        Assert.assertArrayEquals(new double[] {3, 0},
+                AffineTransformMatrix1D.from(v -> v.multiply(3)).toArray(), EPS);
+        Assert.assertArrayEquals(new double[] {3, 6},
+                AffineTransformMatrix1D.from(v -> v.add(Vector1D.of(2)).multiply(3)).toArray(), EPS);
+    }
+
+    @Test
+    public void testFrom_invalidFunction() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            AffineTransformMatrix1D.from(v -> v.multiply(0));
+        }, IllegalArgumentException.class);
+    }
+
+    @Test
     public void testIdentity() {
         // act
         AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity();
@@ -427,15 +449,6 @@ public class AffineTransformMatrix1DTest {
     }
 
     @Test
-    public void testToMatrix() {
-        // arrange
-        AffineTransformMatrix1D t = AffineTransformMatrix1D.of(1, 1);
-
-        // act/assert
-        Assert.assertSame(t, t.toMatrix());
-    }
-
-    @Test
     public void testMultiply() {
         // arrange
         AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 3);
@@ -599,31 +612,31 @@ public class AffineTransformMatrix1DTest {
         // act/assert
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix1D.of(0, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is 0.0");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is 0.0");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix1D.of(Double.NaN, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is NaN");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is NaN");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix1D.of(Double.NEGATIVE_INFINITY, 0.0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is -Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is -Infinity");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix1D.of(Double.POSITIVE_INFINITY, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is Infinity");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix1D.of(1, Double.NaN).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; invalid matrix element: NaN");
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: NaN");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix1D.of(1, Double.NEGATIVE_INFINITY).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; invalid matrix element: -Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: -Infinity");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix1D.of(1, Double.POSITIVE_INFINITY).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; invalid matrix element: Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: Infinity");
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/FunctionTransform1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/FunctionTransform1DTest.java
deleted file mode 100644
index 01fb3a8..0000000
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/FunctionTransform1DTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.oned;
-
-import java.util.function.UnaryOperator;
-
-import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class FunctionTransform1DTest {
-
-    private static final double TEST_EPS = 1e-15;
-
-    @Test
-    public void testIdentity() {
-        // arrange
-        Vector1D p0 = Vector1D.of(0);
-        Vector1D p1 = Vector1D.of(1);
-        Vector1D p2 = Vector1D.of(-1);
-
-        // act
-        Transform1D t = Transform1D.identity();
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p1, t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p2, t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_identity() {
-        // arrange
-        Vector1D p0 = Vector1D.of(0);
-        Vector1D p1 = Vector1D.of(1);
-        Vector1D p2 = Vector1D.of(-1);
-
-        // act
-        Transform1D t = Transform1D.from(UnaryOperator.identity());
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p1, t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p2, t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_scaleAndTranslate() {
-        // arrange
-        Vector1D p0 = Vector1D.of(0);
-        Vector1D p1 = Vector1D.of(1);
-        Vector1D p2 = Vector1D.of(-1);
-
-        // act
-        Transform1D t = Transform1D.from(v -> Vector1D.of((v.getX() + 2) * 3));
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(6), t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(9), t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(3), t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_reflection() {
-        // arrange
-        Vector1D p0 = Vector1D.of(0);
-        Vector1D p1 = Vector1D.of(1);
-        Vector1D p2 = Vector1D.of(-1);
-
-        // act
-        Transform1D t = Transform1D.from(Vector1D::negate);
-
-        // assert
-        Assert.assertFalse(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p2, t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p1, t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testApply() {
-        // arrange
-        Transform1D t = Transform1D.from(v -> {
-            double x = v.getX();
-            return Vector1D.of((-2 * x) + 1);
-        });
-
-        // act/assert
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(1), t.apply(Vector1D.ZERO), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(-1), t.apply(Vector1D.Unit.PLUS), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(-3), t.apply(Vector1D.of(2)), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(3), t.apply(Vector1D.of(-1)), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(5), t.apply(Vector1D.of(-2)), TEST_EPS);
-    }
-
-    @Test
-    public void testApplyVector() {
-        // arrange
-        Transform1D t = Transform1D.from(v -> {
-            double x = v.getX();
-            return Vector1D.of((-2 * x) + 1);
-        });
-
-        // act/assert
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.ZERO, t.applyVector(Vector1D.ZERO), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(-2), t.applyVector(Vector1D.Unit.PLUS), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(-4), t.applyVector(Vector1D.of(2)), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(2), t.applyVector(Vector1D.of(-1)), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(4), t.applyVector(Vector1D.of(-2)), TEST_EPS);
-    }
-
-    @Test
-    public void testToMatrix() {
-        // act/assert
-        Assert.assertArrayEquals(new double[] {1, 0},
-                Transform1D.identity().toMatrix().toArray(), TEST_EPS);
-        Assert.assertArrayEquals(new double[] {1, 2},
-                Transform1D.from(v -> v.add(Vector1D.of(2))).toMatrix().toArray(), TEST_EPS);
-        Assert.assertArrayEquals(new double[] {3, 0},
-                Transform1D.from(v -> v.multiply(3)).toMatrix().toArray(), TEST_EPS);
-        Assert.assertArrayEquals(new double[] {3, 6},
-                Transform1D.from(v -> v.add(Vector1D.of(2)).multiply(3)).toMatrix().toArray(), TEST_EPS);
-    }
-
-    @Test
-    public void testTransformRoundTrip() {
-        // arrange
-        double eps = 1e-8;
-        double delta = 0.11;
-
-        Vector1D p1 = Vector1D.of(1.1);
-        Vector1D p2 = Vector1D.of(-5);
-        Vector1D vec = p1.vectorTo(p2);
-
-        EuclideanTestUtils.permuteSkipZero(-2, 2, delta, (translate, scale) -> {
-
-            Transform1D t = Transform1D.from(v -> {
-                return v.multiply(scale * 0.5)
-                    .add(Vector1D.of(translate))
-                    .multiply(scale * 1.5);
-            });
-
-            // act
-            Vector1D t1 = t.apply(p1);
-            Vector1D t2 = t.apply(p2);
-            Vector1D tvec = t.applyVector(vec);
-
-            Transform1D inverse = t.toMatrix().inverse();
-
-            // assert
-            EuclideanTestUtils.assertCoordinatesEqual(tvec, t1.vectorTo(t2), eps);
-            EuclideanTestUtils.assertCoordinatesEqual(p1, inverse.apply(t1), eps);
-            EuclideanTestUtils.assertCoordinatesEqual(p2, inverse.apply(t2), eps);
-        });
-    }
-}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/IntervalTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/IntervalTest.java
index 46ec589..bf58ecf 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/IntervalTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/IntervalTest.java
@@ -621,7 +621,7 @@ public class IntervalTest {
     @Test
     public void testTransform() {
         // arrange
-        Transform1D transform = FunctionTransform1D.from(p -> Vector1D.of(2.0 * p.getX()));
+        AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(2);
 
         // act/assert
         checkInterval(Interval.of(-1, 2, TEST_PRECISION).transform(transform), -2, 4);
@@ -639,7 +639,7 @@ public class IntervalTest {
     @Test
     public void testTransform_reflection() {
         // arrange
-        Transform1D transform = FunctionTransform1D.from(Vector1D::negate);
+        AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(-1);
 
         // act/assert
         checkInterval(Interval.of(-1, 2, TEST_PRECISION).transform(transform), -2, 1);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/OrientedPointTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/OrientedPointTest.java
index a090e88..b82c117 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/OrientedPointTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/OrientedPointTest.java
@@ -97,7 +97,7 @@ public class OrientedPointTest {
         OrientedPoint neg = OrientedPoint.createPositiveFacing(Double.NEGATIVE_INFINITY, TEST_PRECISION);
 
         Transform<Vector1D> scaleAndTranslate = AffineTransformMatrix1D.identity().scale(10.0).translate(5.0);
-        Transform<Vector1D> negate = FunctionTransform1D.from(Vector1D::negate);
+        Transform<Vector1D> negate = AffineTransformMatrix1D.from(Vector1D::negate);
 
         // act/assert
         assertOrientedPoint(pos.transform(scaleAndTranslate), Double.POSITIVE_INFINITY, false, TEST_PRECISION);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1DTest.java
index 54dd532..bcc4360 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/RegionBSPTree1DTest.java
@@ -627,7 +627,7 @@ public class RegionBSPTree1DTest {
         // arrange
         RegionBSPTree1D tree = RegionBSPTree1D.full();
 
-        Transform1D transform = AffineTransformMatrix1D.createScale(2);
+        AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(2);
 
         // act
         tree.transform(transform);
@@ -644,7 +644,7 @@ public class RegionBSPTree1DTest {
                     Interval.min(3, TEST_PRECISION)
                 );
 
-        Transform1D transform = AffineTransformMatrix1D.createScale(2)
+        AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(2)
                 .translate(3);
 
         // act
@@ -666,7 +666,7 @@ public class RegionBSPTree1DTest {
                     Interval.min(3, TEST_PRECISION)
                 );
 
-        Transform1D transform = AffineTransformMatrix1D.createScale(-2)
+        AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(-2)
                 .translate(3);
 
         // act
@@ -688,7 +688,7 @@ public class RegionBSPTree1DTest {
                     Interval.min(3, TEST_PRECISION)
                 );
 
-        Transform1D transform = FunctionTransform1D.from(Vector1D::negate);
+        AffineTransformMatrix1D transform = AffineTransformMatrix1D.from(Vector1D::negate);
 
         // act
         tree.transform(transform);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3DTest.java
index f3f0c0e..05969bb 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3DTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import java.util.function.UnaryOperator;
+
 import org.apache.commons.geometry.core.GeometryTestUtils;
 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.geometry.euclidean.EuclideanTestUtils.PermuteCallback3D;
@@ -92,6 +94,39 @@ public class AffineTransformMatrix3DTest {
     }
 
     @Test
+    public void testFrom() {
+        // act/assert
+        Assert.assertArrayEquals(new double[] {
+            1, 0, 0, 0,
+            0, 1, 0, 0,
+            0, 0, 1, 0
+        }, AffineTransformMatrix3D.from(UnaryOperator.identity()).toArray(), EPS);
+        Assert.assertArrayEquals(new double[] {
+            1, 0, 0, 2,
+            0, 1, 0, 3,
+            0, 0, 1, -4
+        }, AffineTransformMatrix3D.from(v -> v.add(Vector3D.of(2, 3, -4))).toArray(), EPS);
+        Assert.assertArrayEquals(new double[] {
+            3, 0, 0, 0,
+            0, 3, 0, 0,
+            0, 0, 3, 0
+        }, AffineTransformMatrix3D.from(v -> v.multiply(3)).toArray(), EPS);
+        Assert.assertArrayEquals(new double[] {
+            3, 0, 0, 6,
+            0, 3, 0, 9,
+            0, 0, 3, 12
+        }, AffineTransformMatrix3D.from(v -> v.add(Vector3D.of(2, 3, 4)).multiply(3)).toArray(), EPS);
+    }
+
+    @Test
+    public void testFrom_invalidFunction() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            AffineTransformMatrix3D.from(v -> v.multiply(0));
+        }, IllegalArgumentException.class);
+    }
+
+    @Test
     public void testIdentity() {
         // act
         AffineTransformMatrix3D transform = AffineTransformMatrix3D.identity();
@@ -731,15 +766,6 @@ public class AffineTransformMatrix3DTest {
     }
 
     @Test
-    public void testToMatrix() {
-        // arrange
-        AffineTransformMatrix3D m = AffineTransformMatrix3D.createScale(3);
-
-        // act/assert
-        Assert.assertSame(m, m.toMatrix());
-    }
-
-    @Test
     public void testMultiply_combinesTransformOperations() {
         // arrange
         Vector3D translation1 = Vector3D.of(1, 2, 3);
@@ -948,49 +974,49 @@ public class AffineTransformMatrix3DTest {
                     0, 0, 0, 0,
                     0, 0, 0, 0,
                     0, 0, 0, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is 0.0");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is 0.0");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix3D.of(
                     1, 0, 0, 0,
                     0, 1, 0, 0,
                     0, 0, Double.NaN, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is NaN");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is NaN");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix3D.of(
                     1, 0, 0, 0,
                     0, Double.NEGATIVE_INFINITY, 0, 0,
                     0, 0, 1, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is NaN");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is NaN");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix3D.of(
                     Double.POSITIVE_INFINITY, 0, 0, 0,
                     0, 1, 0, 0,
                     0, 0, 1, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is NaN");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is NaN");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix3D.of(
                     1, 0, 0, Double.NaN,
                     0, 1, 0, 0,
                     0, 0, 1, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; invalid matrix element: NaN");
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: NaN");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix3D.of(
                     1, 0, 0, 0,
                     0, 1, 0, Double.POSITIVE_INFINITY,
                     0, 0, 1, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; invalid matrix element: Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: Infinity");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix3D.of(
                     1, 0, 0, 0,
                     0, 1, 0, 0,
                     0, 0, 1, Double.NEGATIVE_INFINITY).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; invalid matrix element: -Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: -Infinity");
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/FacetTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/FacetTest.java
index ba6129e..8faa42c 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/FacetTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/FacetTest.java
@@ -19,7 +19,6 @@ package org.apache.commons.geometry.euclidean.threed;
 import java.util.Arrays;
 import java.util.List;
 
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.apache.commons.geometry.core.GeometryTestUtils;
 import org.apache.commons.geometry.core.RegionLocation;
 import org.apache.commons.geometry.core.Transform;
@@ -32,6 +31,7 @@ import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
 import org.apache.commons.geometry.euclidean.twod.ConvexArea;
 import org.apache.commons.geometry.euclidean.twod.Line;
 import org.apache.commons.geometry.euclidean.twod.Vector2D;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.junit.Assert;
 import org.junit.Test;
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/FunctionTransform3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/FunctionTransform3DTest.java
deleted file mode 100644
index 7a59b53..0000000
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/FunctionTransform3DTest.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.threed;
-
-import java.util.function.UnaryOperator;
-
-import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class FunctionTransform3DTest {
-
-    private static final double TEST_EPS = 1e-15;
-
-    @Test
-    public void testIdentity() {
-        // arrange
-        Vector3D p0 = Vector3D.of(0, 0, 0);
-        Vector3D p1 = Vector3D.of(1, 1, 1);
-        Vector3D p2 = Vector3D.of(-1, -1, -1);
-
-        // act
-        Transform3D t = Transform3D.identity();
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p1, t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p2, t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_identity() {
-        // arrange
-        Vector3D p0 = Vector3D.of(0, 0, 0);
-        Vector3D p1 = Vector3D.of(1, 1, 1);
-        Vector3D p2 = Vector3D.of(-1, -1, -1);
-
-        // act
-        Transform3D t = Transform3D.from(UnaryOperator.identity());
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p1, t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p2, t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_scaleAndTranslate() {
-        // arrange
-        Vector3D p0 = Vector3D.of(0, 0, 0);
-        Vector3D p1 = Vector3D.of(1, 2, 3);
-        Vector3D p2 = Vector3D.of(-1, -2, -3);
-
-        // act
-        Transform3D t = Transform3D.from(v -> v.multiply(2).add(Vector3D.of(1, -1, 2)));
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, -1, 2), t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(3, 3, 8), t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, -5, -4), t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_reflection_singleAxis() {
-        // arrange
-        Vector3D p0 = Vector3D.of(0, 0, 0);
-        Vector3D p1 = Vector3D.of(1, 2, 3);
-        Vector3D p2 = Vector3D.of(-1, -2, -3);
-
-        // act
-        Transform3D t = Transform3D.from(v -> Vector3D.of(-v.getX(), v.getY(), v.getZ()));
-
-        // assert
-        Assert.assertFalse(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, 2, 3), t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, -2, -3), t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_reflection_twoAxes() {
-        // arrange
-        Vector3D p0 = Vector3D.of(0, 0, 0);
-        Vector3D p1 = Vector3D.of(1, 2, 3);
-        Vector3D p2 = Vector3D.of(-1, -2, -3);
-
-        // act
-        Transform3D t = Transform3D.from(v -> Vector3D.of(-v.getX(), -v.getY(), v.getZ()));
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, -2, 3), t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, -3), t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_reflection_allAxes() {
-        // arrange
-        Vector3D p0 = Vector3D.of(0, 0, 0);
-        Vector3D p1 = Vector3D.of(1, 2, 3);
-        Vector3D p2 = Vector3D.of(-1, -2, -3);
-
-        // act
-        Transform3D t = Transform3D.from(Vector3D::negate);
-
-        // assert
-        Assert.assertFalse(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(-1, -2, -3), t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector3D.of(1, 2, 3), t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testToMatrix() {
-        // act/assert
-        Assert.assertArrayEquals(new double[] {
-            1, 0, 0, 0,
-            0, 1, 0, 0,
-            0, 0, 1, 0
-        }, Transform3D.identity().toMatrix().toArray(), TEST_EPS);
-        Assert.assertArrayEquals(new double[] {
-            1, 0, 0, 2,
-            0, 1, 0, 3,
-            0, 0, 1, -4
-        }, Transform3D.from(v -> v.add(Vector3D.of(2, 3, -4))).toMatrix().toArray(), TEST_EPS);
-        Assert.assertArrayEquals(new double[] {
-            3, 0, 0, 0,
-            0, 3, 0, 0,
-            0, 0, 3, 0
-        }, Transform3D.from(v -> v.multiply(3)).toMatrix().toArray(), TEST_EPS);
-        Assert.assertArrayEquals(new double[] {
-            3, 0, 0, 6,
-            0, 3, 0, 9,
-            0, 0, 3, 12
-        }, Transform3D.from(v -> v.add(Vector3D.of(2, 3, 4)).multiply(3)).toMatrix().toArray(), TEST_EPS);
-    }
-
-    @Test
-    public void testTransformRoundTrip() {
-        // arrange
-        double eps = 1e-8;
-        double delta = 0.11;
-
-        Vector3D p1 = Vector3D.of(1.1, -3, 0);
-        Vector3D p2 = Vector3D.of(-5, 0.2, 2);
-        Vector3D vec = p1.vectorTo(p2);
-
-        EuclideanTestUtils.permuteSkipZero(-2, 2, delta, (translate, scale) -> {
-
-            Transform3D t = Transform3D.from(v -> {
-                return v.multiply(scale * 0.5)
-                    .add(Vector3D.of(translate, 0.5 * translate, 0.25 * translate))
-                    .multiply(scale * 1.5);
-            });
-
-            // act
-            Vector3D t1 = t.apply(p1);
-            Vector3D t2 = t.apply(p2);
-            Vector3D tvec = t.applyVector(vec);
-
-            Transform3D inverse = t.toMatrix().inverse();
-
-            // assert
-            EuclideanTestUtils.assertCoordinatesEqual(tvec, t1.vectorTo(t2), eps);
-            EuclideanTestUtils.assertCoordinatesEqual(p1, inverse.apply(t1), eps);
-            EuclideanTestUtils.assertCoordinatesEqual(p2, inverse.apply(t2), eps);
-        });
-    }
-}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Line3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Line3DTest.java
index 22df394..5e104a7 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Line3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Line3DTest.java
@@ -103,7 +103,7 @@ public class Line3DTest {
         // arrange
         Line3D line = Line3D.fromPointAndDirection(Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 1), TEST_PRECISION);
 
-        Transform3D transform = FunctionTransform3D.from(v -> Vector3D.of(v.getX(), v.getY(), -v.getZ()));
+        AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(v.getX(), v.getY(), -v.getZ()));
 
         // act
         Line3D result = line.transform(transform);
@@ -118,7 +118,7 @@ public class Line3DTest {
         // arrange
         Line3D line = Line3D.fromPointAndDirection(Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 1), TEST_PRECISION);
 
-        Transform3D transform = FunctionTransform3D.from(v -> Vector3D.of(v.getX(), -v.getY(), -v.getZ()));
+        AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(v.getX(), -v.getY(), -v.getZ()));
 
         // act
         Line3D result = line.transform(transform);
@@ -133,7 +133,7 @@ public class Line3DTest {
         // arrange
         Line3D line = Line3D.fromPointAndDirection(Vector3D.of(1, 0, 0), Vector3D.of(1, 1, 1), TEST_PRECISION);
 
-        Transform3D transform = FunctionTransform3D.from(Vector3D::negate);
+        AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(Vector3D::negate);
 
         // act
         Line3D result = line.transform(transform);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
index a0e7044..84a542e 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/PlaneTest.java
@@ -657,7 +657,7 @@ public class PlaneTest {
         Vector3D pt = Vector3D.of(0, 0, 1);
         Plane plane = Plane.fromPointAndPlaneVectors(pt, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
 
-        Transform3D transform = FunctionTransform3D.from(v -> Vector3D.of(-v.getX(), v.getY(), v.getZ()));
+        AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(-v.getX(), v.getY(), v.getZ()));
 
         // act
         Plane result = plane.transform(transform);
@@ -672,7 +672,7 @@ public class PlaneTest {
         Vector3D pt = Vector3D.of(0, 0, 1);
         Plane plane = Plane.fromPointAndPlaneVectors(pt, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
 
-        Transform3D transform = FunctionTransform3D.from(v -> Vector3D.of(-v.getX(), -v.getY(), v.getZ()));
+        AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(v -> Vector3D.of(-v.getX(), -v.getY(), v.getZ()));
 
         // act
         Plane result = plane.transform(transform);
@@ -687,7 +687,7 @@ public class PlaneTest {
         Vector3D pt = Vector3D.of(0, 0, 1);
         Plane plane = Plane.fromPointAndPlaneVectors(pt, Vector3D.Unit.PLUS_X, Vector3D.Unit.PLUS_Y, TEST_PRECISION);
 
-        Transform3D transform = FunctionTransform3D.from(Vector3D::negate);
+        AffineTransformMatrix3D transform = AffineTransformMatrix3D.from(Vector3D::negate);
 
         // act
         Plane result = plane.transform(transform);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLine3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLine3DTest.java
index 25e1ceb..0c3c86f 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLine3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SubLine3DTest.java
@@ -18,7 +18,6 @@ package org.apache.commons.geometry.euclidean.threed;
 
 import java.util.List;
 
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.apache.commons.geometry.core.Transform;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
 import org.apache.commons.geometry.core.precision.EpsilonDoublePrecisionContext;
@@ -26,6 +25,7 @@ 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.threed.rotation.QuaternionRotation;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.junit.Assert;
 import org.junit.Test;
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/AbstractSegmentConnectorTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/AbstractSegmentConnectorTest.java
index 0120706..e00e799 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/AbstractSegmentConnectorTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/AbstractSegmentConnectorTest.java
@@ -22,11 +22,11 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Random;
 
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
 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.twod.AbstractSegmentConnector.ConnectableSegment;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.junit.Assert;
 import org.junit.Test;
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2DTest.java
index 86f89cb..1ebf2be 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2DTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import java.util.function.UnaryOperator;
+
 import org.apache.commons.geometry.core.GeometryTestUtils;
 import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.numbers.angle.PlaneAngleRadians;
@@ -97,6 +99,35 @@ public class AffineTransformMatrix2DTest {
     }
 
     @Test
+    public void testFrom() {
+        // act/assert
+        Assert.assertArrayEquals(new double[] {
+            1, 0, 0,
+            0, 1, 0
+        }, AffineTransformMatrix2D.from(UnaryOperator.identity()).toArray(), EPS);
+        Assert.assertArrayEquals(new double[] {
+            1, 0, 2,
+            0, 1, 3
+        }, AffineTransformMatrix2D.from(v -> v.add(Vector2D.of(2, 3))).toArray(), EPS);
+        Assert.assertArrayEquals(new double[] {
+            3, 0, 0,
+            0, 3, 0
+        }, AffineTransformMatrix2D.from(v -> v.multiply(3)).toArray(), EPS);
+        Assert.assertArrayEquals(new double[] {
+            3, 0, 6,
+            0, 3, 9
+        }, AffineTransformMatrix2D.from(v -> v.add(Vector2D.of(2, 3)).multiply(3)).toArray(), EPS);
+    }
+
+    @Test
+    public void testFrom_invalidFunction() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> {
+            AffineTransformMatrix2D.from(v -> v.multiply(0));
+        }, IllegalArgumentException.class);
+    }
+
+    @Test
     public void testCreateTranslation_xy() {
         // act
         AffineTransformMatrix2D transform = AffineTransformMatrix2D.createTranslation(2, 3);
@@ -675,15 +706,6 @@ public class AffineTransformMatrix2DTest {
     }
 
     @Test
-    public void testToMatrix() {
-        // arrange
-        AffineTransformMatrix2D t = AffineTransformMatrix2D.createScale(2.0);
-
-        // act/assert
-        Assert.assertSame(t, t.toMatrix());
-    }
-
-    @Test
     public void testMultiply() {
         // arrange
         AffineTransformMatrix2D a = AffineTransformMatrix2D.of(
@@ -919,43 +941,43 @@ public class AffineTransformMatrix2DTest {
             AffineTransformMatrix2D.of(
                     0, 0, 0,
                     0, 0, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is 0.0");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is 0.0");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix2D.of(
                     1, 0, 0,
                     0, Double.NaN, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is NaN");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is NaN");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix2D.of(
                     1, 0, 0,
                     0, Double.NEGATIVE_INFINITY, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is -Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is -Infinity");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix2D.of(
                     Double.POSITIVE_INFINITY, 0, 0,
                     0, 1, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; matrix determinant is Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is Infinity");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix2D.of(
                     1, 0, Double.NaN,
                     0, 1, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; invalid matrix element: NaN");
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: NaN");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix2D.of(
                     1, 0, Double.POSITIVE_INFINITY,
                     0, 1, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; invalid matrix element: Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: Infinity");
 
         GeometryTestUtils.assertThrows(() -> {
             AffineTransformMatrix2D.of(
                     1, 0, Double.NEGATIVE_INFINITY,
                     0, 1, 0).inverse();
-        }, IllegalStateException.class, "Transform is not invertible; invalid matrix element: -Infinity");
+        }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: -Infinity");
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/ConvexAreaTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/ConvexAreaTest.java
index 2ed0e37..f04f186 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/ConvexAreaTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/ConvexAreaTest.java
@@ -104,7 +104,7 @@ public class ConvexAreaTest {
     @Test
     public void testTransform_full() {
         // arrange
-        Transform2D transform = FunctionTransform2D.from(v -> v.multiply(3));
+        AffineTransformMatrix2D transform = AffineTransformMatrix2D.createScale(3);
         ConvexArea area = ConvexArea.full();
 
         // act
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/FunctionTransform2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/FunctionTransform2DTest.java
deleted file mode 100644
index 99da23d..0000000
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/FunctionTransform2DTest.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.twod;
-
-import java.util.function.UnaryOperator;
-
-import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class FunctionTransform2DTest {
-
-    private static final double TEST_EPS = 1e-15;
-
-    @Test
-    public void testIdentity() {
-        // arrange
-        Vector2D p0 = Vector2D.of(0, 0);
-        Vector2D p1 = Vector2D.of(1, 1);
-        Vector2D p2 = Vector2D.of(-1, -1);
-
-        // act
-        Transform2D t = Transform2D.identity();
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p1, t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p2, t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_identity() {
-        // arrange
-        Vector2D p0 = Vector2D.of(0, 0);
-        Vector2D p1 = Vector2D.of(1, 1);
-        Vector2D p2 = Vector2D.of(-1, -1);
-
-        // act
-        Transform2D t = Transform2D.from(UnaryOperator.identity());
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p1, t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(p2, t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_scaleAndTranslate() {
-        // arrange
-        Vector2D p0 = Vector2D.of(0, 0);
-        Vector2D p1 = Vector2D.of(1, 2);
-        Vector2D p2 = Vector2D.of(-1, -2);
-
-        // act
-        Transform2D t = Transform2D.from(v -> v.multiply(2).add(Vector2D.of(1, -1)));
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, -1), t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(3, 3), t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, -5), t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_reflection_singleAxis() {
-        // arrange
-        Vector2D p0 = Vector2D.of(0, 0);
-        Vector2D p1 = Vector2D.of(1, 2);
-        Vector2D p2 = Vector2D.of(-1, -2);
-
-        // act
-        Transform2D t = Transform2D.from(v -> Vector2D.of(-v.getX(), v.getY()));
-
-        // assert
-        Assert.assertFalse(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, 2), t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, -2), t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testFrom_reflection_bothAxes() {
-        // arrange
-        Vector2D p0 = Vector2D.of(0, 0);
-        Vector2D p1 = Vector2D.of(1, 2);
-        Vector2D p2 = Vector2D.of(-1, -2);
-
-        // act
-        Transform2D t = Transform2D.from(Vector2D::negate);
-
-        // assert
-        Assert.assertTrue(t.preservesOrientation());
-
-        EuclideanTestUtils.assertCoordinatesEqual(p0, t.apply(p0), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-1, -2), t.apply(p1), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(1, 2), t.apply(p2), TEST_EPS);
-    }
-
-    @Test
-    public void testApplyVector() {
-        // arrange
-        Transform2D t = Transform2D.from(v -> {
-            return v.multiply(-2).add(Vector2D.of(4, 5));
-        });
-
-        // act/assert
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.ZERO, t.applyVector(Vector2D.ZERO), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-2, 0), t.applyVector(Vector2D.Unit.PLUS_X), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(-4, -4), t.applyVector(Vector2D.of(2, 2)), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(2, 0), t.applyVector(Vector2D.of(-1, 0)), TEST_EPS);
-        EuclideanTestUtils.assertCoordinatesEqual(Vector2D.of(4, 6), t.applyVector(Vector2D.of(-2, -3)), TEST_EPS);
-    }
-
-    @Test
-    public void testToMatrix() {
-        // act/assert
-        Assert.assertArrayEquals(new double[] {
-            1, 0, 0,
-            0, 1, 0
-        }, Transform2D.identity().toMatrix().toArray(), TEST_EPS);
-        Assert.assertArrayEquals(new double[] {
-            1, 0, 2,
-            0, 1, 3
-        }, Transform2D.from(v -> v.add(Vector2D.of(2, 3))).toMatrix().toArray(), TEST_EPS);
-        Assert.assertArrayEquals(new double[] {
-            3, 0, 0,
-            0, 3, 0
-        }, Transform2D.from(v -> v.multiply(3)).toMatrix().toArray(), TEST_EPS);
-        Assert.assertArrayEquals(new double[] {
-            3, 0, 6,
-            0, 3, 9
-        }, Transform2D.from(v -> v.add(Vector2D.of(2, 3)).multiply(3)).toMatrix().toArray(), TEST_EPS);
-    }
-
-    @Test
-    public void testTransformRoundTrip() {
-        // arrange
-        double eps = 1e-8;
-        double delta = 0.11;
-
-        Vector2D p1 = Vector2D.of(1.1, -3);
-        Vector2D p2 = Vector2D.of(-5, 0.2);
-        Vector2D vec = p1.vectorTo(p2);
-
-        EuclideanTestUtils.permuteSkipZero(-2, 2, delta, (translate, scale) -> {
-
-            Transform2D t = Transform2D.from(v -> {
-                return v.multiply(scale * 0.5)
-                    .add(Vector2D.of(translate, 0.5 * translate))
-                    .multiply(scale * 1.5);
-            });
-
-            // act
-            Vector2D t1 = t.apply(p1);
-            Vector2D t2 = t.apply(p2);
-            Vector2D tvec = t.applyVector(vec);
-
-            Transform2D inverse = t.toMatrix().inverse();
-
-            // assert
-            EuclideanTestUtils.assertCoordinatesEqual(tvec, t1.vectorTo(t2), eps);
-            EuclideanTestUtils.assertCoordinatesEqual(p1, inverse.apply(t1), eps);
-            EuclideanTestUtils.assertCoordinatesEqual(p2, inverse.apply(t2), eps);
-        });
-    }
-}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/InteriorAngleSegmentConnectorTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/InteriorAngleSegmentConnectorTest.java
index 2853c57..3401b00 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/InteriorAngleSegmentConnectorTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/InteriorAngleSegmentConnectorTest.java
@@ -23,12 +23,12 @@ import java.util.List;
 import java.util.Random;
 import java.util.function.Consumer;
 
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
 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.twod.InteriorAngleSegmentConnector.Maximize;
 import org.apache.commons.geometry.euclidean.twod.InteriorAngleSegmentConnector.Minimize;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.junit.Assert;
 import org.junit.Test;
 
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolylineTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolylineTest.java
index f7af9c7..bf79809 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolylineTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolylineTest.java
@@ -21,7 +21,6 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 
-import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.apache.commons.geometry.core.GeometryTestUtils;
 import org.apache.commons.geometry.core.RegionLocation;
 import org.apache.commons.geometry.core.partitioning.Split;
@@ -29,6 +28,7 @@ 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.twod.Polyline.Builder;
+import org.apache.commons.numbers.angle.PlaneAngleRadians;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -574,7 +574,7 @@ public class PolylineTest {
     public void testTransform_empty() {
         // arrange
         Polyline path = Polyline.empty();
-        FunctionTransform2D t = FunctionTransform2D.from(v -> v.add(Vector2D.Unit.PLUS_X));
+        AffineTransformMatrix2D t = AffineTransformMatrix2D.createTranslation(Vector2D.Unit.PLUS_X);
 
         // act/assert
         Assert.assertSame(path, path.transform(t));
@@ -614,7 +614,7 @@ public class PolylineTest {
         Polyline path = Polyline.fromSegments(
                 Line.fromPoints(Vector2D.ZERO, Vector2D.Unit.PLUS_Y, TEST_PRECISION).span());
 
-        FunctionTransform2D t = FunctionTransform2D.from(v -> v.add(Vector2D.Unit.PLUS_X));
+        AffineTransformMatrix2D t = AffineTransformMatrix2D.createTranslation(Vector2D.Unit.PLUS_X);
 
         // act
         Polyline result = path.transform(t);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2DTest.java
index a1f84d6..c44bbd3 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/RegionBSPTree2DTest.java
@@ -1205,7 +1205,7 @@ public class RegionBSPTree2DTest {
         // arrange
         RegionBSPTree2D tree = Parallelogram.axisAligned(Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION).toTree();
 
-        Transform2D transform = FunctionTransform2D.from(v -> Vector2D.of(-v.getX(), v.getY()));
+        AffineTransformMatrix2D transform = AffineTransformMatrix2D.from(v -> Vector2D.of(-v.getX(), v.getY()));
 
         // act
         tree.transform(transform);
@@ -1228,7 +1228,7 @@ public class RegionBSPTree2DTest {
         RegionBSPTree2D tree = Parallelogram.axisAligned(
                     Vector2D.of(1, 1), Vector2D.of(2, 2), TEST_PRECISION).toTree();
 
-        Transform2D transform = FunctionTransform2D.from(Vector2D::negate);
+        AffineTransformMatrix2D transform = AffineTransformMatrix2D.from(Vector2D::negate);
 
         // act
         tree.transform(transform);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SegmentTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SegmentTest.java
index d1a959d..60d9985 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SegmentTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/SegmentTest.java
@@ -20,6 +20,7 @@ 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.HyperplaneLocation;
 import org.apache.commons.geometry.core.partitioning.Split;
 import org.apache.commons.geometry.core.precision.DoublePrecisionContext;
@@ -409,10 +410,10 @@ public class SegmentTest {
         // arrange
         Segment segment = Segment.fromPoints(Vector2D.of(0, 1), Vector2D.of(2, 3), TEST_PRECISION);
 
-        Transform2D translation = AffineTransformMatrix2D.createTranslation(-1, 1);
-        Transform2D rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
-        Transform2D scale = AffineTransformMatrix2D.createScale(2, 3);
-        Transform2D reflect = FunctionTransform2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
+        Transform<Vector2D> translation = AffineTransformMatrix2D.createTranslation(-1, 1);
+        Transform<Vector2D> rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
+        Transform<Vector2D> scale = AffineTransformMatrix2D.createScale(2, 3);
+        Transform<Vector2D> reflect = AffineTransformMatrix2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
 
         // act/assert
         checkFiniteSegment(segment.transform(translation), Vector2D.of(-1, 2), Vector2D.of(1, 4));
@@ -427,10 +428,10 @@ public class SegmentTest {
         Segment segment = Segment.fromInterval(Line.fromPoints(Vector2D.of(0, 1), Vector2D.of(1, 1), TEST_PRECISION),
                 Interval.point(0, TEST_PRECISION));
 
-        Transform2D translation = AffineTransformMatrix2D.createTranslation(-1, 1);
-        Transform2D rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
-        Transform2D scale = AffineTransformMatrix2D.createScale(2, 3);
-        Transform2D reflect = FunctionTransform2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
+        Transform<Vector2D> translation = AffineTransformMatrix2D.createTranslation(-1, 1);
+        Transform<Vector2D> rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
+        Transform<Vector2D> scale = AffineTransformMatrix2D.createScale(2, 3);
+        Transform<Vector2D> reflect = AffineTransformMatrix2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
 
         // act/assert
         checkFiniteSegment(segment.transform(translation), Vector2D.of(-1, 2), Vector2D.of(-1, 2));
@@ -445,10 +446,10 @@ public class SegmentTest {
         Segment segment = Segment.fromInterval(Line.fromPoints(Vector2D.ZERO, Vector2D.of(2, 1), TEST_PRECISION),
                 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
 
-        Transform2D translation = AffineTransformMatrix2D.createTranslation(-1, 1);
-        Transform2D rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
-        Transform2D scale = AffineTransformMatrix2D.createScale(2, 3);
-        Transform2D reflect = FunctionTransform2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
+        Transform<Vector2D> translation = AffineTransformMatrix2D.createTranslation(-1, 1);
+        Transform<Vector2D> rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
+        Transform<Vector2D> scale = AffineTransformMatrix2D.createScale(2, 3);
+        Transform<Vector2D> reflect = AffineTransformMatrix2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
 
         // act/assert
         Segment translated = segment.transform(translation);
@@ -486,10 +487,10 @@ public class SegmentTest {
         Segment segment = Segment.fromInterval(Line.fromPoints(Vector2D.ZERO, Vector2D.of(2, 1), TEST_PRECISION),
                 0.0, Double.POSITIVE_INFINITY);
 
-        Transform2D translation = AffineTransformMatrix2D.createTranslation(-1, 1);
-        Transform2D rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
-        Transform2D scale = AffineTransformMatrix2D.createScale(2, 3);
-        Transform2D reflect = FunctionTransform2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
+        Transform<Vector2D> translation = AffineTransformMatrix2D.createTranslation(-1, 1);
+        Transform<Vector2D> rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
+        Transform<Vector2D> scale = AffineTransformMatrix2D.createScale(2, 3);
+        Transform<Vector2D> reflect = AffineTransformMatrix2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
 
         // act/assert
         Segment translated = segment.transform(translation);
@@ -527,10 +528,10 @@ public class SegmentTest {
         Segment segment = Segment.fromInterval(Line.fromPoints(Vector2D.ZERO, Vector2D.of(2, 1), TEST_PRECISION),
                 Double.NEGATIVE_INFINITY, 0.0);
 
-        Transform2D translation = AffineTransformMatrix2D.createTranslation(-1, 1);
-        Transform2D rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
-        Transform2D scale = AffineTransformMatrix2D.createScale(2, 3);
-        Transform2D reflect = FunctionTransform2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
+        Transform<Vector2D> translation = AffineTransformMatrix2D.createTranslation(-1, 1);
+        Transform<Vector2D> rotation = AffineTransformMatrix2D.createRotation(PlaneAngleRadians.PI_OVER_TWO);
+        Transform<Vector2D> scale = AffineTransformMatrix2D.createScale(2, 3);
+        Transform<Vector2D> reflect = AffineTransformMatrix2D.from(pt -> Vector2D.of(pt.getX(), -pt.getY()));
 
         // act/assert
         Segment translated = segment.transform(translation);
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/Transform1S.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/Transform1S.java
index 5d2449d..fe8ad80 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/Transform1S.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/Transform1S.java
@@ -130,10 +130,8 @@ public final class Transform1S implements Transform<Point1S> {
         return multiply(other, this);
     }
 
-    /** Return a transform that is the inverse of the current instance. The returned transform
-     * will undo changes applied by this instance.
-     * @return a transform that is the inverse of the current instance
-     */
+    /** {@inheritDoc} */
+    @Override
     public Transform1S inverse() {
         final double invScale = 1.0 / scale;
 
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Transform2S.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Transform2S.java
index bb50fd0..6da7bfa 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Transform2S.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Transform2S.java
@@ -65,10 +65,8 @@ public final class Transform2S implements Transform<Point2S> {
         return euclideanTransform.preservesOrientation();
     }
 
-    /** Return a new instance representing the inverse transform operation
-     * of this instance.
-     * @return a transform representing the inverse of this instance
-     */
+    /** {@inheritDoc} */
+    @Override
     public Transform2S inverse() {
         return new Transform2S(euclideanTransform.inverse());
     }
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
index 52d2095..fe959e8 100644
--- a/src/site/xdoc/index.xml
+++ b/src/site/xdoc/index.xml
@@ -51,10 +51,8 @@ RegionBSPTree3D region = RegionBSPTree3D.from(
         Parallelepiped.axisAligned(Vector3D.of(-0.5, -0.5, -0.5), Vector3D.of(0.5, 0.5, 0.5), precision));
 
 // create a rotated copy of the region
-Transform3D rotation = QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, 0.25 * Math.PI);
-
 RegionBSPTree3D copy = region.copy();
-copy.transform(rotation);
+copy.transform(QuaternionRotation.fromAxisAngle(Vector3D.Unit.PLUS_Z, 0.25 * Math.PI));
 
 // compute the intersection of the regions, storing the result back into the caller
 // (the result could also have been placed into a third region)
diff --git a/src/site/xdoc/userguide/index.xml b/src/site/xdoc/userguide/index.xml
index 6a06a1d..a7ebb6a 100644
--- a/src/site/xdoc/userguide/index.xml
+++ b/src/site/xdoc/userguide/index.xml
@@ -197,27 +197,40 @@ v1.eq(v3, precision); // true - approximately equal according to the given preci
           <a class="code" href="../commons-geometry-core/apidocs/org/apache/commons/geometry/core/Transform.html">Transform</a>
           interface. Useful implementations of this interface exist for each supported space
           and dimension, so users should not need to implement their own. However, it is important to know that
-          all implementations (and instances) of this interface <em>must</em> meet the following requirements:
+          all implementations of this interface <em>must</em> meet the requirements listed below. Transforms that do
+          not meet these requirements cannot be expected to produce correct results in algorithms that use this
+          interface.
         </p>
         <ol>
           <li>
-            The transform must be <strong><a href="https://en.wikipedia.org/wiki/Affine_transformation">affine</a></strong>.
-            In basic terms, this means that the transform must retain the "straightness" and "parallelness" of
-            lines and planes (or whatever is an equivalent concept for the space). For example, a translation or
-            rotation in Euclidean 3D space meets this requirement because all lines that are parallel before the
-            transform remain parallel afterwards. However, a projective transform that causes previously parallel
-            lines to meet at a single point does not.
+            Transforms must represent functions that are <em>one-to-one</em> and <em>onto</em>
+            (i.e. <a href="https://en.wikipedia.org/wiki/Bijection">bijections</a>). This means that every point
+            in the space must be mapped to exactly one other point in the space. This also implies that the function
+            is invertible.
           </li>
           <li>
-            The transform must be <strong>inversible</strong>. An inverse transform must exist that will return
-            the original point if given the transformed point. In other words, for a transform <var>t</var>, there
-            must exist an inverse <var>inv</var> such that <var>inv.apply(t.apply(pt))</var> returns a point equal to
-            the input point <var>pt</var>.
+            Transforms must preserve <a href="https://en.wikipedia.org/wiki/Collinearity">collinearity</a>. This
+            means that if a set of points lie on a common hyperplane before the transform, then they must also lie
+            on a common hyperplane after the transform. For example, if the Euclidean 2D points <var>a</var>,
+            <var>b</var>, and <var>c</var> lie on line <var>L</var>, then the transformed points <var>a'</var>,
+            <var>b'</var>, and <var>c'</var> must lie on line <var>L'</var>, where <var>L'</var> is the transformed
+            form of the line.
+          </li>
+          <li>
+            Transforms must preserve the concept of
+            <a href="https://en.wikipedia.org/wiki/Parallel_(geometry)">parallelism</a> defined for the space.
+            This means that hyperplanes that are parallel before the transformation must remain parallel afterwards,
+            and hyperplanes that intersect must also intersect afterwards. For example, a transform that causes
+            parallel lines to converge to a single point in Euclidean space (such as the projective transforms used to
+            create perspective viewpoints in 3D graphics) would not meet this requirement. However, a transform that
+            turns a square into a rhombus with no right angles would fulfill the requirement, since the two pairs of
+            parallel lines forming the square remain parallel after the transformation.
           </li>
         </ol>
         <p>
-          Transforms that do not meet these requirements cannot be expected to produce correct results in
-          algorithms that use this interface.
+          Transforms that meet the above requirements in Euclidean space (and other affine spaces) are known as
+          <a href="https://en.wikipedia.org/wiki/Affine_transformation">affine transforms</a> and include such common
+          operations as translation, rotation, reflection, scaling, and any combinations thereof.
         </p>
       </subsection>
 
@@ -413,7 +426,8 @@ tree.count(); // number of nodes in the tree = 7
             </li>
             <li>
               <a class="code" href="../commons-geometry-core/apidocs/org/apache/commons/geometry/core/Transform.html">Transform</a> -
-              Represents a mapping between points. Instances are used to transform points and other geometric primitives.
+              Represents an inversible mapping between points that preserves parallelism. Instances are used to transform
+              points and other geometric primitives.
             </li>
           </ul>
         </li>
@@ -474,14 +488,8 @@ tree.count(); // number of nodes in the tree = 7
             Transform
             <ul>
               <li>
-                <a class="code" href="../commons-geometry-euclidean/apidocs/org/apache/commons/geometry/euclidean/oned/Transform1D.html">Transform1D</a> -
-                Primary interface for 1D transforms. A utility method is also provided to construct transform instances
-                from JDK <span class="code">Function</span>'s. Callers are responsible for ensuring that given functions
-                meet the <a href="#transforms">requirements for transforms</a>.
-              </li>
-              <li>
                 <a class="code" href="../commons-geometry-euclidean/apidocs/org/apache/commons/geometry/euclidean/oned/AffineTransformMatrix1D.html">AffineTransformMatrix1D</a> -
-                Represents transforms using a 2x2 matrix.
+                Represents affine transforms using a 2x2 matrix.
               </li>
 
             </ul>
@@ -565,14 +573,8 @@ List&lt;Interval&gt; intervals = tree.toIntervals(); // size = 2
             Transform
             <ul>
               <li>
-                <a class="code" href="../commons-geometry-euclidean/apidocs/org/apache/commons/geometry/euclidean/twod/Transform2D.html">Transform2D</a> -
-                Primary interface for 2D transforms. A utility method is also provided to construct transform instances
-                from JDK <span class="code">Function</span>'s. Callers are responsible for ensuring that given functions
-                meet the <a href="#transforms">requirements for transforms</a>.
-              </li>
-              <li>
                 <a class="code" href="../commons-geometry-euclidean/apidocs/org/apache/commons/geometry/euclidean/twod/AffineTransformMatrix2D.html">AffineTransformMatrix2D</a> -
-                Represents transforms using a 3x3 matrix.
+                Represents affine transforms using a 3x3 matrix.
               </li>
             </ul>
           </li>
@@ -637,8 +639,7 @@ RegionBSPTree2D tree = path.toTree();
 RegionBSPTree2D copy = tree.copy();
 
 // translate the copy
-Vector2D translation = Vector2D.of(0.5, 0.5);
-copy.transform(Transform2D.from(v -> v.add(translation)));
+copy.transform(AffineTransformMatrix2D.createTranslation(Vector2D.of(0.5, 0.5)));
 
 // compute the union of the regions, storing the result back into the
 // first tree
@@ -704,14 +705,8 @@ Vector2D normal = pt.getNormal(); // (1.0, 0.0)
             Transform
             <ul>
               <li>
-                <a class="code" href="../commons-geometry-euclidean/apidocs/org/apache/commons/geometry/euclidean/threed/Transform3D.html">Transform3D</a> -
-                Primary interface for 3D transforms. A utility method is also provided to construct transform instances
-                from JDK <span class="code">Function</span>'s. Callers are responsible for ensuring that given functions
-                meet the <a href="#transforms">requirements for transforms</a>.
-              </li>
-              <li>
                 <a class="code" href="../commons-geometry-euclidean/apidocs/org/apache/commons/geometry/euclidean/threed/AffineTransformMatrix3D.html">AffineTransformMatrix3D</a> -
-                Represents transforms using a 4x4 matrix.
+                Represents affine transforms using a 4x4 matrix.
               </li>
               <li>
                 <a class="code" href="../commons-geometry-euclidean/apidocs/org/apache/commons/geometry/euclidean/threed/rotation/QuaternionRotation.html">QuaternionRotation</a> -