git.net

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GitHub] asfgit closed pull request #11: Geometry 17


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

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/AffinePoint.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/AffinePoint.java
index 80d7b22..a684da4 100644
--- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/AffinePoint.java
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/AffinePoint.java
@@ -30,13 +30,13 @@
  */
 public interface AffinePoint<P extends AffinePoint<P, V>, V extends Vector<V>> extends Point<P> {
 
-    /** Returns the displacement vector from this point to p.
+    /** Get the displacement vector from this point to p.
      * @param p second point
      * @return The displacement vector from this point to p.
      */
     V subtract(P p);
 
-    /** Returns the point resulting from adding the given displacement
+    /** Get the point resulting from adding the given displacement
      * vector to this point.
      * @param v displacement vector
      * @return point resulting from displacing this point by v
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 4804a80..b37c4ea 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
@@ -44,8 +44,8 @@
     double getNorm1();
 
     /** Get the L<sub>2</sub> norm (commonly known as the Euclidean norm) for the vector.
-     * 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.
+     * 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.
      * @see <a href="http://mathworld.wolfram.com/L2-Norm.html";>L2 Norm</a>
      * @return L<sub>2</sub> norm for the vector
      */
@@ -65,28 +65,13 @@
      */
     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)}
+     * norm. 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
+     * @param norm The vector norm
+     * @return a vector with the same direction as the current instance but the given norm
      */
-    V withMagnitude(double magnitude);
+    V withNorm(double norm);
 
     /** Add a vector to the instance.
      * @param v vector to add
@@ -174,34 +159,6 @@
      */
     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 IllegalNormException if the norm of the base vector is zero, NaN, or infinite
-     * @see #reject(Vector)
-     */
-    V project(V base);
-
-    /** 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 IllegalNormException if the norm of the base vector is zero, NaN, or infinite
-     * @see #project(Vector)
-     */
-    V reject(V base);
-
     /** Compute the angular separation between two vectors in radians.
      * @param v other vector
      * @return angular separation between this instance and v in radians
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 5aecf85..f3c332c 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
@@ -26,7 +26,7 @@
  */
 public interface EuclideanPoint<P extends EuclideanPoint<P, V>, V extends EuclideanVector<P, V>> extends AffinePoint<P, V> {
 
-    /** Returns a vector with the same coordinates as this point.
+    /** Get a vector with the same coordinates as this point.
      * This is equivalent to the expression {@code v = P - Z} where
      * {@code P} is this point, {@code Z} is the zero point. and
      * {@code v} is the returned vector.
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 ad4f21b..595d5e3 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
@@ -18,14 +18,14 @@
 
 import org.apache.commons.geometry.core.Vector;
 
-/** Represents a vector in a Euclidean space of any dimension.
+/** Vector extension interface for working in Euclidean space.
  *
  * @param <P> Point implementation type
  * @param <V> Vector implementation type
  */
 public interface EuclideanVector<P extends EuclideanPoint<P, V>, V extends EuclideanVector<P, V>> extends Vector<V> {
 
-    /** Returns a point with the same coordinates as this vector.
+    /** Get a point with the same coordinates as this vector.
      * This is equivalent to the expression {@code P = Z + v}, where
      * {@code v} is this vector, {@code Z} is the zero point, and
      * {@code P} is the returned point.
@@ -33,12 +33,12 @@
      */
     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.
+    /** Get a vector constructed by linearly interpolating between this vector and the given vector.
+     * The vector coordinates are generated by 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
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/MultiDimensionalEuclideanVector.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/MultiDimensionalEuclideanVector.java
new file mode 100644
index 0000000..6553a53
--- /dev/null
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/MultiDimensionalEuclideanVector.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import org.apache.commons.geometry.core.exception.IllegalNormException;
+
+/**
+ * Euclidean vector extension interface with methods applicable to spaces of
+ * two or more dimensions.
+ *
+ * @param <P> Point implementation type
+ * @param <V> Vector implementation type
+ */
+public interface MultiDimensionalEuclideanVector<P extends EuclideanPoint<P, V>, V extends MultiDimensionalEuclideanVector<P, V>>
+        extends EuclideanVector<P, 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 IllegalNormException if the norm of the base vector is zero, NaN, or infinite
+     * @see #reject(MultiDimensionalEuclideanVector)
+     */
+    V project(V base);
+
+    /** 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 IllegalNormException if the norm of the base vector is zero, NaN, or infinite
+     * @see #project(MultiDimensionalEuclideanVector)
+     */
+    V reject(V base);
+
+    /** Get a unit vector orthogonal to the instance.
+     * @return a unit vector orthogonal to the current instance
+     * @throws IllegalNormException if the norm of the current instance is zero, NaN,
+     *  or infinite
+     */
+    V orthogonal();
+
+    /** Get a unit vector orthogonal to the current vector and pointing in the direction
+     * of {@code dir}. This method is equivalent to calling {@code dir.reject(vec).normalize()}
+     * except that no intermediate vector object is produced.
+     * @param dir the direction to use for generating the orthogonal vector
+     * @return unit vector orthogonal to the current vector and pointing in the direction of
+     *      {@code dir} that does not lie along the current vector
+     * @throws IllegalNormException if either vector norm is zero, NaN or infinite,
+     *      or the given vector is collinear with this vector.
+     */
+    V orthogonal(V dir);
+}
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java
index 31ec162..cc054a5 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/Vectors.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.geometry.euclidean.internal;
 
+import org.apache.commons.geometry.core.Vector;
 import org.apache.commons.geometry.core.exception.IllegalNormException;
 
 /** This class consists exclusively of static vector utility methods.
@@ -25,33 +26,44 @@
     /** Private constructor. */
     private Vectors() {}
 
-    /** Returns true if the given value is finite (ie, not NaN or inifinite)
+    /** Returns true if the given value is real (ie, not NaN or inifinite)
      * and not equal to zero.
      * @param value the value to test
      * @return true if {@code value} is not NaN, infinite, or zero; otherwise
      *      false
      */
-    public static boolean isFiniteNonZero(final double value) {
+    public static boolean isRealNonZero(final double value) {
         return Double.isFinite(value) && value != 0.0;
     }
 
 
-    /** Throws an {@link IllegalNormException} if the  given norm value
-     * is not finite (ie, NaN or infinite) or zero. The argument is returned
+    /** Throws an {@link IllegalNormException} if the given norm value
+     * is not real (ie, not NaN or infinite) or zero. The argument is returned
      * to allow this method to be called inline.
      * @param norm vector norm value
      * @return the validated norm value
      * @throws IllegalNormException if the given norm value is NaN, infinite,
      *  or zero
      */
-    public static double ensureFiniteNonZeroNorm(final double norm) throws IllegalNormException {
-        if (!isFiniteNonZero(norm)) {
+    public static double checkedNorm(final double norm) throws IllegalNormException {
+        if (!isRealNonZero(norm)) {
             throw new IllegalNormException(norm);
         }
 
         return norm;
     }
 
+    /** Returns the vector's norm value, throwing an {@link IllegalNormException} if the value
+     * is not real (ie, not NaN or infinite) or zero.
+     * @param vec vector to obtain the real, non-zero norm of
+     * @return the validated norm value
+     * @throws IllegalNormException if the vector norm value is NaN, infinite,
+     *  or zero
+     */
+    public static double checkedNorm(final Vector<?> vec) {
+        return checkedNorm(vec.getNorm());
+    }
+
     /** 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
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
deleted file mode 100644
index fe9ecf1..0000000
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/ZeroNormException.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.geometry.euclidean.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/oned/Vector1D.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/oned/Vector1D.java
index b678559..97cba0a 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
@@ -104,20 +104,8 @@ public double getNormInf() {
 
     /** {@inheritDoc} */
     @Override
-    public double getMagnitude() {
-        return getNorm();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public double getMagnitudeSq() {
-        return getNormSq();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Vector1D withMagnitude(double magnitude) {
-        Vectors.ensureFiniteNonZeroNorm(getNorm());
+    public Vector1D withNorm(double magnitude) {
+        getCheckedNorm(); // validate our norm value
 
         return (getX() > 0.0)? new Vector1D(magnitude) : new Vector1D(-magnitude);
     }
@@ -194,34 +182,15 @@ 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) {
-        Vectors.ensureFiniteNonZeroNorm(base.getNorm());
-
-        return this;
-    }
-
-    /** {@inheritDoc}
-     * <p>For the one-dimensional case, this method simply returns the zero vector.</p>
-     */
-    @Override
-    public Vector1D reject(final Vector1D base) {
-        Vectors.ensureFiniteNonZeroNorm(base.getNorm());
-
-        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) {
-        Vectors.ensureFiniteNonZeroNorm(getNorm());
-        Vectors.ensureFiniteNonZeroNorm(v.getNorm());
+        // validate the norm values
+        getCheckedNorm();
+        v.getCheckedNorm();
 
         final double sig1 = Math.signum(getX());
         final double sig2 = Math.signum(v.getX());
@@ -281,6 +250,15 @@ public boolean equals(Object other) {
         return false;
     }
 
+    /** Returns the vector norm value, throwing an {@link IllegalNormException} if the value
+     * is not real (ie, NaN or infinite) or zero.
+     * @return the vector norm value, guaranteed to be real and non-zero
+     * @throws IllegalNormException if the vector norm is zero, NaN, or infinite
+     */
+    private double getCheckedNorm() {
+        return Vectors.checkedNorm(getNorm());
+    }
+
     /** Returns a vector with the given coordinate value.
      * @param x vector coordinate
      * @return vector instance
@@ -295,7 +273,7 @@ public static Vector1D of(double x) {
      * @throws IllegalNormException if the norm of the given value is zero, NaN, or infinite
      */
     public static Vector1D normalize(final double x) {
-        Vectors.ensureFiniteNonZeroNorm(Vectors.norm(x));
+        Vectors.checkedNorm(Vectors.norm(x));
 
         return (x > 0.0) ? ONE : MINUS_ONE;
     }
@@ -415,7 +393,7 @@ public Vector1D normalize() {
 
         /** {@inheritDoc} */
         @Override
-        public Vector1D withMagnitude(final double mag) {
+        public Vector1D withNorm(final double mag) {
             return scalarMultiply(mag);
         }
     }
diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
index 424fbb3..4d5ba76 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Plane.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.geometry.core.partitioning.Embedding;
 import org.apache.commons.geometry.core.partitioning.Hyperplane;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
@@ -139,10 +140,10 @@ public void reset(final Plane original) {
 
     /** Set the normal vactor.
      * @param normal normal direction to the plane (will be copied)
-     * @exception IllegalArgumentException if the normal norm is too close to zero
+     * @exception IllegalNormException if the normal norm zero, NaN, or infinite
      */
     private void setNormal(final Vector3D normal) {
-        final double norm = Vectors.ensureFiniteNonZeroNorm(normal.getNorm());
+        final double norm = Vectors.checkedNorm(normal);
 
         w = Vector3D.linearCombination(1.0 / norm, normal);
     }
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 466aedc..b845759 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
@@ -173,10 +173,7 @@ public Rotation(Vector3D axis, double angle) {
   public Rotation(final Vector3D axis, final double angle, final RotationConvention convention)
       throws IllegalNormException {
 
-    double norm = axis.getNorm();
-    if (!Vectors.isFiniteNonZero(norm)) {
-      throw new IllegalNormException("Illegal norm for rotation axis: " + norm);
-    }
+    double norm = Vectors.checkedNorm(axis);
 
     double halfAngle = convention == RotationConvention.VECTOR_OPERATOR ? -0.5 * angle : +0.5 * angle;
     double coeff = Math.sin(halfAngle) / norm;
@@ -320,10 +317,7 @@ public Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2)
    */
   public Rotation(Vector3D u, Vector3D v) {
 
-    double normProduct = u.getNorm() * v.getNorm();
-    if (!Vectors.isFiniteNonZero(normProduct)) {
-        throw new IllegalNormException("Illegal norm for rotation defining vector: " + normProduct);
-    }
+    double normProduct = Vectors.checkedNorm(u) * Vectors.checkedNorm(v);
 
     double dot = u.dotProduct(v);
 
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 0014d4d..c075ff2 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
@@ -20,14 +20,14 @@
 import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.geometry.core.internal.DoubleFunction3N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
-import org.apache.commons.geometry.euclidean.EuclideanVector;
+import org.apache.commons.geometry.euclidean.MultiDimensionalEuclideanVector;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in three-dimensional Euclidean space.
  * Instances of this class are guaranteed to be immutable.
  */
-public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Vector3D> {
+public class Vector3D extends Cartesian3D implements MultiDimensionalEuclideanVector<Point3D, Vector3D> {
 
     /** Zero (null) vector (coordinates: 0, 0, 0). */
     public static final Vector3D ZERO   = new Vector3D(0, 0, 0);
@@ -120,20 +120,8 @@ public double getNormInf() {
 
     /** {@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 / getFiniteNonZeroNorm();
+    public Vector3D withNorm(double magnitude) {
+        final double invNorm = 1.0 / getCheckedNorm();
 
         return new Vector3D(
                     magnitude * getX() * invNorm,
@@ -194,89 +182,6 @@ public Vector3D normalize() {
         return normalize(getX(), getY(), getZ());
     }
 
-    /** Get a vector orthogonal to the instance.
-     * <p>There are an infinite number of normalized vectors orthogonal
-     * to the instance. This method picks up one of them almost
-     * arbitrarily. It is useful when one needs to compute a reference
-     * frame with one of the axes in a predefined direction. The
-     * following example shows how to build a frame having the k axis
-     * aligned with the known vector u :
-     * <pre><code>
-     *   Vector3D k = u.normalize();
-     *   Vector3D i = k.orthogonal();
-     *   Vector3D j = Vector3D.crossProduct(k, i);
-     * </code></pre>
-     * @return a new normalized vector orthogonal to the instance
-     * @exception IllegalNormException if the norm of the instance is zero, NaN,
-     *  or infinite
-     */
-    public Vector3D orthogonal() {
-        double threshold = 0.6 * getFiniteNonZeroNorm();
-
-        final double x = getX();
-        final double y = getY();
-        final double z = getZ();
-
-        if (Math.abs(x) <= threshold) {
-            double inverse  = 1 / Math.sqrt(y * y + z * z);
-            return new Vector3D(0, inverse * z, -inverse * y);
-        } else if (Math.abs(y) <= threshold) {
-            double inverse  = 1 / Math.sqrt(x * x + z * z);
-            return new Vector3D(-inverse * z, 0, inverse * x);
-        }
-        double inverse  = 1 / Math.sqrt(x * x + y * y);
-        return new Vector3D(inverse * y, -inverse * x, 0);
-    }
-
-    /** Returns a unit vector orthogonal to the current vector and pointing in the direction
-     * of {@code dir}. This method is equivalent to calling {@code dir.reject(vec).normalize()}
-     * except that no intermediate vector object is produced.
-     * @param dir the direction to use for generating the orthogonal vector
-     * @return unit vector orthogonal to the current vector and pointing in the direction of
-     *      {@code dir} that does not lie along the current vector
-     * @throws IllegalNormException if either vector norm is zero, NaN or infinite,
-     *      or the given vector is collinear with this vector.
-     */
-    public Vector3D orthogonal(Vector3D dir) {
-        return dir.getComponent(this, true, Vector3D::normalize);
-    }
-
-    /** {@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>
-     */
-    @Override
-    public double angle(Vector3D v) {
-        double normProduct = getFiniteNonZeroNorm() * v.getFiniteNonZeroNorm();
-
-        double dot = dotProduct(v);
-        double threshold = normProduct * 0.9999;
-        if ((dot < -threshold) || (dot > threshold)) {
-            // the vectors are almost aligned, compute using the sine
-            Vector3D cross = crossProduct(v);
-            if (dot >= 0) {
-                return Math.asin(cross.getNorm() / normProduct);
-            }
-            return Math.PI - Math.asin(cross.getNorm() / normProduct);
-        }
-
-        // the vectors are sufficiently separated to use the cosine
-        return Math.acos(dot / normProduct);
-    }
-
-    /** Compute the cross-product of the instance with another vector.
-     * @param v other vector
-     * @return the cross product this ^ v as a new Cartesian3D
-     */
-    public Vector3D crossProduct(final Vector3D v) {
-        return new Vector3D(LinearCombination.value(getY(), v.getZ(), -getZ(), v.getY()),
-                            LinearCombination.value(getZ(), v.getX(), -getX(), v.getZ()),
-                            LinearCombination.value(getX(), v.getY(), -getY(), v.getX()));
-    }
-
     /** {@inheritDoc} */
     @Override
     public Vector3D scalarMultiply(double a) {
@@ -336,6 +241,32 @@ public double dotProduct(Vector3D v) {
         return LinearCombination.value(getX(), v.getX(), getY(), v.getY(), getZ(), v.getZ());
     }
 
+    /** {@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>
+     */
+    @Override
+    public double angle(Vector3D v) {
+        double normProduct = getCheckedNorm() * v.getCheckedNorm();
+
+        double dot = dotProduct(v);
+        double threshold = normProduct * 0.9999;
+        if ((dot < -threshold) || (dot > threshold)) {
+            // the vectors are almost aligned, compute using the sine
+            Vector3D cross = crossProduct(v);
+            if (dot >= 0) {
+                return Math.asin(cross.getNorm() / normProduct);
+            }
+            return Math.PI - Math.asin(cross.getNorm() / normProduct);
+        }
+
+        // the vectors are sufficiently separated to use the cosine
+        return Math.acos(dot / normProduct);
+    }
+
     /** {@inheritDoc} */
     @Override
     public Vector3D project(Vector3D base) {
@@ -348,6 +279,57 @@ public Vector3D reject(Vector3D base) {
         return getComponent(base, true, Vector3D::new);
     }
 
+    /** {@inheritDoc}
+     * <p>There are an infinite number of normalized vectors orthogonal
+     * to the instance. This method picks up one of them almost
+     * arbitrarily. It is useful when one needs to compute a reference
+     * frame with one of the axes in a predefined direction. The
+     * following example shows how to build a frame having the k axis
+     * aligned with the known vector u :
+     * <pre><code>
+     *   Vector3D k = u.normalize();
+     *   Vector3D i = k.orthogonal();
+     *   Vector3D j = k.crossProduct(i);
+     * </code></pre>
+     * @return a unit vector orthogonal to the instance
+     * @throws IllegalNormException if the norm of the instance is zero, NaN,
+     *  or infinite
+     */
+    @Override
+    public Vector3D orthogonal() {
+        double threshold = 0.6 * getCheckedNorm();
+
+        final double x = getX();
+        final double y = getY();
+        final double z = getZ();
+
+        if (Math.abs(x) <= threshold) {
+            double inverse  = 1 / Math.sqrt(y * y + z * z);
+            return new Vector3D(0, inverse * z, -inverse * y);
+        } else if (Math.abs(y) <= threshold) {
+            double inverse  = 1 / Math.sqrt(x * x + z * z);
+            return new Vector3D(-inverse * z, 0, inverse * x);
+        }
+        double inverse  = 1 / Math.sqrt(x * x + y * y);
+        return new Vector3D(inverse * y, -inverse * x, 0);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D orthogonal(Vector3D dir) {
+        return dir.getComponent(this, true, Vector3D::normalize);
+    }
+
+    /** Compute the cross-product of the instance with another vector.
+     * @param v other vector
+     * @return the cross product this ^ v as a new Cartesian3D
+     */
+    public Vector3D crossProduct(final Vector3D v) {
+        return new Vector3D(LinearCombination.value(getY(), v.getZ(), -getZ(), v.getY()),
+                            LinearCombination.value(getZ(), v.getX(), -getX(), v.getZ()),
+                            LinearCombination.value(getX(), v.getY(), -getY(), v.getX()));
+    }
+
     /**
      * Get a hashCode for the vector.
      * <p>All NaN values have the same hash code.</p>
@@ -398,15 +380,6 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** Returns the vector norm, throwing an IllegalNormException if the norm is zero,
-     * NaN, or infinite.
-     * @return the finite, non-zero norm value
-     * @throws IllegalNormException if the norm is zero, NaN, or infinite
-     */
-    private double getFiniteNonZeroNorm() {
-        return Vectors.ensureFiniteNonZeroNorm(getNorm());
-    }
-
     /** 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.
@@ -428,7 +401,7 @@ private Vector3D getComponent(Vector3D base, boolean reject, DoubleFunction3N<Ve
         // directly. This will produce the same error result as checking the actual norm since
         // Math.sqrt(0.0) == 0.0, Math.sqrt(Double.NaN) == Double.NaN and
         // Math.sqrt(Double.POSITIVE_INFINITY) == Double.POSITIVE_INFINITY.
-        final double baseMagSq = Vectors.ensureFiniteNonZeroNorm(base.getNormSq());
+        final double baseMagSq = Vectors.checkedNorm(base.getNormSq());
 
         final double scale = aDotB / baseMagSq;
 
@@ -443,6 +416,15 @@ private Vector3D getComponent(Vector3D base, boolean reject, DoubleFunction3N<Ve
         return factory.apply(projX, projY, projZ);
     }
 
+    /** Returns the vector norm value, throwing an {@link IllegalNormException} if the value
+     * is not real (ie, NaN or infinite) or zero.
+     * @return the vector norm value, guaranteed to be real and non-zero
+     * @throws IllegalNormException if the vector norm is zero, NaN, or infinite
+     */
+    private double getCheckedNorm() {
+        return Vectors.checkedNorm(getNorm());
+    }
+
     /** Returns a vector with the given coordinate values.
      * @param x abscissa (first coordinate value)
      * @param y abscissa (second coordinate value)
@@ -484,7 +466,7 @@ public static Vector3D ofSpherical(double radius, double azimuth, double polar)
      * @throws IllegalNormException if the norm of the given values is zero, NaN, or infinite
      */
     public static Vector3D normalize(final double x, final double y, final double z) {
-        final double norm = Vectors.ensureFiniteNonZeroNorm(Vectors.norm(x, y, z));
+        final double norm = Vectors.checkedNorm(Vectors.norm(x, y, z));
         final double invNorm = 1.0 / norm;
 
         return new UnitVector(x * invNorm, y * invNorm, z * invNorm);
@@ -613,7 +595,7 @@ public Vector3D normalize() {
 
         /** {@inheritDoc} */
         @Override
-        public Vector3D withMagnitude(final double mag) {
+        public Vector3D withNorm(final double mag) {
             return scalarMultiply(mag);
         }
     }
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 7ae7edf..3a936f9 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
@@ -17,15 +17,16 @@
 package org.apache.commons.geometry.euclidean.twod;
 
 import org.apache.commons.geometry.core.exception.IllegalNormException;
+import org.apache.commons.geometry.core.internal.DoubleFunction2N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
-import org.apache.commons.geometry.euclidean.EuclideanVector;
+import org.apache.commons.geometry.euclidean.MultiDimensionalEuclideanVector;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
 import org.apache.commons.numbers.arrays.LinearCombination;
 
 /** This class represents a vector in two-dimensional Euclidean space.
  * Instances of this class are guaranteed to be immutable.
  */
-public class Vector2D extends Cartesian2D implements EuclideanVector<Point2D, Vector2D> {
+public class Vector2D extends Cartesian2D implements MultiDimensionalEuclideanVector<Point2D, Vector2D> {
 
     /** Zero vector (coordinates: 0, 0). */
     public static final Vector2D ZERO   = new Vector2D(0, 0);
@@ -110,20 +111,8 @@ public double getNormInf() {
 
     /** {@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 / getFiniteNonZeroNorm();
+    public Vector2D withNorm(double magnitude) {
+        final double invNorm = 1.0 / getCheckedNorm();
 
         return new Vector2D(
                     magnitude * getX() * invNorm,
@@ -203,18 +192,6 @@ public double dotProduct(Vector2D v) {
         return LinearCombination.value(getX(), v.getX(), getY(), v.getY());
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public Vector2D project(Vector2D base) {
-        return getComponent(base, false);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Vector2D reject(Vector2D base) {
-        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
@@ -224,7 +201,7 @@ public Vector2D reject(Vector2D base) {
      */
     @Override
     public double angle(Vector2D v) {
-        double normProduct = getFiniteNonZeroNorm() * v.getFiniteNonZeroNorm();
+        double normProduct = getCheckedNorm() * v.getCheckedNorm();
 
         double dot = dotProduct(v);
         double threshold = normProduct * 0.9999;
@@ -241,6 +218,38 @@ public double angle(Vector2D v) {
         return Math.acos(dot / normProduct);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D project(Vector2D base) {
+        return getComponent(base, false, Vector2D::new);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D reject(Vector2D base) {
+        return getComponent(base, true, Vector2D::new);
+    }
+
+    /** {@inheritDoc}
+     * The returned vector is computed by rotating the current instance {@code pi/2} radians
+     * counterclockwise around the origin and normalizing. For example, if this method is
+     * called on a vector pointing along the positive x-axis, then a unit vector representing
+     * the positive y-axis is returned.
+     * @return a unit vector orthogonal to the current instance
+     * @throws IllegalNormException if the norm of the current instance is zero, NaN,
+     *  or infinite
+     */
+    @Override
+    public Vector2D orthogonal() {
+        return normalize(-getY(), getX());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D orthogonal(Vector2D dir) {
+        return dir.getComponent(this, true, Vector2D::normalize);
+    }
+
     /**
      * Compute the cross-product of the instance and the given vector.
      * <p>
@@ -320,15 +329,6 @@ public boolean equals(Object other) {
         return false;
     }
 
-    /** Returns the vector norm, throwing an IllegalNormException if the norm is zero,
-     * NaN, or infinite.
-     * @return the finite, non-zero norm value
-     * @throws IllegalNormException if the norm is zero, NaN, or infinite
-     */
-    private double getFiniteNonZeroNorm() {
-        return Vectors.ensureFiniteNonZeroNorm(getNorm());
-    }
-
     /** 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.
@@ -336,25 +336,41 @@ private double getFiniteNonZeroNorm() {
      * @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.
+     * @param factory factory function used to build the final vector
      * @return The projection or rejection of this instance relative to {@code base},
      *      depending on the value of {@code reject}.
      * @throws IllegalNormException if {@code base} has a zero, NaN, or infinite norm
      */
-    private Vector2D getComponent(Vector2D base, boolean reject) {
+    private Vector2D getComponent(Vector2D base, boolean reject, DoubleFunction2N<Vector2D> factory) {
         final double aDotB = dotProduct(base);
 
-        final double baseMag = Vectors.ensureFiniteNonZeroNorm(base.getNorm());
+        // We need to check the norm value here to ensure that it's legal. However, we don't
+        // want to incur the cost or floating point error of getting the actual norm and then
+        // multiplying it again to get the square norm. So, we'll just check the squared norm
+        // directly. This will produce the same error result as checking the actual norm since
+        // Math.sqrt(0.0) == 0.0, Math.sqrt(Double.NaN) == Double.NaN and
+        // Math.sqrt(Double.POSITIVE_INFINITY) == Double.POSITIVE_INFINITY.
+        final double baseMagSq = Vectors.checkedNorm(base.getNormSq());
 
-        final double scale = aDotB / (baseMag * baseMag);
+        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 factory.apply(getX() - projX, getY() - projY);
         }
 
-        return new Vector2D(projX, projY);
+        return factory.apply(projX, projY);
+    }
+
+    /** Returns the vector norm value, throwing an {@link IllegalNormException} if the value
+     * is not real (ie, NaN or infinite) or zero.
+     * @return the vector norm value, guaranteed to be real and non-zero
+     * @throws IllegalNormException if the vector norm is zero, NaN, or infinite
+     */
+    private double getCheckedNorm() {
+        return Vectors.checkedNorm(getNorm());
     }
 
     /** Returns a vector with the given coordinate values.
@@ -394,7 +410,7 @@ public static Vector2D ofPolar(final double radius, final double azimuth) {
      * @throws IllegalNormException if the norm of the given values is zero, NaN, or infinite
      */
     public static Vector2D normalize(final double x, final double y) {
-        final double norm = Vectors.ensureFiniteNonZeroNorm(Vectors.norm(x, y));
+        final double norm = Vectors.checkedNorm(Vectors.norm(x, y));
         final double invNorm = 1.0 / norm;
 
         return new UnitVector(x * invNorm, y * invNorm);
@@ -519,7 +535,7 @@ public Vector2D normalize() {
 
         /** {@inheritDoc} */
         @Override
-        public Vector2D withMagnitude(final double mag) {
+        public Vector2D withNorm(final double mag) {
             return scalarMultiply(mag);
         }
     }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/VectorsTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/VectorsTest.java
index 2d38b03..207df63 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/VectorsTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/internal/VectorsTest.java
@@ -18,6 +18,8 @@
 
 import org.apache.commons.geometry.core.GeometryTestUtils;
 import org.apache.commons.geometry.core.exception.IllegalNormException;
+import org.apache.commons.geometry.euclidean.oned.Vector1D;
+import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -29,38 +31,59 @@
     @Test
     public void testIsFiniteNonZero() {
         // act/assert
-        Assert.assertTrue(Vectors.isFiniteNonZero(1e-20));
-        Assert.assertTrue(Vectors.isFiniteNonZero(1e20));
-        Assert.assertTrue(Vectors.isFiniteNonZero(-1e-20));
-        Assert.assertTrue(Vectors.isFiniteNonZero(-1e20));
-
-        Assert.assertFalse(Vectors.isFiniteNonZero(0.0));
-        Assert.assertFalse(Vectors.isFiniteNonZero(Double.NaN));
-        Assert.assertFalse(Vectors.isFiniteNonZero(Double.POSITIVE_INFINITY));
-        Assert.assertFalse(Vectors.isFiniteNonZero(Double.NEGATIVE_INFINITY));
+        Assert.assertTrue(Vectors.isRealNonZero(1e-20));
+        Assert.assertTrue(Vectors.isRealNonZero(1e20));
+        Assert.assertTrue(Vectors.isRealNonZero(-1e-20));
+        Assert.assertTrue(Vectors.isRealNonZero(-1e20));
+
+        Assert.assertFalse(Vectors.isRealNonZero(0.0));
+        Assert.assertFalse(Vectors.isRealNonZero(Double.NaN));
+        Assert.assertFalse(Vectors.isRealNonZero(Double.POSITIVE_INFINITY));
+        Assert.assertFalse(Vectors.isRealNonZero(Double.NEGATIVE_INFINITY));
     }
 
     @Test
-    public void testEnsureFiniteNonZeroNorm() {
+    public void testCheckedNorm_normArg() {
         // act/assert
-        Assert.assertEquals(1.0, Vectors.ensureFiniteNonZeroNorm(1.0), EPS);
-        Assert.assertEquals(23.12, Vectors.ensureFiniteNonZeroNorm(23.12), EPS);
-        Assert.assertEquals(2e-12, Vectors.ensureFiniteNonZeroNorm(2e-12), EPS);
+        Assert.assertEquals(1.0, Vectors.checkedNorm(1.0), EPS);
+        Assert.assertEquals(23.12, Vectors.checkedNorm(23.12), EPS);
+        Assert.assertEquals(2e-12, Vectors.checkedNorm(2e-12), EPS);
 
-        Assert.assertEquals(-1.0, Vectors.ensureFiniteNonZeroNorm(-1.0), EPS);
-        Assert.assertEquals(-23.12, Vectors.ensureFiniteNonZeroNorm(-23.12), EPS);
-        Assert.assertEquals(-2e-12, Vectors.ensureFiniteNonZeroNorm(-2e-12), EPS);
+        Assert.assertEquals(-1.0, Vectors.checkedNorm(-1.0), EPS);
+        Assert.assertEquals(-23.12, Vectors.checkedNorm(-23.12), EPS);
+        Assert.assertEquals(-2e-12, Vectors.checkedNorm(-2e-12), EPS);
 
-        GeometryTestUtils.assertThrows(() -> Vectors.ensureFiniteNonZeroNorm(0.0),
+        GeometryTestUtils.assertThrows(() -> Vectors.checkedNorm(0.0),
                 IllegalNormException.class, "Illegal norm: 0.0");
-        GeometryTestUtils.assertThrows(() -> Vectors.ensureFiniteNonZeroNorm(Double.NaN),
+        GeometryTestUtils.assertThrows(() -> Vectors.checkedNorm(Double.NaN),
                 IllegalNormException.class, "Illegal norm: NaN");
-        GeometryTestUtils.assertThrows(() -> Vectors.ensureFiniteNonZeroNorm(Double.POSITIVE_INFINITY),
+        GeometryTestUtils.assertThrows(() -> Vectors.checkedNorm(Double.POSITIVE_INFINITY),
                 IllegalNormException.class, "Illegal norm: Infinity");
-        GeometryTestUtils.assertThrows(() -> Vectors.ensureFiniteNonZeroNorm(Double.NEGATIVE_INFINITY),
+        GeometryTestUtils.assertThrows(() -> Vectors.checkedNorm(Double.NEGATIVE_INFINITY),
                 IllegalNormException.class, "Illegal norm: -Infinity");
     }
 
+    @Test
+    public void testCheckedNorm_vectorArg() {
+        // act/assert
+        Assert.assertEquals(1.0, Vectors.checkedNorm(Vector1D.of(1.0)), EPS);
+        Assert.assertEquals(23.12, Vectors.checkedNorm(Vector1D.of(23.12)), EPS);
+        Assert.assertEquals(2e-12, Vectors.checkedNorm(Vector1D.of(2e-12)), EPS);
+
+        Assert.assertEquals(1.0, Vectors.checkedNorm(Vector1D.of(-1.0)), EPS);
+        Assert.assertEquals(23.12, Vectors.checkedNorm(Vector1D.of(-23.12)), EPS);
+        Assert.assertEquals(2e-12, Vectors.checkedNorm(Vector1D.of(-2e-12)), EPS);
+
+        GeometryTestUtils.assertThrows(() -> Vectors.checkedNorm(Vector3D.ZERO),
+                IllegalNormException.class, "Illegal norm: 0.0");
+        GeometryTestUtils.assertThrows(() -> Vectors.checkedNorm(Vector3D.NaN),
+                IllegalNormException.class, "Illegal norm: NaN");
+        GeometryTestUtils.assertThrows(() -> Vectors.checkedNorm(Vector3D.POSITIVE_INFINITY),
+                IllegalNormException.class, "Illegal norm: Infinity");
+        GeometryTestUtils.assertThrows(() -> Vectors.checkedNorm(Vector3D.NEGATIVE_INFINITY),
+                IllegalNormException.class, "Illegal norm: Infinity");
+    }
+
     @Test
     public void testNorm1_oneD() {
         // 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 3c7bd50..effc704 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
@@ -110,58 +110,42 @@ public void testNormInf() {
     }
 
     @Test
-    public void testMagnitude() {
+    public void testWithNorm() {
         // 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.ONE.withNorm(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).withNorm(2.0), 2.0);
+        checkVector(Vector1D.of(5).withNorm(3.0), 3.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).withNorm(2.0), -2.0);
+        checkVector(Vector1D.of(-5).withNorm(3.0), -3.0);
     }
 
     @Test
-    public void testWithMagnitude_illegalNorm() {
+    public void testWithNorm_illegalNorm() {
         // act/assert
-        GeometryTestUtils.assertThrows(() -> Vector1D.ZERO.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector1D.ZERO.withNorm(2.0),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.NaN.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector1D.NaN.withNorm(2.0),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.POSITIVE_INFINITY.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector1D.POSITIVE_INFINITY.withNorm(2.0),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.NEGATIVE_INFINITY.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector1D.NEGATIVE_INFINITY.withNorm(2.0),
                 IllegalNormException.class);
     }
 
     @Test
-    public void testWithMagnitude_unitVectors() {
+    public void testWithNorm_unitVectors() {
         // arrange
         Vector1D v = Vector1D.of(2.0).normalize();
 
         // act/assert
-        checkVector(Vector1D.ONE.withMagnitude(2.5), 2.5);
-        checkVector(Vector1D.MINUS_ONE.withMagnitude(3.14), -3.14);
+        checkVector(Vector1D.ONE.withNorm(2.5), 2.5);
+        checkVector(Vector1D.MINUS_ONE.withNorm(3.14), -3.14);
 
         for (double mag = -10.0; mag <= 10.0; ++mag)
         {
-            Assert.assertEquals(Math.abs(mag), v.withMagnitude(mag).getMagnitude(), TEST_TOLERANCE);
+            Assert.assertEquals(Math.abs(mag), v.withNorm(mag).getNorm(), TEST_TOLERANCE);
         }
     }
 
@@ -349,74 +333,6 @@ 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
-    public void testProject_baseHasIllegalNorm() {
-        // act/assert
-        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).project(Vector1D.ZERO),
-                IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).project(Vector1D.NaN),
-                IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).project(Vector1D.POSITIVE_INFINITY),
-                IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).project(Vector1D.NEGATIVE_INFINITY),
-                IllegalNormException.class);
-    }
-
-    @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
-    public void testReject_baseHasIllegalNorm() {
-        // act/assert
-        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).reject(Vector1D.ZERO),
-                IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).reject(Vector1D.NaN),
-                IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).reject(Vector1D.POSITIVE_INFINITY),
-                IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.of(2.0).reject(Vector1D.NEGATIVE_INFINITY),
-                IllegalNormException.class);
-    }
-
     @Test
     public void testAngle() {
         // arrange
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 8d6fe9e..0997f57 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
@@ -126,23 +126,7 @@ public void testNormInf() {
     }
 
     @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() {
+    public void testWithNorm() {
         // arrange
         double x = 2;
         double y = 3;
@@ -155,55 +139,55 @@ public void testWithMagnitude() {
         double normZ = z / len;
 
         // act/assert
-        checkVector(Vector3D.of(x, y, z).withMagnitude(0.0), 0.0, 0.0, 0.0);
+        checkVector(Vector3D.of(x, y, z).withNorm(0.0), 0.0, 0.0, 0.0);
 
-        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).withNorm(1.0), normX, normY, normZ);
+        checkVector(Vector3D.of(x, y, -z).withNorm(1.0), normX, normY, -normZ);
+        checkVector(Vector3D.of(x, -y, z).withNorm(1.0), normX, -normY, normZ);
+        checkVector(Vector3D.of(x, -y, -z).withNorm(1.0), normX, -normY, -normZ);
+        checkVector(Vector3D.of(-x, y, z).withNorm(1.0), -normX, normY, normZ);
+        checkVector(Vector3D.of(-x, y, -z).withNorm(1.0), -normX, normY, -normZ);
+        checkVector(Vector3D.of(-x, -y, z).withNorm(1.0), -normX, -normY, normZ);
+        checkVector(Vector3D.of(-x, -y, -z).withNorm(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);
+        checkVector(Vector3D.of(x, y, z).withNorm(0.5), 0.5 * normX, 0.5 * normY, 0.5 * normZ);
+        checkVector(Vector3D.of(x, y, z).withNorm(3), 3 * normX, 3 * normY, 3 * 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);
+        checkVector(Vector3D.of(x, y, z).withNorm(-0.5), -0.5 * normX, -0.5 * normY, -0.5 * normZ);
+        checkVector(Vector3D.of(x, y, z).withNorm(-3), -3 * normX, -3 * normY, -3 * normZ);
 
         for (double mag = -10.0; mag <= 10.0; ++mag)
         {
-            Assert.assertEquals(Math.abs(mag), Vector3D.of(x, y, z).withMagnitude(mag).getMagnitude(), EPS);
+            Assert.assertEquals(Math.abs(mag), Vector3D.of(x, y, z).withNorm(mag).getNorm(), EPS);
         }
     }
 
     @Test
-    public void testWithMagnitude_illegalNorm() {
+    public void testWithNorm_illegalNorm() {
         // act/assert
-        GeometryTestUtils.assertThrows(() -> Vector3D.ZERO.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector3D.ZERO.withNorm(2.0),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector3D.NaN.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector3D.NaN.withNorm(2.0),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector3D.POSITIVE_INFINITY.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector3D.POSITIVE_INFINITY.withNorm(2.0),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector3D.NEGATIVE_INFINITY.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector3D.NEGATIVE_INFINITY.withNorm(2.0),
                 IllegalNormException.class);
     }
 
     @Test
-    public void testWithMagnitude_unitVectors() {
+    public void testWithNorm_unitVectors() {
         // arrange
         Vector3D v = Vector3D.of(2.0, -3.0, 4.0).normalize();
 
         // act/assert
-        checkVector(Vector3D.PLUS_X.withMagnitude(2.5), 2.5, 0.0, 0.0);
-        checkVector(Vector3D.MINUS_Y.withMagnitude(3.14), 0.0, -3.14, 0.0);
-        checkVector(Vector3D.PLUS_Z.withMagnitude(-1.1), 0.0, 0.0, -1.1);
+        checkVector(Vector3D.PLUS_X.withNorm(2.5), 2.5, 0.0, 0.0);
+        checkVector(Vector3D.MINUS_Y.withNorm(3.14), 0.0, -3.14, 0.0);
+        checkVector(Vector3D.PLUS_Z.withNorm(-1.1), 0.0, 0.0, -1.1);
 
         for (double mag = -10.0; mag <= 10.0; ++mag)
         {
-            Assert.assertEquals(Math.abs(mag), v.withMagnitude(mag).getMagnitude(), EPS);
+            Assert.assertEquals(Math.abs(mag), v.withNorm(mag).getNorm(), EPS);
         }
     }
 
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 a3e43fe..f1855c3 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
@@ -128,70 +128,44 @@ public void testNormInf() {
     }
 
     @Test
-    public void testMagnitude() {
+    public void testWithNorm() {
         // act/assert
-        Assert.assertEquals(0.0, Vector2D.of(0, 0).getMagnitude(), EPS);
+        checkVector(Vector2D.of(3, 4).withNorm(1.0), 0.6, 0.8);
+        checkVector(Vector2D.of(4, 3).withNorm(1.0), 0.8, 0.6);
 
-        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);
+        checkVector(Vector2D.of(-3, 4).withNorm(0.5), -0.3, 0.4);
+        checkVector(Vector2D.of(3, -4).withNorm(2.0), 1.2, -1.6);
+        checkVector(Vector2D.of(-3, -4).withNorm(3.0), -1.8, 3.0 * Math.sin(Math.atan2(-4, -3)));
 
-        Assert.assertEquals(Math.sqrt(5.0), Vector2D.of(-1, -2).getMagnitude(), EPS);
+        checkVector(Vector2D.of(0.5, 0.5).withNorm(2), Math.sqrt(2), Math.sqrt(2));
     }
 
     @Test
-    public void testMagnitudeSq() {
+    public void testWithNorm_illegalNorm() {
         // 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
-    public void testWithMagnitude_illegalNorm() {
-        // act/assert
-        GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.withNorm(2.0),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector2D.NaN.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector2D.NaN.withNorm(2.0),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.withNorm(2.0),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.withMagnitude(2.0),
+        GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.withNorm(2.0),
                 IllegalNormException.class);
     }
 
     @Test
-    public void testWithMagnitude_unitVectors() {
+    public void testWithNorm_unitVectors() {
         // arrange
         double eps = 1e-14;
         Vector2D v = Vector2D.of(2.0, -3.0).normalize();
 
         // act/assert
-        checkVector(Vector2D.PLUS_X.withMagnitude(2.5), 2.5, 0.0);
-        checkVector(Vector2D.MINUS_Y.withMagnitude(3.14), 0.0, -3.14);
+        checkVector(Vector2D.PLUS_X.withNorm(2.5), 2.5, 0.0);
+        checkVector(Vector2D.MINUS_Y.withNorm(3.14), 0.0, -3.14);
 
         for (int i = -10; i <= 10; i++) {
             final double mag = i;
-            Assert.assertEquals(Math.abs(mag), v.withMagnitude(mag).getMagnitude(), eps);
+            Assert.assertEquals(Math.abs(mag), v.withNorm(mag).getNorm(), eps);
         }
     }
 
@@ -409,6 +383,101 @@ public void testDotProduct() {
         Assert.assertEquals(0, Vector2D.PLUS_X.dotProduct(Vector2D.MINUS_Y), EPS);
     }
 
+    @Test
+    public void testOrthogonal() {
+        // arrange
+        double invSqrt2 = 1.0 / Math.sqrt(2.0);
+
+        // act/assert
+        checkVector(Vector2D.PLUS_X.orthogonal(), 0.0, 1.0);
+        checkVector(Vector2D.of(1.0, 1.0).orthogonal(), -invSqrt2, invSqrt2);
+
+        checkVector(Vector2D.PLUS_Y.orthogonal(), -1.0, 0.0);
+        checkVector(Vector2D.of(-1.0, 1.0).orthogonal(), -invSqrt2, -invSqrt2);
+
+        checkVector(Vector2D.MINUS_X.orthogonal(), 0.0, -1.0);
+        checkVector(Vector2D.of(-1.0, -1.0).orthogonal(), invSqrt2, -invSqrt2);
+
+        checkVector(Vector2D.MINUS_Y.orthogonal(), 1.0, 0.0);
+        checkVector(Vector2D.of(1.0, -1.0).orthogonal(), invSqrt2, invSqrt2);
+    }
+
+    @Test
+    public void testOrthogonal_fullCircle() {
+        for (double az = 0.0; az<=Geometry.TWO_PI; az += 0.25) {
+            // arrange
+            Vector2D v = Vector2D.ofPolar(Math.PI, az);
+
+            //act
+            Vector2D ortho = v.orthogonal();
+
+            // assert
+            Assert.assertEquals(1.0, ortho.getNorm(), EPS);
+            Assert.assertEquals(0.0, v.dotProduct(ortho), EPS);
+        }
+    }
+
+    @Test
+    public void testOrthogonal_illegalNorm() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.orthogonal(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NaN.orthogonal(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.orthogonal(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.orthogonal(),
+                IllegalNormException.class);
+    }
+
+    @Test
+    public void testOrthogonal_givenDirection() {
+        // arrange
+        double invSqrt2 = 1.0 / Math.sqrt(2.0);
+
+        // act/assert
+        checkVector(Vector2D.PLUS_X.orthogonal(Vector2D.of(-1.0, 0.1)), 0.0, 1.0);
+        checkVector(Vector2D.PLUS_Y.orthogonal(Vector2D.of(2.0, 2.0)), 1.0, 0.0);
+
+        checkVector(Vector2D.of(2.9, 2.9).orthogonal(Vector2D.of(1.0, 0.22)), invSqrt2, -invSqrt2);
+        checkVector(Vector2D.of(2.9, 2.9).orthogonal(Vector2D.of(0.22, 1.0)), -invSqrt2, invSqrt2);
+    }
+
+    @Test
+    public void testOrthogonal_givenDirection_illegalNorm() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.orthogonal(Vector2D.PLUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NaN.orthogonal(Vector2D.PLUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.orthogonal(Vector2D.PLUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.orthogonal(Vector2D.PLUS_X),
+                IllegalNormException.class);
+
+        GeometryTestUtils.assertThrows(() -> Vector2D.PLUS_X.orthogonal(Vector2D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.PLUS_X.orthogonal(Vector2D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.PLUS_X.orthogonal(Vector2D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.PLUS_X.orthogonal(Vector2D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
+    }
+
+    @Test
+    public void testOrthogonal_givenDirection_directionIsCollinear() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector2D.PLUS_X.orthogonal(Vector2D.PLUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.PLUS_X.orthogonal(Vector2D.MINUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.of(1.0, 1.0).orthogonal(Vector2D.of(2.0, 2.0)),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.of(-1.01, -1.01).orthogonal(Vector2D.of(20.1, 20.1)),
+                IllegalNormException.class);
+    }
+
     @Test
     public void testAngle() {
         // act/assert


 

----------------------------------------------------------------
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@xxxxxxxxxxxxxxxx


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@xxxxxxxxxxxxxxxxxx
For additional commands, e-mail: dev-help@xxxxxxxxxxxxxxxxxx