phaser/src/curves/Curve.js

591 lines
16 KiB
JavaScript
Raw Normal View History

2018-02-12 16:01:20 +00:00
/**
* @author Richard Davey <rich@photonstorm.com>
2019-01-15 16:20:22 +00:00
* @copyright 2019 Photon Storm Ltd.
2019-05-10 15:15:04 +00:00
* @license {@link https://opensource.org/licenses/MIT|MIT License}
2018-02-12 16:01:20 +00:00
*/
2018-02-13 00:40:51 +00:00
var Class = require('../utils/Class');
var FromPoints = require('../geom/rectangle/FromPoints');
var Rectangle = require('../geom/rectangle/Rectangle');
var Vector2 = require('../math/Vector2');
2018-02-07 15:27:21 +00:00
/**
* @classdesc
* A Base Curve class, which all other curve types extend.
*
* Based on the three.js Curve classes created by [zz85](http://www.lab4games.net/zz85/blog)
*
* @class Curve
2018-10-10 09:49:13 +00:00
* @memberof Phaser.Curves
2018-02-07 15:27:21 +00:00
* @constructor
* @since 3.0.0
*
* @param {string} type - [description]
*/
var Curve = new Class({
initialize:
function Curve (type)
{
2017-10-13 16:08:19 +00:00
/**
2018-01-24 17:12:07 +00:00
* String based identifier for the type of curve.
2017-10-13 16:08:19 +00:00
*
2018-02-13 00:40:51 +00:00
* @name Phaser.Curves.Curve#type
* @type {string}
2018-01-24 17:12:07 +00:00
* @since 3.0.0
2017-10-13 16:08:19 +00:00
*/
this.type = type;
2017-10-13 16:08:19 +00:00
/**
2018-01-24 17:12:07 +00:00
* The default number of divisions within the curve.
2017-10-13 16:08:19 +00:00
*
2018-02-13 00:40:51 +00:00
* @name Phaser.Curves.Curve#defaultDivisions
* @type {integer}
2017-10-13 16:08:19 +00:00
* @default 5
2018-01-25 05:26:13 +00:00
* @since 3.0.0
2017-10-13 16:08:19 +00:00
*/
this.defaultDivisions = 5;
2017-10-13 16:08:19 +00:00
/**
2018-01-24 17:12:07 +00:00
* The quantity of arc length divisions within the curve.
2017-10-13 16:08:19 +00:00
*
2018-02-13 00:40:51 +00:00
* @name Phaser.Curves.Curve#arcLengthDivisions
* @type {integer}
2017-10-13 16:08:19 +00:00
* @default 100
2018-01-25 05:26:13 +00:00
* @since 3.0.0
2017-10-13 16:08:19 +00:00
*/
this.arcLengthDivisions = 100;
2017-10-13 16:08:19 +00:00
/**
2018-01-24 17:12:07 +00:00
* An array of cached arc length values.
2017-10-13 16:08:19 +00:00
*
2018-02-13 00:40:51 +00:00
* @name Phaser.Curves.Curve#cacheArcLengths
2018-03-19 15:53:55 +00:00
* @type {number[]}
2017-10-13 16:08:19 +00:00
* @default []
2018-01-25 05:26:13 +00:00
* @since 3.0.0
2017-10-13 16:08:19 +00:00
*/
this.cacheArcLengths = [];
2017-10-13 16:08:19 +00:00
/**
2018-01-24 17:12:07 +00:00
* Does the data of this curve need updating?
2017-10-13 16:08:19 +00:00
*
2018-02-13 00:40:51 +00:00
* @name Phaser.Curves.Curve#needsUpdate
* @type {boolean}
2017-10-13 16:08:19 +00:00
* @default true
2018-01-25 05:26:13 +00:00
* @since 3.0.0
2017-10-13 16:08:19 +00:00
*/
this.needsUpdate = true;
2017-10-13 16:08:19 +00:00
/**
* [description]
*
2018-02-13 00:40:51 +00:00
* @name Phaser.Curves.Curve#active
* @type {boolean}
2017-10-13 16:08:19 +00:00
* @default true
2018-01-25 05:26:13 +00:00
* @since 3.0.0
2017-10-13 16:08:19 +00:00
*/
this.active = true;
2017-10-13 16:08:19 +00:00
/**
2018-01-24 17:12:07 +00:00
* A temporary calculation Vector.
2017-10-13 16:08:19 +00:00
*
2018-02-13 00:40:51 +00:00
* @name Phaser.Curves.Curve#_tmpVec2A
* @type {Phaser.Math.Vector2}
2017-10-13 16:08:19 +00:00
* @private
2018-01-25 05:26:13 +00:00
* @since 3.0.0
2017-10-13 16:08:19 +00:00
*/
this._tmpVec2A = new Vector2();
2017-10-13 16:08:19 +00:00
/**
2018-01-24 17:12:07 +00:00
* A temporary calculation Vector.
2017-10-13 16:08:19 +00:00
*
2018-02-13 00:40:51 +00:00
* @name Phaser.Curves.Curve#_tmpVec2B
* @type {Phaser.Math.Vector2}
2017-10-13 16:08:19 +00:00
* @private
2018-01-25 05:26:13 +00:00
* @since 3.0.0
2017-10-13 16:08:19 +00:00
*/
this._tmpVec2B = new Vector2();
},
2018-01-16 15:20:54 +00:00
/**
2018-01-24 17:12:07 +00:00
* Draws this curve on the given Graphics object.
2018-03-19 15:53:55 +00:00
*
2018-01-24 17:12:07 +00:00
* The curve is drawn using `Graphics.strokePoints` so will be drawn at whatever the present Graphics stroke color is.
* The Graphics object is not cleared before the draw, so the curve will appear on-top of anything else already rendered to it.
2018-01-16 15:20:54 +00:00
*
* @method Phaser.Curves.Curve#draw
* @since 3.0.0
*
2018-04-18 12:39:55 +00:00
* @generic {Phaser.GameObjects.Graphics} G - [graphics,$return]
2018-03-27 12:06:24 +00:00
*
2018-01-24 17:12:07 +00:00
* @param {Phaser.GameObjects.Graphics} graphics - The Graphics instance onto which this curve will be drawn.
* @param {integer} [pointsTotal=32] - The resolution of the curve. The higher the value the smoother it will render, at the cost of rendering performance.
2018-01-16 15:20:54 +00:00
*
2018-01-24 17:12:07 +00:00
* @return {Phaser.GameObjects.Graphics} The Graphics object to which the curve was drawn.
2018-01-16 15:20:54 +00:00
*/
draw: function (graphics, pointsTotal)
{
if (pointsTotal === undefined) { pointsTotal = 32; }
// So you can chain graphics calls
return graphics.strokePoints(this.getPoints(pointsTotal));
},
2018-09-07 16:19:19 +00:00
2018-01-16 15:20:54 +00:00
/**
2018-01-24 17:12:07 +00:00
* Returns a Rectangle where the position and dimensions match the bounds of this Curve.
*
* You can control the accuracy of the bounds. The value given is used to work out how many points
* to plot across the curve. Higher values are more accurate at the cost of calculation speed.
2018-01-16 15:20:54 +00:00
*
* @method Phaser.Curves.Curve#getBounds
* @since 3.0.0
*
2018-03-18 13:43:37 +00:00
* @param {Phaser.Geom.Rectangle} [out] - The Rectangle to store the bounds in. If falsey a new object will be created.
2018-01-24 17:12:07 +00:00
* @param {integer} [accuracy=16] - The accuracy of the bounds calculations.
2018-01-16 15:20:54 +00:00
*
2018-03-18 13:43:37 +00:00
* @return {Phaser.Geom.Rectangle} A Rectangle object holding the bounds of this curve. If `out` was given it will be this object.
2018-01-16 15:20:54 +00:00
*/
getBounds: function (out, accuracy)
{
2018-01-24 17:12:07 +00:00
if (!out) { out = new Rectangle(); }
2018-01-16 15:20:54 +00:00
if (accuracy === undefined) { accuracy = 16; }
var len = this.getLength();
if (accuracy > len)
{
accuracy = len / 2;
}
// The length of the curve in pixels
// So we'll have 1 spaced point per 'accuracy' pixels
var spaced = Math.max(1, Math.round(len / accuracy));
return FromPoints(this.getSpacedPoints(spaced), out);
},
/**
2018-01-24 17:12:07 +00:00
* Returns an array of points, spaced out X distance pixels apart.
* The smaller the distance, the larger the array will be.
2018-01-16 15:20:54 +00:00
*
* @method Phaser.Curves.Curve#getDistancePoints
* @since 3.0.0
*
2018-01-24 17:12:07 +00:00
* @param {integer} distance - The distance, in pixels, between each point along the curve.
2018-01-16 15:20:54 +00:00
*
2018-01-24 17:12:07 +00:00
* @return {Phaser.Geom.Point[]} An Array of Point objects.
2018-01-16 15:20:54 +00:00
*/
getDistancePoints: function (distance)
{
var len = this.getLength();
var spaced = Math.max(1, len / distance);
return this.getSpacedPoints(spaced);
},
/**
* [description]
*
* @method Phaser.Curves.Curve#getEndPoint
* @since 3.0.0
*
2018-09-27 14:29:32 +00:00
* @param {Phaser.Math.Vector2} [out] - Optional Vector object to store the result in.
2018-01-16 15:20:54 +00:00
*
2018-09-27 14:29:32 +00:00
* @return {Phaser.Math.Vector2} Vector2 containing the coordinates of the curves end point.
2018-01-16 15:20:54 +00:00
*/
getEndPoint: function (out)
{
if (out === undefined) { out = new Vector2(); }
return this.getPointAt(1, out);
},
/**
2019-06-17 20:03:22 +00:00
* Get total curve arc length
2018-01-16 15:20:54 +00:00
*
* @method Phaser.Curves.Curve#getLength
* @since 3.0.0
*
* @return {number} [description]
*/
getLength: function ()
{
var lengths = this.getLengths();
return lengths[lengths.length - 1];
},
/**
2019-06-17 20:03:22 +00:00
* Get list of cumulative segment lengths
2018-01-16 15:20:54 +00:00
*
* @method Phaser.Curves.Curve#getLengths
* @since 3.0.0
*
* @param {integer} [divisions] - [description]
*
* @return {number[]} [description]
*/
getLengths: function (divisions)
{
if (divisions === undefined) { divisions = this.arcLengthDivisions; }
if ((this.cacheArcLengths.length === divisions + 1) && !this.needsUpdate)
{
return this.cacheArcLengths;
}
this.needsUpdate = false;
var cache = [];
var current;
var last = this.getPoint(0, this._tmpVec2A);
var sum = 0;
cache.push(0);
for (var p = 1; p <= divisions; p++)
{
current = this.getPoint(p / divisions, this._tmpVec2B);
sum += current.distance(last);
cache.push(sum);
last.copy(current);
}
this.cacheArcLengths = cache;
return cache; // { sums: cache, sum:sum }; Sum is in the last element.
},
// Get point at relative position in curve according to arc length
// - u [0 .. 1]
/**
* [description]
*
* @method Phaser.Curves.Curve#getPointAt
* @since 3.0.0
*
2018-03-27 12:06:24 +00:00
* @generic {Phaser.Math.Vector2} O - [out,$return]
*
* @param {number} u - [description]
2018-01-16 15:20:54 +00:00
* @param {Phaser.Math.Vector2} [out] - [description]
*
* @return {Phaser.Math.Vector2} [description]
*/
getPointAt: function (u, out)
{
var t = this.getUtoTmapping(u);
return this.getPoint(t, out);
},
// Get sequence of points using getPoint( t )
/**
* [description]
*
* @method Phaser.Curves.Curve#getPoints
* @since 3.0.0
*
* @param {integer} divisions - The number of evenly spaced points from the curve to return. If falsy, step param will be used to calculate the number of points.
2019-11-18 03:21:31 +00:00
* @param {number} step - Step between points. Used to calculate the number of points to return when divisions is falsy. Ignored if divisions is positive.
2019-11-18 03:17:15 +00:00
* @param {(array|Phaser.Math.Vector2[])} [out] - An optional array to store the points in.
2018-01-16 15:20:54 +00:00
*
2019-11-18 03:21:31 +00:00
* @return {(array|Phaser.Math.Vector2[])} An array of Points from the curve.
2018-01-16 15:20:54 +00:00
*/
getPoints: function (divisions, stepRate, out)
2018-01-16 15:20:54 +00:00
{
if (out === undefined) { out = []; }
2018-01-16 15:20:54 +00:00
// If divisions is a falsey value (false, null, 0, undefined, etc) then we calculate it based on the stepRate instead.
if (!divisions)
{
2019-11-18 16:31:20 +00:00
if (!stepRate)
{
divisions = this.defaultDivisions;
}
else
{
divisions = this.getLength() / stepRate;
}
}
2018-01-16 15:20:54 +00:00
for (var d = 0; d <= divisions; d++)
{
out.push(this.getPoint(d / divisions));
2018-01-16 15:20:54 +00:00
}
return out;
2018-01-16 15:20:54 +00:00
},
/**
* [description]
*
* @method Phaser.Curves.Curve#getRandomPoint
* @since 3.0.0
*
2018-03-27 12:06:24 +00:00
* @generic {Phaser.Math.Vector2} O - [out,$return]
*
2018-01-16 15:20:54 +00:00
* @param {Phaser.Math.Vector2} [out] - [description]
*
* @return {Phaser.Math.Vector2} [description]
*/
getRandomPoint: function (out)
{
if (out === undefined) { out = new Vector2(); }
return this.getPoint(Math.random(), out);
},
// Get sequence of points using getPointAt( u )
/**
* [description]
*
* @method Phaser.Curves.Curve#getSpacedPoints
* @since 3.0.0
*
2019-11-27 02:05:54 +00:00
* @param {integer} [divisions] - The number of evenly spaced points from the curve to return. If falsy, step param will be used to calculate the number of points.
* @param {number} [step] - Step between points. Used to calculate the number of points to return when divisions is falsy. Ignored if divisions is positive.
* @param {(array|Phaser.Math.Vector2[])} [out] - An optional array to store the points in.
2018-01-16 15:20:54 +00:00
*
* @return {Phaser.Math.Vector2[]} [description]
*/
getSpacedPoints: function (divisions, stepRate, out)
2018-01-16 15:20:54 +00:00
{
if (out === undefined) { out = []; }
2018-01-16 15:20:54 +00:00
// If divisions is a falsey value (false, null, 0, undefined, etc) then we calculate it based on the stepRate instead.
if (!divisions)
{
if (!stepRate)
{
divisions = this.defaultDivisions;
}
else
{
divisions = this.getLength() / stepRate;
}
}
2018-01-16 15:20:54 +00:00
for (var d = 0; d <= divisions; d++)
{
var t = this.getUtoTmapping(d / divisions, null, divisions);
out.push(this.getPoint(t));
2018-01-16 15:20:54 +00:00
}
return out;
2018-01-16 15:20:54 +00:00
},
/**
* [description]
*
* @method Phaser.Curves.Curve#getStartPoint
* @since 3.0.0
*
2018-03-27 12:06:24 +00:00
* @generic {Phaser.Math.Vector2} O - [out,$return]
*
2018-01-16 15:20:54 +00:00
* @param {Phaser.Math.Vector2} [out] - [description]
*
* @return {Phaser.Math.Vector2} [description]
*/
getStartPoint: function (out)
{
if (out === undefined) { out = new Vector2(); }
return this.getPointAt(0, out);
},
/**
2019-06-17 20:03:22 +00:00
* Returns a unit vector tangent at t
* In case any sub curve does not implement its tangent derivation,
* 2 points a small delta apart will be used to find its gradient
* which seems to give a reasonable approximation
2018-01-16 15:20:54 +00:00
*
* @method Phaser.Curves.Curve#getTangent
* @since 3.0.0
*
2018-03-27 12:06:24 +00:00
* @generic {Phaser.Math.Vector2} O - [out,$return]
*
2018-01-16 15:20:54 +00:00
* @param {number} t - [description]
* @param {Phaser.Math.Vector2} [out] - [description]
*
2018-09-27 14:29:32 +00:00
* @return {Phaser.Math.Vector2} Vector approximating the tangent line at the point t (delta +/- 0.0001)
2018-01-16 15:20:54 +00:00
*/
getTangent: function (t, out)
{
if (out === undefined) { out = new Vector2(); }
var delta = 0.0001;
var t1 = t - delta;
var t2 = t + delta;
// Capping in case of danger
if (t1 < 0)
{
t1 = 0;
}
if (t2 > 1)
{
t2 = 1;
}
this.getPoint(t1, this._tmpVec2A);
this.getPoint(t2, out);
return out.subtract(this._tmpVec2A).normalize();
},
/**
* [description]
*
* @method Phaser.Curves.Curve#getTangentAt
* @since 3.0.0
*
2018-03-27 12:06:24 +00:00
* @generic {Phaser.Math.Vector2} O - [out,$return]
*
* @param {number} u - [description]
2018-01-16 15:20:54 +00:00
* @param {Phaser.Math.Vector2} [out] - [description]
*
* @return {Phaser.Math.Vector2} [description]
*/
getTangentAt: function (u, out)
{
var t = this.getUtoTmapping(u);
return this.getTangent(t, out);
},
// Given a distance in pixels, get a t to find p.
/**
* [description]
*
* @method Phaser.Curves.Curve#getTFromDistance
* @since 3.0.0
*
* @param {integer} distance - [description]
* @param {integer} [divisions] - [description]
*
* @return {number} [description]
2018-01-16 15:20:54 +00:00
*/
getTFromDistance: function (distance, divisions)
{
if (distance <= 0)
{
return 0;
}
return this.getUtoTmapping(0, distance, divisions);
},
// Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
/**
* [description]
*
* @method Phaser.Curves.Curve#getUtoTmapping
* @since 3.0.0
*
* @param {number} u - [description]
2018-01-16 15:20:54 +00:00
* @param {integer} distance - [description]
* @param {integer} [divisions] - [description]
*
* @return {number} [description]
*/
getUtoTmapping: function (u, distance, divisions)
{
var arcLengths = this.getLengths(divisions);
var i = 0;
var il = arcLengths.length;
var targetArcLength; // The targeted u distance value to get
if (distance)
{
// Cannot overshoot the curve
targetArcLength = Math.min(distance, arcLengths[il - 1]);
}
else
{
targetArcLength = u * arcLengths[il - 1];
}
// binary search for the index with largest value smaller than target u distance
var low = 0;
var high = il - 1;
var comparison;
while (low <= high)
{
i = Math.floor(low + (high - low) / 2); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
comparison = arcLengths[i] - targetArcLength;
if (comparison < 0)
{
low = i + 1;
}
else if (comparison > 0)
{
high = i - 1;
}
else
{
high = i;
break;
}
}
i = high;
if (arcLengths[i] === targetArcLength)
{
return i / (il - 1);
}
// we could get finer grain at lengths, or use simple interpolation between two points
var lengthBefore = arcLengths[i];
var lengthAfter = arcLengths[i + 1];
var segmentLength = lengthAfter - lengthBefore;
// determine where we are between the 'before' and 'after' points
var segmentFraction = (targetArcLength - lengthBefore) / segmentLength;
// add that fractional amount to t
return (i + segmentFraction) / (il - 1);
},
/**
* [description]
*
* @method Phaser.Curves.Curve#updateArcLengths
* @since 3.0.0
*/
updateArcLengths: function ()
{
this.needsUpdate = true;
this.getLengths();
}
});
module.exports = Curve;