phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
802 lines (684 loc) • 24.6 kB
JavaScript
var Matrix3 = require('../../src/math/Matrix3');
describe('Matrix3', function ()
{
describe('constructor', function ()
{
it('should create an identity matrix by default', function ()
{
var m = new Matrix3();
var v = m.val;
expect(v[0]).toBe(1);
expect(v[1]).toBe(0);
expect(v[2]).toBe(0);
expect(v[3]).toBe(0);
expect(v[4]).toBe(1);
expect(v[5]).toBe(0);
expect(v[6]).toBe(0);
expect(v[7]).toBe(0);
expect(v[8]).toBe(1);
});
it('should create a Float32Array of length 9', function ()
{
var m = new Matrix3();
expect(m.val).toBeInstanceOf(Float32Array);
expect(m.val.length).toBe(9);
});
it('should copy values from a given Matrix3', function ()
{
var src = new Matrix3();
src.val[0] = 2;
src.val[4] = 5;
src.val[8] = 9;
var m = new Matrix3(src);
expect(m.val[0]).toBe(2);
expect(m.val[4]).toBe(5);
expect(m.val[8]).toBe(9);
});
it('should create an independent copy when passed a Matrix3', function ()
{
var src = new Matrix3();
var m = new Matrix3(src);
m.val[0] = 99;
expect(src.val[0]).toBe(1);
});
});
describe('clone', function ()
{
it('should return a new Matrix3 instance', function ()
{
var m = new Matrix3();
var clone = m.clone();
expect(clone).not.toBe(m);
expect(clone).toBeInstanceOf(Matrix3);
});
it('should copy all values to the clone', function ()
{
var m = new Matrix3();
m.fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
var clone = m.clone();
for (var i = 0; i < 9; i++)
{
expect(clone.val[i]).toBe(m.val[i]);
}
});
it('should be independent from the original', function ()
{
var m = new Matrix3();
var clone = m.clone();
clone.val[0] = 99;
expect(m.val[0]).toBe(1);
});
});
describe('set', function ()
{
it('should copy values from the source Matrix3', function ()
{
var src = new Matrix3();
src.fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
var m = new Matrix3();
m.set(src);
for (var i = 0; i < 9; i++)
{
expect(m.val[i]).toBe(src.val[i]);
}
});
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.set(new Matrix3());
expect(result).toBe(m);
});
});
describe('copy', function ()
{
it('should copy all 9 values from the source', function ()
{
var src = new Matrix3();
src.fromArray([9, 8, 7, 6, 5, 4, 3, 2, 1]);
var m = new Matrix3();
m.copy(src);
expect(m.val[0]).toBe(9);
expect(m.val[1]).toBe(8);
expect(m.val[2]).toBe(7);
expect(m.val[3]).toBe(6);
expect(m.val[4]).toBe(5);
expect(m.val[5]).toBe(4);
expect(m.val[6]).toBe(3);
expect(m.val[7]).toBe(2);
expect(m.val[8]).toBe(1);
});
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.copy(new Matrix3());
expect(result).toBe(m);
});
it('should not affect the source matrix', function ()
{
var src = new Matrix3();
src.fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
var m = new Matrix3();
m.copy(src);
m.val[0] = 99;
expect(src.val[0]).toBe(1);
});
});
describe('fromMat4', function ()
{
it('should extract the upper-left 3x3 from a Matrix4', function ()
{
var mat4 = {
val: new Float32Array([
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
])
};
var m = new Matrix3();
m.fromMat4(mat4);
expect(m.val[0]).toBe(1);
expect(m.val[1]).toBe(2);
expect(m.val[2]).toBe(3);
expect(m.val[3]).toBe(5);
expect(m.val[4]).toBe(6);
expect(m.val[5]).toBe(7);
expect(m.val[6]).toBe(9);
expect(m.val[7]).toBe(10);
expect(m.val[8]).toBe(11);
});
it('should return this Matrix3', function ()
{
var mat4 = { val: new Float32Array(16) };
mat4.val[0] = 1; mat4.val[5] = 1; mat4.val[10] = 1; mat4.val[15] = 1;
var m = new Matrix3();
var result = m.fromMat4(mat4);
expect(result).toBe(m);
});
});
describe('fromArray', function ()
{
it('should set all 9 values from the array', function ()
{
var m = new Matrix3();
m.fromArray([10, 20, 30, 40, 50, 60, 70, 80, 90]);
expect(m.val[0]).toBe(10);
expect(m.val[1]).toBe(20);
expect(m.val[2]).toBe(30);
expect(m.val[3]).toBe(40);
expect(m.val[4]).toBe(50);
expect(m.val[5]).toBe(60);
expect(m.val[6]).toBe(70);
expect(m.val[7]).toBe(80);
expect(m.val[8]).toBe(90);
});
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.fromArray([1, 0, 0, 0, 1, 0, 0, 0, 1]);
expect(result).toBe(m);
});
it('should work with floating point values', function ()
{
var m = new Matrix3();
m.fromArray([1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5]);
expect(m.val[0]).toBeCloseTo(1.5);
expect(m.val[4]).toBeCloseTo(5.5);
expect(m.val[8]).toBeCloseTo(9.5);
});
});
describe('identity', function ()
{
it('should reset to the identity matrix', function ()
{
var m = new Matrix3();
m.fromArray([9, 8, 7, 6, 5, 4, 3, 2, 1]);
m.identity();
expect(m.val[0]).toBe(1);
expect(m.val[1]).toBe(0);
expect(m.val[2]).toBe(0);
expect(m.val[3]).toBe(0);
expect(m.val[4]).toBe(1);
expect(m.val[5]).toBe(0);
expect(m.val[6]).toBe(0);
expect(m.val[7]).toBe(0);
expect(m.val[8]).toBe(1);
});
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.identity();
expect(result).toBe(m);
});
});
describe('transpose', function ()
{
it('should transpose the matrix values', function ()
{
var m = new Matrix3();
m.fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
m.transpose();
// column-major: original col1=[1,2,3], col2=[4,5,6], col3=[7,8,9]
// after transpose: row becomes column
expect(m.val[0]).toBe(1);
expect(m.val[1]).toBe(4);
expect(m.val[2]).toBe(7);
expect(m.val[3]).toBe(2);
expect(m.val[4]).toBe(5);
expect(m.val[5]).toBe(8);
expect(m.val[6]).toBe(3);
expect(m.val[7]).toBe(6);
expect(m.val[8]).toBe(9);
});
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.transpose();
expect(result).toBe(m);
});
it('should leave the identity matrix unchanged', function ()
{
var m = new Matrix3();
m.transpose();
expect(m.val[0]).toBe(1);
expect(m.val[4]).toBe(1);
expect(m.val[8]).toBe(1);
expect(m.val[1]).toBe(0);
expect(m.val[3]).toBe(0);
});
it('should be its own inverse: transposing twice returns original', function ()
{
var m = new Matrix3();
m.fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
m.transpose().transpose();
expect(m.val[0]).toBe(1);
expect(m.val[1]).toBe(2);
expect(m.val[2]).toBe(3);
expect(m.val[3]).toBe(4);
expect(m.val[4]).toBe(5);
expect(m.val[5]).toBe(6);
expect(m.val[6]).toBe(7);
expect(m.val[7]).toBe(8);
expect(m.val[8]).toBe(9);
});
});
describe('invert', function ()
{
it('should invert the identity matrix to itself', function ()
{
var m = new Matrix3();
var result = m.invert();
expect(result).toBe(m);
expect(m.val[0]).toBeCloseTo(1);
expect(m.val[4]).toBeCloseTo(1);
expect(m.val[8]).toBeCloseTo(1);
expect(m.val[1]).toBeCloseTo(0);
expect(m.val[3]).toBeCloseTo(0);
});
it('should return null if determinant is zero', function ()
{
var m = new Matrix3();
// All-zero matrix has det=0
m.fromArray([0, 0, 0, 0, 0, 0, 0, 0, 0]);
var result = m.invert();
expect(result).toBeNull();
});
it('should correctly invert a known matrix', function ()
{
var m = new Matrix3();
// A simple scaling matrix: scale by 2 on x, 3 on y
m.fromArray([2, 0, 0, 0, 3, 0, 0, 0, 1]);
m.invert();
expect(m.val[0]).toBeCloseTo(0.5);
expect(m.val[4]).toBeCloseTo(1 / 3);
expect(m.val[8]).toBeCloseTo(1);
});
it('should produce identity when multiplied with its inverse', function ()
{
var m = new Matrix3();
m.fromArray([1, 2, 0, 0, 1, 0, 3, 4, 1]);
var inv = m.clone();
inv.invert();
m.multiply(inv);
expect(m.val[0]).toBeCloseTo(1);
expect(m.val[4]).toBeCloseTo(1);
expect(m.val[8]).toBeCloseTo(1);
expect(m.val[1]).toBeCloseTo(0);
expect(m.val[2]).toBeCloseTo(0);
expect(m.val[3]).toBeCloseTo(0);
expect(m.val[5]).toBeCloseTo(0);
expect(m.val[6]).toBeCloseTo(0);
expect(m.val[7]).toBeCloseTo(0);
});
});
describe('adjoint', function ()
{
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.adjoint();
expect(result).toBe(m);
});
it('should compute the adjoint of the identity matrix as identity', function ()
{
var m = new Matrix3();
m.adjoint();
expect(m.val[0]).toBe(1);
expect(m.val[4]).toBe(1);
expect(m.val[8]).toBe(1);
expect(m.val[1]).toBe(0);
expect(m.val[3]).toBe(0);
});
it('should compute the correct adjoint of a known matrix', function ()
{
var m = new Matrix3();
m.fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
m.adjoint();
// adjoint = transpose of cofactor matrix
// For this singular matrix (det=0), values are well-defined
expect(m.val[0]).toBeCloseTo(-3);
expect(m.val[1]).toBeCloseTo(6);
expect(m.val[2]).toBeCloseTo(-3);
expect(m.val[3]).toBeCloseTo(6);
expect(m.val[4]).toBeCloseTo(-12);
expect(m.val[5]).toBeCloseTo(6);
expect(m.val[6]).toBeCloseTo(-3);
expect(m.val[7]).toBeCloseTo(6);
expect(m.val[8]).toBeCloseTo(-3);
});
});
describe('determinant', function ()
{
it('should return 1 for the identity matrix', function ()
{
var m = new Matrix3();
expect(m.determinant()).toBeCloseTo(1);
});
it('should return 0 for a zero matrix', function ()
{
var m = new Matrix3();
m.fromArray([0, 0, 0, 0, 0, 0, 0, 0, 0]);
expect(m.determinant()).toBe(0);
});
it('should return 0 for a singular matrix', function ()
{
var m = new Matrix3();
m.fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
expect(m.determinant()).toBeCloseTo(0);
});
it('should return the correct determinant for a known matrix', function ()
{
var m = new Matrix3();
// det = 2*3*1 = 6 for diagonal [2,0,0, 0,3,0, 0,0,1]
m.fromArray([2, 0, 0, 0, 3, 0, 0, 0, 1]);
expect(m.determinant()).toBeCloseTo(6);
});
it('should return a negative determinant for orientation-reversing matrices', function ()
{
var m = new Matrix3();
m.fromArray([-1, 0, 0, 0, 1, 0, 0, 0, 1]);
expect(m.determinant()).toBeCloseTo(-1);
});
});
describe('multiply', function ()
{
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.multiply(new Matrix3());
expect(result).toBe(m);
});
it('should leave the identity matrix unchanged when multiplied by identity', function ()
{
var m = new Matrix3();
m.multiply(new Matrix3());
expect(m.val[0]).toBeCloseTo(1);
expect(m.val[4]).toBeCloseTo(1);
expect(m.val[8]).toBeCloseTo(1);
expect(m.val[1]).toBeCloseTo(0);
expect(m.val[3]).toBeCloseTo(0);
});
it('should correctly multiply two known matrices', function ()
{
var a = new Matrix3();
a.fromArray([1, 0, 0, 0, 1, 0, 2, 3, 1]); // 2D translation matrix in col-major
var b = new Matrix3();
b.fromArray([1, 0, 0, 0, 1, 0, 4, 5, 1]);
a.multiply(b);
// Combined translation should be (2+4, 3+5) = (6, 8)
expect(a.val[6]).toBeCloseTo(6);
expect(a.val[7]).toBeCloseTo(8);
expect(a.val[8]).toBeCloseTo(1);
});
it('should not be commutative in general', function ()
{
var a = new Matrix3();
a.fromArray([1, 2, 3, 4, 5, 6, 7, 8, 9]);
var b = new Matrix3();
b.fromArray([9, 8, 7, 6, 5, 4, 3, 2, 1]);
var ab = a.clone();
ab.multiply(b);
var ba = b.clone();
ba.multiply(a);
// At least one value should differ
var allSame = true;
for (var i = 0; i < 9; i++)
{
if (Math.abs(ab.val[i] - ba.val[i]) > 0.001)
{
allSame = false;
break;
}
}
expect(allSame).toBe(false);
});
});
describe('translate', function ()
{
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.translate({ x: 0, y: 0 });
expect(result).toBe(m);
});
it('should translate the identity matrix by (tx, ty)', function ()
{
var m = new Matrix3();
m.translate({ x: 5, y: 3 });
// For identity matrix, translate just sets col 2 = [tx, ty, 1]
expect(m.val[6]).toBeCloseTo(5);
expect(m.val[7]).toBeCloseTo(3);
expect(m.val[8]).toBeCloseTo(1);
});
it('should leave the rest of the identity matrix unchanged', function ()
{
var m = new Matrix3();
m.translate({ x: 10, y: 20 });
expect(m.val[0]).toBe(1);
expect(m.val[1]).toBe(0);
expect(m.val[2]).toBe(0);
expect(m.val[3]).toBe(0);
expect(m.val[4]).toBe(1);
expect(m.val[5]).toBe(0);
});
it('should accumulate translations when called twice', function ()
{
var m = new Matrix3();
m.translate({ x: 3, y: 4 });
m.translate({ x: 1, y: 2 });
expect(m.val[6]).toBeCloseTo(4);
expect(m.val[7]).toBeCloseTo(6);
});
it('should handle negative translation values', function ()
{
var m = new Matrix3();
m.translate({ x: -5, y: -10 });
expect(m.val[6]).toBeCloseTo(-5);
expect(m.val[7]).toBeCloseTo(-10);
});
});
describe('rotate', function ()
{
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.rotate(0);
expect(result).toBe(m);
});
it('should leave the identity matrix unchanged when rotating by 0', function ()
{
var m = new Matrix3();
m.rotate(0);
expect(m.val[0]).toBeCloseTo(1);
expect(m.val[1]).toBeCloseTo(0);
expect(m.val[3]).toBeCloseTo(0);
expect(m.val[4]).toBeCloseTo(1);
});
it('should rotate 90 degrees correctly', function ()
{
var m = new Matrix3();
m.rotate(Math.PI / 2);
expect(m.val[0]).toBeCloseTo(0);
expect(m.val[1]).toBeCloseTo(1);
expect(m.val[3]).toBeCloseTo(-1);
expect(m.val[4]).toBeCloseTo(0);
});
it('should rotate 180 degrees correctly', function ()
{
var m = new Matrix3();
m.rotate(Math.PI);
expect(m.val[0]).toBeCloseTo(-1);
expect(m.val[1]).toBeCloseTo(0);
expect(m.val[3]).toBeCloseTo(0);
expect(m.val[4]).toBeCloseTo(-1);
});
it('should rotate by negative angles', function ()
{
var m = new Matrix3();
m.rotate(-Math.PI / 2);
expect(m.val[0]).toBeCloseTo(0);
expect(m.val[1]).toBeCloseTo(-1);
expect(m.val[3]).toBeCloseTo(1);
expect(m.val[4]).toBeCloseTo(0);
});
it('should preserve the third column (z axis unchanged)', function ()
{
var m = new Matrix3();
m.rotate(Math.PI / 4);
expect(m.val[2]).toBeCloseTo(0);
expect(m.val[5]).toBeCloseTo(0);
expect(m.val[8]).toBeCloseTo(1);
});
});
describe('scale', function ()
{
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.scale({ x: 1, y: 1 });
expect(result).toBe(m);
});
it('should scale the identity matrix by (sx, sy)', function ()
{
var m = new Matrix3();
m.scale({ x: 2, y: 3 });
expect(m.val[0]).toBeCloseTo(2);
expect(m.val[4]).toBeCloseTo(3);
expect(m.val[8]).toBeCloseTo(1);
});
it('should leave the identity unchanged when scaled by (1, 1)', function ()
{
var m = new Matrix3();
m.scale({ x: 1, y: 1 });
expect(m.val[0]).toBeCloseTo(1);
expect(m.val[4]).toBeCloseTo(1);
expect(m.val[8]).toBeCloseTo(1);
});
it('should zero out columns when scaled by 0', function ()
{
var m = new Matrix3();
m.scale({ x: 0, y: 0 });
expect(m.val[0]).toBeCloseTo(0);
expect(m.val[1]).toBeCloseTo(0);
expect(m.val[2]).toBeCloseTo(0);
expect(m.val[3]).toBeCloseTo(0);
expect(m.val[4]).toBeCloseTo(0);
expect(m.val[5]).toBeCloseTo(0);
});
it('should handle negative scale values', function ()
{
var m = new Matrix3();
m.scale({ x: -1, y: -2 });
expect(m.val[0]).toBeCloseTo(-1);
expect(m.val[4]).toBeCloseTo(-2);
});
});
describe('fromQuat', function ()
{
it('should return this Matrix3', function ()
{
var m = new Matrix3();
var result = m.fromQuat({ x: 0, y: 0, z: 0, w: 1 });
expect(result).toBe(m);
});
it('should produce the identity matrix from the identity quaternion', function ()
{
var m = new Matrix3();
m.fromQuat({ x: 0, y: 0, z: 0, w: 1 });
expect(m.val[0]).toBeCloseTo(1);
expect(m.val[1]).toBeCloseTo(0);
expect(m.val[2]).toBeCloseTo(0);
expect(m.val[3]).toBeCloseTo(0);
expect(m.val[4]).toBeCloseTo(1);
expect(m.val[5]).toBeCloseTo(0);
expect(m.val[6]).toBeCloseTo(0);
expect(m.val[7]).toBeCloseTo(0);
expect(m.val[8]).toBeCloseTo(1);
});
it('should produce a 180 degree rotation around Z from quaternion (0, 0, 1, 0)', function ()
{
var m = new Matrix3();
m.fromQuat({ x: 0, y: 0, z: 1, w: 0 });
// 180 deg around Z: x -> -x, y -> -y, z -> z
expect(m.val[0]).toBeCloseTo(-1);
expect(m.val[4]).toBeCloseTo(-1);
expect(m.val[8]).toBeCloseTo(1);
});
it('should produce a 90 degree rotation around Z from quaternion', function ()
{
var half = Math.sqrt(2) / 2;
var m = new Matrix3();
m.fromQuat({ x: 0, y: 0, z: half, w: half });
// fromQuat stores column-major: out[1]=xy-wz=-1, out[3]=xy+wz=1
expect(m.val[0]).toBeCloseTo(0);
expect(m.val[1]).toBeCloseTo(-1);
expect(m.val[3]).toBeCloseTo(1);
expect(m.val[4]).toBeCloseTo(0);
expect(m.val[8]).toBeCloseTo(1);
});
});
describe('normalFromMat4', function ()
{
it('should return this Matrix3 for an invertible Matrix4', function ()
{
var mat4 = {
val: new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
])
};
var m = new Matrix3();
var result = m.normalFromMat4(mat4);
expect(result).toBe(m);
});
it('should return null when Matrix4 determinant is zero', function ()
{
var mat4 = { val: new Float32Array(16) }; // all zeros, det = 0
var m = new Matrix3();
var result = m.normalFromMat4(mat4);
expect(result).toBeNull();
});
it('should produce the identity matrix from an identity Matrix4', function ()
{
var mat4 = {
val: new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
])
};
var m = new Matrix3();
m.normalFromMat4(mat4);
expect(m.val[0]).toBeCloseTo(1);
expect(m.val[4]).toBeCloseTo(1);
expect(m.val[8]).toBeCloseTo(1);
expect(m.val[1]).toBeCloseTo(0);
expect(m.val[2]).toBeCloseTo(0);
expect(m.val[3]).toBeCloseTo(0);
expect(m.val[5]).toBeCloseTo(0);
expect(m.val[6]).toBeCloseTo(0);
expect(m.val[7]).toBeCloseTo(0);
});
it('should produce inverse-transpose of upper-left 3x3 for a uniform scale Matrix4', function ()
{
// Uniform scale by 2: normal matrix should be identity (scale cancels in normalize)
var mat4 = {
val: new Float32Array([
2, 0, 0, 0,
0, 2, 0, 0,
0, 0, 2, 0,
0, 0, 0, 1
])
};
var m = new Matrix3();
m.normalFromMat4(mat4);
// Normal matrix = transpose(inverse) of upper-left 3x3 = scale by 0.5
expect(m.val[0]).toBeCloseTo(0.5);
expect(m.val[4]).toBeCloseTo(0.5);
expect(m.val[8]).toBeCloseTo(0.5);
});
});
});