From 70979247aad156418c32959bbf4962f175191ec2 Mon Sep 17 00:00:00 2001
From: Sven Gothel
+ * 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
+ *
+ * Using {@value}, which is ~20 times {@link FloatUtil#EPSILON}.
+ *
+ * The axis must be a normalized vector.
+ *
+ * A rotational quaternion is made from the given angle and axis.
+ *
+ * Note: Method does not normalize this quaternion!
+ *
+ * Implementation generates a 3x3 matrix
+ * and is equal with ProjectFloat's lookAt(..).
- * Note: Method does not normalize this quaternion!
+ * If axis == 0,0,0 the quaternion is set to identity.
*
- * See http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/
- * quaternions/slerp/
+ * If axis == 0,0,0 the quaternion is set to identity.
*
+ * The [-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 void add(Quaternion q) {
+ 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 void subtract(Quaternion q) {
+ public final Quaternion subtract(final Quaternion q) {
x -= q.x;
y -= q.y;
z -= q.z;
+ w -= q.w;
+ return this;
}
/**
- * Divide a quaternion by a constant
+ * Multiply this quaternion by the param quaternion
*
- * @param n a float to divide by
+ * @param q a quaternion to multiply with
+ * @return this quaternion for chaining.
+ * @see Matrix-FAQ Q53
+ * @see euclideanspace.com-QuaternionMul
*/
- public void divide(float n) {
- x /= n;
- y /= n;
- z /= n;
+ 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 );
}
/**
- * Multiply this quaternion by the param quaternion
+ * Scale this quaternion by a constant
*
- * @param q a quaternion to multiply with
+ * @param n a float constant
+ * @return this quaternion for chaining.
+ * @see euclideanspace.com-QuaternionScale
*/
- public void mult(Quaternion q) {
- final float w1 = w * q.w - x * q.x - y * q.y - z * q.z;
+ public final Quaternion scale(final float n) {
+ x *= n;
+ y *= n;
+ z *= n;
+ w *= n;
+ return this;
+ }
- final float x1 = w * q.x + x * q.w + y * q.z - z * q.y;
- final float y1 = w * q.y - x * q.z + y * q.w + z * q.x;
- final float z1 = w * q.z + x * q.y - y * q.x + z * q.w;
+ /**
+ * Rotate this quaternion by the given angle and axis.
+ *
+ *
+ * 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);
+ }
}
/**
- * Set this quaternion from a Sphereical interpolation of two param
- * quaternion, used mostly for rotational animation.
+ * 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
* angradXYZ
in radians.
+ * 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 void setIdentity() { - x = y = z = 0; - w = 1; + 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]); } /** - * compute the quaternion from a 3x3 column matrix + * Initializes this quaternion from a 4x4 column rotation matrix + *
+ * See Graphics Gems Code,
+ * MatrixTrace.
+ *
+ * Buggy Matrix-FAQ Q55 + *
* - * @param m 3x3 column matrix + * @param m 4x4 column matrix + * @return this quaternion for chaining. + * @see #toMatrix(FloatBuffer) */ - public void setFromMatrix(float[] m) { - final float T = m[0] + m[4] + m[8] + 1; - if (T > 0) { - final float S = 0.5f / FloatUtil.sqrt(T); - w = 0.25f / S; - x = (m[5] - m[7]) * S; - y = (m[6] - m[2]) * S; - z = (m[1] - m[3]) * S; + 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 { - if ((m[0] > m[4]) && (m[0] > m[8])) { - final float S = FloatUtil.sqrt(1.0f + m[0] - m[4] - m[8]) * 2f; // S=4*qx - w = (m[7] - m[5]) / S; - x = 0.25f * S; - y = (m[3] + m[1]) / S; - z = (m[6] + m[2]) / S; - } else if (m[4] > m[8]) { - final float S = FloatUtil.sqrt(1.0f + m[4] - m[0] - m[8]) * 2f; // S=4*qy - w = (m[6] - m[2]) / S; - x = (m[3] + m[1]) / S; - y = 0.25f * S; - z = (m[7] + m[5]) / S; - } else { - final float S = FloatUtil.sqrt(1.0f + m[8] - m[0] - m[4]) * 2f; // S=4*qz - w = (m[3] - m[1]) / S; - x = (m[6] + m[2]) / S; - y = (m[7] + m[5]) / S; - z = 0.25f * S; - } + // 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); } /** @@ -391,7 +1083,7 @@ public class Quaternion { * @param m 3x3 column matrix * @return true if representing a rotational matrix, false otherwise */ - public boolean isRotationMatrix(float[] m) { + 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; @@ -405,11 +1097,38 @@ public class Quaternion { return false; if (FloatUtil.abs(m[2] * m[2] + m[5] * m[5] + m[8] * m[8] - 1) > epsilon) return false; - return (FloatUtil.abs(determinant(m) - 1) < epsilon); + return (FloatUtil.abs(determinant4f(m) - 1) < epsilon); } - private float determinant(float[] m) { + 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+"]"; + } } diff --git a/src/jogl/classes/com/jogamp/opengl/util/PMVMatrix.java b/src/jogl/classes/com/jogamp/opengl/util/PMVMatrix.java index 2001f8cdf..2d88f7937 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/PMVMatrix.java +++ b/src/jogl/classes/com/jogamp/opengl/util/PMVMatrix.java @@ -48,6 +48,7 @@ import com.jogamp.common.nio.Buffers; import com.jogamp.common.os.Platform; import com.jogamp.common.util.FloatStack; import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.Quaternion; import com.jogamp.opengl.math.geom.Frustum; /** @@ -535,6 +536,14 @@ public class PMVMatrix implements GLMatrixFunc { m.position(spos); } + /** + * Load the current matrix with the values of the given {@link Quaternion}'s rotation {@link Quaternion#toMatrix(float[], int) matrix representation}. + */ + public final void glLoadMatrix(final Quaternion quat) { + quat.toMatrix(tmpMatrix, 0); + glLoadMatrixf(tmpMatrix, 0); + } + @Override public final void glPopMatrix() { final FloatStack stack; @@ -637,6 +646,14 @@ public class PMVMatrix implements GLMatrixFunc { glMultMatrixf(matrixRot, 0); } + /** + * Rotate the current matrix with the given {@link Quaternion}'s rotation {@link Quaternion#toMatrix(float[], int) matrix representation}. + */ + public final void glRotate(final Quaternion quat) { + quat.toMatrix(tmpMatrix, 0); + glMultMatrixf(tmpMatrix, 0); + } + @Override public final void glScalef(final float x, final float y, final float z) { // Scale matrix (Any Order): diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/math/TestFloatUtil01NOUI.java b/src/test/com/jogamp/opengl/test/junit/jogl/math/TestFloatUtil01NOUI.java new file mode 100644 index 000000000..370cb4a2f --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/math/TestFloatUtil01NOUI.java @@ -0,0 +1,50 @@ +/** + * Copyright 2014 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.test.junit.jogl.math; + +import org.junit.Test; +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +import com.jogamp.opengl.math.FloatUtil; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestFloatUtil01NOUI { + static final float MACH_EPSILON = FloatUtil.getMachineEpsilon(); + + @Test + public void test01Epsilon() { + System.err.println("Machine Epsilon: "+MACH_EPSILON); + System.err.println("Fixed Epsilon: "+FloatUtil.EPSILON+", diff "+Math.abs(MACH_EPSILON-FloatUtil.EPSILON)); + } + + public static void main(String args[]) { + org.junit.runner.JUnitCore.main(TestFloatUtil01NOUI.class.getName()); + } +} diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/math/TestQuaternion01NOUI.java b/src/test/com/jogamp/opengl/test/junit/jogl/math/TestQuaternion01NOUI.java new file mode 100644 index 000000000..0f47f5889 --- /dev/null +++ b/src/test/com/jogamp/opengl/test/junit/jogl/math/TestQuaternion01NOUI.java @@ -0,0 +1,817 @@ +/** + * Copyright 2014 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.test.junit.jogl.math; + +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.FixMethodOrder; +import org.junit.runners.MethodSorters; + +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.math.Quaternion; +import com.jogamp.opengl.math.VectorUtil; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestQuaternion01NOUI { + static final boolean DEBUG = false; + + static final Quaternion QUAT_IDENT = new Quaternion(0f, 0f, 0f, 1f); + + static final float[] ZERO = new float[] { 0f, 0f, 0f }; + static final float[] ONE = new float[] { 1f, 1f, 1f }; + static final float[] NEG_ONE = new float[] { -1f, -1f, -1f }; + static final float[] UNIT_X = new float[] { 1f, 0f, 0f }; + static final float[] UNIT_Y = new float[] { 0f, 1f, 0f }; + static final float[] UNIT_Z = new float[] { 0f, 0f, 1f }; + static final float[] NEG_UNIT_X = new float[] { -1f, 0f, 0f }; + static final float[] NEG_UNIT_Y = new float[] { 0f, -1f, 0f }; + static final float[] NEG_UNIT_Z = new float[] { 0f, 0f, -1f }; + + static final float[] NEG_ONE_v4 = new float[] { -1f, -1f, -1f, 0f }; + static final float[] ONE_v4 = new float[] { 1f, 1f, 1f, 0f }; + + static final float MACH_EPSILON = FloatUtil.getMachineEpsilon(); + + // + // Basic + // + + @Test + public void test01Normalize() { + final Quaternion quat = new Quaternion(0, 1, 2, 3); + final Quaternion quat2 = new Quaternion(quat).normalize(); + // Assert.assertTrue(Math.abs(1 - quat2.magnitude()) <= MACH_EPSILON); + Assert.assertEquals(0f, Math.abs(1 - quat2.magnitude()), MACH_EPSILON); + } + + @Test + public void test02RotateZeroVector() { + final Quaternion quat = new Quaternion(); + final float[] rotVec0 = quat.rotateVector(new float[3], 0, ZERO, 0); + Assert.assertArrayEquals(ZERO, rotVec0, FloatUtil.EPSILON); + } + + @Test + public void test03InvertAndConj() { + // inversion check + { + final Quaternion quat0 = new Quaternion(0, 1, 2, 3); + final Quaternion quat0Inv = new Quaternion(quat0).invert(); + Assert.assertEquals(quat0, quat0Inv.invert()); + } + // conjugate check + { + final Quaternion quat0 = new Quaternion(-1f, -2f, -3f, 4f); + final Quaternion quat0Conj = new Quaternion( 1f, 2f, 3f, 4f).conjugate(); + Assert.assertEquals(quat0, quat0Conj); + } + } + + @Test + public void test04Dot() { + final Quaternion quat = new Quaternion(7f, 2f, 5f, -1f); + Assert.assertTrue(35.0f == quat.dot(3f, 1f, 2f, -2f)); + Assert.assertTrue(-11.0f == quat.dot(new Quaternion(-1f, 1f, -1f, 1f))); + } + + + // + // Conversion + // + + @Test + public void test10AngleAxis() { + final float[] tmpV3f = new float[3]; + final Quaternion quat1 = new Quaternion().setFromAngleAxis(FloatUtil.HALF_PI, new float[] { 2, 0, 0 }, tmpV3f ); + final Quaternion quat2 = new Quaternion().setFromAngleNormalAxis(FloatUtil.HALF_PI, new float[] { 1, 0, 0 } ); + + Assert.assertEquals(quat2, quat1); + // System.err.println("M "+quat2.magnitude()+", 1-M "+(1f-quat2.magnitude())+", Eps "+FloatUtil.EPSILON); + Assert.assertEquals(0f, 1 - quat2.magnitude(), FloatUtil.EPSILON); + Assert.assertTrue(1 - quat1.magnitude() <= FloatUtil.EPSILON); + + final float[] vecOut1 = new float[3]; + final float[] vecOut2 = new float[3]; + quat1.rotateVector(vecOut1, 0, ONE, 0); + quat2.rotateVector(vecOut2, 0, ONE, 0); + Assert.assertArrayEquals(vecOut1, vecOut2, FloatUtil.EPSILON); + Assert.assertEquals(0f, Math.abs( VectorUtil.distance(vecOut1, vecOut2) ), FloatUtil.EPSILON ); + + quat1.rotateVector(vecOut1, 0, UNIT_Z, 0); + Assert.assertEquals(0f, Math.abs( VectorUtil.distance(NEG_UNIT_Y, vecOut1) ), FloatUtil.EPSILON ); + + quat2.setFromAngleAxis(FloatUtil.HALF_PI, ZERO, tmpV3f); + Assert.assertEquals(QUAT_IDENT, quat2); + + float angle = quat1.toAngleAxis(vecOut1); + quat2.setFromAngleAxis(angle, vecOut1, tmpV3f); + Assert.assertEquals(quat1, quat2); + + quat1.set(0, 0, 0, 0); + angle = quat1.toAngleAxis(vecOut1); + Assert.assertTrue(0.0f == angle); + Assert.assertArrayEquals(UNIT_X, vecOut1, FloatUtil.EPSILON); + } + + @Test + public void test11FromVectorToVector() { + final float[] tmp0V3f = new float[3]; + final float[] tmp1V3f = new float[3]; + final float[] vecOut = new float[3]; + final Quaternion quat = new Quaternion(); + quat.setFromVectors(UNIT_Z, UNIT_X, tmp0V3f, tmp1V3f); + + final Quaternion quat2 = new Quaternion(); + quat2.setFromNormalVectors(UNIT_Z, UNIT_X, tmp0V3f); + Assert.assertEquals(quat, quat2); + + quat2.setFromAngleAxis(FloatUtil.HALF_PI, UNIT_Y, tmp0V3f); + Assert.assertEquals(quat2, quat); + + quat.setFromVectors(UNIT_Z, NEG_UNIT_Z, tmp0V3f, tmp1V3f); + quat.rotateVector(vecOut, 0, UNIT_Z, 0); + // System.err.println("vecOut: "+Arrays.toString(vecOut)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_Z, vecOut) ), Quaternion.ALLOWED_DEVIANCE ); + + quat.setFromVectors(UNIT_X, NEG_UNIT_X, tmp0V3f, tmp1V3f); + quat.rotateVector(vecOut, 0, UNIT_X, 0); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_X, vecOut) ), Quaternion.ALLOWED_DEVIANCE ); + + quat.setFromVectors(UNIT_Y, NEG_UNIT_Y, tmp0V3f, tmp1V3f); + quat.rotateVector(vecOut, 0, UNIT_Y, 0); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_Y, vecOut) ), Quaternion.ALLOWED_DEVIANCE ); + + quat.setFromVectors(ONE, NEG_ONE, tmp0V3f, tmp1V3f); + quat.rotateVector(vecOut, 0, ONE, 0); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_ONE, vecOut) ), Quaternion.ALLOWED_DEVIANCE ); + + quat.setFromVectors(ZERO, ZERO, tmp0V3f, tmp1V3f); + Assert.assertEquals(QUAT_IDENT, quat); + } + + @Test + public void test12FromAndToEulerAngles() { + // Y.Z.X -> X.Y.Z + final Quaternion quat = new Quaternion(); + final float[] angles0Exp = new float[] { 0f, FloatUtil.HALF_PI, 0f}; + quat.setFromEuler(angles0Exp); + Assert.assertEquals(1.0f, quat.magnitude(), FloatUtil.EPSILON); + + final float[] angles0Has = quat.toEuler(new float[3]); + // System.err.println("exp0 "+Arrays.toString(angles0Exp)); + // System.err.println("has0 "+Arrays.toString(angles0Has)); + Assert.assertArrayEquals(angles0Exp, angles0Has, FloatUtil.EPSILON); + + final Quaternion quat2 = new Quaternion(); + quat2.setFromEuler(angles0Has); + Assert.assertEquals(quat, quat2); + + /// + + final float[] angles1Exp = new float[] { 0f, 0f, -FloatUtil.HALF_PI }; + quat.setFromEuler(angles1Exp); + Assert.assertEquals(1.0f, quat.magnitude(), FloatUtil.EPSILON); + + final float[] angles1Has = quat.toEuler(new float[3]); + // System.err.println("exp1 "+Arrays.toString(angles1Exp)); + // System.err.println("has1 "+Arrays.toString(angles1Has)); + Assert.assertArrayEquals(angles1Exp, angles1Has, FloatUtil.EPSILON); + + quat2.setFromEuler(angles1Has); + Assert.assertEquals(quat, quat2); + + /// + + final float[] angles2Exp = new float[] { FloatUtil.HALF_PI, 0f, 0f }; + quat.setFromEuler(angles2Exp); + Assert.assertEquals(1.0f, quat.magnitude(), FloatUtil.EPSILON); + + final float[] angles2Has = quat.toEuler(new float[3]); + // System.err.println("exp2 "+Arrays.toString(angles2Exp)); + // System.err.println("has2 "+Arrays.toString(angles2Has)); + Assert.assertArrayEquals(angles2Exp, angles2Has, FloatUtil.EPSILON); + + quat2.setFromEuler(angles2Has); + Assert.assertEquals(quat, quat2); + } + + @Test + public void test13FromEulerAnglesAndRotateVector() { + final Quaternion quat = new Quaternion(); + quat.setFromEuler(0, FloatUtil.HALF_PI, 0); // 90 degrees y-axis + Assert.assertEquals(1.0f, quat.magnitude(), FloatUtil.EPSILON); + + final float[] v2 = quat.rotateVector(new float[3], 0, UNIT_X, 0); + Assert.assertEquals(0f, Math.abs(VectorUtil.distance(NEG_UNIT_Z, v2)), FloatUtil.EPSILON); + + quat.setFromEuler(0, 0, -FloatUtil.HALF_PI); + Assert.assertEquals(1.0f, quat.magnitude(), FloatUtil.EPSILON); + quat.rotateVector(v2, 0, UNIT_X, 0); + Assert.assertEquals(0f, Math.abs(VectorUtil.distance(NEG_UNIT_Y, v2)), FloatUtil.EPSILON); + + quat.setFromEuler(FloatUtil.HALF_PI, 0, 0); + Assert.assertEquals(1.0f, quat.magnitude(), FloatUtil.EPSILON); + quat.rotateVector(v2, 0, UNIT_Y, 0); + Assert.assertEquals(0f, Math.abs(VectorUtil.distance(UNIT_Z, v2)), FloatUtil.EPSILON); + } + + @Test + public void test14Matrix() { + final float[] vecHas = new float[3]; + final float[] vecOut2 = new float[4]; + float[] mat1 = new float[4*4]; + float[] mat2 = new float[4*4]; + final Quaternion quat = new Quaternion(); + + // + // IDENTITY CHECK + // + FloatUtil.makeIdentityf(mat1, 0); + quat.set(0, 0, 0, 0); + quat.toMatrix(mat2, 0); + Assert.assertArrayEquals(mat1, mat2, FloatUtil.EPSILON); + + // + // 90 degrees rotation on X + // + + float a = FloatUtil.HALF_PI; + mat1 = new float[] { // Column Order + 1, 0, 0, 0, // + 0, FloatUtil.cos(a), FloatUtil.sin(a), 0, // + 0, -FloatUtil.sin(a), FloatUtil.cos(a), 0, + 0, 0, 0, 1 }; + { + // Validate Matrix via Euler rotation on Quaternion! + quat.setFromEuler(a, 0f, 0f); + quat.toMatrix(mat2, 0); + // System.err.println(FloatUtil.matrixToString(null, "quat-rot", "%10.5f", mat1, 0, mat2, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(mat1, mat2, FloatUtil.EPSILON); + quat.rotateVector(vecHas, 0, UNIT_Y, 0); + // System.err.println("exp0 "+Arrays.toString(NEG_UNIT_X)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(UNIT_Z, vecHas) ), Quaternion.ALLOWED_DEVIANCE ); + } + quat.setFromMatrix(mat1, 0); + quat.rotateVector(vecHas, 0, UNIT_Y, 0); + // System.err.println("exp0 "+Arrays.toString(UNIT_Z)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(UNIT_Z, vecHas) ), Quaternion.ALLOWED_DEVIANCE ); + + quat.toMatrix(mat2, 0); + // System.err.println(FloatUtil.matrixToString(null, null, "%10.5f", mat1, 0, mat2, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(mat1, mat2, FloatUtil.EPSILON); + + quat.rotateVector(vecHas, 0, NEG_ONE, 0); + FloatUtil.multMatrixVecf(mat2, NEG_ONE_v4, vecOut2); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(vecHas, vecOut2) ), Quaternion.ALLOWED_DEVIANCE ); + + // + // 180 degrees rotation on X + // + a = FloatUtil.PI; + mat1 = new float[] { // Column Order + 1, 0, 0, 0, // + 0, FloatUtil.cos(a), FloatUtil.sin(a), 0, // + 0, -FloatUtil.sin(a), FloatUtil.cos(a), 0, + 0, 0, 0, 1 }; + { + // Validate Matrix via Euler rotation on Quaternion! + quat.setFromEuler(a, 0f, 0f); + quat.toMatrix(mat2, 0); + // System.err.println(FloatUtil.matrixToString(null, "quat-rot", "%10.5f", mat1, 0, mat2, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(mat1, mat2, FloatUtil.EPSILON); + quat.rotateVector(vecHas, 0, UNIT_Y, 0); + // System.err.println("exp0 "+Arrays.toString(NEG_UNIT_X)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_Y, vecHas) ), Quaternion.ALLOWED_DEVIANCE ); + } + quat.setFromMatrix(mat1, 0); + quat.rotateVector(vecHas, 0, UNIT_Y, 0); + // System.err.println("exp0 "+Arrays.toString(NEG_UNIT_Y)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_Y, vecHas) ), Quaternion.ALLOWED_DEVIANCE ); + + quat.toMatrix(mat2, 0); + // System.err.println(FloatUtil.matrixToString(null, null, "%10.5f", mat1, 0, mat2, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(mat1, mat2, FloatUtil.EPSILON); + + quat.rotateVector(vecHas, 0, ONE, 0); + FloatUtil.multMatrixVecf(mat2, ONE_v4, vecOut2); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(vecHas, vecOut2) ), Quaternion.ALLOWED_DEVIANCE ); + + // + // 180 degrees rotation on Y + // + a = FloatUtil.PI; + mat1 = new float[] { // Column Order + FloatUtil.cos(a), 0, -FloatUtil.sin(a), 0, // + 0, 1, 0, 0, // + FloatUtil.sin(a), 0, FloatUtil.cos(a), 0, + 0, 0, 0, 1 }; + { + // Validate Matrix via Euler rotation on Quaternion! + quat.setFromEuler(0f, a, 0f); + quat.toMatrix(mat2, 0); + // System.err.println(FloatUtil.matrixToString(null, "quat-rot", "%10.5f", mat1, 0, mat2, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(mat1, mat2, FloatUtil.EPSILON); + quat.rotateVector(vecHas, 0, UNIT_X, 0); + // System.err.println("exp0 "+Arrays.toString(NEG_UNIT_X)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_X, vecHas) ), Quaternion.ALLOWED_DEVIANCE ); + } + quat.setFromMatrix(mat1, 0); + quat.rotateVector(vecHas, 0, UNIT_X, 0); + // System.err.println("exp0 "+Arrays.toString(NEG_UNIT_X)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_X, vecHas) ), Quaternion.ALLOWED_DEVIANCE ); + + quat.toMatrix(mat2, 0); + // System.err.println(FloatUtil.matrixToString(null, "matr-rot", "%10.5f", mat1, 0, mat2, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(mat1, mat2, FloatUtil.EPSILON); + + quat.rotateVector(vecHas, 0, NEG_ONE, 0); + FloatUtil.multMatrixVecf(mat2, NEG_ONE_v4, vecOut2); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(vecHas, vecOut2) ), Quaternion.ALLOWED_DEVIANCE ); + + // + // 180 degrees rotation on Z + // + a = FloatUtil.PI; + mat1 = new float[] { // Column Order + FloatUtil.cos(a), FloatUtil.sin(a), 0, 0, // + -FloatUtil.sin(a), FloatUtil.cos(a), 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 }; + { + // Validate Matrix via Euler rotation on Quaternion! + quat.setFromEuler(0f, 0f, a); + quat.toMatrix(mat2, 0); + // System.err.println(FloatUtil.matrixToString(null, "quat-rot", "%10.5f", mat1, 0, mat2, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(mat1, mat2, FloatUtil.EPSILON); + quat.rotateVector(vecHas, 0, UNIT_X, 0); + // System.err.println("exp0 "+Arrays.toString(NEG_UNIT_X)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_X, vecHas) ), Quaternion.ALLOWED_DEVIANCE ); + } + quat.setFromMatrix(mat1, 0); + quat.rotateVector(vecHas, 0, UNIT_X, 0); + // System.err.println("exp0 "+Arrays.toString(NEG_UNIT_X)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_X, vecHas) ), Quaternion.ALLOWED_DEVIANCE ); + + quat.toMatrix(mat2, 0); + // System.err.println(FloatUtil.matrixToString(null, "matr-rot", "%10.5f", mat1, 0, mat2, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(mat1, mat2, FloatUtil.EPSILON); + + quat.rotateVector(vecHas, 0, ONE, 0); + FloatUtil.multMatrixVecf(mat2, ONE_v4, vecOut2); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(vecHas, vecOut2) ), Quaternion.ALLOWED_DEVIANCE ); + + // + // Test Matrix-Columns + // + + a = FloatUtil.QUARTER_PI; + float[] vecExp = new float[3]; + float[] vecCol = new float[3]; + mat1 = new float[] { // Column Order + FloatUtil.cos(a), FloatUtil.sin(a), 0, 0, // + -FloatUtil.sin(a), FloatUtil.cos(a), 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 }; + quat.setFromMatrix(mat1, 0); + FloatUtil.copyMatrixColumn(mat1, 0, 0, vecExp, 0); + quat.copyMatrixColumn(0, vecCol, 0); + // System.err.println("exp0 "+Arrays.toString(vecExp)); + // System.err.println("has0 "+Arrays.toString(vecCol)); + Assert.assertEquals(0f, Math.abs( VectorUtil.distance(vecExp, vecCol)), FloatUtil.EPSILON); + + FloatUtil.copyMatrixColumn(mat1, 0, 1, vecExp, 0); + quat.copyMatrixColumn(1, vecCol, 0); + // System.err.println("exp1 "+Arrays.toString(vecExp)); + // System.err.println("has1 "+Arrays.toString(vecCol)); + Assert.assertEquals(0f, Math.abs( VectorUtil.distance(vecExp, vecCol)), FloatUtil.EPSILON); + + FloatUtil.copyMatrixColumn(mat1, 0, 2, vecExp, 0); + quat.copyMatrixColumn(2, vecCol, 0); + // System.err.println("exp2 "+Arrays.toString(vecExp)); + // System.err.println("has2 "+Arrays.toString(vecCol)); + Assert.assertEquals(0f, Math.abs( VectorUtil.distance(vecExp, vecCol)), FloatUtil.EPSILON); + + quat.set(0f, 0f, 0f, 0f); + Assert.assertArrayEquals(UNIT_X, quat.copyMatrixColumn(0, vecCol, 0), FloatUtil.EPSILON); + + } + + @Test + public void test15aAxesAndMatrix() { + final float[] eulerExp = new float[] { 0f, FloatUtil.HALF_PI, 0f }; + final float[] matExp = new float[4*4]; + FloatUtil.makeRotationEuler(eulerExp[0], eulerExp[1], eulerExp[2], matExp, 0); // 45 degr on X, 90 degr on Y + + final float[] matHas = new float[4*4]; + final Quaternion quat1 = new Quaternion(); + quat1.setFromEuler(eulerExp); + quat1.toMatrix(matHas, 0); + // System.err.println(FloatUtil.matrixToString(null, "exp-has", "%10.5f", matExp, 0, matHas, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(matExp, matHas, FloatUtil.EPSILON); + + final float[] eulerHas = new float[3]; + final Quaternion quat2 = new Quaternion(); + quat2.setFromMatrix(matExp, 0); + quat2.toEuler(eulerHas); + // System.err.println("exp-euler "+Arrays.toString(eulerExp)); + // System.err.println("has-euler "+Arrays.toString(eulerHas)); + Assert.assertArrayEquals(eulerExp, eulerHas, FloatUtil.EPSILON); + + Assert.assertEquals(quat2, quat1); + + final float[] angles = new float[3]; + quat2.toEuler(angles); + quat1.setFromEuler(angles); + Assert.assertEquals(quat2, quat1); + } + + @Test + public void test15bAxesAndMatrix() { + final float[] eulerExp = new float[] { FloatUtil.HALF_PI, 0f, 0f }; + final float[] matExp = new float[4*4]; + FloatUtil.makeRotationEuler(eulerExp[0], eulerExp[1], eulerExp[2], matExp, 0); // 45 degr on X, 90 degr on Y + + final float[] matHas = new float[4*4]; + final Quaternion quat1 = new Quaternion(); + quat1.setFromEuler(eulerExp); + quat1.toMatrix(matHas, 0); + // System.err.println(FloatUtil.matrixToString(null, "exp-has", "%10.5f", matExp, 0, matHas, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(matExp, matHas, FloatUtil.EPSILON); + + final float[] eulerHas = new float[3]; + final Quaternion quat2 = new Quaternion(); + quat2.setFromMatrix(matExp, 0); + quat2.toEuler(eulerHas); + // System.err.println("exp-euler "+Arrays.toString(eulerExp)); + // System.err.println("has-euler "+Arrays.toString(eulerHas)); + Assert.assertArrayEquals(eulerExp, eulerHas, FloatUtil.EPSILON); + + Assert.assertEquals(quat2, quat1); + + final float[] angles = new float[3]; + quat2.toEuler(angles); + quat1.setFromEuler(angles); + Assert.assertEquals(quat2, quat1); + } + + @Test + public void test15cAxesAndMatrix() { + final float[] eulerExp = new float[] { FloatUtil.QUARTER_PI, FloatUtil.HALF_PI, 0f }; + final float[] matExp = new float[4*4]; + FloatUtil.makeRotationEuler(eulerExp[0], eulerExp[1], eulerExp[2], matExp, 0); // 45 degr on X, 90 degr on Y + + final float[] matHas = new float[4*4]; + final Quaternion quat1 = new Quaternion(); + quat1.setFromEuler(eulerExp); + quat1.toMatrix(matHas, 0); + // System.err.println(FloatUtil.matrixToString(null, "exp-has", "%10.5f", matExp, 0, matHas, 0, 4, 4, false).toString()); + Assert.assertArrayEquals(matExp, matHas, FloatUtil.EPSILON); + + final float[] eulerHas = new float[3]; + final Quaternion quat2 = new Quaternion(); + quat2.setFromMatrix(matExp, 0); + quat2.toEuler(eulerHas); + // System.err.println("exp-euler "+Arrays.toString(eulerExp)); + // System.err.println("has-euler "+Arrays.toString(eulerHas)); + Assert.assertArrayEquals(eulerExp, eulerHas, FloatUtil.EPSILON); + + Assert.assertEquals(quat2, quat1); + + final float[] angles = new float[3]; + quat2.toEuler(angles); + quat1.setFromEuler(angles); + Assert.assertEquals(quat2, quat1); + } + + // + // Functions + // + + @Test + public void test20AddSubtract() { + final Quaternion quatExp1 = new Quaternion(1, 2, 3, 4); + final Quaternion quat1 = new Quaternion(0, 1, 2, 3); + final Quaternion quat2 = new Quaternion(1, 1, 1, 1); + + final Quaternion quatHas = new Quaternion(); + quatHas.set(quat1); + quatHas.add(quat2); // q3 = q1 + q2 + Assert.assertEquals(quatExp1, quatHas); + + quat1.set(0, 1, 2, 3); + quat2.set(1, 1, 1, 1); + quatHas.set(quat1); + quatHas.subtract(quat2); // q3 = q1 - q2 + Assert.assertEquals(new Quaternion(-1, 0, 1, 2), quatHas); + } + + @Test + public void test21Multiply() { + final Quaternion quat1 = new Quaternion(0.5f, 1f, 2f, 3f); + final Quaternion quat2 = new Quaternion(); + + quat2.set(quat1); + quat2.scale(2f); // q2 = q1 * 2f + Assert.assertEquals(new Quaternion(1, 2, 4, 6), quat2); + + quat2.set(quat1); + quat2.scale(4f); // q2 = q1 * 4f + Assert.assertEquals(new Quaternion(2, 4, 8, 12), quat2); + + // + // mul and cmp rotated vector + // + quat1.setFromAngleNormalAxis(FloatUtil.QUARTER_PI, UNIT_Y); // 45 degr on Y + quat2.set(quat1); + quat2.mult(quat1); // q2 = q1 * q1 -> 2 * 45 degr -> 90 degr on Y + + final float[] vecOut = new float[3]; + quat2.rotateVector(vecOut, 0, UNIT_Z, 0); + Assert.assertTrue( Math.abs( VectorUtil.distance(UNIT_X, vecOut)) <= Quaternion.ALLOWED_DEVIANCE); + + quat2.setFromAngleNormalAxis(FloatUtil.HALF_PI, UNIT_Y); // 90 degr on Y + quat1.mult(quat1); // q1 = q1 * q1 -> 2 * 45 degr -> 90 degr on Y + quat1.mult(quat2); // q1 = q1 * q2 -> 2 * 90 degr -> 180 degr on Y + quat1.rotateVector(vecOut, 0, UNIT_Z, 0); + Assert.assertTrue( Math.abs( VectorUtil.distance(NEG_UNIT_Z, vecOut)) <= Quaternion.ALLOWED_DEVIANCE); + + quat2.setFromEuler(0f, FloatUtil.HALF_PI, 0f); + quat1.mult(quat2); // q1 = q1 * q2 = q1 * rotMat(0, 90degr, 0) + quat1.rotateVector(vecOut, 0, UNIT_Z, 0); + Assert.assertTrue( Math.abs( VectorUtil.distance(NEG_UNIT_X, vecOut)) <= Quaternion.ALLOWED_DEVIANCE); + } + + @Test + public void test22InvertMultNormalAndConj() { + final Quaternion quat0 = new Quaternion(0, 1, 2, 3); + final Quaternion quat1 = new Quaternion(quat0); + final Quaternion quat2 = new Quaternion(quat0); + quat1.invert(); // q1 = invert(q0) + quat2.mult(quat1); // q2 = q0 * q1 = q0 * invert(q0) + Assert.assertEquals(QUAT_IDENT, quat2); + quat1.invert(); + Assert.assertEquals(quat0, quat1); + + // normalized version + quat0.setFromAngleNormalAxis(FloatUtil.QUARTER_PI, UNIT_Y); + quat1.set(quat0); + quat1.invert(); // q1 = invert(q0) + quat2.set(quat0); + quat2.mult(quat1); // q2 = q0 * q1 = q0 * invert(q0) + Assert.assertEquals(QUAT_IDENT, quat2); + quat1.invert(); + Assert.assertEquals(quat0, quat1); + + // conjugate check + quat0.set(-1f, -2f, -3f, 4f); + quat1.set( 1f, 2f, 3f, 4f); + quat2.set(quat1); + quat2.conjugate(); + Assert.assertEquals(quat0, quat2); + } + + @Test + public void test23RotationOrder() { + { + final Quaternion quat1 = new Quaternion().setFromEuler( -2f*FloatUtil.HALF_PI, 0f, 0f); // -180 degr X + final Quaternion quat2 = new Quaternion().rotateByAngleX( -2f * FloatUtil.HALF_PI); // angle: -180 degrees, axis X + Assert.assertEquals(quat1, quat2); + } + { + final Quaternion quat1 = new Quaternion().setFromEuler( FloatUtil.HALF_PI, 0f, 0f); // 90 degr X + final Quaternion quat2 = new Quaternion().rotateByAngleX( FloatUtil.HALF_PI); // angle: 90 degrees, axis X + Assert.assertEquals(quat1, quat2); + } + { + final Quaternion quat1 = new Quaternion().setFromEuler( FloatUtil.HALF_PI, FloatUtil.QUARTER_PI, 0f); + final Quaternion quat2 = new Quaternion().rotateByAngleY(FloatUtil.QUARTER_PI).rotateByAngleX(FloatUtil.HALF_PI); + Assert.assertEquals(quat1, quat2); + } + { + final Quaternion quat1 = new Quaternion().setFromEuler( FloatUtil.PI, FloatUtil.QUARTER_PI, FloatUtil.HALF_PI); + final Quaternion quat2 = new Quaternion().rotateByAngleY(FloatUtil.QUARTER_PI).rotateByAngleZ(FloatUtil.HALF_PI).rotateByAngleX(FloatUtil.PI); + Assert.assertEquals(quat1, quat2); + } + + + float[] vecExp = new float[3]; + float[] vecRot = new float[3]; + final Quaternion quat = new Quaternion(); + + // Try a new way with new angles... + quat.setFromEuler(FloatUtil.HALF_PI, FloatUtil.QUARTER_PI, FloatUtil.PI); + vecRot = new float[] { 1f, 1f, 1f }; + quat.rotateVector(vecRot, 0, vecRot, 0); + + // expected + vecExp = new float[] { 1f, 1f, 1f }; + final Quaternion worker = new Quaternion(); + // put together matrix, then apply to vector, so YZX + worker.rotateByAngleY(FloatUtil.QUARTER_PI).rotateByAngleZ(FloatUtil.PI).rotateByAngleX(FloatUtil.HALF_PI); + quat.rotateVector(vecExp, 0, vecExp, 0); + Assert.assertEquals(0f, VectorUtil.distance(vecExp, vecRot), FloatUtil.EPSILON); + + // test axis rotation methods against general purpose + // X AXIS + vecExp = new float[] { 1f, 1f, 1f }; + vecRot = new float[] { 1f, 1f, 1f }; + worker.setIdentity().rotateByAngleX(FloatUtil.QUARTER_PI).rotateVector(vecExp, 0, vecExp, 0); + worker.setIdentity().rotateByAngleNormalAxis(FloatUtil.QUARTER_PI, 1f, 0f, 0f).rotateVector(vecRot, 0, vecRot, 0); + // System.err.println("exp0 "+Arrays.toString(vecExp)+", len "+VectorUtil.length(vecExp)); + // System.err.println("has0 "+Arrays.toString(vecRot)+", len "+VectorUtil.length(vecRot)); + Assert.assertEquals(0f, VectorUtil.distance(vecExp, vecRot), FloatUtil.EPSILON); + + // Y AXIS + vecExp = new float[] { 1f, 1f, 1f }; + vecRot = new float[] { 1f, 1f, 1f }; + worker.setIdentity().rotateByAngleY(FloatUtil.QUARTER_PI).rotateVector(vecExp, 0, vecExp, 0); + worker.setIdentity().rotateByAngleNormalAxis(FloatUtil.QUARTER_PI, 0f, 1f, 0f).rotateVector(vecRot, 0, vecRot, 0); + // System.err.println("exp0 "+Arrays.toString(vecExp)); + // System.err.println("has0 "+Arrays.toString(vecRot)); + Assert.assertEquals(0f, VectorUtil.distance(vecExp, vecRot), FloatUtil.EPSILON); + + // Z AXIS + vecExp = new float[] { 1f, 1f, 1f }; + vecRot = new float[] { 1f, 1f, 1f }; + worker.setIdentity().rotateByAngleZ(FloatUtil.QUARTER_PI).rotateVector(vecExp, 0, vecExp, 0); + worker.setIdentity().rotateByAngleNormalAxis(FloatUtil.QUARTER_PI, 0f, 0f, 1f).rotateVector(vecRot, 0, vecRot, 0); + // System.err.println("exp0 "+Arrays.toString(vecExp)); + // System.err.println("has0 "+Arrays.toString(vecRot)); + Assert.assertEquals(0f, VectorUtil.distance(vecExp, vecRot), FloatUtil.EPSILON); + + quat.set(worker); + worker.rotateByAngleNormalAxis(0f, 0f, 0f, 0f); + Assert.assertEquals(quat, worker); + } + + @Test + public void test24Axes() { + final Quaternion quat0 = new Quaternion().rotateByAngleX(FloatUtil.QUARTER_PI).rotateByAngleY(FloatUtil.HALF_PI); + final float[] rotMat = new float[4*4]; + quat0.toMatrix(rotMat, 0); + final float[] xAxis = new float[3]; + final float[] yAxis = new float[3]; + final float[] zAxis = new float[3]; + FloatUtil.copyMatrixColumn(rotMat, 0, 0, xAxis, 0); + FloatUtil.copyMatrixColumn(rotMat, 0, 1, yAxis, 0); + FloatUtil.copyMatrixColumn(rotMat, 0, 2, zAxis, 0); + + final Quaternion quat1 = new Quaternion().setFromAxes(xAxis, yAxis, zAxis); + Assert.assertEquals(quat0, quat1); + final Quaternion quat2 = new Quaternion().setFromMatrix(rotMat, 0); + Assert.assertEquals(quat2, quat1); + + quat1.toAxes(xAxis, yAxis, zAxis, rotMat); + quat2.setFromAxes(xAxis, yAxis, zAxis); + Assert.assertEquals(quat0, quat2); + Assert.assertEquals(quat1, quat2); + } + + @Test + public void test25Slerp() { + final Quaternion quat1 = new Quaternion(); // angle: 0 degrees + final Quaternion quat2 = new Quaternion().rotateByAngleY(FloatUtil.HALF_PI); // angle: 90 degrees, axis Y + + float[] vecExp = new float[] { FloatUtil.sin(FloatUtil.QUARTER_PI), 0f, FloatUtil.sin(FloatUtil.QUARTER_PI) }; + final float[] vecHas = new float[3]; + final Quaternion quatS = new Quaternion(); + // System.err.println("Slerp #01: 1/2 * 90 degrees Y"); + quatS.setSlerp(quat1, quat2, 0.5f); + quatS.rotateVector(vecHas, 0, UNIT_Z, 0); + // System.err.println("exp0 "+Arrays.toString(vecExp)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(vecExp, vecHas)), Quaternion.ALLOWED_DEVIANCE); + + // delta == 100% + quat2.setIdentity().rotateByAngleZ(FloatUtil.PI); // angle: 180 degrees, axis Z + // System.err.println("Slerp #02: 1 * 180 degrees Z"); + quatS.setSlerp(quat1, quat2, 1.0f); + quatS.rotateVector(vecHas, 0, UNIT_X, 0); + // System.err.println("exp0 "+Arrays.toString(NEG_UNIT_X)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(NEG_UNIT_X, vecHas)), Quaternion.ALLOWED_DEVIANCE); + + quat2.setIdentity().rotateByAngleZ(FloatUtil.PI); // angle: 180 degrees, axis Z + // System.err.println("Slerp #03: 1/2 * 180 degrees Z"); + quatS.setSlerp(quat1, quat2, 0.5f); + quatS.rotateVector(vecHas, 0, UNIT_X, 0); + // System.err.println("exp0 "+Arrays.toString(UNIT_Y)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(UNIT_Y, vecHas)), Quaternion.ALLOWED_DEVIANCE); + + // delta == 0% + quat2.setIdentity().rotateByAngleZ(FloatUtil.PI); // angle: 180 degrees, axis Z + // System.err.println("Slerp #04: 0 * 180 degrees Z"); + quatS.setSlerp(quat1, quat2, 0.0f); + quatS.rotateVector(vecHas, 0, UNIT_X, 0); + // System.err.println("exp0 "+Arrays.toString(UNIT_X)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(UNIT_X, vecHas)), Quaternion.ALLOWED_DEVIANCE); + + // a==b + quat2.setIdentity(); + // System.err.println("Slerp #05: 1/4 * 0 degrees"); + quatS.setSlerp(quat1, quat2, 0.25f); // 1/4 of identity .. NOP + quatS.rotateVector(vecHas, 0, UNIT_X, 0); + // System.err.println("exp0 "+Arrays.toString(UNIT_X)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(UNIT_X, vecHas)), Quaternion.ALLOWED_DEVIANCE); + + // negative dot product + vecExp = new float[] { 0f, -FloatUtil.sin(FloatUtil.QUARTER_PI), FloatUtil.sin(FloatUtil.QUARTER_PI) }; + quat1.setIdentity().rotateByAngleX( -2f * FloatUtil.HALF_PI); // angle: -180 degrees, axis X + quat2.setIdentity().rotateByAngleX( FloatUtil.HALF_PI); // angle: 90 degrees, axis X + // System.err.println("Slerp #06: 1/2 * 270 degrees"); + quatS.setSlerp(quat1, quat2, 0.5f); + quatS.rotateVector(vecHas, 0, UNIT_Y, 0); + // System.err.println("exp0 "+Arrays.toString(vecExp)); + // System.err.println("has0 "+Arrays.toString(vecHas)); + Assert.assertEquals( 0f, Math.abs( VectorUtil.distance(vecExp, vecHas)), Quaternion.ALLOWED_DEVIANCE); + + + } + + @Test + public void test26LookAt() { + final float[] direction = new float[3]; + final float[] xAxis = new float[3]; + final float[] yAxis = new float[3]; + final float[] zAxis = new float[3]; + final float[] vecHas = new float[3]; + + if( DEBUG ) System.err.println("LookAt #01"); + VectorUtil.copyVec3(direction, 0, NEG_UNIT_X, 0); + final Quaternion quat = new Quaternion().setLookAt(direction, UNIT_Y, xAxis, yAxis, zAxis); + Assert.assertEquals(0f, VectorUtil.distance(direction, quat.rotateVector(vecHas, 0, UNIT_Z, 0)), Quaternion.ALLOWED_DEVIANCE); + + if( DEBUG ) System.err.println("LookAt #02"); + VectorUtil.normalize(VectorUtil.copyVec3(direction, 0, ONE, 0)); + quat.setLookAt(direction, UNIT_Y, xAxis, yAxis, zAxis); + if( DEBUG )System.err.println("quat0 "+quat); + quat.rotateVector(vecHas, 0, UNIT_Z, 0); + if( DEBUG ) { + System.err.println("xAxis "+Arrays.toString(xAxis)+", len "+VectorUtil.length(xAxis)); + System.err.println("yAxis "+Arrays.toString(yAxis)+", len "+VectorUtil.length(yAxis)); + System.err.println("zAxis "+Arrays.toString(zAxis)+", len "+VectorUtil.length(zAxis)); + System.err.println("exp0 "+Arrays.toString(direction)+", len "+VectorUtil.length(direction)); + System.err.println("has0 "+Arrays.toString(vecHas)+", len "+VectorUtil.length(vecHas)); + } + // Assert.assertEquals(0f, VectorUtil.distance(direction, quat.rotateVector(vecHas, 0, UNIT_Z, 0)), Quaternion.ALLOWED_DEVIANCE); + Assert.assertEquals(0f, VectorUtil.distance(direction, vecHas), Quaternion.ALLOWED_DEVIANCE); + + if( DEBUG )System.err.println("LookAt #03"); + VectorUtil.normalize(VectorUtil.copyVec3(direction, 0, new float[] { -1f, 2f, -1f }, 0)); + quat.setLookAt(direction, UNIT_Y, xAxis, yAxis, zAxis); + if( DEBUG )System.err.println("quat0 "+quat); + quat.rotateVector(vecHas, 0, UNIT_Z, 0); + if( DEBUG ) { + System.err.println("xAxis "+Arrays.toString(xAxis)+", len "+VectorUtil.length(xAxis)); + System.err.println("yAxis "+Arrays.toString(yAxis)+", len "+VectorUtil.length(yAxis)); + System.err.println("zAxis "+Arrays.toString(zAxis)+", len "+VectorUtil.length(zAxis)); + System.err.println("exp0 "+Arrays.toString(direction)+", len "+VectorUtil.length(direction)); + System.err.println("has0 "+Arrays.toString(vecHas)+", len "+VectorUtil.length(vecHas)); + } + // Assert.assertEquals(0f, VectorUtil.distance(direction, quat.rotateVector(vecHas, 0, UNIT_Z, 0)), Quaternion.ALLOWED_DEVIANCE); + Assert.assertEquals(0f, VectorUtil.distance(direction, vecHas), Quaternion.ALLOWED_DEVIANCE); + } + + public static void main(String args[]) { + org.junit.runner.JUnitCore.main(TestQuaternion01NOUI.class.getName()); + } +} -- cgit v1.2.3