From d65fb010f677fe330da560eb546c9672c135d41c Mon Sep 17 00:00:00 2001 From: Dimitrios Efthymiou Date: Fri, 14 Jul 2023 13:40:37 +0100 Subject: [PATCH] GEOMETRY-150: implemented a way to check if 2 vectors are codirectional --- .../apache/commons/geometry/core/Vector.java | 9 ++++++++ .../geometry/euclidean/oned/Vector1D.java | 12 ++++++++++ .../geometry/euclidean/threed/Vector3D.java | 23 +++++++++++++++++++ .../geometry/euclidean/twod/Vector2D.java | 20 ++++++++++++++++ .../geometry/euclidean/oned/Vector1DTest.java | 15 ++++++++++++ .../euclidean/threed/Vector3DTest.java | 22 ++++++++++++++++++ .../geometry/euclidean/twod/Vector2DTest.java | 21 +++++++++++++++++ 7 files changed, 122 insertions(+) diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java index dbd3e49d5..57b21d449 100644 --- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java +++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/Vector.java @@ -142,4 +142,13 @@ public interface Vector> extends Spatial { * @throws IllegalArgumentException if either vector has a zero, NaN, or infinite norm */ double angle(V v); + + /** Checks if the two vectors point at the same direction. + * This means that each vector can be obtained from the other by multiplying by a positive scalar. + * Any vector is considered as codirectional to a zero vector. + * @param v other vector + * @return {@code true} if both vectors point at the same direction. + * @throws IllegalArgumentException if either vector has a zero, NaN, or infinite norm + */ + boolean isCodirectionalTo(V v); } 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 6453f13c1..f426e3b59 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 @@ -232,6 +232,18 @@ public double angle(final Vector1D v) { return (sig1 == sig2) ? 0.0 : Math.PI; } + + /** + * {@inheritDoc} + */ + @Override public boolean isCodirectionalTo(Vector1D v) { + // validate the norm values + getCheckedNorm(); + v.getCheckedNorm(); + return v.x / x > 0.0; + } + + /** Convenience method to apply a function to this vector. This * can be used to transform the vector inline with other methods. * @param fn the function to apply 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 0ac52dae0..dd94c6919 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 @@ -26,6 +26,7 @@ import org.apache.commons.geometry.euclidean.EuclideanVectorSum; import org.apache.commons.geometry.euclidean.MultiDimensionalEuclideanVector; import org.apache.commons.geometry.euclidean.internal.Vectors; +import org.apache.commons.geometry.euclidean.twod.Vector2D; import org.apache.commons.numbers.core.Precision; /** This class represents vectors and points in three-dimensional Euclidean space. @@ -320,6 +321,28 @@ public double angle(final Vector3D v) { return Math.acos(dot / normProduct); } + + /** + * {@inheritDoc} + */ + @Override public boolean isCodirectionalTo(Vector3D v) { + // validate the norm values + double magnitudeOfThis = getCheckedNorm(); + double magnitudeOfV = v.getCheckedNorm(); + // Handle zero vectors: consider any vector as codirectional to a zero vector + if ((x == 0 && y == 0 && z == 0) || (v.x == 0 && v.y == 0 && v.z == 0)) { + return true; + } + + // If the ratios are equal, the vectors are codirectional. + double ratioX = x / v.x; + double ratioY = y / v.y; + double ratioZ = z / v.z; + // use a tolerance due to floating point inaccuracies + return Math.abs(ratioX - ratioY) < 0.000001 && Math.abs(ratioY - ratioZ) < 0.000001; + } + + /** {@inheritDoc} */ @Override public Vector3D project(final Vector3D base) { 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 738bbf75b..2ef3225ae 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 @@ -269,6 +269,26 @@ public double angle(final Vector2D v) { return Math.acos(dot / normProduct); } + + /** + * {@inheritDoc} + */ + @Override public boolean isCodirectionalTo(Vector2D v) { + // validate the norm values + double magnitudeOfThis = getCheckedNorm(); + double magnitudeOfV = v.getCheckedNorm(); + // Handle zero vectors: consider any vector as codirectional to a zero vector + if ((x == 0 && y == 0) || (v.x == 0 && v.y == 0)) { + return true; + } + + // If the ratios are equal, the vectors are codirectional. + double ratio = x / v.x; + // use a tolerance due to floating point inaccuracies + return Math.abs(ratio - (y / v.y)) < 0.000001; + } + + /** {@inheritDoc} */ @Override public Vector2D project(final Vector2D base) { 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 4bd8e614b..80ba92479 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 @@ -756,6 +756,21 @@ void testUnitFactoryOptimization() { Assertions.assertSame(v, v.normalize()); } + @Test + void testIsCodirectionalTo() { + final Vector1D v1 = Vector1D.of(1); + final Vector1D v2 = Vector1D.of(4); + Assertions.assertTrue(v1.isCodirectionalTo(v2)); + Assertions.assertTrue(v2.isCodirectionalTo(v1)); + + final Vector1D v3 = Vector1D.of(-1); + final Vector1D v4 = Vector1D.of(-4); + Assertions.assertTrue(v3.isCodirectionalTo(v4)); + Assertions.assertTrue(v3.isCodirectionalTo(v4)); + + Assertions.assertFalse(v1.isCodirectionalTo(v3)); + } + private void checkVector(final Vector1D v, final double x) { Assertions.assertEquals(x, v.getX(), TEST_TOLERANCE); } 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 4d7c435f3..13d565c8f 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 @@ -26,6 +26,7 @@ import org.apache.commons.geometry.core.GeometryTestUtils; import org.apache.commons.geometry.euclidean.EuclideanTestUtils; +import org.apache.commons.geometry.euclidean.oned.Vector1D; import org.apache.commons.numbers.angle.Angle; import org.apache.commons.numbers.core.Precision; import org.apache.commons.rng.UniformRandomProvider; @@ -1368,6 +1369,27 @@ void testUnitFactoryOptimization() { Assertions.assertSame(v, v.normalize()); } + @Test + void testIsCodirectionalTo() { + final Vector3D v1 = Vector3D.of(2, 2, 2); + final Vector3D v2 = Vector3D.of(1, 1, 1); + final Vector3D v3 = Vector3D.of(-2, -2, -2); + final Vector3D v4 = Vector3D.of(2, -2, 2); + + // Test codirectional vectors (same direction) + Assertions.assertTrue(v1.isCodirectionalTo(v2)); + Assertions.assertTrue(v2.isCodirectionalTo(v1)); + + // Test codirectional vectors (opposite direction) + Assertions.assertTrue(v1.isCodirectionalTo(v3)); + Assertions.assertTrue(v3.isCodirectionalTo(v1)); + + // Test non-codirectional vectors + Assertions.assertFalse(v1.isCodirectionalTo(v4)); + Assertions.assertFalse(v4.isCodirectionalTo(v1)); + + } + private void checkVector(final Vector3D v, final double x, final double y, final double z) { Assertions.assertEquals(x, v.getX(), EPS); Assertions.assertEquals(y, v.getY(), EPS); 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 72eb3ae1b..d036f9e7d 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 @@ -25,6 +25,7 @@ import org.apache.commons.geometry.core.GeometryTestUtils; import org.apache.commons.geometry.euclidean.EuclideanTestUtils; +import org.apache.commons.geometry.euclidean.oned.Vector1D; import org.apache.commons.numbers.angle.Angle; import org.apache.commons.numbers.core.Precision; import org.junit.jupiter.api.Assertions; @@ -1169,6 +1170,26 @@ void testUnitFactoryOptimization() { Assertions.assertSame(v, v.normalize()); } + @Test + void testIsCodirectionalTo() { + final Vector2D v1 = Vector2D.of(2, 2); + final Vector2D v2 = Vector2D.of(1, 1); + final Vector2D v3 = Vector2D.of(-2, -2); + final Vector2D v4 = Vector2D.of(2, -2); + + // Test codirectional vectors (same direction) + Assertions.assertTrue(v1.isCodirectionalTo(v2)); + Assertions.assertTrue(v2.isCodirectionalTo(v1)); + + // Test codirectional vectors (opposite direction) + Assertions.assertTrue(v1.isCodirectionalTo(v3)); + Assertions.assertTrue(v3.isCodirectionalTo(v1)); + + // Test non-codirectional vectors + Assertions.assertFalse(v1.isCodirectionalTo(v4)); + Assertions.assertFalse(v4.isCodirectionalTo(v1)); + } + private void checkVector(final Vector2D v, final double x, final double y) { checkVector(v, x, y, EPS); }