/** * 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); } /** * @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()}) */ public final float magnitude() { final float magnitudeSQ = magnitudeSquared(); if (magnitudeSQ == 1f) { 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()); } /** * Check if this quaternion represents an identity matrix for rotation, * , ie (0,0,0,1). * * @return true if it is an identity rep., false otherwise */ public final boolean isIdentity() { return w == 1 && x == 0 && y == 0 && z == 0; } /*** * 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 = 0; w = 1; return this; } /** * Normalize a quaternion required if to be used as a rotational quaternion * @return this quaternion for chaining. */ public final Quaternion normalize() { final float norm = magnitude(); if (norm == 0.0f) { setIdentity(); } else { w /= norm; x /= norm; y /= norm; z /= norm; } 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
* @return this quaternion for chaining.
* @see Matrix-FAQ Q50
*/
public final Quaternion invert() {
final float magnitudeSQ = magnitudeSquared();
if ( FloatUtil.equals(1.0f, magnitudeSQ, FloatUtil.EPSILON) ) {
conjugate();
} else {
w /= magnitudeSQ;
x = -x / magnitudeSQ;
y = -y / magnitudeSQ;
z = -z / magnitudeSQ;
}
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); } /*** * 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.isZero(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(..).
*
* q = (s,v) = (v1•v2 , v1 × v2), * angle = angle(v1, v2) = v1•v2 * axis = normal(v1 x v2) ** @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.length(v1) * VectorUtil.length(v2); if ( FloatUtil.isZero(factor, FloatUtil.EPSILON ) ) { return setIdentity(); } else { final float dot = VectorUtil.dot(v1, v2) / factor; // normalize final float theta = FloatUtil.acos(Math.max(-1.0f, Math.min(dot, 1.0f))); // clipping [-1..1] VectorUtil.cross(tmpPivotVec, v1, v2); if ( dot < 0.0f && FloatUtil.isZero( VectorUtil.length(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 ** @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.length(v1) * VectorUtil.length(v2); if ( FloatUtil.isZero(factor, FloatUtil.EPSILON ) ) { return setIdentity(); } else { final float dot = VectorUtil.dot(v1, v2) / factor; // normalize final float theta = FloatUtil.acos(Math.max(-1.0f, Math.min(dot, 1.0f))); // clipping [-1..1] VectorUtil.cross(tmpPivotVec, v1, v2); if ( dot < 0.0f && FloatUtil.isZero( VectorUtil.length(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 *
* If axis == 0,0,0 the quaternion is set to identity. *
* @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.normalize(tmpV3f, vector); return setFromAngleNormalAxis(angle, tmpV3f); } /*** * Initialize this quaternion with given normalized axis vector and rotation angle ** If axis == 0,0,0 the quaternion is set to identity. *
* @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.isZero(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 arrayangradXYZ
in radians.
*
* The angradXYZ
array is laid out in natural order:
*
* The rotations are applied in the given order: *
* The rotations are applied in the given order: *
* 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. * * @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(); final float srecip = norm == 1.0f ? 2.0f : norm > 0.0f ? 2.0f / norm : 0f; 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. * * @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(); final float srecip = norm == 1.0f ? 2.0f : norm > 0.0f ? 2.0f / norm : 0f; 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) { final float norm = magnitudeSquared(); final float s = norm == 1.0f ? 2.0f : norm > 0.0f ? 2.0f / norm : 0f; // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs // will be used 2-4 times each. final float xs = x * s; final float ys = y * s; final float zs = z * s; 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(determinant4f(m) - 1) < epsilon); } private final float determinant4f(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+"]"; } }