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

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

commit a78ced7f202486d2bf57eeb070e9529c2e77269d
Author: Matt Juntunen <[email protected]>
AuthorDate: Sun Sep 2 22:05:35 2018 -0400

    GEOMETRY-9: adding project and reject methods to Vector2D and Vector3D; 
adding MultiDimensionalVector interface to contain new methods
---
 .../geometry/core/MultiDimensionalVector.java      |  60 ++++++++++++
 .../geometry/euclidean/threed/Vector3D.java        |  53 +++++++++-
 .../commons/geometry/euclidean/twod/Vector2D.java  |  56 +++++++++--
 .../geometry/euclidean/threed/Vector3DTest.java    | 107 +++++++++++++++++++++
 .../geometry/euclidean/twod/Vector2DTest.java      | 101 +++++++++++++++++++
 5 files changed, 364 insertions(+), 13 deletions(-)

diff --git 
a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/MultiDimensionalVector.java
 
b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/MultiDimensionalVector.java
new file mode 100644
index 0000000..a8eb74e
--- /dev/null
+++ 
b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/MultiDimensionalVector.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.geometry.core;
+
+/** Interface representing a vector in a vector space with two or more
+ * dimensions.
+ *
+ * @param <V> Vector implementation type
+ */
+public interface MultiDimensionalVector<V extends MultiDimensionalVector<V>> 
extends Vector<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
+     * @return the vector projection of the instance onto {@code base}
+     * @exception IllegalStateException if the norm of the base vector is zero
+     * @see #reject(MultiDimensionalVector)
+     */
+    V project(V base) throws IllegalStateException;
+
+    /** Get the rejection of the instance from the given base vector. The 
returned
+     * vector is orthogonal to {@code base}. This operation can be interpreted 
as
+     * returning the orthogonal projection of the instance onto the hyperplane
+     * orthogonal to {@code base}. Vector projection and rejection onto
+     * a given base are related by the equation
+     * <code>
+     *      <strong>v</strong> = <strong>v<sub>projection</sub></strong> + 
<strong>v<sub>rejection</sub></strong>
+     * </code>
+     * @param base
+     * @return the vector rejection of the instance from {@code base}
+     * @exception IllegalStateException if the norm of the base vector is zero
+     * @see #project(MultiDimensionalVector)
+     */
+    V reject(V base) throws IllegalStateException;
+
+    /** Compute the angular separation in radians between two vectors.
+     * @param v other vector
+     * @return angular separation between this instance and v in radians
+     * @exception IllegalStateException if either vector has a zero norm
+     */
+    double angle(V v) throws IllegalStateException;
+}
diff --git 
a/commons-geometry-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 cac3b23..62856a3 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,6 +16,7 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
+import org.apache.commons.geometry.core.MultiDimensionalVector;
 import org.apache.commons.geometry.core.internal.DoubleFunction3N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.core.util.Vectors;
@@ -25,7 +26,7 @@ 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 final class Vector3D extends Cartesian3D implements 
EuclideanVector<Point3D, Vector3D> {
+public final class Vector3D extends Cartesian3D implements 
EuclideanVector<Point3D, Vector3D>, MultiDimensionalVector<Vector3D> {
 
     /** Zero (null) vector (coordinates: 0, 0, 0). */
     public static final Vector3D ZERO   = new Vector3D(0, 0, 0);
@@ -229,16 +230,14 @@ public final class Vector3D extends Cartesian3D 
implements EuclideanVector<Point
         return new Vector3D(inverse * y, -inverse * x, 0);
     }
 
-    /** Compute the angular separation between two vectors.
+    /** {@inheritDoc}
      * <p>This method computes the angular separation between two
      * vectors using the dot product for well separated vectors and the
      * cross product for almost aligned vectors. This allows to have a
      * good accuracy in all cases, even for vectors very close to each
      * other.</p>
-     * @param v other vector
-     * @return angular separation between this instance and v
-     * @exception IllegalStateException if either vector has a zero norm
      */
+    @Override
     public double angle(Vector3D v) throws IllegalStateException {
         double normProduct = getNonZeroNorm() * v.getNonZeroNorm();
 
@@ -326,6 +325,50 @@ public final class Vector3D extends Cartesian3D implements 
EuclideanVector<Point
         return LinearCombination.value(getX(), v.getX(), getY(), v.getY(), 
getZ(), v.getZ());
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D project(Vector3D base) throws IllegalStateException {
+        return getComponent(base, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector3D reject(Vector3D base) throws IllegalStateException {
+        return getComponent(base, true);
+    }
+
+    /** Returns a component of the current instance relative to the given base
+     * vector. If {@code reject} is true, the vector rejection is returned; 
otherwise,
+     * the projection is returned.
+     * @param base The base vector
+     * @param reject If true, the rejection of this instance from {@code base} 
is
+     *      returned. If false, the projection of this instance onto {@code 
base}
+     *      is returned.
+     * @return The projection or rejection of this instance relative to {@code 
base},
+     *      depending on the value of {@code reject}.
+     * @throws IllegalStateException if {@code base} has a zero norm
+     */
+    private Vector3D getComponent(Vector3D base, boolean reject) throws 
IllegalStateException {
+        final double aDotB = dotProduct(base);
+
+        final double baseMagSq = base.getNormSq();
+        if (baseMagSq == 0.0) {
+            throw new IllegalStateException("Invalid base vector: norm is 
zero");
+        }
+
+        final double scale = aDotB / baseMagSq;
+
+        final double projX = scale * base.getX();
+        final double projY = scale * base.getY();
+        final double projZ = scale * base.getZ();
+
+        if (reject) {
+            return new Vector3D(getX() - projX, getY() - projY, getZ() - 
projZ);
+        }
+
+        return new Vector3D(projX, projY, projZ);
+    }
+
     /**
      * Get a hashCode for the vector.
      * <p>All NaN values have the same hash code.</p>
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 52a16f3..004b6c3 100644
--- 
a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
+++ 
b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/twod/Vector2D.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.geometry.euclidean.twod;
 
+import org.apache.commons.geometry.core.MultiDimensionalVector;
 import org.apache.commons.geometry.core.internal.DoubleFunction2N;
 import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
 import org.apache.commons.geometry.core.util.Vectors;
@@ -25,7 +26,7 @@ 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 final class Vector2D extends Cartesian2D implements 
EuclideanVector<Point2D, Vector2D> {
+public final class Vector2D extends Cartesian2D implements 
EuclideanVector<Point2D, Vector2D>, MultiDimensionalVector<Vector2D> {
 
     /** Zero vector (coordinates: 0, 0). */
     public static final Vector2D ZERO   = new Vector2D(0, 0);
@@ -215,19 +216,58 @@ public final class Vector2D extends Cartesian2D 
implements EuclideanVector<Point
         return LinearCombination.value(getX(), v.getX(), getY(), v.getY());
     }
 
-    /** Compute the angular separation in radians between this vector
-     * and the given vector.
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D project(Vector2D base) throws IllegalStateException {
+        return getComponent(base, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Vector2D reject(Vector2D base) throws IllegalStateException {
+        return getComponent(base, true);
+    }
+
+    /** Returns a component of the current instance relative to the given base
+     * vector. If {@code reject} is true, the vector rejection is returned; 
otherwise,
+     * the projection is returned.
+     * @param base The base vector
+     * @param reject If true, the rejection of this instance from {@code base} 
is
+     *      returned. If false, the projection of this instance onto {@code 
base}
+     *      is returned.
+     * @return The projection or rejection of this instance relative to {@code 
base},
+     *      depending on the value of {@code reject}.
+     * @throws IllegalStateException if {@code base} has a zero norm
+     */
+    private Vector2D getComponent(Vector2D base, boolean reject) throws 
IllegalStateException {
+        final double aDotB = dotProduct(base);
+
+        final double baseMagSq = base.getNormSq();
+        if (baseMagSq == 0.0) {
+            throw new IllegalStateException("Invalid base vector: norm is 
zero");
+        }
+
+        final double scale = aDotB / baseMagSq;
+
+        final double projX = scale * base.getX();
+        final double projY = scale * base.getY();
+
+        if (reject) {
+            return new Vector2D(getX() - projX, getY() - projY);
+        }
+
+        return new Vector2D(projX, projY);
+    }
+
+    /** {@inheritDoc}
      * <p>This method computes the angular separation between the two
      * vectors using the dot product for well separated vectors and the
      * cross product for almost aligned vectors. This allows to have a
      * good accuracy in all cases, even for vectors very close to each
      * other.</p>
-     *
-     * @param v vector to compute the angle with
-     * @return angular separation between this vector and v in radians
-     * @exception IllegalStateException if either vector has a zero norm
      */
-    public double angle(Vector2D v) throws IllegalArgumentException {
+    @Override
+    public double angle(Vector2D v) throws IllegalStateException {
         double normProduct = getNonZeroNorm() * v.getNonZeroNorm();
 
         double dot = dotProduct(v);
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 a0dfd4b..834b472 100644
--- 
a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
+++ 
b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/Vector3DTest.java
@@ -20,6 +20,7 @@ package org.apache.commons.geometry.euclidean.threed;
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.numbers.core.Precision;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.simple.RandomSource;
@@ -616,6 +617,112 @@ public class Vector3DTest {
     }
 
     @Test
+    public void testProject() {
+        // arrange
+        Vector3D v1 = Vector3D.of(2.0, 3.0, 4.0);
+        Vector3D v2 = Vector3D.of(-5.0, -6.0, -7.0);
+
+        // act/assert
+        checkVector(v1.project(Vector3D.PLUS_X), 2.0, 0.0, 0.0);
+        checkVector(v1.project(Vector3D.MINUS_X), 2.0, 0.0, 0.0);
+        checkVector(v1.project(Vector3D.PLUS_Y), 0.0, 3.0, 0.0);
+        checkVector(v1.project(Vector3D.MINUS_Y), 0.0, 3.0, 0.0);
+        checkVector(v1.project(Vector3D.PLUS_Z), 0.0, 0.0, 4.0);
+        checkVector(v1.project(Vector3D.MINUS_Z), 0.0, 0.0, 4.0);
+
+        checkVector(v2.project(Vector3D.PLUS_X), -5.0, 0.0, 0.0);
+        checkVector(v2.project(Vector3D.MINUS_X), -5.0, 0.0, 0.0);
+        checkVector(v2.project(Vector3D.PLUS_Y), 0.0, -6.0, 0.0);
+        checkVector(v2.project(Vector3D.MINUS_Y), 0.0, -6.0, 0.0);
+        checkVector(v2.project(Vector3D.PLUS_Z), 0.0, 0.0, -7.0);
+        checkVector(v2.project(Vector3D.MINUS_Z), 0.0, 0.0, -7.0);
+
+        checkVector(v1.project(Vector3D.of(1.0, 1.0, 1.0)), 3.0, 3.0, 3.0);
+        checkVector(v1.project(Vector3D.of(-1.0, -1.0, -1.0)), 3.0, 3.0, 3.0);
+
+        checkVector(v2.project(Vector3D.of(1.0, 1.0, 1.0)), -6.0, -6.0, -6.0);
+        checkVector(v2.project(Vector3D.of(-1.0, -1.0, -1.0)), -6.0, -6.0, 
-6.0);
+    }
+
+    @Test
+    public void testReject() {
+        // arrange
+        Vector3D v1 = Vector3D.of(2.0, 3.0, 4.0);
+        Vector3D v2 = Vector3D.of(-5.0, -6.0, -7.0);
+
+        // act/assert
+        checkVector(v1.reject(Vector3D.PLUS_X), 0.0, 3.0, 4.0);
+        checkVector(v1.reject(Vector3D.MINUS_X), 0.0, 3.0, 4.0);
+        checkVector(v1.reject(Vector3D.PLUS_Y), 2.0, 0.0, 4.0);
+        checkVector(v1.reject(Vector3D.MINUS_Y), 2.0, 0.0, 4.0);
+        checkVector(v1.reject(Vector3D.PLUS_Z), 2.0, 3.0, 0.0);
+        checkVector(v1.reject(Vector3D.MINUS_Z), 2.0, 3.0, 0.0);
+
+        checkVector(v2.reject(Vector3D.PLUS_X), 0.0, -6.0, -7.0);
+        checkVector(v2.reject(Vector3D.MINUS_X), 0.0, -6.0, -7.0);
+        checkVector(v2.reject(Vector3D.PLUS_Y), -5.0, 0.0, -7.0);
+        checkVector(v2.reject(Vector3D.MINUS_Y), -5.0, 0.0, -7.0);
+        checkVector(v2.reject(Vector3D.PLUS_Z), -5.0, -6.0, 0.0);
+        checkVector(v2.reject(Vector3D.MINUS_Z), -5.0, -6.0, 0.0);
+
+        checkVector(v1.reject(Vector3D.of(1.0, 1.0, 1.0)), -1.0, 0.0, 1.0);
+        checkVector(v1.reject(Vector3D.of(-1.0, -1.0, -1.0)), -1.0, 0.0, 1.0);
+
+        checkVector(v2.reject(Vector3D.of(1.0, 1.0, 1.0)), 1.0, 0.0, -1.0);
+        checkVector(v2.reject(Vector3D.of(-1.0, -1.0, -1.0)), 1.0, 0.0, -1.0);
+    }
+
+    @Test
+    public void testProjectAndReject_areComplementary() {
+        // arrange
+        double eps = 1e-12;
+
+        // act/assert
+        checkProjectAndRejectFullSphere(Vector3D.of(1.0, 0.0, 0.0), 1.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(0.0, 1.0, 0.0), 2.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(0.0, 0.0, 1.0), 2.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(1.0, 1.0, 1.0), 3.0, eps);
+
+        checkProjectAndRejectFullSphere(Vector3D.of(-2.0, 0.0, 0.0), 1.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(0.0, -2.0, 0.0), 2.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(0.0, 0.0, -2.0), 2.0, eps);
+        checkProjectAndRejectFullSphere(Vector3D.of(-2.0, -2.0, -2.0), 3.0, 
eps);
+    }
+
+    private void checkProjectAndRejectFullSphere(Vector3D vec, double baseMag, 
double eps) {
+        for (double polar = 0.0; polar <= Geometry.PI; polar += 0.5) {
+            for (double azimuth = 0.0; azimuth <= Geometry.TWO_PI; azimuth += 
0.5) {
+                Vector3D base = Vector3D.ofSpherical(baseMag, azimuth, polar);
+
+                Vector3D proj = vec.project(base);
+                Vector3D rej = vec.reject(base);
+
+                // ensure that the projection and rejection sum to the 
original vector
+                EuclideanTestUtils.assertCoordinatesEqual(vec, proj.add(rej), 
eps);
+
+                double angle = base.angle(vec);
+
+                // check the angle between the projection and the base; this 
will
+                // be undefined when the angle between the original vector and 
the
+                // base is pi/2 (which means that the projection is the zero 
vector)
+                if (angle < Geometry.HALF_PI) {
+                    Assert.assertEquals(0.0, proj.angle(base), eps);
+                }
+                else if (angle > Geometry.HALF_PI) {
+                    Assert.assertEquals(Geometry.PI, proj.angle(base), eps);
+                }
+
+                // check the angle between the rejection and the base; this 
should
+                // always be pi/2 except for when the angle between the 
original vector
+                // and the base is 0 or pi, in which case the rejection is the 
zero vector.
+                if (angle > 0.0 && angle < Geometry.PI) {
+                    Assert.assertEquals(Geometry.HALF_PI, rej.angle(base), 
eps);
+                }
+            }
+        }
+    }
+
+    @Test
     public void testHashCode() {
         // arrange
         double delta = 10 * Precision.EPSILON;
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 3bf0687..7c31092 100644
--- 
a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
+++ 
b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/twod/Vector2DTest.java
@@ -19,6 +19,7 @@ package org.apache.commons.geometry.euclidean.twod;
 import java.util.regex.Pattern;
 
 import org.apache.commons.geometry.core.Geometry;
+import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
 import org.apache.commons.numbers.core.Precision;
 import org.junit.Assert;
 import org.junit.Test;
@@ -436,6 +437,106 @@ public class Vector2DTest {
     }
 
     @Test
+    public void testProject() {
+        // arrange
+        Vector2D v1 = Vector2D.of(3.0, 4.0);
+        Vector2D v2 = Vector2D.of(1.0, 4.0);
+
+        // act/assert
+        checkVector(Vector2D.ZERO.project(v1), 0.0, 0.0);
+
+        checkVector(v1.project(v1), 3.0, 4.0);
+        checkVector(v1.project(v1.negate()), 3.0, 4.0);
+
+        checkVector(v1.project(Vector2D.PLUS_X), 3.0, 0.0);
+        checkVector(v1.project(Vector2D.MINUS_X), 3.0, 0.0);
+
+        checkVector(v1.project(Vector2D.PLUS_Y), 0.0, 4.0);
+        checkVector(v1.project(Vector2D.MINUS_Y), 0.0, 4.0);
+
+        checkVector(v2.project(v1), (19.0 / 25.0) * 3.0, (19.0 / 25.0) * 4.0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testProject_baseHasZeroNorm() {
+        // act/assert
+        Vector2D.of(1.0, 1.0).project(Vector2D.ZERO);
+    }
+
+    @Test
+    public void testReject() {
+        // arrange
+        Vector2D v1 = Vector2D.of(3.0, 4.0);
+        Vector2D v2 = Vector2D.of(1.0, 4.0);
+
+        // act/assert
+        checkVector(Vector2D.ZERO.reject(v1), 0.0, 0.0);
+
+        checkVector(v1.reject(v1), 0.0, 0.0);
+        checkVector(v1.reject(v1.negate()), 0.0, 0.0);
+
+        checkVector(v1.reject(Vector2D.PLUS_X), 0.0, 4.0);
+        checkVector(v1.reject(Vector2D.MINUS_X), 0.0, 4.0);
+
+        checkVector(v1.reject(Vector2D.PLUS_Y), 3.0, 0.0);
+        checkVector(v1.reject(Vector2D.MINUS_Y), 3.0, 0.0);
+
+        checkVector(v2.reject(v1), (-32.0 / 25.0), (6.0 / 25.0) * 4.0);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testReject_baseHasZeroNorm() {
+        // act/assert
+        Vector2D.of(1.0, 1.0).reject(Vector2D.ZERO);
+    }
+
+    @Test
+    public void testProjectAndReject_areComplementary() {
+        // arrange
+        double eps = 1e-12;
+
+        // act/assert
+        checkProjectAndRejectFullCircle(Vector2D.of(1.0, 0.0), 1.0, eps);
+        checkProjectAndRejectFullCircle(Vector2D.of(0.0, 1.0), 2.0, eps);
+        checkProjectAndRejectFullCircle(Vector2D.of(1.0, 1.0), 3.0, eps);
+
+        checkProjectAndRejectFullCircle(Vector2D.of(-2.0, 0.0), 4.0, eps);
+        checkProjectAndRejectFullCircle(Vector2D.of(0.0, -2.0), 5.0, eps);
+        checkProjectAndRejectFullCircle(Vector2D.of(-2.0, -2.0), 6.0, eps);
+    }
+
+    private void checkProjectAndRejectFullCircle(Vector2D vec, double baseMag, 
double eps) {
+        for (double theta = 0.0; theta <= Geometry.TWO_PI; theta += 0.5) {
+            Vector2D base = Vector2D.ofPolar(baseMag, theta);
+
+            Vector2D proj = vec.project(base);
+            Vector2D rej = vec.reject(base);
+
+            // ensure that the projection and rejection sum to the original 
vector
+            EuclideanTestUtils.assertCoordinatesEqual(vec, proj.add(rej), eps);
+
+            double angle = base.angle(vec);
+
+            // check the angle between the projection and the base; this will
+            // be undefined when the angle between the original vector and the
+            // base is pi/2 (which means that the projection is the zero 
vector)
+            if (angle < Geometry.HALF_PI) {
+                Assert.assertEquals(0.0, proj.angle(base), eps);
+            }
+            else if (angle > Geometry.HALF_PI) {
+                Assert.assertEquals(Geometry.PI, proj.angle(base), eps);
+            }
+
+            // check the angle between the rejection and the base; this should
+            // always be pi/2 except for when the angle between the original 
vector
+            // and the base is 0 or pi, in which case the rejection is the 
zero vector.
+            if (angle > 0.0 && angle < Geometry.PI) {
+                Assert.assertEquals(Geometry.HALF_PI, rej.angle(base), eps);
+            }
+        }
+    }
+
+    @Test
     public void testHashCode() {
         // arrange
         Vector2D u = Vector2D.of(1, 1);

Reply via email to