phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
1,021 lines (913 loc) • 33 kB
JavaScript
var Quaternion = require('../../src/math/Quaternion');
var Vector3 = require('../../src/math/Vector3');
var Matrix3 = require('../../src/math/Matrix3');
describe('Quaternion', function ()
{
describe('constructor', function ()
{
it('should create a quaternion with default values', function ()
{
var q = new Quaternion();
expect(q.x).toBe(0);
expect(q.y).toBe(0);
expect(q.z).toBe(0);
expect(q.w).toBe(0);
});
it('should create a quaternion with given values', function ()
{
var q = new Quaternion(1, 2, 3, 4);
expect(q.x).toBe(1);
expect(q.y).toBe(2);
expect(q.z).toBe(3);
expect(q.w).toBe(4);
});
it('should have an onChangeCallback function', function ()
{
var q = new Quaternion();
expect(typeof q.onChangeCallback).toBe('function');
});
});
describe('copy', function ()
{
it('should copy components from another quaternion', function ()
{
var src = new Quaternion(1, 2, 3, 4);
var q = new Quaternion();
q.copy(src);
expect(q.x).toBe(1);
expect(q.y).toBe(2);
expect(q.z).toBe(3);
expect(q.w).toBe(4);
});
it('should return itself', function ()
{
var src = new Quaternion(1, 2, 3, 4);
var q = new Quaternion();
expect(q.copy(src)).toBe(q);
});
it('should copy from a plain object with x, y, z, w', function ()
{
var src = { x: 5, y: 6, z: 7, w: 8 };
var q = new Quaternion();
q.copy(src);
expect(q.x).toBe(5);
expect(q.y).toBe(6);
expect(q.z).toBe(7);
expect(q.w).toBe(8);
});
});
describe('set', function ()
{
it('should set components from numbers', function ()
{
var q = new Quaternion();
q.set(1, 2, 3, 4);
expect(q.x).toBe(1);
expect(q.y).toBe(2);
expect(q.z).toBe(3);
expect(q.w).toBe(4);
});
it('should set components from an object', function ()
{
var q = new Quaternion();
q.set({ x: 5, y: 6, z: 7, w: 8 });
expect(q.x).toBe(5);
expect(q.y).toBe(6);
expect(q.z).toBe(7);
expect(q.w).toBe(8);
});
it('should default missing object properties to zero', function ()
{
var q = new Quaternion();
q.set({ x: 1 });
expect(q.x).toBe(1);
expect(q.y).toBe(0);
expect(q.z).toBe(0);
expect(q.w).toBe(0);
});
it('should return itself', function ()
{
var q = new Quaternion();
expect(q.set(1, 2, 3, 4)).toBe(q);
});
it('should invoke onChangeCallback when update is true', function ()
{
var q = new Quaternion();
var called = false;
q.onChangeCallback = function () { called = true; };
q.set(1, 2, 3, 4, true);
expect(called).toBe(true);
});
it('should not invoke onChangeCallback when update is false', function ()
{
var q = new Quaternion();
var called = false;
q.onChangeCallback = function () { called = true; };
q.set(1, 2, 3, 4, false);
expect(called).toBe(false);
});
});
describe('add', function ()
{
it('should add components from another quaternion', function ()
{
var q = new Quaternion(1, 2, 3, 4);
var v = { x: 10, y: 20, z: 30, w: 40 };
q.add(v);
expect(q.x).toBe(11);
expect(q.y).toBe(22);
expect(q.z).toBe(33);
expect(q.w).toBe(44);
});
it('should return itself', function ()
{
var q = new Quaternion(1, 2, 3, 4);
expect(q.add({ x: 1, y: 1, z: 1, w: 1 })).toBe(q);
});
it('should add zero without changing values', function ()
{
var q = new Quaternion(1, 2, 3, 4);
q.add({ x: 0, y: 0, z: 0, w: 0 });
expect(q.x).toBe(1);
expect(q.y).toBe(2);
expect(q.z).toBe(3);
expect(q.w).toBe(4);
});
it('should add negative values', function ()
{
var q = new Quaternion(5, 5, 5, 5);
q.add({ x: -3, y: -3, z: -3, w: -3 });
expect(q.x).toBe(2);
expect(q.y).toBe(2);
expect(q.z).toBe(2);
expect(q.w).toBe(2);
});
});
describe('subtract', function ()
{
it('should subtract components from another quaternion', function ()
{
var q = new Quaternion(10, 20, 30, 40);
var v = { x: 1, y: 2, z: 3, w: 4 };
q.subtract(v);
expect(q.x).toBe(9);
expect(q.y).toBe(18);
expect(q.z).toBe(27);
expect(q.w).toBe(36);
});
it('should return itself', function ()
{
var q = new Quaternion(1, 2, 3, 4);
expect(q.subtract({ x: 1, y: 1, z: 1, w: 1 })).toBe(q);
});
it('should subtract zero without changing values', function ()
{
var q = new Quaternion(1, 2, 3, 4);
q.subtract({ x: 0, y: 0, z: 0, w: 0 });
expect(q.x).toBe(1);
expect(q.y).toBe(2);
expect(q.z).toBe(3);
expect(q.w).toBe(4);
});
});
describe('scale', function ()
{
it('should scale all components by a factor', function ()
{
var q = new Quaternion(1, 2, 3, 4);
q.scale(2);
expect(q.x).toBe(2);
expect(q.y).toBe(4);
expect(q.z).toBe(6);
expect(q.w).toBe(8);
});
it('should return itself', function ()
{
var q = new Quaternion(1, 2, 3, 4);
expect(q.scale(2)).toBe(q);
});
it('should scale by zero resulting in all zeros', function ()
{
var q = new Quaternion(1, 2, 3, 4);
q.scale(0);
expect(q.x).toBe(0);
expect(q.y).toBe(0);
expect(q.z).toBe(0);
expect(q.w).toBe(0);
});
it('should scale by negative factor', function ()
{
var q = new Quaternion(1, 2, 3, 4);
q.scale(-1);
expect(q.x).toBe(-1);
expect(q.y).toBe(-2);
expect(q.z).toBe(-3);
expect(q.w).toBe(-4);
});
});
describe('length', function ()
{
it('should return the length of the quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 1);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should return zero for a zero quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 0);
expect(q.length()).toBe(0);
});
it('should return correct length for general quaternion', function ()
{
var q = new Quaternion(1, 2, 3, 4);
expect(q.length()).toBeCloseTo(Math.sqrt(30), 5);
});
it('should return 1 for a normalized quaternion', function ()
{
var q = new Quaternion(1, 0, 0, 0);
expect(q.length()).toBeCloseTo(1, 5);
});
});
describe('lengthSq', function ()
{
it('should return the squared length of the quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 1);
expect(q.lengthSq()).toBeCloseTo(1, 5);
});
it('should return zero for a zero quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 0);
expect(q.lengthSq()).toBe(0);
});
it('should return correct squared length for general quaternion', function ()
{
var q = new Quaternion(1, 2, 3, 4);
expect(q.lengthSq()).toBeCloseTo(30, 5);
});
it('should equal length squared', function ()
{
var q = new Quaternion(1, 2, 3, 4);
expect(q.lengthSq()).toBeCloseTo(q.length() * q.length(), 5);
});
});
describe('normalize', function ()
{
it('should normalize to unit length', function ()
{
var q = new Quaternion(1, 2, 3, 4);
q.normalize();
expect(q.length()).toBeCloseTo(1, 5);
});
it('should return itself', function ()
{
var q = new Quaternion(1, 2, 3, 4);
expect(q.normalize()).toBe(q);
});
it('should not change a zero-length quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 0);
q.normalize();
expect(q.x).toBe(0);
expect(q.y).toBe(0);
expect(q.z).toBe(0);
expect(q.w).toBe(0);
});
it('should not change an already-normalized quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.normalize();
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
});
describe('dot', function ()
{
it('should return the dot product of two quaternions', function ()
{
var q1 = new Quaternion(1, 2, 3, 4);
var q2 = { x: 1, y: 2, z: 3, w: 4 };
expect(q1.dot(q2)).toBeCloseTo(30, 5);
});
it('should return zero for perpendicular quaternions', function ()
{
var q1 = new Quaternion(1, 0, 0, 0);
var q2 = { x: 0, y: 1, z: 0, w: 0 };
expect(q1.dot(q2)).toBe(0);
});
it('should return 1 for identical unit quaternions', function ()
{
var q1 = new Quaternion(0, 0, 0, 1);
var q2 = { x: 0, y: 0, z: 0, w: 1 };
expect(q1.dot(q2)).toBeCloseTo(1, 5);
});
});
describe('lerp', function ()
{
it('should return the starting quaternion when t=0', function ()
{
var q = new Quaternion(1, 0, 0, 0);
var target = { x: 0, y: 1, z: 0, w: 0 };
q.lerp(target, 0);
expect(q.x).toBeCloseTo(1, 5);
expect(q.y).toBeCloseTo(0, 5);
});
it('should return the target quaternion when t=1', function ()
{
var q = new Quaternion(1, 0, 0, 0);
var target = { x: 0, y: 1, z: 0, w: 0 };
q.lerp(target, 1);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(1, 5);
});
it('should interpolate halfway when t=0.5', function ()
{
var q = new Quaternion(0, 0, 0, 0);
var target = { x: 2, y: 4, z: 6, w: 8 };
q.lerp(target, 0.5);
expect(q.x).toBeCloseTo(1, 5);
expect(q.y).toBeCloseTo(2, 5);
expect(q.z).toBeCloseTo(3, 5);
expect(q.w).toBeCloseTo(4, 5);
});
it('should default t to 0 when not provided', function ()
{
var q = new Quaternion(1, 2, 3, 4);
var target = { x: 10, y: 20, z: 30, w: 40 };
q.lerp(target);
expect(q.x).toBeCloseTo(1, 5);
expect(q.y).toBeCloseTo(2, 5);
expect(q.z).toBeCloseTo(3, 5);
expect(q.w).toBeCloseTo(4, 5);
});
it('should return itself', function ()
{
var q = new Quaternion(1, 0, 0, 0);
expect(q.lerp({ x: 0, y: 1, z: 0, w: 0 }, 0.5)).toBe(q);
});
});
describe('rotationTo', function ()
{
it('should return itself', function ()
{
var q = new Quaternion();
var a = new Vector3(1, 0, 0);
var b = new Vector3(0, 1, 0);
expect(q.rotationTo(a, b)).toBe(q);
});
it('should set identity when vectors are the same', function ()
{
var q = new Quaternion();
var a = new Vector3(1, 0, 0);
q.rotationTo(a, a);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
it('should produce a unit quaternion for any two unit vectors', function ()
{
var q = new Quaternion();
var a = new Vector3(1, 0, 0);
var b = new Vector3(0, 1, 0);
q.rotationTo(a, b);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should handle opposite vectors', function ()
{
var q = new Quaternion();
var a = new Vector3(0, 1, 0);
var b = new Vector3(0, -1, 0);
q.rotationTo(a, b);
expect(q.length()).toBeCloseTo(1, 5);
});
});
describe('setAxes', function ()
{
it('should return itself', function ()
{
var q = new Quaternion();
var view = new Vector3(0, 0, -1);
var right = new Vector3(1, 0, 0);
var up = new Vector3(0, 1, 0);
expect(q.setAxes(view, right, up)).toBe(q);
});
it('should produce a unit quaternion from orthogonal axes', function ()
{
var q = new Quaternion();
var view = new Vector3(0, 0, -1);
var right = new Vector3(1, 0, 0);
var up = new Vector3(0, 1, 0);
q.setAxes(view, right, up);
expect(q.length()).toBeCloseTo(1, 5);
});
});
describe('identity', function ()
{
it('should set quaternion to identity (0, 0, 0, 1)', function ()
{
var q = new Quaternion(1, 2, 3, 4);
q.identity();
expect(q.x).toBe(0);
expect(q.y).toBe(0);
expect(q.z).toBe(0);
expect(q.w).toBe(1);
});
it('should return itself', function ()
{
var q = new Quaternion();
expect(q.identity()).toBe(q);
});
it('should have length of 1', function ()
{
var q = new Quaternion();
q.identity();
expect(q.length()).toBeCloseTo(1, 5);
});
});
describe('setAxisAngle', function ()
{
it('should return itself', function ()
{
var q = new Quaternion();
var axis = new Vector3(0, 1, 0);
expect(q.setAxisAngle(axis, Math.PI)).toBe(q);
});
it('should produce a unit quaternion', function ()
{
var q = new Quaternion();
var axis = new Vector3(0, 1, 0);
q.setAxisAngle(axis, Math.PI / 2);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should set identity quaternion for zero angle', function ()
{
var q = new Quaternion();
var axis = new Vector3(0, 1, 0);
q.setAxisAngle(axis, 0);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
it('should set correct quaternion for 90 degree rotation around Y', function ()
{
var q = new Quaternion();
var axis = new Vector3(0, 1, 0);
q.setAxisAngle(axis, Math.PI / 2);
var halfAngle = Math.PI / 4;
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(Math.sin(halfAngle), 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(Math.cos(halfAngle), 5);
});
});
describe('multiply', function ()
{
it('should return itself', function ()
{
var q = new Quaternion(0, 0, 0, 1);
var b = { x: 0, y: 0, z: 0, w: 1 };
expect(q.multiply(b)).toBe(q);
});
it('should multiply by identity and remain unchanged', function ()
{
var q = new Quaternion(1, 2, 3, 4);
var identity = { x: 0, y: 0, z: 0, w: 1 };
q.multiply(identity);
expect(q.x).toBeCloseTo(1, 5);
expect(q.y).toBeCloseTo(2, 5);
expect(q.z).toBeCloseTo(3, 5);
expect(q.w).toBeCloseTo(4, 5);
});
it('should combine two 90 degree rotations into a 180 degree rotation', function ()
{
var q = new Quaternion();
var axis = new Vector3(0, 0, 1);
q.setAxisAngle(axis, Math.PI / 2);
var half = Math.sin(Math.PI / 4);
var halfW = Math.cos(Math.PI / 4);
q.multiply({ x: 0, y: 0, z: half, w: halfW });
expect(q.length()).toBeCloseTo(1, 5);
});
it('should not be commutative in general', function ()
{
var q1 = new Quaternion(1, 0, 0, 0);
var q2 = { x: 0, y: 1, z: 0, w: 0 };
var q1copy = new Quaternion(1, 0, 0, 0);
q1.multiply(q2);
var q2q = new Quaternion(0, 1, 0, 0);
q2q.multiply({ x: 1, y: 0, z: 0, w: 0 });
// q1*q2 != q2*q1 in general for non-trivial quaternions
expect(q1.z).not.toBeCloseTo(q2q.z, 5);
});
});
describe('slerp', function ()
{
it('should return itself', function ()
{
var q = new Quaternion(0, 0, 0, 1);
expect(q.slerp({ x: 0, y: 0, z: 0, w: 1 }, 0.5)).toBe(q);
});
it('should return starting quaternion when t=0', function ()
{
var q = new Quaternion(0, 0, 0, 1);
var target = { x: 0, y: 1, z: 0, w: 0 };
q.slerp(target, 0);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
it('should return target quaternion when t=1', function ()
{
var q = new Quaternion(0, 0, 0, 1);
var target = { x: 0, y: 1, z: 0, w: 0 };
q.slerp(target, 1);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(1, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(0, 5);
});
it('should interpolate between identical quaternions', function ()
{
var q = new Quaternion(0, 0, 0, 1);
var target = { x: 0, y: 0, z: 0, w: 1 };
q.slerp(target, 0.5);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
it('should handle negative dot product case (opposite hemispheres)', function ()
{
var q = new Quaternion(0, 0, 0, 1);
var target = { x: 0, y: 0, z: 0, w: -1 };
q.slerp(target, 0.5);
expect(q.length()).toBeCloseTo(1, 5);
});
});
describe('invert', function ()
{
it('should return itself', function ()
{
var q = new Quaternion(0, 0, 0, 1);
expect(q.invert()).toBe(q);
});
it('should invert the identity quaternion to itself', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.invert();
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
it('should negate x, y, z and keep w positive for a unit quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 1);
var axis = new Vector3(1, 0, 0);
q.setAxisAngle(axis, Math.PI / 2);
var origX = q.x;
var origW = q.w;
q.invert();
expect(q.x).toBeCloseTo(-origX, 5);
expect(q.w).toBeCloseTo(origW, 5);
});
it('should produce identity when multiplied by original', function ()
{
var q = new Quaternion();
var axis = new Vector3(0, 1, 0);
q.setAxisAngle(axis, Math.PI / 4);
var origX = q.x;
var origY = q.y;
var origZ = q.z;
var origW = q.w;
var inv = new Quaternion();
inv.setAxisAngle(axis, Math.PI / 4);
inv.invert();
q.multiply(inv);
expect(q.x).toBeCloseTo(0, 4);
expect(q.y).toBeCloseTo(0, 4);
expect(q.z).toBeCloseTo(0, 4);
expect(Math.abs(q.w)).toBeCloseTo(1, 4);
});
it('should not change a zero-length quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 0);
q.invert();
expect(q.x).toBe(0);
expect(q.y).toBe(0);
expect(q.z).toBe(0);
expect(q.w).toBe(0);
});
});
describe('conjugate', function ()
{
it('should negate x, y, z components', function ()
{
var q = new Quaternion(1, 2, 3, 4);
q.conjugate();
expect(q.x).toBe(-1);
expect(q.y).toBe(-2);
expect(q.z).toBe(-3);
expect(q.w).toBe(4);
});
it('should return itself', function ()
{
var q = new Quaternion(1, 2, 3, 4);
expect(q.conjugate()).toBe(q);
});
it('should leave w unchanged', function ()
{
var q = new Quaternion(1, 2, 3, 5);
q.conjugate();
expect(q.w).toBe(5);
});
it('should be its own inverse (double conjugate is original)', function ()
{
var q = new Quaternion(1, 2, 3, 4);
q.conjugate().conjugate();
expect(q.x).toBe(1);
expect(q.y).toBe(2);
expect(q.z).toBe(3);
expect(q.w).toBe(4);
});
});
describe('rotateX', function ()
{
it('should return itself', function ()
{
var q = new Quaternion(0, 0, 0, 1);
expect(q.rotateX(Math.PI / 2)).toBe(q);
});
it('should produce a unit quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.rotateX(Math.PI / 3);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should not change quaternion when rotating by zero', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.rotateX(0);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
it('should set correct quaternion when rotating identity by 90 degrees', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.rotateX(Math.PI / 2);
var half = Math.PI / 4;
expect(q.x).toBeCloseTo(Math.sin(half), 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(Math.cos(half), 5);
});
});
describe('rotateY', function ()
{
it('should return itself', function ()
{
var q = new Quaternion(0, 0, 0, 1);
expect(q.rotateY(Math.PI / 2)).toBe(q);
});
it('should produce a unit quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.rotateY(Math.PI / 3);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should not change quaternion when rotating by zero', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.rotateY(0);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
it('should set correct quaternion when rotating identity by 90 degrees', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.rotateY(Math.PI / 2);
var half = Math.PI / 4;
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(Math.sin(half), 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(Math.cos(half), 5);
});
});
describe('rotateZ', function ()
{
it('should return itself', function ()
{
var q = new Quaternion(0, 0, 0, 1);
expect(q.rotateZ(Math.PI / 2)).toBe(q);
});
it('should produce a unit quaternion', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.rotateZ(Math.PI / 3);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should not change quaternion when rotating by zero', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.rotateZ(0);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
it('should set correct quaternion when rotating identity by 90 degrees', function ()
{
var q = new Quaternion(0, 0, 0, 1);
q.rotateZ(Math.PI / 2);
var half = Math.PI / 4;
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(Math.sin(half), 5);
expect(q.w).toBeCloseTo(Math.cos(half), 5);
});
});
describe('calculateW', function ()
{
it('should return itself', function ()
{
var q = new Quaternion(0, 0, 0, 0);
expect(q.calculateW()).toBe(q);
});
it('should calculate w for zero x, y, z to be -1', function ()
{
var q = new Quaternion(0, 0, 0, 0);
q.calculateW();
expect(q.w).toBeCloseTo(-1, 5);
});
it('should compute w to make unit quaternion length', function ()
{
var q = new Quaternion(0.5, 0.5, 0.5, 0);
q.calculateW();
var len = Math.sqrt(0.5 * 0.5 + 0.5 * 0.5 + 0.5 * 0.5 + q.w * q.w);
expect(len).toBeCloseTo(1, 5);
});
});
describe('setFromEuler', function ()
{
it('should return itself', function ()
{
var q = new Quaternion();
var euler = { x: 0, y: 0, z: 0, order: 'XYZ' };
expect(q.setFromEuler(euler)).toBe(q);
});
it('should produce identity for zero Euler angles (XYZ order)', function ()
{
var q = new Quaternion();
var euler = { x: 0, y: 0, z: 0, order: 'XYZ' };
q.setFromEuler(euler);
expect(q.x).toBeCloseTo(0, 5);
expect(q.y).toBeCloseTo(0, 5);
expect(q.z).toBeCloseTo(0, 5);
expect(q.w).toBeCloseTo(1, 5);
});
it('should produce a unit quaternion for XYZ order', function ()
{
var q = new Quaternion();
var euler = { x: 0.3, y: 0.5, z: 0.7, order: 'XYZ' };
q.setFromEuler(euler);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should produce a unit quaternion for YXZ order', function ()
{
var q = new Quaternion();
var euler = { x: 0.3, y: 0.5, z: 0.7, order: 'YXZ' };
q.setFromEuler(euler);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should produce a unit quaternion for ZXY order', function ()
{
var q = new Quaternion();
var euler = { x: 0.3, y: 0.5, z: 0.7, order: 'ZXY' };
q.setFromEuler(euler);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should produce a unit quaternion for ZYX order', function ()
{
var q = new Quaternion();
var euler = { x: 0.3, y: 0.5, z: 0.7, order: 'ZYX' };
q.setFromEuler(euler);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should produce a unit quaternion for YZX order', function ()
{
var q = new Quaternion();
var euler = { x: 0.3, y: 0.5, z: 0.7, order: 'YZX' };
q.setFromEuler(euler);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should produce a unit quaternion for XZY order', function ()
{
var q = new Quaternion();
var euler = { x: 0.3, y: 0.5, z: 0.7, order: 'XZY' };
q.setFromEuler(euler);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should produce different results for different rotation orders', function ()
{
var q1 = new Quaternion();
var q2 = new Quaternion();
var euler1 = { x: 0.5, y: 0.5, z: 0.5, order: 'XYZ' };
var euler2 = { x: 0.5, y: 0.5, z: 0.5, order: 'ZYX' };
q1.setFromEuler(euler1);
q2.setFromEuler(euler2);
var same = (
Math.abs(q1.x - q2.x) < 0.0001 &&
Math.abs(q1.y - q2.y) < 0.0001 &&
Math.abs(q1.z - q2.z) < 0.0001 &&
Math.abs(q1.w - q2.w) < 0.0001
);
expect(same).toBe(false);
});
});
describe('setFromRotationMatrix', function ()
{
it('should return itself', function ()
{
var q = new Quaternion();
var mat4 = { val: new Float32Array(16) };
mat4.val[0] = 1; mat4.val[5] = 1; mat4.val[10] = 1; mat4.val[15] = 1;
expect(q.setFromRotationMatrix(mat4)).toBe(q);
});
it('should produce identity quaternion from identity matrix', function ()
{
var q = new Quaternion();
var val = new Float32Array(16);
val[0] = 1; val[5] = 1; val[10] = 1; val[15] = 1;
q.setFromRotationMatrix({ val: val });
expect(q.length()).toBeCloseTo(1, 4);
});
it('should produce a unit quaternion from a valid rotation matrix', function ()
{
var q = new Quaternion();
// 90 degree rotation around Z axis as column-major matrix
var val = new Float32Array(16);
val[0] = 0; val[1] = 1; val[2] = 0; val[3] = 0;
val[4] = -1; val[5] = 0; val[6] = 0; val[7] = 0;
val[8] = 0; val[9] = 0; val[10] = 1; val[11] = 0;
val[12] = 0; val[13] = 0; val[14] = 0; val[15] = 1;
q.setFromRotationMatrix({ val: val });
expect(q.length()).toBeCloseTo(1, 4);
});
});
describe('fromMat3', function ()
{
it('should return itself', function ()
{
var q = new Quaternion();
var mat3 = new Matrix3();
expect(q.fromMat3(mat3)).toBe(q);
});
it('should produce identity quaternion from identity matrix', function ()
{
var q = new Quaternion();
var mat3 = new Matrix3();
q.fromMat3(mat3);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should produce unit quaternion from a rotation matrix', function ()
{
var q = new Quaternion();
var mat3 = new Matrix3();
// 90 degree rotation around Z axis
var m = mat3.val;
m[0] = 0; m[1] = 1; m[2] = 0;
m[3] = -1; m[4] = 0; m[5] = 0;
m[6] = 0; m[7] = 0; m[8] = 1;
q.fromMat3(mat3);
expect(q.length()).toBeCloseTo(1, 5);
});
it('should match setAxisAngle result for a known rotation', function ()
{
var q1 = new Quaternion();
var axis = new Vector3(0, 0, 1);
q1.setAxisAngle(axis, Math.PI / 2);
var q2 = new Quaternion();
var mat3 = new Matrix3();
var m = mat3.val;
var c = Math.cos(Math.PI / 2);
var s = Math.sin(Math.PI / 2);
m[0] = c; m[1] = s; m[2] = 0;
m[3] = -s; m[4] = c; m[5] = 0;
m[6] = 0; m[7] = 0; m[8] = 1;
q2.fromMat3(mat3);
expect(Math.abs(q1.x) - Math.abs(q2.x)).toBeCloseTo(0, 4);
expect(Math.abs(q1.y) - Math.abs(q2.y)).toBeCloseTo(0, 4);
expect(Math.abs(q1.z) - Math.abs(q2.z)).toBeCloseTo(0, 4);
expect(Math.abs(q1.w) - Math.abs(q2.w)).toBeCloseTo(0, 4);
});
});
});