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 dd0d3baa4f58cdb5d75759696a5f17be2c1d0708 Author: Matt Juntunen <matt.juntu...@hotmail.com> AuthorDate: Sun Sep 16 23:24:42 2018 -0400 GEOMETRY-10: adding private UnitVector private classes in Vector2D and Vector1D for normalization optimizations; adding Point?D#directionTo() method --- .../commons/geometry/euclidean/EuclideanPoint.java | 11 ++++ .../commons/geometry/euclidean/oned/Point1D.java | 6 ++ .../commons/geometry/euclidean/oned/Vector1D.java | 51 +++++++++++++-- .../commons/geometry/euclidean/threed/Point3D.java | 2 + .../geometry/euclidean/threed/Vector3D.java | 8 +-- .../commons/geometry/euclidean/twod/Point2D.java | 9 +++ .../commons/geometry/euclidean/twod/Vector2D.java | 63 ++++++++++++++---- .../geometry/euclidean/oned/Point1DTest.java | 35 ++++++++++ .../geometry/euclidean/oned/Vector1DTest.java | 68 ++++++++++++++++++-- .../geometry/euclidean/threed/Point3DTest.java | 37 +++++++++++ .../geometry/euclidean/threed/Vector3DTest.java | 15 ++++- .../geometry/euclidean/twod/Point2DTest.java | 37 +++++++++++ .../geometry/euclidean/twod/Vector2DTest.java | 74 +++++++++++++++++++--- 13 files changed, 376 insertions(+), 40 deletions(-) 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..ed662b2 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 @@ -41,6 +41,17 @@ public interface EuclideanPoint<P extends EuclideanPoint<P, V>, V extends Euclid */ 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 @@ -83,6 +83,12 @@ public final class Point1D extends Cartesian1D implements EuclideanPoint<Point1D /** {@inheritDoc} */ @Override + public Vector1D directionTo(Point1D p) { + return Vector1D.normalize(p.getX() - getX()); + } + + /** {@inheritDoc} */ + @Override public Point1D lerp(Point1D p, double t) { return vectorCombination(1.0 - t, this, t, p); } 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 @@ import org.apache.commons.numbers.arrays.LinearCombination; /** 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 final class Vector1D extends Cartesian1D implements EuclideanVector<Point /** {@inheritDoc} */ @Override public Vector1D normalize() { - Vectors.ensureFiniteNonZeroNorm(getNorm()); - - return (getX() > 0.0) ? ONE : MINUS_ONE; + return normalize(getX()); } /** {@inheritDoc} */ @@ -290,6 +289,17 @@ public final class Vector1D extends Cartesian1D implements EuclideanVector<Point 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 final class Vector1D extends Cartesian1D implements EuclideanVector<Point 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 9e44bf5..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,8 @@ public final class Point3D extends Cartesian3D implements EuclideanPoint<Point3D return p.subtract(this); } + /** {@inheritDoc} */ + @Override public Vector3D directionTo(Point3D p) { return Vector3D.normalize( p.getX() - getX(), 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 cb66b1c..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 @@ -425,7 +425,7 @@ public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Ve // 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 result as checking the actual norm since + // 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()); @@ -478,10 +478,10 @@ public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Ve /** Returns a normalized vector derived from the given values. * @param x abscissa (first coordinate value) - * @param y abscissa (second 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 + * @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)); @@ -598,7 +598,7 @@ public class Vector3D extends Cartesian3D implements EuclideanVector<Point3D, Ve /** 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 y ordinate (second coordinate value) * @param z height (third coordinate value) */ private UnitVector(final double x, final double y, final double z) { 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 @@ -80,6 +80,15 @@ public final class Point2D extends Cartesian2D implements EuclideanPoint<Point2D /** {@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) { return vectorCombination(1.0 - t, this, t, 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 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 @@ 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 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 @@ public final class Vector2D extends Cartesian2D implements EuclideanVector<Point 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 final class Vector2D extends Cartesian2D implements EuclideanVector<Point /** {@inheritDoc} */ @Override public Vector2D normalize() { - return scalarMultiply(1.0 / getFiniteNonZeroNorm()); + return normalize(getX(), getY()); } /** {@inheritDoc} */ @@ -395,6 +387,19 @@ public final class Vector2D extends Cartesian2D implements EuclideanVector<Point 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 final class Vector2D extends Cartesian2D implements EuclideanVector<Point 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 @@ package org.apache.commons.geometry.euclidean.oned; 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; @@ -118,6 +120,39 @@ public class Point1DTest { } @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 Point1D p1 = Point1D.of(1); 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 @@ -41,6 +41,22 @@ public class Vector1DTest { } @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 checkPoint(Vector1D.of(0.0).asPoint(), 0.0); @@ -135,6 +151,21 @@ public class Vector1DTest { } @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 Vector1D v1 = Vector1D.of(1); @@ -208,17 +239,27 @@ public class Vector1DTest { @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 checkVector(Vector1D.of(0.1).negate(), -0.1); @@ -541,6 +582,25 @@ public class Vector1DTest { } @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 checkVector(Vector1D.linearCombination(2, Vector1D.of(3)), 6); 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 @@ package org.apache.commons.geometry.euclidean.threed; 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; @@ -103,6 +105,41 @@ public class Point3DTest { } @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 Point3D p1 = Point3D.of(1, -5, 2); 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 48c4524..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 @@ -53,8 +53,17 @@ public class Vector3DTest { } @Test - public void testNonZeroConstants_areUnitVectorInstances() { + 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); @@ -392,7 +401,9 @@ public class Vector3DTest { 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)), + 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); } 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 @@ package org.apache.commons.geometry.euclidean.twod; 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; @@ -94,6 +96,41 @@ public class Point2DTest { } @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 Point2D p1 = Point2D.of(1, -5); 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 class Vector2DTest { } @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 @@ -175,6 +180,22 @@ public class Vector2DTest { } @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 Vector2D v1 = Vector2D.of(-1, 2); @@ -249,7 +270,7 @@ public class Vector2DTest { 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 @@ -266,6 +287,17 @@ public class Vector2DTest { } @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 checkVector(Vector2D.of(1, 2).negate(), -1, -2); @@ -703,6 +735,28 @@ public class Vector2DTest { } @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 Vector2D p1 = Vector2D.of(1, 2);