You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by GitBox <gi...@apache.org> on 2018/09/14 00:20:39 UTC

[GitHub] asfgit closed pull request #8: Geometry 9

asfgit closed pull request #8: Geometry 9
URL: https://github.com/apache/commons-geometry/pull/8
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java
index 1d0269d..d4eb296 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java
@@ -36,7 +36,6 @@
 
     /** Get the L<sub>1</sub> norm for the vector. This is defined as the
      * sum of the absolute values of all vector components.
-     *
      * @see <a href="http://mathworld.wolfram.com/L1-Norm.html">L1 Norm</a>
      * @return L<sub>1</sub> norm for the vector
      */
@@ -46,14 +45,14 @@
      * This corresponds to the common notion of vector magnitude or length.
      * This is defined as the square root of the sum of the squares of all vector components.
      * @see <a href="http://mathworld.wolfram.com/L2-Norm.html">L2 Norm</a>
-     * @return Euclidean norm for the vector
+     * @return L<sub>2</sub> norm for the vector
      */
     double getNorm();
 
     /** Get the square of the L<sub>2</sub> norm (also known as the Euclidean norm)
      * for the vector. This is equal to the sum of the squares of all vector components.
      * @see #getNorm()
-     * @return square of the Euclidean norm for the vector
+     * @return square of the L<sub>2</sub> norm for the vector
      */
     double getNormSq();
 
@@ -64,6 +63,29 @@
      */
     double getNormInf();
 
+    /** Returns the magnitude (i.e. length) of the vector. This is
+     * the same value as returned by {@link #getNorm()}.
+     * @return the magnitude, or length, of the vector
+     * @see #getNorm()
+     */
+    double getMagnitude();
+
+    /** Returns the squared magnitude of the vector. This is the
+     * same value as returned by {@link #getNormSq()}.
+     * @return the squared magnitude of the vector
+     * @see #getMagnitude()
+     * @see #getNormSq()
+     */
+    double getMagnitudeSq();
+
+    /** Returns a vector with the same direction but with the given
+     * magnitude. This is equivalent to calling {@code vec.normalize().scalarMultiply(mag)}
+     * but without the intermediate vector.
+     * @param magnitude The vector magnitude
+     * @return a vector with the same direction as the current instance but the given magnitude
+     */
+    V withMagnitude(double magnitude);
+
     /** Add a vector to the instance.
      * @param v vector to add
      * @return a new vector
@@ -149,4 +171,39 @@
      * @return the dot product this &middot; v
      */
     double dotProduct(V v);
+
+    /** Get the projection of the instance onto the given base vector. The returned
+     * vector is parallel to {@code base}. Vector projection and rejection onto
+     * a given base are related by the equation
+     * <code>
+     *      <strong>v</strong> = <strong>v<sub>projection</sub></strong> + <strong>v<sub>rejection</sub></strong>
+     * </code>
+     * @param base base vector
+     * @return the vector projection of the instance onto {@code base}
+     * @exception IllegalStateException if the norm of the base vector is zero
+     * @see #reject(Vector)
+     */
+    V project(V base) throws IllegalStateException;
+
+    /** Get the rejection of the instance from the given base vector. The returned
+     * vector is orthogonal to {@code base}. This operation can be interpreted as
+     * returning the orthogonal projection of the instance onto the hyperplane
+     * orthogonal to {@code base}. Vector projection and rejection onto
+     * a given base are related by the equation
+     * <code>
+     *      <strong>v</strong> = <strong>v<sub>projection</sub></strong> + <strong>v<sub>rejection</sub></strong>
+     * </code>
+     * @param base base vector
+     * @return the vector rejection of the instance from {@code base}
+     * @exception IllegalStateException if the norm of the base vector is zero
+     * @see #project(Vector)
+     */
+    V reject(V base) throws IllegalStateException;
+
+    /** Compute the angular separation between two vectors in radians.
+     * @param v other vector
+     * @return angular separation between this instance and v in radians
+     * @exception IllegalStateException if either vector has a zero norm
+     */
+    double angle(V v) throws IllegalStateException;
 }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java
index 041380f..4cca8cc 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/SimpleTupleFormat.java
@@ -93,7 +93,7 @@ public String getSuffix() {
      * @return 1-tuple string
      */
     public String format(double a) {
-        StringBuilder sb = new StringBuilder();
+        final StringBuilder sb = new StringBuilder();
 
         if (prefix != null) {
             sb.append(prefix);
@@ -114,16 +114,16 @@ public String format(double a) {
      * @return 2-tuple string
      */
     public String format(double a1, double a2) {
-        StringBuilder sb = new StringBuilder();
+        final StringBuilder sb = new StringBuilder();
 
         if (prefix != null) {
             sb.append(prefix);
         }
 
-        sb.append(a1);
-        sb.append(separator);
-        sb.append(SPACE);
-        sb.append(a2);
+        sb.append(a1)
+            .append(separator)
+            .append(SPACE)
+            .append(a2);
 
         if (suffix != null) {
             sb.append(suffix);
@@ -139,19 +139,19 @@ public String format(double a1, double a2) {
      * @return 3-tuple string
      */
     public String format(double a1, double a2, double a3) {
-        StringBuilder sb = new StringBuilder();
+        final StringBuilder sb = new StringBuilder();
 
         if (prefix != null) {
             sb.append(prefix);
         }
 
-        sb.append(a1);
-        sb.append(separator);
-        sb.append(SPACE);
-        sb.append(a2);
-        sb.append(separator);
-        sb.append(SPACE);
-        sb.append(a3);
+        sb.append(a1)
+            .append(separator)
+            .append(SPACE)
+            .append(a2)
+            .append(separator)
+            .append(SPACE)
+            .append(a3);
 
         if (suffix != null) {
             sb.append(suffix);
@@ -208,7 +208,7 @@ public String format(double a1, double a2, double a3) {
      * @throws IllegalArgumentException if the input string format is invalid
      */
     public <T> T parse(String str, DoubleFunction3N<T> fn) throws IllegalArgumentException {
-        ParsePosition pos = new ParsePosition(0);
+        final ParsePosition pos = new ParsePosition(0);
 
         readPrefix(str, pos);
         final double v1 = readTupleValue(str, pos);
@@ -257,9 +257,9 @@ private double readTupleValue(String str, ParsePosition pos) throws IllegalArgum
             }
         }
 
-        String substr = str.substring(startIdx, endIdx);
+        final String substr = str.substring(startIdx, endIdx);
         try {
-            double value = Double.parseDouble(substr);
+            final double value = Double.parseDouble(substr);
 
             // advance the position and move past any terminating separator
             pos.setIndex(endIdx);
@@ -388,7 +388,7 @@ private void fail(String msg, String str, ParsePosition pos) throws IllegalArgum
      * @throws IllegalArgumentException the exception signaling a parse failure
      */
     private void fail(String msg, String str, ParsePosition pos, Throwable cause) throws IllegalArgumentException {
-        String fullMsg = String.format("Failed to parse string \"%s\" at index %d: %s", str, pos.getIndex(), msg);
+        final String fullMsg = String.format("Failed to parse string \"%s\" at index %d: %s", str, pos.getIndex(), msg);
 
         throw new TupleParseException(fullMsg, cause);
     }
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/Vectors.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/Vectors.java
new file mode 100644
index 0000000..673d53b
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/internal/Vectors.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.internal;
+
+/** This class consists exclusively of static vector utility methods.
+ */
+public final class Vectors {
+
+    /** Private constructor. */
+    private Vectors() {}
+
+    /** Get the L<sub>1</sub> norm for the vector with the given components.
+     * This is defined as the sum of the absolute values of all vector components.
+     * @param x vector component
+     * @return L<sub>1</sub> norm for the vector with the given components
+     * @see <a href="http://mathworld.wolfram.com/L1-Norm.html">L1 Norm</a>
+     */
+    public static double norm1(final double x) {
+        return Math.abs(x);
+    }
+
+    /** Get the L<sub>1</sub> norm for the vector with the given components.
+     * This is defined as the sum of the absolute values of all vector components.
+     * @param x1 first vector component
+     * @param x2 second vector component
+     * @return L<sub>1</sub> norm for the vector with the given components
+     * @see <a href="http://mathworld.wolfram.com/L1-Norm.html">L1 Norm</a>
+     */
+    public static double norm1(final double x1, final double x2) {
+        return Math.abs(x1) + Math.abs(x2);
+    }
+
+    /** Get the L<sub>1</sub> norm for the vector with the given components.
+     * This is defined as the sum of the absolute values of all vector components.
+     * @param x1 first vector component
+     * @param x2 second vector component
+     * @param x3 third vector component
+     * @return L<sub>1</sub> norm for the vector with the given components
+     * @see <a href="http://mathworld.wolfram.com/L1-Norm.html">L1 Norm</a>
+     */
+    public static double norm1(final double x1, final double x2, final double x3) {
+        return Math.abs(x1) + Math.abs(x2) + Math.abs(x3);
+    }
+
+    /** Get the L<sub>2</sub> norm (commonly known as the Euclidean norm) for the vector
+     * with the given components. This corresponds to the common notion of vector magnitude
+     * or length and is defined as the square root of the sum of the squares of all vector components.
+     * @param x vector component
+     * @return L<sub>2</sub> norm for the vector with the given components
+     * @see <a href="http://mathworld.wolfram.com/L2-Norm.html">L2 Norm</a>
+     */
+    public static double norm(final double x) {
+        return Math.abs(x);
+    }
+
+    /** Get the L<sub>2</sub> norm (commonly known as the Euclidean norm) for the vector
+     * with the given components. This corresponds to the common notion of vector magnitude
+     * or length and is defined as the square root of the sum of the squares of all vector components.
+     * @param x1 first vector component
+     * @param x2 second vector component
+     * @return L<sub>2</sub> norm for the vector with the given components
+     * @see <a href="http://mathworld.wolfram.com/L2-Norm.html">L2 Norm</a>
+     */
+    public static double norm(final double x1, final double x2) {
+        return Math.hypot(x1, x2);
+    }
+
+    /** Get the L<sub>2</sub> norm (commonly known as the Euclidean norm) for the vector
+     * with the given components. This corresponds to the common notion of vector magnitude
+     * or length and is defined as the square root of the sum of the squares of all vector components.
+     * @param x1 first vector component
+     * @param x2 second vector component
+     * @param x3 third vector component
+     * @return L<sub>2</sub> norm for the vector with the given components
+     * @see <a href="http://mathworld.wolfram.com/L2-Norm.html">L2 Norm</a>
+     */
+    public static double norm(final double x1, final double x2, final double x3) {
+        return Math.sqrt(normSq(x1, x2, x3));
+    }
+
+    /** Get the square of the L<sub>2</sub> norm (also known as the Euclidean norm)
+     * for the vector with the given components. This is equal to the sum of the squares of
+     * all vector components.
+     * @param x vector component
+     * @return square of the L<sub>2</sub> norm for the vector with the given components
+     * @see #norm(double)
+     */
+    public static double normSq(final double x) {
+        return x * x;
+    }
+
+    /** Get the square of the L<sub>2</sub> norm (also known as the Euclidean norm)
+     * for the vector with the given components. This is equal to the sum of the squares of
+     * all vector components.
+     * @param x1 first vector component
+     * @param x2 second vector component
+     * @return square of the L<sub>2</sub> norm for the vector with the given components
+     * @see #norm(double, double)
+     */
+    public static double normSq(final double x1, final double x2) {
+        return (x1 * x1) + (x2 * x2);
+    }
+
+    /** Get the square of the L<sub>2</sub> norm (also known as the Euclidean norm)
+     * for the vector with the given components. This is equal to the sum of the squares of
+     * all vector components.
+     * @param x1 first vector component
+     * @param x2 second vector component
+     * @param x3 third vector component
+     * @return square of the L<sub>2</sub> norm for the vector with the given components
+     * @see #norm(double, double, double)
+     */
+    public static double normSq(final double x1, final double x2, final double x3) {
+        return (x1 * x1) + (x2 * x2) + (x3 * x3);
+    }
+
+    /** Get the L<sub>&infin;</sub> norm for the vector with the given components. This is defined
+     * as the maximum of the absolute values of all vector components.
+     * @param x vector component
+     * @return L<sub>&infin;</sub> norm for the vector with the given components
+     * @see <a href="http://mathworld.wolfram.com/L-Infinity-Norm.html">L<sub>&infin;</sub> Norm</a>
+     */
+    public static double normInf(final double x) {
+        return Math.abs(x);
+    }
+
+    /** Get the L<sub>&infin;</sub> norm for the vector with the given components. This is defined
+     * as the maximum of the absolute values of all vector components.
+     * @param x1 first vector component
+     * @param x2 second vector component
+     * @return L<sub>&infin;</sub> norm for the vector with the given components
+     * @see <a href="http://mathworld.wolfram.com/L-Infinity-Norm.html">L<sub>&infin;</sub> Norm</a>
+     */
+    public static double normInf(final double x1, final double x2) {
+        return Math.max(Math.abs(x1), Math.abs(x2));
+    }
+
+    /** Get the L<sub>&infin;</sub> norm for the vector with the given components. This is defined
+     * as the maximum of the absolute values of all vector components.
+     * @param x1 first vector component
+     * @param x2 second vector component
+     * @param x3 third vector component
+     * @return L<sub>&infin;</sub> norm for the vector with the given components
+     * @see <a href="http://mathworld.wolfram.com/L-Infinity-Norm.html">L<sub>&infin;</sub> Norm</a>
+     */
+    public static double normInf(final double x1, final double x2, final double x3) {
+        return Math.max(Math.max(Math.abs(x1), Math.abs(x2)), Math.abs(x3));
+    }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/VectorsTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/VectorsTest.java
new file mode 100644
index 0000000..9817b28
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/internal/VectorsTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core.internal;
+
+import org.apache.commons.geometry.core.internal.Vectors;
+import org.junit.Assert;
+import org.junit.Test;
+
+
+public class VectorsTest {
+
+    private static final double EPS = Math.ulp(1d);
+
+    @Test
+    public void testNorm1_oneD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.norm1(0.0), EPS);
+
+        Assert.assertEquals(2.0, Vectors.norm1(-2.0), EPS);
+        Assert.assertEquals(1.0, Vectors.norm1(-1.0), EPS);
+
+        Assert.assertEquals(1.0, Vectors.norm1(1.0), EPS);
+        Assert.assertEquals(2.0, Vectors.norm1(2.0), EPS);
+    }
+
+    @Test
+    public void testNorm1_twoD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.norm1(0.0, 0.0), EPS);
+
+        Assert.assertEquals(3.0, Vectors.norm1(1.0, 2.0), EPS);
+        Assert.assertEquals(7.0, Vectors.norm1(3.0, -4.0), EPS);
+        Assert.assertEquals(11.0, Vectors.norm1(-5.0, 6.0), EPS);
+        Assert.assertEquals(16.0, Vectors.norm1(-7.0, -9.0), EPS);
+    }
+
+    @Test
+    public void testNorm1_threeD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.norm1(0.0, 0.0, 0.0), EPS);
+
+        Assert.assertEquals(6.0, Vectors.norm1(1.0, 2.0, 3.0), EPS);
+        Assert.assertEquals(15.0, Vectors.norm1(4.0, 5.0, -6.0), EPS);
+        Assert.assertEquals(24.0, Vectors.norm1(7.0, -8.0, 9.0), EPS);
+        Assert.assertEquals(33.0, Vectors.norm1(10.0, -11.0, -12.0), EPS);
+        Assert.assertEquals(42.0, Vectors.norm1(-13.0, 14.0, 15.0), EPS);
+        Assert.assertEquals(51.0, Vectors.norm1(-16.0, 17.0, -18.0), EPS);
+        Assert.assertEquals(60.0, Vectors.norm1(-19.0, -20.0, 21.0), EPS);
+        Assert.assertEquals(69.0, Vectors.norm1(-22.0, -23.0, -24.0), EPS);
+    }
+
+    @Test
+    public void testNorm_oneD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.norm(0.0), EPS);
+
+        Assert.assertEquals(2.0, Vectors.norm(-2.0), EPS);
+        Assert.assertEquals(1.0, Vectors.norm(-1.0), EPS);
+
+        Assert.assertEquals(1.0, Vectors.norm(1.0), EPS);
+        Assert.assertEquals(2.0, Vectors.norm(2.0), EPS);
+    }
+
+    @Test
+    public void testNorm_twoD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.norm(0.0, 0.0), EPS);
+
+        Assert.assertEquals(Math.sqrt(5.0), Vectors.norm(1.0, 2.0), EPS);
+        Assert.assertEquals(5.0, Vectors.norm(3.0, -4.0), EPS);
+        Assert.assertEquals(Math.sqrt(61.0), Vectors.norm(-5.0, 6.0), EPS);
+        Assert.assertEquals(Math.sqrt(130.0), Vectors.norm(-7.0, -9.0), EPS);
+    }
+
+    @Test
+    public void testNorm_threeD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.norm(0.0, 0.0, 0.0), EPS);
+
+        Assert.assertEquals(Math.sqrt(14.0), Vectors.norm(1.0, 2.0, 3.0), EPS);
+        Assert.assertEquals(Math.sqrt(77.0), Vectors.norm(4.0, 5.0, -6.0), EPS);
+        Assert.assertEquals(Math.sqrt(194.0), Vectors.norm(7.0, -8.0, 9.0), EPS);
+        Assert.assertEquals(Math.sqrt(365.0), Vectors.norm(10.0, -11.0, -12.0), EPS);
+        Assert.assertEquals(Math.sqrt(590.0), Vectors.norm(-13.0, 14.0, 15.0), EPS);
+        Assert.assertEquals(Math.sqrt(869.0), Vectors.norm(-16.0, 17.0, -18.0), EPS);
+        Assert.assertEquals(Math.sqrt(1202.0), Vectors.norm(-19.0, -20.0, 21.0), EPS);
+        Assert.assertEquals(Math.sqrt(1589.0), Vectors.norm(-22.0, -23.0, -24.0), EPS);
+    }
+
+    @Test
+    public void testNormSq_oneD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.normSq(0.0), EPS);
+
+        Assert.assertEquals(9.0, Vectors.normSq(-3.0), EPS);
+        Assert.assertEquals(1.0, Vectors.normSq(-1.0), EPS);
+
+        Assert.assertEquals(1.0, Vectors.normSq(1.0), EPS);
+        Assert.assertEquals(9.0, Vectors.normSq(3.0), EPS);
+    }
+
+    @Test
+    public void testNormSq_twoD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.normSq(0.0, 0.0), EPS);
+
+        Assert.assertEquals(5.0, Vectors.normSq(1.0, 2.0), EPS);
+        Assert.assertEquals(25.0, Vectors.normSq(3.0, -4.0), EPS);
+        Assert.assertEquals(61.0, Vectors.normSq(-5.0, 6.0), EPS);
+        Assert.assertEquals(130.0, Vectors.normSq(-7.0, -9.0), EPS);
+    }
+
+    @Test
+    public void testNormSq_threeD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.normSq(0.0, 0.0, 0.0), EPS);
+
+        Assert.assertEquals(14.0, Vectors.normSq(1.0, 2.0, 3.0), EPS);
+        Assert.assertEquals(77.0, Vectors.normSq(4.0, 5.0, -6.0), EPS);
+        Assert.assertEquals(194.0, Vectors.normSq(7.0, -8.0, 9.0), EPS);
+        Assert.assertEquals(365.0, Vectors.normSq(10.0, -11.0, -12.0), EPS);
+        Assert.assertEquals(590.0, Vectors.normSq(-13.0, 14.0, 15.0), EPS);
+        Assert.assertEquals(869.0, Vectors.normSq(-16.0, 17.0, -18.0), EPS);
+        Assert.assertEquals(1202.0, Vectors.normSq(-19.0, -20.0, 21.0), EPS);
+        Assert.assertEquals(1589.0, Vectors.normSq(-22.0, -23.0, -24.0), EPS);
+    }
+
+    @Test
+    public void testNormInf_oneD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.normInf(0.0), EPS);
+
+        Assert.assertEquals(2.0, Vectors.normInf(-2.0), EPS);
+        Assert.assertEquals(1.0, Vectors.normInf(-1.0), EPS);
+
+        Assert.assertEquals(1.0, Vectors.normInf(1.0), EPS);
+        Assert.assertEquals(2.0, Vectors.normInf(2.0), EPS);
+    }
+
+    @Test
+    public void testNormInf_twoD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.normInf(0.0, 0.0), EPS);
+
+        Assert.assertEquals(2.0, Vectors.normInf(2.0, 1.0), EPS);
+        Assert.assertEquals(4.0, Vectors.normInf(3.0, -4.0), EPS);
+        Assert.assertEquals(6.0, Vectors.normInf(-6.0, 5.0), EPS);
+        Assert.assertEquals(9.0, Vectors.normInf(-7.0, -9.0), EPS);
+    }
+
+    @Test
+    public void testNormInf_threeD() {
+        // act/assert
+        Assert.assertEquals(0.0, Vectors.normInf(0.0, 0.0, 0.0), EPS);
+
+        Assert.assertEquals(3.0, Vectors.normInf(1.0, 3.0, 2.0), EPS);
+        Assert.assertEquals(6.0, Vectors.normInf(6.0, 5.0, -4.0), EPS);
+        Assert.assertEquals(9.0, Vectors.normInf(7.0, -9.0, 8.0), EPS);
+        Assert.assertEquals(12.0, Vectors.normInf(10.0, -11.0, -12.0), EPS);
+        Assert.assertEquals(15.0, Vectors.normInf(-13.0, 14.0, 15.0), EPS);
+        Assert.assertEquals(18.0, Vectors.normInf(-16.0, 17.0, -18.0), EPS);
+        Assert.assertEquals(21.0, Vectors.normInf(-21.0, -19.0, 20.0), EPS);
+        Assert.assertEquals(24.0, Vectors.normInf(-22.0, -23.0, -24.0), EPS);
+    }
+}
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser3DTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser3DTest.java
index b7fc5e0..8d5e3d2 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser3DTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/enclosing/WelzlEncloser3DTest.java
@@ -109,13 +109,13 @@ public void testLargeSamples() throws IOException {
             // define the reference sphere we want to compute
             double d = 25 * random.nextDouble();
             double refRadius = 10 * random.nextDouble();
-            Point3D refCenter = Point3D.vectorCombination(d, Point3D.of(sr.nextVector()));
+            Point3D refCenter = Point3D.vectorCombination(d, Point3D.ofArray(sr.nextVector()));
             // set up a large sample inside the reference sphere
             int nbPoints = random.nextInt(1000);
             List<Point3D> points = new ArrayList<>();
             for (int i = 0; i < nbPoints; ++i) {
                 double r = refRadius * random.nextDouble();
-                points.add(Point3D.vectorCombination(1.0, refCenter, r, Point3D.of(sr.nextVector())));
+                points.add(Point3D.vectorCombination(1.0, refCenter, r, Point3D.ofArray(sr.nextVector())));
             }
 
             // test we find a sphere at most as large as the one used for random drawings
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGeneratorTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGeneratorTest.java
index b65d726..8ab75e4 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGeneratorTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/threed/enclosing/SphereGeneratorTest.java
@@ -139,10 +139,10 @@ public void testRandom() {
         for (int i = 0; i < 100; ++i) {
             double d = 25 * random.nextDouble();
             double refRadius = 10 * random.nextDouble();
-            Point3D refCenter = Point3D.vectorCombination(d, Point3D.of(sr.nextVector()));
+            Point3D refCenter = Point3D.vectorCombination(d, Point3D.ofArray(sr.nextVector()));
             List<Point3D> support = new ArrayList<>();
             for (int j = 0; j < 5; ++j) {
-                support.add(Point3D.vectorCombination(1.0, refCenter, refRadius, Point3D.of(sr.nextVector())));
+                support.add(Point3D.vectorCombination(1.0, refCenter, refRadius, Point3D.ofArray(sr.nextVector())));
             }
             EnclosingBall<Point3D> sphere = new SphereGenerator().ballOnSupport(support);
             Assert.assertEquals(0.0, refCenter.distance(sphere.getCenter()), 4e-7 * refRadius);
diff --git a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGeneratorTest.java b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGeneratorTest.java
index 8770b6b..e5972e5 100644
--- a/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGeneratorTest.java
+++ b/commons-geometry-enclosing/src/test/java/org/apache/commons/geometry/euclidean/twod/enclosing/DiskGeneratorTest.java
@@ -106,10 +106,10 @@ public void testRandom() {
         for (int i = 0; i < 500; ++i) {
             double d = 25 * random.nextDouble();
             double refRadius = 10 * random.nextDouble();
-            Point2D refCenter = Point2D.vectorCombination(d, Point2D.of(sr.nextVector()));
+            Point2D refCenter = Point2D.vectorCombination(d, Point2D.ofArray(sr.nextVector()));
             List<Point2D> support = new ArrayList<>();
             for (int j = 0; j < 3; ++j) {
-                support.add(Point2D.vectorCombination(1.0, refCenter, refRadius, Point2D.of(sr.nextVector())));
+                support.add(Point2D.vectorCombination(1.0, refCenter, refRadius, Point2D.ofArray(sr.nextVector())));
             }
             EnclosingBall<Point2D> disk = new DiskGenerator().ballOnSupport(support);
             Assert.assertEquals(0.0, refCenter.distance(disk.getCenter()), 3e-9 * refRadius);
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanPoint.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanPoint.java
index 033b6bd..bde9f67 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanPoint.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanPoint.java
@@ -40,4 +40,16 @@
      * @return vector representing the displacement <em>from</em> this point <em>to</em> the given point
      */
     V vectorTo(P p);
+
+    /** Linearly interpolates between this point and the given point using the equation
+     * {@code P = (1 - t)*A + t*B}, where {@code A} is the current point and {@code B}
+     * is the given point. This means that if {@code t = 0}, a point equal to the current
+     * point will be returned. If {@code t = 1}, a point equal to the argument will be returned.
+     * The {@code t} parameter is not constrained to the range {@code [0, 1]}, meaning that
+     * linear extrapolation can also be performed with this method.
+     * @param p other point
+     * @param t interpolation parameter
+     * @return interpolated or extrapolated point
+     */
+    P lerp(P p, double t);
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVector.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVector.java
index 0622c89..ad4f21b 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVector.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVector.java
@@ -32,4 +32,16 @@
      * @return point with the same coordinates as this vector
      */
     P asPoint();
+
+    /** Linearly interpolates between this vector and the given vector using the equation
+     * {@code V = (1 - t)*A + t*B}, where {@code A} is the current vector and {@code B}
+     * is the given vector. This means that if {@code t = 0}, a vector equal to the current
+     * vector will be returned. If {@code t = 1}, a vector equal to the argument will be returned.
+     * The {@code t} parameter is not constrained to the range {@code [0, 1]}, meaning that
+     * linear extrapolation can also be performed with this method.
+     * @param v other vector
+     * @param t interpolation parameter
+     * @return interpolated or extrapolated vector
+     */
+    V lerp(V v, double t);
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/ZeroNormException.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/ZeroNormException.java
new file mode 100644
index 0000000..fe9ecf1
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/ZeroNormException.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.euclidean.internal;
+
+/** Internal exception class with constants for frequently used messages.
+ * This exception is thrown when vector operations requiring a non-zero
+ * vector norm are attempted with a vector with a zero norm.
+ */
+public class ZeroNormException extends IllegalStateException {
+
+    /** Default zero-norm error message. */
+    public static final String ZERO_NORM_MSG = "Norm is zero";
+
+    /** Error message for cases where code is attempting to use a zero-norm vector
+     * as a base vector.
+     */
+    public static final String INVALID_BASE = "Invalid base vector: norm is zero";
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20180903L;
+
+    /**
+     * Simple constructor, using the default error message.
+     */
+    public ZeroNormException() {
+        this(ZERO_NORM_MSG);
+    }
+
+    /**
+     * Constructs an instance with the given error message.
+     * @param msg error message
+     */
+    public ZeroNormException(String msg) {
+        super(msg);
+    }
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/package-info.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/package-info.java
new file mode 100644
index 0000000..e70a48b
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+/**
+ *
+ * <p>
+ * This package contains Euclidean geometry utilities and classes intended
+ * for internal use only. No guarantees are made for the stability of the
+ * contained APIs.
+ * </p>
+ */
+package org.apache.commons.geometry.euclidean.internal;
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java
index 2100e72..eec4b79 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Point1D.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
-import org.apache.commons.geometry.core.internal.DoubleFunction1N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
 import org.apache.commons.numbers.arrays.LinearCombination;
@@ -32,6 +31,9 @@
     /** Unit (coordinates: 1). */
     public static final Point1D ONE  = new Point1D(1.0);
 
+    /** Negative unit (coordinates: 1). */
+    public static final Point1D MINUS_ONE  = new Point1D(-1.0);
+
     // CHECKSTYLE: stop ConstantName
     /** A vector with all coordinates set to NaN. */
     public static final Point1D NaN = new Point1D(Double.NaN);
@@ -48,16 +50,6 @@
     /** Serializable UID. */
     private static final long serialVersionUID = 20180710L;
 
-    /** Factory for delegating instance creation. */
-    private static DoubleFunction1N<Point1D> FACTORY = new DoubleFunction1N<Point1D>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public Point1D apply(double n) {
-            return new Point1D(n);
-        }
-    };
-
     /** Simple constructor.
      * @param x abscissa (coordinate value)
      */
@@ -68,7 +60,7 @@ private Point1D(double x) {
     /** {@inheritDoc} */
     @Override
     public Vector1D asVector() {
-        return Vector1D.of(this);
+        return Vector1D.of(getX());
     }
 
     /** {@inheritDoc} */
@@ -89,6 +81,12 @@ public Vector1D vectorTo(Point1D p) {
         return p.subtract(this);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Point1D lerp(Point1D p, double t) {
+        return vectorCombination(1.0 - t, this, t, p);
+    }
+
     /** {@inheritDoc} */
     @Override
     public Point1D add(Vector1D v) {
@@ -153,14 +151,6 @@ public static Point1D of(double x) {
         return new Point1D(x);
     }
 
-    /** Returns a point instance with the given coordinate value.
-     * @param value point coordinate
-     * @return point instance
-     */
-    public static Point1D of(Cartesian1D value) {
-        return new Point1D(value.getX());
-    }
-
     /** Parses the given string and returns a new point instance. The expected string
      * format is the same as that returned by {@link #toString()}.
      * @param str the string to parse
@@ -168,7 +158,7 @@ public static Point1D of(Cartesian1D value) {
      * @throws IllegalArgumentException if the given string has an invalid format
      */
     public static Point1D parse(String str) throws IllegalArgumentException {
-        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(str, Point1D::new);
     }
 
     /** Returns a point with coordinates calculated by multiplying each input coordinate
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 1a915f6..df40670 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
@@ -16,9 +16,11 @@
  */
 package org.apache.commons.geometry.euclidean.oned;
 
-import org.apache.commons.geometry.core.internal.DoubleFunction1N;
+import org.apache.commons.geometry.core.Geometry;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
+import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
+import org.apache.commons.geometry.euclidean.internal.ZeroNormException;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in one-dimensional Euclidean space.
@@ -32,6 +34,9 @@
     /** Unit vector (coordinates: 1). */
     public static final Vector1D ONE  = new Vector1D(1.0);
 
+    /** Negation of unit vector (coordinates: -1). */
+    public static final Vector1D MINUS_ONE = new Vector1D(-1.0);
+
     // CHECKSTYLE: stop ConstantName
     /** A vector with all coordinates set to NaN. */
     public static final Vector1D NaN = new Vector1D(Double.NaN);
@@ -48,16 +53,6 @@
     /** Serializable UID. */
     private static final long serialVersionUID = 20180710L;
 
-    /** Factory for delegating instance creation. */
-    private static DoubleFunction1N<Vector1D> FACTORY = new DoubleFunction1N<Vector1D>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public Vector1D apply(double n) {
-            return new Vector1D(n);
-        }
-    };
-
     /** Simple constructor.
      * @param x abscissa (coordinate value)
      */
@@ -68,7 +63,13 @@ private Vector1D(double x) {
     /** {@inheritDoc} */
     @Override
     public Point1D asPoint() {
-        return Point1D.of(this);
+        return Point1D.of(getX());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector1D lerp(Vector1D p, double t) {
+        return linearCombination(1.0 - t, this, t, p);
     }
 
     /** {@inheritDoc} */
@@ -80,27 +81,52 @@ public Vector1D getZero() {
     /** {@inheritDoc} */
     @Override
     public double getNorm1() {
-        return getNorm();
+        return Vectors.norm1(getX());
     }
 
     /** {@inheritDoc} */
     @Override
     public double getNorm() {
-        return Math.abs(getX());
+        return Vectors.norm(getX());
     }
 
     /** {@inheritDoc} */
     @Override
     public double getNormSq() {
-        return getX() * getX();
+        return Vectors.normSq(getX());
     }
 
     /** {@inheritDoc} */
     @Override
     public double getNormInf() {
+        return Vectors.normInf(getX());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMagnitude() {
         return getNorm();
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public double getMagnitudeSq() {
+        return getNormSq();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector1D withMagnitude(double magnitude) {
+        final double x = getX();
+        if (x > 0.0) {
+            return new Vector1D(magnitude);
+        }
+        else if (x < 0.0) {
+            return new Vector1D(-magnitude);
+        }
+        throw new ZeroNormException();
+    }
+
     /** {@inheritDoc} */
     @Override
     public Vector1D add(Vector1D v) {
@@ -134,11 +160,14 @@ public Vector1D negate() {
     /** {@inheritDoc} */
     @Override
     public Vector1D normalize() throws IllegalStateException {
-        double s = getNorm();
-        if (s == 0) {
-            throw new IllegalStateException("Norm is zero");
+        final double x = getX();
+        if (x > 0.0) {
+            return ONE;
+        }
+        else if (x < 0.0) {
+            return MINUS_ONE;
         }
-        return scalarMultiply(1.0 / s);
+        throw new ZeroNormException();
     }
 
     /** {@inheritDoc} */
@@ -150,26 +179,25 @@ public Vector1D scalarMultiply(double a) {
     /** {@inheritDoc} */
     @Override
     public double distance1(Vector1D v) {
-        return distance(v);
+        return Vectors.norm1(getX() - v.getX());
     }
 
     /** {@inheritDoc} */
     @Override
     public double distance(Vector1D v) {
-        return Math.abs(v.getX() - getX());
+        return Vectors.norm(getX() - v.getX());
     }
 
     /** {@inheritDoc} */
     @Override
     public double distanceInf(Vector1D v) {
-        return distance(v);
+        return Vectors.normInf(getX() - v.getX());
     }
 
     /** {@inheritDoc} */
     @Override
     public double distanceSq(Vector1D v) {
-        final double dx = v.getX() - getX();
-        return dx * dx;
+        return Vectors.normSq(getX() - v.getX());
     }
 
     /** {@inheritDoc} */
@@ -178,6 +206,44 @@ public double dotProduct(Vector1D v) {
         return getX() * v.getX();
     }
 
+    /** {@inheritDoc}
+     * <p>For the one-dimensional case, this method simply returns the current instance.</p>
+     */
+    @Override
+    public Vector1D project(final Vector1D base) throws IllegalStateException {
+        if (base.getX() == 0) {
+            throw new ZeroNormException(ZeroNormException.INVALID_BASE);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc}
+     * <p>For the one-dimensional case, this method simply returns the zero vector.</p>
+     */
+    @Override
+    public Vector1D reject(final Vector1D base) throws IllegalStateException {
+        if (base.getX() == 0) {
+            throw new ZeroNormException(ZeroNormException.INVALID_BASE);
+        }
+        return Vector1D.ZERO;
+    }
+
+    /** {@inheritDoc}
+     * <p>For the one-dimensional case, this method returns 0 if the vector x values have
+     * the same sign and {@code pi} if they are opposite.</p>
+     */
+    @Override
+    public double angle(final Vector1D v) throws IllegalStateException {
+        final double sig1 = Math.signum(getX());
+        final double sig2 = Math.signum(v.getX());
+
+        if (sig1 == 0 || sig2 == 0) {
+            throw new ZeroNormException();
+        }
+        // the angle is 0 if the x value signs are the same and pi if not
+        return (sig1 == sig2) ? 0.0 : Geometry.PI;
+    }
+
     /**
      * Get a hashCode for the vector.
      * <p>All NaN values have the same hash code.</p>
@@ -237,14 +303,6 @@ public static Vector1D of(double x) {
         return new Vector1D(x);
     }
 
-    /** Returns a vector instance with the given coordinate value.
-     * @param value vector coordinate
-     * @return vector instance
-     */
-    public static Vector1D of(Cartesian1D value) {
-        return new Vector1D(value.getX());
-    }
-
     /** Parses the given string and returns a new vector instance. The expected string
      * format is the same as that returned by {@link #toString()}.
      * @param str the string to parse
@@ -252,7 +310,7 @@ public static Vector1D of(Cartesian1D value) {
      * @throws IllegalArgumentException if the given string has an invalid format
      */
     public static Vector1D parse(String str) throws IllegalArgumentException {
-        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(str, Vector1D::new);
     }
 
     /** Returns a vector consisting of the linear combination of the inputs.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Cartesian3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Cartesian3D.java
index f8c07b9..fe3b662 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Cartesian3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Cartesian3D.java
@@ -108,17 +108,4 @@ public boolean isInfinite() {
     public String toString() {
         return SimpleTupleFormat.getDefault().format(getX(), getY(), getZ());
     }
-
-    /** Returns the Euclidean distance from this set of coordinates to the given coordinates.
-     * @param other coordinates to compute the distance to.
-     * @return Euclidean distance value
-     */
-    protected double euclideanDistance(Cartesian3D other) {
-        // there are no cancellation problems here, so we use the straightforward formula
-        final double dx = x - other.x;
-        final double dy = y - other.y;
-        final double dz = z - other.z;
-
-        return Math.sqrt((dx * dx) + (dy * dy) + (dz * dz));
-    }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java
index e099130..8e15230 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Point3D.java
@@ -17,8 +17,8 @@
 
 package org.apache.commons.geometry.euclidean.threed;
 
-import org.apache.commons.geometry.core.internal.DoubleFunction3N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
+import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -43,16 +43,6 @@
     public static final Point3D NEGATIVE_INFINITY =
         new Point3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
 
-    /** Package private factory for delegating instance creation. */
-    static final DoubleFunction3N<Point3D> FACTORY = new DoubleFunction3N<Point3D>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public Point3D apply(double n1, double n2, double n3) {
-            return new Point3D(n1, n2, n3);
-        }
-    };
-
     /** Serializable version identifier. */
     private static final long serialVersionUID = 20180710L;
 
@@ -75,7 +65,10 @@ public Vector3D asVector() {
     /** {@inheritDoc} */
     @Override
     public double distance(Point3D p) {
-        return euclideanDistance(p);
+        return Vectors.norm(
+                getX() - p.getX(),
+                getY() - p.getY(),
+                getZ() - p.getZ());
     }
 
     /** {@inheritDoc} */
@@ -94,6 +87,12 @@ public Vector3D vectorTo(Point3D p) {
         return p.subtract(this);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Point3D lerp(Point3D p, double t) {
+        return vectorCombination(1.0 - t, this, t, p);
+    }
+
     /** {@inheritDoc} */
     @Override
     public Point3D add(Vector3D v) {
@@ -164,20 +163,12 @@ public static Point3D of(double x, double y, double z) {
         return new Point3D(x, y, z);
     }
 
-    /** Returns a point with the given coordinates.
-     * @param value coordinate values
-     * @return point instance
-     */
-    public static Point3D of(Cartesian3D value) {
-        return new Point3D(value.getX(), value.getY(), value.getZ());
-    }
-
     /** Creates a point from the coordinates in the given 3-element array.
      * @param p coordinates array
      * @return new point
      * @exception IllegalArgumentException if the array does not have 3 elements
      */
-    public static Point3D of(double[] p) {
+    public static Point3D ofArray(double[] p) {
         if (p.length != 3) {
             throw new IllegalArgumentException("Dimension mismatch: " + p.length + " != 3");
         }
@@ -192,7 +183,7 @@ public static Point3D of(double[] p) {
      * @return a point instance with the given set of spherical coordinates
      */
     public static Point3D ofSpherical(double radius, double azimuth, double polar) {
-        return SphericalCoordinates.toCartesian(radius, azimuth, polar, FACTORY);
+        return SphericalCoordinates.toCartesian(radius, azimuth, polar, Point3D::new);
     }
 
     /** Parses the given string and returns a new point instance. The expected string
@@ -202,7 +193,7 @@ public static Point3D ofSpherical(double radius, double azimuth, double polar) {
      * @throws IllegalArgumentException if the given string has an invalid format
      */
     public static Point3D parse(String str) throws IllegalArgumentException {
-        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(str, Point3D::new);
     }
 
     /** Returns a point with coordinates calculated by multiplying each input coordinate
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java
index a24129c..272d5c0 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java
@@ -94,7 +94,7 @@
   public static final Rotation IDENTITY = new Rotation(1.0, 0.0, 0.0, 0.0, false);
 
   /** Serializable version identifier */
-  private static final long serialVersionUID = -2153622329907944313L;
+  private static final long serialVersionUID = 20180903L;
 
   /** Error message for Cardan angle singularities */
   private static final String CARDAN_SINGULARITY_MSG = "Cardan angles singularity";
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 86b664c..9c244d0 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
@@ -70,16 +70,6 @@
     /** Serializable version identifier. */
     private static final long serialVersionUID = 20180623L;
 
-    /** Factory object for delegating instance creation. */
-    private static final DoubleFunction3N<SphericalCoordinates> FACTORY = new DoubleFunction3N<SphericalCoordinates>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public SphericalCoordinates apply(double n1, double n2, double n3) {
-            return new SphericalCoordinates(n1, n2, n3);
-        }
-    };
-
     /** Radius value. */
     private final double radius;
 
@@ -153,7 +143,7 @@ public boolean isInfinite() {
      *      coordinates.
      */
     public Vector3D toVector() {
-        return toCartesian(radius, azimuth, polar, Vector3D.FACTORY);
+        return toCartesian(radius, azimuth, polar, Vector3D::of);
     }
 
     /** Convert this set of spherical coordinates to a 3 dimensional point.
@@ -161,7 +151,7 @@ public Vector3D toVector() {
     *      coordinates.
     */
     public Point3D toPoint() {
-        return toCartesian(radius, azimuth, polar, Point3D.FACTORY);
+        return toCartesian(radius, azimuth, polar, Point3D::of);
     }
 
     /** Get a hashCode for this set of spherical coordinates.
@@ -257,7 +247,7 @@ public static SphericalCoordinates ofCartesian(final double x, final double y, f
      * @throws IllegalArgumentException if the string format is invalid.
      */
     public static SphericalCoordinates parse(String input) {
-        return SimpleTupleFormat.getDefault().parse(input, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(input, SphericalCoordinates::new);
     }
 
     /** Normalize an azimuth value to be within the range {@code [0, 2pi)}. This
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
index 078a5d3..fd52558 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Vector3D.java
@@ -16,9 +16,10 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
-import org.apache.commons.geometry.core.internal.DoubleFunction3N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
+import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
+import org.apache.commons.geometry.euclidean.internal.ZeroNormException;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in three-dimensional Euclidean space.
@@ -60,22 +61,9 @@
     public static final Vector3D NEGATIVE_INFINITY =
         new Vector3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
 
-    /** Package private factory for delegating instance creation. */
-    static final DoubleFunction3N<Vector3D> FACTORY = new DoubleFunction3N<Vector3D>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public Vector3D apply(double n1, double n2, double n3) {
-            return new Vector3D(n1, n2, n3);
-        }
-    };
-
     /** Serializable UID */
     private static final long serialVersionUID = 20180710L;
 
-    /** Error message when norms are zero. */
-    private static final String ZERO_NORM_MSG = "Norm is zero";
-
     /** Simple constructor.
      * Build a vector from its coordinates
      * @param x abscissa
@@ -98,36 +86,58 @@ public Point3D asPoint() {
         return Point3D.of(getX(), getY(), getZ());
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D lerp(Vector3D p, double t) {
+        return linearCombination(1.0 - t, this, t, p);
+    }
+
     /** {@inheritDoc} */
     @Override
     public double getNorm1() {
-        return Math.abs(getX()) + Math.abs(getY()) + Math.abs(getZ());
+        return Vectors.norm1(getX(), getY(), getZ());
     }
 
     /** {@inheritDoc} */
     @Override
     public double getNorm() {
-        // there are no cancellation problems here, so we use the straightforward formula
-        final double x = getX();
-        final double y = getY();
-        final double z = getZ();
-        return Math.sqrt ((x * x) + (y * y) + (z * z));
+        return Vectors.norm(getX(), getY(), getZ());
     }
 
     /** {@inheritDoc} */
     @Override
     public double getNormSq() {
-        // there are no cancellation problems here, so we use the straightforward formula
-        final double x = getX();
-        final double y = getY();
-        final double z = getZ();
-        return (x * x) + (y * y) + (z * z);
+        return Vectors.normSq(getX(), getY(), getZ());
     }
 
     /** {@inheritDoc} */
     @Override
     public double getNormInf() {
-        return Math.max(Math.max(Math.abs(getX()), Math.abs(getY())), Math.abs(getZ()));
+        return Vectors.normInf(getX(), getY(), getZ());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMagnitude() {
+        return getNorm();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMagnitudeSq() {
+        return getNormSq();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D withMagnitude(double magnitude) {
+        final double invNorm = 1.0 / getNonZeroNorm();
+
+        return new Vector3D(
+                    magnitude * getX() * invNorm,
+                    magnitude * getY() * invNorm,
+                    magnitude * getZ() * invNorm
+                );
     }
 
     /** {@inheritDoc} */
@@ -179,11 +189,7 @@ public Vector3D negate() {
     /** {@inheritDoc} */
     @Override
     public Vector3D normalize() throws IllegalStateException {
-        double s = getNorm();
-        if (s == 0) {
-            throw new IllegalStateException(ZERO_NORM_MSG);
-        }
-        return scalarMultiply(1 / s);
+        return scalarMultiply(1.0 / getNonZeroNorm());
     }
 
     /** Get a vector orthogonal to the instance.
@@ -202,10 +208,7 @@ public Vector3D normalize() throws IllegalStateException {
      * @exception IllegalStateException if the norm of the instance is zero
      */
     public Vector3D orthogonal() throws IllegalStateException {
-        double threshold = 0.6 * getNorm();
-        if (threshold == 0) {
-            throw new IllegalStateException(ZERO_NORM_MSG);
-        }
+        double threshold = 0.6 * getNonZeroNorm();
 
         final double x = getX();
         final double y = getY();
@@ -222,21 +225,16 @@ public Vector3D orthogonal() throws IllegalStateException {
         return new Vector3D(inverse * y, -inverse * x, 0);
     }
 
-    /** Compute the angular separation between two vectors.
+    /** {@inheritDoc}
      * <p>This method computes the angular separation between two
      * vectors using the dot product for well separated vectors and the
      * cross product for almost aligned vectors. This allows to have a
      * good accuracy in all cases, even for vectors very close to each
      * other.</p>
-     * @param v other vector
-     * @return angular separation between this instance and v
-     * @exception IllegalStateException if either vector has a zero norm
      */
+    @Override
     public double angle(Vector3D v) throws IllegalStateException {
-        double normProduct = getNorm() * v.getNorm();
-        if (normProduct == 0) {
-            throw new IllegalStateException(ZERO_NORM_MSG);
-        }
+        double normProduct = getNonZeroNorm() * v.getNonZeroNorm();
 
         double dot = dotProduct(v);
         double threshold = normProduct * 0.9999;
@@ -272,37 +270,41 @@ public Vector3D scalarMultiply(double a) {
     /** {@inheritDoc} */
     @Override
     public double distance1(Vector3D v) {
-        double dx = Math.abs(v.getX() - getX());
-        double dy = Math.abs(v.getY() - getY());
-        double dz = Math.abs(v.getZ() - getZ());
-
-        return dx + dy + dz;
+        return Vectors.norm1(
+                    getX() - v.getX(),
+                    getY() - v.getY(),
+                    getZ() - v.getZ()
+                );
     }
 
     /** {@inheritDoc} */
     @Override
     public double distance(Vector3D v) {
-        return euclideanDistance(v);
+        return Vectors.norm(
+                getX() - v.getX(),
+                getY() - v.getY(),
+                getZ() - v.getZ()
+            );
     }
 
     /** {@inheritDoc} */
     @Override
     public double distanceInf(Vector3D v) {
-        double dx = Math.abs(v.getX() - getX());
-        double dy = Math.abs(v.getY() - getY());
-        double dz = Math.abs(v.getZ() - getZ());
-
-        return Math.max(Math.max(dx, dy), dz);
+        return Vectors.normInf(
+                getX() - v.getX(),
+                getY() - v.getY(),
+                getZ() - v.getZ()
+            );
     }
 
     /** {@inheritDoc} */
     @Override
     public double distanceSq(Vector3D v) {
-        double dx = v.getX() - getX();
-        double dy = v.getY() - getY();
-        double dz = v.getZ() - getZ();
-
-        return (dx * dx) + (dy * dy) + (dz * dz);
+        return Vectors.normSq(
+                getX() - v.getX(),
+                getY() - v.getY(),
+                getZ() - v.getZ()
+            );
     }
 
     /** {@inheritDoc}
@@ -318,6 +320,18 @@ public double dotProduct(Vector3D v) {
         return LinearCombination.value(getX(), v.getX(), getY(), v.getY(), getZ(), v.getZ());
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D project(Vector3D base) throws IllegalStateException {
+        return getComponent(base, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D reject(Vector3D base) throws IllegalStateException {
+        return getComponent(base, true);
+    }
+
     /**
      * Get a hashCode for the vector.
      * <p>All NaN values have the same hash code.</p>
@@ -368,37 +382,49 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** Computes the dot product between to vectors. This method simply
-     * calls {@code v1.dotProduct(v2)}.
-     * @param v1 first vector
-     * @param v2 second vector
-     * @return the dot product
-     * @see #dotProduct(Vector3D)
+    /** Returns the vector norm, throwing an IllegalStateException if the norm is zero.
+     * @return the non-zero norm value
+     * @throws IllegalStateException if the norm is zero
      */
-    public static double dotProduct(Vector3D v1, Vector3D v2) {
-        return v1.dotProduct(v2);
-    }
+    private double getNonZeroNorm() throws IllegalStateException {
+        final double n = getNorm();
+        if (n == 0) {
+            throw new ZeroNormException();
+        }
 
-    /** Computes the angle in radians between two vectors. This method
-     * simply calls {@code v1.angle(v2)}.
-     * @param v1 first vector
-     * @param v2 second vector
-     * @return the angle between the vectors in radians
-     * @see #angle(Vector3D)
-     */
-    public static double angle(Vector3D v1, Vector3D v2) {
-        return v1.angle(v2);
+        return n;
     }
 
-    /** Computes the cross product between two vectors. This method simply
-     * calls {@code v1.crossProduct(v2)}.
-     * @param v1 first vector
-     * @param v2 second vector
-     * @return the computed cross product vector
-     * @see #crossProduct(Vector3D)
+    /** Returns a component of the current instance relative to the given base
+     * vector. If {@code reject} is true, the vector rejection is returned; otherwise,
+     * the projection is returned.
+     * @param base The base vector
+     * @param reject If true, the rejection of this instance from {@code base} is
+     *      returned. If false, the projection of this instance onto {@code base}
+     *      is returned.
+     * @return The projection or rejection of this instance relative to {@code base},
+     *      depending on the value of {@code reject}.
+     * @throws IllegalStateException if {@code base} has a zero norm
      */
-    public static Vector3D crossProduct(Vector3D v1, Vector3D v2) {
-        return v1.crossProduct(v2);
+    private Vector3D getComponent(Vector3D base, boolean reject) throws IllegalStateException {
+        final double aDotB = dotProduct(base);
+
+        final double baseMagSq = base.getNormSq();
+        if (baseMagSq == 0.0) {
+            throw new ZeroNormException(ZeroNormException.INVALID_BASE);
+        }
+
+        final double scale = aDotB / baseMagSq;
+
+        final double projX = scale * base.getX();
+        final double projY = scale * base.getY();
+        final double projZ = scale * base.getZ();
+
+        if (reject) {
+            return new Vector3D(getX() - projX, getY() - projY, getZ() - projZ);
+        }
+
+        return new Vector3D(projX, projY, projZ);
     }
 
     /** Returns a vector with the given coordinate values.
@@ -411,20 +437,12 @@ public static Vector3D of(double x, double y, double z) {
         return new Vector3D(x, y, z);
     }
 
-    /** Returns a vector instance with the given coordinate values.
-     * @param value vector coordinates
-     * @return vector instance
-     */
-    public static Vector3D of(Cartesian3D value) {
-        return new Vector3D(value.getX(), value.getY(), value.getZ());
-    }
-
     /** Creates a vector from the coordinates in the given 3-element array.
      * @param v coordinates array
      * @return new vector
      * @exception IllegalArgumentException if the array does not have 3 elements
      */
-    public static Vector3D of(double[] v) {
+    public static Vector3D ofArray(double[] v) {
         if (v.length != 3) {
             throw new IllegalArgumentException("Dimension mismatch: " + v.length + " != 3");
         }
@@ -439,7 +457,7 @@ public static Vector3D of(double[] v) {
      * @return a vector instance with the given set of spherical coordinates
      */
     public static Vector3D ofSpherical(double radius, double azimuth, double polar) {
-        return SphericalCoordinates.toCartesian(radius, azimuth, polar, FACTORY);
+        return SphericalCoordinates.toCartesian(radius, azimuth, polar, Vector3D::new);
     }
 
     /** Parses the given string and returns a new vector instance. The expected string
@@ -449,7 +467,7 @@ public static Vector3D ofSpherical(double radius, double azimuth, double polar)
      * @throws IllegalArgumentException if the given string has an invalid format
      */
     public static Vector3D parse(String str) throws IllegalArgumentException {
-        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(str, Vector3D::new);
     }
 
     /** Returns a vector consisting of the linear combination of the inputs.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Cartesian2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Cartesian2D.java
index 7ed4778..a4b48a9 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Cartesian2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Cartesian2D.java
@@ -97,14 +97,4 @@ public boolean isInfinite() {
     public String toString() {
         return SimpleTupleFormat.getDefault().format(getX(), getY());
     }
-
-    /** Returns the Euclidean distance from this value to the given value.
-     * @param other the set of coordinates to compute the distance to
-     * @return Euclidean distance
-     */
-    protected double euclideanDistance(Cartesian2D other) {
-        final double dx = x - other.x;
-        final double dy = y - other.y;
-        return Math.hypot(dx, dy);
-    }
 }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java
index 94c2272..e7a89cb 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Point2D.java
@@ -16,8 +16,8 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
-import org.apache.commons.geometry.core.internal.DoubleFunction2N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
+import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanPoint;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
@@ -42,16 +42,6 @@
     public static final Point2D NEGATIVE_INFINITY =
         new Point2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
 
-    /** Package private factory for delegating instance creation. */
-    static final DoubleFunction2N<Point2D> FACTORY = new DoubleFunction2N<Point2D>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public Point2D apply(double n1, double n2) {
-            return new Point2D(n1, n2);
-        }
-    };
-
     /** Serializable UID. */
     private static final long serialVersionUID = 20180710L;
 
@@ -73,7 +63,7 @@ public Vector2D asVector() {
     /** {@inheritDoc} */
     @Override
     public double distance(Point2D p) {
-        return euclideanDistance(p);
+        return Vectors.norm(getX() - p.getX(), getY() - p.getY());
     }
 
     /** {@inheritDoc} */
@@ -88,6 +78,12 @@ public Vector2D vectorTo(Point2D p) {
         return p.subtract(this);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Point2D lerp(Point2D p, double t) {
+        return vectorCombination(1.0 - t, this, t, p);
+    }
+
     /** {@inheritDoc} */
     @Override
     public Point2D add(Vector2D v) {
@@ -152,20 +148,12 @@ public static Point2D of(double x, double y) {
         return new Point2D(x, y);
     }
 
-    /** Returns a point with the given coordinates.
-     * @param value coordinate values
-     * @return point instance
-     */
-    public static Point2D of(Cartesian2D value) {
-        return new Point2D(value.getX(), value.getY());
-    }
-
     /** Returns a point with the coordinates from the given 2-element array.
      * @param p coordinates array
      * @return new point
      * @exception IllegalArgumentException if the array does not have 2 elements
      */
-    public static Point2D of(double[] p) {
+    public static Point2D ofArray(double[] p) {
         if (p.length != 2) {
             throw new IllegalArgumentException("Dimension mismatch: " + p.length + " != 2");
         }
@@ -178,7 +166,7 @@ public static Point2D of(double[] p) {
      * @return point instance with coordinates equivalent to the given polar coordinates.
      */
     public static Point2D ofPolar(final double radius, final double azimuth) {
-        return PolarCoordinates.toCartesian(radius, azimuth, FACTORY);
+        return PolarCoordinates.toCartesian(radius, azimuth, Point2D::new);
     }
 
     /** Parses the given string and returns a new point instance. The expected string
@@ -188,7 +176,7 @@ public static Point2D ofPolar(final double radius, final double azimuth) {
      * @throws IllegalArgumentException if the given string has an invalid format
      */
     public static Point2D parse(String str) throws IllegalArgumentException {
-        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(str, Point2D::new);
     }
 
     /** Returns a point with coordinates calculated by multiplying each input coordinate
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinates.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinates.java
index c910629..cdf5b5b 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinates.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinates.java
@@ -55,16 +55,6 @@
     /** Serializable version UID */
     private static final long serialVersionUID = 20180630L;
 
-    /** Factory object for delegating instance creation. */
-    private static final DoubleFunction2N<PolarCoordinates> FACTORY = new DoubleFunction2N<PolarCoordinates>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public PolarCoordinates apply(double n1, double n2) {
-            return new PolarCoordinates(n1, n2);
-        }
-    };
-
     /** Radius value */
     private final double radius;
 
@@ -125,7 +115,7 @@ public boolean isInfinite() {
      *      coordinates.
      */
     public Vector2D toVector() {
-        return toCartesian(radius, azimuth, Vector2D.FACTORY);
+        return toCartesian(radius, azimuth, Vector2D::of);
     }
 
     /** Convert this set of polar coordinates to a 2-dimensional
@@ -134,7 +124,7 @@ public Vector2D toVector() {
      *      coordinates.
      */
     public Point2D toPoint() {
-        return toCartesian(radius, azimuth, Point2D.FACTORY);
+        return toCartesian(radius, azimuth, Point2D::of);
     }
 
     /** Get a hashCode for this set of polar coordinates.
@@ -222,7 +212,7 @@ public static PolarCoordinates ofCartesian(final double x, final double y) {
      * @throws IllegalArgumentException if the string format is invalid.
      */
     public static PolarCoordinates parse(String input) {
-        return SimpleTupleFormat.getDefault().parse(input, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(input, PolarCoordinates::new);
     }
 
     /** Normalize an azimuth value to be within the range {@code [0, 2pi)}.
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
index 36c300e..fec9764 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
@@ -16,9 +16,10 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
-import org.apache.commons.geometry.core.internal.DoubleFunction2N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
+import org.apache.commons.geometry.core.internal.Vectors;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
+import org.apache.commons.geometry.euclidean.internal.ZeroNormException;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in two-dimensional Euclidean space.
@@ -54,22 +55,9 @@
     public static final Vector2D NEGATIVE_INFINITY =
         new Vector2D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
 
-    /** Package private factory for delegating instance creation. */
-    static final DoubleFunction2N<Vector2D> FACTORY = new DoubleFunction2N<Vector2D>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public Vector2D apply(double n1, double n2) {
-            return new Vector2D(n1, n2);
-        }
-    };
-
     /** Serializable UID */
     private static final long serialVersionUID = 20180710L;
 
-    /** Error message when norms are zero. */
-    private static final String ZERO_NORM_MSG = "Norm is zero";
-
     /** Simple constructor.
      * @param x abscissa (first coordinate)
      * @param y ordinate (second coordinate)
@@ -92,6 +80,12 @@ public Point2D asPoint() {
         return Point2D.of(getX(), getY());
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D lerp(Vector2D p, double t) {
+        return linearCombination(1.0 - t, this, t, p);
+    }
+
     /** {@inheritDoc} */
     @Override
     public Vector2D getZero() {
@@ -101,29 +95,48 @@ public Vector2D getZero() {
     /** {@inheritDoc} */
     @Override
     public double getNorm1() {
-        return Math.abs(getX()) + Math.abs(getY());
+        return Vectors.norm1(getX(), getY());
     }
 
     /** {@inheritDoc} */
     @Override
     public double getNorm() {
-        final double x = getX();
-        final double y = getY();
-        return Math.sqrt ((x * x) + (y * y));
+        return Vectors.norm(getX(), getY());
     }
 
     /** {@inheritDoc} */
     @Override
     public double getNormSq() {
-        final double x = getX();
-        final double y = getY();
-        return (x * x) + (y * y);
+        return Vectors.normSq(getX(), getY());
     }
 
     /** {@inheritDoc} */
     @Override
     public double getNormInf() {
-        return Math.max(Math.abs(getX()), Math.abs(getY()));
+        return Vectors.normInf(getX(), getY());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMagnitude() {
+        return getNorm();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getMagnitudeSq() {
+        return getNormSq();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D withMagnitude(double magnitude) {
+        final double invNorm = 1.0 / getNonZeroNorm();
+
+        return new Vector2D(
+                    magnitude * getX() * invNorm,
+                    magnitude * getY() * invNorm
+                );
     }
 
     /** {@inheritDoc} */
@@ -159,11 +172,7 @@ public Vector2D negate() {
     /** {@inheritDoc} */
     @Override
     public Vector2D normalize() throws IllegalStateException {
-        double n = getNorm();
-        if (n == 0) {
-            throw new IllegalStateException(ZERO_NORM_MSG);
-        }
-        return scalarMultiply(1 / n);
+        return scalarMultiply(1.0 / getNonZeroNorm());
     }
 
     /** {@inheritDoc} */
@@ -175,31 +184,25 @@ public Vector2D scalarMultiply(double a) {
     /** {@inheritDoc} */
     @Override
     public double distance1(Vector2D v) {
-        double dx = Math.abs(getX() - v.getX());
-        double dy = Math.abs(getY() - v.getY());
-        return dx + dy;
+        return Vectors.norm1(getX() - v.getX(), getY() - v.getY());
     }
 
     /** {@inheritDoc} */
     @Override
     public double distance(Vector2D v) {
-        return euclideanDistance(v);
+        return Vectors.norm(getX() - v.getX(), getY() - v.getY());
     }
 
     /** {@inheritDoc} */
     @Override
     public double distanceInf(Vector2D v) {
-        double dx = Math.abs(getX() - v.getX());
-        double dy = Math.abs(getY() - v.getY());
-        return Math.max(dx, dy);
+        return Vectors.normInf(getX() - v.getX(), getY() - v.getY());
     }
 
     /** {@inheritDoc} */
     @Override
     public double distanceSq(Vector2D v) {
-        double dx = getX() - v.getX();
-        double dy = getY() - v.getY();
-        return (dx * dx) + (dy * dy);
+        return Vectors.normSq(getX() - v.getX(), getY() - v.getY());
     }
 
     /** {@inheritDoc} */
@@ -208,23 +211,28 @@ public double dotProduct(Vector2D v) {
         return LinearCombination.value(getX(), v.getX(), getY(), v.getY());
     }
 
-    /** Compute the angular separation in radians between this vector
-     * and the given vector.
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D project(Vector2D base) throws IllegalStateException {
+        return getComponent(base, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D reject(Vector2D base) throws IllegalStateException {
+        return getComponent(base, true);
+    }
+
+    /** {@inheritDoc}
      * <p>This method computes the angular separation between the two
      * vectors using the dot product for well separated vectors and the
      * cross product for almost aligned vectors. This allows to have a
      * good accuracy in all cases, even for vectors very close to each
      * other.</p>
-     *
-     * @param v vector to compute the angle with
-     * @return angular separation between this vector and v in radians
-     * @exception IllegalArgumentException if either vector has a zero norm
      */
-    public double angle(Vector2D v) throws IllegalArgumentException {
-        double normProduct = getNorm() * v.getNorm();
-        if (normProduct == 0) {
-            throw new IllegalArgumentException(ZERO_NORM_MSG);
-        }
+    @Override
+    public double angle(Vector2D v) throws IllegalStateException {
+        double normProduct = getNonZeroNorm() * v.getNonZeroNorm();
 
         double dot = dotProduct(v);
         double threshold = normProduct * 0.9999;
@@ -320,26 +328,48 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** Computes the dot product between to vectors. This method simply
-     * calls {@code v1.dotProduct(v2)}.
-     * @param v1 first vector
-     * @param v2 second vector
-     * @return the dot product
-     * @see #dotProduct(Vector2D)
+    /** Returns the vector norm, throwing an IllegalStateException if the norm is zero.
+     * @return the non-zero norm value
+     * @throws IllegalStateException if the norm is zero
      */
-    public static double dotProduct(Vector2D v1, Vector2D v2) {
-        return v1.dotProduct(v2);
+    private double getNonZeroNorm() throws IllegalStateException {
+        final double n = getNorm();
+        if (n == 0) {
+            throw new ZeroNormException();
+        }
+
+        return n;
     }
 
-    /** Computes the angle in radians between two vectors. This method
-     * simply calls {@code v1.angle(v2)}.
-     * @param v1 first vector
-     * @param v2 second vector
-     * @return the angle between the vectors in radians
-     * @see #angle(Vector2D)
+    /** Returns a component of the current instance relative to the given base
+     * vector. If {@code reject} is true, the vector rejection is returned; otherwise,
+     * the projection is returned.
+     * @param base The base vector
+     * @param reject If true, the rejection of this instance from {@code base} is
+     *      returned. If false, the projection of this instance onto {@code base}
+     *      is returned.
+     * @return The projection or rejection of this instance relative to {@code base},
+     *      depending on the value of {@code reject}.
+     * @throws IllegalStateException if {@code base} has a zero norm
      */
-    public static double angle(Vector2D v1, Vector2D v2) {
-        return v1.angle(v2);
+    private Vector2D getComponent(Vector2D base, boolean reject) throws IllegalStateException {
+        final double aDotB = dotProduct(base);
+
+        final double baseMagSq = base.getNormSq();
+        if (baseMagSq == 0.0) {
+            throw new ZeroNormException(ZeroNormException.INVALID_BASE);
+        }
+
+        final double scale = aDotB / baseMagSq;
+
+        final double projX = scale * base.getX();
+        final double projY = scale * base.getY();
+
+        if (reject) {
+            return new Vector2D(getX() - projX, getY() - projY);
+        }
+
+        return new Vector2D(projX, projY);
     }
 
     /** Returns a vector with the given coordinate values.
@@ -351,20 +381,12 @@ public static Vector2D of(double x, double y) {
         return new Vector2D(x, y);
     }
 
-    /** Returns a vector instance with the given coordinate values.
-     * @param value vector coordinates
-     * @return vector instance
-     */
-    public static Vector2D of(Cartesian2D value) {
-        return new Vector2D(value.getX(), value.getY());
-    }
-
     /** Creates a vector from the coordinates in the given 2-element array.
      * @param v coordinates array
      * @return new vector
      * @exception IllegalArgumentException if the array does not have 2 elements
      */
-    public static Vector2D of(double[] v) {
+    public static Vector2D ofArray(double[] v) {
         if (v.length != 2) {
             throw new IllegalArgumentException("Dimension mismatch: " + v.length + " != 2");
         }
@@ -377,7 +399,7 @@ public static Vector2D of(double[] v) {
      * @return vector instance with coordinates equivalent to the given polar coordinates.
      */
     public static Vector2D ofPolar(final double radius, final double azimuth) {
-        return PolarCoordinates.toCartesian(radius, azimuth, Vector2D.FACTORY);
+        return PolarCoordinates.toCartesian(radius, azimuth, Vector2D::new);
     }
 
     /** Parses the given string and returns a new vector instance. The expected string
@@ -387,7 +409,7 @@ public static Vector2D ofPolar(final double radius, final double azimuth) {
      * @throws IllegalArgumentException if the given string has an invalid format
      */
     public static Vector2D parse(String str) throws IllegalArgumentException {
-        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(str, Vector2D::new);
     }
 
     /** Returns a vector consisting of the linear combination of the inputs.
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java
index 4b65983..25b2fe6 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Point1DTest.java
@@ -32,6 +32,7 @@ public void testConstants() {
         // act/assert
         checkPoint(Point1D.ZERO, 0.0);
         checkPoint(Point1D.ONE, 1.0);
+        checkPoint(Point1D.MINUS_ONE, -1.0);
         checkPoint(Point1D.NaN, Double.NaN);
         checkPoint(Point1D.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
         checkPoint(Point1D.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
@@ -116,6 +117,32 @@ public void testVectorTo() {
         checkVector(p3.vectorTo(p2), -14.0);
     }
 
+    @Test
+    public void testLerp() {
+        // arrange
+        Point1D p1 = Point1D.of(1);
+        Point1D p2 = Point1D.of(-4);
+        Point1D p3 = Point1D.of(10);
+
+        // act/assert
+        checkPoint(p1.lerp(p1, 0), 1);
+        checkPoint(p1.lerp(p1, 1), 1);
+
+        checkPoint(p1.lerp(p2, -0.25), 2.25);
+        checkPoint(p1.lerp(p2, 0), 1);
+        checkPoint(p1.lerp(p2, 0.25), -0.25);
+        checkPoint(p1.lerp(p2, 0.5), -1.5);
+        checkPoint(p1.lerp(p2, 0.75), -2.75);
+        checkPoint(p1.lerp(p2, 1), -4);
+        checkPoint(p1.lerp(p2, 1.25), -5.25);
+
+        checkPoint(p1.lerp(p3, 0), 1);
+        checkPoint(p1.lerp(p3, 0.25), 3.25);
+        checkPoint(p1.lerp(p3, 0.5), 5.5);
+        checkPoint(p1.lerp(p3, 0.75), 7.75);
+        checkPoint(p1.lerp(p3, 1), 10);
+    }
+
     @Test
     public void testAdd() {
         // arrange
@@ -215,18 +242,6 @@ public void testOf() {
         checkPoint(Point1D.of(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY);
     }
 
-    @Test
-    public void testOf_coordinateArg() {
-        // act/assert
-        checkPoint(Point1D.of(Vector1D.of(0)), 0.0);
-        checkPoint(Point1D.of(Vector1D.of(-1)), -1.0);
-        checkPoint(Point1D.of(Vector1D.of(1)), 1.0);
-        checkPoint(Point1D.of(Vector1D.of(Math.PI)), Math.PI);
-        checkPoint(Point1D.of(Vector1D.of(Double.NaN)), Double.NaN);
-        checkPoint(Point1D.of(Vector1D.of(Double.NEGATIVE_INFINITY)), Double.NEGATIVE_INFINITY);
-        checkPoint(Point1D.of(Vector1D.of(Double.POSITIVE_INFINITY)), Double.POSITIVE_INFINITY);
-    }
-
     @Test
     public void testVectorCombination() {
         // act/assert
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
index 9e9119c..e5b580a 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/oned/Vector1DTest.java
@@ -18,6 +18,7 @@
 
 import java.util.regex.Pattern;
 
+import org.apache.commons.geometry.core.Geometry;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -31,6 +32,7 @@ public void testConstants() {
         // act/assert
         checkVector(Vector1D.ZERO, 0.0);
         checkVector(Vector1D.ONE, 1.0);
+        checkVector(Vector1D.MINUS_ONE, -1.0);
         checkVector(Vector1D.NaN, Double.NaN);
         checkVector(Vector1D.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
         checkVector(Vector1D.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
@@ -89,6 +91,40 @@ public void testNormInf() {
         Assert.assertEquals(3.0, Vector1D.of(-3).getNormInf(), TEST_TOLERANCE);
     }
 
+    @Test
+    public void testMagnitude() {
+        // act/assert
+        Assert.assertEquals(0.0, Vector1D.ZERO.getMagnitude(), TEST_TOLERANCE);
+        Assert.assertEquals(3.0, Vector1D.of(3).getMagnitude(), TEST_TOLERANCE);
+        Assert.assertEquals(3.0, Vector1D.of(-3).getMagnitude(), TEST_TOLERANCE);
+    }
+
+    @Test
+    public void testMagnitudeSq() {
+        // act/assert
+        Assert.assertEquals(0.0, Vector1D.of(0).getMagnitudeSq(), TEST_TOLERANCE);
+        Assert.assertEquals(9.0, Vector1D.of(3).getMagnitudeSq(), TEST_TOLERANCE);
+        Assert.assertEquals(9.0, Vector1D.of(-3).getMagnitudeSq(), TEST_TOLERANCE);
+    }
+
+    @Test
+    public void testWithMagnitude() {
+        // act/assert
+        checkVector(Vector1D.ONE.withMagnitude(0.0), 0.0);
+
+        checkVector(Vector1D.of(0.5).withMagnitude(2.0), 2.0);
+        checkVector(Vector1D.of(5).withMagnitude(3.0), 3.0);
+
+        checkVector(Vector1D.of(-0.5).withMagnitude(2.0), -2.0);
+        checkVector(Vector1D.of(-5).withMagnitude(3.0), -3.0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testWithMagnitude_zeroNorm() {
+        // act/assert
+        Vector1D.ZERO.withMagnitude(1.0);
+    }
+
     @Test
     public void testAdd() {
         // arrange
@@ -256,6 +292,128 @@ public void testDotProduct() {
         Assert.assertEquals(6.0, v3.dotProduct(v1), TEST_TOLERANCE);
     }
 
+    @Test
+    public void testProject() {
+        // arrange
+        Vector1D v1 = Vector1D.of(2);
+        Vector1D v2 = Vector1D.of(-3);
+        Vector1D v3 = Vector1D.of(4);
+
+        // act/assert
+        checkVector(Vector1D.ZERO.project(v1), 0);
+        checkVector(Vector1D.ZERO.project(v2), 0);
+        checkVector(Vector1D.ZERO.project(v3), 0);
+
+        checkVector(v1.project(v1), 2);
+        checkVector(v1.project(v2), 2);
+        checkVector(v1.project(v3), 2);
+
+        checkVector(v2.project(v1), -3);
+        checkVector(v2.project(v2), -3);
+        checkVector(v2.project(v3), -3);
+
+        checkVector(v3.project(v1), 4);
+        checkVector(v3.project(v2), 4);
+        checkVector(v3.project(v3), 4);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testProject_baseHasZeroNorm() {
+        // act/assert
+        Vector1D.of(2.0).project(Vector1D.ZERO);
+    }
+
+    @Test
+    public void testReject() {
+        // arrange
+        Vector1D v1 = Vector1D.of(2);
+        Vector1D v2 = Vector1D.of(-3);
+
+        // act/assert
+        checkVector(Vector1D.ZERO.reject(v1), 0);
+        checkVector(Vector1D.ZERO.reject(v2), 0);
+
+        checkVector(v1.reject(v1), 0);
+        checkVector(v1.reject(v2), 0);
+
+        checkVector(v2.reject(v1), 0);
+        checkVector(v2.reject(v2), 0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testReject_baseHasZeroNorm() {
+        // act/assert
+        Vector1D.of(2.0).reject(Vector1D.ZERO);
+    }
+
+    @Test
+    public void testAngle() {
+        // arrange
+        Vector1D v1 = Vector1D.of(2);
+        Vector1D v2 = Vector1D.of(-3);
+        Vector1D v3 = Vector1D.of(4);
+        Vector1D v4 = Vector1D.of(-5);
+
+        // act/assert
+        Assert.assertEquals(0.0, v1.angle(v1), TEST_TOLERANCE);
+        Assert.assertEquals(Geometry.PI, v1.angle(v2), TEST_TOLERANCE);
+        Assert.assertEquals(0.0, v1.angle(v3), TEST_TOLERANCE);
+        Assert.assertEquals(Geometry.PI, v1.angle(v4), TEST_TOLERANCE);
+
+        Assert.assertEquals(Geometry.PI, v2.angle(v1), TEST_TOLERANCE);
+        Assert.assertEquals(0.0, v2.angle(v2), TEST_TOLERANCE);
+        Assert.assertEquals(Geometry.PI, v2.angle(v3), TEST_TOLERANCE);
+        Assert.assertEquals(0.0, v2.angle(v4), TEST_TOLERANCE);
+
+        Assert.assertEquals(0.0, v3.angle(v1), TEST_TOLERANCE);
+        Assert.assertEquals(Geometry.PI, v3.angle(v2), TEST_TOLERANCE);
+        Assert.assertEquals(0.0, v3.angle(v3), TEST_TOLERANCE);
+        Assert.assertEquals(Geometry.PI, v3.angle(v4), TEST_TOLERANCE);
+
+        Assert.assertEquals(Geometry.PI, v4.angle(v1), TEST_TOLERANCE);
+        Assert.assertEquals(0.0, v4.angle(v2), TEST_TOLERANCE);
+        Assert.assertEquals(Geometry.PI, v4.angle(v3), TEST_TOLERANCE);
+        Assert.assertEquals(0.0, v4.angle(v4), TEST_TOLERANCE);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAngle_firstVectorZero() {
+        // act/assert
+        Vector1D.ZERO.angle(Vector1D.of(1.0));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAngle_secondVectorZero() {
+        // act/assert
+        Vector1D.of(1.0).angle(Vector1D.ZERO);
+    }
+
+    @Test
+    public void testLerp() {
+        // arrange
+        Vector1D v1 = Vector1D.of(1);
+        Vector1D v2 = Vector1D.of(-4);
+        Vector1D v3 = Vector1D.of(10);
+
+        // act/assert
+        checkVector(v1.lerp(v1, 0), 1);
+        checkVector(v1.lerp(v1, 1), 1);
+
+        checkVector(v1.lerp(v2, -0.25), 2.25);
+        checkVector(v1.lerp(v2, 0), 1);
+        checkVector(v1.lerp(v2, 0.25), -0.25);
+        checkVector(v1.lerp(v2, 0.5), -1.5);
+        checkVector(v1.lerp(v2, 0.75), -2.75);
+        checkVector(v1.lerp(v2, 1), -4);
+        checkVector(v1.lerp(v2, 1.25), -5.25);
+
+        checkVector(v1.lerp(v3, 0), 1);
+        checkVector(v1.lerp(v3, 0.25), 3.25);
+        checkVector(v1.lerp(v3, 0.5), 5.5);
+        checkVector(v1.lerp(v3, 0.75), 7.75);
+        checkVector(v1.lerp(v3, 1), 10);
+    }
+
     @Test
     public void testHashCode() {
         // arrange
@@ -339,18 +497,6 @@ public void testOf() {
         checkVector(Vector1D.of(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY);
     }
 
-    @Test
-    public void testOf_coordinateArg() {
-        // act/assert
-        checkVector(Vector1D.of(Vector1D.of(0)), 0.0);
-        checkVector(Vector1D.of(Vector1D.of(-1)), -1.0);
-        checkVector(Vector1D.of(Vector1D.of(1)), 1.0);
-        checkVector(Vector1D.of(Vector1D.of(Math.PI)), Math.PI);
-        checkVector(Vector1D.of(Vector1D.of(Double.NaN)), Double.NaN);
-        checkVector(Vector1D.of(Vector1D.of(Double.NEGATIVE_INFINITY)), Double.NEGATIVE_INFINITY);
-        checkVector(Vector1D.of(Vector1D.of(Double.POSITIVE_INFINITY)), Double.POSITIVE_INFINITY);
-    }
-
     @Test
     public void testLinearCombination() {
         // act/assert
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java
index b76cbda..3e312dd 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Point3DTest.java
@@ -102,6 +102,32 @@ public void testVectorTo() {
         checkVector(p3.vectorTo(p1), 8, 10, 12);
     }
 
+    @Test
+    public void testLerp() {
+        // arrange
+        Point3D p1 = Point3D.of(1, -5, 2);
+        Point3D p2 = Point3D.of(-4, 0, 2);
+        Point3D p3 = Point3D.of(10, -4, 0);
+
+        // act/assert
+        checkPoint(p1.lerp(p1, 0), 1, -5, 2);
+        checkPoint(p1.lerp(p1, 1), 1, -5, 2);
+
+        checkPoint(p1.lerp(p2, -0.25), 2.25, -6.25, 2);
+        checkPoint(p1.lerp(p2, 0), 1, -5, 2);
+        checkPoint(p1.lerp(p2, 0.25), -0.25, -3.75, 2);
+        checkPoint(p1.lerp(p2, 0.5), -1.5, -2.5, 2);
+        checkPoint(p1.lerp(p2, 0.75), -2.75, -1.25, 2);
+        checkPoint(p1.lerp(p2, 1), -4, 0, 2);
+        checkPoint(p1.lerp(p2, 1.25), -5.25, 1.25, 2);
+
+        checkPoint(p1.lerp(p3, 0), 1, -5, 2);
+        checkPoint(p1.lerp(p3, 0.25), 3.25, -4.75, 1.5);
+        checkPoint(p1.lerp(p3, 0.5), 5.5, -4.5, 1);
+        checkPoint(p1.lerp(p3, 0.75), 7.75, -4.25, 0.5);
+        checkPoint(p1.lerp(p3, 1), 10, -4, 0);
+    }
+
     @Test
     public void testAdd() {
         // act/assert
@@ -214,32 +240,21 @@ public void testOf() {
                 Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E);
     }
 
-    @Test
-    public void testOf_coordinateArg() {
-        // act/assert
-        checkPoint(Point3D.of(Vector3D.of(1, 2, 3)), 1, 2, 3);
-        checkPoint(Point3D.of(Vector3D.of(-1, -2, -3)), -1, -2, -3);
-        checkPoint(Point3D.of(Vector3D.of(Math.PI, Double.NaN, Double.POSITIVE_INFINITY)),
-                Math.PI, Double.NaN, Double.POSITIVE_INFINITY);
-        checkPoint(Point3D.of(Vector3D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E)),
-                   Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E);
-    }
-
     @Test
     public void testOf_arrayArg() {
         // act/assert
-        checkPoint(Point3D.of(new double[] { 1, 2, 3 }), 1, 2, 3);
-        checkPoint(Point3D.of(new double[] { -1, -2, -3 }), -1, -2, -3);
-        checkPoint(Point3D.of(new double[] { Math.PI, Double.NaN, Double.POSITIVE_INFINITY }),
+        checkPoint(Point3D.ofArray(new double[] { 1, 2, 3 }), 1, 2, 3);
+        checkPoint(Point3D.ofArray(new double[] { -1, -2, -3 }), -1, -2, -3);
+        checkPoint(Point3D.ofArray(new double[] { Math.PI, Double.NaN, Double.POSITIVE_INFINITY }),
                 Math.PI, Double.NaN, Double.POSITIVE_INFINITY);
-        checkPoint(Point3D.of(new double[] { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E}),
+        checkPoint(Point3D.ofArray(new double[] { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E}),
                 Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testOf_arrayArg_invalidDimensions() {
         // act/assert
-        Point3D.of(new double[] { 0.0, 0.0 });
+        Point3D.ofArray(new double[] { 0.0, 0.0 });
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java
index ce4f03f..61339dd 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java
@@ -180,7 +180,7 @@ public void testToVector() {
     public void testToCartesian_static() {
         // arrange
         double sqrt3 = Math.sqrt(3);
-        DoubleFunction3N<Point3D> factory = Point3D.FACTORY;
+        DoubleFunction3N<Point3D> factory = Point3D::of;
 
         // act/assert
         checkPoint(SphericalCoordinates.toCartesian(0, 0, 0, factory), 0, 0, 0);
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
index f6a1606..3aa2dd7 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
@@ -20,6 +20,7 @@
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.numbers.core.Precision;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.simple.RandomSource;
@@ -87,7 +88,7 @@ public void testNorm() {
     @Test
     public void testNormSq() {
         // act/assert
-        Assert.assertEquals(0.0, Vector3D.ZERO.getNorm(), 0);
+        Assert.assertEquals(0.0, Vector3D.ZERO.getNormSq(), 0);
         Assert.assertEquals(29, Vector3D.of(2, 3, 4).getNormSq(), EPS);
         Assert.assertEquals(29, Vector3D.of(-2, -3, -4).getNormSq(), EPS);
     }
@@ -100,6 +101,55 @@ public void testNormInf() {
         Assert.assertEquals(4, Vector3D.of(-2, -3, -4).getNormInf(), EPS);
     }
 
+    @Test
+    public void testMagnitude() {
+        // act/assert
+        Assert.assertEquals(0.0, Vector3D.ZERO.getMagnitude(), 0);
+        Assert.assertEquals(Math.sqrt(29), Vector3D.of(2, 3, 4).getMagnitude(), EPS);
+        Assert.assertEquals(Math.sqrt(29), Vector3D.of(-2, -3, -4).getMagnitude(), EPS);
+    }
+
+    @Test
+    public void testMagnitudeSq() {
+        // act/assert
+        Assert.assertEquals(0.0, Vector3D.ZERO.getMagnitudeSq(), 0);
+        Assert.assertEquals(29, Vector3D.of(2, 3, 4).getMagnitudeSq(), EPS);
+        Assert.assertEquals(29, Vector3D.of(-2, -3, -4).getMagnitudeSq(), EPS);
+    }
+
+    @Test
+    public void testWithMagnitude() {
+        // arrange
+        double x = 2;
+        double y = 3;
+        double z = 4;
+
+        double len = Math.sqrt((x * x) + (y * y) + (z * z));
+
+        double normX = x / len;
+        double normY = y / len;
+        double normZ = z / len;
+
+        // act/assert
+        checkVector(Vector3D.of(x, y, z).withMagnitude(1.0), normX, normY, normZ);
+        checkVector(Vector3D.of(x, y, -z).withMagnitude(1.0), normX, normY, -normZ);
+        checkVector(Vector3D.of(x, -y, z).withMagnitude(1.0), normX, -normY, normZ);
+        checkVector(Vector3D.of(x, -y, -z).withMagnitude(1.0), normX, -normY, -normZ);
+        checkVector(Vector3D.of(-x, y, z).withMagnitude(1.0), -normX, normY, normZ);
+        checkVector(Vector3D.of(-x, y, -z).withMagnitude(1.0), -normX, normY, -normZ);
+        checkVector(Vector3D.of(-x, -y, z).withMagnitude(1.0), -normX, -normY, normZ);
+        checkVector(Vector3D.of(-x, -y, -z).withMagnitude(1.0), -normX, -normY, -normZ);
+
+        checkVector(Vector3D.of(x, y, z).withMagnitude(0.5), 0.5 * normX, 0.5 * normY, 0.5 * normZ);
+        checkVector(Vector3D.of(x, y, z).withMagnitude(3), 3 * normX, 3 * normY, 3 * normZ);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testWithMagnitude_zeroNorm() {
+        // act/assert
+        Vector3D.ZERO.withMagnitude(1.0);
+    }
+
     @Test
     public void testAdd() {
         // arrange
@@ -266,28 +316,6 @@ public void testAngle_angularSeparation() {
 
         // act/assert
         Assert.assertTrue(Math.abs(v1.angle(v2) - 1.2) < 1.0e-12);
-  }
-
-    @Test
-    public void testAngle_static() {
-        // arrange
-        double tolerance = 1e-10;
-
-        Vector3D v1 = Vector3D.of(1, 2, 3);
-        Vector3D v2 = Vector3D.of(4, 5, 6);
-
-        // act/assert
-        Assert.assertEquals(0.22572612855273393616, Vector3D.angle(v1, v2), tolerance);
-        Assert.assertEquals(7.98595620686106654517199e-8, Vector3D.angle(v1, Vector3D.of(2, 4, 6.000001)), tolerance);
-        Assert.assertEquals(3.14159257373023116985197793156, Vector3D.angle(v1, Vector3D.of(-2, -4, -6.000001)), tolerance);
-
-        Assert.assertEquals(0.0, Vector3D.angle(Vector3D.PLUS_X, Vector3D.PLUS_X), tolerance);
-        Assert.assertEquals(Geometry.PI, Vector3D.angle(Vector3D.PLUS_X, Vector3D.MINUS_X), tolerance);
-
-        Assert.assertEquals(Geometry.HALF_PI, Vector3D.angle(Vector3D.PLUS_X, Vector3D.PLUS_Y), tolerance);
-        Assert.assertEquals(Geometry.HALF_PI, Vector3D.angle(Vector3D.PLUS_X, Vector3D.MINUS_Y), tolerance);
-        Assert.assertEquals(Geometry.HALF_PI, Vector3D.angle(Vector3D.PLUS_X, Vector3D.PLUS_Z), tolerance);
-        Assert.assertEquals(Geometry.HALF_PI, Vector3D.angle(Vector3D.PLUS_X, Vector3D.MINUS_Z), tolerance);
     }
 
     @Test
@@ -371,21 +399,6 @@ public void testCrossProduct_cancellation() {
         checkVector(big1.crossProduct(small2), -1, 2, 1);
     }
 
-    @Test
-    public void testCrossProduct_static() {
-        // act/assert
-        checkVector(Vector3D.crossProduct(Vector3D.PLUS_X, Vector3D.PLUS_Y), 0, 0, 1);
-        checkVector(Vector3D.crossProduct(Vector3D.PLUS_X, Vector3D.MINUS_Y), 0, 0, -1);
-
-        checkVector(Vector3D.crossProduct(Vector3D.MINUS_X, Vector3D.MINUS_Y), 0, 0, 1);
-        checkVector(Vector3D.crossProduct(Vector3D.MINUS_X, Vector3D.PLUS_Y), 0, 0, -1);
-
-        checkVector(Vector3D.crossProduct(Vector3D.of(2, 1, -4), Vector3D.of(3, 1, -1)), 3, -10, -1);
-
-        double invSqrt6 = 1 / Math.sqrt(6);
-        checkVector(Vector3D.crossProduct(Vector3D.of(1, 1, 1), Vector3D.of(-1, 0, 1)).normalize(), invSqrt6, - 2 * invSqrt6, invSqrt6);
-    }
-
     @Test
     public void testScalarMultiply() {
         // arrange
@@ -550,20 +563,151 @@ public void testDotProduct_accuracy() {
     }
 
     @Test
-    public void testDotProduct_static() {
+    public void testProject() {
         // arrange
-        Vector3D v1 = Vector3D.of(1, -2, 3);
-        Vector3D v2 = Vector3D.of(-4, 5, -6);
-        Vector3D v3 = Vector3D.of(7, 8, 9);
+        Vector3D v1 = Vector3D.of(2.0, 3.0, 4.0);
+        Vector3D v2 = Vector3D.of(-5.0, -6.0, -7.0);
 
         // act/assert
-        Assert.assertEquals(14, Vector3D.dotProduct(v1, v1), EPS);
+        checkVector(Vector3D.ZERO.project(Vector3D.PLUS_X), 0.0, 0.0, 0.0);
 
-        Assert.assertEquals(-32, Vector3D.dotProduct(v1, v2), EPS);
-        Assert.assertEquals(-32, Vector3D.dotProduct(v2, v1), EPS);
+        checkVector(v1.project(Vector3D.PLUS_X), 2.0, 0.0, 0.0);
+        checkVector(v1.project(Vector3D.MINUS_X), 2.0, 0.0, 0.0);
+        checkVector(v1.project(Vector3D.PLUS_Y), 0.0, 3.0, 0.0);
+        checkVector(v1.project(Vector3D.MINUS_Y), 0.0, 3.0, 0.0);
+        checkVector(v1.project(Vector3D.PLUS_Z), 0.0, 0.0, 4.0);
+        checkVector(v1.project(Vector3D.MINUS_Z), 0.0, 0.0, 4.0);
 
-        Assert.assertEquals(18, Vector3D.dotProduct(v1, v3), EPS);
-        Assert.assertEquals(18, Vector3D.dotProduct(v3, v1), EPS);
+        checkVector(v2.project(Vector3D.PLUS_X), -5.0, 0.0, 0.0);
+        checkVector(v2.project(Vector3D.MINUS_X), -5.0, 0.0, 0.0);
+        checkVector(v2.project(Vector3D.PLUS_Y), 0.0, -6.0, 0.0);
+        checkVector(v2.project(Vector3D.MINUS_Y), 0.0, -6.0, 0.0);
+        checkVector(v2.project(Vector3D.PLUS_Z), 0.0, 0.0, -7.0);
+        checkVector(v2.project(Vector3D.MINUS_Z), 0.0, 0.0, -7.0);
+
+        checkVector(v1.project(Vector3D.of(1.0, 1.0, 1.0)), 3.0, 3.0, 3.0);
+        checkVector(v1.project(Vector3D.of(-1.0, -1.0, -1.0)), 3.0, 3.0, 3.0);
+
+        checkVector(v2.project(Vector3D.of(1.0, 1.0, 1.0)), -6.0, -6.0, -6.0);
+        checkVector(v2.project(Vector3D.of(-1.0, -1.0, -1.0)), -6.0, -6.0, -6.0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testProject_baseHasZeroNorm() {
+        // act/assert
+        Vector3D.of(1.0, 1.0, 1.0).project(Vector3D.ZERO);
+    }
+
+    @Test
+    public void testReject() {
+        // arrange
+        Vector3D v1 = Vector3D.of(2.0, 3.0, 4.0);
+        Vector3D v2 = Vector3D.of(-5.0, -6.0, -7.0);
+
+        // act/assert
+        checkVector(Vector3D.ZERO.reject(Vector3D.PLUS_X), 0.0, 0.0, 0.0);
+
+        checkVector(v1.reject(Vector3D.PLUS_X), 0.0, 3.0, 4.0);
+        checkVector(v1.reject(Vector3D.MINUS_X), 0.0, 3.0, 4.0);
+        checkVector(v1.reject(Vector3D.PLUS_Y), 2.0, 0.0, 4.0);
+        checkVector(v1.reject(Vector3D.MINUS_Y), 2.0, 0.0, 4.0);
+        checkVector(v1.reject(Vector3D.PLUS_Z), 2.0, 3.0, 0.0);
+        checkVector(v1.reject(Vector3D.MINUS_Z), 2.0, 3.0, 0.0);
+
+        checkVector(v2.reject(Vector3D.PLUS_X), 0.0, -6.0, -7.0);
+        checkVector(v2.reject(Vector3D.MINUS_X), 0.0, -6.0, -7.0);
+        checkVector(v2.reject(Vector3D.PLUS_Y), -5.0, 0.0, -7.0);
+        checkVector(v2.reject(Vector3D.MINUS_Y), -5.0, 0.0, -7.0);
+        checkVector(v2.reject(Vector3D.PLUS_Z), -5.0, -6.0, 0.0);
+        checkVector(v2.reject(Vector3D.MINUS_Z), -5.0, -6.0, 0.0);
+
+        checkVector(v1.reject(Vector3D.of(1.0, 1.0, 1.0)), -1.0, 0.0, 1.0);
+        checkVector(v1.reject(Vector3D.of(-1.0, -1.0, -1.0)), -1.0, 0.0, 1.0);
+
+        checkVector(v2.reject(Vector3D.of(1.0, 1.0, 1.0)), 1.0, 0.0, -1.0);
+        checkVector(v2.reject(Vector3D.of(-1.0, -1.0, -1.0)), 1.0, 0.0, -1.0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testReject_baseHasZeroNorm() {
+        // act/assert
+        Vector3D.of(1.0, 1.0, 1.0).reject(Vector3D.ZERO);
+    }
+
+    @Test
+    public void testProjectAndReject_areComplementary() {
+        // arrange
+        double eps = 1e-12;
+
+        // act/assert
+        checkProjectAndRejectFullSphere(Vector3D.of(1.0, 0.0, 0.0), 1.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(0.0, 1.0, 0.0), 2.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(0.0, 0.0, 1.0), 2.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(1.0, 1.0, 1.0), 3.0, eps);
+
+        checkProjectAndRejectFullSphere(Vector3D.of(-2.0, 0.0, 0.0), 1.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(0.0, -2.0, 0.0), 2.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(0.0, 0.0, -2.0), 2.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(-2.0, -2.0, -2.0), 3.0, eps);
+    }
+
+    private void checkProjectAndRejectFullSphere(Vector3D vec, double baseMag, double eps) {
+        for (double polar = 0.0; polar <= Geometry.PI; polar += 0.5) {
+            for (double azimuth = 0.0; azimuth <= Geometry.TWO_PI; azimuth += 0.5) {
+                Vector3D base = Vector3D.ofSpherical(baseMag, azimuth, polar);
+
+                Vector3D proj = vec.project(base);
+                Vector3D rej = vec.reject(base);
+
+                // ensure that the projection and rejection sum to the original vector
+                EuclideanTestUtils.assertCoordinatesEqual(vec, proj.add(rej), eps);
+
+                double angle = base.angle(vec);
+
+                // check the angle between the projection and the base; this will
+                // be undefined when the angle between the original vector and the
+                // base is pi/2 (which means that the projection is the zero vector)
+                if (angle < Geometry.HALF_PI) {
+                    Assert.assertEquals(0.0, proj.angle(base), eps);
+                }
+                else if (angle > Geometry.HALF_PI) {
+                    Assert.assertEquals(Geometry.PI, proj.angle(base), eps);
+                }
+
+                // check the angle between the rejection and the base; this should
+                // always be pi/2 except for when the angle between the original vector
+                // and the base is 0 or pi, in which case the rejection is the zero vector.
+                if (angle > 0.0 && angle < Geometry.PI) {
+                    Assert.assertEquals(Geometry.HALF_PI, rej.angle(base), eps);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testLerp() {
+        // arrange
+        Vector3D v1 = Vector3D.of(1, -5, 2);
+        Vector3D v2 = Vector3D.of(-4, 0, 2);
+        Vector3D v3 = Vector3D.of(10, -4, 0);
+
+        // act/assert
+        checkVector(v1.lerp(v1, 0), 1, -5, 2);
+        checkVector(v1.lerp(v1, 1), 1, -5, 2);
+
+        checkVector(v1.lerp(v2, -0.25), 2.25, -6.25, 2);
+        checkVector(v1.lerp(v2, 0), 1, -5, 2);
+        checkVector(v1.lerp(v2, 0.25), -0.25, -3.75, 2);
+        checkVector(v1.lerp(v2, 0.5), -1.5, -2.5, 2);
+        checkVector(v1.lerp(v2, 0.75), -2.75, -1.25, 2);
+        checkVector(v1.lerp(v2, 1), -4, 0, 2);
+        checkVector(v1.lerp(v2, 1.25), -5.25, 1.25, 2);
+
+        checkVector(v1.lerp(v3, 0), 1, -5, 2);
+        checkVector(v1.lerp(v3, 0.25), 3.25, -4.75, 1.5);
+        checkVector(v1.lerp(v3, 0.5), 5.5, -4.5, 1);
+        checkVector(v1.lerp(v3, 0.75), 7.75, -4.25, 0.5);
+        checkVector(v1.lerp(v3, 1), 10, -4, 0);
     }
 
     @Test
@@ -659,32 +803,21 @@ public void testOf() {
                 Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E);
     }
 
-    @Test
-    public void testOf_coordinateArg() {
-        // act/assert
-        checkVector(Vector3D.of(Point3D.of(1, 2, 3)), 1, 2, 3);
-        checkVector(Vector3D.of(Point3D.of(-1, -2, -3)), -1, -2, -3);
-        checkVector(Vector3D.of(Point3D.of(Math.PI, Double.NaN, Double.POSITIVE_INFINITY)),
-                Math.PI, Double.NaN, Double.POSITIVE_INFINITY);
-        checkVector(Vector3D.of(Point3D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E)),
-                   Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E);
-    }
-
     @Test
     public void testOf_arrayArg() {
         // act/assert
-        checkVector(Vector3D.of(new double[] { 1, 2, 3 }), 1, 2, 3);
-        checkVector(Vector3D.of(new double[] { -1, -2, -3 }), -1, -2, -3);
-        checkVector(Vector3D.of(new double[] { Math.PI, Double.NaN, Double.POSITIVE_INFINITY }),
+        checkVector(Vector3D.ofArray(new double[] { 1, 2, 3 }), 1, 2, 3);
+        checkVector(Vector3D.ofArray(new double[] { -1, -2, -3 }), -1, -2, -3);
+        checkVector(Vector3D.ofArray(new double[] { Math.PI, Double.NaN, Double.POSITIVE_INFINITY }),
                 Math.PI, Double.NaN, Double.POSITIVE_INFINITY);
-        checkVector(Vector3D.of(new double[] { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E}),
+        checkVector(Vector3D.ofArray(new double[] { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E}),
                 Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Math.E);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testOf_arrayArg_invalidDimensions() {
         // act/assert
-        Vector3D.of(new double[] { 0.0, 0.0 });
+        Vector3D.ofArray(new double[] { 0.0, 0.0 });
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java
index 69c3aa3..d5903de 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Point2DTest.java
@@ -93,6 +93,32 @@ public void testVectorTo() {
         checkVector(p3.vectorTo(p1), 2, 1);
     }
 
+    @Test
+    public void testLerp() {
+        // arrange
+        Point2D p1 = Point2D.of(1, -5);
+        Point2D p2 = Point2D.of(-4, 0);
+        Point2D p3 = Point2D.of(10, -4);
+
+        // act/assert
+        checkPoint(p1.lerp(p1, 0), 1, -5);
+        checkPoint(p1.lerp(p1, 1), 1, -5);
+
+        checkPoint(p1.lerp(p2, -0.25), 2.25, -6.25);
+        checkPoint(p1.lerp(p2, 0), 1, -5);
+        checkPoint(p1.lerp(p2, 0.25), -0.25, -3.75);
+        checkPoint(p1.lerp(p2, 0.5), -1.5, -2.5);
+        checkPoint(p1.lerp(p2, 0.75), -2.75, -1.25);
+        checkPoint(p1.lerp(p2, 1), -4, 0);
+        checkPoint(p1.lerp(p2, 1.25), -5.25, 1.25);
+
+        checkPoint(p1.lerp(p3, 0), 1, -5);
+        checkPoint(p1.lerp(p3, 0.25), 3.25, -4.75);
+        checkPoint(p1.lerp(p3, 0.5), 5.5, -4.5);
+        checkPoint(p1.lerp(p3, 0.75), 7.75, -4.25);
+        checkPoint(p1.lerp(p3, 1), 10, -4);
+    }
+
     @Test
     public void testAdd() {
         // arrange
@@ -195,28 +221,19 @@ public void testOf() {
         checkPoint(Point2D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
     }
 
-    @Test
-    public void testOf_coordinateArg() {
-        // act/assert
-        checkPoint(Point2D.of(Vector2D.of(0, 1)), 0, 1);
-        checkPoint(Point2D.of(Vector2D.of(-1, -2)), -1, -2);
-        checkPoint(Point2D.of(Vector2D.of(Math.PI, Double.NaN)), Math.PI, Double.NaN);
-        checkPoint(Point2D.of(Vector2D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
-    }
-
     @Test
     public void testOf_arrayArg() {
         // act/assert
-        checkPoint(Point2D.of(new double[] { 0, 1 }), 0, 1);
-        checkPoint(Point2D.of(new double[] { -1, -2 }), -1, -2);
-        checkPoint(Point2D.of(new double[] { Math.PI, Double.NaN }), Math.PI, Double.NaN);
-        checkPoint(Point2D.of(new double[] { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY }), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
+        checkPoint(Point2D.ofArray(new double[] { 0, 1 }), 0, 1);
+        checkPoint(Point2D.ofArray(new double[] { -1, -2 }), -1, -2);
+        checkPoint(Point2D.ofArray(new double[] { Math.PI, Double.NaN }), Math.PI, Double.NaN);
+        checkPoint(Point2D.ofArray(new double[] { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY }), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testOf_arrayArg_invalidDimensions() {
         // act/assert
-        Point2D.of(new double[] {0.0 });
+        Point2D.ofArray(new double[] {0.0 });
     }
 
     @Test
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinatesTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinatesTest.java
index 524e7e0..7b36c31 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinatesTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/PolarCoordinatesTest.java
@@ -257,7 +257,7 @@ public void testToPoint() {
     @Test
     public void testToCartesian_static() {
         // arrange
-        DoubleFunction2N<Point2D> factory = Point2D.FACTORY;
+        DoubleFunction2N<Point2D> factory = Point2D::of;
         double sqrt2 = Math.sqrt(2);
 
         // act/assert
@@ -278,7 +278,7 @@ public void testToCartesian_static() {
     @Test
     public void testToCartesian_static_NaNAndInfinite() {
         // arrange
-        DoubleFunction2N<Point2D> factory = Point2D.FACTORY;
+        DoubleFunction2N<Point2D> factory = Point2D::of;
 
         // act/assert
         Assert.assertTrue(PolarCoordinates.toCartesian(Double.NaN, 0, factory).isNaN());
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
index 1846eac..62e0c8e 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
@@ -19,6 +19,7 @@
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -119,6 +120,51 @@ public void testNormInf() {
         Assert.assertEquals(100.0, Vector2D.of(100, -99).getNormInf(), EPS);
     }
 
+    @Test
+    public void testMagnitude() {
+        // act/assert
+        Assert.assertEquals(0.0, Vector2D.of(0, 0).getMagnitude(), EPS);
+
+        Assert.assertEquals(5.0, Vector2D.of(3, 4).getMagnitude(), EPS);
+        Assert.assertEquals(5.0, Vector2D.of(3, -4).getMagnitude(), EPS);
+        Assert.assertEquals(5.0, Vector2D.of(-3, 4).getMagnitude(), EPS);
+        Assert.assertEquals(5.0, Vector2D.of(-3, -4).getMagnitude(), EPS);
+
+        Assert.assertEquals(Math.sqrt(5.0), Vector2D.of(-1, -2).getMagnitude(), EPS);
+    }
+
+    @Test
+    public void testMagnitudeSq() {
+        // act/assert
+        Assert.assertEquals(0.0, Vector2D.of(0, 0).getMagnitudeSq(), EPS);
+
+        Assert.assertEquals(25.0, Vector2D.of(3, 4).getMagnitudeSq(), EPS);
+        Assert.assertEquals(25.0, Vector2D.of(3, -4).getMagnitudeSq(), EPS);
+        Assert.assertEquals(25.0, Vector2D.of(-3, 4).getMagnitudeSq(), EPS);
+        Assert.assertEquals(25.0, Vector2D.of(-3, -4).getMagnitudeSq(), EPS);
+
+        Assert.assertEquals(5.0, Vector2D.of(-1, -2).getMagnitudeSq(), EPS);
+    }
+
+    @Test
+    public void testWithMagnitude() {
+        // act/assert
+        checkVector(Vector2D.of(3, 4).withMagnitude(1.0), 0.6, 0.8);
+        checkVector(Vector2D.of(4, 3).withMagnitude(1.0), 0.8, 0.6);
+
+        checkVector(Vector2D.of(-3, 4).withMagnitude(0.5), -0.3, 0.4);
+        checkVector(Vector2D.of(3, -4).withMagnitude(2.0), 1.2, -1.6);
+        checkVector(Vector2D.of(-3, -4).withMagnitude(3.0), -1.8, 3.0 * Math.sin(Math.atan2(-4, -3)));
+
+        checkVector(Vector2D.of(0.5, 0.5).withMagnitude(2), Math.sqrt(2), Math.sqrt(2));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testWithMagnitude_zeroNorm() {
+        // act/assert
+        Vector2D.ZERO.withMagnitude(1.0);
+    }
+
     @Test
     public void testAdd() {
         // arrange
@@ -314,30 +360,6 @@ public void testDotProduct() {
         Assert.assertEquals(0, Vector2D.PLUS_X.dotProduct(Vector2D.MINUS_Y), EPS);
     }
 
-    @Test
-    public void testDotProduct_static() {
-        // arrange
-        Vector2D v1 = Vector2D.of(1, 1);
-        Vector2D v2 = Vector2D.of(4, 5);
-        Vector2D v3 = Vector2D.of(-1, 0);
-
-        // act/assert
-        Assert.assertEquals(2, Vector2D.dotProduct(v1, v1), EPS);
-        Assert.assertEquals(41, Vector2D.dotProduct(v2, v2), EPS);
-        Assert.assertEquals(1, Vector2D.dotProduct(v3, v3), EPS);
-
-        Assert.assertEquals(9, Vector2D.dotProduct(v1, v2), EPS);
-        Assert.assertEquals(9, Vector2D.dotProduct(v2, v1), EPS);
-
-        Assert.assertEquals(-1, Vector2D.dotProduct(v1, v3), EPS);
-        Assert.assertEquals(-1, Vector2D.dotProduct(v3, v1), EPS);
-
-        Assert.assertEquals(1, Vector2D.dotProduct(Vector2D.PLUS_X, Vector2D.PLUS_X), EPS);
-        Assert.assertEquals(0, Vector2D.dotProduct(Vector2D.PLUS_X, Vector2D.PLUS_Y), EPS);
-        Assert.assertEquals(-1, Vector2D.dotProduct(Vector2D.PLUS_X, Vector2D.MINUS_X), EPS);
-        Assert.assertEquals(0, Vector2D.dotProduct(Vector2D.PLUS_X, Vector2D.MINUS_Y), EPS);
-    }
-
     @Test
     public void testAngle() {
         // act/assert
@@ -353,23 +375,8 @@ public void testAngle() {
         Assert.assertEquals(0.004999958333958323, Vector2D.of(20.0, 0.0).angle(Vector2D.of(20.0, 0.1)), EPS);
     }
 
-    @Test
-    public void testAngle_static() {
-        // act/assert
-        Assert.assertEquals(0, Vector2D.angle(Vector2D.PLUS_X, Vector2D.PLUS_X), EPS);
-
-        Assert.assertEquals(Geometry.PI, Vector2D.angle(Vector2D.PLUS_X, Vector2D.MINUS_X), EPS);
-        Assert.assertEquals(Geometry.HALF_PI, Vector2D.angle(Vector2D.PLUS_X, Vector2D.PLUS_Y), EPS);
-        Assert.assertEquals(Geometry.HALF_PI, Vector2D.angle(Vector2D.PLUS_X, Vector2D.MINUS_Y), EPS);
-
-        Assert.assertEquals(Geometry.PI / 4, Vector2D.angle(Vector2D.of(1, 1), Vector2D.of(1, 0)), EPS);
-        Assert.assertEquals(Geometry.PI / 4, Vector2D.angle(Vector2D.of(1, 0), Vector2D.of(1, 1)), EPS);
-
-        Assert.assertEquals(0.004999958333958323, Vector2D.angle(Vector2D.of(20.0, 0.0), Vector2D.of(20.0, 0.1)), EPS);
-    }
-
 
-    @Test(expected = IllegalArgumentException.class)
+    @Test(expected = IllegalStateException.class)
     public void testAngle_zeroNorm() {
         Vector2D.of(1, 1).angle(Vector2D.ZERO);
     }
@@ -390,6 +397,132 @@ public void testCrossProduct() {
         Assert.assertEquals(-1.0, p5.crossProduct(p1, p2), EPS);
     }
 
+    @Test
+    public void testProject() {
+        // arrange
+        Vector2D v1 = Vector2D.of(3.0, 4.0);
+        Vector2D v2 = Vector2D.of(1.0, 4.0);
+
+        // act/assert
+        checkVector(Vector2D.ZERO.project(v1), 0.0, 0.0);
+
+        checkVector(v1.project(v1), 3.0, 4.0);
+        checkVector(v1.project(v1.negate()), 3.0, 4.0);
+
+        checkVector(v1.project(Vector2D.PLUS_X), 3.0, 0.0);
+        checkVector(v1.project(Vector2D.MINUS_X), 3.0, 0.0);
+
+        checkVector(v1.project(Vector2D.PLUS_Y), 0.0, 4.0);
+        checkVector(v1.project(Vector2D.MINUS_Y), 0.0, 4.0);
+
+        checkVector(v2.project(v1), (19.0 / 25.0) * 3.0, (19.0 / 25.0) * 4.0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testProject_baseHasZeroNorm() {
+        // act/assert
+        Vector2D.of(1.0, 1.0).project(Vector2D.ZERO);
+    }
+
+    @Test
+    public void testReject() {
+        // arrange
+        Vector2D v1 = Vector2D.of(3.0, 4.0);
+        Vector2D v2 = Vector2D.of(1.0, 4.0);
+
+        // act/assert
+        checkVector(Vector2D.ZERO.reject(v1), 0.0, 0.0);
+
+        checkVector(v1.reject(v1), 0.0, 0.0);
+        checkVector(v1.reject(v1.negate()), 0.0, 0.0);
+
+        checkVector(v1.reject(Vector2D.PLUS_X), 0.0, 4.0);
+        checkVector(v1.reject(Vector2D.MINUS_X), 0.0, 4.0);
+
+        checkVector(v1.reject(Vector2D.PLUS_Y), 3.0, 0.0);
+        checkVector(v1.reject(Vector2D.MINUS_Y), 3.0, 0.0);
+
+        checkVector(v2.reject(v1), (-32.0 / 25.0), (6.0 / 25.0) * 4.0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testReject_baseHasZeroNorm() {
+        // act/assert
+        Vector2D.of(1.0, 1.0).reject(Vector2D.ZERO);
+    }
+
+    @Test
+    public void testProjectAndReject_areComplementary() {
+        // arrange
+        double eps = 1e-12;
+
+        // act/assert
+        checkProjectAndRejectFullCircle(Vector2D.of(1.0, 0.0), 1.0, eps);
+        checkProjectAndRejectFullCircle(Vector2D.of(0.0, 1.0), 2.0, eps);
+        checkProjectAndRejectFullCircle(Vector2D.of(1.0, 1.0), 3.0, eps);
+
+        checkProjectAndRejectFullCircle(Vector2D.of(-2.0, 0.0), 4.0, eps);
+        checkProjectAndRejectFullCircle(Vector2D.of(0.0, -2.0), 5.0, eps);
+        checkProjectAndRejectFullCircle(Vector2D.of(-2.0, -2.0), 6.0, eps);
+    }
+
+    private void checkProjectAndRejectFullCircle(Vector2D vec, double baseMag, double eps) {
+        for (double theta = 0.0; theta <= Geometry.TWO_PI; theta += 0.5) {
+            Vector2D base = Vector2D.ofPolar(baseMag, theta);
+
+            Vector2D proj = vec.project(base);
+            Vector2D rej = vec.reject(base);
+
+            // ensure that the projection and rejection sum to the original vector
+            EuclideanTestUtils.assertCoordinatesEqual(vec, proj.add(rej), eps);
+
+            double angle = base.angle(vec);
+
+            // check the angle between the projection and the base; this will
+            // be undefined when the angle between the original vector and the
+            // base is pi/2 (which means that the projection is the zero vector)
+            if (angle < Geometry.HALF_PI) {
+                Assert.assertEquals(0.0, proj.angle(base), eps);
+            }
+            else if (angle > Geometry.HALF_PI) {
+                Assert.assertEquals(Geometry.PI, proj.angle(base), eps);
+            }
+
+            // check the angle between the rejection and the base; this should
+            // always be pi/2 except for when the angle between the original vector
+            // and the base is 0 or pi, in which case the rejection is the zero vector.
+            if (angle > 0.0 && angle < Geometry.PI) {
+                Assert.assertEquals(Geometry.HALF_PI, rej.angle(base), eps);
+            }
+        }
+    }
+
+    @Test
+    public void testLerp() {
+        // arrange
+        Vector2D v1 = Vector2D.of(1, -5);
+        Vector2D v2 = Vector2D.of(-4, 0);
+        Vector2D v3 = Vector2D.of(10, -4);
+
+        // act/assert
+        checkVector(v1.lerp(v1, 0), 1, -5);
+        checkVector(v1.lerp(v1, 1), 1, -5);
+
+        checkVector(v1.lerp(v2, -0.25), 2.25, -6.25);
+        checkVector(v1.lerp(v2, 0), 1, -5);
+        checkVector(v1.lerp(v2, 0.25), -0.25, -3.75);
+        checkVector(v1.lerp(v2, 0.5), -1.5, -2.5);
+        checkVector(v1.lerp(v2, 0.75), -2.75, -1.25);
+        checkVector(v1.lerp(v2, 1), -4, 0);
+        checkVector(v1.lerp(v2, 1.25), -5.25, 1.25);
+
+        checkVector(v1.lerp(v3, 0), 1, -5);
+        checkVector(v1.lerp(v3, 0.25), 3.25, -4.75);
+        checkVector(v1.lerp(v3, 0.5), 5.5, -4.5);
+        checkVector(v1.lerp(v3, 0.75), 7.75, -4.25);
+        checkVector(v1.lerp(v3, 1), 10, -4);
+    }
+
     @Test
     public void testHashCode() {
         // arrange
@@ -475,28 +608,19 @@ public void testOf() {
         checkVector(Vector2D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
     }
 
-    @Test
-    public void testOf_coordinateArg() {
-        // act/assert
-        checkVector(Vector2D.of(Point2D.of(0, 1)), 0, 1);
-        checkVector(Vector2D.of(Point2D.of(-1, -2)), -1, -2);
-        checkVector(Vector2D.of(Point2D.of(Math.PI, Double.NaN)), Math.PI, Double.NaN);
-        checkVector(Vector2D.of(Point2D.of(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
-    }
-
     @Test
     public void testOf_arrayArg() {
         // act/assert
-        checkVector(Vector2D.of(new double[] { 0, 1 }), 0, 1);
-        checkVector(Vector2D.of(new double[] { -1, -2 }), -1, -2);
-        checkVector(Vector2D.of(new double[] { Math.PI, Double.NaN }), Math.PI, Double.NaN);
-        checkVector(Vector2D.of(new double[] { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY }), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
+        checkVector(Vector2D.ofArray(new double[] { 0, 1 }), 0, 1);
+        checkVector(Vector2D.ofArray(new double[] { -1, -2 }), -1, -2);
+        checkVector(Vector2D.ofArray(new double[] { Math.PI, Double.NaN }), Math.PI, Double.NaN);
+        checkVector(Vector2D.ofArray(new double[] { Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY }), Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void testOf_arrayArg_invalidDimensions() {
         // act/assert
-        Vector2D.of(new double[] {0.0 });
+        Vector2D.ofArray(new double[] {0.0 });
     }
 
     @Test
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
index a9df1aa..1d6615f 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/oned/S1Point.java
@@ -19,7 +19,6 @@
 import java.io.Serializable;
 
 import org.apache.commons.geometry.core.Point;
-import org.apache.commons.geometry.core.internal.DoubleFunction1N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.twod.PolarCoordinates;
 import org.apache.commons.geometry.euclidean.twod.Vector2D;
@@ -37,16 +36,6 @@
     /** Serializable UID. */
     private static final long serialVersionUID = 20180710L;
 
-    /** Factory for delegating instance creation. */
-    private static DoubleFunction1N<S1Point> FACTORY = new DoubleFunction1N<S1Point>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public S1Point apply(double n) {
-            return new S1Point(n);
-        }
-    };
-
     /** Azimuthal angle in radians. */
     private final double azimuth;
 
@@ -183,6 +172,6 @@ public static S1Point of(double azimuth) {
      * @throws IllegalArgumentException if the given string has an invalid format
      */
     public static S1Point parse(String str) throws IllegalArgumentException {
-        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(str, S1Point::new);
     }
 }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
index e0aaba0..357eaba 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Circle.java
@@ -168,7 +168,7 @@ public double getPhase(final Vector3D direction) {
      */
     @Override
     public S2Point toSpace(final S1Point point) {
-        return S2Point.of(getPointAt(point.getAzimuth()));
+        return S2Point.ofVector(getPointAt(point.getAzimuth()));
     }
 
     /** Get a circle point from its phase around the circle.
@@ -306,7 +306,7 @@ public boolean sameOrientationAs(final Hyperplane<S2Point> other) {
         /** {@inheritDoc} */
         @Override
         public S2Point apply(final S2Point point) {
-            return S2Point.of(rotation.applyTo(point.getVector()));
+            return S2Point.ofVector(rotation.applyTo(point.getVector()));
         }
 
         /** {@inheritDoc} */
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
index 609408a..27b0137 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/Edge.java
@@ -148,7 +148,7 @@ void split(final Circle splitCircle,
             if (unwrappedEnd >= 0) {
                 // the start of the edge is inside the circle
                 previousVertex = addSubEdge(previousVertex,
-                                            new Vertex(S2Point.of(circle.getPointAt(edgeStart + unwrappedEnd))),
+                                            new Vertex(S2Point.ofVector(circle.getPointAt(edgeStart + unwrappedEnd))),
                                             unwrappedEnd, insideList, splitCircle);
                 alreadyManagedLength = unwrappedEnd;
             }
@@ -166,7 +166,7 @@ void split(final Circle splitCircle,
             } else {
                 // the edge is long enough to enter inside the circle
                 previousVertex = addSubEdge(previousVertex,
-                                            new Vertex(S2Point.of(circle.getPointAt(edgeStart + arcRelativeStart))),
+                                            new Vertex(S2Point.ofVector(circle.getPointAt(edgeStart + arcRelativeStart))),
                                             arcRelativeStart - alreadyManagedLength, outsideList, splitCircle);
                 alreadyManagedLength = arcRelativeStart;
 
@@ -177,7 +177,7 @@ void split(final Circle splitCircle,
                 } else {
                     // the edge is long enough to exit outside of the circle
                     previousVertex = addSubEdge(previousVertex,
-                                                new Vertex(S2Point.of(circle.getPointAt(edgeStart + arcRelativeStart))),
+                                                new Vertex(S2Point.ofVector(circle.getPointAt(edgeStart + arcRelativeStart))),
                                                 arcRelativeStart - alreadyManagedLength, insideList, splitCircle);
                     alreadyManagedLength = arcRelativeStart;
                     previousVertex = addSubEdge(previousVertex, end,
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
index 085f9c5..b44ee95 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/EdgesBuilder.java
@@ -126,7 +126,7 @@ private Edge getFollowingEdge(final Edge previous)
             for (final Edge edge : nodeToEdgesList.get(node)) {
                 if (edge != previous && edge.getStart().getIncoming() == null) {
                     final Vector3D edgeStart = edge.getStart().getLocation().getVector();
-                    final double gap         = Vector3D.angle(point.getVector(), edgeStart);
+                    final double gap         = point.getVector().angle(edgeStart);
                     if (gap <= closest) {
                         closest   = gap;
                         following = edge;
@@ -137,7 +137,7 @@ private Edge getFollowingEdge(final Edge previous)
 
         if (following == null) {
             final Vector3D previousStart = previous.getStart().getLocation().getVector();
-            if (Vector3D.angle(point.getVector(), previousStart) <= tolerance) {
+            if (point.getVector().angle(previousStart) <= tolerance) {
                 // the edge connects back to itself
                 return previous;
             }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
index 258b17e..3f6da88 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/PropertiesComputer.java
@@ -110,8 +110,8 @@ private double convexCellArea(final Vertex start) {
             final Vector3D previousPole = e.getCircle().getPole();
             final Vector3D nextPole     = e.getEnd().getOutgoing().getCircle().getPole();
             final Vector3D point        = e.getEnd().getLocation().getVector();
-            double alpha = Math.atan2(Vector3D.dotProduct(nextPole, Vector3D.crossProduct(point, previousPole)),
-                                          -Vector3D.dotProduct(nextPole, previousPole));
+            double alpha = Math.atan2(nextPole.dotProduct(point.crossProduct(previousPole)),
+                                          - nextPole.dotProduct(previousPole));
             if (alpha < 0) {
                 alpha += Geometry.TWO_PI;
             }
@@ -160,7 +160,7 @@ public S2Point getBarycenter() {
         if (summedBarycenter.getNormSq() == 0) {
             return S2Point.NaN;
         } else {
-            return S2Point.of(summedBarycenter);
+            return S2Point.ofVector(summedBarycenter);
         }
     }
 
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
index 1031b4b..cd78701 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/S2Point.java
@@ -19,7 +19,6 @@
 import java.io.Serializable;
 
 import org.apache.commons.geometry.core.Point;
-import org.apache.commons.geometry.core.internal.DoubleFunction2N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.threed.SphericalCoordinates;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
@@ -55,16 +54,6 @@
     /** Serializable UID. */
     private static final long serialVersionUID = 20180710L;
 
-    /** Factory for delegating instance creation. */
-    private static DoubleFunction2N<S2Point> FACTORY = new DoubleFunction2N<S2Point>() {
-
-        /** {@inheritDoc} */
-        @Override
-        public S2Point apply(double n1, double n2) {
-            return S2Point.of(n1, n2);
-        }
-    };
-
     /** Azimuthal angle in the x-y plane. */
     private final double azimuth;
 
@@ -221,7 +210,7 @@ public static S2Point of(final double azimuth, final double polar) {
      * @return point instance with the coordinates determined by the given 3D vector
      * @exception IllegalStateException if vector norm is zero
      */
-    public static S2Point of(final Vector3D vector) {
+    public static S2Point ofVector(final Vector3D vector) {
         SphericalCoordinates coords = vector.toSpherical();
 
         return new S2Point(coords.getAzimuth(), coords.getPolar(), vector.normalize());
@@ -234,6 +223,6 @@ public static S2Point of(final Vector3D vector) {
      * @throws IllegalArgumentException if the given string has an invalid format
      */
     public static S2Point parse(String str) throws IllegalArgumentException {
-        return SimpleTupleFormat.getDefault().parse(str, FACTORY);
+        return SimpleTupleFormat.getDefault().parse(str, S2Point::of);
     }
 }
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
index b1b6b50..d16ef8b 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSet.java
@@ -159,13 +159,13 @@ public SphericalPolygonsSet(final double hyperplaneThickness, final S2Point ...
     private static S2Point[] createRegularPolygonVertices(final Vector3D center, final Vector3D meridian,
                                                           final double outsideRadius, final int n) {
         final S2Point[] array = new S2Point[n];
-        final Rotation r0 = new Rotation(Vector3D.crossProduct(center, meridian),
+        final Rotation r0 = new Rotation(center.crossProduct(meridian),
                                          outsideRadius, RotationConvention.VECTOR_OPERATOR);
-        array[0] = S2Point.of(r0.applyTo(center));
+        array[0] = S2Point.ofVector(r0.applyTo(center));
 
         final Rotation r = new Rotation(center, Geometry.TWO_PI / n, RotationConvention.VECTOR_OPERATOR);
         for (int i = 1; i < n; ++i) {
-            array[i] = S2Point.of(r.applyTo(array[i - 1].getVector()));
+            array[i] = S2Point.ofVector(r.applyTo(array[i - 1].getVector()));
         }
 
         return array;
@@ -223,7 +223,7 @@ public SphericalPolygonsSet(final double hyperplaneThickness, final S2Point ...
 
             // create the edge and store it
             edges.add(new Edge(start, end,
-                               Vector3D.angle(start.getLocation().getVector(),
+                               start.getLocation().getVector().angle(
                                               end.getLocation().getVector()),
                                circle));
 
@@ -478,13 +478,13 @@ protected void computeGeometricalProperties() throws IllegalStateException {
         if (isEmpty(root.getMinus()) && isFull(root.getPlus())) {
             // the polygon covers an hemisphere, and its boundary is one 2π long edge
             final Circle circle = (Circle) root.getCut().getHyperplane();
-            return new EnclosingBall<>(S2Point.of(circle.getPole()).negate(),
+            return new EnclosingBall<>(S2Point.ofVector(circle.getPole()).negate(),
                                                         0.5 * Math.PI);
         }
         if (isFull(root.getMinus()) && isEmpty(root.getPlus())) {
             // the polygon covers an hemisphere, and its boundary is one 2π long edge
             final Circle circle = (Circle) root.getCut().getHyperplane();
-            return new EnclosingBall<>(S2Point.of(circle.getPole()),
+            return new EnclosingBall<>(S2Point.ofVector(circle.getPole()),
                                                         0.5 * Math.PI);
         }
 
@@ -517,7 +517,7 @@ protected void computeGeometricalProperties() throws IllegalStateException {
             EnclosingBall<S2Point> enclosingS2 =
                     new EnclosingBall<>(S2Point.PLUS_K, Double.POSITIVE_INFINITY);
             for (Point3D outsidePoint : getOutsidePoints()) {
-                final S2Point outsideS2 = S2Point.of(outsidePoint.asVector());
+                final S2Point outsideS2 = S2Point.ofVector(outsidePoint.asVector());
                 final BoundaryProjection<S2Point> projection = projectToBoundary(outsideS2);
                 if (Math.PI - projection.getOffset() < enclosingS2.getRadius()) {
                     enclosingS2 = new EnclosingBall<>(outsideS2.negate(),
@@ -529,11 +529,11 @@ protected void computeGeometricalProperties() throws IllegalStateException {
         }
         final S2Point[] support = new S2Point[support3D.length];
         for (int i = 0; i < support3D.length; ++i) {
-            support[i] = S2Point.of(support3D[i].asVector());
+            support[i] = S2Point.ofVector(support3D[i].asVector());
         }
 
         final EnclosingBall<S2Point> enclosingS2 =
-                new EnclosingBall<>(S2Point.of(enclosing3D.getCenter().asVector()),
+                new EnclosingBall<>(S2Point.ofVector(enclosing3D.getCenter().asVector()),
                                                      Math.acos((1 + h * h - r * r) / (2 * h)),
                                                      support);
 
diff --git a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SubCircle.java b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SubCircle.java
index 2205060..30349ca 100644
--- a/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SubCircle.java
+++ b/commons-geometry-spherical/src/main/java/org/apache/commons/geometry/spherical/twod/SubCircle.java
@@ -19,7 +19,6 @@
 import org.apache.commons.geometry.core.partitioning.AbstractSubHyperplane;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.core.partitioning.Region;
-import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.geometry.spherical.oned.Arc;
 import org.apache.commons.geometry.spherical.oned.ArcsSet;
 import org.apache.commons.geometry.spherical.oned.S1Point;
@@ -50,7 +49,7 @@ public SubCircle(final Hyperplane<S2Point> hyperplane,
 
         final Circle thisCircle   = (Circle) getHyperplane();
         final Circle otherCircle  = (Circle) hyperplane;
-        final double angle = Vector3D.angle(thisCircle.getPole(), otherCircle.getPole());
+        final double angle = thisCircle.getPole().angle(otherCircle.getPole());
 
         if (angle < thisCircle.getTolerance() || angle > Math.PI - thisCircle.getTolerance()) {
             // the two circles are aligned or opposite
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
index 8e96248..f281988 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/CircleTest.java
@@ -50,11 +50,11 @@ public void testXY() {
         Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
         Assert.assertEquals(0.0, circle.getPointAt(0).distance(circle.getXAxis()), 1.0e-10);
         Assert.assertEquals(0.0, circle.getPointAt(0.5 * Math.PI).distance(circle.getYAxis()), 1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(circle.getXAxis(), circle.getYAxis()), 1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(circle.getXAxis(), circle.getPole()), 1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(circle.getPole(), circle.getYAxis()), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.getXAxis().angle(circle.getYAxis()), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.getXAxis().angle(circle.getPole()), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.getPole().angle(circle.getYAxis()), 1.0e-10);
         Assert.assertEquals(0.0,
-                            circle.getPole().distance(Vector3D.crossProduct(circle.getXAxis(), circle.getYAxis())),
+                            circle.getPole().distance(circle.getXAxis().crossProduct(circle.getYAxis())),
                             1.0e-10);
     }
 
@@ -64,20 +64,19 @@ public void testReverse() {
         Circle reversed = circle.getReverse();
         Assert.assertEquals(0.0, reversed.getPointAt(0).distance(reversed.getXAxis()), 1.0e-10);
         Assert.assertEquals(0.0, reversed.getPointAt(0.5 * Math.PI).distance(reversed.getYAxis()), 1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(reversed.getXAxis(), reversed.getYAxis()), 1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(reversed.getXAxis(), reversed.getPole()), 1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(reversed.getPole(), reversed.getYAxis()), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, reversed.getXAxis().angle(reversed.getYAxis()), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, reversed.getXAxis().angle(reversed.getPole()), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, reversed.getPole().angle(reversed.getYAxis()), 1.0e-10);
         Assert.assertEquals(0.0,
-                            reversed.getPole().distance(Vector3D.crossProduct(reversed.getXAxis(), reversed.getYAxis())),
+                            reversed.getPole().distance(reversed.getXAxis().crossProduct(reversed.getYAxis())),
                             1.0e-10);
 
-        Assert.assertEquals(0, Vector3D.angle(circle.getXAxis(), reversed.getXAxis()), 1.0e-10);
-        Assert.assertEquals(Math.PI, Vector3D.angle(circle.getYAxis(), reversed.getYAxis()), 1.0e-10);
-        Assert.assertEquals(Math.PI, Vector3D.angle(circle.getPole(), reversed.getPole()), 1.0e-10);
+        Assert.assertEquals(0, circle.getXAxis().angle(reversed.getXAxis()), 1.0e-10);
+        Assert.assertEquals(Math.PI, circle.getYAxis().angle(reversed.getYAxis()), 1.0e-10);
+        Assert.assertEquals(Math.PI, circle.getPole().angle(reversed.getPole()), 1.0e-10);
 
         Assert.assertTrue(circle.sameOrientationAs(circle));
         Assert.assertFalse(circle.sameOrientationAs(reversed));
-
     }
 
     @Test
@@ -86,23 +85,22 @@ public void testPhase() {
         Vector3D p = Vector3D.of(1, 2, -4);
         Vector3D samePhase = circle.getPointAt(circle.getPhase(p));
         Assert.assertEquals(0.0,
-                            Vector3D.angle(Vector3D.crossProduct(circle.getPole(), p),
-                                           Vector3D.crossProduct(circle.getPole(), samePhase)),
+                            circle.getPole().crossProduct(p).angle(
+                                           circle.getPole().crossProduct(samePhase)),
                             1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(circle.getPole(), samePhase), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.getPole().angle(samePhase), 1.0e-10);
         Assert.assertEquals(circle.getPhase(p), circle.getPhase(samePhase), 1.0e-10);
         Assert.assertEquals(0.0, circle.getPhase(circle.getXAxis()), 1.0e-10);
         Assert.assertEquals(0.5 * Math.PI, circle.getPhase(circle.getYAxis()), 1.0e-10);
-
     }
 
     @Test
     public void testSubSpace() {
         Circle circle = new Circle(S2Point.of(1.2, 2.5), S2Point.of(-4.3, 0), 1.0e-10);
-        Assert.assertEquals(0.0, circle.toSubSpace(S2Point.of(circle.getXAxis())).getAzimuth(), 1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, circle.toSubSpace(S2Point.of(circle.getYAxis())).getAzimuth(), 1.0e-10);
+        Assert.assertEquals(0.0, circle.toSubSpace(S2Point.ofVector(circle.getXAxis())).getAzimuth(), 1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.toSubSpace(S2Point.ofVector(circle.getYAxis())).getAzimuth(), 1.0e-10);
         Vector3D p = Vector3D.of(1, 2, -4);
-        Assert.assertEquals(circle.getPhase(p), circle.toSubSpace(S2Point.of(p)).getAzimuth(), 1.0e-10);
+        Assert.assertEquals(circle.getPhase(p), circle.toSubSpace(S2Point.ofVector(p)).getAzimuth(), 1.0e-10);
     }
 
     @Test
@@ -113,19 +111,19 @@ public void testSpace() {
                                       Math.sin(alpha), circle.getYAxis());
             Vector3D q = circle.toSpace(S1Point.of(alpha)).getVector();
             Assert.assertEquals(0.0, p.distance(q), 1.0e-10);
-            Assert.assertEquals(0.5 * Math.PI, Vector3D.angle(circle.getPole(), q), 1.0e-10);
+            Assert.assertEquals(0.5 * Math.PI, circle.getPole().angle(q), 1.0e-10);
         }
     }
 
     @Test
     public void testOffset() {
         Circle circle = new Circle(Vector3D.PLUS_Z, 1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.PLUS_X)),  1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.MINUS_X)), 1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.PLUS_Y)),  1.0e-10);
-        Assert.assertEquals(0.0,                circle.getOffset(S2Point.of(Vector3D.MINUS_Y)), 1.0e-10);
-        Assert.assertEquals(-0.5 * Math.PI, circle.getOffset(S2Point.of(Vector3D.PLUS_Z)),  1.0e-10);
-        Assert.assertEquals(0.5 * Math.PI, circle.getOffset(S2Point.of(Vector3D.MINUS_Z)), 1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.ofVector(Vector3D.PLUS_X)),  1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.ofVector(Vector3D.MINUS_X)), 1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.ofVector(Vector3D.PLUS_Y)),  1.0e-10);
+        Assert.assertEquals(0.0,                circle.getOffset(S2Point.ofVector(Vector3D.MINUS_Y)), 1.0e-10);
+        Assert.assertEquals(-0.5 * Math.PI, circle.getOffset(S2Point.ofVector(Vector3D.PLUS_Z)),  1.0e-10);
+        Assert.assertEquals(0.5 * Math.PI, circle.getOffset(S2Point.ofVector(Vector3D.MINUS_Z)), 1.0e-10);
 
     }
 
@@ -134,8 +132,8 @@ public void testInsideArc() {
         UnitSphereSampler sphRandom = new UnitSphereSampler(3, RandomSource.create(RandomSource.WELL_1024_A,
                                                                                    0xbfd34e92231bbcfel));
         for (int i = 0; i < 100; ++i) {
-            Circle c1 = new Circle(Vector3D.of(sphRandom.nextVector()), 1.0e-10);
-            Circle c2 = new Circle(Vector3D.of(sphRandom.nextVector()), 1.0e-10);
+            Circle c1 = new Circle(Vector3D.ofArray(sphRandom.nextVector()), 1.0e-10);
+            Circle c2 = new Circle(Vector3D.ofArray(sphRandom.nextVector()), 1.0e-10);
             checkArcIsInside(c1, c2);
             checkArcIsInside(c2, c1);
         }
@@ -159,16 +157,16 @@ public void testTransform() {
         UnitSphereSampler sphRandom = new UnitSphereSampler(3, random);
         for (int i = 0; i < 100; ++i) {
 
-            Rotation r = new Rotation(Vector3D.of(sphRandom.nextVector()),
+            Rotation r = new Rotation(Vector3D.ofArray(sphRandom.nextVector()),
                                       Math.PI * random.nextDouble(),
                                       RotationConvention.VECTOR_OPERATOR);
             Transform<S2Point, S1Point> t = Circle.getTransform(r);
 
-            S2Point  p = S2Point.of(Vector3D.of(sphRandom.nextVector()));
+            S2Point  p = S2Point.ofVector(Vector3D.ofArray(sphRandom.nextVector()));
             S2Point tp = t.apply(p);
             Assert.assertEquals(0.0, r.applyTo(p.getVector()).distance(tp.getVector()), 1.0e-10);
 
-            Circle  c = new Circle(Vector3D.of(sphRandom.nextVector()), 1.0e-10);
+            Circle  c = new Circle(Vector3D.ofArray(sphRandom.nextVector()), 1.0e-10);
             Circle tc = (Circle) t.apply(c);
             Assert.assertEquals(0.0, r.applyTo(c.getPole()).distance(tc.getPole()),   1.0e-10);
             Assert.assertEquals(0.0, r.applyTo(c.getXAxis()).distance(tc.getXAxis()), 1.0e-10);
diff --git a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
index ce6b231..9c9f891 100644
--- a/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
+++ b/commons-geometry-spherical/src/test/java/org/apache/commons/geometry/spherical/twod/SphericalPolygonsSetTest.java
@@ -42,8 +42,8 @@ public void testFullSphere() {
                 new UnitSphereSampler(3, RandomSource.create(RandomSource.WELL_1024_A,
                                                              0x852fd2a0ed8d2f6dl));
         for (int i = 0; i < 1000; ++i) {
-            Vector3D v = Vector3D.of(random.nextVector());
-            Assert.assertEquals(Location.INSIDE, full.checkPoint(S2Point.of(v)));
+            Vector3D v = Vector3D.ofArray(random.nextVector());
+            Assert.assertEquals(Location.INSIDE, full.checkPoint(S2Point.ofVector(v)));
         }
         Assert.assertEquals(4 * Math.PI, new SphericalPolygonsSet(0.01, new S2Point[0]).getSize(), 1.0e-10);
         Assert.assertEquals(0, new SphericalPolygonsSet(0.01, new S2Point[0]).getBoundarySize(), 1.0e-10);
@@ -60,8 +60,8 @@ public void testEmpty() {
                 new UnitSphereSampler(3, RandomSource.create(RandomSource.WELL_1024_A,
                                                              0x76d9205d6167b6ddl));
         for (int i = 0; i < 1000; ++i) {
-            Vector3D v = Vector3D.of(random.nextVector());
-            Assert.assertEquals(Location.OUTSIDE, empty.checkPoint(S2Point.of(v)));
+            Vector3D v = Vector3D.ofArray(random.nextVector());
+            Assert.assertEquals(Location.OUTSIDE, empty.checkPoint(S2Point.ofVector(v)));
         }
         Assert.assertEquals(0, empty.getSize(), 1.0e-10);
         Assert.assertEquals(0, empty.getBoundarySize(), 1.0e-10);
@@ -79,13 +79,13 @@ public void testSouthHemisphere() {
                 new UnitSphereSampler(3, RandomSource.create(RandomSource.WELL_1024_A,
                                                              0x6b9d4a6ad90d7b0bl));
         for (int i = 0; i < 1000; ++i) {
-            Vector3D v = Vector3D.of(random.nextVector());
+            Vector3D v = Vector3D.ofArray(random.nextVector());
             if (v.getZ() < -sinTol) {
-                Assert.assertEquals(Location.INSIDE, south.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.INSIDE, south.checkPoint(S2Point.ofVector(v)));
             } else if (v.getZ() > sinTol) {
-                Assert.assertEquals(Location.OUTSIDE, south.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.OUTSIDE, south.checkPoint(S2Point.ofVector(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, south.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.BOUNDARY, south.checkPoint(S2Point.ofVector(v)));
             }
         }
         Assert.assertEquals(1, south.getBoundaryLoops().size());
@@ -115,13 +115,13 @@ public void testPositiveOctantByIntersection() {
                 new UnitSphereSampler(3, RandomSource.create(RandomSource.WELL_1024_A,
                                                              0x9c9802fde3cbcf25l));
         for (int i = 0; i < 1000; ++i) {
-            Vector3D v = Vector3D.of(random.nextVector());
+            Vector3D v = Vector3D.ofArray(random.nextVector());
             if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, octant.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.INSIDE, octant.checkPoint(S2Point.ofVector(v)));
             } else if ((v.getX() < -sinTol) || (v.getY() < -sinTol) || (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(S2Point.ofVector(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(S2Point.ofVector(v)));
             }
         }
 
@@ -156,7 +156,7 @@ public void testPositiveOctantByIntersection() {
         Assert.assertEquals(3, count);
 
         Assert.assertEquals(0.0,
-                            octant.getBarycenter().distance(S2Point.of(Vector3D.of(1, 1, 1))),
+                            octant.getBarycenter().distance(S2Point.ofVector(Vector3D.of(1, 1, 1))),
                             1.0e-10);
         Assert.assertEquals(0.5 * Math.PI, octant.getSize(), 1.0e-10);
 
@@ -166,7 +166,7 @@ public void testPositiveOctantByIntersection() {
 
         EnclosingBall<S2Point> reversedCap =
                 ((SphericalPolygonsSet) factory.getComplement(octant)).getEnclosingCap();
-        Assert.assertEquals(0, reversedCap.getCenter().distance(S2Point.of(Vector3D.of(-1, -1, -1))), 1.0e-10);
+        Assert.assertEquals(0, reversedCap.getCenter().distance(S2Point.ofVector(Vector3D.of(-1, -1, -1))), 1.0e-10);
         Assert.assertEquals(Math.PI - Math.asin(1.0 / Math.sqrt(3)), reversedCap.getRadius(), 1.0e-10);
 
     }
@@ -180,13 +180,13 @@ public void testPositiveOctantByVertices() {
                 new UnitSphereSampler(3, RandomSource.create(RandomSource.WELL_1024_A,
                                                              0xb8fc5acc91044308l));
         for (int i = 0; i < 1000; ++i) {
-            Vector3D v = Vector3D.of(random.nextVector());
+            Vector3D v = Vector3D.ofArray(random.nextVector());
             if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, octant.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.INSIDE, octant.checkPoint(S2Point.ofVector(v)));
             } else if ((v.getX() < -sinTol) || (v.getY() < -sinTol) || (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.OUTSIDE, octant.checkPoint(S2Point.ofVector(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.BOUNDARY, octant.checkPoint(S2Point.ofVector(v)));
             }
         }
     }
@@ -206,13 +206,13 @@ public void testNonConvex() {
                 new UnitSphereSampler(3, RandomSource.create(RandomSource.WELL_1024_A,
                                                              0x9c9802fde3cbcf25l));
         for (int i = 0; i < 1000; ++i) {
-            Vector3D v = Vector3D.of(random.nextVector());
+            Vector3D v = Vector3D.ofArray(random.nextVector());
             if (((v.getX() < -sinTol) || (v.getY() < -sinTol)) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, threeOctants.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.INSIDE, threeOctants.checkPoint(S2Point.ofVector(v)));
             } else if (((v.getX() > sinTol) && (v.getY() > sinTol)) || (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.OUTSIDE, threeOctants.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.OUTSIDE, threeOctants.checkPoint(S2Point.ofVector(v)));
             } else {
-                Assert.assertEquals(Location.BOUNDARY, threeOctants.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.BOUNDARY, threeOctants.checkPoint(S2Point.ofVector(v)));
             }
         }
 
@@ -274,14 +274,14 @@ public void testModeratlyComplexShape() {
         boundary.add(create(Vector3D.PLUS_Z,  Vector3D.MINUS_Y, Vector3D.PLUS_X,  tol, 0.0, 0.5 * Math.PI));
         SphericalPolygonsSet polygon = new SphericalPolygonsSet(boundary, tol);
 
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of( 1,  1,  1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of(-1,  1,  1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of(-1, -1,  1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of( 1, -1,  1).normalize())));
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of( 1,  1, -1).normalize())));
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of(-1,  1, -1).normalize())));
-        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.of(Vector3D.of(-1, -1, -1).normalize())));
-        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(Vector3D.of( 1, -1, -1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.ofVector(Vector3D.of( 1,  1,  1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.ofVector(Vector3D.of(-1,  1,  1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.ofVector(Vector3D.of(-1, -1,  1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.ofVector(Vector3D.of( 1, -1,  1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.ofVector(Vector3D.of( 1,  1, -1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.ofVector(Vector3D.of(-1,  1, -1).normalize())));
+        Assert.assertEquals(Location.INSIDE,  polygon.checkPoint(S2Point.ofVector(Vector3D.of(-1, -1, -1).normalize())));
+        Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.ofVector(Vector3D.of( 1, -1, -1).normalize())));
 
         Assert.assertEquals(Geometry.TWO_PI, polygon.getSize(), 1.0e-10);
         Assert.assertEquals(3 * Math.PI, polygon.getBoundarySize(), 1.0e-10);
@@ -340,17 +340,17 @@ public void testSeveralParts() {
                 new UnitSphereSampler(3, RandomSource.create(RandomSource.WELL_1024_A,
                                                              0xcc5ce49949e0d3ecl));
         for (int i = 0; i < 1000; ++i) {
-            Vector3D v = Vector3D.of(random.nextVector());
+            Vector3D v = Vector3D.ofArray(random.nextVector());
             if ((v.getX() < -sinTol) && (v.getY() < -sinTol) && (v.getZ() < -sinTol)) {
-                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(S2Point.ofVector(v)));
             } else if ((v.getX() < sinTol) && (v.getY() < sinTol) && (v.getZ() < sinTol)) {
-                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(S2Point.ofVector(v)));
             } else if ((v.getX() > sinTol) && (v.getY() > sinTol) && (v.getZ() > sinTol)) {
-                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.INSIDE, polygon.checkPoint(S2Point.ofVector(v)));
             } else if ((v.getX() > -sinTol) && (v.getY() > -sinTol) && (v.getZ() > -sinTol)) {
-                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.BOUNDARY, polygon.checkPoint(S2Point.ofVector(v)));
             } else {
-                Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.of(v)));
+                Assert.assertEquals(Location.OUTSIDE, polygon.checkPoint(S2Point.ofVector(v)));
             }
         }
 
@@ -366,7 +366,7 @@ public void testSeveralParts() {
     public void testPartWithHole() {
         double tol = 0.01;
         double alpha = 0.7;
-        S2Point center = S2Point.of(Vector3D.of(1, 1, 1));
+        S2Point center = S2Point.ofVector(Vector3D.of(1, 1, 1));
         SphericalPolygonsSet hexa = new SphericalPolygonsSet(center.getVector(), Vector3D.PLUS_Z, alpha, 6, tol);
         SphericalPolygonsSet hole  = new SphericalPolygonsSet(tol,
                                                               S2Point.of(Math.PI / 6, Math.PI / 3),
@@ -428,7 +428,7 @@ public void testConcentricSubParts() {
                             concentric.getSize(), 1.0e-10);
 
         // we expect lots of sign changes as we traverse all concentric rings
-        double phi = S2Point.of(center).getPolar();
+        double phi = S2Point.ofVector(center).getPolar();
         Assert.assertEquals(+0.207, concentric.projectToBoundary(S2Point.of(-0.60,  phi)).getOffset(), 0.01);
         Assert.assertEquals(-0.048, concentric.projectToBoundary(S2Point.of(-0.21,  phi)).getOffset(), 0.01);
         Assert.assertEquals(+0.027, concentric.projectToBoundary(S2Point.of(-0.10,  phi)).getOffset(), 0.01);
@@ -478,7 +478,7 @@ public void testGeographicalMap() {
                 ++count;
                 for (int i = 0; i < Math.ceil(v.getOutgoing().getLength() / step); ++i) {
                     Vector3D p = v.getOutgoing().getPointAt(i * step);
-                    Assert.assertTrue(Vector3D.angle(p, enclosingCenter) <= enclosing.getRadius());
+                    Assert.assertTrue(p.angle(enclosingCenter) <= enclosing.getRadius());
                 }
             }
         }
@@ -500,7 +500,7 @@ public void testGeographicalMap() {
                 ++count;
                 for (int i = 0; i < Math.ceil(v.getOutgoing().getLength() / step); ++i) {
                     Vector3D p = v.getOutgoing().getPointAt(i * step);
-                    Assert.assertTrue(Vector3D.angle(p, continentalCenter) <= continentalInscribed.getRadius());
+                    Assert.assertTrue(p.angle(continentalCenter) <= continentalInscribed.getRadius());
                 }
             }
         }
@@ -515,7 +515,7 @@ public void testGeographicalMap() {
                 ++count;
                 for (int i = 0; i < Math.ceil(v.getOutgoing().getLength() / step); ++i) {
                     Vector3D p = v.getOutgoing().getPointAt(i * step);
-                    Assert.assertTrue(Vector3D.angle(p, corsicaCenter) <= corsicaInscribed.getRadius());
+                    Assert.assertTrue(p.angle(corsicaCenter) <= corsicaInscribed.getRadius());
                 }
             }
         }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
For additional commands, e-mail: dev-help@commons.apache.org