You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2018/09/17 13:41:53 UTC
[commons-geometry] 01/07: GEOMETRY-10: adding Vector3D private
class for normalization optimizations;
adding Vector3D#orthogonal(Vector3D) method
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 fb028f007055c035c3ac66f557eac871b940e17f
Author: Matt Juntunen <ma...@hotmail.com>
AuthorDate: Tue Sep 4 23:49:44 2018 -0400
GEOMETRY-10: adding Vector3D private class for normalization optimizations; adding Vector3D#orthogonal(Vector3D) method
---
.../commons/geometry/euclidean/threed/Point3D.java | 7 ++
.../geometry/euclidean/threed/Vector3D.java | 93 +++++++++++++++++----
.../geometry/euclidean/threed/Vector3DTest.java | 96 +++++++++++++++++++++-
3 files changed, 178 insertions(+), 18 deletions(-)
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 52c574b..cfe0af9 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,13 @@ public final class Point3D extends Cartesian3D implements EuclideanPoint<Point3D
return p.subtract(this);
}
+ 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 2a1a5f7..61c73d6 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.internal.DoubleFunction3N;
import org.apache.commons.geometry.core.internal.SimpleTupleFormat;
import org.apache.commons.geometry.core.util.Vectors;
import org.apache.commons.geometry.euclidean.EuclideanVector;
@@ -25,28 +26,28 @@ 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 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 +62,8 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point
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 +190,7 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point
/** {@inheritDoc} */
@Override
public Vector3D normalize() throws IllegalStateException {
- return scalarMultiply(1.0 / getNonZeroNorm());
+ return normalize(getX(), getY(), getZ());
}
/** Get a vector orthogonal to the instance.
@@ -225,6 +226,19 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point
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 IllegalStateException if the norm of the current vector is zero or the given
+ * vector is collinear with this vector.
+ */
+ public Vector3D orthogonal(Vector3D dir) throws IllegalStateException {
+ 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
@@ -323,13 +337,13 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point
/** {@inheritDoc} */
@Override
public Vector3D project(Vector3D base) throws IllegalStateException {
- return getComponent(base, false);
+ return getComponent(base, false, Vector3D::new);
}
/** {@inheritDoc} */
@Override
public Vector3D reject(Vector3D base) throws IllegalStateException {
- return getComponent(base, true);
+ return getComponent(base, true, Vector3D::new);
}
/**
@@ -402,11 +416,12 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point
* @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 IllegalStateException if {@code base} has a zero norm
*/
- private Vector3D getComponent(Vector3D base, boolean reject) throws IllegalStateException {
+ private Vector3D getComponent(Vector3D base, boolean reject, DoubleFunction3N<Vector3D> factory) throws IllegalStateException {
final double aDotB = dotProduct(base);
final double baseMagSq = base.getNormSq();
@@ -421,10 +436,10 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point
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);
}
/** Computes the dot product between to vectors. This method simply
@@ -515,6 +530,23 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point
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 abscissa (second coordinate value)
+ * @param z height (third coordinate value)
+ * @return normalized vector instance
+ * @throws IllegalStateException if the norm of the given values is zero
+ */
+ public static Vector3D normalize(final double x, final double y, final double z) throws IllegalStateException {
+ final double norm = Vectors.norm(x, y, z);
+ if (norm == 0.0) {
+ throw new ZeroNormException();
+ }
+ 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
@@ -623,4 +655,35 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point
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 abscissa (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/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 4880438..e0de1c5 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
@@ -51,6 +51,19 @@ public class Vector3DTest {
}
@Test
+ public void testNonZeroConstants_areUnitVectorInstances() {
+ // act/assert
+ 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
Vector3D zero = Vector3D.of(1, 2, 3).getZero();
@@ -131,6 +144,8 @@ public class Vector3DTest {
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);
@@ -142,6 +157,14 @@ public class Vector3DTest {
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(expected = IllegalStateException.class)
@@ -151,6 +174,22 @@ public class Vector3DTest {
}
@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
Vector3D v1 = Vector3D.of(1, 2, 3);
@@ -247,7 +286,7 @@ public class Vector3DTest {
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(expected = IllegalStateException.class)
@@ -257,6 +296,17 @@ public class Vector3DTest {
}
@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
Vector3D v1 = Vector3D.of(0.1, 2.5, 1.3);
@@ -278,6 +328,31 @@ public class Vector3DTest {
}
@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(expected = IllegalStateException.class)
+ public void testOrthogonal_givenDirection_zeroNorm() {
+ // act/assert
+ Vector3D.ZERO.orthogonal(Vector3D.PLUS_X);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testOrthogonal_givenDirection_directionIsCollinear() {
+ // act/assert
+ Vector3D.PLUS_X.orthogonal(Vector3D.of(-2.0, 0.0, 0.0));
+ }
+
+ @Test
public void testAngle() {
// arrange
double tolerance = 1e-10;
@@ -956,7 +1031,7 @@ public class Vector3DTest {
}
@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);
@@ -967,7 +1042,7 @@ public class Vector3DTest {
}
@Test(expected = IllegalArgumentException.class)
- public void testOf_arrayArg_invalidDimensions() {
+ public void testOfArray_invalidDimensions() {
// act/assert
Vector3D.ofArray(new double[] { 0.0, 0.0 });
}
@@ -994,6 +1069,21 @@ public class Vector3DTest {
}
@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(expected = IllegalStateException.class)
+ public void testNormalize_static_zeroNorm() {
+ Vector3D.normalize(0.0, 0.0, 0.0);
+ }
+
+ @Test
public void testLinearCombination1() {
// arrange
Vector3D p1 = Vector3D.of(1, 2, 3);