All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.gov.nasa.worldwind.geom.Quaternion Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show newest version
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.geom;

import gov.nasa.worldwind.util.Logging;

/**
 * @author Chris Maxwell
 * @version $Id: Quaternion.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class Quaternion
{
    // Multiplicative identity quaternion.
    public static final Quaternion IDENTITY = new Quaternion(0, 0, 0, 1);

    public final double x;
    public final double y;
    public final double z;
    public final double w;

    // 4 values in a quaternion.
    private static final int NUM_ELEMENTS = 4;
    // Cached computations.
    private int hashCode;

    public Quaternion(double x, double y, double z, double w)
    {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    }

    public final boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null || obj.getClass() != this.getClass())
            return false;

        Quaternion that = (Quaternion) obj;
        return (this.x == that.x)
            && (this.y == that.y)
            && (this.z == that.z)
            && (this.w == that.w);
    }

    public final int hashCode()
    {
        if (this.hashCode == 0)
        {
            int result;
            long tmp;
            tmp = Double.doubleToLongBits(this.x);
            result = (int) (tmp ^ (tmp >>> 32));
            tmp = Double.doubleToLongBits(this.y);
            result = 31 * result + (int) (tmp ^ (tmp >>> 32));
            tmp = Double.doubleToLongBits(this.z);
            result = 31 * result + (int) (tmp ^ (tmp >>> 32));
            tmp = Double.doubleToLongBits(this.w);
            result = 31 * result + (int) (tmp ^ (tmp >>> 32));
            this.hashCode = result;
        }
        return this.hashCode;
    }

    public static Quaternion fromArray(double[] compArray, int offset)
    {
        if (compArray == null)
        {
            String msg = Logging.getMessage("nullValue.ArrayIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }
        if ((compArray.length - offset) < NUM_ELEMENTS)
        {
            String msg = Logging.getMessage("generic.ArrayInvalidLength", compArray.length);
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        //noinspection PointlessArithmeticExpression                
        return new Quaternion(
            compArray[0 + offset],
            compArray[1 + offset],
            compArray[2 + offset],
            compArray[3 + offset]);
    }

    public final double[] toArray(double[] compArray, int offset)
    {
        if (compArray == null)
        {
            String msg = Logging.getMessage("nullValue.ArrayIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }
        if ((compArray.length - offset) < NUM_ELEMENTS)
        {
            String msg = Logging.getMessage("generic.ArrayInvalidLength", compArray.length);
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }
        
        //noinspection PointlessArithmeticExpression
        compArray[0 + offset] = this.x;
        compArray[1 + offset] = this.y;
        compArray[2 + offset] = this.z;
        compArray[3 + offset] = this.w;
        return compArray;
    }

    public final String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        sb.append(this.x).append(", ");
        sb.append(this.y).append(", ");
        sb.append(this.z).append(", ");
        sb.append(this.w);
        sb.append(")");
        return sb.toString();
    }

    public final double getX()
    {
        return this.x;
    }

    public final double getY()
    {
        return this.y;
    }

    public final double getZ()
    {
        return this.z;
    }

    public final double getW()
    {
        return this.w;
    }

    public final double x()
    {
        return this.x;
    }

    public final double y()
    {
        return this.y;
    }

    public final double z()
    {
        return this.z;
    }

    public final double w()
    {
        return this.w;
    }

    // ============== Factory Functions ======================= //
    // ============== Factory Functions ======================= //
    // ============== Factory Functions ======================= //

    public static Quaternion fromAxisAngle(Angle angle, Vec4 axis)
    {
        if (angle == null)
        {
            String msg = Logging.getMessage("nullValue.AngleIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }
        if (axis == null)
        {
            String msg = Logging.getMessage("nullValue.Vec4IsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return fromAxisAngle(angle, axis.x, axis.y, axis.z, true);
    }

    public static Quaternion fromAxisAngle(Angle angle, double axisX, double axisY, double axisZ)
    {
        if (angle == null)
        {
            String msg = Logging.getMessage("nullValue.AngleIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }
        return fromAxisAngle(angle, axisX, axisY, axisZ, true);
    }

    private static Quaternion fromAxisAngle(Angle angle, double axisX, double axisY, double axisZ, boolean normalize)
    {
        if (angle == null)
        {
            String msg = Logging.getMessage("nullValue.AngleIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        if (normalize)
        {
            double length = Math.sqrt((axisX * axisX) + (axisY * axisY) + (axisZ * axisZ));
            if (!isZero(length) && (length != 1.0))
            {
                axisX /= length;
                axisY /= length;
                axisZ /= length;
            }
        }

        double s = angle.sinHalfAngle();
        double c = angle.cosHalfAngle();
        return new Quaternion(axisX * s, axisY * s, axisZ * s, c);
    }

    public static Quaternion fromMatrix(Matrix matrix)
    {
        if (matrix == null)
        {
            String msg = Logging.getMessage("nullValue.MatrixIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }
        
        double t = 1.0 + matrix.m11 + matrix.m22 + matrix.m33;
        double x, y, z, w;
        double s;
        final double EPSILON = 0.00000001;
        if (t > EPSILON)
        {
			s = 2.0 * Math.sqrt(t);
			x = (matrix.m32 - matrix.m23) / s;
            y = (matrix.m13 - matrix.m31) / s;
            z = (matrix.m21 - matrix.m12) / s;
            w = s / 4.0;
        }
        else if ((matrix.m11 > matrix.m22) && (matrix.m11 > matrix.m33))
        {
			s = 2.0 * Math.sqrt(1.0 + matrix.m11 - matrix.m22 - matrix.m33);
			x = s / 4.0;
			y = (matrix.m21 + matrix.m12) / s;
			z = (matrix.m13 + matrix.m31) / s;
			w = (matrix.m32 - matrix.m23) / s;
		}
        else if (matrix.m22 > matrix.m33)
        {
			s = 2.0 * Math.sqrt(1.0 + matrix.m22 - matrix.m11 - matrix.m33);
			x = (matrix.m21 + matrix.m12) / s;
			y = s / 4.0;
			z = (matrix.m32 + matrix.m23) / s;
			w = (matrix.m13 - matrix.m31) / s;
		}
        else
        {
			s = 2.0 * Math.sqrt(1.0 + matrix.m33 - matrix.m11 - matrix.m22);
			x = (matrix.m13 + matrix.m31) / s;
			y = (matrix.m32 + matrix.m23) / s;
			z = s / 4.0;
			w = (matrix.m21 - matrix.m12) / s;
		}
        return new Quaternion(x, y, z, w);
    }

    /**
     * Returns a Quaternion created from three Euler angle rotations. The angles represent rotation about their
     * respective unit-axes. The angles are applied in the order X, Y, Z.
     * Angles can be extracted by calling {@link #getRotationX}, {@link #getRotationY}, {@link #getRotationZ}.
     *
     * @param x Angle rotation about unit-X axis.
     * @param y Angle rotation about unit-Y axis.
     * @param z Angle rotation about unit-Z axis.
     * @return Quaternion representation of the combined X-Y-Z rotation.
     */
    public static Quaternion fromRotationXYZ(Angle x, Angle y, Angle z)
    {
        if (x == null || y == null || z == null)
        {
            String msg = Logging.getMessage("nullValue.AngleIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        double cx = x.cosHalfAngle();
        double cy = y.cosHalfAngle();
        double cz = z.cosHalfAngle();
        double sx = x.sinHalfAngle();
        double sy = y.sinHalfAngle();
        double sz = z.sinHalfAngle();

        // The order in which the three Euler angles are applied is critical. This can be thought of as multiplying
        // three quaternions together, one for each Euler angle (and corresponding unit axis). Like matrices,
        // quaternions affect vectors in reverse order. For example, suppose we construct a quaternion
        //     Q = (QX * QX) * QZ
        // then transform some vector V by Q. This can be thought of as first transforming V by QZ, then QY, and
        // finally by QX. This means that the order of quaternion multiplication is the reverse of the order in which
        // the Euler angles are applied.
        //
        // The ordering below refers to the order in which angles are applied.
        //
        // QX = (sx, 0,  0,  cx)
        // QY = (0,  sy, 0,  cy)
        // QZ = (0,  0,  sz, cz)
        //
        // 1. XYZ Ordering
        // (QZ * QY * QX)
        // qw = (cx * cy * cz) + (sx * sy * sz);
        // qx = (sx * cy * cz) - (cx * sy * sz);
        // qy = (cx * sy * cz) + (sx * cy * sz);
        // qz = (cx * cy * sz) - (sx * sy * cz);
        //
        // 2. ZYX Ordering
        // (QX * QY * QZ)
        // qw = (cx * cy * cz) - (sx * sy * sz);
        // qx = (sx * cy * cz) + (cx * sy * sz);
        // qy = (cx * sy * cz) - (sx * cy * sz);
        // qz = (cx * cy * sz) + (sx * sy * cz);
        //

        double qw = (cx * cy * cz) + (sx * sy * sz);
        double qx = (sx * cy * cz) - (cx * sy * sz);
        double qy = (cx * sy * cz) + (sx * cy * sz);
        double qz = (cx * cy * sz) - (sx * sy * cz);

        return new Quaternion(qx, qy, qz, qw);
    }

    /**
     * Returns a Quaternion created from latitude and longitude rotations.
     * Latitude and longitude can be extracted from a Quaternion by calling
     * {@link #getLatLon}.
     *
     * @param latitude Angle rotation of latitude.
     * @param longitude Angle rotation of longitude.
     * @return Quaternion representing combined latitude and longitude rotation.
     */
    public static Quaternion fromLatLon(Angle latitude, Angle longitude)
    {
        if (latitude == null || longitude == null)
        {
            String msg = Logging.getMessage("nullValue.AngleIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        double clat = latitude.cosHalfAngle();
        double clon = longitude.cosHalfAngle();
        double slat = latitude.sinHalfAngle();
        double slon = longitude.sinHalfAngle();
        
        // The order in which the lat/lon angles are applied is critical. This can be thought of as multiplying two
        // quaternions together, one for each lat/lon angle. Like matrices, quaternions affect vectors in reverse
        // order. For example, suppose we construct a quaternion
        //     Q = QLat * QLon
        // then transform some vector V by Q. This can be thought of as first transforming V by QLat, then QLon. This
        // means that the order of quaternion multiplication is the reverse of the order in which the lat/lon angles
        // are applied.
        //
        // The ordering below refers to order in which angles are applied.
        //
        // QLat = (0,    slat, 0, clat)
        // QLon = (slon, 0,    0, clon)
        //
        // 1. LatLon Ordering
        // (QLon * QLat)
        // qw = clat * clon;
        // qx = clat * slon;
        // qy = slat * clon;
        // qz = slat * slon;
        //
        // 2. LonLat Ordering
        // (QLat * QLon)
        // qw = clat * clon;
        // qx = clat * slon;
        // qy = slat * clon;
        // qz = - slat * slon;
        //

        double qw = clat * clon;
        double qx = clat * slon;
        double qy = slat * clon;
        double qz = 0.0 - slat * slon;

        return new Quaternion(qx, qy, qz, qw);
    }

    // ============== Arithmetic Functions ======================= //
    // ============== Arithmetic Functions ======================= //
    // ============== Arithmetic Functions ======================= //

    public final Quaternion add(Quaternion quaternion)
    {
        if (quaternion == null)
        {
            String msg = Logging.getMessage("nullValue.QuaternionIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return new Quaternion(
            this.x + quaternion.x,
            this.y + quaternion.y,
            this.z + quaternion.z,
            this.w + quaternion.w);
    }

    public final Quaternion subtract(Quaternion quaternion)
    {
        if (quaternion == null)
        {
            String msg = Logging.getMessage("nullValue.QuaternionIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return new Quaternion(
            this.x - quaternion.x,
            this.y - quaternion.y,
            this.z - quaternion.z,
            this.w - quaternion.w);
    }

    public final Quaternion multiplyComponents(double value)
    {
        return new Quaternion(
            this.x * value,
            this.y * value,
            this.z * value,
            this.w * value);
    }

    public final Quaternion multiply(Quaternion quaternion)
    {
        if (quaternion == null)
        {
            String msg = Logging.getMessage("nullValue.QuaternionIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return new Quaternion(
            (this.w * quaternion.x) + (this.x * quaternion.w) + (this.y * quaternion.z) - (this.z * quaternion.y),
            (this.w * quaternion.y) + (this.y * quaternion.w) + (this.z * quaternion.x) - (this.x * quaternion.z),
            (this.w * quaternion.z) + (this.z * quaternion.w) + (this.x * quaternion.y) - (this.y * quaternion.x),
            (this.w * quaternion.w) - (this.x * quaternion.x) - (this.y * quaternion.y) - (this.z * quaternion.z));
    }

    public final Quaternion divideComponents(double value)
    {
        if (isZero(value))
        {
            String msg = Logging.getMessage("generic.ArgumentOutOfRange", value);
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return new Quaternion(
            this.x / value,
            this.y / value,
            this.z / value,
            this.w / value);
    }

    public final Quaternion divideComponents(Quaternion quaternion)
    {
        if (quaternion == null)
        {
            String msg = Logging.getMessage("nullValue.QuaternionIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return new Quaternion(
            this.x / quaternion.x,
            this.y / quaternion.y,
            this.z / quaternion.z,
            this.w / quaternion.w);
    }

    public final Quaternion getConjugate()
    {
        return new Quaternion(
            0.0 - this.x,
            0.0 - this.y,
            0.0 - this.z,
            this.w);
    }

    public final Quaternion getNegative()
    {
        return new Quaternion(
            0.0 - this.x,
            0.0 - this.y,
            0.0 - this.z,
            0.0 - this.w);   
    }

    // ============== Geometric Functions ======================= //
    // ============== Geometric Functions ======================= //
    // ============== Geometric Functions ======================= //

    public final double getLength()
    {
        return Math.sqrt(this.getLengthSquared());
    }

    public final double getLengthSquared()
    {
        return (this.x * this.x)
             + (this.y * this.y)
             + (this.z * this.z)
             + (this.w * this.w);
    }

    public final Quaternion normalize()
    {
        double length = this.getLength();
        // Vector has zero length.
        if (isZero(length))
        {
            return this;
        }
        else
        {
            return new Quaternion(
                this.x / length,
                this.y / length,
                this.z / length,
                this.w / length);
        }
    }

    public final double dot(Quaternion quaternion)
    {
        if (quaternion == null)
        {
            String msg = Logging.getMessage("nullValue.QuaternionIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        return (this.x * quaternion.x) + (this.y * quaternion.y) + (this.z * quaternion.z) + (this.w * quaternion.w);
    }

    public final Quaternion getInverse()
    {
        double length = this.getLength();
        // Vector has zero length.
        if (isZero(length))
        {
            return this;
        }
        else
        {
            return new Quaternion(
                (0.0 - this.x) / length,
                (0.0 - this.y) / length,
                (0.0 - this.z) / length,
                this.w / length);
        }
    }

    // ============== Mixing Functions ======================= //
    // ============== Mixing Functions ======================= //
    // ============== Mixing Functions ======================= //

    public static Quaternion mix(double amount, Quaternion value1, Quaternion value2)
    {
        if ((value1 == null) || (value2 == null))
        {
            String msg = Logging.getMessage("nullValue.QuaternionIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        if (amount < 0.0)
            return value1;
        else if (amount > 1.0)
            return value2;

        double t1 = 1.0 - amount;
        return new Quaternion(
            (value1.x * t1) + (value2.x * amount),
            (value1.y * t1) + (value2.y * amount),
            (value1.z * t1) + (value2.z * amount),
            (value1.w * t1) + (value2.w * amount));
    }

    public static Quaternion slerp(double amount, Quaternion value1, Quaternion value2)
    {
        if ((value1 == null) || (value2 == null))
        {
            String msg = Logging.getMessage("nullValue.QuaternionIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        if (amount < 0.0)
            return value1;
        else if (amount > 1.0)
            return value2;

        double dot = value1.dot(value2);
        double x2, y2, z2, w2;
        if (dot < 0.0)
        {
            dot = 0.0 - dot;
            x2 = 0.0 - value2.x;
            y2 = 0.0 - value2.y;
            z2 = 0.0 - value2.z;
            w2 = 0.0 - value2.w;
        }
        else
        {
            x2 = value2.x;
            y2 = value2.y;
            z2 = value2.z;
            w2 = value2.w;
        }

        double t1, t2;

        final double EPSILON = 0.0001;
        if ((1.0 - dot) > EPSILON) // standard case (slerp)
        {
            double angle = Math.acos(dot);
            double sinAngle = Math.sin(angle);
            t1 = Math.sin((1.0 - amount) * angle) / sinAngle;
            t2 = Math.sin(amount * angle) / sinAngle;
        }
        else // just lerp
        {
            t1 = 1.0 - amount;
            t2 = amount;
        }

        return new Quaternion(
            (value1.x * t1) + (x2 * t2),
            (value1.y * t1) + (y2 * t2),
            (value1.z * t1) + (z2 * t2),
            (value1.w * t1) + (w2 * t2));
    }

    // ============== Accessor Functions ======================= //
    // ============== Accessor Functions ======================= //
    // ============== Accessor Functions ======================= //

    public final Angle getAngle()
    {
        double w = this.w;

        double length = this.getLength();
        if (!isZero(length) && (length != 1.0))
            w /= length;

        double radians = 2.0 * Math.acos(w);
        if (Double.isNaN(radians))
            return null;

        return Angle.fromRadians(radians);
    }

    public final Vec4 getAxis()
    {
        double x = this.x;
        double y = this.y;
        double z = this.z;

        double length = this.getLength();
        if (!isZero(length) && (length != 1.0))
        {
            x /= length;
            y /= length;
            z /= length;
        }

        double vecLength = Math.sqrt((x * x) + (y * y) + (z * z));
        if (!isZero(vecLength) && (vecLength != 1.0))
        {
            x /= vecLength;
            y /= vecLength;
            z /= vecLength;
        }

        return new Vec4(x, y, z);
    }

    public final Angle getRotationX()
    {
        double radians = Math.atan2((2.0 * this.x * this.w) - (2.0 * this.y * this.z),
                                    1.0 - 2.0 * (this.x * this.x) - 2.0 * (this.z * this.z));
        if (Double.isNaN(radians))
            return null;

        return Angle.fromRadians(radians);
    }

    public final Angle getRotationY()
    {
        double radians = Math.atan2((2.0 * this.y * this.w) - (2.0 * this.x * this.z),
                                    1.0 - (2.0 * this.y * this.y) - (2.0 * this.z * this.z));
        if (Double.isNaN(radians))
            return null;

        return Angle.fromRadians(radians);
    }

    public final Angle getRotationZ()
    {
        double radians = Math.asin((2.0 * this.x * this.y) + (2.0 * this.z * this.w));
        if (Double.isNaN(radians))
            return null;

        return Angle.fromRadians(radians);
    }

    public final LatLon getLatLon()
    {
        double latRadians = Math.asin((2.0 * this.y * this.w) - (2.0 * this.x * this.z));
        double lonRadians = Math.atan2((2.0 * this.y * this.z) + (2.0 * this.x * this.w),
                                       (this.w * this.w) - (this.x * this.x) - (this.y * this.y) + (this.z * this.z));
        if (Double.isNaN(latRadians) || Double.isNaN(lonRadians))
            return null;

        return LatLon.fromRadians(latRadians, lonRadians);
    }

    // ============== Helper Functions ======================= //
    // ============== Helper Functions ======================= //
    // ============== Helper Functions ======================= //

    private static final Double PositiveZero = +0.0d;

    private static final Double NegativeZero = -0.0d;

    private static boolean isZero(double value)
    {
        return (PositiveZero.compareTo(value) == 0)
            || (NegativeZero.compareTo(value) == 0);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy