package org.papervision3d.core.math { /** * @author Tim Knip */ public class Quaternion { private var _matrix:Matrix3D; public static const EPSILON:Number = 0.000001; public static const DEGTORAD:Number = (Math.PI/180.0); public static const RADTODEG:Number = (180.0/Math.PI); /** */ public var x:Number; /** */ public var y:Number; /** */ public var z:Number; /** */ public var w:Number; /** * constructor. * * @param x * @param y * @param z * @param w * @return */ public function Quaternion( x:Number = 0, y:Number = 0, z:Number = 0, w:Number = 1 ) { this.x = x; this.y = y; this.z = z; this.w = w; _matrix = Matrix3D.IDENTITY; } /** * Clone. * */ public function clone():Quaternion { return new Quaternion(this.x, this.y, this.z, this.w); } /** * Multiply. * * @param a * @param b */ public function calculateMultiply( a:Quaternion, b:Quaternion ):void { this.x = a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y; this.y = a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x; this.z = a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w; this.w = a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z; } /** * Creates a Quaternion from a axis and a angle. * * @param x X-axis * @param y Y-axis * @param z Z-axis * @param angle angle in radians. * * @return */ public function setFromAxisAngle( x:Number, y:Number, z:Number, angle:Number ):void { var sin:Number = Math.sin( angle / 2 ); var cos:Number = Math.cos( angle / 2 ); this.x = x * sin; this.y = y * sin; this.z = z * sin; this.w = cos; this.normalize(); } /** * Sets this Quaternion from Euler angles. * * @param ax X-angle in radians. * @param ay Y-angle in radians. * @param az Z-angle in radians. */ public function setFromEuler(ax:Number, ay:Number, az:Number, useDegrees:Boolean=false):void { if( useDegrees ) { ax *= DEGTORAD; ay *= DEGTORAD; az *= DEGTORAD; } var fSinPitch :Number = Math.sin( ax * 0.5 ); var fCosPitch :Number = Math.cos( ax * 0.5 ); var fSinYaw :Number = Math.sin( ay * 0.5 ); var fCosYaw :Number = Math.cos( ay * 0.5 ); var fSinRoll :Number = Math.sin( az * 0.5 ); var fCosRoll :Number = Math.cos( az * 0.5 ); var fCosPitchCosYaw :Number = fCosPitch * fCosYaw; var fSinPitchSinYaw :Number = fSinPitch * fSinYaw; this.x = fSinRoll * fCosPitchCosYaw - fCosRoll * fSinPitchSinYaw; this.y = fCosRoll * fSinPitch * fCosYaw + fSinRoll * fCosPitch * fSinYaw; this.z = fCosRoll * fCosPitch * fSinYaw - fSinRoll * fSinPitch * fCosYaw; this.w = fCosRoll * fCosPitchCosYaw + fSinRoll * fSinPitchSinYaw; } /** * Modulo. * * @param a * @return */ public function get modulo():Number { return Math.sqrt(x*x + y*y + z*z + w*w); } /** * Conjugate. * * @param a * @return */ public static function conjugate( a:Quaternion ):Quaternion { var q:Quaternion = new Quaternion(); q.x = -a.x; q.y = -a.y; q.z = -a.z; q.w = a.w; return q; } /** * Creates a Quaternion from a axis and a angle. * * @param x X-axis * @param y Y-axis * @param z Z-axis * @param angle angle in radians. * * @return */ public static function createFromAxisAngle( x:Number, y:Number, z:Number, angle:Number ):Quaternion { var q:Quaternion = new Quaternion(); q.setFromAxisAngle(x, y, z, angle); return q; } /** * Creates a Quaternion from Euler angles. * * @param ax X-angle in radians. * @param ay Y-angle in radians. * @param az Z-angle in radians. * * @return */ public static function createFromEuler( ax:Number, ay:Number, az:Number, useDegrees:Boolean = false ):Quaternion { if( useDegrees ) { ax *= DEGTORAD; ay *= DEGTORAD; az *= DEGTORAD; } var fSinPitch :Number = Math.sin( ax * 0.5 ); var fCosPitch :Number = Math.cos( ax * 0.5 ); var fSinYaw :Number = Math.sin( ay * 0.5 ); var fCosYaw :Number = Math.cos( ay * 0.5 ); var fSinRoll :Number = Math.sin( az * 0.5 ); var fCosRoll :Number = Math.cos( az * 0.5 ); var fCosPitchCosYaw :Number = fCosPitch * fCosYaw; var fSinPitchSinYaw :Number = fSinPitch * fSinYaw; var q:Quaternion = new Quaternion(); q.x = fSinRoll * fCosPitchCosYaw - fCosRoll * fSinPitchSinYaw; q.y = fCosRoll * fSinPitch * fCosYaw + fSinRoll * fCosPitch * fSinYaw; q.z = fCosRoll * fCosPitch * fSinYaw - fSinRoll * fSinPitch * fCosYaw; q.w = fCosRoll * fCosPitchCosYaw + fSinRoll * fSinPitchSinYaw; return q; } /** * Creates a Quaternion from a matrix. * * @param matrix a matrix. @see org.papervision3d.core.Matrix3D * * @return the created Quaternion */ public static function createFromMatrix( matrix:Matrix3D ):Quaternion { var quat:Quaternion = new Quaternion(); var s:Number; var q:Array = new Array(4); var i:int, j:int, k:int; var tr:Number = matrix.n11 + matrix.n22 + matrix.n33; // check the diagonal if (tr > 0.0) { s = Math.sqrt(tr + 1.0); quat.w = s / 2.0; s = 0.5 / s; quat.x = (matrix.n32 - matrix.n23) * s; quat.y = (matrix.n13 - matrix.n31) * s; quat.z = (matrix.n21 - matrix.n12) * s; } else { // diagonal is negative var nxt:Array = [1, 2, 0]; var m:Array = [ [matrix.n11, matrix.n12, matrix.n13, matrix.n14], [matrix.n21, matrix.n22, matrix.n23, matrix.n24], [matrix.n31, matrix.n32, matrix.n33, matrix.n34] ]; i = 0; if (m[1][1] > m[0][0]) i = 1; if (m[2][2] > m[i][i]) i = 2; j = nxt[i]; k = nxt[j]; s = Math.sqrt((m[i][i] - (m[j][j] + m[k][k])) + 1.0); q[i] = s * 0.5; if (s != 0.0) s = 0.5 / s; q[3] = (m[k][j] - m[j][k]) * s; q[j] = (m[j][i] + m[i][j]) * s; q[k] = (m[k][i] + m[i][k]) * s; quat.x = q[0]; quat.y = q[1]; quat.z = q[2]; quat.w = q[3]; } return quat; } /** * Creates a Quaternion from a orthonormal matrix. * * @param m a orthonormal matrix. @see org.papervision3d.core.Matrix3D * * @return the created Quaternion */ public static function createFromOrthoMatrix( m:Matrix3D ):Quaternion { var q:Quaternion = new Quaternion(); q.w = Math.sqrt( Math.max(0, 1 + m.n11 + m.n22 + m.n33) ) / 2; q.x = Math.sqrt( Math.max(0, 1 + m.n11 - m.n22 - m.n33) ) / 2; q.y = Math.sqrt( Math.max(0, 1 - m.n11 + m.n22 - m.n33) ) / 2; q.z = Math.sqrt( Math.max(0, 1 - m.n11 - m.n22 + m.n33) ) / 2; // recover signs q.x = m.n32 - m.n23 < 0 ? (q.x < 0 ? q.x : -q.x) : (q.x < 0 ? -q.x : q.x); q.y = m.n13 - m.n31 < 0 ? (q.y < 0 ? q.y : -q.y) : (q.y < 0 ? -q.y : q.y); q.z = m.n21 - m.n12 < 0 ? (q.z < 0 ? q.z : -q.z) : (q.z < 0 ? -q.z : q.z); return q; } /** * Dot product. * * @param a * @param b * * @return */ public static function dot( a:Quaternion, b:Quaternion ):Number { return (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w); } /** * Multiply. * * @param a * @param b * @return */ public static function multiply( a:Quaternion, b:Quaternion ):Quaternion { var c:Quaternion = new Quaternion(); c.x = a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y; c.y = a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x; c.z = a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w; c.w = a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z; return c; } /** * Multiply by another Quaternion. * * @param b The Quaternion to multiply by. */ public function mult( b:Quaternion ):void { var aw:Number = this.w, ax:Number = this.x, ay:Number = this.y, az:Number = this.z; x = aw*b.x + ax*b.w + ay*b.z - az*b.y; y = aw*b.y - ax*b.z + ay*b.w + az*b.x; z = aw*b.z + ax*b.y - ay*b.x + az*b.w; w = aw*b.w - ax*b.x - ay*b.y - az*b.z; } public function toString():String{ return "Quaternion: x:"+this.x+" y:"+this.y+" z:"+this.z+" w:"+this.w; } /** * Normalize. * * @param a * * @return */ public function normalize():void { var len:Number = this.modulo; if( Math.abs(len) < EPSILON ) { x = y = z = 0.0; w = 1.0; } else { var m:Number = 1 / len; x *= m; y *= m; z *= m; w *= m; } } /** * SLERP (Spherical Linear intERPolation). @author Trevor Burton * * @param qa start quaternion * @param qb end quaternion * @param alpha a value between 0 and 1 * * @return the interpolated quaternion. */ public static function slerp( qa:Quaternion, qb:Quaternion, alpha:Number ):Quaternion { var angle:Number = qa.w * qb.w + qa.x * qb.x + qa.y * qb.y + qa.z * qb.z if (angle < 0.0) { qa.x *= -1.0; qa.y *= -1.0; qa.z *= -1.0; qa.w *= -1.0; angle *= -1.0; } var scale:Number; var invscale:Number; if ((angle + 1.0) > EPSILON) // Take the shortest path { if ((1.0 - angle) >= EPSILON) // spherical interpolation { var theta:Number = Math.acos(angle); var invsintheta:Number = 1.0 / Math.sin(theta); scale = Math.sin(theta * (1.0-alpha)) * invsintheta; invscale = Math.sin(theta * alpha) * invsintheta; } else // linear interploation { scale = 1.0 - alpha; invscale = alpha; } } else // long way to go... { qb.y = -qa.y; qb.x = qa.x; qb.w = -qa.w; qb.z = qa.z; scale = Math.sin(Math.PI * (0.5 - alpha)); invscale = Math.sin(Math.PI * alpha); } return new Quaternion( scale * qa.x + invscale * qb.x, scale * qa.y + invscale * qb.y, scale * qa.z + invscale * qb.z, scale * qa.w + invscale * qb.w ); } /** * SLERP (Spherical Linear intERPolation). * * @param qa start quaternion * @param qb end quaternion * @param alpha a value between 0 and 1 * * @return the interpolated quaternion. */ public static function slerpOld( qa:Quaternion, qb:Quaternion, alpha:Number ):Quaternion { var qm:Quaternion = new Quaternion(); // Calculate angle between them. var cosHalfTheta:Number = qa.w * qb.w + qa.x * qb.x + qa.y * qb.y + qa.z * qb.z; // if qa=qb or qa=-qb then theta = 0 and we can return qa if(Math.abs(cosHalfTheta) >= 1.0) { qm.w = qa.w; qm.x = qa.x; qm.y = qa.y; qm.z = qa.z; return qm; } // Calculate temporary values. var halfTheta:Number = Math.acos(cosHalfTheta); var sinHalfTheta:Number = Math.sqrt(1.0 - 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.001) { qm.w = (qa.w * 0.5 + qb.w * 0.5); qm.x = (qa.x * 0.5 + qb.x * 0.5); qm.y = (qa.y * 0.5 + qb.y * 0.5); qm.z = (qa.z * 0.5 + qb.z * 0.5); return qm; } var ratioA:Number = Math.sin((1 - alpha) * halfTheta) / sinHalfTheta; var ratioB:Number = Math.sin(alpha * halfTheta) / sinHalfTheta; //calculate Quaternion. qm.w = (qa.w * ratioA + qb.w * ratioB); qm.x = (qa.x * ratioA + qb.x * ratioB); qm.y = (qa.y * ratioA + qb.y * ratioB); qm.z = (qa.z * ratioA + qb.z * ratioB); return qm; } public function toEuler():Number3D { var euler :Number3D = new Number3D(); var q1 :Quaternion = this; var test :Number = q1.x*q1.y + q1.z*q1.w; if (test > 0.499) { // singularity at north pole euler.x = 2 * Math.atan2(q1.x,q1.w); euler.y = Math.PI/2; euler.z = 0; return euler; } if (test < -0.499) { // singularity at south pole euler.x = -2 * Math.atan2(q1.x,q1.w); euler.y = - Math.PI/2; euler.z = 0; return euler; } var sqx :Number = q1.x*q1.x; var sqy :Number = q1.y*q1.y; var sqz :Number = q1.z*q1.z; euler.x = Math.atan2(2*q1.y*q1.w-2*q1.x*q1.z , 1 - 2*sqy - 2*sqz); euler.y = Math.asin(2*test); euler.z = Math.atan2(2*q1.x*q1.w-2*q1.y*q1.z , 1 - 2*sqx - 2*sqz); return euler; } /** * Gets the matrix representation of this Quaternion. * * @return matrix. @see org.papervision3d.core.Matrix3D */ public function get matrix():Matrix3D { var xx:Number = x * x; var xy:Number = x * y; var xz:Number = x * z; var xw:Number = x * w; var yy:Number = y * y; var yz:Number = y * z; var yw:Number = y * w; var zz:Number = z * z; var zw:Number = z * w; _matrix.n11 = 1 - 2 * ( yy + zz ); _matrix.n12 = 2 * ( xy - zw ); _matrix.n13 = 2 * ( xz + yw ); _matrix.n21 = 2 * ( xy + zw ); _matrix.n22 = 1 - 2 * ( xx + zz ); _matrix.n23 = 2 * ( yz - xw ); _matrix.n31 = 2 * ( xz - yw ); _matrix.n32 = 2 * ( yz + xw ); _matrix.n33 = 1 - 2 * ( xx + yy ); return _matrix; } public static function sub(a:Quaternion, b:Quaternion):Quaternion { return new Quaternion(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); } public static function add(a:Quaternion, b:Quaternion):Quaternion { return new Quaternion(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); } } }