Browse Source

add new credits to Quaternion.js, expand Quaternion unit tests, move Matrix4 set* methods to standard file location.

Ben Houston 12 years ago
parent
commit
a1c4489a2e
3 changed files with 249 additions and 169 deletions
  1. 137 138
      src/math/Matrix4.js
  2. 35 12
      src/math/Quaternion.js
  3. 77 19
      test/math/Quaternion.js

+ 137 - 138
src/math/Matrix4.js

@@ -74,6 +74,143 @@ THREE.Matrix4.prototype = {
 
 	},
 
+	setRotationFromEuler: function ( v, order ) {
+
+		var te = this.elements;
+
+		var x = v.x, y = v.y, z = v.z;
+		var a = Math.cos( x ), b = Math.sin( x );
+		var c = Math.cos( y ), d = Math.sin( y );
+		var e = Math.cos( z ), f = Math.sin( z );
+
+		if ( order === undefined || order === 'XYZ' ) {
+
+			var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+			te[0] = c * e;
+			te[4] = - c * f;
+			te[8] = d;
+
+			te[1] = af + be * d;
+			te[5] = ae - bf * d;
+			te[9] = - b * c;
+
+			te[2] = bf - ae * d;
+			te[6] = be + af * d;
+			te[10] = a * c;
+
+		} else if ( order === 'YXZ' ) {
+
+			var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+			te[0] = ce + df * b;
+			te[4] = de * b - cf;
+			te[8] = a * d;
+
+			te[1] = a * f;
+			te[5] = a * e;
+			te[9] = - b;
+
+			te[2] = cf * b - de;
+			te[6] = df + ce * b;
+			te[10] = a * c;
+
+		} else if ( order === 'ZXY' ) {
+
+			var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+			te[0] = ce - df * b;
+			te[4] = - a * f;
+			te[8] = de + cf * b;
+
+			te[1] = cf + de * b;
+			te[5] = a * e;
+			te[9] = df - ce * b;
+
+			te[2] = - a * d;
+			te[6] = b;
+			te[10] = a * c;
+
+		} else if ( order === 'ZYX' ) {
+
+			var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+			te[0] = c * e;
+			te[4] = be * d - af;
+			te[8] = ae * d + bf;
+
+			te[1] = c * f;
+			te[5] = bf * d + ae;
+			te[9] = af * d - be;
+
+			te[2] = - d;
+			te[6] = b * c;
+			te[10] = a * c;
+
+		} else if ( order === 'YZX' ) {
+
+			var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+			te[0] = c * e;
+			te[4] = bd - ac * f;
+			te[8] = bc * f + ad;
+
+			te[1] = f;
+			te[5] = a * e;
+			te[9] = - b * e;
+
+			te[2] = - d * e;
+			te[6] = ad * f + bc;
+			te[10] = ac - bd * f;
+
+		} else if ( order === 'XZY' ) {
+
+			var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+			te[0] = c * e;
+			te[4] = - f;
+			te[8] = d * e;
+
+			te[1] = ac * f + bd;
+			te[5] = a * e;
+			te[9] = ad * f - bc;
+
+			te[2] = bc * f - ad;
+			te[6] = b * e;
+			te[10] = bd * f + ac;
+
+		}
+
+		return this;
+
+	},
+
+	setRotationFromQuaternion: function ( q ) {
+
+		var te = this.elements;
+
+		var x = q.x, y = q.y, z = q.z, w = q.w;
+		var x2 = x + x, y2 = y + y, z2 = z + z;
+		var xx = x * x2, xy = x * y2, xz = x * z2;
+		var yy = y * y2, yz = y * z2, zz = z * z2;
+		var wx = w * x2, wy = w * y2, wz = w * z2;
+
+		te[0] = 1 - ( yy + zz );
+		te[4] = xy - wz;
+		te[8] = xz + wy;
+
+		te[1] = xy + wz;
+		te[5] = 1 - ( xx + zz );
+		te[9] = yz - wx;
+
+		te[2] = xz - wy;
+		te[6] = yz + wx;
+		te[10] = 1 - ( xx + yy );
+
+		return this;
+
+	},
+
 	lookAt: function ( eye, target, up ) {
 
 		var te = this.elements;
@@ -440,144 +577,6 @@ THREE.Matrix4.prototype = {
 
 	},
 
-	setRotationFromEuler: function ( v, order ) {
-
-		var te = this.elements;
-
-		var x = v.x, y = v.y, z = v.z;
-		var a = Math.cos( x ), b = Math.sin( x );
-		var c = Math.cos( y ), d = Math.sin( y );
-		var e = Math.cos( z ), f = Math.sin( z );
-
-		if ( order === undefined || order === 'XYZ' ) {
-
-			var ae = a * e, af = a * f, be = b * e, bf = b * f;
-
-			te[0] = c * e;
-			te[4] = - c * f;
-			te[8] = d;
-
-			te[1] = af + be * d;
-			te[5] = ae - bf * d;
-			te[9] = - b * c;
-
-			te[2] = bf - ae * d;
-			te[6] = be + af * d;
-			te[10] = a * c;
-
-		} else if ( order === 'YXZ' ) {
-
-			var ce = c * e, cf = c * f, de = d * e, df = d * f;
-
-			te[0] = ce + df * b;
-			te[4] = de * b - cf;
-			te[8] = a * d;
-
-			te[1] = a * f;
-			te[5] = a * e;
-			te[9] = - b;
-
-			te[2] = cf * b - de;
-			te[6] = df + ce * b;
-			te[10] = a * c;
-
-		} else if ( order === 'ZXY' ) {
-
-			var ce = c * e, cf = c * f, de = d * e, df = d * f;
-
-			te[0] = ce - df * b;
-			te[4] = - a * f;
-			te[8] = de + cf * b;
-
-			te[1] = cf + de * b;
-			te[5] = a * e;
-			te[9] = df - ce * b;
-
-			te[2] = - a * d;
-			te[6] = b;
-			te[10] = a * c;
-
-		} else if ( order === 'ZYX' ) {
-
-			var ae = a * e, af = a * f, be = b * e, bf = b * f;
-
-			te[0] = c * e;
-			te[4] = be * d - af;
-			te[8] = ae * d + bf;
-
-			te[1] = c * f;
-			te[5] = bf * d + ae;
-			te[9] = af * d - be;
-
-			te[2] = - d;
-			te[6] = b * c;
-			te[10] = a * c;
-
-		} else if ( order === 'YZX' ) {
-
-			var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
-
-			te[0] = c * e;
-			te[4] = bd - ac * f;
-			te[8] = bc * f + ad;
-
-			te[1] = f;
-			te[5] = a * e;
-			te[9] = - b * e;
-
-			te[2] = - d * e;
-			te[6] = ad * f + bc;
-			te[10] = ac - bd * f;
-
-		} else if ( order === 'XZY' ) {
-
-			var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
-
-			te[0] = c * e;
-			te[4] = - f;
-			te[8] = d * e;
-
-			te[1] = ac * f + bd;
-			te[5] = a * e;
-			te[9] = ad * f - bc;
-
-			te[2] = bc * f - ad;
-			te[6] = b * e;
-			te[10] = bd * f + ac;
-
-		}
-
-		return this;
-
-	},
-
-
-	setRotationFromQuaternion: function ( q ) {
-
-		var te = this.elements;
-
-		var x = q.x, y = q.y, z = q.z, w = q.w;
-		var x2 = x + x, y2 = y + y, z2 = z + z;
-		var xx = x * x2, xy = x * y2, xz = x * z2;
-		var yy = y * y2, yz = y * z2, zz = z * z2;
-		var wx = w * x2, wy = w * y2, wz = w * z2;
-
-		te[0] = 1 - ( yy + zz );
-		te[4] = xy - wz;
-		te[8] = xz + wy;
-
-		te[1] = xy + wz;
-		te[5] = 1 - ( xx + zz );
-		te[9] = yz - wx;
-
-		te[2] = xz - wy;
-		te[6] = yz + wx;
-		te[10] = 1 - ( xx + yy );
-
-		return this;
-
-	},
-
 	compose: function ( translation, rotation, scale ) {
 
 		var te = this.elements;

+ 35 - 12
src/math/Quaternion.js

@@ -2,6 +2,8 @@
  * @author mikael emtinger / http://gomo.se/
  * @author alteredq / http://alteredqualia.com/
  * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ * @author Hasan Kamal-Al-Deen / [email protected]
  */
 
 THREE.Quaternion = function( x, y, z, w ) {
@@ -196,6 +198,28 @@ THREE.Quaternion.prototype = {
 
 	},
 
+	sub: function ( a, b ) {
+
+		this.x = a.x - b.x;
+		this.y = a.y - b.y;
+		this.z = a.z - b.z;
+		this.w = a.w - b.w;
+
+		return this;
+
+	},
+
+	subSelf: function ( v ) {
+
+		this.x -= v.x;
+		this.y -= v.y;
+		this.z -= v.z;
+		this.w -= v.w;
+
+		return this;
+
+	},
+
 	inverse: function () {
 
 		this.conjugate().normalize();
@@ -214,15 +238,21 @@ THREE.Quaternion.prototype = {
 
 	},
 
+	lengthSq: function () {
+
+		return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
+
+	},
+
 	length: function () {
 
-		return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
+		return Math.sqrt( this.lengthSq() );
 
 	},
 
 	normalize: function () {
 
-		var l = Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
+		var l = this.length();
 
 		if ( l === 0 ) {
 
@@ -248,21 +278,14 @@ THREE.Quaternion.prototype = {
 
 	multiply: function ( a, b ) {
 
-		// from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
-		var qax = a.x, qay = a.y, qaz = a.z, qaw = a.w,
-		qbx = b.x, qby = b.y, qbz = b.z, qbw = b.w;
-
-		this.x =  qax * qbw + qay * qbz - qaz * qby + qaw * qbx;
-		this.y = -qax * qbz + qay * qbw + qaz * qbx + qaw * qby;
-		this.z =  qax * qby - qay * qbx + qaz * qbw + qaw * qbz;
-		this.w = -qax * qbx - qay * qby - qaz * qbz + qaw * qbw;
-
-		return this;
+		this.copy( a );
+		return this.multiplySelf( b );
 
 	},
 
 	multiplySelf: function ( b ) {
 
+		// from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
 		var qax = this.x, qay = this.y, qaz = this.z, qaw = this.w,
 		qbx = b.x, qby = b.y, qbz = b.z, qbw = b.w;
 

+ 77 - 19
test/math/Quaternion.js

@@ -4,6 +4,10 @@
 
 module( "Quaternion" );
 
+var orders = [ 'XYZ', 'YXZ', 'ZXY', 'ZYX', 'YZX', 'XZY' ];
+var eulerAngles = new THREE.Vector3( 0.1, -0.3, 0.25 );
+
+
 test( "constructor", function() {
 	var a = new THREE.Quaternion();
 	ok( a.x == 0, "Passed!" );
@@ -72,21 +76,34 @@ test( "setFromAxisAngle", function() {
 	ok( a.equals( b1 ), "Passed!" );
 });
 
-test( "setFromRotationMatrix", function() {
 
-	// TODO: find cases to validate.
-	ok( true, "Passed!" );
+test( "setFromEuler/toEuler", function() {
+
+	var angles = [ new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ) ];
+
+	for( var i = 0; i < angles.length; i ++ ) {
+		// check only supported toEuler format
+		var eulers2 = new THREE.Quaternion().setFromEuler( angles[i], "XYZ" ).toEuler( "XYZ" );
+		console.log( eulerAngles)
+		ok( eulers2.distanceTo( angles[i] ) < 0.001, "Passed!" );
+	}
 
 });
 
-test( "fromEuler/toEuler", function() {
+test( "setFromEuler/setFromRotationMatrix", function() {
 
-	// TODO: find cases to validate.
-	ok( true, "Passed!" );
+	// ensure euler conversion for Quaternion matches that of Matrix4
+	for( var i = 0; i < orders.length; i ++ ) {
+		var q = new THREE.Quaternion().setFromEuler( eulerAngles, orders[i] );
+		var m = new THREE.Matrix4().setRotationFromEuler( eulerAngles, orders[i] );
+		var q2 = new THREE.Quaternion().setFromRotationMatrix( m );
+
+		ok( q.subSelf( q2 ).length() < 0.001, "Passed!" );
+	}
 
 });
 
-test( "add", function() {
+test( "add/addSelf", function() {
 	var a = new THREE.Quaternion( x, y, z, w );
 	var b = new THREE.Quaternion( -x, -y, -z, -w );
 
@@ -103,17 +120,38 @@ test( "add", function() {
 	ok( c.w == -2*w, "Passed!" );	
 });
 
-test( "normalize/length", function() {
+test( "sub/subSelf", function() {
+	var a = new THREE.Quaternion( x, y, z, w );
+	var b = new THREE.Quaternion( -x, -y, -z, -w );
+
+	a.subSelf( b );
+	ok( a.x == 2*x, "Passed!" );
+	ok( a.y == 2*y, "Passed!" );
+	ok( a.z == 2*z, "Passed!" );
+	ok( a.w == 2*w, "Passed!" );
+
+	var c = new THREE.Quaternion().sub( b, b );
+	ok( c.x == 0, "Passed!" );
+	ok( c.y == 0, "Passed!" );	
+	ok( c.z == 0, "Passed!" );
+	ok( c.w == 0, "Passed!" );	
+});
+
+test( "normalize/length/lengthSq", function() {
 	var a = new THREE.Quaternion( x, y, z, w );
 	var b = new THREE.Quaternion( -x, -y, -z, -w );
 
 	ok( a.length() != 1, "Passed!");
+	ok( a.lengthSq() != 1, "Passed!");
 	a.normalize();
 	ok( a.length() == 1, "Passed!");
+	ok( a.lengthSq() == 1, "Passed!");
 
 	a.set( 0, 0, 0, 0 );
+	ok( a.lengthSq() == 0, "Passed!");
 	ok( a.length() == 0, "Passed!");
 	a.normalize();
+	ok( a.lengthSq() == 1, "Passed!");
 	ok( a.length() == 1, "Passed!");
 });
 
@@ -132,23 +170,43 @@ test( "inverse/conjugate", function() {
 
 
 test( "multiply/multiplySelf", function() {
+	
+	var angles = [ new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ) ];
 
-	// TODO: find cases to validate.
-	ok( true, "Passed!" );
+	var q1 = new THREE.Quaternion().setFromEuler( angles[0], "XYZ" );
+	var q2 = new THREE.Quaternion().setFromEuler( angles[1], "XYZ" );
+	var q3 = new THREE.Quaternion().setFromEuler( angles[2], "XYZ" );
 
-});
+	var q = new THREE.Quaternion().multiply( q1, q2 ).multiplySelf( q3 );
 
-test( "multiplyVector3", function() {
-	
-	// TODO: find cases to validate.
-	ok( true, "Passed!" );
+	var m1 = new THREE.Matrix4().setRotationFromEuler( angles[0], "XYZ" );
+	var m2 = new THREE.Matrix4().setRotationFromEuler( angles[1], "XYZ" );
+	var m3 = new THREE.Matrix4().setRotationFromEuler( angles[2], "XYZ" );
 
-});
+	var m = new THREE.Matrix4().multiply( m1, m2 ).multiplySelf( m3 );
 
-test( "slerpSelf/slerp", function() {
+	var qFromM = new THREE.Quaternion().setFromRotationMatrix( m );
 
-	// TODO: find cases to validate.
-	ok( true, "Passed!" );
+	ok( q.subSelf( qFromM ).length() < 0.001, "Passed!" );
+});
+
+test( "multiplyVector3", function() {
+	
+	var angles = [ new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ) ];
+
+	// ensure euler conversion for Quaternion matches that of Matrix4
+	for( var i = 0; i < orders.length; i ++ ) {
+		for( var j = 0; j < angles.length; j ++ ) {
+			var q = new THREE.Quaternion().setFromEuler( angles[j], orders[i] );
+			var m = new THREE.Matrix4().setRotationFromEuler( angles[j], orders[i] );
+
+			var v0 = new THREE.Vector3(1, 0, 0);
+			var qv = q.multiplyVector3( v0.clone() );
+			var mv = m.multiplyVector3( v0.clone() );
+		
+			ok( qv.distanceTo( mv ) < 0.001, "Passed!" );
+		}
+	}
 
 });