phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
903 lines (777 loc) • 27.4 kB
JavaScript
var Transform = require('../../../src/gameobjects/components/Transform');
var TransformMatrix = require('../../../src/gameobjects/components/TransformMatrix');
var MATH_CONST = require('../../../src/math/const');
var _FLAG = 4;
function createGameObject ()
{
var obj = {
x: 0,
y: 0,
z: 0,
w: 0,
_scaleX: 1,
_scaleY: 1,
_rotation: 0,
renderFlags: 15,
parentContainer: null,
scrollFactorX: 1,
scrollFactorY: 1,
_originComponent: false,
_displayOriginX: 0,
_displayOriginY: 0
};
obj.setPosition = Transform.setPosition;
obj.copyPosition = Transform.copyPosition;
obj.setRandomPosition = Transform.setRandomPosition;
obj.setRotation = Transform.setRotation;
obj.setAngle = Transform.setAngle;
obj.setScale = Transform.setScale;
obj.setX = Transform.setX;
obj.setY = Transform.setY;
obj.setZ = Transform.setZ;
obj.setW = Transform.setW;
obj.getLocalTransformMatrix = Transform.getLocalTransformMatrix;
obj.getWorldTransformMatrix = Transform.getWorldTransformMatrix;
obj.getLocalPoint = Transform.getLocalPoint;
obj.getWorldPoint = Transform.getWorldPoint;
obj.getParentRotation = Transform.getParentRotation;
Object.defineProperties(obj, {
scale: Transform.scale,
scaleX: Transform.scaleX,
scaleY: Transform.scaleY,
angle: Transform.angle,
rotation: Transform.rotation
});
return obj;
}
describe('Transform', function ()
{
var go;
beforeEach(function ()
{
go = createGameObject();
});
// -------------------------------------------------------------------------
// Module shape
// -------------------------------------------------------------------------
describe('module', function ()
{
it('should export an object', function ()
{
expect(typeof Transform).toBe('object');
});
it('should have hasTransformComponent set to true', function ()
{
expect(Transform.hasTransformComponent).toBe(true);
});
it('should have default _scaleX of 1', function ()
{
expect(Transform._scaleX).toBe(1);
});
it('should have default _scaleY of 1', function ()
{
expect(Transform._scaleY).toBe(1);
});
it('should have default _rotation of 0', function ()
{
expect(Transform._rotation).toBe(0);
});
it('should have default x, y, z, w of 0', function ()
{
expect(Transform.x).toBe(0);
expect(Transform.y).toBe(0);
expect(Transform.z).toBe(0);
expect(Transform.w).toBe(0);
});
});
// -------------------------------------------------------------------------
// scale getter / setter
// -------------------------------------------------------------------------
describe('scale', function ()
{
it('should return the average of scaleX and scaleY', function ()
{
go._scaleX = 2;
go._scaleY = 4;
expect(go.scale).toBe(3);
});
it('should return 1 when both scales are 1', function ()
{
expect(go.scale).toBe(1);
});
it('should set both _scaleX and _scaleY', function ()
{
go.scale = 3;
expect(go._scaleX).toBe(3);
expect(go._scaleY).toBe(3);
});
it('should clear _FLAG when set to zero', function ()
{
go.renderFlags = 15;
go.scale = 0;
expect(go.renderFlags & _FLAG).toBe(0);
});
it('should set _FLAG when set to a non-zero value', function ()
{
go.renderFlags = 0;
go.scale = 2;
expect(go.renderFlags & _FLAG).toBe(_FLAG);
});
it('should set _FLAG when set back to 1 after being zeroed', function ()
{
go.scale = 0;
go.scale = 1;
expect(go.renderFlags & _FLAG).toBe(_FLAG);
});
});
// -------------------------------------------------------------------------
// scaleX getter / setter
// -------------------------------------------------------------------------
describe('scaleX', function ()
{
it('should get _scaleX', function ()
{
go._scaleX = 5;
expect(go.scaleX).toBe(5);
});
it('should set _scaleX', function ()
{
go.scaleX = 2;
expect(go._scaleX).toBe(2);
});
it('should clear _FLAG when set to zero', function ()
{
go.renderFlags = 15;
go.scaleX = 0;
expect(go.renderFlags & _FLAG).toBe(0);
});
it('should set _FLAG when non-zero and scaleY is non-zero', function ()
{
go.renderFlags = 0;
go._scaleY = 1;
go.scaleX = 2;
expect(go.renderFlags & _FLAG).toBe(_FLAG);
});
it('should not set _FLAG when non-zero but scaleY is zero', function ()
{
go.renderFlags = 0;
go._scaleY = 0;
go.scaleX = 2;
expect(go.renderFlags & _FLAG).toBe(0);
});
});
// -------------------------------------------------------------------------
// scaleY getter / setter
// -------------------------------------------------------------------------
describe('scaleY', function ()
{
it('should get _scaleY', function ()
{
go._scaleY = 7;
expect(go.scaleY).toBe(7);
});
it('should set _scaleY', function ()
{
go.scaleY = 3;
expect(go._scaleY).toBe(3);
});
it('should clear _FLAG when set to zero', function ()
{
go.renderFlags = 15;
go.scaleY = 0;
expect(go.renderFlags & _FLAG).toBe(0);
});
it('should set _FLAG when non-zero and scaleX is non-zero', function ()
{
go.renderFlags = 0;
go._scaleX = 1;
go.scaleY = 2;
expect(go.renderFlags & _FLAG).toBe(_FLAG);
});
it('should not set _FLAG when non-zero but scaleX is zero', function ()
{
go.renderFlags = 0;
go._scaleX = 0;
go.scaleY = 2;
expect(go.renderFlags & _FLAG).toBe(0);
});
});
// -------------------------------------------------------------------------
// angle getter / setter
// -------------------------------------------------------------------------
describe('angle', function ()
{
it('should return 0 when rotation is 0', function ()
{
go._rotation = 0;
expect(go.angle).toBe(0);
});
it('should return degrees corresponding to the rotation in radians', function ()
{
go._rotation = Math.PI / 2;
expect(go.angle).toBeCloseTo(90, 5);
});
it('should return -180 or 180 for PI rotation (wrapped)', function ()
{
go._rotation = Math.PI;
var a = go.angle;
expect(Math.abs(a)).toBeCloseTo(180, 5);
});
it('should set rotation from degrees', function ()
{
go.angle = 90;
expect(go._rotation).toBeCloseTo(Math.PI / 2, 5);
});
it('should wrap angle values above 180 into the -180..180 range', function ()
{
go.angle = 270;
expect(go.angle).toBeCloseTo(-90, 5);
});
it('should wrap angle values below -180 into the -180..180 range', function ()
{
go.angle = -270;
expect(go.angle).toBeCloseTo(90, 5);
});
it('should handle 0 degrees', function ()
{
go.angle = 0;
expect(go.angle).toBe(0);
});
});
// -------------------------------------------------------------------------
// rotation getter / setter
// -------------------------------------------------------------------------
describe('rotation', function ()
{
it('should return 0 by default', function ()
{
expect(go.rotation).toBe(0);
});
it('should set _rotation via WrapAngle', function ()
{
go.rotation = Math.PI / 4;
expect(go._rotation).toBeCloseTo(Math.PI / 4, 10);
});
it('should wrap values greater than PI', function ()
{
go.rotation = Math.PI * 3;
// 3*PI wraps to PI (or -PI boundary)
expect(Math.abs(go._rotation)).toBeLessThanOrEqual(Math.PI + 0.0001);
});
it('should wrap values less than -PI', function ()
{
go.rotation = -Math.PI * 3;
expect(Math.abs(go._rotation)).toBeLessThanOrEqual(Math.PI + 0.0001);
});
it('should return the stored _rotation', function ()
{
go._rotation = 1.23;
expect(go.rotation).toBe(1.23);
});
});
// -------------------------------------------------------------------------
// setPosition
// -------------------------------------------------------------------------
describe('setPosition', function ()
{
it('should set all coordinates to 0 when called with no arguments', function ()
{
go.x = 10;
go.y = 20;
go.z = 30;
go.w = 40;
go.setPosition();
expect(go.x).toBe(0);
expect(go.y).toBe(0);
expect(go.z).toBe(0);
expect(go.w).toBe(0);
});
it('should set x and use it for y when only x is provided', function ()
{
go.setPosition(5);
expect(go.x).toBe(5);
expect(go.y).toBe(5);
expect(go.z).toBe(0);
expect(go.w).toBe(0);
});
it('should set x and y independently when both are provided', function ()
{
go.setPosition(3, 7);
expect(go.x).toBe(3);
expect(go.y).toBe(7);
});
it('should set all four coordinates when provided', function ()
{
go.setPosition(1, 2, 3, 4);
expect(go.x).toBe(1);
expect(go.y).toBe(2);
expect(go.z).toBe(3);
expect(go.w).toBe(4);
});
it('should return the game object (chainable)', function ()
{
var result = go.setPosition(1, 2);
expect(result).toBe(go);
});
it('should work with negative values', function ()
{
go.setPosition(-10, -20, -30, -40);
expect(go.x).toBe(-10);
expect(go.y).toBe(-20);
expect(go.z).toBe(-30);
expect(go.w).toBe(-40);
});
it('should work with floating point values', function ()
{
go.setPosition(1.5, 2.7, 0.1, -3.3);
expect(go.x).toBeCloseTo(1.5);
expect(go.y).toBeCloseTo(2.7);
expect(go.z).toBeCloseTo(0.1);
expect(go.w).toBeCloseTo(-3.3);
});
});
// -------------------------------------------------------------------------
// copyPosition
// -------------------------------------------------------------------------
describe('copyPosition', function ()
{
it('should copy x and y from a Vector2-like source', function ()
{
go.copyPosition({ x: 10, y: 20 });
expect(go.x).toBe(10);
expect(go.y).toBe(20);
});
it('should copy x, y, z from a Vector3-like source', function ()
{
go.copyPosition({ x: 1, y: 2, z: 3 });
expect(go.x).toBe(1);
expect(go.y).toBe(2);
expect(go.z).toBe(3);
});
it('should copy all four components from a Vector4-like source', function ()
{
go.copyPosition({ x: 1, y: 2, z: 3, w: 4 });
expect(go.x).toBe(1);
expect(go.y).toBe(2);
expect(go.z).toBe(3);
expect(go.w).toBe(4);
});
it('should not modify coordinates absent from source', function ()
{
go.x = 99;
go.y = 88;
go.z = 77;
go.w = 66;
go.copyPosition({ x: 10 });
expect(go.x).toBe(10);
expect(go.y).toBe(88);
expect(go.z).toBe(77);
expect(go.w).toBe(66);
});
it('should not modify any coordinate when source has no matching properties', function ()
{
go.x = 5;
go.y = 6;
go.copyPosition({});
expect(go.x).toBe(5);
expect(go.y).toBe(6);
});
it('should return the game object (chainable)', function ()
{
var result = go.copyPosition({ x: 1 });
expect(result).toBe(go);
});
});
// -------------------------------------------------------------------------
// setRandomPosition
// -------------------------------------------------------------------------
describe('setRandomPosition', function ()
{
it('should place the object within the given area', function ()
{
var mockScene = {
sys: {
scale: { width: 800, height: 600 }
}
};
go.scene = mockScene;
for (var i = 0; i < 20; i++)
{
go.setRandomPosition(100, 200, 300, 400);
expect(go.x).toBeGreaterThanOrEqual(100);
expect(go.x).toBeLessThan(400);
expect(go.y).toBeGreaterThanOrEqual(200);
expect(go.y).toBeLessThan(600);
}
});
it('should fall back to scene dimensions when width/height are omitted', function ()
{
var mockScene = {
sys: {
scale: { width: 800, height: 600 }
}
};
go.scene = mockScene;
for (var i = 0; i < 20; i++)
{
go.setRandomPosition();
expect(go.x).toBeGreaterThanOrEqual(0);
expect(go.x).toBeLessThan(800);
expect(go.y).toBeGreaterThanOrEqual(0);
expect(go.y).toBeLessThan(600);
}
});
it('should return the game object (chainable)', function ()
{
go.scene = { sys: { scale: { width: 800, height: 600 } } };
var result = go.setRandomPosition(0, 0, 100, 100);
expect(result).toBe(go);
});
});
// -------------------------------------------------------------------------
// setRotation
// -------------------------------------------------------------------------
describe('setRotation', function ()
{
it('should default rotation to 0 when called with no arguments', function ()
{
go.rotation = 1.5;
go.setRotation();
expect(go._rotation).toBe(0);
});
it('should set rotation to given radians', function ()
{
go.setRotation(Math.PI / 4);
expect(go._rotation).toBeCloseTo(Math.PI / 4, 10);
});
it('should wrap rotation values (via rotation setter)', function ()
{
go.setRotation(Math.PI * 3);
expect(Math.abs(go._rotation)).toBeLessThanOrEqual(Math.PI + 0.0001);
});
it('should return the game object (chainable)', function ()
{
var result = go.setRotation(1);
expect(result).toBe(go);
});
});
// -------------------------------------------------------------------------
// setAngle
// -------------------------------------------------------------------------
describe('setAngle', function ()
{
it('should default angle to 0 when called with no arguments', function ()
{
go.setAngle(90);
go.setAngle();
expect(go._rotation).toBe(0);
});
it('should set the angle in degrees', function ()
{
go.setAngle(90);
expect(go._rotation).toBeCloseTo(Math.PI / 2, 5);
});
it('should wrap degrees outside -180..180', function ()
{
go.setAngle(450);
expect(go.angle).toBeCloseTo(90, 5);
});
it('should return the game object (chainable)', function ()
{
var result = go.setAngle(45);
expect(result).toBe(go);
});
});
// -------------------------------------------------------------------------
// setScale
// -------------------------------------------------------------------------
describe('setScale', function ()
{
it('should default to 1 when called with no arguments', function ()
{
go.setScale();
expect(go._scaleX).toBe(1);
expect(go._scaleY).toBe(1);
});
it('should set both scales when only x is provided', function ()
{
go.setScale(3);
expect(go._scaleX).toBe(3);
expect(go._scaleY).toBe(3);
});
it('should set x and y scales independently when both are provided', function ()
{
go.setScale(2, 5);
expect(go._scaleX).toBe(2);
expect(go._scaleY).toBe(5);
});
it('should return the game object (chainable)', function ()
{
var result = go.setScale(1, 2);
expect(result).toBe(go);
});
it('should clear _FLAG when scale is set to zero', function ()
{
go.renderFlags = 15;
go.setScale(0, 0);
expect(go.renderFlags & _FLAG).toBe(0);
});
});
// -------------------------------------------------------------------------
// setX / setY / setZ / setW
// -------------------------------------------------------------------------
describe('setX', function ()
{
it('should default to 0 when called with no argument', function ()
{
go.x = 99;
go.setX();
expect(go.x).toBe(0);
});
it('should set x to the given value', function ()
{
go.setX(42);
expect(go.x).toBe(42);
});
it('should return the game object (chainable)', function ()
{
expect(go.setX(1)).toBe(go);
});
});
describe('setY', function ()
{
it('should default to 0 when called with no argument', function ()
{
go.y = 99;
go.setY();
expect(go.y).toBe(0);
});
it('should set y to the given value', function ()
{
go.setY(-7);
expect(go.y).toBe(-7);
});
it('should return the game object (chainable)', function ()
{
expect(go.setY(1)).toBe(go);
});
});
describe('setZ', function ()
{
it('should default to 0 when called with no argument', function ()
{
go.z = 99;
go.setZ();
expect(go.z).toBe(0);
});
it('should set z to the given value', function ()
{
go.setZ(100);
expect(go.z).toBe(100);
});
it('should return the game object (chainable)', function ()
{
expect(go.setZ(1)).toBe(go);
});
});
describe('setW', function ()
{
it('should default to 0 when called with no argument', function ()
{
go.w = 99;
go.setW();
expect(go.w).toBe(0);
});
it('should set w to the given value', function ()
{
go.setW(55);
expect(go.w).toBe(55);
});
it('should return the game object (chainable)', function ()
{
expect(go.setW(1)).toBe(go);
});
});
// -------------------------------------------------------------------------
// getLocalTransformMatrix
// -------------------------------------------------------------------------
describe('getLocalTransformMatrix', function ()
{
it('should return a TransformMatrix', function ()
{
var matrix = go.getLocalTransformMatrix();
expect(matrix).toBeInstanceOf(TransformMatrix);
});
it('should accept an existing TransformMatrix and populate it', function ()
{
var tm = new TransformMatrix();
var result = go.getLocalTransformMatrix(tm);
expect(result).toBe(tm);
});
it('should encode the current position and scale', function ()
{
go.setPosition(10, 20);
go.setScale(2, 3);
go.setRotation(0);
var matrix = go.getLocalTransformMatrix();
expect(matrix.tx).toBeCloseTo(10, 5);
expect(matrix.ty).toBeCloseTo(20, 5);
});
it('should create a new matrix when none is provided', function ()
{
var m1 = go.getLocalTransformMatrix();
var m2 = go.getLocalTransformMatrix();
expect(m1).not.toBe(m2);
});
});
// -------------------------------------------------------------------------
// getWorldTransformMatrix
// -------------------------------------------------------------------------
describe('getWorldTransformMatrix', function ()
{
it('should return the local matrix when there is no parent container', function ()
{
go.x = 5;
go.y = 10;
go.parentContainer = null;
var matrix = go.getWorldTransformMatrix();
expect(matrix.tx).toBeCloseTo(5, 5);
expect(matrix.ty).toBeCloseTo(10, 5);
});
it('should accept an existing tempMatrix and return it', function ()
{
var tm = new TransformMatrix();
var result = go.getWorldTransformMatrix(tm);
expect(result).toBe(tm);
});
it('should factor in a single parent container', function ()
{
var parent = createGameObject();
parent.x = 100;
parent.y = 200;
parent._rotation = 0;
parent._scaleX = 1;
parent._scaleY = 1;
parent.parentContainer = null;
go.x = 10;
go.y = 20;
go._rotation = 0;
go._scaleX = 1;
go._scaleY = 1;
go.parentContainer = parent;
var matrix = go.getWorldTransformMatrix();
// At zero rotation and scale=1, world position is child + parent
expect(matrix.tx).toBeCloseTo(110, 4);
expect(matrix.ty).toBeCloseTo(220, 4);
});
it('should handle a chain of two parent containers', function ()
{
var grandparent = createGameObject();
grandparent.x = 50;
grandparent.y = 50;
grandparent._rotation = 0;
grandparent._scaleX = 1;
grandparent._scaleY = 1;
grandparent.parentContainer = null;
var parent = createGameObject();
parent.x = 50;
parent.y = 50;
parent._rotation = 0;
parent._scaleX = 1;
parent._scaleY = 1;
parent.parentContainer = grandparent;
go.x = 0;
go.y = 0;
go._rotation = 0;
go._scaleX = 1;
go._scaleY = 1;
go.parentContainer = parent;
var matrix = go.getWorldTransformMatrix();
expect(matrix.tx).toBeCloseTo(100, 4);
expect(matrix.ty).toBeCloseTo(100, 4);
});
});
// -------------------------------------------------------------------------
// getWorldPoint
// -------------------------------------------------------------------------
describe('getWorldPoint', function ()
{
it('should return x and y directly when no parent container', function ()
{
go.x = 123;
go.y = 456;
go.parentContainer = null;
var point = go.getWorldPoint();
expect(point.x).toBe(123);
expect(point.y).toBe(456);
});
it('should accept a point object and populate it', function ()
{
var point = { x: 0, y: 0 };
go.x = 10;
go.y = 20;
go.parentContainer = null;
var result = go.getWorldPoint(point);
expect(result).toBe(point);
expect(point.x).toBe(10);
expect(point.y).toBe(20);
});
it('should use world transform matrix when parent container exists', function ()
{
var parent = createGameObject();
parent.x = 100;
parent.y = 100;
parent._rotation = 0;
parent._scaleX = 1;
parent._scaleY = 1;
parent.parentContainer = null;
go.x = 50;
go.y = 50;
go._rotation = 0;
go._scaleX = 1;
go._scaleY = 1;
go.parentContainer = parent;
var point = go.getWorldPoint();
expect(point.x).toBeCloseTo(150, 4);
expect(point.y).toBeCloseTo(150, 4);
});
});
// -------------------------------------------------------------------------
// getParentRotation
// -------------------------------------------------------------------------
describe('getParentRotation', function ()
{
it('should return 0 when there is no parent container', function ()
{
go.parentContainer = null;
expect(go.getParentRotation()).toBe(0);
});
it('should return the parent rotation when there is one parent', function ()
{
var parent = createGameObject();
parent.rotation = Math.PI / 4;
parent.parentContainer = null;
go.parentContainer = parent;
expect(go.getParentRotation()).toBeCloseTo(Math.PI / 4, 10);
});
it('should sum rotations from a chain of parents', function ()
{
var grandparent = createGameObject();
grandparent.rotation = Math.PI / 4;
grandparent.parentContainer = null;
var parent = createGameObject();
parent.rotation = Math.PI / 4;
parent.parentContainer = grandparent;
go.parentContainer = parent;
expect(go.getParentRotation()).toBeCloseTo(Math.PI / 2, 10);
});
it('should handle negative parent rotations', function ()
{
var parent = createGameObject();
parent.rotation = -Math.PI / 3;
parent.parentContainer = null;
go.parentContainer = parent;
expect(go.getParentRotation()).toBeCloseTo(-Math.PI / 3, 10);
});
});
});