phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
741 lines (652 loc) • 24.7 kB
JavaScript
var Curve = require('../../src/curves/Curve');
var Vector2 = require('../../src/math/Vector2');
var Rectangle = require('../../src/geom/rectangle/Rectangle');
// A simple concrete curve: straight horizontal line from (0,0) to (100,0)
function createLineCurve ()
{
var curve = new Curve('LineCurve');
curve.getPoint = function (t, out)
{
if (out === undefined) { out = new Vector2(); }
out.x = t * 100;
out.y = 0;
return out;
};
return curve;
}
// A diagonal line from (0,0) to (100,100)
function createDiagonalCurve ()
{
var curve = new Curve('DiagonalCurve');
curve.getPoint = function (t, out)
{
if (out === undefined) { out = new Vector2(); }
out.x = t * 100;
out.y = t * 100;
return out;
};
return curve;
}
describe('Phaser.Curves.Curve', function ()
{
describe('constructor', function ()
{
it('should set the curve type', function ()
{
var curve = new Curve('TestCurve');
expect(curve.type).toBe('TestCurve');
});
it('should set defaultDivisions to 5', function ()
{
var curve = new Curve('TestCurve');
expect(curve.defaultDivisions).toBe(5);
});
it('should set arcLengthDivisions to 100', function ()
{
var curve = new Curve('TestCurve');
expect(curve.arcLengthDivisions).toBe(100);
});
it('should set needsUpdate to true', function ()
{
var curve = new Curve('TestCurve');
expect(curve.needsUpdate).toBe(true);
});
it('should set active to true', function ()
{
var curve = new Curve('TestCurve');
expect(curve.active).toBe(true);
});
it('should initialize cacheArcLengths as an empty array', function ()
{
var curve = new Curve('TestCurve');
expect(Array.isArray(curve.cacheArcLengths)).toBe(true);
expect(curve.cacheArcLengths.length).toBe(0);
});
});
describe('getLengths', function ()
{
it('should return an array with length divisions + 1', function ()
{
var curve = createLineCurve();
var lengths = curve.getLengths(10);
expect(Array.isArray(lengths)).toBe(true);
expect(lengths.length).toBe(11);
});
it('should always start at 0', function ()
{
var curve = createLineCurve();
var lengths = curve.getLengths(10);
expect(lengths[0]).toBe(0);
});
it('should end at the total arc length', function ()
{
var curve = createLineCurve();
var lengths = curve.getLengths(100);
expect(lengths[lengths.length - 1]).toBeCloseTo(100, 1);
});
it('should be monotonically non-decreasing', function ()
{
var curve = createLineCurve();
var lengths = curve.getLengths(20);
for (var i = 1; i < lengths.length; i++)
{
expect(lengths[i]).toBeGreaterThanOrEqual(lengths[i - 1]);
}
});
it('should cache the result and return the same array reference on subsequent calls', function ()
{
var curve = createLineCurve();
var lengths1 = curve.getLengths(10);
var lengths2 = curve.getLengths(10);
expect(lengths1).toBe(lengths2);
});
it('should set needsUpdate to false after calculating', function ()
{
var curve = createLineCurve();
curve.getLengths(10);
expect(curve.needsUpdate).toBe(false);
});
it('should recalculate when needsUpdate is true', function ()
{
var curve = createLineCurve();
var lengths1 = curve.getLengths(10);
curve.needsUpdate = true;
var lengths2 = curve.getLengths(10);
// After recalculation a new array is created
expect(lengths2).not.toBe(lengths1);
expect(lengths2[lengths2.length - 1]).toBeCloseTo(100, 1);
});
it('should use arcLengthDivisions when no divisions argument given', function ()
{
var curve = createLineCurve();
curve.arcLengthDivisions = 50;
var lengths = curve.getLengths();
expect(lengths.length).toBe(51);
});
it('should correctly compute lengths for a diagonal curve', function ()
{
var curve = createDiagonalCurve();
var lengths = curve.getLengths(100);
// Diagonal of a 100x100 square is ~141.42
expect(lengths[lengths.length - 1]).toBeCloseTo(141.42, 0);
});
});
describe('getLength', function ()
{
it('should return the total arc length of the curve', function ()
{
var curve = createLineCurve();
expect(curve.getLength()).toBeCloseTo(100, 1);
});
it('should return the correct length for a diagonal curve', function ()
{
var curve = createDiagonalCurve();
expect(curve.getLength()).toBeCloseTo(141.42, 0);
});
});
describe('getUtoTmapping', function ()
{
it('should return 0 for u=0', function ()
{
var curve = createLineCurve();
expect(curve.getUtoTmapping(0)).toBeCloseTo(0, 5);
});
it('should return 1 for u=1', function ()
{
var curve = createLineCurve();
expect(curve.getUtoTmapping(1)).toBeCloseTo(1, 5);
});
it('should return 0.5 for u=0.5 on a uniform curve', function ()
{
var curve = createLineCurve();
expect(curve.getUtoTmapping(0.5)).toBeCloseTo(0.5, 2);
});
it('should use the distance argument when provided instead of u', function ()
{
var curve = createLineCurve();
// distance=50 on a 100px line maps to t≈0.5
var t = curve.getUtoTmapping(0, 50);
expect(t).toBeCloseTo(0.5, 2);
});
it('should cap distance to the maximum arc length', function ()
{
var curve = createLineCurve();
var t = curve.getUtoTmapping(0, 9999);
expect(t).toBeCloseTo(1, 5);
});
it('should return 0 when distance is 0', function ()
{
var curve = createLineCurve();
var t = curve.getUtoTmapping(0, 0);
// distance=0 treated as falsy, uses u=0
expect(t).toBeCloseTo(0, 5);
});
});
describe('getTFromDistance', function ()
{
it('should return 0 for distance of 0', function ()
{
var curve = createLineCurve();
expect(curve.getTFromDistance(0)).toBe(0);
});
it('should return 0 for negative distance', function ()
{
var curve = createLineCurve();
expect(curve.getTFromDistance(-50)).toBe(0);
});
it('should return the correct t for a mid-curve distance', function ()
{
var curve = createLineCurve();
var t = curve.getTFromDistance(50);
expect(t).toBeCloseTo(0.5, 2);
});
it('should return close to 1 for the full curve distance', function ()
{
var curve = createLineCurve();
var t = curve.getTFromDistance(100);
expect(t).toBeCloseTo(1, 2);
});
it('should return a value between 0 and 1 for valid distances', function ()
{
var curve = createLineCurve();
var t = curve.getTFromDistance(25);
expect(t).toBeGreaterThanOrEqual(0);
expect(t).toBeLessThanOrEqual(1);
});
});
describe('getPointAt', function ()
{
it('should return a Vector2', function ()
{
var curve = createLineCurve();
var point = curve.getPointAt(0.5);
expect(typeof point.x).toBe('number');
expect(typeof point.y).toBe('number');
});
it('should return the start point for u=0', function ()
{
var curve = createLineCurve();
var point = curve.getPointAt(0);
expect(point.x).toBeCloseTo(0, 1);
expect(point.y).toBeCloseTo(0, 1);
});
it('should return the end point for u=1', function ()
{
var curve = createLineCurve();
var point = curve.getPointAt(1);
expect(point.x).toBeCloseTo(100, 1);
expect(point.y).toBeCloseTo(0, 1);
});
it('should return the midpoint for u=0.5 on a uniform curve', function ()
{
var curve = createLineCurve();
var point = curve.getPointAt(0.5);
expect(point.x).toBeCloseTo(50, 1);
expect(point.y).toBeCloseTo(0, 1);
});
it('should store the result in the provided out vector', function ()
{
var curve = createLineCurve();
var out = new Vector2();
var result = curve.getPointAt(0.5, out);
expect(result).toBe(out);
});
});
describe('getStartPoint', function ()
{
it('should return the start point of the curve', function ()
{
var curve = createLineCurve();
var point = curve.getStartPoint();
expect(point.x).toBeCloseTo(0, 1);
expect(point.y).toBeCloseTo(0, 1);
});
it('should create and return a new Vector2 when no out is provided', function ()
{
var curve = createLineCurve();
var point = curve.getStartPoint();
expect(point).toBeDefined();
expect(typeof point.x).toBe('number');
});
it('should store the result in the provided out vector', function ()
{
var curve = createLineCurve();
var out = new Vector2();
var result = curve.getStartPoint(out);
expect(result).toBe(out);
});
});
describe('getEndPoint', function ()
{
it('should return the end point of the curve', function ()
{
var curve = createLineCurve();
var point = curve.getEndPoint();
expect(point.x).toBeCloseTo(100, 1);
expect(point.y).toBeCloseTo(0, 1);
});
it('should create and return a new Vector2 when no out is provided', function ()
{
var curve = createLineCurve();
var point = curve.getEndPoint();
expect(point).toBeDefined();
expect(typeof point.x).toBe('number');
});
it('should store the result in the provided out vector', function ()
{
var curve = createLineCurve();
var out = new Vector2();
var result = curve.getEndPoint(out);
expect(result).toBe(out);
});
});
describe('getPoints', function ()
{
it('should return divisions + 1 points', function ()
{
var curve = createLineCurve();
var points = curve.getPoints(10);
expect(points.length).toBe(11);
});
it('should use defaultDivisions when called with no arguments', function ()
{
var curve = createLineCurve();
var points = curve.getPoints();
expect(points.length).toBe(curve.defaultDivisions + 1);
});
it('should use stepRate to calculate divisions when divisions is falsy', function ()
{
var curve = createLineCurve();
// length=100, stepRate=10 => divisions=10 => 11 points
var points = curve.getPoints(0, 10);
expect(points.length).toBe(11);
});
it('should append to an existing out array', function ()
{
var curve = createLineCurve();
var out = [];
var result = curve.getPoints(5, undefined, out);
expect(result).toBe(out);
expect(out.length).toBe(6);
});
it('should return points spanning the full curve', function ()
{
var curve = createLineCurve();
var points = curve.getPoints(4);
expect(points[0].x).toBeCloseTo(0, 5);
expect(points[2].x).toBeCloseTo(50, 5);
expect(points[4].x).toBeCloseTo(100, 5);
});
it('should return an array of Vector2-like objects', function ()
{
var curve = createLineCurve();
var points = curve.getPoints(5);
for (var i = 0; i < points.length; i++)
{
expect(typeof points[i].x).toBe('number');
expect(typeof points[i].y).toBe('number');
}
});
});
describe('getSpacedPoints', function ()
{
it('should return divisions + 1 points', function ()
{
var curve = createLineCurve();
var points = curve.getSpacedPoints(10);
expect(points.length).toBe(11);
});
it('should use defaultDivisions when called with no arguments', function ()
{
var curve = createLineCurve();
var points = curve.getSpacedPoints();
expect(points.length).toBe(curve.defaultDivisions + 1);
});
it('should use stepRate to calculate divisions when divisions is falsy', function ()
{
var curve = createLineCurve();
// length=100, stepRate=10 => divisions=10 => 11 points
var points = curve.getSpacedPoints(0, 10);
expect(points.length).toBe(11);
});
it('should append to an existing out array', function ()
{
var curve = createLineCurve();
var out = [];
var result = curve.getSpacedPoints(5, undefined, out);
expect(result).toBe(out);
expect(out.length).toBe(6);
});
it('should return evenly spaced points from start to end', function ()
{
var curve = createLineCurve();
var points = curve.getSpacedPoints(4);
expect(points[0].x).toBeCloseTo(0, 1);
expect(points[4].x).toBeCloseTo(100, 1);
});
it('should return an array of Vector2-like objects', function ()
{
var curve = createLineCurve();
var points = curve.getSpacedPoints(5);
for (var i = 0; i < points.length; i++)
{
expect(typeof points[i].x).toBe('number');
expect(typeof points[i].y).toBe('number');
}
});
});
describe('getRandomPoint', function ()
{
it('should return a point with numeric x and y', function ()
{
var curve = createLineCurve();
var point = curve.getRandomPoint();
expect(typeof point.x).toBe('number');
expect(typeof point.y).toBe('number');
});
it('should return a point lying on the curve', function ()
{
var curve = createLineCurve();
for (var i = 0; i < 20; i++)
{
var point = curve.getRandomPoint();
expect(point.x).toBeGreaterThanOrEqual(0);
expect(point.x).toBeLessThanOrEqual(100);
expect(point.y).toBeCloseTo(0, 5);
}
});
it('should create a new Vector2 when no out is provided', function ()
{
var curve = createLineCurve();
var point = curve.getRandomPoint();
expect(point).toBeDefined();
});
it('should store the result in the provided out vector', function ()
{
var curve = createLineCurve();
var out = new Vector2();
var result = curve.getRandomPoint(out);
expect(result).toBe(out);
});
});
describe('getTangent', function ()
{
it('should return a unit vector for a mid-curve horizontal line', function ()
{
var curve = createLineCurve();
var tangent = curve.getTangent(0.5);
expect(tangent.x).toBeCloseTo(1, 3);
expect(tangent.y).toBeCloseTo(0, 3);
});
it('should return a unit vector at t=0 (clamped start)', function ()
{
var curve = createLineCurve();
var tangent = curve.getTangent(0);
expect(tangent.x).toBeCloseTo(1, 3);
expect(tangent.y).toBeCloseTo(0, 3);
});
it('should return a unit vector at t=1 (clamped end)', function ()
{
var curve = createLineCurve();
var tangent = curve.getTangent(1);
expect(tangent.x).toBeCloseTo(1, 3);
expect(tangent.y).toBeCloseTo(0, 3);
});
it('should return a normalized vector with magnitude close to 1', function ()
{
var curve = createLineCurve();
var tangent = curve.getTangent(0.5);
var length = Math.sqrt(tangent.x * tangent.x + tangent.y * tangent.y);
expect(length).toBeCloseTo(1, 5);
});
it('should return the correct tangent direction for a diagonal curve', function ()
{
var curve = createDiagonalCurve();
var tangent = curve.getTangent(0.5);
// Diagonal direction normalized: (1/sqrt(2), 1/sqrt(2))
expect(tangent.x).toBeCloseTo(0.7071, 3);
expect(tangent.y).toBeCloseTo(0.7071, 3);
});
it('should store the result in the provided out vector', function ()
{
var curve = createLineCurve();
var out = new Vector2();
var result = curve.getTangent(0.5, out);
expect(result).toBe(out);
});
});
describe('getTangentAt', function ()
{
it('should return the tangent at a given arc-length position', function ()
{
var curve = createLineCurve();
var tangent = curve.getTangentAt(0.5);
expect(tangent.x).toBeCloseTo(1, 3);
expect(tangent.y).toBeCloseTo(0, 3);
});
it('should return a normalized vector', function ()
{
var curve = createLineCurve();
var tangent = curve.getTangentAt(0.5);
var length = Math.sqrt(tangent.x * tangent.x + tangent.y * tangent.y);
expect(length).toBeCloseTo(1, 5);
});
it('should store the result in the provided out vector', function ()
{
var curve = createLineCurve();
var out = new Vector2();
var result = curve.getTangentAt(0.5, out);
expect(result).toBe(out);
});
});
describe('getBounds', function ()
{
it('should return an object with x, y, width, and height', function ()
{
var curve = createLineCurve();
var bounds = curve.getBounds();
expect(typeof bounds.x).toBe('number');
expect(typeof bounds.y).toBe('number');
expect(typeof bounds.width).toBe('number');
expect(typeof bounds.height).toBe('number');
});
it('should store the result in the provided out Rectangle', function ()
{
var curve = createLineCurve();
var out = new Rectangle();
var result = curve.getBounds(out);
expect(result).toBe(out);
});
it('should create a new Rectangle when out is not provided', function ()
{
var curve = createLineCurve();
var bounds = curve.getBounds();
expect(bounds).toBeDefined();
});
it('should return bounds that encompass the full horizontal line', function ()
{
var curve = createLineCurve();
var bounds = curve.getBounds(undefined, 1);
expect(bounds.x).toBeCloseTo(0, 0);
expect(bounds.x + bounds.width).toBeCloseTo(100, 0);
});
it('should use accuracy=16 by default', function ()
{
var curve = createLineCurve();
// Just verifying it runs without error with the default accuracy
var bounds = curve.getBounds();
expect(bounds).toBeDefined();
});
});
describe('getDistancePoints', function ()
{
it('should return an array', function ()
{
var curve = createLineCurve();
var points = curve.getDistancePoints(10);
expect(Array.isArray(points)).toBe(true);
});
it('should return at least one point', function ()
{
var curve = createLineCurve();
var points = curve.getDistancePoints(10);
expect(points.length).toBeGreaterThan(0);
});
it('should return more points for a smaller distance', function ()
{
var curve = createLineCurve();
var points5 = curve.getDistancePoints(5);
var points25 = curve.getDistancePoints(25);
expect(points5.length).toBeGreaterThan(points25.length);
});
it('should return approximately length/distance points', function ()
{
var curve = createLineCurve();
// length=100, distance=10 => spaced=10 => getSpacedPoints(10) => 11 points
var points = curve.getDistancePoints(10);
expect(points.length).toBe(11);
});
});
describe('draw', function ()
{
it('should call strokePoints on the graphics object', function ()
{
var curve = createLineCurve();
var called = false;
var mockGraphics = {
strokePoints: function (points)
{
called = true;
return mockGraphics;
}
};
curve.draw(mockGraphics, 16);
expect(called).toBe(true);
});
it('should return the graphics object', function ()
{
var curve = createLineCurve();
var mockGraphics = {
strokePoints: function (points) { return mockGraphics; }
};
var result = curve.draw(mockGraphics, 16);
expect(result).toBe(mockGraphics);
});
it('should pass 32 + 1 points by default when pointsTotal is not specified', function ()
{
var curve = createLineCurve();
var capturedPoints;
var mockGraphics = {
strokePoints: function (points)
{
capturedPoints = points;
return mockGraphics;
}
};
curve.draw(mockGraphics);
expect(capturedPoints.length).toBe(33);
});
it('should pass the correct number of points for a custom pointsTotal', function ()
{
var curve = createLineCurve();
var capturedPoints;
var mockGraphics = {
strokePoints: function (points)
{
capturedPoints = points;
return mockGraphics;
}
};
curve.draw(mockGraphics, 10);
expect(capturedPoints.length).toBe(11);
});
});
describe('updateArcLengths', function ()
{
it('should trigger recalculation of arc lengths', function ()
{
var curve = createLineCurve();
curve.getLengths(10);
expect(curve.needsUpdate).toBe(false);
// Spy to confirm getLengths runs a fresh calculation
var oldCache = curve.cacheArcLengths;
curve.updateArcLengths();
// A new array should have been computed
expect(curve.cacheArcLengths).not.toBe(oldCache);
});
it('should result in a valid cacheArcLengths after update', function ()
{
var curve = createLineCurve();
curve.updateArcLengths();
expect(curve.cacheArcLengths.length).toBeGreaterThan(0);
expect(curve.cacheArcLengths[0]).toBe(0);
});
it('should not leave needsUpdate as true after completing', function ()
{
var curve = createLineCurve();
curve.updateArcLengths();
// updateArcLengths sets needsUpdate=true then calls getLengths which sets it false
expect(curve.needsUpdate).toBe(false);
});
});
});