/**
* Copyright 2010 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package com.jogamp.opengl.math;
import java.nio.FloatBuffer;
/**
* Quaternion implementation supporting
* Gimbal-Lock free rotations.
*
* All matrix operation provided are in column-major order,
* as specified in the OpenGL fixed function pipeline, i.e. compatibility profile.
* See {@link FloatUtil}.
*
*
* See Matrix-FAQ
*
*
* See euclideanspace.com-Quaternion
*
*/
public class Quaternion {
private float x, y, z, w;
/**
* Quaternion Epsilon, used with equals method to determine if two Quaternions are close enough to be considered equal.
*
* Using {@value}, which is ~20 times {@link FloatUtil#EPSILON}.
*
*/
public static final float ALLOWED_DEVIANCE = 1.0E-6f; // FloatUtil.EPSILON == 1.1920929E-7f; double ALLOWED_DEVIANCE: 1.0E-8f
public Quaternion() {
x = y = z = 0; w = 1;
}
public Quaternion(final Quaternion q) {
set(q);
}
public Quaternion(final float x, final float y, final float z, final float w) {
set(x, y, z, w);
}
/**
* See {@link #magnitude()} for special handling of {@link FloatUtil#EPSILON epsilon},
* which is not applied here.
* @return the squared magnitude of this quaternion.
*/
public final float magnitudeSquared() {
return w*w + x*x + y*y + z*z;
}
/**
* Return the magnitude of this quaternion, i.e. sqrt({@link #magnitude()})
*
* A magnitude of zero shall equal {@link #isIdentity() identity},
* as performed by {@link #normalize()}.
*
*
* Implementation Details:
*
* - returns 0f if {@link #magnitudeSquared()} is {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
* - returns 1f if {@link #magnitudeSquared()} is {@link FloatUtil#isEqual(float, float, float) equals 1f} using {@link FloatUtil#EPSILON epsilon}
*
*
*/
public final float magnitude() {
final float magnitudeSQ = magnitudeSquared();
if ( FloatUtil.isZero(magnitudeSQ, FloatUtil.EPSILON) ) {
return 0f;
}
if ( FloatUtil.isEqual(1f, magnitudeSQ, FloatUtil.EPSILON) ) {
return 1f;
}
return FloatUtil.sqrt(magnitudeSQ);
}
public final float getW() {
return w;
}
public final void setW(final float w) {
this.w = w;
}
public final float getX() {
return x;
}
public final void setX(final float x) {
this.x = x;
}
public final float getY() {
return y;
}
public final void setY(final float y) {
this.y = y;
}
public final float getZ() {
return z;
}
public final void setZ(final float z) {
this.z = z;
}
/**
* Returns the dot product of this quaternion with the given x,y,z and w components.
*/
public final float dot(final float x, final float y, final float z, final float w) {
return this.x * x + this.y * y + this.z * z + this.w * w;
}
/**
* Returns the dot product of this quaternion with the given quaternion
*/
public final float dot(final Quaternion quat) {
return dot(quat.getX(), quat.getY(), quat.getZ(), quat.getW());
}
/**
* Returns true
if this quaternion has identity.
*
* Implementation uses {@link FloatUtil#EPSILON epsilon} to compare
* {@link #getW() W} {@link FloatUtil#isEqual(float, float, float) against 1f} and
* {@link #getX() X}, {@link #getY() Y} and {@link #getZ() Z}
* {@link FloatUtil#isZero(float, float) against zero}.
*
*/
public final boolean isIdentity() {
return FloatUtil.isEqual(1f, w, FloatUtil.EPSILON) && VectorUtil.isZero(x, y, z, FloatUtil.EPSILON);
// return w == 1f && x == 0f && y == 0f && z == 0f;
}
/***
* Set this quaternion to identity (x=0,y=0,z=0,w=1)
* @return this quaternion for chaining.
*/
public final Quaternion setIdentity() {
x = y = z = 0f; w = 1f;
return this;
}
/**
* Normalize a quaternion required if to be used as a rotational quaternion.
*
* Implementation Details:
*
* - {@link #setIdentity()} if {@link #magnitude()} is {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
*
*
* @return this quaternion for chaining.
*/
public final Quaternion normalize() {
final float norm = magnitude();
if ( FloatUtil.isZero(norm, FloatUtil.EPSILON) ) {
setIdentity();
} else {
final float invNorm = 1f/norm;
w *= invNorm;
x *= invNorm;
y *= invNorm;
z *= invNorm;
}
return this;
}
/**
* Conjugates this quaternion [-x, -y, -z, w]
.
* @return this quaternion for chaining.
* @see Matrix-FAQ Q49
*/
public Quaternion conjugate() {
x = -x;
y = -y;
z = -z;
return this;
}
/**
* Invert the quaternion If rotational, will produce a the inverse rotation
*
* Implementation Details:
*
* - {@link #conjugate() conjugates} if {@link #magnitudeSquared()} is is {@link FloatUtil#isEqual(float, float, float) equals 1f} using {@link FloatUtil#EPSILON epsilon}
*
*
* @return this quaternion for chaining.
* @see Matrix-FAQ Q50
*/
public final Quaternion invert() {
final float magnitudeSQ = magnitudeSquared();
if ( FloatUtil.isEqual(1.0f, magnitudeSQ, FloatUtil.EPSILON) ) {
conjugate();
} else {
final float invmsq = 1f/magnitudeSQ;
w *= invmsq;
x = -x * invmsq;
y = -y * invmsq;
z = -z * invmsq;
}
return this;
}
/**
* Set all values of this quaternion using the given src.
* @return this quaternion for chaining.
*/
public final Quaternion set(final Quaternion src) {
this.x = src.x;
this.y = src.y;
this.z = src.z;
this.w = src.w;
return this;
}
/**
* Set all values of this quaternion using the given components.
* @return this quaternion for chaining.
*/
public final Quaternion set(final float x, final float y, final float z, final float w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
return this;
}
/**
* Add a quaternion
*
* @param q quaternion
* @return this quaternion for chaining.
* @see euclideanspace.com-QuaternionAdd
*/
public final Quaternion add(final Quaternion q) {
x += q.x;
y += q.y;
z += q.z;
w += q.w;
return this;
}
/**
* Subtract a quaternion
*
* @param q quaternion
* @return this quaternion for chaining.
* @see euclideanspace.com-QuaternionAdd
*/
public final Quaternion subtract(final Quaternion q) {
x -= q.x;
y -= q.y;
z -= q.z;
w -= q.w;
return this;
}
/**
* Multiply this quaternion by the param quaternion
*
* @param q a quaternion to multiply with
* @return this quaternion for chaining.
* @see Matrix-FAQ Q53
* @see euclideanspace.com-QuaternionMul
*/
public final Quaternion mult(final Quaternion q) {
return set( w * q.x + x * q.w + y * q.z - z * q.y,
w * q.y - x * q.z + y * q.w + z * q.x,
w * q.z + x * q.y - y * q.x + z * q.w,
w * q.w - x * q.x - y * q.y - z * q.z );
}
/**
* Scale this quaternion by a constant
*
* @param n a float constant
* @return this quaternion for chaining.
* @see euclideanspace.com-QuaternionScale
*/
public final Quaternion scale(final float n) {
x *= n;
y *= n;
z *= n;
w *= n;
return this;
}
/**
* Rotate this quaternion by the given angle and axis.
*
* The axis must be a normalized vector.
*
*
* A rotational quaternion is made from the given angle and axis.
*
*
* @param angle in radians
* @param axisX x-coord of rotation axis
* @param axisY y-coord of rotation axis
* @param axisZ z-coord of rotation axis
* @return this quaternion for chaining.
*/
public Quaternion rotateByAngleNormalAxis(final float angle, final float axisX, final float axisY, final float axisZ) {
if( VectorUtil.isZero(axisX, axisY, axisZ, FloatUtil.EPSILON) ) {
// no change
return this;
}
final float halfAngle = 0.5f * angle;
final float sin = FloatUtil.sin(halfAngle);
final float qw = FloatUtil.cos(halfAngle);
final float qx = sin * axisX;
final float qy = sin * axisY;
final float qz = sin * axisZ;
return set( x * qw + y * qz - z * qy + w * qx,
-x * qz + y * qw + z * qx + w * qy,
x * qy - y * qx + z * qw + w * qz,
-x * qx - y * qy - z * qz + w * qw);
}
/**
* Rotate this quaternion around X axis with the given angle in radians
*
* @param angle in radians
* @return this quaternion for chaining.
*/
public Quaternion rotateByAngleX(final float angle) {
final float halfAngle = 0.5f * angle;
final float sin = FloatUtil.sin(halfAngle);
final float cos = FloatUtil.cos(halfAngle);
return set( x * cos + w * sin,
y * cos + z * sin,
-y * sin + z * cos,
-x * sin + w * cos);
}
/**
* Rotate this quaternion around Y axis with the given angle in radians
*
* @param angle in radians
* @return this quaternion for chaining.
*/
public Quaternion rotateByAngleY(final float angle) {
final float halfAngle = 0.5f * angle;
final float sin = FloatUtil.sin(halfAngle);
final float cos = FloatUtil.cos(halfAngle);
return set( x * cos - z * sin,
y * cos + w * sin,
x * sin + z * cos,
-y * sin + w * cos);
}
/**
* Rotate this quaternion around Z axis with the given angle in radians
*
* @param angle in radians
* @return this quaternion for chaining.
*/
public Quaternion rotateByAngleZ(final float angle) {
final float halfAngle = 0.5f * angle;
final float sin = FloatUtil.sin(halfAngle);
final float cos = FloatUtil.cos(halfAngle);
return set( x * cos + y * sin,
-x * sin + y * cos,
z * cos + w * sin,
-z * sin + w * cos);
}
/**
* Rotates this quaternion from the given Euler rotation array angradXYZ
in radians.
*
* The angradXYZ
array is laid out in natural order:
*
* - x - bank
* - y - heading
* - z - attitude
*
*
* For details see {@link #rotateByEuler(float, float, float)}.
* @param angradXYZ euler angel array in radians
* @return this quaternion for chaining.
* @see #rotateByEuler(float, float, float)
*/
public final Quaternion rotateByEuler(final float[] angradXYZ) {
return rotateByEuler(angradXYZ[0], angradXYZ[1], angradXYZ[2]);
}
/**
* Rotates this quaternion from the given Euler rotation angles in radians.
*
* The rotations are applied in the given order and using chained rotation per axis:
*
* - y - heading - {@link #rotateByAngleY(float)}
* - z - attitude - {@link #rotateByAngleZ(float)}
* - x - bank - {@link #rotateByAngleX(float)}
*
*
*
* Implementation Details:
*
* - NOP if all angles are {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
* - result is {@link #normalize()}ed
*
*
* @param bankX the Euler pitch angle in radians. (rotation about the X axis)
* @param headingY the Euler yaw angle in radians. (rotation about the Y axis)
* @param attitudeZ the Euler roll angle in radians. (rotation about the Z axis)
* @return this quaternion for chaining.
* @see #rotateByAngleY(float)
* @see #rotateByAngleZ(float)
* @see #rotateByAngleX(float)
* @see #setFromEuler(float, float, float)
*/
public final Quaternion rotateByEuler(final float bankX, final float headingY, float attitudeZ) {
if ( VectorUtil.isZero(bankX, headingY, attitudeZ, FloatUtil.EPSILON) ) {
return this;
} else {
// setFromEuler muls: ( 8 + 4 ) , + quat muls 24 = 36
// this: 8 + 8 + 8 + 4 = 28 muls
return rotateByAngleY(headingY).rotateByAngleZ(attitudeZ).rotateByAngleX(bankX).normalize();
}
}
/***
* Rotate the given vector by this quaternion
*
* @param vecOut result float[3] storage for rotated vector, maybe equal to vecIn for in-place rotation
* @param vecOutOffset offset in result storage
* @param vecIn float[3] vector to be rotated
* @param vecInOffset offset in vecIn
* @return the given vecOut store for chaining
* @see Matrix-FAQ Q63
*/
public final float[] rotateVector(final float[] vecOut, final int vecOutOffset, final float[] vecIn, final int vecInOffset) {
if ( VectorUtil.isVec3Zero(vecIn, vecInOffset, FloatUtil.EPSILON) ) {
vecOut[0+vecOutOffset] = 0f;
vecOut[1+vecOutOffset] = 0f;
vecOut[2+vecOutOffset] = 0f;
} else {
final float vecX = vecIn[0+vecInOffset];
final float vecY = vecIn[1+vecInOffset];
final float vecZ = vecIn[2+vecInOffset];
final float x_x = x*x;
final float y_y = y*y;
final float z_z = z*z;
final float w_w = w*w;
vecOut[0+vecOutOffset] = w_w * vecX
+ x_x * vecX
- z_z * vecX
- y_y * vecX
+ 2f * ( y*w*vecZ - z*w*vecY + y*x*vecY + z*x*vecZ );
;
vecOut[1+vecOutOffset] = y_y * vecY
- z_z * vecY
+ w_w * vecY
- x_x * vecY
+ 2f * ( x*y*vecX + z*y*vecZ + w*z*vecX - x*w*vecZ );
;
vecOut[2+vecOutOffset] = z_z * vecZ
- y_y * vecZ
- x_x * vecZ
+ w_w * vecZ
+ 2f * ( x*z*vecX + y*z*vecY - w*y*vecX + w*x*vecY );
;
}
return vecOut;
}
/**
* Set this quaternion to a spherical linear interpolation
* between the given start and end quaternions by the given change amount.
*
* Note: Method does not normalize this quaternion!
*
*
* @param a start quaternion
* @param b end quaternion
* @param changeAmnt float between 0 and 1 representing interpolation.
* @return this quaternion for chaining.
* @see euclideanspace.com-QuaternionSlerp
*/
public final Quaternion setSlerp(final Quaternion a, final Quaternion b, final float changeAmnt) {
// System.err.println("Slerp.0: A "+a+", B "+b+", t "+changeAmnt);
if (changeAmnt == 0.0f) {
set(a);
} else if (changeAmnt == 1.0f) {
set(b);
} else {
float bx = b.x;
float by = b.y;
float bz = b.z;
float bw = b.w;
// Calculate angle between them (quat dot product)
float cosHalfTheta = a.x * bx + a.y * by + a.z * bz + a.w * bw;
final float scale0, scale1;
if( cosHalfTheta >= 0.95f ) {
// quaternions are close, just use linear interpolation
scale0 = 1.0f - changeAmnt;
scale1 = changeAmnt;
// System.err.println("Slerp.1: Linear Interpol; cosHalfTheta "+cosHalfTheta);
} else if ( cosHalfTheta <= -0.99f ) {
// the quaternions are nearly opposite,
// we can pick any axis normal to a,b to do the rotation
scale0 = 0.5f;
scale1 = 0.5f;
// System.err.println("Slerp.2: Any; cosHalfTheta "+cosHalfTheta);
} else {
// System.err.println("Slerp.3: cosHalfTheta "+cosHalfTheta);
if( cosHalfTheta <= -FloatUtil.EPSILON ) { // FIXME: .. or shall we use the upper bound 'cosHalfTheta < FloatUtil.EPSILON' ?
// Negate the second quaternion and the result of the dot product (Inversion)
bx *= -1f;
by *= -1f;
bz *= -1f;
bw *= -1f;
cosHalfTheta *= -1f;
// System.err.println("Slerp.4: Inverted cosHalfTheta "+cosHalfTheta);
}
final float halfTheta = FloatUtil.acos(cosHalfTheta);
final float sinHalfTheta = FloatUtil.sqrt(1.0f - cosHalfTheta*cosHalfTheta);
// if theta = 180 degrees then result is not fully defined
// we could rotate around any axis normal to qa or qb
if ( Math.abs(sinHalfTheta) < 0.001f ){ // fabs is floating point absolute
scale0 = 0.5f;
scale1 = 0.5f;
// throw new InternalError("XXX"); // FIXME should not be reached due to above inversion ?
} else {
// Calculate the scale for q1 and q2, according to the angle and
// it's sine value
scale0 = FloatUtil.sin((1f - changeAmnt) * halfTheta) / sinHalfTheta;
scale1 = FloatUtil.sin(changeAmnt * halfTheta) / sinHalfTheta;
}
}
x = a.x * scale0 + bx * scale1;
y = a.y * scale0 + by * scale1;
z = a.z * scale0 + bz * scale1;
w = a.w * scale0 + bw * scale1;
}
// System.err.println("Slerp.X: Result "+this);
return this;
}
/**
* Set this quaternion to equal the rotation required
* to point the z-axis at direction and the y-axis to up.
*
* Implementation generates a 3x3 matrix
* and is equal with ProjectFloat's lookAt(..).
*
* Implementation Details:
*
* - result is {@link #normalize()}ed
*
*
* @param directionIn where to look at
* @param upIn a vector indicating the local up direction.
* @param xAxisOut vector storing the orthogonal x-axis of the coordinate system.
* @param yAxisOut vector storing the orthogonal y-axis of the coordinate system.
* @param zAxisOut vector storing the orthogonal z-axis of the coordinate system.
* @return this quaternion for chaining.
* @see euclideanspace.com-LookUp
*/
public Quaternion setLookAt(final float[] directionIn, final float[] upIn,
final float[] xAxisOut, final float[] yAxisOut, final float[] zAxisOut) {
// Z = norm(dir)
VectorUtil.normalizeVec3(zAxisOut, directionIn);
// X = upIn x Z
// (borrow yAxisOut for upNorm)
VectorUtil.normalizeVec3(yAxisOut, upIn);
VectorUtil.crossVec3(xAxisOut, yAxisOut, zAxisOut);
VectorUtil.normalizeVec3(xAxisOut);
// Y = Z x X
//
VectorUtil.crossVec3(yAxisOut, zAxisOut, xAxisOut);
VectorUtil.normalizeVec3(yAxisOut);
/**
final float m00 = xAxisOut[0];
final float m01 = yAxisOut[0];
final float m02 = zAxisOut[0];
final float m10 = xAxisOut[1];
final float m11 = yAxisOut[1];
final float m12 = zAxisOut[1];
final float m20 = xAxisOut[2];
final float m21 = yAxisOut[2];
final float m22 = zAxisOut[2];
*/
return setFromAxes(xAxisOut, yAxisOut, zAxisOut).normalize();
}
//
// Conversions
//
/**
* Initialize this quaternion from two vectors
*
* q = (s,v) = (v1•v2 , v1 × v2),
* angle = angle(v1, v2) = v1•v2
* axis = normal(v1 x v2)
*
*
* Implementation Details:
*
* - {@link #setIdentity()} if square vector-length is {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
*
*
* @param v1 not normalized
* @param v2 not normalized
* @param tmpPivotVec float[3] temp storage for cross product
* @param tmpNormalVec float[3] temp storage to normalize vector
* @return this quaternion for chaining.
*/
public final Quaternion setFromVectors(final float[] v1, final float[] v2, final float[] tmpPivotVec, final float[] tmpNormalVec) {
final float factor = VectorUtil.normVec3(v1) * VectorUtil.normVec3(v2);
if ( FloatUtil.isZero(factor, FloatUtil.EPSILON ) ) {
return setIdentity();
} else {
final float dot = VectorUtil.dotVec3(v1, v2) / factor; // normalize
final float theta = FloatUtil.acos(Math.max(-1.0f, Math.min(dot, 1.0f))); // clipping [-1..1]
VectorUtil.crossVec3(tmpPivotVec, v1, v2);
if ( dot < 0.0f && FloatUtil.isZero( VectorUtil.normVec3(tmpPivotVec), FloatUtil.EPSILON ) ) {
// Vectors parallel and opposite direction, therefore a rotation of 180 degrees about any vector
// perpendicular to this vector will rotate vector a onto vector b.
//
// The following guarantees the dot-product will be 0.0.
int dominantIndex;
if (Math.abs(v1[0]) > Math.abs(v1[1])) {
if (Math.abs(v1[0]) > Math.abs(v1[2])) {
dominantIndex = 0;
} else {
dominantIndex = 2;
}
} else {
if (Math.abs(v1[1]) > Math.abs(v1[2])) {
dominantIndex = 1;
} else {
dominantIndex = 2;
}
}
tmpPivotVec[dominantIndex] = -v1[(dominantIndex + 1) % 3];
tmpPivotVec[(dominantIndex + 1) % 3] = v1[dominantIndex];
tmpPivotVec[(dominantIndex + 2) % 3] = 0f;
}
return setFromAngleAxis(theta, tmpPivotVec, tmpNormalVec);
}
}
/**
* Initialize this quaternion from two normalized vectors
*
* q = (s,v) = (v1•v2 , v1 × v2),
* angle = angle(v1, v2) = v1•v2
* axis = v1 x v2
*
*
* Implementation Details:
*
* - {@link #setIdentity()} if square vector-length is {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
*
*
* @param v1 normalized
* @param v2 normalized
* @param tmpPivotVec float[3] temp storage for cross product
* @return this quaternion for chaining.
*/
public final Quaternion setFromNormalVectors(final float[] v1, final float[] v2, final float[] tmpPivotVec) {
final float factor = VectorUtil.normVec3(v1) * VectorUtil.normVec3(v2);
if ( FloatUtil.isZero(factor, FloatUtil.EPSILON ) ) {
return setIdentity();
} else {
final float dot = VectorUtil.dotVec3(v1, v2) / factor; // normalize
final float theta = FloatUtil.acos(Math.max(-1.0f, Math.min(dot, 1.0f))); // clipping [-1..1]
VectorUtil.crossVec3(tmpPivotVec, v1, v2);
if ( dot < 0.0f && FloatUtil.isZero( VectorUtil.normVec3(tmpPivotVec), FloatUtil.EPSILON ) ) {
// Vectors parallel and opposite direction, therefore a rotation of 180 degrees about any vector
// perpendicular to this vector will rotate vector a onto vector b.
//
// The following guarantees the dot-product will be 0.0.
int dominantIndex;
if (Math.abs(v1[0]) > Math.abs(v1[1])) {
if (Math.abs(v1[0]) > Math.abs(v1[2])) {
dominantIndex = 0;
} else {
dominantIndex = 2;
}
} else {
if (Math.abs(v1[1]) > Math.abs(v1[2])) {
dominantIndex = 1;
} else {
dominantIndex = 2;
}
}
tmpPivotVec[dominantIndex] = -v1[(dominantIndex + 1) % 3];
tmpPivotVec[(dominantIndex + 1) % 3] = v1[dominantIndex];
tmpPivotVec[(dominantIndex + 2) % 3] = 0f;
}
return setFromAngleNormalAxis(theta, tmpPivotVec);
}
}
/***
* Initialize this quaternion with given non-normalized axis vector and rotation angle
*
* Implementation Details:
*
* - {@link #setIdentity()} if axis is {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
*
*
* @param angle rotation angle (rads)
* @param vector axis vector not normalized
* @param tmpV3f float[3] temp storage to normalize vector
* @return this quaternion for chaining.
*
* @see Matrix-FAQ Q56
* @see #toAngleAxis(float[])
*/
public final Quaternion setFromAngleAxis(final float angle, final float[] vector, final float[] tmpV3f) {
VectorUtil.normalizeVec3(tmpV3f, vector);
return setFromAngleNormalAxis(angle, tmpV3f);
}
/***
* Initialize this quaternion with given normalized axis vector and rotation angle
*
* Implementation Details:
*
* - {@link #setIdentity()} if axis is {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
*
*
* @param angle rotation angle (rads)
* @param vector axis vector normalized
* @return this quaternion for chaining.
*
* @see Matrix-FAQ Q56
* @see #toAngleAxis(float[])
*/
public final Quaternion setFromAngleNormalAxis(final float angle, final float[] vector) {
if ( VectorUtil.isVec3Zero(vector, 0, FloatUtil.EPSILON) ) {
setIdentity();
} else {
final float halfangle = angle * 0.5f;
final float sin = FloatUtil.sin(halfangle);
x = vector[0] * sin;
y = vector[1] * sin;
z = vector[2] * sin;
w = FloatUtil.cos(halfangle);
}
return this;
}
/**
* Transform the rotational quaternion to axis based rotation angles
*
* @param axis float[3] storage for computed axis
* @return the rotation angle in radians
* @see #setFromAngleAxis(float, float[], float[])
*/
public final float toAngleAxis(final float[] axis) {
final float sqrLength = x*x + y*y + z*z;
float angle;
if ( FloatUtil.isZero(sqrLength, FloatUtil.EPSILON) ) { // length is ~0
angle = 0.0f;
axis[0] = 1.0f;
axis[1] = 0.0f;
axis[2] = 0.0f;
} else {
angle = FloatUtil.acos(w) * 2.0f;
final float invLength = 1.0f / FloatUtil.sqrt(sqrLength);
axis[0] = x * invLength;
axis[1] = y * invLength;
axis[2] = z * invLength;
}
return angle;
}
/**
* Initializes this quaternion from the given Euler rotation array angradXYZ
in radians.
*
* The angradXYZ
array is laid out in natural order:
*
* - x - bank
* - y - heading
* - z - attitude
*
*
* For details see {@link #setFromEuler(float, float, float)}.
* @param angradXYZ euler angel array in radians
* @return this quaternion for chaining.
* @see #setFromEuler(float, float, float)
*/
public final Quaternion setFromEuler(final float[] angradXYZ) {
return setFromEuler(angradXYZ[0], angradXYZ[1], angradXYZ[2]);
}
/**
* Initializes this quaternion from the given Euler rotation angles in radians.
*
* The rotations are applied in the given order:
*
* - y - heading
* - z - attitude
* - x - bank
*
*
*
* Implementation Details:
*
* - {@link #setIdentity()} if all angles are {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
* - result is {@link #normalize()}ed
*
*
* @param bankX the Euler pitch angle in radians. (rotation about the X axis)
* @param headingY the Euler yaw angle in radians. (rotation about the Y axis)
* @param attitudeZ the Euler roll angle in radians. (rotation about the Z axis)
* @return this quaternion for chaining.
*
* @see Matrix-FAQ Q60
* @see Gems
* @see euclideanspace.com-eulerToQuaternion
* @see #toEuler(float[])
*/
public final Quaternion setFromEuler(final float bankX, final float headingY, float attitudeZ) {
if ( VectorUtil.isZero(bankX, headingY, attitudeZ, FloatUtil.EPSILON) ) {
return setIdentity();
} else {
float angle = headingY * 0.5f;
final float sinHeadingY = FloatUtil.sin(angle);
final float cosHeadingY = FloatUtil.cos(angle);
angle = attitudeZ * 0.5f;
final float sinAttitudeZ = FloatUtil.sin(angle);
final float cosAttitudeZ = FloatUtil.cos(angle);
angle = bankX * 0.5f;
final float sinBankX = FloatUtil.sin(angle);
final float cosBankX = FloatUtil.cos(angle);
// variables used to reduce multiplication calls.
final float cosHeadingXcosAttitude = cosHeadingY * cosAttitudeZ;
final float sinHeadingXsinAttitude = sinHeadingY * sinAttitudeZ;
final float cosHeadingXsinAttitude = cosHeadingY * sinAttitudeZ;
final float sinHeadingXcosAttitude = sinHeadingY * cosAttitudeZ;
w = cosHeadingXcosAttitude * cosBankX - sinHeadingXsinAttitude * sinBankX;
x = cosHeadingXcosAttitude * sinBankX + sinHeadingXsinAttitude * cosBankX;
y = sinHeadingXcosAttitude * cosBankX + cosHeadingXsinAttitude * sinBankX;
z = cosHeadingXsinAttitude * cosBankX - sinHeadingXcosAttitude * sinBankX;
return normalize();
}
}
/**
* Transform this quaternion to Euler rotation angles in radians (pitchX, yawY and rollZ).
*
* @param result the float[] array storing the computed angles.
* @return the double[] array, filled with heading, attitude and bank in that order..
* @see euclideanspace.com-quaternionToEuler
* @see #setFromEuler(float, float, float)
*/
public float[] toEuler(final float[] result) {
final float sqw = w*w;
final float sqx = x*x;
final float sqy = y*y;
final float sqz = z*z;
final float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise
// is correction factor
final float test = x*y + z*w;
if (test > 0.499f * unit) { // singularity at north pole
result[0] = 0f;
result[1] = 2f * FloatUtil.atan2(x, w);
result[2] = FloatUtil.HALF_PI;
} else if (test < -0.499f * unit) { // singularity at south pole
result[0] = 0f;
result[1] = -2 * FloatUtil.atan2(x, w);
result[2] = -FloatUtil.HALF_PI;
} else {
result[0] = FloatUtil.atan2(2f * x * w - 2 * y * z, -sqx + sqy - sqz + sqw);
result[1] = FloatUtil.atan2(2f * y * w - 2 * x * z, sqx - sqy - sqz + sqw);
result[2] = FloatUtil.asin( 2f * test / unit);
}
return result;
}
/**
* Initializes this quaternion from a 4x4 column rotation matrix
*
* See Graphics Gems Code,
* MatrixTrace.
*
*
* Buggy Matrix-FAQ Q55
*
*
* @param m 4x4 column matrix
* @return this quaternion for chaining.
* @see #toMatrix(float[], int)
*/
public final Quaternion setFromMatrix(final float[] m, final int m_off) {
return setFromMatrix(m[0+0*4+m_off], m[0+1*4+m_off], m[0+2*4+m_off],
m[1+0*4+m_off], m[1+1*4+m_off], m[1+2*4+m_off],
m[2+0*4+m_off], m[2+1*4+m_off], m[2+2*4+m_off]);
}
/**
* Initializes this quaternion from a 4x4 column rotation matrix
*
* See Graphics Gems Code,
* MatrixTrace.
*
*
* Buggy Matrix-FAQ Q55
*
*
* @param m 4x4 column matrix
* @return this quaternion for chaining.
* @see #toMatrix(FloatBuffer)
*/
public final Quaternion setFromMatrix(final FloatBuffer m) {
final int m_off = m.position();
return setFromMatrix(m.get(0+0*4+m_off), m.get(0+1*4+m_off), m.get(0+2*4+m_off),
m.get(1+0*4+m_off), m.get(1+1*4+m_off), m.get(1+2*4+m_off),
m.get(2+0*4+m_off), m.get(2+1*4+m_off), m.get(2+2*4+m_off));
}
/**
* Compute the quaternion from a 3x3 column rotation matrix
*
* See Graphics Gems Code,
* MatrixTrace.
*
*
* Buggy Matrix-FAQ Q55
*
*
* @return this quaternion for chaining.
* @see #toMatrix(float[], int)
*/
public Quaternion setFromMatrix(final float m00, final float m01, final float m02,
final float m10, final float m11, final float m12,
final float m20, final float m21, final float m22) {
// Note: Other implementations uses 'T' w/o '+1f' and compares 'T >= 0' while adding missing 1f in sqrt expr.
// However .. this causes setLookAt(..) to fail and actually violates the 'trace definition'.
// The trace T is the sum of the diagonal elements; see
// http://mathworld.wolfram.com/MatrixTrace.html
final float T = m00 + m11 + m22 + 1f;
// System.err.println("setFromMatrix.0 T "+T+", m00 "+m00+", m11 "+m11+", m22 "+m22);
if ( T > 0f ) {
// System.err.println("setFromMatrix.1");
final float S = 0.5f / FloatUtil.sqrt(T); // S = 1 / ( 2 t )
w = 0.25f / S; // w = 1 / ( 4 S ) = t / 2
x = ( m21 - m12 ) * S;
y = ( m02 - m20 ) * S;
z = ( m10 - m01 ) * S;
} else if ( m00 > m11 && m00 > m22) {
// System.err.println("setFromMatrix.2");
final float S = 0.5f / FloatUtil.sqrt(1.0f + m00 - m11 - m22); // S=4*qx
w = ( m21 - m12 ) * S;
x = 0.25f / S;
y = ( m10 + m01 ) * S;
z = ( m02 + m20 ) * S;
} else if ( m11 > m22 ) {
// System.err.println("setFromMatrix.3");
final float S = 0.5f / FloatUtil.sqrt(1.0f + m11 - m00 - m22); // S=4*qy
w = ( m02 - m20 ) * S;
x = ( m20 + m01 ) * S;
y = 0.25f / S;
z = ( m21 + m12 ) * S;
} else {
// System.err.println("setFromMatrix.3");
final float S = 0.5f / FloatUtil.sqrt(1.0f + m22 - m00 - m11); // S=4*qz
w = ( m10 - m01 ) * S;
x = ( m02 + m20 ) * S;
y = ( m21 + m12 ) * S;
z = 0.25f / S;
}
return this;
}
/**
* Transform this quaternion to a normalized 4x4 column matrix representing the rotation.
*
* Implementation Details:
*
* - makes identity matrix if {@link #magnitudeSquared()} is {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
*
*
*
* @param matrix float[16] store for the resulting normalized column matrix 4x4
* @param mat_offset
* @return the given matrix store
* @see Matrix-FAQ Q54
* @see #setFromMatrix(float[], int)
*/
public final float[] toMatrix(final float[] matrix, final int mat_offset) {
// pre-multiply scaled-reciprocal-magnitude to reduce multiplications
final float norm = magnitudeSquared();
if ( FloatUtil.isZero(norm, FloatUtil.EPSILON) ) {
// identity matrix -> srecip = 0f
FloatUtil.makeIdentityf(matrix, mat_offset);
return matrix;
}
final float srecip;
if ( FloatUtil.isEqual(1f, norm, FloatUtil.EPSILON) ) {
srecip = 2f;
} else {
srecip = 2.0f / norm;
}
final float xs = srecip * x;
final float ys = srecip * y;
final float zs = srecip * z;
final float xx = x * xs;
final float xy = x * ys;
final float xz = x * zs;
final float xw = xs * w;
final float yy = y * ys;
final float yz = y * zs;
final float yw = ys * w;
final float zz = z * zs;
final float zw = zs * w;
matrix[0+0*4+mat_offset] = 1f - ( yy + zz );
matrix[0+1*4+mat_offset] = ( xy - zw );
matrix[0+2*4+mat_offset] = ( xz + yw );
matrix[0+3*4+mat_offset] = 0f;
matrix[1+0*4+mat_offset] = ( xy + zw );
matrix[1+1*4+mat_offset] = 1f - ( xx + zz );
matrix[1+2*4+mat_offset] = ( yz - xw );
matrix[1+3*4+mat_offset] = 0f;
matrix[2+0*4+mat_offset] = ( xz - yw );
matrix[2+1*4+mat_offset] = ( yz + xw );
matrix[2+2*4+mat_offset] = 1f - ( xx + yy );
matrix[2+3*4+mat_offset] = 0f;
matrix[3+0*4+mat_offset] = 0f;
matrix[3+1*4+mat_offset] = 0f;
matrix[3+2*4+mat_offset] = 0f;
matrix[3+3*4+mat_offset] = 1f;
return matrix;
}
/**
* Transform this quaternion to a normalized 4x4 column matrix representing the rotation.
*
* Implementation Details:
*
* - makes identity matrix if {@link #magnitudeSquared()} is {@link FloatUtil#isZero(float, float) is zero} using {@link FloatUtil#EPSILON epsilon}
*
*
*
* @param matrix FloatBuffer store for the resulting normalized column matrix 4x4
* @param mat_offset
* @return the given matrix store
* @see Matrix-FAQ Q54
* @see #setFromMatrix(FloatBuffer)
*/
public final FloatBuffer toMatrix(final FloatBuffer matrix) {
final int mat_offset = matrix.position();
// pre-multipliy scaled-reciprocal-magnitude to reduce multiplications
final float norm = magnitudeSquared();
if ( FloatUtil.isZero(norm, FloatUtil.EPSILON) ) {
// identity matrix -> srecip = 0f
FloatUtil.makeIdentityf(matrix);
return matrix;
}
final float srecip;
if ( FloatUtil.isEqual(1f, norm, FloatUtil.EPSILON) ) {
srecip = 2f;
} else {
srecip = 2.0f / norm;
}
final float xs = srecip * x;
final float ys = srecip * y;
final float zs = srecip * z;
final float xx = x * xs;
final float xy = x * ys;
final float xz = x * zs;
final float xw = xs * w;
final float yy = y * ys;
final float yz = y * zs;
final float yw = ys * w;
final float zz = z * zs;
final float zw = zs * w;
matrix.put(0+0*4+mat_offset, 1f - ( yy + zz ));
matrix.put(0+1*4+mat_offset, ( xy - zw ));
matrix.put(0+2*4+mat_offset, ( xz + yw ));
matrix.put(0+3*4+mat_offset, 0f);
matrix.put(1+0*4+mat_offset, ( xy + zw ));
matrix.put(1+1*4+mat_offset, 1f - ( xx + zz ));
matrix.put(1+2*4+mat_offset, ( yz - xw ));
matrix.put(1+3*4+mat_offset, 0f);
matrix.put(2+0*4+mat_offset, ( xz - yw ));
matrix.put(2+1*4+mat_offset, ( yz + xw ));
matrix.put(2+2*4+mat_offset, 1f - ( xx + yy ));
matrix.put(2+3*4+mat_offset, 0f);
matrix.put(3+0*4+mat_offset, 0f);
matrix.put(3+1*4+mat_offset, 0f);
matrix.put(3+2*4+mat_offset, 0f);
matrix.put(3+3*4+mat_offset, 1f);
return matrix;
}
/**
* @param index the 3x3 rotation matrix column to retrieve from this quaternion (normalized). Must be between 0 and 2.
* @param result the vector object to store the result in.
* @return the result column-vector for chaining.
*/
public float[] copyMatrixColumn(final int index, final float[] result, final int resultOffset) {
// pre-multipliy scaled-reciprocal-magnitude to reduce multiplications
final float norm = magnitudeSquared();
final float srecip;
if ( FloatUtil.isZero(norm, FloatUtil.EPSILON) ) {
srecip= 0f;
} else if ( FloatUtil.isEqual(1f, norm, FloatUtil.EPSILON) ) {
srecip= 2f;
} else {
srecip= 2.0f / norm;
}
// compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
// will be used 2-4 times each.
final float xs = x * srecip;
final float ys = y * srecip;
final float zs = z * srecip;
final float xx = x * xs;
final float xy = x * ys;
final float xz = x * zs;
final float xw = w * xs;
final float yy = y * ys;
final float yz = y * zs;
final float yw = w * ys;
final float zz = z * zs;
final float zw = w * zs;
// using s=2/norm (instead of 1/norm) saves 3 multiplications by 2 here
switch (index) {
case 0:
result[0+resultOffset] = 1.0f - (yy + zz);
result[1+resultOffset] = xy + zw;
result[2+resultOffset] = xz - yw;
break;
case 1:
result[0+resultOffset] = xy - zw;
result[1+resultOffset] = 1.0f - (xx + zz);
result[2+resultOffset] = yz + xw;
break;
case 2:
result[0+resultOffset] = xz + yw;
result[1+resultOffset] = yz - xw;
result[2+resultOffset] = 1.0f - (xx + yy);
break;
default:
throw new IllegalArgumentException("Invalid column index. " + index);
}
return result;
}
/**
* Initializes this quaternion to represent a rotation formed by the given three orthogonal axes.
*
* No validation whether the axes are orthogonal is performed.
*
*
* @param xAxis vector representing the orthogonal x-axis of the coordinate system.
* @param yAxis vector representing the orthogonal y-axis of the coordinate system.
* @param zAxis vector representing the orthogonal z-axis of the coordinate system.
* @return this quaternion for chaining.
*/
public final Quaternion setFromAxes(final float[] xAxis, final float[] yAxis, final float[] zAxis) {
return setFromMatrix(xAxis[0], yAxis[0], zAxis[0],
xAxis[1], yAxis[1], zAxis[1],
xAxis[2], yAxis[2], zAxis[2]);
}
/**
* Extracts this quaternion's orthogonal rotation axes.
*
* @param xAxis vector representing the orthogonal x-axis of the coordinate system.
* @param yAxis vector representing the orthogonal y-axis of the coordinate system.
* @param zAxis vector representing the orthogonal z-axis of the coordinate system.
* @param tmpMat4 temporary float[4] matrix, used to transform this quaternion to a matrix.
*/
public void toAxes(final float[] xAxis, final float[] yAxis, final float[] zAxis, final float[] tmpMat4) {
toMatrix(tmpMat4, 0);
FloatUtil.copyMatrixColumn(tmpMat4, 0, 2, zAxis, 0);
FloatUtil.copyMatrixColumn(tmpMat4, 0, 1, yAxis, 0);
FloatUtil.copyMatrixColumn(tmpMat4, 0, 0, xAxis, 0);
}
/**
* Check if the the 3x3 matrix (param) is in fact an affine rotational
* matrix
*
* @param m 3x3 column matrix
* @return true if representing a rotational matrix, false otherwise
*/
public final boolean isRotationMatrix3f(float[] m) {
final float epsilon = 0.01f; // margin to allow for rounding errors
if (FloatUtil.abs(m[0] * m[3] + m[3] * m[4] + m[6] * m[7]) > epsilon)
return false;
if (FloatUtil.abs(m[0] * m[2] + m[3] * m[5] + m[6] * m[8]) > epsilon)
return false;
if (FloatUtil.abs(m[1] * m[2] + m[4] * m[5] + m[7] * m[8]) > epsilon)
return false;
if (FloatUtil.abs(m[0] * m[0] + m[3] * m[3] + m[6] * m[6] - 1) > epsilon)
return false;
if (FloatUtil.abs(m[1] * m[1] + m[4] * m[4] + m[7] * m[7] - 1) > epsilon)
return false;
if (FloatUtil.abs(m[2] * m[2] + m[5] * m[5] + m[8] * m[8] - 1) > epsilon)
return false;
return (FloatUtil.abs(determinant3f(m) - 1) < epsilon);
}
private final float determinant3f(float[] m) {
return m[0] * m[4] * m[8] + m[3] * m[7] * m[2] + m[6] * m[1] * m[5]
- m[0] * m[7] * m[5] - m[3] * m[1] * m[8] - m[6] * m[4] * m[2];
}
//
// std java overrides
//
/**
* @param o the object to compare for equality
* @return true if this quaternion and the provided quaternion have roughly the same x, y, z and w values.
*/
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Quaternion)) {
return false;
}
final Quaternion comp = (Quaternion) o;
return Math.abs(x - comp.getX()) <= ALLOWED_DEVIANCE &&
Math.abs(y - comp.getY()) <= ALLOWED_DEVIANCE &&
Math.abs(z - comp.getZ()) <= ALLOWED_DEVIANCE &&
Math.abs(w - comp.getW()) <= ALLOWED_DEVIANCE;
}
public String toString() {
return "Quaternion[x "+x+", y "+y+", z "+z+", w "+w+"]";
}
}