UNPKG

quaternion

Version:

A rotation library using quaternions

1,240 lines (935 loc) 187 kB
var assert = require("assert"); var Quaternion = require("quaternion"); var EPS = 1e-4; assert.matrixEqual = function(mat1, mat2) { assert.equal(mat1.length, mat2.length) // 2 dimension matrices if (mat1.length <= 4) { mat1.forEach((array, idx1) => { array.forEach((value, idx2) => { if (Math.abs(value - mat2[idx1][idx2]) > EPS) { assert.equal(String(mat1), String(mat2)); } }) }); } else { mat1.forEach((value, idx1) => { if (Math.abs(value - mat2[idx1]) > EPS) { assert.equal(String(mat1), String(mat2)); } }); } }; assert.q = function(a, b) { if ('w' in a && 'w' in b) { } else { assert(false); } var e = EPS; if (Math.abs(a.w - b.w) < e && Math.abs(a.x - b.x) < e && Math.abs(a.y - b.y) < e && Math.abs(a.z - b.z) < e) { } else { assert.equal(a.toString(), b.toString()); } }; assert.v = function(a, b) { var e = EPS; if (Math.abs(a[0] - b[0]) < e && Math.abs(a[1] - b[1]) < e && Math.abs(a[2] - b[2]) < e) { } else { assert.equal(a.toString(), b.toString()); } }; assert.approx = function(is, should) { if (Math.abs(is - should) > EPS) assert.equal(is, should); }; function CQ(a, b) { assert.q(a, b); return true; } function RollPitchYaw1(γ, β, α) { // XYZ var { sin, cos } = Math; //https://msl.cs.uiuc.edu/planning/node102.html return [ [cos(β) * cos(α), sin(γ) * sin(β) * cos(α) - sin(α) * cos(γ), sin(γ) * sin(α) + sin(β) * cos(γ) * cos(α)], [sin(α) * cos(β), sin(γ) * sin(β) * sin(α) + cos(γ) * cos(α), -sin(γ) * cos(α) + sin(β) * sin(α) * cos(γ)], [-sin(β), sin(γ) * cos(β), cos(γ) * cos(β)] ]; } function RollPitchYaw2(α, β, γ) { // ZYX var { sin, cos } = Math; // https://reference.wolfram.com/language/ref/RollPitchYawMatrix.html return [ [cos(α) * cos(β), -cos(β) * sin(α), sin(β)], [cos(γ) * sin(α) + cos(α) * sin(β) * sin(γ), cos(α) * cos(γ) - sin(α) * sin(β) * sin(γ), -cos(β) * sin(γ)], [-cos(α) * cos(γ) * sin(β) + sin(α) * sin(γ), cos(γ) * sin(α) * sin(β) + cos(α) * sin(γ), cos(β) * cos(γ)]]; } function ThreeFromEuler(x, y, z, order) { // https://github.com/mrdoob/three.js/blob/master/src/math/Quaternion.js var { sin, cos } = Math; const cX = cos(x / 2); const cY = cos(y / 2); const cZ = cos(z / 2); const sX = sin(x / 2); const sY = sin(y / 2); const sZ = sin(z / 2); switch (order) { case 'XYZ': return Quaternion( cX * cY * cZ - sX * sY * sZ, sX * cY * cZ + cX * sY * sZ, cX * sY * cZ - sX * cY * sZ, cX * cY * sZ + sX * sY * cZ ); case 'YXZ': return Quaternion( cX * cY * cZ + sX * sY * sZ, sX * cY * cZ + cX * sY * sZ, cX * sY * cZ - sX * cY * sZ, cX * cY * sZ - sX * sY * cZ ); case 'ZXY': return Quaternion( cX * cY * cZ - sX * sY * sZ, sX * cY * cZ - cX * sY * sZ, cX * sY * cZ + sX * cY * sZ, cX * cY * sZ + sX * sY * cZ ); case 'ZYX': return Quaternion( cX * cY * cZ + sX * sY * sZ, sX * cY * cZ - cX * sY * sZ, cX * sY * cZ + sX * cY * sZ, cX * cY * sZ - sX * sY * cZ ); case 'YZX': return Quaternion( cX * cY * cZ - sX * sY * sZ, sX * cY * cZ + cX * sY * sZ, cX * sY * cZ + sX * cY * sZ, cX * cY * sZ - sX * sY * cZ ); case 'XZY': return Quaternion( cX * cY * cZ + sX * sY * sZ, sX * cY * cZ - cX * sY * sZ, cX * sY * cZ - sX * cY * sZ, cX * cY * sZ + sX * sY * cZ ); } } function TestFromEuler(roll, pitch, yaw) { const cy = Math.cos(0.5 * yaw); const cr = Math.cos(0.5 * roll); const cp = Math.cos(0.5 * pitch); const sy = Math.sin(0.5 * yaw); const sr = Math.sin(0.5 * roll); const sp = Math.sin(0.5 * pitch); return Quaternion( cp * cr * cy - sp * sr * sy, cp * cy * sr - sp * cr * sy, cp * sr * sy + cr * cy * sp, cp * cr * sy + sp * cy * sr) } function getBaseQuaternion(alpha, beta, gamma) { // https://dev.opera.com/articles/w3c-device-orientation-usage/ var degtorad = Math.PI / 180; var _x = beta ? beta * degtorad : 0; // beta value var _y = gamma ? gamma * degtorad : 0; // gamma value var _z = alpha ? alpha * degtorad : 0; // alpha value var cX = Math.cos(_x / 2); var cY = Math.cos(_y / 2); var cZ = Math.cos(_z / 2); var sX = Math.sin(_x / 2); var sY = Math.sin(_y / 2); var sZ = Math.sin(_z / 2); // // ZXY quaternion construction. // var w = cX * cY * cZ - sX * sY * sZ; var x = sX * cY * cZ - cX * sY * sZ; var y = cX * sY * cZ + sX * cY * sZ; var z = cX * cY * sZ + sX * sY * cZ; return Quaternion([w, x, y, z]); } function eulerToQuaternion(heading, attitude, bank) { // YZX // http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm var c1 = Math.cos(heading / 2); var s1 = Math.sin(heading / 2); var c2 = Math.cos(attitude / 2); var s2 = Math.sin(attitude / 2); var c3 = Math.cos(bank / 2); var s3 = Math.sin(bank / 2); var c1c2 = c1 * c2; var s1s2 = s1 * s2; return Quaternion( c1c2 * c3 - s1s2 * s3, c1c2 * s3 + s1s2 * c3, s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3); } describe("Quaternions", function() { it("should work with different params", function() { assert.equal(Quaternion(), '1'); assert.equal(Quaternion().add(), '1'); // constructor gets 1, all others get 0 assert.equal(Quaternion(2), '2'); assert.equal(Quaternion(2, 3), '2 + 3i'); assert.equal(Quaternion(2, 3, 4), '2 + 3i + 4j'); assert.equal(Quaternion(2, 3, 4, 5), '2 + 3i + 4j + 5k'); }); it("should work with arrays", function() { assert.equal(Quaternion(1, [2, 3, 4]).equals(1, 2, 3, 4), true); assert.equal(Quaternion([1, 2, 3, 4]).equals(1, 2, 3, 4), true); assert.equal(Quaternion([1, 2, 3]).equals(0, 1, 2, 3), true); }); it("should parse strings", function() { assert.equal(Quaternion("1").toString(), '1'); assert.equal(Quaternion("1+1").toString(), '2'); assert.equal(Quaternion("1-1").toString(), '0'); assert.equal(Quaternion("1+i").toString(), '1 + i'); assert.equal(Quaternion("1+ i +j - k").toString(), '1 + i + j - k'); assert.equal(Quaternion(" - 13 + 55i - 1j - 5k").toString(), '-13 + 55i - j - 5k'); }); it("should add two quats", function() { assert.equal("10 + 10i + 10j - 2k", Quaternion(1, 2, 3, 4).add(9, 8, 7, -6).toString()); assert.equal("10 + 6i + 10j + 2k", Quaternion(1, -2, 3, -4).add(9, 8, 7, 6).toString()); assert.equal("6i + 10j + 2k", Quaternion(-9, -2, 3, -4).add(9, 8, 7, 6).toString()); assert.equal("0", Quaternion(0, 0, 0, 0).add(0, 0, 0, 0).toString()); assert.equal("1", Quaternion(2, 0, 0, 0).add(-1, 0, 0, 0).toString()); assert.equal("-1 + k", Quaternion(0, 0, 0, 1).add(-1, 0, 0, 0).toString()); assert.q(Quaternion(1).add("i"), Quaternion(1, 1, 0, 0)); assert.q(Quaternion(1, 2, 3, 4).add(Quaternion(5, 6, 7, 8)), Quaternion(6, 8, 10, 12)); assert.q(Quaternion(-1, 2, 3, 4).add(Quaternion(5, 6, 7, 8)), Quaternion(4, 8, 10, 12)); assert.q(Quaternion(1, -2, 3, 4).add(Quaternion(5, 6, 7, 8)), Quaternion(6, 4, 10, 12)); assert.q(Quaternion(1, 2, -3, 4).add(Quaternion(5, 6, 7, 8)), Quaternion(6, 8, 4, 12)); assert.q(Quaternion(1, 2, 3, -4).add(Quaternion(5, 6, 7, 8)), Quaternion(6, 8, 10, 4)); assert.q(Quaternion(0, 0, 0, 0).add(Quaternion(0, 0, 0, 0)), Quaternion(0, 0, 0, 0)); assert.q(Quaternion(1, 0, 0, 0).add(Quaternion(-1, 0, 0, 0)), Quaternion(0, 0, 0, 0)); assert.q(Quaternion(0, 1, 0, 0).add(Quaternion(0, -1, 0, 0)), Quaternion(0, 0, 0, 0)); assert.q(Quaternion(0, 0, 1, 0).add(Quaternion(0, 0, -1, 0)), Quaternion(0, 0, 0, 0)); assert.q(Quaternion(0, 0, 0, 1).add(Quaternion(0, 0, 0, -1)), Quaternion(0, 0, 0, 0)); assert.q(Quaternion(1, 0, 0, 0).add(Quaternion(0, 0, 0, 0)), Quaternion(1, 0, 0, 0)); assert.q(Quaternion(0, 1, 0, 0).add(Quaternion(0, 0, 0, 0)), Quaternion(0, 1, 0, 0)); assert.q(Quaternion(0, 0, 1, 0).add(Quaternion(0, 0, 0, 0)), Quaternion(0, 0, 1, 0)); assert.q(Quaternion(0, 0, 0, 1).add(Quaternion(0, 0, 0, 0)), Quaternion(0, 0, 0, 1)); }); it("should subtract two quats", function() { assert.equal("-8 - 6i - j + 10k", Quaternion(1, 2, 3, 4).sub(9, 8, 4, -6).toString()); assert.equal("-8 - 10i - 4j - 10k", Quaternion(1, -2, 3, -4).sub(9, 8, 7, 6).toString()); assert.equal("-18 - 10i - 4j - 10k", Quaternion(-9, -2, 3, -4).sub(9, 8, 7, 6).toString()); assert.equal("0", Quaternion(0, 0, 0, 0).sub(0, 0, 0, 0).toString()); assert.equal("3", Quaternion(2, 0, 0, 0).sub(-1, 0, 0, 0).toString()); assert.equal("1 + k", Quaternion(0, 0, 0, 1).sub(-1, 0, 0, 0).toString()); assert.q(Quaternion(0).sub(Quaternion(0)), Quaternion(0)); assert.q(Quaternion(0).sub(Quaternion(1, 2, 3, 4)), Quaternion(-1, -2, -3, -4)); assert.q(Quaternion(0).sub(Quaternion(-1, -2, -3, -4)), Quaternion(1, 2, 3, 4)); assert.q(Quaternion(10, 9, 8, 7).sub(Quaternion(1, 2, 3, 4)), Quaternion(9, 7, 5, 3)); }); it("should calculate the norm of a quat", function() { assert.equal(1, Quaternion().norm()); assert.equal(2, Quaternion(1, 1, 1, 1).norm()); assert.equal(1, Quaternion([3, 2, 5, 4]).normalize().norm()); assert.equal(Math.sqrt(1 + 4 + 9 + 16), Quaternion(1, 2, 3, 4).norm()); assert.equal(1 + 4 + 9 + 16, Quaternion(1, 2, 3, 4).normSq()); assert.equal(Quaternion({ w: 5 }).norm(), 5); assert.equal(Quaternion({ w: -5 }).norm(), 5); assert.equal(Quaternion(1, 1, 1, 1).norm(), 2); assert.equal(Quaternion(0, 0, 0, 0).norm(), 0); assert.equal(Quaternion(3, 4, 0, 0).norm(), 5); assert.equal(Quaternion(0, 3, 4, 0).norm(), 5); assert.equal(Quaternion(0, 0, 3, 4).norm(), 5); assert.equal(Quaternion(-3, 4, 0, 0).norm(), 5); assert.equal(Quaternion(0, -3, 4, 0).norm(), 5); assert.equal(Quaternion(0, 0, -3, 4).norm(), 5); assert.equal(Quaternion(1, 2, 2, 0).norm(), 3); assert.equal(Quaternion(0, 1, 2, 2).norm(), 3); assert.equal(Quaternion(1, 2, 6, 20).norm(), 21); assert.equal(Quaternion(20, 1, 2, 6).norm(), 21); assert.equal(Quaternion(6, 20, 1, 2).norm(), 21); assert.equal(Quaternion(2, 6, 20, 1).norm(), 21); assert.equal(Quaternion(1).norm(), 1); assert.equal(Quaternion("i").norm(), 1); assert.equal(Quaternion([3, 2, 5, 4]).norm(), 7.3484692283495345); }); it("should calculate the inverse of a quat", function() { assert.equal('0.03333333333333333 - 0.06666666666666667i - 0.1j - 0.13333333333333333k', Quaternion(1, 2, 3, 4).inverse().toString()); var p = Quaternion([3, 2, 5, 4]); var p_ = Quaternion(p).conjugate(); var l = p.norm(); var r = 1 / (l * l); assert.approx(l, p_.norm()); assert.q(p.inverse(), p_.scale(r)); }); it("should calculate the conjugate of a quat", function() { assert.equal('1 - 2i - 3j - 4k', Quaternion(1, 2, 3, 4).conjugate().toString()); assert.q(Quaternion(1, 2, 3, 4).conjugate(), Quaternion(1, -2, -3, -4)); assert.q(Quaternion(-1, -2, -3, -4).conjugate(), Quaternion(-1, 2, 3, 4)); assert.q(Quaternion(0, 0, 0, 0).conjugate(), Quaternion(0, 0, 0, 0)); assert.q(Quaternion(1, 0, 0, 0).conjugate(), Quaternion(1, 0, 0, 0)); assert.q(Quaternion(0, 1, 0, 0).conjugate(), Quaternion(0, -1, 0, 0)); assert.q(Quaternion(0, 0, 1, 0).conjugate(), Quaternion(0, 0, -1, 0)); assert.q(Quaternion(0, 0, 0, 1).conjugate(), Quaternion(0, 0, 0, -1)); assert.q(Quaternion(-1, 0, 0, 0).conjugate(), Quaternion(-1, 0, 0, 0)); assert.q(Quaternion(0, -1, 0, 0).conjugate(), Quaternion(0, 1, 0, 0)); assert.q(Quaternion(0, 0, -1, 0).conjugate(), Quaternion(0, 0, 1, 0)); assert.q(Quaternion(0, 0, 0, -1).conjugate(), Quaternion(0, 0, 0, 1)); assert.q(Quaternion(1).conjugate(), Quaternion([1, -0, -0, -0])); assert.q(Quaternion("i").conjugate(), Quaternion([0, -1, -0, -0])); assert.q(Quaternion("j").conjugate(), Quaternion([0, -0, -1, -0])); assert.q(Quaternion("k").conjugate(), Quaternion([0, -0, -0, -1])); assert.q(Quaternion([3, 2, 5, 4]).conjugate(), Quaternion([3, -2, -5, -4])); }); it('should pass conjugate properties', function() { var p1 = new Quaternion(8, 1, 2, 3); var p2 = new Quaternion(6, 9, 8, 7); // Test multiplicative property assert.q(p1.mul(p2).conjugate(), p2.conjugate().mul(p1.conjugate())); var p = new Quaternion(Math.random(), Math.random(), Math.random(), Math.random()).normalize(); // Test unit quaternion property as inverse assert.q(p.conjugate().mul(p), p.mul(p.conjugate())); var q = Quaternion(1); // Test one element assert.q(q, q.conjugate()); var q = Quaternion(0); // Test zero element assert.q(q, q.conjugate()); // Test pure quats var q1 = new Quaternion(0, 1, 2, 3); var q2 = new Quaternion(0, 9, 8, 7); assert.q(q2.mul(q1), q1.mul(q2).conjugate()); }); it('should pass hamilton rules', function() { var i2 = Quaternion("i").mul("i"); var j2 = Quaternion("j").mul("j"); var k2 = Quaternion("k").mul("k"); var ijk = Quaternion("i").mul("j").mul("k"); assert.q(i2, j2); assert.q(j2, k2); assert.q(k2, ijk); assert.q(ijk, Quaternion([-1, 0, 0, 0])); var q = Quaternion(1, 0, 0, 0); var qI = Quaternion(0, 1, 0, 0); var qJ = Quaternion(0, 0, 1, 0); var qK = Quaternion(0, 0, 0, 1); var qTip = Quaternion(2, 3, 5, 7); assert.q(qI.mul(qI), Quaternion(-1)); assert.q(qJ.mul(qJ), Quaternion(-1)); assert.q(qK.mul(qK), Quaternion(-1)); assert.q(qI.mul(qJ), Quaternion("k")); assert.q(qJ.mul(qI), Quaternion("-k")); assert.q(qJ.mul(qK), Quaternion("i")); assert.q(qK.mul(qJ), Quaternion("-i")); assert.q(qK.mul(qI), Quaternion("j")); assert.q(qI.mul(qK), Quaternion("-j")); }); it('should add a number to a Quaternion', function() { assert.q(Quaternion(1, 2, 3, 4).add(5), Quaternion(6, 2, 3, 4)); assert.q(Quaternion(1, 2, 3, 4).add(-5), Quaternion(-4, 2, 3, 4)); assert.q(Quaternion(1, 2, 3, 4).add(0), Quaternion(1, 2, 3, 4)); assert.q(Quaternion(0, 0, 0, 0).add(5), Quaternion(5, 0, 0, 0)); }); it("should return the real and imaginary part", function() { var q = new Quaternion(7, 2, 3, 4); assert.equal(Quaternion(q.imag()).toString(), '2i + 3j + 4k'); assert.equal(q.real(), 7); }); it("should result in the same for the inverse of normalized quats", function() { var q = Quaternion(9, 8, 7, 6).normalize(); assert.q(q.inverse(), q.conjugate()); }); it("should normalize quaternion", function() { var q = Quaternion(Math.random() * 1000, Math.random() * 1000, Math.random() * 1000, Math.random() * 1000).normalize(); assert(CQ(Quaternion(q.norm()), Quaternion(1, 0, 0, 0))); }); it("should invert quaternion", function() { var q = Quaternion(Math.random() * 1000, Math.random() * 1000, Math.random() * 1000, Math.random() * 1000); assert(CQ(q.mul(q.inverse()), Quaternion(1, 0, 0, 0))); assert(CQ(q.inverse().mul(q), Quaternion(1, 0, 0, 0))); }); it("should work to check if two quats are the same", function() { assert.equal(Quaternion(9, 8, 7, 6).equals(9, 8, 7, 6), true); assert.equal(Quaternion(8, 8, 7, 6).equals(9, 8, 7, 6), false); assert.equal(Quaternion(9, 7, 7, 6).equals(9, 8, 7, 6), false); assert.equal(Quaternion(9, 8, 6, 6).equals(9, 8, 7, 6), false); assert.equal(Quaternion(9, 8, 7, 5).equals(9, 8, 7, 6), false); }); it("should calculate the dot product", function() { assert.equal(Quaternion(9, 8, 7, 6).dot(1, 2, 3, 4).toString(), '70'); assert.equal(Quaternion(9, 8, 7, 6).normSq(), Quaternion(9, 8, 7, 6).dot(9, 8, 7, 6)); }); it('should pass trivial cases', function() { var q0 = new Quaternion(0); var q1 = Quaternion(Math.random(), Math.random(), Math.random(), Math.random()); var q2 = Quaternion(Math.random(), Math.random(), Math.random(), Math.random()); var l = Math.random() * 2.0 - 1.0; var lp = Math.random(); assert.q(q1.add(q2), (q2.add(q1))); assert.q(q0.sub(q1), (q1.neg())); assert.q(q1.conjugate().conjugate(), (q1)); assert.approx(q1.normalize().norm(), 1); assert.q(q1.inverse(), (q1.conjugate().scale(1 / Math.pow(q1.norm(), 2)))); assert.q(q1.div(q2), q1.mul(q2.inverse())); assert.approx(q1.scale(l).norm(), Math.abs(q1.norm() * l)); assert.approx(q1.mul(q2).norm(), q1.norm() * q2.norm()); assert.q((new Quaternion(l)).exp(), (new Quaternion(Math.exp(l)))); assert.q((new Quaternion(lp)).log(), (new Quaternion(Math.log(lp)))); assert.q(q1.exp().log(), q1); assert.q(q1.log().exp(), q1); // TODO: assert.q(q1.add(q2).exp(), q1.exp().mul(q2.exp())); assert.q(q1.pow(2.0), q1.mul(q1)); assert.q(q1.mul(q1.inverse()), (Quaternion.ONE)); assert.q(q1.inverse().mul(q1), Quaternion.ONE); assert.q(q1.add(q1.conjugate()), Quaternion(2 * q1.w)); }); it('should pass other trivial cases', function() { var x = Quaternion(1, 2, -0.5, -1); var y = Quaternion(-3, 4, 0, 2); var z = Quaternion(-2, 1, 2, -4); assert.approx(y.normSq(), 29); assert.approx(z.normSq(), 25); assert.q(z.normalize(), Quaternion(-0.4, 0.2, 0.4, -0.8)); assert.q(x.exp().log(), x); assert.q(x.mul(y), Quaternion(-9.0, -3.0, -6.5, 7.0)); assert.approx(y.dot(y), 29); }); it("should calculate the product", function() { assert.equal(Quaternion(5).mul(6).toString(), '30'); assert.equal(Quaternion(1, 2, 3, 4).mul(6).toString(), '6 + 12i + 18j + 24k'); // scale assert.equal(Quaternion(6).mul(1, 2, 3, 4).toString(), '6 + 12i + 18j + 24k'); // scale assert.equal(Quaternion(5, 6).mul(6, 7).toString(), '-12 + 71i'); assert.equal(Quaternion(1, 1, 1, 1).mul(2, 2, 2, 2).toString(), '-4 + 4i + 4j + 4k'); assert.q(Quaternion(1, 2, 3, 4).mul(5, 6, 7, 8), Quaternion(-60, 12, 30, 24)); assert.q(Quaternion(3, 2, 5, 4).mul(4, 5, 3, 1), Quaternion(-17, 16, 47, 0)); assert.q(Quaternion().mul(Quaternion(1, 2, 3, 4)), Quaternion(1, 2, 3, 4)); assert.q(Quaternion().mul(Quaternion()), Quaternion()); assert.q(Quaternion(1, 0, 0, 0).mul(Quaternion(1, 0, 0, 0)), Quaternion(1, 0, 0, 0)); assert.q(Quaternion(0, 1, 0, 0).mul(Quaternion(1, 0, 0, 0)), Quaternion(0, 1, 0, 0)); assert.q(Quaternion(0, 0, 1, 0).mul(Quaternion(1, 0, 0, 0)), Quaternion(0, 0, 1, 0)); assert.q(Quaternion(0, 0, 0, 1).mul(Quaternion(1, 0, 0, 0)), Quaternion(0, 0, 0, 1)); assert.q(Quaternion(1, 0, 0, 0).mul(Quaternion(0, 1, 0, 0)), Quaternion(0, 1, 0, 0)); assert.q(Quaternion(0, 1, 0, 0).mul(Quaternion(0, 1, 0, 0)), Quaternion(-1, 0, 0, 0)); assert.q(Quaternion(0, 0, 1, 0).mul(Quaternion(0, 1, 0, 0)), Quaternion(0, 0, 0, -1)); assert.q(Quaternion(0, 0, 0, 1).mul(Quaternion(0, 1, 0, 0)), Quaternion(0, 0, 1, 0)); assert.q(Quaternion(1, 0, 0, 0).mul(Quaternion(0, 0, 1, 0)), Quaternion(0, 0, 1, 0)); assert.q(Quaternion(0, 1, 0, 0).mul(Quaternion(0, 0, 1, 0)), Quaternion(0, 0, 0, 1)); assert.q(Quaternion(0, 0, 1, 0).mul(Quaternion(0, 0, 1, 0)), Quaternion(-1, 0, 0, 0)); assert.q(Quaternion(0, 0, 0, 1).mul(Quaternion(0, 0, 1, 0)), Quaternion(0, -1, 0, 0)); assert.q(Quaternion(1, 0, 0, 0).mul(Quaternion(0, 0, 0, 1)), Quaternion(0, 0, 0, 1)); assert.q(Quaternion(0, 1, 0, 0).mul(Quaternion(0, 0, 0, 1)), Quaternion(0, 0, -1, 0)); assert.q(Quaternion(0, 0, 1, 0).mul(Quaternion(0, 0, 0, 1)), Quaternion(0, 1, 0, 0)); assert.q(Quaternion(0, 0, 0, 1).mul(Quaternion(0, 0, 0, 1)), Quaternion(-1, 0, 0, 0)); assert.q(Quaternion(1, 0, 0, 0).mul(Quaternion(-1, 0, 0, 0)), Quaternion(-1, 0, 0, 0)); assert.q(Quaternion(0, 1, 0, 0).mul(Quaternion(-1, 0, 0, 0)), Quaternion(0, -1, 0, 0)); assert.q(Quaternion(0, 0, 1, 0).mul(Quaternion(-1, 0, 0, 0)), Quaternion(0, 0, -1, 0)); assert.q(Quaternion(0, 0, 0, 1).mul(Quaternion(-1, 0, 0, 0)), Quaternion(0, 0, 0, -1)); assert.q(Quaternion(1, 0, 0, 0).mul(Quaternion(0, -1, 0, 0)), Quaternion(0, -1, 0, 0)); assert.q(Quaternion(0, 1, 0, 0).mul(Quaternion(0, -1, 0, 0)), Quaternion(1, 0, 0, 0)); assert.q(Quaternion(0, 0, 1, 0).mul(Quaternion(0, -1, 0, 0)), Quaternion(0, 0, 0, 1)); assert.q(Quaternion(0, 0, 0, 1).mul(Quaternion(0, -1, 0, 0)), Quaternion(0, 0, -1, 0)); assert.q(Quaternion(1, 0, 0, 0).mul(Quaternion(0, 0, -1, 0)), Quaternion(0, 0, -1, 0)); assert.q(Quaternion(0, 1, 0, 0).mul(Quaternion(0, 0, -1, 0)), Quaternion(0, 0, 0, -1)); assert.q(Quaternion(0, 0, 1, 0).mul(Quaternion(0, 0, -1, 0)), Quaternion(1, 0, 0, 0)); assert.q(Quaternion(0, 0, 0, 1).mul(Quaternion(0, 0, -1, 0)), Quaternion(0, 1, 0, 0)); assert.q(Quaternion(1, 0, 0, 0).mul(Quaternion(0, 0, 0, -1)), Quaternion(0, 0, 0, -1)); assert.q(Quaternion(0, 1, 0, 0).mul(Quaternion(0, 0, 0, -1)), Quaternion(0, 0, 1, 0)); assert.q(Quaternion(0, 0, 1, 0).mul(Quaternion(0, 0, 0, -1)), Quaternion(0, -1, 0, 0)); assert.q(Quaternion(0, 0, 0, 1).mul(Quaternion(0, 0, 0, -1)), Quaternion(1, 0, 0, 0)); }); it("should scale a quaternion", function() { assert.q(Quaternion([3, 2, 5, 4]).scale(3), Quaternion([9, 6, 15, 12])); }); it("should calculate the quotient", function() { assert.equal(Quaternion(6).div(2).toString(), '3'); assert.equal(Quaternion(1).div(2).toString(), '0.5'); assert.equal(Quaternion(1).div(2).toString(), '0.5'); assert.equal(Quaternion(4, 2).div(1, 1).toString(), '3 - i'); assert.equal(Quaternion(3, -2).div(Quaternion.I).toString(), '-2 - 3i'); assert.equal(Quaternion(25).div(3, -4).toString(), '3 + 4i'); }); it("should result in norm=1 with fromAxisAngle", function() { var axis = [1, 1, 1]; var angle = Math.PI; assert.equal(Quaternion.fromAxisAngle(axis, angle).norm(), 1); }); it("should have no effect to rotate on axis parallel to vector direction", function() { var v = [1, 1, 1]; var angle = Math.random(); var axis = [1, 1, 1]; var r = Quaternion.fromAxisAngle(axis, angle).rotateVector(v); assert.v(r, [1, 1, 1]); }); it("should work with fromAxisAngle -> toAxisAngle", function() { var angle = Math.random(); var axis = [Math.random(), Math.random(), Math.random()]; var axisNorm = Math.hypot(...axis); var [v, a] = Quaternion.fromAxisAngle(axis, angle).toAxisAngle(); assert((a - angle) < EPS); assert.v(v, axis.map(x => x / axisNorm)); }); it("should generate a unit quaternion from euler angle", function() { var n = Quaternion.fromEuler(Math.PI, Math.PI, Math.PI).norm(); assert.equal(n, 1); }); it("should rotate a vector in direct and indirect manner", function() { var v = [1, 9, 3]; var q = Quaternion("1+2i+3j+4k").normalize(); var a = q.mul(v).mul(q.conjugate()).toVector(); var b = q.rotateVector(v); assert.v(a.slice(1), b); }); it("should rotate a vector correctly", function() { var theta = 2 * Math.PI / 3.0; var axis = [1.0, 1.0, 1.0]; var vector = [3.0, 4.0, 5.0]; var p = Quaternion.fromAxisAngle(axis, theta).rotateVector(vector); assert.v(p, [5.0, 3.0, 4.0]); }); it("should rotate a vector correctly", function() { var v = [1.0, 1.0, 1.0]; var q = Quaternion.fromAxisAngle([0.0, 1.0, 0.0], Math.PI); var p = q.rotateVector(v); assert.v(p, [-1, 1, -1]); }); it("should rotate a vector correctly based on Euler angles I", function() { var v = [1.0, 2.0, 3.0]; var q = Quaternion.fromEulerLogical(0.0, Math.PI, 0.0, 'ZXY'); var p = q.rotateVector(v); assert.v(p, [1, -2, -3]); var q = Quaternion.fromEulerLogical(Math.PI, 0.0, 0.0, 'ZXY'); var p = q.rotateVector(v); assert.v(p, [-1, -2, 3]); var q = Quaternion.fromEulerLogical(0.0, 0.0, Math.PI, 'ZXY'); var p = q.rotateVector(v); assert.v(p, [-1, 2, -3]); }); it("should rotate a vector correctly based on Euler angles II", function() { var v = [1.0, 2.0, 3.0]; var q = Quaternion.fromEulerLogical(0.0, Math.PI, 0.0, 'XYZ'); var p = q.rotateVector(v); assert.v(p, [-1, 2, -3]); var q = Quaternion.fromEulerLogical(Math.PI, 0.0, 0.0, 'XYZ'); var p = q.rotateVector(v); assert.v(p, [1, -2, -3]); var q = Quaternion.fromEulerLogical(0, 0, Math.PI, 'XYZ'); var p = q.rotateVector(v); assert.v(p, [-1, -2, 3]); }); it("should rotate a vector correctly based on Euler angles III", function() { var v = [1.0, 2.0, 3.0]; var q = Quaternion.fromEulerLogical(0.0, Math.PI, 0.0, 'XZY'); var p = q.rotateVector(v); assert.v(p, [-1, -2, 3]); var q = Quaternion.fromEulerLogical(Math.PI, 0.0, 0.0, 'XZY'); var p = q.rotateVector(v); assert.v(p, [1, -2, -3]); var q = Quaternion.fromEulerLogical(0, 0, Math.PI, 'XZY'); var p = q.rotateVector(v); assert.v(p, [-1, 2, -3]); }); it("should create a rotation matrix correctly based on Euler angles", function() { // https://de.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors var a = -Math.PI / 3; var b = Math.PI / 4; var c = -Math.PI / 5; var q = Quaternion.fromEuler(a, b, c, 'ZYX'); assert.matrixEqual(q.toMatrix(false), [ // fromEuler(a, b, c, 'ZYX') 0.3536, 0.4928, 0.7951, -0.6124, 0.7645, -0.2015, -0.7071, -0.4156, 0.5721 ]); }); it("should test against Three.js implementation XYZ", function() { var x = Math.random(); var y = Math.random(); var z = Math.random(); assert.q(Quaternion.fromEuler(x, y, z, 'XYZ'), ThreeFromEuler(x, y, z, 'XYZ')); }); it("should test against Three.js implementation YXZ", function() { var x = Math.random(); var y = Math.random(); var z = Math.random(); assert.q(Quaternion.fromEuler(x, y, z, 'YXZ'), ThreeFromEuler(y, x, z, 'YXZ')); }); it("should test against Three.js implementation ZXY", function() { var x = Math.random(); var y = Math.random(); var z = Math.random(); assert.q(Quaternion.fromEuler(x, y, z, 'ZXY'), ThreeFromEuler(y, z, x, 'ZXY')); // Bug in three.js? }); it("should test against Three.js implementation ZYX", function() { var x = Math.random(); var y = Math.random(); var z = Math.random(); assert.q(Quaternion.fromEuler(x, y, z, 'ZYX'), ThreeFromEuler(z, y, x, 'ZYX')); }); it("should test against Three.js implementation YZX", function() { var x = Math.random(); var y = Math.random(); var z = Math.random(); assert.q(Quaternion.fromEuler(x, y, z, 'YZX'), ThreeFromEuler(z, x, y, 'YZX')); // Bug in thre.js? }); it("should test against Three.js implementation XZY", function() { var x = Math.random(); var y = Math.random(); var z = Math.random(); assert.q(Quaternion.fromEuler(x, y, z, 'XZY'), ThreeFromEuler(x, z, y, 'XZY')); }); it("should test against a fromEuler implementation with ZXY", function() { var x = Math.random(); var y = Math.random(); var z = Math.random(); assert.q(Quaternion.fromEulerLogical(x, y, z, 'YXZ'), TestFromEuler(y, x, z)); }); it("should exp and log a quaternion", function() { var q = new Quaternion(Math.random() * 10, Math.random() * 10, Math.random() * 10, Math.random() * 10); assert(CQ(q, q.log().exp())); }); it("should exp and log real numbers", function() { var n = Math.random() * 10; var q = Quaternion(n); assert.v(q.exp().toVector(), [Math.exp(n), 0, 0, 0]); assert.v(q.log().toVector(), [Math.log(n), 0, 0, 0]); }); it("should work with scalar powers", function() { var q = new Quaternion(Math.random() * 10, Math.random() * 10, Math.random() * 10, Math.random() * 10); assert(CQ(q.pow(3), q.mul(q).mul(q))); }); it('should compare 2 quaternions correctly', function() { assert.equal(!Quaternion().equals(Quaternion(1, 0, 0, 0)), false); assert.equal(!Quaternion(1, 1, 1, 1).equals(Quaternion(1, 1, 1, 1)), false); assert.equal(!Quaternion(1, 1, 1, 0).equals(Quaternion(1, 1, 1, 0)), false); assert.equal(!Quaternion(1, 1, 0, 1).equals(Quaternion(1, 1, 0, 1)), false); assert.equal(!Quaternion(1, 0, 1, 1).equals(Quaternion(1, 0, 1, 1)), false); assert.equal(!Quaternion(0, 1, 1, 1).equals(Quaternion(0, 1, 1, 1)), false); assert.equal(!Quaternion(1, 1, 1, 1).equals(Quaternion(-1, 1, 1, 1)), true); assert.equal(!Quaternion(1, 1, 1, 1).equals(Quaternion(1, -1, 1, 1)), true); assert.equal(!Quaternion(1, 1, 1, 1).equals(Quaternion(1, 1, -1, 1)), true); assert.equal(!Quaternion(1, 1, 1, 1).equals(Quaternion(1, 1, 1, -1)), true); }); it('should square Quaternions', function() { assert(CQ(Quaternion("i").pow(2), Quaternion(-1))); assert(CQ(Quaternion("j").pow(2), Quaternion(-1))); assert(CQ(Quaternion("k").pow(2), Quaternion(-1))); assert(CQ(Quaternion(1).pow(2), Quaternion({ w: 1 }))); assert(CQ(Quaternion(3, -2, -3, 4).pow(2), Quaternion(-20, -12, -18, 24))); assert(CQ(Quaternion(1, 2, 3, 4).pow(2), Quaternion(-28, 4, 6, 8))); assert(CQ(Quaternion(-1, 2, 3, 4).pow(2), Quaternion(-28, -4, -6, -8))); assert(CQ(Quaternion(1, -2, 3, 4).pow(2), Quaternion(-28, -4, 6, 8))); assert(CQ(Quaternion(1, 2, -3, 4).pow(2), Quaternion(-28, 4, -6, 8))); assert(CQ(Quaternion(1, 2, 3, -4).pow(2), Quaternion(-28, 4, 6, -8))); assert(CQ(Quaternion(5, 4, 3, 2).pow(2), Quaternion(-4, 40, 30, 20))); assert(CQ(Quaternion(-5, 4, 3, 2).pow(2), Quaternion(-4, -40, -30, -20))); assert(CQ(Quaternion(5, -4, 3, 2).pow(2), Quaternion(-4, -40, 30, 20))); assert(CQ(Quaternion(5, 4, -3, 2).pow(2), Quaternion(-4, 40, -30, 20))); assert(CQ(Quaternion(5, 4, 3, -2).pow(2), Quaternion(-4, 40, 30, -20))); }); it('should raise Quaternion to a Quaternion power', function() { assert(CQ(Quaternion(1, 4, 0, 0).pow(Quaternion(-2, 3, 0, 0)), Quaternion(-0.000030177061938851806, 0.0011015451057806702, 0, 0))); assert(CQ(Quaternion(1, 4, -3, 2).pow(Quaternion(4, 2, -3, 2)), Quaternion(4.023822744421112, -0.08808649248602358, 0.10799947333843203, -0.045858528052467734))); assert(CQ(Quaternion(-1, -1, 0, 4).pow(Quaternion(-4, 5, 1, 1.5)), Quaternion(0.00009562614911354535, 0.0010196374737841477, 0.0015348157881126755, -0.0007464390363321687))); assert(CQ(Quaternion(0, 2, 0, 0).pow(Quaternion(1, 0, 0, 0)), Quaternion(0, 2, 0, 0))); assert(CQ(Quaternion(0, 2, 0, 0).pow(Quaternion(0, 1, 0, 0)), Quaternion(0.15990905692806803, 0.13282699942462048, 0, 0))); assert(CQ(Quaternion(0, 2, 0, 0).pow(Quaternion(0, 0, 1, 0)), Quaternion(-0.145615694487965, 0, 0.399409670603132, 0.905134235650981))); assert(CQ(Quaternion(0, 2, 0, 0).pow(Quaternion(0, 0, 0, 1)), Quaternion(-0.145615694487965, 0, -0.905134235650981, 0.399409670603132))); assert(CQ(Quaternion(0, 0, 2, 0).pow(Quaternion(1, 0, 0, 0)), Quaternion(0, 0, 2, 0))); assert(CQ(Quaternion(0, 0, 2, 0).pow(Quaternion(0, 1, 0, 0)), Quaternion(-0.145615694487965, 0.399409670603132, 0, -0.905134235650981))); assert(CQ(Quaternion(0, 0, 2, 0).pow(Quaternion(0, 0, 1, 0)), Quaternion(0.159909056928068, 0, 0.13282699942462, 0))); assert(CQ(Quaternion(0, 0, 2, 0).pow(Quaternion(0, 0, 0, 1)), Quaternion(-0.145615694487965, 0.905134235650981, 0, 0.399409670603132))); assert(CQ(Quaternion(0, 0, 0, 2).pow(Quaternion(1, 0, 0, 0)), Quaternion(0, 0, 0, 2))); assert(CQ(Quaternion(0, 0, 0, 2).pow(Quaternion(0, 1, 0, 0)), Quaternion(-0.145615694487965, 0.399409670603132, 0.905134235650981, 0))); assert(CQ(Quaternion(0, 0, 0, 2).pow(Quaternion(0, 0, 1, 0)), Quaternion(-0.145615694487965, -0.905134235650981, 0.399409670603132, 0))); assert(CQ(Quaternion(0, 0, 0, 2).pow(Quaternion(0, 0, 0, 1)), Quaternion(0.159909056928068, 0, 0, 0.13282699942462))); }); it('should raise reals to quaternion powers', function() { assert(CQ(Quaternion(1).pow(Quaternion(3, 4, 5, 9)), Quaternion(1))); assert(CQ(Quaternion(-2).pow(Quaternion(4, 2, 1, 1.5)), Quaternion(-0.024695944127665907, 0.015530441791896946, -0.004473740387907085, 0.004654139181719533))); assert(CQ(Quaternion(2, 0, 0, 0).pow(Quaternion(1, 0, 0, 0)), Quaternion(2, 0, 0, 0))); assert(CQ(Quaternion(2, 0, 0, 0).pow(Quaternion(0, 1, 0, 0)), Quaternion(0.7692389013639721, 0.6389612763136348, 0, 0))); assert(CQ(Quaternion(2, 0, 0, 0).pow(Quaternion(0, 0, 1, 0)), Quaternion(0.769238901363972, 0, 0.638961276313635, 0))); assert(CQ(Quaternion(2, 0, 0, 0).pow(Quaternion(0, 0, 0, 1)), Quaternion(0.769238901363972, 0, 0, 0.638961276313635))); }); it('should return the square root of a Quaternion', function() { assert(CQ(Quaternion(1, 2, 3, 4).pow(1 / 2), Quaternion(1.7996146219471072, 0.5556745248702426, 0.8335117873053639, 1.1113490497404852))); assert(CQ(Quaternion(-1, -2, -3, -4).pow(1 / 2).pow(2), Quaternion(-1, -2, -3, -4))); assert(CQ(Quaternion().pow(1 / 2), Quaternion())); assert(CQ(Quaternion(1, 0, 0, 0).pow(1 / 2), Quaternion(1, 0, 0, 0))); assert(CQ(Quaternion(0, 1, 0, 0).pow(1 / 2), Quaternion(0.7071067811865476, 0.7071067811865475, 0, 0))); assert(CQ(Quaternion("1-2i").pow(1 / 2), Quaternion("1.272019649514069 - 0.7861513777574233i"))) assert(CQ(Quaternion(0, 0, 1, 0).pow(1 / 2), Quaternion(0.7071067811865476, 0, 0.7071067811865475, 0))); assert(CQ(Quaternion(0, 0, 0, 1).pow(1 / 2), Quaternion(0.7071067811865476, 0, 0, 0.7071067811865475))); assert(CQ(Quaternion(-1).pow(1 / 2), Quaternion("i"))); }); it('should return the log base e of a quaternion', function() { assert(CQ(Quaternion(1, 2, 3, 4).log(), Quaternion(1.7005986908310777, 0.515190292664085, 0.7727854389961275, 1.03038058532817))); assert(CQ(Quaternion(-1, 2, 3, 4).log(), Quaternion(1.7005986908310777, 0.6515679277817118, 0.9773518916725678, 1.3031358555634236))); assert(CQ(Quaternion(1, -2, 3, 4).log(), Quaternion(1.7005986908310777, -0.515190292664085, 0.7727854389961275, 1.03038058532817))); assert(CQ(Quaternion(1, 2, -3, 4).log(), Quaternion(1.7005986908310777, 0.515190292664085, -0.7727854389961275, 1.03038058532817))); assert(CQ(Quaternion(1, 2, 3, -4).log(), Quaternion(1.7005986908310777, 0.515190292664085, 0.7727854389961275, -1.03038058532817))); assert(CQ(Quaternion(2, 3, 4, 5).log(), Quaternion(1.9944920232821373, 0.549487105217117, 0.7326494736228226, 0.9158118420285283))); assert(CQ(Quaternion(5, 2, 3, 4).log(), Quaternion(1.9944920232821373, 0.30545737557546476, 0.45818606336319717, 0.6109147511509295))); assert(CQ(Quaternion(4, 5, 2, 3).log(), Quaternion(1.9944920232821373, 0.8072177296195943, 0.3228870918478377, 0.48433063777175656))); assert(CQ(Quaternion(3, 4, 5, 2).log(), Quaternion(1.9944920232821373, 0.685883734654061, 0.8573546683175763, 0.3429418673270305))); }); it('should return the exp of a quaternion', function() { assert(CQ(Quaternion(0, 0, 0, 0).exp(), Quaternion(1, 0, 0, 0))); assert(CQ(Quaternion(1, 0, 0, 0).exp(), Quaternion(2.718281828459045, 0, 0, 0))); assert(CQ(Quaternion(0, 1, 0, 0).exp(), Quaternion(0.5403023058681398, 0.8414709848078965, 0, 0))); assert(CQ(Quaternion(0, 0, 1, 0).exp(), Quaternion(0.5403023058681398, 0, 0.8414709848078965, 0))); assert(CQ(Quaternion(0, 0, 0, 1).exp(), Quaternion(0.5403023058681398, 0, 0, 0.8414709848078965))); assert(CQ(Quaternion(-1, 0, 0, 0).exp(), Quaternion(0.3678794411714424, 0, 0, 0))); assert(CQ(Quaternion(0, -1, 0, 0).exp(), Quaternion(0.5403023058681398, -0.8414709848078965, 0, 0))); assert(CQ(Quaternion(0, 0, -1, 0).exp(), Quaternion(0.5403023058681398, 0, -0.8414709848078965, 0))); assert(CQ(Quaternion(0, 0, 0, -1).exp(), Quaternion(0.5403023058681398, 0, 0, -0.8414709848078965))); assert(CQ(Quaternion(1, 2, 3, 4).exp(), Quaternion(1.6939227236832994, -0.7895596245415588, -1.184339436812338, -1.5791192490831176))); assert(CQ(Quaternion(4, 1, 2, 3).exp(), Quaternion(-45.05980201339819, -8.240025266756877, -16.480050533513754, -24.720075800270628))); assert(CQ(Quaternion(3, 4, 1, 2).exp(), Quaternion(-2.6000526954284027, -17.384580348249628, -4.346145087062407, -8.692290174124814))); assert(CQ(Quaternion(2, 3, 4, 1).exp(), Quaternion(2.786189997492657, -4.026439818820405, -5.3685864250938735, -1.3421466062734684))); }); it('should divide quaternions by each other', function() { assert(CQ(Quaternion({ z: 1 }).div(Quaternion({ y: 1 })), Quaternion({ x: 1 }))); assert(CQ(Quaternion({ x: 1 }).div(Quaternion({ z: 1 })), Quaternion({ y: 1 }))); assert(CQ(Quaternion(3, -2, -3, 4).div(Quaternion(3, -2, -3, 4)), Quaternion(1, 0, 0, 0))); assert(CQ(Quaternion(1, 2, 3, 4).div(Quaternion(-1, 1, 2, 3)), Quaternion(19 / 15, -4 / 15, -1 / 5, -8 / 15))); assert(CQ(Quaternion(1, 0, 0, 0).div(Quaternion(1, 0, 0, 0)), Quaternion(1, 0, 0, 0))); assert(CQ(Quaternion(1, 0, 0, 0).div(Quaternion(0, 1, 0, 0)), Quaternion(0, -1, 0, 0))); assert(CQ(Quaternion(1, 0, 0, 0).div(Quaternion(0, 0, 1, 0)), Quaternion(0, 0, -1, 0))); assert(CQ(Quaternion(1, 0, 0, 0).div(Quaternion(0, 0, 0, 1)), Quaternion(0, 0, 0, -1))); }); it('should raise Quaternion to real powers', function() { assert(CQ(Quaternion(1, 2, 3, 4).pow(2), Quaternion(-28, 4, 6, 8))); assert(CQ(Quaternion(1, 2, 3, 4).pow(0), Quaternion({ w: 1 }))); assert(CQ(Quaternion(1, 2, 3, 4).pow(2.5), Quaternion(-66.50377063575604, -8.360428208578368, -12.54064231286755, -16.720856417156735))); assert(CQ(Quaternion(1, 2, 3, 4).pow(-2.5), Quaternion(-0.0134909686430938, 0.0016959981926818065, 0.0025439972890227095, 0.003391996385363613))); }); it('should rotate the optimized function', function() { var v = [Math.random() * 100, Math.random() * 50, Math.random() * 20]; var q = Quaternion.random(); assert.v(q.rotateVector(v), q.mul(0, v).mul(q.conjugate()).toVector().slice(1)); }); it('should rotate one vector onto the other', function() { var u = [Math.random() * 100, Math.random() * 100, Math.random() * 100]; var v = [Math.random() * 100, Math.random() * 100, Math.random() * 100]; var q = Quaternion.fromVectors(u, v); var vPrime = q.rotateVector(u); // Is the length of rotated equal to the original? assert.approx(Quaternion(u).norm(), Quaternion(vPrime).norm()); // Do they look in the same direction? assert.q(Quaternion(v).normalize(), Quaternion(vPrime).normalize()); }); it('should rotate one vector onto the other around PI', function() { var u = [10, 0, 0]; var v = [-12, 0, 0]; var q = Quaternion.fromVectors(u, v); var vPrime = q.rotateVector(u); // Is the length of rotated equal to the original? assert.approx(Quaternion(u).norm(), Quaternion(vPrime).norm()); // Do they look in the same direction? assert.q(Quaternion(v).normalize(), Quaternion(vPrime).normalize()); var u = [0, 10, 0]; var v = [0, -12, 0]; var q = Quaternion.fromVectors(u, v); var vPrime = q.rotateVector(u); // Is the length of rotated equal to the original? assert.approx(Quaternion(u).norm(), Quaternion(vPrime).norm()); // Do they look in the same direction? assert.q(Quaternion(v).normalize(), Quaternion(vPrime).normalize()); var u = [0, 0, 10]; var v = [0, 0, 150]; var q = Quaternion.fromVectors(u, v); var vPrime = q.rotateVector(u); // Is the length of rotated equal to the original? assert.approx(Quaternion(u).norm(), Quaternion(vPrime).norm()); // Do they look in the same direction? assert.q(Quaternion(v).normalize(), Quaternion(vPrime).normalize()); }); it('should rotate additive inverse to the same point', function() { var q1 = Quaternion(Math.random(), Math.random(), Math.random(), Math.random()).normalize(); var q2 = Quaternion(Math.random(), Math.random(), Math.random(), Math.random()).normalize(); var v = [Math.random(), Math.random(), Math.random()]; assert.v(q1.neg().rotateVector(v), q1.rotateVector(v)); assert.v(q1.conjugate().neg().rotateVector(v), q1.conjugate().rotateVector(v)); }); it('should slerp around', function() { var q1 = Quaternion(Math.random(), Math.random(), Math.random(), Math.random()).normalize(); var q2 = Quaternion(Math.random(), Math.random(), Math.random(), Math.random()).normalize(); assert.q(q1.slerp(q2)(0), q1); assert.q(q1.slerp(q2)(1), q2); }); it("should work with fromEuler -> toEuler ZXY", function() { let t = [ Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI ]; let r = Quaternion.fromEuler(...t, 'ZXY'); let s = Quaternion.fromEuler(...r.toEuler('ZXY'), 'ZXY'); assert.matrixEqual(r.toMatrix(), s.toMatrix()); }); it("should work with fromEuler -> toEuler XYZ", function() { let t = [ Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI ]; let r = Quaternion.fromEuler(...t, 'XYZ'); let s = Quaternion.fromEuler(...r.toEuler('XYZ'), 'XYZ'); assert.matrixEqual(r.toMatrix(), s.toMatrix()); }); it("should work with fromEuler -> toEuler YXZ", function() { let t = [ Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI ]; let r = Quaternion.fromEuler(...t, 'YXZ'); let s = Quaternion.fromEuler(...r.toEuler('YXZ'), 'YXZ'); assert.matrixEqual(r.toMatrix(), s.toMatrix()); }); it("should work with fromEuler -> toEuler ZYX", function() { let t = [ Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI ]; let r = Quaternion.fromEuler(...t, 'ZYX'); let s = Quaternion.fromEuler(...r.toEuler('ZYX'), 'ZYX'); assert.matrixEqual(r.toMatrix(), s.toMatrix()); }); it("should work with fromEuler -> toEuler YZX", function() { let t = [ Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI ]; let r = Quaternion.fromEuler(...t, 'YZX'); let s = Quaternion.fromEuler(...r.toEuler('YZX'), 'YZX'); assert.matrixEqual(r.toMatrix(), s.toMatrix()); }); it("should work with fromEuler -> toEuler XZY", function() { let t = [ Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI ]; let r = Quaternion.fromEuler(...t, 'XZY'); let s = Quaternion.fromEuler(...r.toEuler('XZY'), 'XZY'); assert.matrixEqual(r.toMatrix(), s.toMatrix()); }); it("should work with fromEuler -> toEuler RPY", function() { let t = [ Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI ]; let r = Quaternion.fromEuler(...t, 'RPY'); let s = Quaternion.fromEuler(...r.toEuler('RPY'), 'RPY'); assert.matrixEqual(r.toMatrix(), s.toMatrix()); }); it("should work with fromEuler -> toEuler YPR", function() { let t = [ Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI, Math.PI * 2 * Math.random() - Math.PI ]; let r = Quaternion.fromEuler(...t, 'YPR'); let s = Quaternion.fromEuler(...r.toEuler('YPR'), 'YPR'); assert.matrixEqual(r.toMatrix(), s.toMatrix()); }); it("should work with deviceorientation from Opera Site", function() { var alpha = Math.random() * 360; var beta = Math.random() * 360; var gamma = Math.random() * 360; let r = getBaseQuaternion(alpha, beta, gamma); // Bug: Wrong order on Opera site! let s = Quaternion.fromEuler(alpha * Math.PI / 180, beta * Math.PI / 180, gamma * Math.PI / 180, 'ZXY'); assert.q(r, s); }); it("should work with euclideanspace.com", function() { var alpha = Math.random() * 360; var beta = Math.random() * 360; var gamma = Math.random() * 360; let r = eulerToQuaternion(alpha * Math.PI / 180, beta * Math.PI / 180, gamma * Math.PI / 180); let s = Quaternion.fromEuler(alpha * Math.PI / 180, beta * Math.PI / 180, gamma * Math.PI / 180, 'YZX'); assert.q(r, s); }); describe('fromMatrix', function() { it('Should create a quaternion from a matrix with m22 positive and m00 < -m11', function() { const q = Quaternion.fromMatrix([ -0.14040120936120104, -0.03817338250185204, 0.9893585261563572, -0.2992237727316569, -0.9508943214366381, -0.0791525318090902, 0.9437969242597403, -0.3071527019707341, 0.12208432917426992]) assert.q(q, new Quaternion(-0.08773368562933877, 0.6496939246485931, -0.12982927130494584, 0.7438716051799714)) }) it('Should create a quaternion from a matrix with m22 positive and m00 >= -m11', function() { const q = Quaternion.fromMatrix([ 0.7441469599075261, -0.6679403460655629, 0.010049684482756005, 0.4879070091702578, 0.5331745589946937, -0.6911379312722953, 0.4562806729009248, 0.5192115019321414, 0.7226530037289329]) assert.q(q, new Quaternion(0.8660217264351906, 0.34939926916920544, -0.12881633762671046, 0.3336658076679094)) }) it('Should create a quaternion from a matrix with m22 negative and m00 > m11', function() { const q = Quaternion.fromMatrix([ 0.060549599475538174, 0.13230044593141155, 0.9893585487626324, 0.14484523930352894, -0.981850454507192, 0.12243178359856144, 0.9875999203394337, 0.13589068029254686, -0.07861374151618494]) assert.q(q, new Quaternion(0.004620699410323448, 0.7281850375246176, 0.09514947127211848, 0.6787280592246553)) }) it('Should create a quaternion from a matrix with m22 negative and m00 <= m11', function() { const q = Quaternion.fromMatrix([ -0.9899924978109478, -0.13532339494964624, -0.04003040166351807, -0.00000000597024110, 0.28366218433821, -0.9589242749959328, 0.14111999956788723, -0.9493278379757846, -0.2808234352154073]) assert.q(q, new Quaternion(-0.05667065226343984, -0.04233424460838274, 0.7991367400771543, -0.5969729638470511)) }) }) describe('toMatrix', function() { it('Should create a matrix from a quaternion with m22 positive and m00 < -m11', function() { assert.matrixEqual( new Quaternion(-0.08773368562933877, 0.6496939246485931, -0.12982927130494584, 0.7438716051799714).toMatrix(true), [[-0.14040120936120104, -0.03817338250185204, 0.9893585261563572], [-0.2992237727316569, -0.9508943214366381, -0.0791525318090902], [0.9437969242597403, -0.3071527019707341, 0.12208432917426992]]) assert.matrixEqual( new Quaternion(-0.08773368562933877, 0.6496939246485931, -0.12982927130494584, 0.7438716051799714).toMatrix(false), [-0.14040120936120104, -0.03817338250185204, 0.9893585261563572, -0.2992237727316569, -0.9508943214366381, -0.0791525318090902, 0.9437969242597403, -0.3071527019707341, 0.12208432917426992]) }) it('Should create a matrix from a quaternion with m22 positive and m00 >= -m11', function() { assert.matrixEqual( new Quaternion(-0.08773368562933877, 0.6496939246485931, -0.12982927130494584, 0.7438716051799714).toMatrix(true), [[-0.14040120936120104, -0.03817338250185204, 0.9893585261563572], [-0.2992237727316569, -0.9508943214366381, -0.0791525318090902], [0.9437969242597403, -0.3071527019707341, 0.12208432917426992]]) assert.matrixEqual( new Quaternion(-0.08773368562933877, 0.6496939246485931, -0.12982927130494584, 0.7438716051799714).toMatrix(false), [-0.14040120936120104, -0.03817338250185204, 0.9893585261563572, -0.2992237727316569, -0.9508943214366381, -0.0791525318090902, 0.9437969242597403, -0.3071527019707341, 0.12208432917426992]) }) it('Should create a matrix from a quaternion with m22 negative and m00 > m11', function() { assert.matrixEqual( new Quaternion(-0.08773368562933877, 0.6496939246485931, -0.12982927130494584, 0.7438716051799714).toMatrix(true), [[-0.14040120936120104, -0.03817338250185204, 0.9893585261563572], [-0.2992237727316569, -0.9508943214366381, -0.0791525318090902], [0.9437969242597403, -0.3071527019707341, 0.12208432917426992]]) assert.matrixEqual( new Quaternion(-0.08773368562933877, 0.6496939246485931, -0.12982927130494584, 0.7438716051799714).toMatrix(false), [-0.14040120936120104, -0.03817338250185204, 0.9893585261563572, -0.2992237727316569, -0.9508943214366381, -0.0791525318090902, 0.9437969242597403, -0.3071527019707341, 0.12208432917426992]) }) it('Should create a matrix from a quaternion with m22 negative and m00 <= m11', function() { assert.matrixEqual( new Quaternion(-0.08773368562933877, 0.6496939246485931, -0.12982927130494584, 0.7438716051799714).toMatrix(true), [[-0.14040120936120104, -0.03817338250185204, 0.9893585261563572], [-0.2992237727316569, -0.9508943214366381, -0.0791525318090902], [0.9437969242597403, -0.3071527019707341, 0.12208432917426992]]) assert.matrixEqual( new Quaternion(-0.08773368562933877, 0.6496939246485931, -0.12982927130494584, 0.7438716051799714).toMatrix(false), [-0.14040120936120104, -0.03817338250185204, 0.9893585261563572, -0.2992237727316569, -0.9508943214366381, -0.0791525318090902, 0.9437969242597403, -0.3071527019707341, 0.12208