/** * @author Richard Davey * @copyright 2019 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ var Class = require('../utils/Class'); var FromPoints = require('../geom/rectangle/FromPoints'); var Rectangle = require('../geom/rectangle/Rectangle'); var Vector2 = require('../math/Vector2'); /** * @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 * @memberof Phaser.Curves * @constructor * @since 3.0.0 * * @param {string} type - [description] */ var Curve = new Class({ initialize: function Curve (type) { /** * String based identifier for the type of curve. * * @name Phaser.Curves.Curve#type * @type {string} * @since 3.0.0 */ this.type = type; /** * The default number of divisions within the curve. * * @name Phaser.Curves.Curve#defaultDivisions * @type {integer} * @default 5 * @since 3.0.0 */ this.defaultDivisions = 5; /** * The quantity of arc length divisions within the curve. * * @name Phaser.Curves.Curve#arcLengthDivisions * @type {integer} * @default 100 * @since 3.0.0 */ this.arcLengthDivisions = 100; /** * An array of cached arc length values. * * @name Phaser.Curves.Curve#cacheArcLengths * @type {number[]} * @default [] * @since 3.0.0 */ this.cacheArcLengths = []; /** * Does the data of this curve need updating? * * @name Phaser.Curves.Curve#needsUpdate * @type {boolean} * @default true * @since 3.0.0 */ this.needsUpdate = true; /** * [description] * * @name Phaser.Curves.Curve#active * @type {boolean} * @default true * @since 3.0.0 */ this.active = true; /** * A temporary calculation Vector. * * @name Phaser.Curves.Curve#_tmpVec2A * @type {Phaser.Math.Vector2} * @private * @since 3.0.0 */ this._tmpVec2A = new Vector2(); /** * A temporary calculation Vector. * * @name Phaser.Curves.Curve#_tmpVec2B * @type {Phaser.Math.Vector2} * @private * @since 3.0.0 */ this._tmpVec2B = new Vector2(); }, /** * Draws this curve on the given Graphics object. * * 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. * * @method Phaser.Curves.Curve#draw * @since 3.0.0 * * @generic {Phaser.GameObjects.Graphics} G - [graphics,$return] * * @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. * * @return {Phaser.GameObjects.Graphics} The Graphics object to which the curve was drawn. */ draw: function (graphics, pointsTotal) { if (pointsTotal === undefined) { pointsTotal = 32; } // So you can chain graphics calls return graphics.strokePoints(this.getPoints(pointsTotal)); }, /** * 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. * * @method Phaser.Curves.Curve#getBounds * @since 3.0.0 * * @param {Phaser.Geom.Rectangle} [out] - The Rectangle to store the bounds in. If falsey a new object will be created. * @param {integer} [accuracy=16] - The accuracy of the bounds calculations. * * @return {Phaser.Geom.Rectangle} A Rectangle object holding the bounds of this curve. If `out` was given it will be this object. */ getBounds: function (out, accuracy) { if (!out) { out = new Rectangle(); } 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); }, /** * Returns an array of points, spaced out X distance pixels apart. * The smaller the distance, the larger the array will be. * * @method Phaser.Curves.Curve#getDistancePoints * @since 3.0.0 * * @param {integer} distance - The distance, in pixels, between each point along the curve. * * @return {Phaser.Geom.Point[]} An Array of Point objects. */ 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 * * @param {Phaser.Math.Vector2} [out] - Optional Vector object to store the result in. * * @return {Phaser.Math.Vector2} Vector2 containing the coordinates of the curves end point. */ getEndPoint: function (out) { if (out === undefined) { out = new Vector2(); } return this.getPointAt(1, out); }, // Get total curve arc length /** * [description] * * @method Phaser.Curves.Curve#getLength * @since 3.0.0 * * @return {number} [description] */ getLength: function () { var lengths = this.getLengths(); return lengths[lengths.length - 1]; }, // Get list of cumulative segment lengths /** * [description] * * @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 * * @generic {Phaser.Math.Vector2} O - [out,$return] * * @param {number} u - [description] * @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] - [description] * * @return {Phaser.Math.Vector2[]} [description] */ getPoints: function (divisions) { if (divisions === undefined) { divisions = this.defaultDivisions; } var points = []; for (var d = 0; d <= divisions; d++) { points.push(this.getPoint(d / divisions)); } return points; }, /** * [description] * * @method Phaser.Curves.Curve#getRandomPoint * @since 3.0.0 * * @generic {Phaser.Math.Vector2} O - [out,$return] * * @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 * * @param {integer} [divisions] - [description] * * @return {Phaser.Math.Vector2[]} [description] */ getSpacedPoints: function (divisions) { if (divisions === undefined) { divisions = this.defaultDivisions; } var points = []; for (var d = 0; d <= divisions; d++) { var t = this.getUtoTmapping(d / divisions, null, divisions); points.push(this.getPoint(t)); } return points; }, /** * [description] * * @method Phaser.Curves.Curve#getStartPoint * @since 3.0.0 * * @generic {Phaser.Math.Vector2} O - [out,$return] * * @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); }, // 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 /** * [description] * * @method Phaser.Curves.Curve#getTangent * @since 3.0.0 * * @generic {Phaser.Math.Vector2} O - [out,$return] * * @param {number} t - [description] * @param {Phaser.Math.Vector2} [out] - [description] * * @return {Phaser.Math.Vector2} Vector approximating the tangent line at the point t (delta +/- 0.0001) */ 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 * * @generic {Phaser.Math.Vector2} O - [out,$return] * * @param {number} u - [description] * @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] */ 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] * @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;