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 128f322ff5a329f80692cedcaf4f5e37cd2129ae
Author: Matt Juntunen <matt.juntu...@hotmail.com>
AuthorDate: Mon Sep 17 22:49:58 2018 -0400

    GEOMETRY-17: adding Vector2D#orthogonal methods
---
 .../geometry/euclidean/threed/Vector3D.java        |  2 +-
 .../commons/geometry/euclidean/twod/Vector2D.java  | 47 +++++++++--
 .../geometry/euclidean/twod/Vector2DTest.java      | 95 ++++++++++++++++++++++
 3 files changed, 136 insertions(+), 8 deletions(-)

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 9e81e61..9f9bb03 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
@@ -198,7 +198,7 @@ public class Vector3D extends Cartesian3D implements 
EuclideanVector<Point3D, Ve
      * <pre><code>
      *   Vector3D k = u.normalize();
      *   Vector3D i = k.orthogonal();
-     *   Vector3D j = Vector3D.crossProduct(k, i);
+     *   Vector3D j = k.crossProduct(i);
      * </code></pre>
      * @return a new normalized vector orthogonal to the instance
      * @exception IllegalNormException if the norm of the instance is zero, 
NaN,
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 6f78508..dc3b925 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,6 +17,7 @@
 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.internal.Vectors;
@@ -200,13 +201,38 @@ public class Vector2D extends Cartesian2D implements 
EuclideanVector<Point2D, Ve
     /** {@inheritDoc} */
     @Override
     public Vector2D project(Vector2D base) {
-        return getComponent(base, false);
+        return getComponent(base, false, Vector2D::new);
     }
 
     /** {@inheritDoc} */
     @Override
     public Vector2D reject(Vector2D base) {
-        return getComponent(base, true);
+        return getComponent(base, true, Vector2D::new);
+    }
+
+    /** Returns a unit vector orthogonal to the current vector by rotating the
+     * vector {@code pi/2} radians counterclockwise around the origin. For 
example,
+     * if this method is called on the vector representing the positive 
x-axis, then
+     * a 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
+     */
+    public Vector2D orthogonal() {
+        return normalize(-getY(), getX());
+    }
+
+    /** 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 Vector2D orthogonal(Vector2D dir) {
+        return dir.getComponent(this, true, Vector2D::normalize);
     }
 
     /** {@inheritDoc}
@@ -321,25 +347,32 @@ public class Vector2D extends Cartesian2D implements 
EuclideanVector<Point2D, Ve
      * @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.ensureRealNonZeroNorm(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.ensureRealNonZeroNorm(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 a vector with the given coordinate values.
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 9d61862..19b4814 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
@@ -405,6 +405,101 @@ public class Vector2DTest {
     }
 
     @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
         Assert.assertEquals(0, Vector2D.PLUS_X.angle(Vector2D.PLUS_X), EPS);

Reply via email to