osdir.com


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

[GitHub] asfgit closed pull request #10: Geometry 10


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

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-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 bde9f67..5aecf85 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
@@ -17,6 +17,7 @@
 package org.apache.commons.geometry.euclidean;
 
 import org.apache.commons.geometry.core.AffinePoint;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 
 /** Represents a point in a Euclidean space of any dimension.
  *
@@ -41,6 +42,17 @@
      */
     V vectorTo(P p);
 
+    /** Returns the unit vector representing the direction of displacement from this
+     * point to the given point. This is exactly equivalent to {@code p.subtract(thisPoint).normalize()}
+     * but without the intermediate vector instance.
+     * @param p the point the returned vector will be directed toward
+     * @return unit vector representing the direction of displacement <em>from</em> this point
+     *      <em>to</em> the given point
+     * @throws IllegalNormException if the norm of the vector pointing from this point to {@code p}
+     *      is zero, NaN, or infinite
+     */
+    V directionTo(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
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 4f0fec1..7e977b7 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
@@ -81,6 +81,12 @@ public Vector1D vectorTo(Point1D p) {
         return p.subtract(this);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Vector1D directionTo(Point1D p) {
+        return Vector1D.normalize(p.getX() - getX());
+    }
+
     /** {@inheritDoc} */
     @Override
     public Point1D lerp(Point1D p, double t) {
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 79a4d35..b678559 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
@@ -17,6 +17,7 @@
 package org.apache.commons.geometry.euclidean.oned;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.euclidean.EuclideanVector;
 import org.apache.commons.geometry.euclidean.internal.Vectors;
@@ -25,16 +26,16 @@
 /** This class represents a vector in one-dimensional Euclidean space.
  * Instances of this class are guaranteed to be immutable.
  */
-public final class Vector1D extends Cartesian1D implements EuclideanVector<Point1D, Vector1D> {
+public class Vector1D extends Cartesian1D implements EuclideanVector<Point1D, Vector1D> {
 
     /** Zero vector (coordinates: 0). */
     public static final Vector1D ZERO = new Vector1D(0.0);
 
     /** Unit vector (coordinates: 1). */
-    public static final Vector1D ONE  = new Vector1D(1.0);
+    public static final Vector1D ONE  = new UnitVector(1.0);
 
     /** Negation of unit vector (coordinates: -1). */
-    public static final Vector1D MINUS_ONE = new Vector1D(-1.0);
+    public static final Vector1D MINUS_ONE = new UnitVector(-1.0);
 
     // CHECKSTYLE: stop ConstantName
     /** A vector with all coordinates set to NaN. */
@@ -154,9 +155,7 @@ public Vector1D negate() {
     /** {@inheritDoc} */
     @Override
     public Vector1D normalize() {
-        Vectors.ensureFiniteNonZeroNorm(getNorm());
-
-        return (getX() > 0.0) ? ONE : MINUS_ONE;
+        return normalize(getX());
     }
 
     /** {@inheritDoc} */
@@ -290,6 +289,17 @@ public static Vector1D of(double x) {
         return new Vector1D(x);
     }
 
+    /** Returns a normalized vector derived from the given value.
+     * @param x abscissa (first coordinate value)
+     * @return normalized vector instance
+     * @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));
+
+        return (x > 0.0) ? ONE : MINUS_ONE;
+    }
+
     /** 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
@@ -380,4 +390,33 @@ public static Vector1D linearCombination(double a1, Cartesian1D c1, double a2, C
         return new Vector1D(
                 LinearCombination.value(a1, c1.getX(), a2, c2.getX(), a3, c3.getX(), a4, c4.getX()));
     }
+
+    /** Private class used to represent unit vectors. This allows optimizations to be performed for certain
+     * operations.
+     */
+    private static final class UnitVector extends Vector1D {
+
+        /** Serializable version identifier */
+        private static final long serialVersionUID = 20180903L;
+
+        /** Simple constructor. Callers are responsible for ensuring that the given
+         * values represent a normalized vector.
+         * @param x abscissa (first coordinate value)
+         */
+        private UnitVector(final double x) {
+            super(x);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector1D normalize() {
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector1D withMagnitude(final double mag) {
+            return scalarMultiply(mag);
+        }
+    }
 }
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 51f725e..24626ec 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
@@ -87,6 +87,15 @@ public Vector3D vectorTo(Point3D p) {
         return p.subtract(this);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D directionTo(Point3D p) {
+        return Vector3D.normalize(
+                p.getX() - getX(),
+                p.getY() - getY(),
+                p.getZ() - getZ());
+    }
+
     /** {@inheritDoc} */
     @Override
     public Point3D lerp(Point3D p, double t) {
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 4b96f43..0014d4d 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,7 +16,9 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+
 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.internal.Vectors;
@@ -25,28 +27,28 @@
 /** This class represents a vector in three-dimensional Euclidean space.
  * Instances of this class are guaranteed to be immutable.
  */
-public final class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Vector3D> {
+public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Vector3D> {
 
     /** Zero (null) vector (coordinates: 0, 0, 0). */
     public static final Vector3D ZERO   = new Vector3D(0, 0, 0);
 
     /** First canonical vector (coordinates: 1, 0, 0). */
-    public static final Vector3D PLUS_X = new Vector3D(1, 0, 0);
+    public static final Vector3D PLUS_X = new UnitVector(1, 0, 0);
 
     /** Opposite of the first canonical vector (coordinates: -1, 0, 0). */
-    public static final Vector3D MINUS_X = new Vector3D(-1, 0, 0);
+    public static final Vector3D MINUS_X = new UnitVector(-1, 0, 0);
 
     /** Second canonical vector (coordinates: 0, 1, 0). */
-    public static final Vector3D PLUS_Y = new Vector3D(0, 1, 0);
+    public static final Vector3D PLUS_Y = new UnitVector(0, 1, 0);
 
     /** Opposite of the second canonical vector (coordinates: 0, -1, 0). */
-    public static final Vector3D MINUS_Y = new Vector3D(0, -1, 0);
+    public static final Vector3D MINUS_Y = new UnitVector(0, -1, 0);
 
     /** Third canonical vector (coordinates: 0, 0, 1). */
-    public static final Vector3D PLUS_Z = new Vector3D(0, 0, 1);
+    public static final Vector3D PLUS_Z = new UnitVector(0, 0, 1);
 
     /** Opposite of the third canonical vector (coordinates: 0, 0, -1).  */
-    public static final Vector3D MINUS_Z = new Vector3D(0, 0, -1);
+    public static final Vector3D MINUS_Z = new UnitVector(0, 0, -1);
 
     // CHECKSTYLE: stop ConstantName
     /** A vector with all coordinates set to NaN. */
@@ -61,8 +63,8 @@
     public static final Vector3D NEGATIVE_INFINITY =
         new Vector3D(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
 
-    /** Serializable UID */
-    private static final long serialVersionUID = 20180710L;
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 20180903L;
 
     /** Simple constructor.
      * Build a vector from its coordinates
@@ -189,7 +191,7 @@ public Vector3D negate() {
     /** {@inheritDoc} */
     @Override
     public Vector3D normalize() {
-        return scalarMultiply(1.0 / getFiniteNonZeroNorm());
+        return normalize(getX(), getY(), getZ());
     }
 
     /** Get a vector orthogonal to the instance.
@@ -226,6 +228,19 @@ public Vector3D orthogonal() {
         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
@@ -324,13 +339,13 @@ public double dotProduct(Vector3D v) {
     /** {@inheritDoc} */
     @Override
     public Vector3D project(Vector3D base) {
-        return getComponent(base, false);
+        return getComponent(base, false, Vector3D::new);
     }
 
     /** {@inheritDoc} */
     @Override
     public Vector3D reject(Vector3D base) {
-        return getComponent(base, true);
+        return getComponent(base, true, Vector3D::new);
     }
 
     /**
@@ -399,26 +414,33 @@ 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 Vector3D getComponent(Vector3D base, boolean reject) {
+    private Vector3D getComponent(Vector3D base, boolean reject, DoubleFunction3N<Vector3D> 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.ensureFiniteNonZeroNorm(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();
         final double projZ = scale * base.getZ();
 
         if (reject) {
-            return new Vector3D(getX() - projX, getY() - projY, getZ() - projZ);
+            return factory.apply(getX() - projX, getY() - projY, getZ() - projZ);
         }
 
-        return new Vector3D(projX, projY, projZ);
+        return factory.apply(projX, projY, projZ);
     }
 
     /** Returns a vector with the given coordinate values.
@@ -454,6 +476,20 @@ public static Vector3D ofSpherical(double radius, double azimuth, double polar)
         return SphericalCoordinates.toCartesian(radius, azimuth, polar, Vector3D::new);
     }
 
+    /** Returns a normalized vector derived from the given values.
+     * @param x abscissa (first coordinate value)
+     * @param y ordinate (second coordinate value)
+     * @param z height (third coordinate value)
+     * @return normalized vector instance
+     * @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 invNorm = 1.0 / norm;
+
+        return new UnitVector(x * invNorm, y * invNorm, z * invNorm);
+    }
+
     /** 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
@@ -550,4 +586,35 @@ public static Vector3D linearCombination(double a1, Cartesian3D c1, double a2, C
                 LinearCombination.value(a1, c1.getY(), a2, c2.getY(), a3, c3.getY(), a4, c4.getY()),
                 LinearCombination.value(a1, c1.getZ(), a2, c2.getZ(), a3, c3.getZ(), a4, c4.getZ()));
     }
+
+    /** Private class used to represent unit vectors. This allows optimizations to be performed for certain
+     * operations.
+     */
+    private static final class UnitVector extends Vector3D {
+
+        /** Serializable version identifier */
+        private static final long serialVersionUID = 20180903L;
+
+        /** Simple constructor. Callers are responsible for ensuring that the given
+         * values represent a normalized vector.
+         * @param x abscissa (first coordinate value)
+         * @param y ordinate (second coordinate value)
+         * @param z height (third coordinate value)
+         */
+        private UnitVector(final double x, final double y, final double z) {
+            super(x, y, z);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector3D normalize() {
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector3D withMagnitude(final double mag) {
+            return scalarMultiply(mag);
+        }
+    }
 }
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 ece33f0..ddd707e 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
@@ -78,6 +78,15 @@ public Vector2D vectorTo(Point2D p) {
         return p.subtract(this);
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D directionTo(Point2D p) {
+        return Vector2D.normalize(
+                    p.getX() - getX(),
+                    p.getY() - getY()
+                );
+    }
+
     /** {@inheritDoc} */
     @Override
     public Point2D lerp(Point2D p, double t) {
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 36338d0..7ae7edf 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
@@ -25,22 +25,22 @@
 /** This class represents a vector in two-dimensional Euclidean space.
  * Instances of this class are guaranteed to be immutable.
  */
-public final class Vector2D extends Cartesian2D implements EuclideanVector<Point2D, Vector2D> {
+public class Vector2D extends Cartesian2D implements EuclideanVector<Point2D, Vector2D> {
 
     /** Zero vector (coordinates: 0, 0). */
     public static final Vector2D ZERO   = new Vector2D(0, 0);
 
     /** Unit vector pointing in the direction of the positive x-axis. */
-    public static final Vector2D PLUS_X = new Vector2D(1, 0);
+    public static final Vector2D PLUS_X = new UnitVector(1, 0);
 
     /** Unit vector pointing in the direction of the negative x-axis. */
-    public static final Vector2D MINUS_X = new Vector2D(-1, 0);
+    public static final Vector2D MINUS_X = new UnitVector(-1, 0);
 
     /** Unit vector pointing in the direction of the positive y-axis. */
-    public static final Vector2D PLUS_Y = new Vector2D(0, 1);
+    public static final Vector2D PLUS_Y = new UnitVector(0, 1);
 
     /** Unit vector pointing in the direction of the negative y-axis. */
-    public static final Vector2D MINUS_Y = new Vector2D(0, -1);
+    public static final Vector2D MINUS_Y = new UnitVector(0, -1);
 
     // CHECKSTYLE: stop ConstantName
     /** A vector with all coordinates set to NaN. */
@@ -66,14 +66,6 @@ private Vector2D(double x, double y) {
         super(x, y);
     }
 
-    /** Get the vector coordinates as a dimension 2 array.
-     * @return vector coordinates
-     */
-    @Override
-    public double[] toArray() {
-        return new double[] { getX(), getY() };
-    }
-
     /** {@inheritDoc} */
     @Override
     public Point2D asPoint() {
@@ -172,7 +164,7 @@ public Vector2D negate() {
     /** {@inheritDoc} */
     @Override
     public Vector2D normalize() {
-        return scalarMultiply(1.0 / getFiniteNonZeroNorm());
+        return normalize(getX(), getY());
     }
 
     /** {@inheritDoc} */
@@ -395,6 +387,19 @@ public static Vector2D ofPolar(final double radius, final double azimuth) {
         return PolarCoordinates.toCartesian(radius, azimuth, Vector2D::new);
     }
 
+    /** Returns a normalized vector derived from the given values.
+     * @param x abscissa (first coordinate value)
+     * @param y ordinate (second coordinate value)
+     * @return normalized vector instance
+     * @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 invNorm = 1.0 / norm;
+
+        return new UnitVector(x * invNorm, y * invNorm);
+    }
+
     /** 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
@@ -488,4 +493,34 @@ public static Vector2D linearCombination(double a1, Cartesian2D c1, double a2, C
                 LinearCombination.value(a1, c1.getX(), a2, c2.getX(), a3, c3.getX(), a4, c4.getX()),
                 LinearCombination.value(a1, c1.getY(), a2, c2.getY(), a3, c3.getY(), a4, c4.getY()));
     }
+
+    /** Private class used to represent unit vectors. This allows optimizations to be performed for certain
+     * operations.
+     */
+    private static final class UnitVector extends Vector2D {
+
+        /** Serializable version identifier */
+        private static final long serialVersionUID = 20180903L;
+
+        /** Simple constructor. Callers are responsible for ensuring that the given
+         * values represent a normalized vector.
+         * @param x abscissa (first coordinate value)
+         * @param y abscissa (second coordinate value)
+         */
+        private UnitVector(final double x, final double y) {
+            super(x, y);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector2D normalize() {
+            return this;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Vector2D withMagnitude(final double mag) {
+            return scalarMultiply(mag);
+        }
+    }
 }
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 25b2fe6..1b40bf2 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
@@ -19,6 +19,8 @@
 
 import java.util.regex.Pattern;
 
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -117,6 +119,39 @@ public void testVectorTo() {
         checkVector(p3.vectorTo(p2), -14.0);
     }
 
+    @Test
+    public void testDirectionTo() {
+        // act/assert
+        Point1D p1 = Point1D.of(1);
+        Point1D p2 = Point1D.of(5);
+        Point1D p3 = Point1D.of(-2);
+
+        // act/assert
+        checkVector(p1.directionTo(p2), 1);
+        checkVector(p2.directionTo(p1), -1);
+
+        checkVector(p1.directionTo(p3), -1);
+        checkVector(p3.directionTo(p1), 1);
+    }
+
+    @Test
+    public void testDirectionTo_illegalNorm() {
+        // arrange
+        Point1D p = Point1D.of(2);
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Point1D.ZERO.directionTo(Point1D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> p.directionTo(p),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> p.directionTo(Point1D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Point1D.NEGATIVE_INFINITY.directionTo(p),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> p.directionTo(Point1D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+    }
+
     @Test
     public void testLerp() {
         // arrange
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 ee817cc..3c7bd50 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
@@ -40,6 +40,22 @@ public void testConstants() {
         checkVector(Vector1D.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
     }
 
+    @Test
+    public void testConstants_normalize() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector1D.ZERO.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.NaN.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.POSITIVE_INFINITY.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.NEGATIVE_INFINITY.normalize(),
+                IllegalNormException.class);
+
+        Assert.assertSame(Vector1D.ONE.normalize(), Vector1D.ONE);
+        Assert.assertSame(Vector1D.MINUS_ONE.normalize(), Vector1D.MINUS_ONE);
+    }
+
     @Test
     public void testAsPoint() {
         // act/assert
@@ -134,6 +150,21 @@ public void testWithMagnitude_illegalNorm() {
                 IllegalNormException.class);
     }
 
+    @Test
+    public void testWithMagnitude_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);
+
+        for (double mag = -10.0; mag <= 10.0; ++mag)
+        {
+            Assert.assertEquals(Math.abs(mag), v.withMagnitude(mag).getMagnitude(), TEST_TOLERANCE);
+        }
+    }
+
     @Test
     public void testAdd() {
         // arrange
@@ -208,16 +239,26 @@ public void testNormalize() {
     @Test
     public void testNormalize_illegalNorm() {
         // act/assert
-        GeometryTestUtils.assertThrows(() -> Vector1D.ZERO.normalize(),
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(0.0).normalize(),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.NaN.normalize(),
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(Double.NaN).normalize(),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.POSITIVE_INFINITY.normalize(),
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(Double.POSITIVE_INFINITY).normalize(),
                 IllegalNormException.class);
-        GeometryTestUtils.assertThrows(() -> Vector1D.NEGATIVE_INFINITY.normalize(),
+        GeometryTestUtils.assertThrows(() -> Vector1D.of(Double.NEGATIVE_INFINITY).normalize(),
                 IllegalNormException.class);
     }
 
+    @Test
+    public void testNormalize_isIdempotent() {
+        // arrange
+        Vector1D v = Vector1D.of(2).normalize();
+
+        // act/assert
+        Assert.assertSame(v, v.normalize());
+        checkVector(v.normalize(), 1.0);
+    }
+
     @Test
     public void testNegate() {
         // act/assert
@@ -540,6 +581,25 @@ public void testOf() {
         checkVector(Vector1D.of(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY);
     }
 
+    @Test
+    public void testNormalize_static() {
+        // act/assert
+        checkVector(Vector1D.normalize(2.0), 1);
+        checkVector(Vector1D.normalize(-4.0), -1);
+    }
+
+    @Test
+    public void testNormalize_static_illegalNorm() {
+        GeometryTestUtils.assertThrows(() -> Vector1D.normalize(0.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.normalize(Double.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.normalize(Double.NEGATIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector1D.normalize(Double.POSITIVE_INFINITY),
+                IllegalNormException.class);
+    }
+
     @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 3e312dd..e1cd149 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
@@ -19,6 +19,8 @@
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -102,6 +104,41 @@ public void testVectorTo() {
         checkVector(p3.vectorTo(p1), 8, 10, 12);
     }
 
+    @Test
+    public void testDirectionTo() {
+        // act/assert
+        double invSqrt3 = 1.0 / Math.sqrt(3);
+
+        Point3D p1 = Point3D.of(1, 1, 1);
+        Point3D p2 = Point3D.of(1, 5, 1);
+        Point3D p3 = Point3D.of(-2, -2, -2);
+
+        // act/assert
+        checkVector(p1.directionTo(p2), 0, 1, 0);
+        checkVector(p2.directionTo(p1), 0, -1, 0);
+
+        checkVector(p1.directionTo(p3), -invSqrt3, -invSqrt3, -invSqrt3);
+        checkVector(p3.directionTo(p1), invSqrt3, invSqrt3, invSqrt3);
+    }
+
+    @Test
+    public void testDirectionTo_illegalNorm() {
+        // arrange
+        Point3D p = Point3D.of(1, 2, 3);
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Point3D.ZERO.directionTo(Point3D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> p.directionTo(p),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> p.directionTo(Point3D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Point3D.NEGATIVE_INFINITY.directionTo(p),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> p.directionTo(Point3D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+    }
+
     @Test
     public void testLerp() {
         // 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 4d34a91..8d6fe9e 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
@@ -52,6 +52,28 @@ public void testConstants() {
         checkVector(Vector3D.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
     }
 
+    @Test
+    public void testConstants_normalize() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector3D.ZERO.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NaN.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.POSITIVE_INFINITY.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NEGATIVE_INFINITY.normalize(),
+                IllegalNormException.class);
+
+        Assert.assertSame(Vector3D.PLUS_X.normalize(), Vector3D.PLUS_X);
+        Assert.assertSame(Vector3D.MINUS_X.normalize(), Vector3D.MINUS_X);
+
+        Assert.assertSame(Vector3D.PLUS_Y.normalize(), Vector3D.PLUS_Y);
+        Assert.assertSame(Vector3D.MINUS_Y.normalize(), Vector3D.MINUS_Y);
+
+        Assert.assertSame(Vector3D.PLUS_Z.normalize(), Vector3D.PLUS_Z);
+        Assert.assertSame(Vector3D.MINUS_Z.normalize(), Vector3D.MINUS_Z);
+    }
+
     @Test
     public void testZero() {
         // act
@@ -133,6 +155,8 @@ 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).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);
@@ -144,6 +168,14 @@ public void testWithMagnitude() {
 
         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).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);
+
+        for (double mag = -10.0; mag <= 10.0; ++mag)
+        {
+            Assert.assertEquals(Math.abs(mag), Vector3D.of(x, y, z).withMagnitude(mag).getMagnitude(), EPS);
+        }
     }
 
     @Test
@@ -159,6 +191,22 @@ public void testWithMagnitude_illegalNorm() {
                 IllegalNormException.class);
     }
 
+    @Test
+    public void testWithMagnitude_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);
+
+        for (double mag = -10.0; mag <= 10.0; ++mag)
+        {
+            Assert.assertEquals(Math.abs(mag), v.withMagnitude(mag).getMagnitude(), EPS);
+        }
+    }
+
     @Test
     public void testAdd() {
         // arrange
@@ -256,7 +304,7 @@ public void testNormalize() {
         checkVector(Vector3D.of(2, 2, 2).normalize(), invSqrt3, invSqrt3, invSqrt3);
         checkVector(Vector3D.of(-2, -2, -2).normalize(), -invSqrt3, -invSqrt3, -invSqrt3);
 
-        Assert.assertEquals(1.0, Vector3D.of(5, -4, 2).normalize().getNorm(), 1.0e-12);
+        Assert.assertEquals(1.0, Vector3D.of(5, -4, 2).normalize().getNorm(), EPS);
     }
 
     @Test
@@ -272,6 +320,17 @@ public void testNormalize_illegalNorm() {
                 IllegalNormException.class);
     }
 
+    @Test
+    public void testNormalize_isIdempotent() {
+        // arrange
+        double invSqrt3 = 1 / Math.sqrt(3);
+        Vector3D v = Vector3D.of(2, 2, 2).normalize();
+
+        // act/assert
+        Assert.assertSame(v, v.normalize());
+        checkVector(v.normalize(), invSqrt3, invSqrt3, invSqrt3);
+    }
+
     @Test
     public void testOrthogonal() {
         // arrange
@@ -300,6 +359,54 @@ public void testOrthogonal_illegalNorm() {
                 IllegalNormException.class);
     }
 
+    @Test
+    public void testOrthogonal_givenDirection() {
+        // arrange
+        double invSqrt2 = 1.0 / Math.sqrt(2.0);
+
+        // act/assert
+        checkVector(Vector3D.PLUS_X.orthogonal(Vector3D.of(-1.0, 0.1, 0.0)), 0.0, 1.0, 0.0);
+        checkVector(Vector3D.PLUS_Y.orthogonal(Vector3D.of(2.0, 2.0, 2.0)), invSqrt2, 0.0, invSqrt2);
+        checkVector(Vector3D.PLUS_Z.orthogonal(Vector3D.of(3.0, 3.0, -3.0)), invSqrt2, invSqrt2, 0.0);
+
+        checkVector(Vector3D.of(invSqrt2, invSqrt2, 0.0).orthogonal(Vector3D.of(1.0, 1.0, 0.2)), 0.0, 0.0, 1.0);
+    }
+
+    @Test
+    public void testOrthogonal_givenDirection_illegalNorm() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector3D.ZERO.orthogonal(Vector3D.PLUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NaN.orthogonal(Vector3D.PLUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.POSITIVE_INFINITY.orthogonal(Vector3D.PLUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.NEGATIVE_INFINITY.orthogonal(Vector3D.PLUS_X),
+                IllegalNormException.class);
+
+        GeometryTestUtils.assertThrows(() -> Vector3D.PLUS_X.orthogonal(Vector3D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.PLUS_X.orthogonal(Vector3D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.PLUS_X.orthogonal(Vector3D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.PLUS_X.orthogonal(Vector3D.NEGATIVE_INFINITY),
+                IllegalNormException.class);
+    }
+
+    @Test
+    public void testOrthogonal_givenDirection_directionIsCollinear() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector3D.PLUS_X.orthogonal(Vector3D.PLUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.PLUS_X.orthogonal(Vector3D.MINUS_X),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.of(1.0, 1.0, 1.0).orthogonal(Vector3D.of(2.0, 2.0, 2.0)),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.of(-1.01, -1.01, -1.01).orthogonal(Vector3D.of(20.1, 20.1, 20.1)),
+                IllegalNormException.class);
+    }
+
     @Test
     public void testAngle() {
         // arrange
@@ -866,7 +973,7 @@ public void testOf() {
     }
 
     @Test
-    public void testOf_arrayArg() {
+    public void testOfArray() {
         // act/assert
         checkVector(Vector3D.ofArray(new double[] { 1, 2, 3 }), 1, 2, 3);
         checkVector(Vector3D.ofArray(new double[] { -1, -2, -3 }), -1, -2, -3);
@@ -877,7 +984,7 @@ public void testOf_arrayArg() {
     }
 
     @Test(expected = IllegalArgumentException.class)
-    public void testOf_arrayArg_invalidDimensions() {
+    public void testOfArray_invalidDimensions() {
         // act/assert
         Vector3D.ofArray(new double[] { 0.0, 0.0 });
     }
@@ -903,6 +1010,28 @@ public void testOfSpherical() {
         checkVector(Vector3D.ofSpherical(sqrt3, -0.75 * Geometry.PI, Math.acos(-1 / sqrt3)), -1, -1, -1);
     }
 
+    @Test
+    public void testNormalize_static() {
+        // arrange
+        double invSqrt3 = 1.0 / Math.sqrt(3.0);
+
+        // act/assert
+        checkVector(Vector3D.normalize(2.0, -2.0, 2.0), invSqrt3, -invSqrt3, invSqrt3);
+        checkVector(Vector3D.normalize(-4.0, 4.0, -4.0), -invSqrt3, invSqrt3, -invSqrt3);
+    }
+
+    @Test
+    public void testNormalize_static_illegalNorm() {
+        GeometryTestUtils.assertThrows(() -> Vector3D.normalize(0.0, 0.0, 0.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.normalize(Double.NaN, 1.0, 1.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.normalize(1.0, Double.NEGATIVE_INFINITY, 1.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector3D.normalize(1.0, 1.0, Double.POSITIVE_INFINITY),
+                IllegalNormException.class);
+    }
+
     @Test
     public void testLinearCombination1() {
         // arrange
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 d5903de..dcf89d9 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
@@ -19,6 +19,8 @@
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.core.GeometryTestUtils;
+import org.apache.commons.geometry.core.exception.IllegalNormException;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -93,6 +95,41 @@ public void testVectorTo() {
         checkVector(p3.vectorTo(p1), 2, 1);
     }
 
+    @Test
+    public void testDirectionTo() {
+        // act/assert
+        double invSqrt2 = 1.0 / Math.sqrt(2);
+
+        Point2D p1 = Point2D.of(1, 1);
+        Point2D p2 = Point2D.of(1, 5);
+        Point2D p3 = Point2D.of(-2, -2);
+
+        // act/assert
+        checkVector(p1.directionTo(p2), 0, 1);
+        checkVector(p2.directionTo(p1), 0, -1);
+
+        checkVector(p1.directionTo(p3), -invSqrt2, -invSqrt2);
+        checkVector(p3.directionTo(p1), invSqrt2, invSqrt2);
+    }
+
+    @Test
+    public void testDirectionTo_illegalNorm() {
+        // arrange
+        Point2D p = Point2D.of(1, 2);
+
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Point2D.ZERO.directionTo(Point2D.ZERO),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> p.directionTo(p),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> p.directionTo(Point2D.NaN),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Point2D.NEGATIVE_INFINITY.directionTo(p),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> p.directionTo(Point2D.POSITIVE_INFINITY),
+                IllegalNormException.class);
+    }
+
     @Test
     public void testLerp() {
         // arrange
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 791f1c6..0beb633 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
@@ -44,17 +44,22 @@ public void testConstants() {
     }
 
     @Test
-    public void testToArray() {
-        // arrange
-        Vector2D v = Vector2D.of(1, 2);
+    public void testConstants_normalize() {
+        // act/assert
+        GeometryTestUtils.assertThrows(() -> Vector2D.ZERO.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NaN.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.POSITIVE_INFINITY.normalize(),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.NEGATIVE_INFINITY.normalize(),
+                IllegalNormException.class);
 
-        // act
-        double[] arr = v.toArray();
+        Assert.assertSame(Vector2D.PLUS_X.normalize(), Vector2D.PLUS_X);
+        Assert.assertSame(Vector2D.MINUS_X.normalize(), Vector2D.MINUS_X);
 
-        // assert
-        Assert.assertEquals(2, arr.length);
-        Assert.assertEquals(1, arr[0], EPS);
-        Assert.assertEquals(2, arr[1], EPS);
+        Assert.assertSame(Vector2D.PLUS_Y.normalize(), Vector2D.PLUS_Y);
+        Assert.assertSame(Vector2D.MINUS_Y.normalize(), Vector2D.MINUS_Y);
     }
 
     @Test
@@ -174,6 +179,22 @@ public void testWithMagnitude_illegalNorm() {
                 IllegalNormException.class);
     }
 
+    @Test
+    public void testWithMagnitude_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);
+
+        for (double mag = -10.0; mag <= 10.0; ++mag)
+        {
+            Assert.assertEquals(Math.abs(mag), v.withMagnitude(mag).getMagnitude(), eps);
+        }
+    }
+
     @Test
     public void testAdd() {
         // arrange
@@ -249,7 +270,7 @@ public void testNormalize() {
         checkVector(Vector2D.of(-100, 0).normalize(), -1, 0);
         checkVector(Vector2D.of(0, 100).normalize(), 0, 1);
         checkVector(Vector2D.of(0, -100).normalize(), 0, -1);
-        checkVector(Vector2D.of(-1, 2).normalize(), -1.0/Math.sqrt(5), 2.0/Math.sqrt(5));
+        checkVector(Vector2D.of(-1, 2).normalize(), -1.0 / Math.sqrt(5), 2.0 / Math.sqrt(5));
     }
 
     @Test
@@ -265,6 +286,17 @@ public void testNormalize_illegalNorm() {
                 IllegalNormException.class);
     }
 
+    @Test
+    public void testNormalize_isIdempotent() {
+        // arrange
+        double invSqrt2 = 1.0 / Math.sqrt(2);
+        Vector2D v = Vector2D.of(2, 2).normalize();
+
+        // act/assert
+        Assert.assertSame(v, v.normalize());
+        checkVector(v.normalize(), invSqrt2, invSqrt2);
+    }
+
     @Test
     public void testNegate() {
         // act/assert
@@ -702,6 +734,28 @@ public void testOfPolar() {
         checkVector(Vector2D.ofPolar(2, -0.75 * Geometry.PI), -sqrt2, - sqrt2, eps);
     }
 
+    @Test
+    public void testNormalize_static() {
+        // arrange
+        double invSqrt2 = 1.0 / Math.sqrt(2.0);
+
+        // act/assert
+        checkVector(Vector2D.normalize(2.0, -2.0), invSqrt2, -invSqrt2);
+        checkVector(Vector2D.normalize(-4.0, 4.0), -invSqrt2, invSqrt2);
+    }
+
+    @Test
+    public void testNormalize_static_illegalNorm() {
+        GeometryTestUtils.assertThrows(() -> Vector2D.normalize(0.0, 0.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.normalize(Double.NaN, 1.0),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.normalize(1.0, Double.NEGATIVE_INFINITY),
+                IllegalNormException.class);
+        GeometryTestUtils.assertThrows(() -> Vector2D.normalize(1.0, Double.POSITIVE_INFINITY),
+                IllegalNormException.class);
+    }
+
     @Test
     public void testLinearCombination1() {
         // arrange


 

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