diff --git a/src/math.js b/src/math.js index a634e42c..bbd4c66b 100644 --- a/src/math.js +++ b/src/math.js @@ -644,6 +644,505 @@ Crafty.math.Vector2D = (function () { return Vector2D; })(); +Crafty.math.Vector3D = (function () { + /**@ + * #Crafty.math.Vector3D + * @category 2D + * @class This is a general purpose 3D vector class + * + * Vector3D uses the following form: + * + * + * @public + * @sign public {Vector3D} Vector3D(); + * @sign public {Vector3D} Vector3D(Vector3D); + * @sign public {Vector3D} Vector3D(Number, Number, Number); + * @param {Vector3D|Number=0} x + * @param {Number=0} y + * @param {Number=0} z + */ + + function Vector3D(x, y, z) { + if (x instanceof Vector3D) { + this.x = x.x; + this.y = x.y; + this.z = x.z; + } else if (arguments.length === 3) { + this.x = x; + this.y = y; + this.z = z; + } else if (arguments.length > 0) + throw "Unexpected number of arguments for Vector3D()"; + } // class Vector3D + + Vector3D.prototype.x = 0; + Vector3D.prototype.y = 0; + Vector3D.prototype.z = 0; + + /**@ + * #.add + * @comp Crafty.math.Vector3D + * + * Adds the passed vector to this vector + * + * @public + * @sign public {Vector3D} add(Vector3D); + * @param {Vector3D} vecRH + * @returns {Vector3D} this after adding + */ + Vector3D.prototype.add = function (vecRH) { + this.x += vecRH.x; + this.y += vecRH.y; + this.z += vecRH.z; + return this; + }; // add + + /**@ + * #.angleBetween + * @comp Crafty.math.Vector3D + * + * Calculates the angle between the passed vector and this vector, using <0,0,0> as the point of reference. + * Angles returned have the range (−π, π]. + * Taken from [John Blackburne's Blog](http://johnblackburne.blogspot.co.at/2012/05/angle-between-two-3d-vectors.html) + * + * + * @public + * @sign public {Number} angleBetween(Vector3D); + * @param {Vector3D} vecRH + * @returns {Number} the angle between the two vectors in radians + */ + Vector3D.prototype.angleBetween = (function() { + var tempVec = new Vector3D(); + + return function (vecRH) { + var fCross = this.crossProduct(vecRH, tempVec).magnitude(); + var fDot = this.dotProduct(vecRH); + + return Math.atan2(fCross, fDot); + };})(); // angleBetween + + /**@ + * #.angleTo + * @comp Crafty.math.Vector3D + * + * Calculates the angle to the passed vector from <1,0,0>, using this vector as the point of origin. + * + * @public + * @sign public {Number} angleTo(Vector3D); + * @param {Vector3D} vecRH + * @returns {Number} the angle to the passed vector in radians + */ + Vector3D.prototype.angleTo = (function() { + var tempVec = new Vector3D(); + + return function (vecRH) { + tempVec.setValues(vecRH).subtract(this); // temp = other - this + return Math.atan2(Math.sqrt(tempVec.y*tempVec.y + tempVec.z*tempVec.z), tempVec.x); + };})(); // angleTo + + /**@ + * #.clone + * @comp Crafty.math.Vector3D + * + * Creates and exact, numeric copy of this vector + * + * @public + * @sign public {Vector3D} clone(); + * @returns {Vector3D} the new vector + */ + Vector3D.prototype.clone = function () { + return new Vector3D(this); + }; // clone + + /**@ + * #.distance + * @comp Crafty.math.Vector3D + * + * Calculates the distance from this vector to the passed vector. + * + * @public + * @sign public {Number} distance(Vector3D); + * @param {Vector3D} vecRH + * @returns {Number} the distance between the two vectors + */ + Vector3D.prototype.distance = function (vecRH) { + return Math.sqrt( + (vecRH.x - this.x) * (vecRH.x - this.x) + + (vecRH.y - this.y) * (vecRH.y - this.y) + + (vecRH.z - this.z) * (vecRH.z - this.z) + ); + }; // distance + + /**@ + * #.distanceSq + * @comp Crafty.math.Vector3D + * + * Calculates the squared distance from this vector to the passed vector. + * This function avoids calculating the square root, thus being slightly faster than .distance( ). + * + * @public + * @sign public {Number} distanceSq(Vector3D); + * @param {Vector3D} vecRH + * @returns {Number} the squared distance between the two vectors + * @see .distance + */ + Vector3D.prototype.distanceSq = function (vecRH) { + return ( + (vecRH.x - this.x) * (vecRH.x - this.x) + + (vecRH.y - this.y) * (vecRH.y - this.y) + + (vecRH.z - this.z) * (vecRH.z - this.z) + ); + }; // distanceSq + + /**@ + * #.divide + * @comp Crafty.math.Vector3D + * + * Divides this vector by the passed vector. + * + * @public + * @sign public {Vector3D} divide(Vector3D); + * @param {Vector3D} vecRH + * @returns {Vector3D} this vector after dividing + */ + Vector3D.prototype.divide = function (vecRH) { + this.x /= vecRH.x; + this.y /= vecRH.y; + this.z /= vecRH.z; + return this; + }; // divide + + /**@ + * #.dotProduct + * @comp Crafty.math.Vector3D + * + * Calculates the dot product of this and the passed vectors + * + * @public + * @sign public {Number} dotProduct(Vector3D); + * @param {Vector3D} vecRH + * @returns {Number} the resultant dot product + */ + Vector3D.prototype.dotProduct = function (vecRH) { + return this.x * vecRH.x + this.y * vecRH.y + this.z * vecRH.z; + }; // dotProduct + + /**@ + * #.crossProduct + * @comp Crafty.math.Vector3D + * + * Calculates the cross product of this and the passed vectors. + * + * @public + * @sign public {Vector3D} crossProduct(Vector3D[, Vector3D]); + * @param {Vector3D} vecRH + * @param {Vector3D} [result] optional vector to store the result in + * @returns {Vector3D} the resultant cross product + */ + Vector3D.prototype.crossProduct = function (vecRH, result) { + result = result || new Vector3D(); + result.setValues(this.y * vecRH.z - this.z * vecRH.y, this.z * vecRH.x - this.x * vecRH.z, this.x * vecRH.y - this.y * vecRH.x); + return result; + }; // crossProduct + + /**@ + * #.equals + * @comp Crafty.math.Vector3D + * + * Determines if this vector is numerically equivalent to the passed vector. + * + * @public + * @sign public {Boolean} equals(Vector3D); + * @param {Vector3D} vecRH + * @returns {Boolean} true if the vectors are equivalent + */ + Vector3D.prototype.equals = function (vecRH) { + return vecRH instanceof Vector3D && + this.x == vecRH.x && this.y == vecRH.y && this.z == vecRH.z; + }; // equals + + /**@ + * #.getNormal + * @comp Crafty.math.Vector3D + * + * Calculates a new right-handed unit vector that is perpendicular to the plane defined by this vector and the other vector. + * + * @public + * @sign public {Vector3D} getNormal(Vector3D); + * @param {Vector3D} vecRH + * @param {Vector3D} [result] the vector to store the result in + * @returns {Vector3D} the new normal vector + */ + Vector3D.prototype.getNormal = function (vecRH, result) { + result = result || new Vector3D(); + result.setValues(this); + return result.crossProduct(vecRH).normalize(); + }; // getNormal + + /**@ + * #.isZero + * @comp Crafty.math.Vector3D + * + * Determines if this vector is equal to <0,0,0> + * + * @public + * @sign public {Boolean} isZero(); + * @returns {Boolean} true if this vector is equal to <0,0,0> + */ + Vector3D.prototype.isZero = function () { + return this.x === 0 && this.y === 0 && this.z === 0; + }; // isZero + + /**@ + * #.magnitude + * @comp Crafty.math.Vector3D + * + * Calculates the magnitude of this vector. + * Note: Function objects in JavaScript already have a 'length' member, hence the use of magnitude instead. + * + * @public + * @sign public {Number} magnitude(); + * @returns {Number} the magnitude of this vector + */ + Vector3D.prototype.magnitude = function () { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + }; // magnitude + + /**@ + * #.magnitudeSq + * @comp Crafty.math.Vector3D + * + * Calculates the square of the magnitude of this vector. + * This function avoids calculating the square root, thus being slightly faster than .magnitude( ). + * + * @public + * @sign public {Number} magnitudeSq(); + * @returns {Number} the square of the magnitude of this vector + * @see .magnitude + */ + Vector3D.prototype.magnitudeSq = function () { + return this.x * this.x + this.y * this.y + this.z * this.z; + }; // magnitudeSq + + /**@ + * #.multiply + * @comp Crafty.math.Vector3D + * + * Multiplies this vector by the passed vector + * + * @public + * @sign public {Vector3D} multiply(Vector3D); + * @param {Vector3D} vecRH + * @returns {Vector3D} this vector after multiplying + */ + Vector3D.prototype.multiply = function (vecRH) { + this.x *= vecRH.x; + this.y *= vecRH.y; + this.z *= vecRH.z; + return this; + }; // multiply + + /**@ + * #.negate + * @comp Crafty.math.Vector3D + * + * Negates this vector (ie. <-x,-y,-z>) + * + * @public + * @sign public {Vector3D} negate(); + * @returns {Vector3D} this vector after negation + */ + Vector3D.prototype.negate = function () { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + return this; + }; // negate + + /**@ + * #.normalize + * @comp Crafty.math.Vector3D + * + * Normalizes this vector (scales the vector so that its new magnitude is 1) + * For vectors where magnitude is 0, <1,0,0> is returned. + * + * @public + * @sign public {Vector3D} normalize(); + * @returns {Vector3D} this vector after normalization + */ + Vector3D.prototype.normalize = function () { + var lng = this.magnitude(); + + if (lng === 0) { + // default due East + this.x = 1; + this.y = 0; + this.z = 0; + } else { + this.x /= lng; + this.y /= lng; + this.z /= lng; + } // else + + return this; + }; // normalize + + /**@ + * #.scale + * @comp Crafty.math.Vector3D + * + * Scales this vector by the passed amount(s) + * If scalarY or scalarZ is omitted, scalarX is used for all axes + * + * @public + * @sign public {Vector3D} scale(Number[, Number, Number]); + * @param {Number} scalarX + * @param {Number} [scalarY] + * @param {Number} [scalarZ] + * @returns {Vector3D} this after scaling + */ + Vector3D.prototype.scale = function (scalarX, scalarY, scalarZ) { + if (scalarY === undefined || scalarZ === undefined) { + scalarZ = scalarY = scalarX; + } + + this.x *= scalarX; + this.y *= scalarY; + this.z *= scalarZ; + + return this; + }; // scale + + /**@ + * #.scaleToMagnitude + * @comp Crafty.math.Vector3D + * + * Scales this vector such that its new magnitude is equal to the passed value. + * + * @public + * @sign public {Vector3D} scaleToMagnitude(Number); + * @param {Number} mag + * @returns {Vector3D} this vector after scaling + */ + Vector3D.prototype.scaleToMagnitude = function (mag) { + var k = mag / this.magnitude(); + this.x *= k; + this.y *= k; + this.z *= k; + return this; + }; // scaleToMagnitude + + /**@ + * #.setValues + * @comp Crafty.math.Vector3D + * + * Sets the values of this vector using a passed vector or a triplet of numbers. + * + * @public + * @sign public {Vector3D} setValues(Vector3D); + * @sign public {Vector3D} setValues(Number, Number, Number); + * @param {Number|Vector3D} x + * @param {Number} y + * @param {Number} z + * @returns {Vector3D} this vector after setting of values + */ + Vector3D.prototype.setValues = function (x, y, z) { + if (x instanceof Vector3D) { + this.x = x.x; + this.y = x.y; + this.z = x.z; + } else { + this.x = x; + this.y = y; + this.z = z; + } // else + + return this; + }; // setValues + + /**@ + * #.subtract + * @comp Crafty.math.Vector3D + * + * Subtracts the passed vector from this vector. + * + * @public + * @sign public {Vector3D} subtract(Vector3D); + * @param {Vector3D} vecRH + * @returns {Vector3D} this vector after subtracting + */ + Vector3D.prototype.subtract = function (vecRH) { + this.x -= vecRH.x; + this.y -= vecRH.y; + this.z -= vecRH.z; + return this; + }; // subtract + + /**@ + * #.toString + * @comp Crafty.math.Vector3D + * + * Returns a string representation of this vector. + * + * @public + * @sign public {String} toString(); + * @returns {String} + */ + Vector3D.prototype.toString = function () { + return "Vector3D(" + this.x + ", " + this.y + ", " + this.z + ")"; + }; // toString + + /**@ + * #.translate + * @comp Crafty.math.Vector3D + * + * Translates (moves) this vector by the passed amounts. + * If dy or dz is omitted, dx is used for all axes. + * + * @public + * @sign public {Vector3D} translate(Number[, Number, Number]); + * @param {Number} dx + * @param {Number} [dy] + * @param {Number} [dz] + * @returns {Vector3D} this vector after translating + */ + Vector3D.prototype.translate = function (dx, dy, dz) { + if (dy === undefined || dz === undefined) + dz = dy = dx; + + this.x += dx; + this.y += dy; + this.z += dz; + + return this; + }; // translate + + /**@ + * #.tripleProduct + * @comp Crafty.math.Vector3D + * + * Calculates the triple product of three vectors. + * triple vector product = b(a•c) - a(b•c) + * + * @public + * @static + * @sign public {Vector3D} tripleProduct(Vector3D, Vector3D, Vector3D[, Vector3D]); + * @param {Vector3D} a + * @param {Vector3D} b + * @param {Vector3D} c + * @param {Vector3D} [result] optional vector to save the result to + * @return {Vector3D} the triple product as a new vector + */ + Vector3D.tripleProduct = function (a, b, c, result) { + result = result || new Crafty.math.Vector3D(); + var ac = a.dotProduct(c); + var bc = b.dotProduct(c); + return result.setValues(b.x * ac - a.x * bc, b.y * ac - a.y * bc, b.z * ac - a.z * bc); + }; + + return Vector3D; +})(); + Crafty.math.Matrix2D = (function () { /**@ * #Crafty.math.Matrix2D diff --git a/tests/math.js b/tests/math.js index ecd9cb4a..e1710c07 100644 --- a/tests/math.js +++ b/tests/math.js @@ -1,5 +1,6 @@ var Matrix2D = Crafty.math.Matrix2D; var Vector2D = Crafty.math.Vector2D; +var Vector3D = Crafty.math.Vector3D; // tests for general functions should go here (.abs(), .amountOf(), etc) @@ -217,6 +218,229 @@ test("tripleProduct()", function() { equal(Vector2D.tripleProduct(va, vb, vc).equals(vtp), true, "tripleProduct(<1,2>, <3,4>, <5,6>) = <10,-12>"); }); +module("Math - Vector3D", { + setup: function() { + // prepare something for all following tests + }, + teardown: function() { + // clean up after each test + Crafty("*").destroy(); + } +}); + +test("constructor", function() { + var v0 = new Vector3D(); + var v000 = new Vector3D(0, 0, 0); + var v123 = new Vector3D(1, 2, 3); + var v123_2 = new Vector3D(v123); + + equal(v0.equals(v000), true, "new Vector3D() equals new Vector3D(0, 0, 0)"); + equal(v123.equals(v123_2), true, "new Vector3D(1, 2, 3) equals new Vector3D(new Vector3D(1,2,3))"); +}); + +test("add()", function() { + var v12 = new Vector3D(1, 2, 5); + var v34 = new Vector3D(3, 4, 7); + var v = new Vector3D(4, 6, 12); + + equal(v12.add(v34).equals(v), true, "<1,2,5> + <3,4,7> = <4,6,12>"); +}); + +test("angleBetween()", function() { + var v100 = new Vector3D(1, 0, 0); + var v_101 = new Vector3D(-1, 0, 1); + var v0_11 = new Vector3D(0, -1, 1); + + equal(v100.angleBetween(v_101), 3 * Math.PI / 4, "<1,0,0>.angleBetween(<-1,0,1>) = 3*PI/4"); + equal(v100.angleBetween(v0_11), Math.PI / 2, "<1,0,0>.angleBetween(<0,-1,1>) = PI/2"); +}); + +test("angleTo()", function() { + var v123 = new Vector3D(3, 2, 1); + var v321 = new Vector3D(1, 2, 3); + var result = new Vector3D(v321).subtract(v123).angleBetween(new Vector3D(1,0,0)); + var result2 = v123.angleTo(v321); + + equal(result, result2, "<1,2,3>.angleTo(<3,2,1>) = (<3,2,1>-<1,2,3>).angleBetween(<1,0,0>)"); +}); + +test("clone()", function() { + var v0 = new Vector3D(); + var v3_72 = new Vector3D(3, -7, 2); + + equal(v0.equals(v0.clone()), true, "<0,0,0> = <0,0,0>.clone()"); + equal(v3_72.clone().equals(v3_72), true, "<3,-7,2>.clone() = <3,-7,2>"); +}); + +test("distance()", function() { + var v0 = new Vector3D(); + var v100 = new Vector3D(1, 0, 0); + var v110 = new Vector3D(1, 1, 0); + + equal(v100.distance(v110), 1, "<1,0,0>.distance(<1,1,0>) = 1"); + equal(v0.distance(v110), Math.sqrt(2), "<0,0,0>.distance(<1,1,0>) = sqrt(2)"); +}); + +test("distanceSq()", function() { + var v0 = new Vector3D(); + var v100 = new Vector3D(1, 0, 0); + var v110 = new Vector3D(1, 1, 0); + + equal(v100.distanceSq(v110), 1, "<1,0,0>.distanceSq(<1,1,0>) = 1"); + equal(v0.distanceSq(v110), 2, "<0,0,0>.distanceSq(<1,1,0>) = 2"); +}); + +test("divide()", function() { + var v125 = new Vector3D(1, 2, 5); + var v3410 = new Vector3D(3, 4, 10); + var v = new Vector3D(1 / 3, 2 / 4, 5 / 10); + + equal(v125.divide(v3410).equals(v), true, "<1,2,5> / <3,4,6> = <1/3,1/2,1/2>"); +}); + +test("dotProduct()", function() { + var v127 = new Vector3D(1, 2, 7); + var v348 = new Vector3D(3, 4, 8); + var v469 = new Vector3D(4, 6, 9); + + equal(v127.dotProduct(v348), 67, "<1,2,7>.dotProduct(<3,4,8>) = 67"); + equal(v348.dotProduct(v469), 108, "<3,4,8>.dotProduct(<4,6,9>) = 108"); + equal(v469.dotProduct(v127), 79, "<4,6,9>.dotProduct(<1,2,7>) = 79"); +}); + +test("crossProduct()", function() { + var v127 = new Vector3D(1, 2, 7); + var v348 = new Vector3D(3, 4, 8); + var v469 = new Vector3D(4, 6, 9); + + equal(v127.crossProduct(v348).equals(new Vector3D(-12,13,-2)), true, "<1,2,7>.crossProduct(<3,4,8>) = <-12,13,-2>"); + equal(v348.crossProduct(v469).equals(new Vector3D(-12,5,2)), true, "<3,4,8>.crossProduct(<4,6,9>) = <-12,5,2>"); + equal(v469.crossProduct(v127).equals(new Vector3D(24,-19,2)), true, "<4,6,9>.crossProduct(<1,2,7>) = <24,-19,2>"); +}); + +test("equals()", function() { + var v127 = new Vector3D(1, 2, 7); + var v348 = new Vector3D(3, 4, 8); + var v469 = new Vector3D(4, 6, 9); + + equal(v127.equals(new Vector3D(1, 2, 7)), true, "<1,2,7>.equals(<1,2,7>) = true"); + equal(v348.equals(new Vector3D(3, 4, 8)), true, "<3,4,8>.equals(<3,4,8>) = true"); + equal(v469.equals(new Vector3D(4, 6, 9)), true, "<4,6,9>.equals(<4,6,9>) = true"); +}); + +test("getNormal()", function() { + var v100 = new Vector3D(1, 0, 0); + var v321 = new Vector3D(3, 2, 1); + + equal(v100.getNormal(v321).equals((new Vector3D(0, -1, 2)).normalize()), true, "<1,0,0>.getNormal(<3,2,1>) = <0, -1/sqrt(5), 2/sqrt(5)>"); +}); + +test("isZero()", function() { + var v0 = new Vector3D(); + var v102 = new Vector3D(1, 0, 2); + + equal(v0.isZero(), true, "<0,0,0>.isZero() = true"); + equal(v102.isZero(), false, "<1,0,2>.isZero() = false"); +}); + +test("magnitude()", function() { + var v0 = new Vector3D(); + var v100 = new Vector3D(1, 0, 0); + var v_792 = new Vector3D(-7, 9, 2); + + equal(v0.magnitude(), 0, "<0,0,0>.magnitude() = 0"); + equal(v100.magnitude(), 1, "<1,0,0>.magnitude() = 1"); + equal(v_792.magnitude(), Math.sqrt(134), "<-7,9,2>.magnitude() = Math.sqrt(134)"); +}); + +test("magnitudeSq()", function() { + var v0 = new Vector3D(); + var v100 = new Vector3D(1, 0, 0); + var v_792 = new Vector3D(-7, 9, 2); + + equal(v0.magnitudeSq(), 0, "<0,0,0>.magnitudeSq() = 0"); + equal(v100.magnitudeSq(), 1, "<1,0,0>.magnitudeSq() = 1"); + equal(v_792.magnitudeSq(), 134, "<-7,9,2>.magnitudeSq() = 134"); +}); + +test("multiply()", function() { + var v125 = new Vector3D(1, 2, 5); + var v346 = new Vector3D(3, 4, 6); + var v = new Vector3D(3, 8, 30); + + equal(v125.multiply(v346).equals(v), true, "<1,2,5> * <3,4,6> = <3,8,30>"); +}); + +test("negate()", function() { + var v_79_2 = new Vector3D(-7, 9, -2); + var v7_92 = new Vector3D(7, -9, 2); + + equal(v_79_2.negate().equals(v7_92), true, "<-7,9,-2>.negate() = <7,-9,2>"); +}); + +test("normalize()", function() { + var v0 = new Vector3D(); + var v010 = new Vector3D(0, 1, 0); + var v_48_2 = new Vector3D(-4, 8, -2); + + equal(v0.normalize().equals(new Vector3D(1, 0, 0)), true, "<0,0,0>.normalize() = <1,0,0>"); + equal(v010.normalize().equals(new Vector3D(0, 1, 0)), true, "<0,1,0>.normalize() = <0,1,0>"); + equal(v_48_2.normalize().equals(new Vector3D(-2/Math.sqrt(21), 4/Math.sqrt(21), -1/Math.sqrt(21))), true, + "<-4,8,-2>.normalize() = <-2/Math.sqrt(21), 4/Math.sqrt(21), -1/Math.sqrt(21)>"); +}); + +test("scale()", function() { + var v112 = new Vector3D(1, 1, 2); + + equal(v112.scale(2).equals(new Vector3D(2, 2, 4)), true, "<1,1,2>.scale(2) = <2,2,4>"); + equal(v112.scale(2, -3, -1).equals(new Vector3D(4, -6, -4)), true, "<2,2,4>.scale(2, -3, -1) = <4,-6, -4>"); +}); + +test("magnitudeSq()", function() { + var v345 = new Vector3D(3, 4, 12); + + equal(v345.normalize().scaleToMagnitude(13).equals(new Vector3D(3, 4, 12)), true, "<3,4,12>.normalize().scaleToMagnitude(13) = <3,4,12>"); +}); + +test("setValues", function() { + var v0 = new Vector3D(); + var v126 = new Vector3D(1, 2, 6); + var v448 = new Vector3D(4, 4, 8); + + equal(v0.setValues(1, 2, 6).equals(v126), true, "<0,0,0>.setValues(<1,2,6>) = <1,2,6>"); + equal(v0.setValues(v448).equals(v448), true, "<1,2,6>.setValues(<4,4,8>) = <4,4,8>"); +}); + +test("subtract()", function() { + var v121 = new Vector3D(1, 2, 1); + var v341 = new Vector3D(3, 4, 1); + var v = new Vector3D(-2, -2, 0); + + equal(v121.subtract(v341).equals(v), true, "<1,2,1> - <3,4,1> = <-2,-2,0>"); +}); + +test("toString()", function() { + var v123 = new Vector3D(1, 2, 3); + + equal(v123.toString(), "Vector3D(1, 2, 3)", "<1,2,3> = \"Vector3D(1, 2, 3)\""); +}); + +test("translate()", function() { + var v111 = new Vector3D(1, 1, 1); + + equal(v111.translate(2).equals(new Vector3D(3, 3, 3)), true, "<1,1,1>.translate(2) = <3,3,3>"); + equal(v111.translate(2, -3, 1).equals(new Vector3D(5, 0, 4)), true, "<3,3,3>.translate(2, -3, 1) = <5,0,4>"); +}); + +test("tripleProduct()", function() { + var va = new Vector3D(1, 2, 7); + var vb = new Vector3D(3, 4, 8); + var vc = new Vector3D(5, 6, 9); + var vtp = new Vector3D(129, 98, -137); + + equal(Vector3D.tripleProduct(va, vb, vc).equals(vtp), true, "tripleProduct(<1,2,7>, <3,4,8>, <5,6,9>) = <129,98,-137>"); +}); + module("Matrix2D"); test("apply()", function() {