/*
* Copyright (c) 2007 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution 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.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
* MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
* ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
* DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
* DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
* SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for use
* in the design, construction, operation or maintenance of any nuclear
* facility.
*
*/
package net.java.joglutils.msg.math;
/** Represents a rotation in 3D space with single-precision
components. Uses a quaternion as the internal representation. */
public class Rotf {
private static float EPSILON = 1.0e-7f;
// Representation is a quaternion. Element 0 is the scalar part (=
// cos(theta/2)), elements 1..3 the imaginary/"vector" part (=
// sin(theta/2) * axis).
private float q0;
private float q1;
private float q2;
private float q3;
/** Default constructor initializes to the identity quaternion */
public Rotf() {
init();
}
public Rotf(Rotf arg) {
set(arg);
}
/** Axis does not need to be normalized but must not be the zero
vector. Angle is in radians. */
public Rotf(Vec3f axis, float angle) {
set(axis, angle);
}
/** Creates a rotation which will rotate vector "from" into vector
"to". */
public Rotf(Vec3f from, Vec3f to) {
set(from, to);
}
/** Re-initialize this quaternion to be the identity quaternion "e"
(i.e., no rotation) */
public void init() {
q0 = 1;
q1 = q2 = q3 = 0;
}
/** Test for "approximate equality" -- performs componentwise test
to see whether difference between all components is less than
epsilon. */
public boolean withinEpsilon(Rotf arg, float epsilon) {
return ((Math.abs(q0 - arg.q0) < epsilon) &&
(Math.abs(q1 - arg.q1) < epsilon) &&
(Math.abs(q2 - arg.q2) < epsilon) &&
(Math.abs(q3 - arg.q3) < epsilon));
}
/** Axis does not need to be normalized but must not be the zero
vector. Angle is in radians. */
public void set(Vec3f axis, float angle) {
float halfTheta = angle / 2.0f;
q0 = (float) Math.cos(halfTheta);
float sinHalfTheta = (float) Math.sin(halfTheta);
Vec3f realAxis = new Vec3f(axis);
realAxis.normalize();
q1 = realAxis.x() * sinHalfTheta;
q2 = realAxis.y() * sinHalfTheta;
q3 = realAxis.z() * sinHalfTheta;
}
/** Sets this rotation to the contents of the passed one. */
public void set(Rotf arg) {
q0 = arg.q0;
q1 = arg.q1;
q2 = arg.q2;
q3 = arg.q3;
}
/** Sets this rotation to that which will rotate vector "from" into
vector "to". from and to do not have to be the same length. */
public void set(Vec3f from, Vec3f to) {
Vec3f axis = from.cross(to);
if (axis.lengthSquared() < EPSILON) {
init();
return;
}
float dotp = from.dot(to);
float denom = from.length() * to.length();
if (denom < EPSILON) {
init();
return;
}
dotp /= denom;
set(axis, (float) Math.acos(dotp));
}
/** Returns angle (in radians) and mutates the given vector to be
the axis. */
public float get(Vec3f axis) {
// FIXME: Is this numerically stable? Is there a better way to
// extract the angle from a quaternion?
// NOTE: remove (float) to illustrate compiler bug
float retval = (float) (2.0f * Math.acos(q0));
axis.set(q1, q2, q3);
float len = axis.length();
if (len == 0.0f) {
axis.set(0, 0, 1);
} else {
axis.scale(1.0f / len);
}
return retval;
}
/** Returns inverse of this rotation; creates a new rotation. */
public Rotf inverse() {
Rotf tmp = new Rotf(this);
tmp.invert();
return tmp;
}
/** Mutate this quaternion to be its inverse. This is equivalent to
the conjugate of the quaternion. */
public void invert() {
q1 = -q1;
q2 = -q2;
q3 = -q3;
}
/** Returns the length of this quaternion in four-space. */
public float length() {
return (float) Math.sqrt(lengthSquared());
}
/** This dotted with this */
public float lengthSquared() {
return (q0 * q0 +
q1 * q1 +
q2 * q2 +
q3 * q3);
}
/** Make this quaternion a unit quaternion again. If you are
composing dozens of quaternions you probably should call this
periodically to ensure that you have a valid rotation. */
public void normalize() {
float len = length();
q0 /= len;
q1 /= len;
q2 /= len;
q3 /= len;
}
/** Returns this * b, in that order; creates new rotation */
public Rotf times(Rotf b) {
Rotf tmp = new Rotf();
tmp.mul(this, b);
return tmp;
}
/** Compose two rotations: this = A * B in that order. NOTE that
because we assume a column vector representation that this
implies that a vector rotated by the cumulative rotation will be
rotated first by B, then A. NOTE: "this" must be different than
both a and b. */
public void mul(Rotf a, Rotf b) {
q0 = (a.q0 * b.q0 - a.q1 * b.q1 -
a.q2 * b.q2 - a.q3 * b.q3);
q1 = (a.q0 * b.q1 + a.q1 * b.q0 +
a.q2 * b.q3 - a.q3 * b.q2);
q2 = (a.q0 * b.q2 + a.q2 * b.q0 -
a.q1 * b.q3 + a.q3 * b.q1);
q3 = (a.q0 * b.q3 + a.q3 * b.q0 +
a.q1 * b.q2 - a.q2 * b.q1);
}
/** Turns this rotation into a 3x3 rotation matrix. NOTE: only
mutates the upper-left 3x3 of the passed Mat4f. Implementation
from B. K. P. Horn's Robot Vision textbook. */
public void toMatrix(Mat4f mat) {
float q00 = q0 * q0;
float q11 = q1 * q1;
float q22 = q2 * q2;
float q33 = q3 * q3;
// Diagonal elements
mat.set(0, 0, q00 + q11 - q22 - q33);
mat.set(1, 1, q00 - q11 + q22 - q33);
mat.set(2, 2, q00 - q11 - q22 + q33);
// 0,1 and 1,0 elements
float q03 = q0 * q3;
float q12 = q1 * q2;
mat.set(0, 1, 2.0f * (q12 - q03));
mat.set(1, 0, 2.0f * (q03 + q12));
// 0,2 and 2,0 elements
float q02 = q0 * q2;
float q13 = q1 * q3;
mat.set(0, 2, 2.0f * (q02 + q13));
mat.set(2, 0, 2.0f * (q13 - q02));
// 1,2 and 2,1 elements
float q01 = q0 * q1;
float q23 = q2 * q3;
mat.set(1, 2, 2.0f * (q23 - q01));
mat.set(2, 1, 2.0f * (q01 + q23));
}
/** Turns the upper left 3x3 of the passed matrix into a rotation.
Implementation from Watt and Watt, Advanced Animation and
Rendering Techniques.
@see Mat4f#getRotation */
public void fromMatrix(Mat4f mat) {
// FIXME: Should reimplement to follow Horn's advice of using
// eigenvector decomposition to handle roundoff error in given
// matrix.
float tr, s;
int i, j, k;
tr = mat.get(0, 0) + mat.get(1, 1) + mat.get(2, 2);
if (tr > 0.0) {
s = (float) Math.sqrt(tr + 1.0f);
q0 = s * 0.5f;
s = 0.5f / s;
q1 = (mat.get(2, 1) - mat.get(1, 2)) * s;
q2 = (mat.get(0, 2) - mat.get(2, 0)) * s;
q3 = (mat.get(1, 0) - mat.get(0, 1)) * s;
} else {
i = 0;
if (mat.get(1, 1) > mat.get(0, 0))
i = 1;
if (mat.get(2, 2) > mat.get(i, i))
i = 2;
j = (i+1)%3;
k = (j+1)%3;
s = (float) Math.sqrt( (mat.get(i, i) - (mat.get(j, j) + mat.get(k, k))) + 1.0f);
setQ(i+1, s * 0.5f);
s = 0.5f / s;
q0 = (mat.get(k, j) - mat.get(j, k)) * s;
setQ(j+1, (mat.get(j, i) + mat.get(i, j)) * s);
setQ(k+1, (mat.get(k, i) + mat.get(i, k)) * s);
}
}
/** Rotate a vector by this quaternion. Implementation is from
Horn's Robot Vision. NOTE: src and dest must be different
vectors. */
public void rotateVector(Vec3f src, Vec3f dest) {
Vec3f qVec = new Vec3f(q1, q2, q3);
Vec3f qCrossX = qVec.cross(src);
Vec3f qCrossXCrossQ = qCrossX.cross(qVec);
qCrossX.scale(2.0f * q0);
qCrossXCrossQ.scale(-2.0f);
dest.add(src, qCrossX);
dest.add(dest, qCrossXCrossQ);
}
/** Rotate a vector by this quaternion, returning newly-allocated result. */
public Vec3f rotateVector(Vec3f src) {
Vec3f tmp = new Vec3f();
rotateVector(src, tmp);
return tmp;
}
public String toString() {
return "(" + q0 + ", " + q1 + ", " + q2 + ", " + q3 + ")";
}
private void setQ(int i, float val) {
switch (i) {
case 0: q0 = val; break;
case 1: q1 = val; break;
case 2: q2 = val; break;
case 3: q3 = val; break;
default: throw new IndexOutOfBoundsException();
}
}
}