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-numbers.git
commit d216fe76769afcd8d271ba39038251bb5b9a4659 Author: Gilles Sadowski <[email protected]> AuthorDate: Tue Dec 11 17:21:57 2018 +0100 NUMBERS-80: Specialized implementations for normalized and positive polar form quaternions. --- .../commons/numbers/quaternion/Quaternion.java | 256 +++++++++++++++------ .../commons/numbers/quaternion/QuaternionTest.java | 42 ++++ 2 files changed, 232 insertions(+), 66 deletions(-) diff --git a/commons-numbers-quaternion/src/main/java/org/apache/commons/numbers/quaternion/Quaternion.java b/commons-numbers-quaternion/src/main/java/org/apache/commons/numbers/quaternion/Quaternion.java index b1d7f7d..15f10e3 100644 --- a/commons-numbers-quaternion/src/main/java/org/apache/commons/numbers/quaternion/Quaternion.java +++ b/commons-numbers-quaternion/src/main/java/org/apache/commons/numbers/quaternion/Quaternion.java @@ -27,17 +27,17 @@ import org.apache.commons.numbers.core.Precision; * * <p>Instance of this class are guaranteed to be immutable.</p> */ -public final class Quaternion implements Serializable { - /** Identity quaternion. */ - public static final Quaternion IDENTITY = new Quaternion(1, 0, 0, 0); +public abstract class Quaternion implements Serializable { /** Zero quaternion. */ - public static final Quaternion ZERO = new Quaternion(0, 0, 0, 0); + public static final Quaternion ZERO = of(0, 0, 0, 0); + /** Identity quaternion. */ + public static final Quaternion IDENTITY = new PositivePolarForm(of(1, 0, 0, 0)); /** i */ - public static final Quaternion I = new Quaternion(0, 1, 0, 0); + public static final Quaternion I = new PositivePolarForm(of(0, 1, 0, 0)); /** j */ - public static final Quaternion J = new Quaternion(0, 0, 1, 0); + public static final Quaternion J = new PositivePolarForm(of(0, 0, 1, 0)); /** k */ - public static final Quaternion K = new Quaternion(0, 0, 0, 1); + public static final Quaternion K = new PositivePolarForm(of(0, 0, 0, 1)); /** Serializable version identifier. */ private static final long serialVersionUID = 20170118L; @@ -79,39 +79,19 @@ public final class Quaternion implements Serializable { } /** - * Builds a quaternion from scalar and vector parts. - * - * @param scalar Scalar part of the quaternion. - * @param v Components of the vector part of the quaternion. - * - * @throws IllegalArgumentException if the array length is not 3. - */ - private Quaternion(final double scalar, - final double[] v) { - if (v.length != 3) { - throw new IllegalArgumentException("Size of array must be 3"); - } - - w = scalar; - x = v[0]; - y = v[1]; - z = v[2]; - } - - /** * Builds a quaternion from its components. * * @param a Scalar component. * @param b First vector component. * @param c Second vector component. * @param d Third vector component. - * @return a quaternion instance + * @return a quaternion instance. */ public static Quaternion of(final double a, final double b, final double c, final double d) { - return new Quaternion(a, b, c, d); + return new Default(a, b, c, d); } /** @@ -119,13 +99,17 @@ public final class Quaternion implements Serializable { * * @param scalar Scalar part of the quaternion. * @param v Components of the vector part of the quaternion. - * @return a quaternion instance + * @return a quaternion instance. * * @throws IllegalArgumentException if the array length is not 3. */ public static Quaternion of(final double scalar, final double[] v) { - return new Quaternion(scalar, v); + if (v.length != 3) { + throw new IllegalArgumentException("Size of array must be 3"); + } + + return of(scalar, v[0], v[1], v[2]); } /** @@ -133,10 +117,10 @@ public final class Quaternion implements Serializable { * part is zero). * * @param v Components of the vector part of the pure quaternion. - * @return a quaternion instance + * @return a quaternion instance. */ public static Quaternion of(final double[] v) { - return new Quaternion(0, v); + return of(0, v); } /** @@ -146,15 +130,15 @@ public final class Quaternion implements Serializable { * @return the conjugate of this quaternion object. */ public Quaternion conjugate() { - return new Quaternion(w, -x, -y, -z); + return of(w, -x, -y, -z); } /** * Returns the Hamilton product of two quaternions. * - * @param x First quaternion. + * @param q1 First quaternion. * @param q2 Second quaternion. - * @return the product {@code x} and {@code q2}, in that order. + * @return the product {@code q1} and {@code q2}, in that order. */ public static Quaternion multiply(final Quaternion q1, final Quaternion q2) { @@ -176,7 +160,7 @@ public final class Quaternion implements Serializable { final double y = q1a * q2c - q1b * q2d + q1c * q2a + q1d * q2b; final double z = q1a * q2d + q1b * q2c - q1c * q2b + q1d * q2a; - return new Quaternion(w, x, y, z); + return of(w, x, y, z); } /** @@ -198,10 +182,10 @@ public final class Quaternion implements Serializable { */ public static Quaternion add(final Quaternion q1, final Quaternion q2) { - return new Quaternion(q1.w + q2.w, - q1.x + q2.x, - q1.y + q2.y, - q1.z + q2.z); + return of(q1.w + q2.w, + q1.x + q2.x, + q1.y + q2.y, + q1.z + q2.z); } /** @@ -223,10 +207,10 @@ public final class Quaternion implements Serializable { */ public static Quaternion subtract(final Quaternion q1, final Quaternion q2) { - return new Quaternion(q1.w - q2.w, - q1.x - q2.x, - q1.y - q2.y, - q1.z - q2.z); + return of(q1.w - q2.w, + q1.x - q2.x, + q1.y - q2.y, + q1.z - q2.z); } /** @@ -299,7 +283,13 @@ public final class Quaternion implements Serializable { throw new IllegalStateException(ZERO_NORM_MSG); } - return this.divide(norm); + final Quaternion unit = divide(norm); + + if (w >= 0) { + return new PositivePolarForm(unit); + } else { + return new Normalized(unit); + } } /** @@ -375,20 +365,26 @@ public final class Quaternion implements Serializable { * @return the unit quaternion with positive scalar part. */ public Quaternion positivePolarForm() { - if (w < 0) { - final Quaternion unitQ = normalize(); + if (w >= 0) { + return normalize(); + } else { // The quaternion of rotation (normalized quaternion) q and -q // are equivalent (i.e. represent the same rotation). - return new Quaternion(-unitQ.w, - -unitQ.x, - -unitQ.y, - -unitQ.z); - } else { - return this.normalize(); + return minus().normalize(); } } /** + * Returns the opposite of this instance. + * + * @return the quaternion for which all components have an opposite + * sign to this one. + */ + public Quaternion minus() { + return of(-w, -x, -y, -z); + } + + /** * Returns the inverse of this instance. * The norm of the quaternion must not be zero. * @@ -401,10 +397,10 @@ public final class Quaternion implements Serializable { throw new IllegalStateException(ZERO_NORM_MSG); } - return new Quaternion(w / squareNorm, - -x / squareNorm, - -y / squareNorm, - -z / squareNorm); + return of(w / squareNorm, + -x / squareNorm, + -y / squareNorm, + -z / squareNorm); } /** @@ -475,10 +471,10 @@ public final class Quaternion implements Serializable { * @return a scaled quaternion. */ public Quaternion multiply(final double alpha) { - return new Quaternion(alpha * w, - alpha * x, - alpha * y, - alpha * z); + return of(alpha * w, + alpha * x, + alpha * y, + alpha * z); } /** @@ -488,10 +484,10 @@ public final class Quaternion implements Serializable { * @return a scaled quaternion. */ public Quaternion divide(final double alpha) { - return new Quaternion(w / alpha, - x / alpha, - y / alpha, - z / alpha); + return of(w / alpha, + x / alpha, + y / alpha, + z / alpha); } /** @@ -576,4 +572,132 @@ public final class Quaternion implements Serializable { super(msg); } } + + /** + * Default implementation. + */ + private static class Default extends Quaternion { + /** + * Builds a quaternion from its components. + * + * @param a Scalar component. + * @param b First vector component. + * @param c Second vector component. + * @param d Third vector component. + */ + Default(final double a, + final double b, + final double c, + final double d) { + super(a, b, c, d); + } + } + + /** + * Special implementation for unit quaternions. + */ + private static class Normalized extends Default { + /** + * Builds a quaternion from its components. + * It is the caller's responsibility to ensure that the arguments + * do actually form a normalized quaternion. + * + * @param a Scalar component. + * @param b First vector component. + * @param c Second vector component. + * @param d Third vector component. + */ + Normalized(final double a, + final double b, + final double c, + final double d) { + super(a, b, c, d); + } + + /** + * Builds a normalized quaternion. + * It is the caller's responsibility to ensure that {@code q} + * is actually a normalized quaternion. + * + * @param q Unit quaternion. + */ + Normalized(Quaternion q) { + super(q.getW(), q.getX(), q.getY(), q.getZ()); + } + + /** {@inheritDoc} */ + @Override + public Quaternion normalize() { + return this; + } + + /** {@inheritDoc} */ + @Override + public double norm() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public double normSq() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isUnitQuaternion(double eps) { + return true; + } + + /** {@inheritDoc} */ + @Override + public Quaternion inverse() { + return new Normalized(getW(), -getX(), -getY(), -getZ()); + } + } + + /** + * Special implementation for positive polar forms. + */ + private static class PositivePolarForm extends Normalized { + /** + * Builds a quaternion from its components. + * It is the caller's responsibility to ensure that the arguments + * do actually constitute a positive polar form. + * + * @param a Scalar component. + * @param b First vector component. + * @param c Second vector component. + * @param d Third vector component. + */ + PositivePolarForm(final double a, + final double b, + final double c, + final double d) { + super(a, b, c, d); + } + + /** + * Builds a positive polar form. + * It is the caller's responsibility to ensure that {@code q} + * is actually a positive polar form. + * + * @param q Positive polar form. + */ + PositivePolarForm(Quaternion q) { + super(q); + } + + /** {@inheritDoc} */ + @Override + public Quaternion positivePolarForm() { + return this; + } + + /** {@inheritDoc} */ + @Override + public Quaternion inverse() { + return new PositivePolarForm(getW(), -getX(), -getY(), -getZ()); + } + } } diff --git a/commons-numbers-quaternion/src/test/java/org/apache/commons/numbers/quaternion/QuaternionTest.java b/commons-numbers-quaternion/src/test/java/org/apache/commons/numbers/quaternion/QuaternionTest.java index d9586da..53b15f2 100644 --- a/commons-numbers-quaternion/src/test/java/org/apache/commons/numbers/quaternion/QuaternionTest.java +++ b/commons-numbers-quaternion/src/test/java/org/apache/commons/numbers/quaternion/QuaternionTest.java @@ -28,6 +28,26 @@ public class QuaternionTest { private static final double COMPARISON_EPS = 1e-14; @Test + public void testZeroQuaternion() { + Assert.assertEquals(0, Quaternion.ZERO.norm(), 0d); + } + + @Test + public void testUnitQuaternions() { + Assert.assertEquals(1, Quaternion.IDENTITY.norm(), 0d); + Assert.assertTrue(Quaternion.IDENTITY.normalize() == Quaternion.IDENTITY); + + Assert.assertEquals(1, Quaternion.I.norm(), 0d); + Assert.assertTrue(Quaternion.I.normalize() == Quaternion.I); + + Assert.assertEquals(1, Quaternion.J.norm(), 0d); + Assert.assertTrue(Quaternion.J.normalize() == Quaternion.J); + + Assert.assertEquals(1, Quaternion.K.norm(), 0d); + Assert.assertTrue(Quaternion.K.normalize() == Quaternion.K); + } + + @Test public final void testAccessors1() { final double q0 = 2; final double q1 = 5.4; @@ -307,6 +327,8 @@ public class QuaternionTest { Assert.assertEquals(-2.0 / 5.0, versor.getZ(), 0); Assert.assertEquals(1, versor.norm(), 0); + + Assert.assertTrue(versor.normalize() == versor); } @Test(expected=IllegalStateException.class) @@ -434,6 +456,8 @@ public class QuaternionTest { Quaternion actual = q.positivePolarForm(); Quaternion expected = Quaternion.of(0.5, -0.5, -0.5, 0.5); assertEquals(actual, expected, EPS); + + Assert.assertTrue(actual.positivePolarForm() == actual); } @Test @@ -442,6 +466,8 @@ public class QuaternionTest { Quaternion actual = q.positivePolarForm(); Quaternion expected = Quaternion.of(0.5, -0.5, 0.5, -0.5); assertEquals(actual, expected, EPS); + + Assert.assertTrue(actual.positivePolarForm() == actual); } /* TODO remove dependency on Rotation @@ -499,6 +525,22 @@ public class QuaternionTest { } @Test + public void testInverseNormalized() { + final Quaternion invQ = Quaternion.of(-1.2, 3.4, -5.6, -7.8).normalize().inverse(); + final Quaternion q = invQ.inverse(); + final Quaternion result = q.multiply(invQ); + Assert.assertTrue(result.toString(), Quaternion.IDENTITY.equals(result, EPS)); + } + + @Test + public void testInversePositivePolarForm() { + final Quaternion invQ = Quaternion.of(1.2, -3.4, 5.6, -7.8).positivePolarForm().inverse(); + final Quaternion q = invQ.inverse(); + final Quaternion result = q.multiply(invQ); + Assert.assertTrue(result.toString(), Quaternion.IDENTITY.equals(result, EPS)); + } + + @Test public final void testMultiply() { final Quaternion q1 = Quaternion.of(1, 2, 3, 4); final Quaternion q2 = Quaternion.of(4, 3, 2, 1);
