diff --git a/src/webglu.js b/src/webglu.js index c5b1766..bb251de 100644 --- a/src/webglu.js +++ b/src/webglu.js @@ -48,6 +48,12 @@ $W = { /** The raw WebGL object for low level work. */ GL : null, + /** Simulated world */ + world: { + /** Physical objects */ + objects: [] + }, + /** Renderable objects */ objects : [], @@ -905,6 +911,9 @@ $W = { /** @namespace Utility functions. */ util:{ + integrateEuler:function(fun, t, dt) { + return fun(t + dt); + }, /** XXX Not yet implemented. */ getPickRay : function(x, y) { @@ -1431,7 +1440,31 @@ $W = { } } } + }, + + /** @class A physical simulation. + */ + Simulation:function() { + $W.anim.ProceduralAnimation.call(this); // subclass of ProceduralAnimation + + /** Bounding sphere radius */ + this.radius = 1; + /** Object mass */ + this.mass = 1; + /** Object velocity */ + this.velocity = Vector.Zero(3); + /** Angular velocity */ + this.omega = Vector.Zero(3); + + this.update = function(dt) { + // calculate forces + // integrate position/rotation + // update momentum + // calculate velocities + } + } + }, @@ -1504,8 +1537,84 @@ $W = { }, // Classes + /** @class Quaternion implementation. + * based on reference implementation at + * http://3dengine.org/Quaternions + */ + Quaternion:function() { + // Called as Quaternion(x, y, z, theta) + if (arguments.length == 4) { + var angle = (arguments[3] / 180.0) * Math.PI; + var result = Math.sin(angle / 2); + this.w = Math.cos(angle / 2); + this.x = arguments[0] * result; + this.y = arguments[1] * result; + this.z = arguments[2] * result; + + // Called as Quaternion([w, x, y, z]) + }else if (arguments[0] != undefined && arguments[0].length == 4) { + this.w = arguments[0][0]; + this.x = arguments[0][1]; + this.y = arguments[0][2]; + this.z = arguments[0][3]; + + // Called as Quaternion() + }else { + this.w = 1; + this.x = 0; + this.y = 0; + this.z = 0; + } + + this.matrix = function() { + var w = this.w; + var x = this.x; + var y = this.y; + var z = this.z; + var xx = x * x; + var yy = y * y; + var zz = z * z; + var xy = x * y; + var xz = x * z; + var xw = x * w; + var yz = y * z; + var yw = y * w; + var zw = z * w; + + var m = [[],[],[],[]]; + + m[0][0] = 1 - 2 * (yy + zz); + m[0][1] = 2 * (xy + zw); + m[0][2] = 2 * (xz - yw); + m[0][3] = 0; + m[1][0] = 2 * (xy - zw); + m[1][1] = 1 - 2 * (xx + zz); + m[1][2] = 2 * (yz + xw); + m[1][3] = 0; + m[2][0] = 2 * (xz + yw); + m[2][1] = 2 * (yz - xw); + m[2][2] = 1 - 2 * (xx + yy); + m[2][3] = 0; + m[3][0] = 0; + m[3][1] = 0; + m[3][2] = 0; + m[3][3] = 1; + + return $M(m); + } + + this.multiply = function(q) { + var result = new $W.Quaternion(); + result.w = this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z; + result.x = this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y; + result.y = this.w * q.y + this.y * q.w + this.z * q.x - this.x * q.z; + result.z = this.w * q.z + this.z * q.w + this.x * q.y - this.y * q.x; + return result; + } + }, /** @class Quaternion implementation XXX broken */ + /* Quaternion:function(m) { this.vec = Vector.Zero(4); @@ -1535,6 +1644,7 @@ $W = { return result; } }, + */ /** @class Common state representation (position, rotation, scale). * A position, rotation, and scale. diff --git a/tests/test.html b/tests/test.html index d50fea3..5fa1253 100644 --- a/tests/test.html +++ b/tests/test.html @@ -12,7 +12,7 @@ - + @@ -73,6 +73,13 @@ WebGLUTestSuite.add( new YT.TestCase({ name:'Shader Tests', + setUp : function() { + $W.initialize(); + }, + + tearDown : function() { + $W.reset(); + }, test_newProgram : function() { @@ -84,6 +91,13 @@ WebGLUTestSuite.add( new YT.TestCase({ name:'Object Tests', + setUp : function() { + $W.initialize(); + }, + + tearDown : function() { + $W.reset(); + }, test_newObject : function() { @@ -94,6 +108,94 @@ }) ); + WebGLUTestSuite.add( + new YT.TestCase({ + name:'Math Tests', + setUp : function() { + $W.initialize(); + }, + + tearDown : function() { + $W.reset(); + }, + + test_newQuaternion : function() { + var q; + + var error1 = "Quaternion(x, y, z, theta) failed"; + var x1 = 1; + var y1 = 0; + var z1 = 0; + var theta = 90; + var expected_q1x = 0.7071; + var expected_q1y = 0; + var expected_q1z = 0; + var expected_q1w = 0.7071; + + var error2 = "Quaternion([w, x, y, z]) failed"; + var w2 = 0.1; + var x2 = 0.3; + var y2 = 0.3; + var z2 = 0.5; + + var error3 = "Quaternion() failed"; + + // Test Quaternion(x, y, z, theta) + q = new $W.Quaternion(x1, y1, z1, theta); + assert.areEqual(expected_q1x, q.x.toPrecision(4), error1); + assert.areEqual(expected_q1y, q.y.toPrecision(4), error1); + assert.areEqual(expected_q1z, q.z.toPrecision(4), error1); + assert.areEqual(expected_q1w, q.w.toPrecision(4), error1); + + // Test Quaternion([w, x, y, z]) + q = new $W.Quaternion([w2, x2, y2, z2]); + assert.areEqual(w2, q.w, error2); + assert.areEqual(x2, q.x, error2); + assert.areEqual(y2, q.y, error2); + assert.areEqual(z2, q.z, error2); + + // Test Quaternion() + q = new $W.Quaternion(); + assert.areEqual(1, q.w, error3); + assert.areEqual(0, q.x, error3); + assert.areEqual(0, q.y, error3); + assert.areEqual(0, q.z, error3); + }, + + test_multQuaternions : function() { + var roll = 30; + var pitch = 45; + var heading = 30; + var expected_glMatrix = + [0.57322332027622536, 0.73919890801437749, + -0.3535533898605015, 0.0, -0.3535533898605015, + 0.61237244705783134, 0.70710677171312086, 0.0, + 0.73919890801437749, -0.28033009198934633, + 0.61237244705783134, 0.0, 0, 0, 0, 1.0] + + var q1 = new $W.Quaternion(0, 0, 1, roll); + var q2 = new $W.Quaternion(1, 0, 0, pitch); + var q3 = new $W.Quaternion(0, 1, 0, heading); + + var qr = (q1.multiply(q2)).multiply(q3); + + var result_glMatrix = qr.matrix().elements.flatten(); + + console.log(expected_glMatrix); + console.log(result_glMatrix); + + for (var i = 0; i < expected_glMatrix.length; i++) { + // 7 digit precision because reference values were + // calculated with Pi accurate to just 7 digits. + assert.areEqual( + expected_glMatrix[i].toPrecision(7), + result_glMatrix[i].toPrecision(7), + "Quaternion multiplation failure"); + } + } + }) + ); + logger = new YAHOO.tool.TestLogger(); with(YAHOO.tool.TestRunner) { //subscribe(TEST_FAIL_EVENT, handleTestResult);