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 204b994e40ba3ce0a917a20a2a8cd41430ae98ec Author: Matt Juntunen <[email protected]> AuthorDate: Mon Sep 3 15:04:28 2018 -0400 GEOMETRY-9, GEOMETRY-8: removing MultiDimensionalVector class since project, reject, and angle do not require more than one dimension to work mathematically; adding internal ZeroNormException class --- .../geometry/core/MultiDimensionalVector.java | 60 ------------- .../org/apache/commons/geometry/core/Vector.java | 35 ++++++++ .../commons/geometry/euclidean/EuclideanPoint.java | 4 +- .../geometry/euclidean/EuclideanVector.java | 4 +- .../euclidean/internal/ZeroNormException.java | 50 +++++++++++ .../geometry/euclidean/internal/package-info.java | 25 ++++++ .../commons/geometry/euclidean/oned/Vector1D.java | 47 +++++++++-- .../geometry/euclidean/threed/Rotation.java | 2 +- .../geometry/euclidean/threed/Vector3D.java | 8 +- .../commons/geometry/euclidean/twod/Vector2D.java | 8 +- .../geometry/euclidean/oned/Vector1DTest.java | 97 ++++++++++++++++++++++ 11 files changed, 264 insertions(+), 76 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 deleted file mode 100644 index cf21b56..0000000 --- a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/MultiDimensionalVector.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 base vector - * @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 base vector - * @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-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 618757a..d4eb296 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 @@ -171,4 +171,39 @@ public interface Vector<V extends Vector<V>> extends Spatial { * @return the dot product this · v */ double dotProduct(V 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 base vector + * @return the vector projection of the instance onto {@code base} + * @exception IllegalStateException if the norm of the base vector is zero + * @see #reject(Vector) + */ + 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 base vector + * @return the vector rejection of the instance from {@code base} + * @exception IllegalStateException if the norm of the base vector is zero + * @see #project(Vector) + */ + V reject(V base) throws IllegalStateException; + + /** Compute the angular separation between two vectors in radians. + * @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/EuclideanPoint.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanPoint.java index 4941306..bde9f67 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 @@ -45,9 +45,11 @@ public interface EuclideanPoint<P extends EuclideanPoint<P, V>, V extends Euclid * {@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 * point will be returned. If {@code t = 1}, a point equal to the argument will be returned. + * The {@code t} parameter is not constrained to the range {@code [0, 1]}, meaning that + * linear extrapolation can also be performed with this method. * @param p other point * @param t interpolation parameter - * @return interpolated point + * @return interpolated or extrapolated point */ P lerp(P p, double t); } diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVector.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVector.java index 8df3a91..ad4f21b 100644 --- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVector.java +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/EuclideanVector.java @@ -37,9 +37,11 @@ public interface EuclideanVector<P extends EuclideanPoint<P, V>, V extends Eucli * {@code V = (1 - t)*A + t*B}, where {@code A} is the current vector and {@code B} * is the given vector. This means that if {@code t = 0}, a vector equal to the current * vector will be returned. If {@code t = 1}, a vector equal to the argument will be returned. + * The {@code t} parameter is not constrained to the range {@code [0, 1]}, meaning that + * linear extrapolation can also be performed with this method. * @param v other vector * @param t interpolation parameter - * @return interpolated vector + * @return interpolated or extrapolated vector */ V lerp(V v, double t); } diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/ZeroNormException.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/ZeroNormException.java new file mode 100644 index 0000000..fe9ecf1 --- /dev/null +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/ZeroNormException.java @@ -0,0 +1,50 @@ +/* + * 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.euclidean.internal; + +/** Internal exception class with constants for frequently used messages. + * This exception is thrown when vector operations requiring a non-zero + * vector norm are attempted with a vector with a zero norm. + */ +public class ZeroNormException extends IllegalStateException { + + /** Default zero-norm error message. */ + public static final String ZERO_NORM_MSG = "Norm is zero"; + + /** Error message for cases where code is attempting to use a zero-norm vector + * as a base vector. + */ + public static final String INVALID_BASE = "Invalid base vector: norm is zero"; + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20180903L; + + /** + * Simple constructor, using the default error message. + */ + public ZeroNormException() { + this(ZERO_NORM_MSG); + } + + /** + * Constructs an instance with the given error message. + * @param msg error message + */ + public ZeroNormException(String msg) { + super(msg); + } +} diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/package-info.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/package-info.java new file mode 100644 index 0000000..e70a48b --- /dev/null +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/internal/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ +/** + * + * <p> + * This package contains Euclidean geometry utilities and classes intended + * for internal use only. No guarantees are made for the stability of the + * contained APIs. + * </p> + */ +package org.apache.commons.geometry.euclidean.internal; 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 be97a91..68fec66 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 @@ -16,9 +16,11 @@ */ package org.apache.commons.geometry.euclidean.oned; +import org.apache.commons.geometry.core.Geometry; import org.apache.commons.geometry.core.internal.SimpleTupleFormat; import org.apache.commons.geometry.core.util.Vectors; import org.apache.commons.geometry.euclidean.EuclideanVector; +import org.apache.commons.geometry.euclidean.internal.ZeroNormException; import org.apache.commons.numbers.arrays.LinearCombination; /** This class represents a vector in one-dimensional Euclidean space. @@ -51,9 +53,6 @@ public final class Vector1D extends Cartesian1D implements EuclideanVector<Point /** Serializable UID. */ private static final long serialVersionUID = 20180710L; - /** Error message when a norm is zero. */ - private static final String ZERO_NORM_MSG = "Norm is zero"; - /** Simple constructor. * @param x abscissa (coordinate value) */ @@ -125,7 +124,7 @@ public final class Vector1D extends Cartesian1D implements EuclideanVector<Point else if (x < 0.0) { return new Vector1D(-magnitude); } - throw new IllegalStateException(ZERO_NORM_MSG); + throw new ZeroNormException(); } /** {@inheritDoc} */ @@ -168,7 +167,7 @@ public final class Vector1D extends Cartesian1D implements EuclideanVector<Point else if (x < 0.0) { return MINUS_ONE; } - throw new IllegalStateException(ZERO_NORM_MSG); + throw new ZeroNormException(); } /** {@inheritDoc} */ @@ -207,6 +206,44 @@ public final class Vector1D extends Cartesian1D implements EuclideanVector<Point return getX() * v.getX(); } + /** {@inheritDoc} + * <p>For the one-dimensional case, this method simply returns the current instance.</p> + */ + @Override + public Vector1D project(final Vector1D base) throws IllegalStateException { + if (base.getX() == 0) { + throw new ZeroNormException(ZeroNormException.INVALID_BASE); + } + return this; + } + + /** {@inheritDoc} + * <p>For the one-dimensional case, this method simply returns the zero vector.</p> + */ + @Override + public Vector1D reject(final Vector1D base) throws IllegalStateException { + if (base.getX() == 0) { + throw new ZeroNormException(ZeroNormException.INVALID_BASE); + } + return Vector1D.ZERO; + } + + /** {@inheritDoc} + * <p>For the one-dimensional case, this method returns 0 if the vector x values have + * the same sign and {@code pi} if they are opposite.</p> + */ + @Override + public double angle(final Vector1D v) throws IllegalStateException { + final double sig1 = Math.signum(getX()); + final double sig2 = Math.signum(v.getX()); + + if (sig1 == 0 || sig2 == 0) { + throw new ZeroNormException(); + } + // the angle is 0 if the x value signs are the same and pi if not + return (sig1 == sig2) ? 0.0 : Geometry.PI; + } + /** * 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/threed/Rotation.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java index a24129c..272d5c0 100644 --- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java +++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/Rotation.java @@ -94,7 +94,7 @@ public class Rotation implements Serializable { public static final Rotation IDENTITY = new Rotation(1.0, 0.0, 0.0, 0.0, false); /** Serializable version identifier */ - private static final long serialVersionUID = -2153622329907944313L; + private static final long serialVersionUID = 20180903L; /** Error message for Cardan angle singularities */ private static final String CARDAN_SINGULARITY_MSG = "Cardan angles singularity"; 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 640f232..2a1a5f7 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,16 +16,16 @@ */ package org.apache.commons.geometry.euclidean.threed; -import org.apache.commons.geometry.core.MultiDimensionalVector; import org.apache.commons.geometry.core.internal.SimpleTupleFormat; import org.apache.commons.geometry.core.util.Vectors; import org.apache.commons.geometry.euclidean.EuclideanVector; +import org.apache.commons.geometry.euclidean.internal.ZeroNormException; 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>, MultiDimensionalVector<Vector3D> { +public final 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); @@ -389,7 +389,7 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point private double getNonZeroNorm() throws IllegalStateException { final double n = getNorm(); if (n == 0) { - throw new IllegalStateException("Norm is zero"); + throw new ZeroNormException(); } return n; @@ -411,7 +411,7 @@ public final class Vector3D extends Cartesian3D implements EuclideanVector<Point final double baseMagSq = base.getNormSq(); if (baseMagSq == 0.0) { - throw new IllegalStateException("Invalid base vector: norm is zero"); + throw new ZeroNormException(ZeroNormException.INVALID_BASE); } final double scale = aDotB / baseMagSq; 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 c94b52d..60877cb 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,16 +16,16 @@ */ package org.apache.commons.geometry.euclidean.twod; -import org.apache.commons.geometry.core.MultiDimensionalVector; import org.apache.commons.geometry.core.internal.SimpleTupleFormat; import org.apache.commons.geometry.core.util.Vectors; import org.apache.commons.geometry.euclidean.EuclideanVector; +import org.apache.commons.geometry.euclidean.internal.ZeroNormException; 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>, MultiDimensionalVector<Vector2D> { +public final class Vector2D extends Cartesian2D implements EuclideanVector<Point2D, Vector2D> { /** Zero vector (coordinates: 0, 0). */ public static final Vector2D ZERO = new Vector2D(0, 0); @@ -335,7 +335,7 @@ public final class Vector2D extends Cartesian2D implements EuclideanVector<Point private double getNonZeroNorm() throws IllegalStateException { final double n = getNorm(); if (n == 0) { - throw new IllegalStateException("Norm is zero"); + throw new ZeroNormException(); } return n; @@ -357,7 +357,7 @@ public final class Vector2D extends Cartesian2D implements EuclideanVector<Point final double baseMagSq = base.getNormSq(); if (baseMagSq == 0.0) { - throw new IllegalStateException("Invalid base vector: norm is zero"); + throw new ZeroNormException(ZeroNormException.INVALID_BASE); } final double scale = aDotB / baseMagSq; 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 14b40b7..20396fe 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 @@ -18,6 +18,7 @@ package org.apache.commons.geometry.euclidean.oned; import java.util.regex.Pattern; +import org.apache.commons.geometry.core.Geometry; import org.apache.commons.numbers.core.Precision; import org.junit.Assert; import org.junit.Test; @@ -292,6 +293,102 @@ public class Vector1DTest { } @Test + public void testProject() { + // arrange + Vector1D v1 = Vector1D.of(2); + Vector1D v2 = Vector1D.of(-3); + Vector1D v3 = Vector1D.of(4); + + // act/assert + checkVector(Vector1D.ZERO.project(v1), 0); + checkVector(Vector1D.ZERO.project(v2), 0); + checkVector(Vector1D.ZERO.project(v3), 0); + + checkVector(v1.project(v1), 2); + checkVector(v1.project(v2), 2); + checkVector(v1.project(v3), 2); + + checkVector(v2.project(v1), -3); + checkVector(v2.project(v2), -3); + checkVector(v2.project(v3), -3); + + checkVector(v3.project(v1), 4); + checkVector(v3.project(v2), 4); + checkVector(v3.project(v3), 4); + } + + @Test(expected = IllegalStateException.class) + public void testProject_baseHasZeroNorm() { + // act/assert + Vector1D.of(2.0).project(Vector1D.ZERO); + } + + @Test + public void testReject() { + // arrange + Vector1D v1 = Vector1D.of(2); + Vector1D v2 = Vector1D.of(-3); + + // act/assert + checkVector(Vector1D.ZERO.reject(v1), 0); + checkVector(Vector1D.ZERO.reject(v2), 0); + + checkVector(v1.reject(v1), 0); + checkVector(v1.reject(v2), 0); + + checkVector(v2.reject(v1), 0); + checkVector(v2.reject(v2), 0); + } + + @Test(expected = IllegalStateException.class) + public void testReject_baseHasZeroNorm() { + // act/assert + Vector1D.of(2.0).reject(Vector1D.ZERO); + } + + @Test + public void testAngle() { + // arrange + Vector1D v1 = Vector1D.of(2); + Vector1D v2 = Vector1D.of(-3); + Vector1D v3 = Vector1D.of(4); + Vector1D v4 = Vector1D.of(-5); + + // act/assert + Assert.assertEquals(0.0, v1.angle(v1), TEST_TOLERANCE); + Assert.assertEquals(Geometry.PI, v1.angle(v2), TEST_TOLERANCE); + Assert.assertEquals(0.0, v1.angle(v3), TEST_TOLERANCE); + Assert.assertEquals(Geometry.PI, v1.angle(v4), TEST_TOLERANCE); + + Assert.assertEquals(Geometry.PI, v2.angle(v1), TEST_TOLERANCE); + Assert.assertEquals(0.0, v2.angle(v2), TEST_TOLERANCE); + Assert.assertEquals(Geometry.PI, v2.angle(v3), TEST_TOLERANCE); + Assert.assertEquals(0.0, v2.angle(v4), TEST_TOLERANCE); + + Assert.assertEquals(0.0, v3.angle(v1), TEST_TOLERANCE); + Assert.assertEquals(Geometry.PI, v3.angle(v2), TEST_TOLERANCE); + Assert.assertEquals(0.0, v3.angle(v3), TEST_TOLERANCE); + Assert.assertEquals(Geometry.PI, v3.angle(v4), TEST_TOLERANCE); + + Assert.assertEquals(Geometry.PI, v4.angle(v1), TEST_TOLERANCE); + Assert.assertEquals(0.0, v4.angle(v2), TEST_TOLERANCE); + Assert.assertEquals(Geometry.PI, v4.angle(v3), TEST_TOLERANCE); + Assert.assertEquals(0.0, v4.angle(v4), TEST_TOLERANCE); + } + + @Test(expected = IllegalStateException.class) + public void testAngle_firstVectorZero() { + // act/assert + Vector1D.ZERO.angle(Vector1D.of(1.0)); + } + + @Test(expected = IllegalStateException.class) + public void testAngle_secondVectorZero() { + // act/assert + Vector1D.of(1.0).angle(Vector1D.ZERO); + } + + @Test public void testLerp() { // arrange Vector1D v1 = Vector1D.of(1);
