From bec8345573a072d542ec4052c6a7f6b19420fdc5 Mon Sep 17 00:00:00 2001 From: photonstorm Date: Thu, 5 Jan 2017 15:46:47 +0000 Subject: [PATCH] Added Hermite class and functions. --- v3/src/checksum.js | 2 +- v3/src/geom/hermite/FindT.js | 37 ++ v3/src/geom/hermite/GetAngle.js | 20 + v3/src/geom/hermite/GetAngleWithDistance.js | 25 ++ v3/src/geom/hermite/GetEntryTangent.js | 16 + v3/src/geom/hermite/GetPoint.js | 35 ++ v3/src/geom/hermite/GetPointWithDistance.js | 31 ++ v3/src/geom/hermite/GetX.js | 33 ++ v3/src/geom/hermite/GetY.js | 33 ++ v3/src/geom/hermite/Hermite.js | 412 ++++++++++++++++++++ v3/src/geom/hermite/index.js | 14 + v3/src/geom/index.js | 1 + 12 files changed, 658 insertions(+), 1 deletion(-) create mode 100644 v3/src/geom/hermite/FindT.js create mode 100644 v3/src/geom/hermite/GetAngle.js create mode 100644 v3/src/geom/hermite/GetAngleWithDistance.js create mode 100644 v3/src/geom/hermite/GetEntryTangent.js create mode 100644 v3/src/geom/hermite/GetPoint.js create mode 100644 v3/src/geom/hermite/GetPointWithDistance.js create mode 100644 v3/src/geom/hermite/GetX.js create mode 100644 v3/src/geom/hermite/GetY.js create mode 100644 v3/src/geom/hermite/Hermite.js create mode 100644 v3/src/geom/hermite/index.js diff --git a/v3/src/checksum.js b/v3/src/checksum.js index 5779fe9c8..b6f4dd5a7 100644 --- a/v3/src/checksum.js +++ b/v3/src/checksum.js @@ -1,4 +1,4 @@ var CHECKSUM = { -build: '528a0210-d341-11e6-a2a2-8fbbe27dda30' +build: '20b3f530-d35e-11e6-b3f2-61f4b6b8c9a3' }; module.exports = CHECKSUM; \ No newline at end of file diff --git a/v3/src/geom/hermite/FindT.js b/v3/src/geom/hermite/FindT.js new file mode 100644 index 000000000..cd0718da2 --- /dev/null +++ b/v3/src/geom/hermite/FindT.js @@ -0,0 +1,37 @@ +/** +* Convert a distance along this curve into a `time` value which will be between 0 and 1. +* +* For example if this curve has a length of 100 pixels then `findT(50)` would return `0.5`. +* +* @method Phaser.Hermite#findT +* @param {integer} distance - The distance into the curve in pixels. Should be a positive integer. +* @return {number} The time (`t`) value, a float between 0 and 1. +*/ +var FindT = function (curve, distance) +{ + if (distance <= 0) + { + return 0; + } + + // Find the _points which bracket the distance value + var ti = Math.floor(distance / curve.length * curve._accuracy); + + while (ti > 0 && curve._points[ti] > distance) + { + ti--; + } + + while (ti < curve._accuracy && curve._points[ti] < distance) + { + ti++; + } + + // Linear interpolation to get a more accurate fix + var dt = curve._points[ti] - curve._points[ti - 1]; + var d = distance - curve._points[ti - 1]; + + return ((ti - 1) / curve._accuracy) + d / (dt * curve._accuracy); +}; + +module.exports = FindT; diff --git a/v3/src/geom/hermite/GetAngle.js b/v3/src/geom/hermite/GetAngle.js new file mode 100644 index 000000000..9ca786b99 --- /dev/null +++ b/v3/src/geom/hermite/GetAngle.js @@ -0,0 +1,20 @@ +var GetPoint = require('./GetPoint'); + +/** +* Calculate and return the angle, in radians, of the curves tangent based on time. +* +* @method Phaser.Hermite#getAngle +* @param {number} [t=0] - The `t` (time) value at which to find the angle. Must be between 0 and 1. +* @return {number} The angle of the line at the specified `t` time value along the curve. The value is in radians. +*/ +var GetAngle = function (curve, t) +{ + if (t === undefined) { t = 0; } + + GetPoint(curve, t - 0.01, curve._temp1); + GetPoint(curve, t + 0.01, curve._temp2); + + return Math.atan2(curve._temp2.y - curve._temp1.y, curve._temp2.x - curve._temp1.x); +}; + +module.exports = GetAngle; diff --git a/v3/src/geom/hermite/GetAngleWithDistance.js b/v3/src/geom/hermite/GetAngleWithDistance.js new file mode 100644 index 000000000..632bce831 --- /dev/null +++ b/v3/src/geom/hermite/GetAngleWithDistance.js @@ -0,0 +1,25 @@ +var GetAngle = require('./GetAngle'); +var FindT = require('./FindT'); + +/** +* Calculate and return the angle, in radians, of the curves tangent at the given pixel distance along the curves length. +* +* @method Phaser.Hermite#getAngleWithDistance +* @param {number} [distance=0] - The distance along the curve to get the angle from, in pixels. +* @return {number} The angle of the line at the specified distance along the curve. The value is in radians. +*/ +var GetAngleWithDistance = function (curve, distance) +{ + if (distance === undefined) { distance = 0; } + + if (distance <= 0) + { + return Math.atan2(this._v1y, this._v1x); + } + else + { + return GetAngle(curve, FindT(curve, distance)); + } +}; + +module.exports = GetAngleWithDistance; diff --git a/v3/src/geom/hermite/GetEntryTangent.js b/v3/src/geom/hermite/GetEntryTangent.js new file mode 100644 index 000000000..50f3bd9ba --- /dev/null +++ b/v3/src/geom/hermite/GetEntryTangent.js @@ -0,0 +1,16 @@ +/** +* Get the angle of the curves entry point. +* +* @method Phaser.Hermite#getEntryTangent +* @param {Phaser.Point|Object} point - The Phaser.Point object, or an Object with public `x` and `y` properties, in which the tangent vector values will be stored. +* @return {Phaser.Point} A Point object containing the tangent vector of this Hermite curve. +*/ +var GetEntryTangent = function (curve, point) +{ + point.x = curve._v1x; + point.y = curve._v1y; + + return point; +}; + +module.exports = GetEntryTangent; diff --git a/v3/src/geom/hermite/GetPoint.js b/v3/src/geom/hermite/GetPoint.js new file mode 100644 index 000000000..6d11b7c10 --- /dev/null +++ b/v3/src/geom/hermite/GetPoint.js @@ -0,0 +1,35 @@ +var Point = require('../point/Point'); + +/** +* Get a point on the curve using the `t` (time) value, which must be between 0 and 1. +* +* @method Phaser.Hermite#getPoint +* @param {number} [t=0] - The time value along the curve from which to extract a point. This is a value between 0 and 1, where 0 represents the start of the curve and 1 the end. +* @param {Phaser.Point|Object} [point] - An optional Phaser.Point, or Object containing public `x` and `y` properties. If given the resulting values will be stored in the Objects `x` and `y` properties. If omitted a new Phaser.Point object is created. +* @return {Phaser.Point} An Object with the x, y coordinate of the curve at the specified `t` value set in its `x` and `y` properties. +*/ +var GetPoint = function (curve, t, out) +{ + if (t === undefined) { t = 0; } + if (out === undefined) { out = new Point(); } + + if (t < 0) + { + t = 0; + } + + if (t > 1) + { + t = 1; + } + + var t2 = t * t; + var t3 = t * t2; + + out.x = t3 * curve._ax + t2 * curve._bx + t * curve._v1x + curve._p1x; + out.y = t3 * curve._ay + t2 * curve._by + t * curve._v1y + curve._p1y; + + return out; +}; + +module.exports = GetPoint; diff --git a/v3/src/geom/hermite/GetPointWithDistance.js b/v3/src/geom/hermite/GetPointWithDistance.js new file mode 100644 index 000000000..72f155236 --- /dev/null +++ b/v3/src/geom/hermite/GetPointWithDistance.js @@ -0,0 +1,31 @@ +var Point = require('../point/Point'); +var GetPoint = require('./GetPoint'); +var FindT = require('./FindT'); + +/** +* Get a point on the curve using the distance, in pixels, along the curve. +* +* @method Phaser.Hermite#getPointWithDistance +* @param {integer} [distance=0] - The distance along the curve to get the point from, given in pixels. +* @param {Phaser.Point|Object} [point] - An optional Phaser.Point, or Object containing public `x` and `y` properties. If given the resulting values will be stored in the Objects `x` and `y` properties. If omitted a new Phaser.Point object is created. +* @return {Phaser.Point} The point on the line at the specified 'distance' along the curve. +*/ +var GetPointWithDistance = function (curve, distance, out) +{ + if (distance === undefined) { distance = 0; } + if (out === undefined) { out = new Point(); } + + if (distance <= 0) + { + out.x = this._p1x; + out.y = this._p1y; + } + else + { + GetPoint(curve, FindT(curve, distance), out); + } + + return out; +}; + +module.exports = GetPointWithDistance; diff --git a/v3/src/geom/hermite/GetX.js b/v3/src/geom/hermite/GetX.js new file mode 100644 index 000000000..f92312147 --- /dev/null +++ b/v3/src/geom/hermite/GetX.js @@ -0,0 +1,33 @@ +/** +* Get the X component of a point on the curve based on the `t` (time) value, which must be between 0 and 1. +* +* @method Phaser.Hermite#getX +* @param {number} [t=0] - The time value along the curve from which to extract a point. This is a value between 0 and 1, where 0 represents the start of the curve and 1 the end. +* @return {number} The X component of a point on the curve based on the `t` (time) value. +*/ +var GetX = function (curve, t) +{ + if (t === undefined) + { + t = 0; + } + else + { + if (t < 0) + { + t = 0; + } + + if (t > 1) + { + t = 1; + } + } + + var t2 = t * t; + var t3 = t * t2; + + return (t3 * curve._ax + t2 * curve._bx + t * curve._v1x + curve._p1x); +}; + +module.exports = GetX; diff --git a/v3/src/geom/hermite/GetY.js b/v3/src/geom/hermite/GetY.js new file mode 100644 index 000000000..494438c18 --- /dev/null +++ b/v3/src/geom/hermite/GetY.js @@ -0,0 +1,33 @@ +/** +* Get the Y component of a point on the curve based on the `t` (time) value, which must be between 0 and 1. +* +* @method Phaser.Hermite#getY +* @param {number} [t=0] - The time value along the curve from which to extract a point. This is a value between 0 and 1, where 0 represents the start of the curve and 1 the end. +* @return {number} The Y component of a point on the curve based on the `t` (time) value. +*/ +var GetY = function (curve, t) +{ + if (t === undefined) + { + t = 0; + } + else + { + if (t < 0) + { + t = 0; + } + + if (t > 1) + { + t = 1; + } + } + + var t2 = t * t; + var t3 = t * t2; + + return (t3 * curve._ay + t2 * curve._by + t * curve._v1y + curve._p1y); +}; + +module.exports = GetY; diff --git a/v3/src/geom/hermite/Hermite.js b/v3/src/geom/hermite/Hermite.js new file mode 100644 index 000000000..d5364f350 --- /dev/null +++ b/v3/src/geom/hermite/Hermite.js @@ -0,0 +1,412 @@ +var Point = require('../point/Point'); + +/** +* @author Richard Davey +* @author Pete Baron +* @copyright 2016 Photon Storm Ltd. +* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} +*/ + +/** +* A data representation of a Hermite Curve (see http://en.wikipedia.org/wiki/Cubic_Hermite_spline) +* +* A Hermite curve has a start and end point and tangent vectors for both of them. +* The curve will always pass through the two control points and the shape of it is controlled +* by the length and direction of the tangent vectors. At the control points the curve will +* be facing exactly in the vector direction. +* +* As these curves change speed (speed = distance between points separated by an equal change in +* 't' value - see Hermite.getPoint) this class attempts to reduce the variation by pre-calculating +* the `accuracy` number of points on the curve. The straight-line distances to these points are stored +* in the private 'points' array, and this information is used by Hermite.findT() to convert a pixel +* distance along the curve into a 'time' value. +* +* Higher `accuracy` values will result in more even movement, but require more memory for the points +* list. 5 works, but 10 seems to be an ideal value for the length of curves found in most games on +* a desktop screen. If you use very long curves (more than 400 pixels) you may need to increase +* this value further. +* +* @class Phaser.Hermite +* @constructor +* @param {number} p1x - The x coordinate of the start of the curve. +* @param {number} p1y - The y coordinate of the start of the curve. +* @param {number} p2x - The x coordinate of the end of the curve. +* @param {number} p2y - The y coordinate of the end of the curve. +* @param {number} v1x - The x component of the tangent vector for the start of the curve. +* @param {number} v1y - The y component of the tangent vector for the start of the curve. +* @param {number} v2x - The x component of the tangent vector for the end of the curve. +* @param {number} v2y - The y component of the tangent vector for the end of the curve. +* @param {number} [accuracy=10] The amount of points to pre-calculate on the curve. +*/ +var Hermite = function (p1x, p1y, p2x, p2y, v1x, v1y, v2x, v2y, accuracy) +{ + if (accuracy === undefined) { accuracy = 10; } + + /** + * @property {number} _accuracy - The amount of points to pre-calculate on the curve. + * @private + */ + this._accuracy = accuracy; + + /** + * @property {number} _p1x - The x coordinate of the start of the curve. + * @private + */ + this._p1x = p1x; + + /** + * @property {number} _p1y - The y coordinate of the start of the curve. + * @private + */ + this._p1y = p1y; + + /** + * @property {number} _p2x - The x coordinate of the end of the curve. + * @private + */ + this._p2x = p2x; + + /** + * @property {number} _p2y - The y coordinate of the end of the curve. + * @private + */ + this._p2y = p2y; + + /** + * @property {number} _v1x - The x component of the tangent vector for the start of the curve. + * @private + */ + this._v1x = v1x; + + /** + * @property {number} _v1y - The y component of the tangent vector for the start of the curve. + * @private + */ + this._v1y = v1y; + + /** + * @property {number} _v2x - The x component of the tangent vector for the end of the curve. + * @private + */ + this._v2x = v2x; + + /** + * @property {number} _v2y - The y component of the tangent vector for the end of the curve. + * @private + */ + this._v2y = v2y; + + /** + * @property {array} _points - A local array of cached points. + * @private + */ + this._points = []; + + /** + * @property {Phaser.Point} _temp1 - A local cached Point object. + * @private + */ + this._temp1 = new Point(); + + /** + * @property {Phaser.Point} _temp2 - A local cached Point object. + * @private + */ + this._temp2 = new Point(); + + this.recalculate(); +}; + +Hermite.prototype.constructor = Hermite; + +Hermite.prototype = { + + /** + * Performs the curve calculations. + * + * This is called automatically if you change any of the curves public properties, such as `Hermite.p1x` or `Hermite.v2y`. + * + * If you adjust any of the internal private values, then call this to update the points. + * + * @method Phaser.Hermite#recalculate + * @return {Phaser.Hermite} This object. + */ + recalculate: function () { + + this._ax = (2 * this._p1x - 2 * this._p2x + this._v1x + this._v2x); + this._ay = (2 * this._p1y - 2 * this._p2y + this._v1y + this._v2y); + this._bx = (-3 * this._p1x + 3 * this._p2x - 2 * this._v1x - this._v2x); + this._by = (-3 * this._p1y + 3 * this._p2y - 2 * this._v1y - this._v2y); + + this.length = this.calculateEvenPoints(); + + return this; + + }, + + /** + * Calculate a number of points along the curve, based on `Hermite.accuracy`, and stores them in the private `_points` array. + * + * @method Phaser.Hermite#calculateEvenPoints + * @return {number} The total length of the curve approximated as straight line distances between the points. + */ + calculateEvenPoints: function () { + + var totalLength = 0; + + this._temp1.setTo(0, 0); // pnt + this._temp2.setTo(this._p1x, this._p1y); // lastPnt + + this._points[0] = 0; + + for (var i = 1; i <= this._accuracy; i++) + { + this.getPoint(i / this._accuracy, this._temp1); + totalLength += this._temp1.distance(this._temp2); + this._points[i] = totalLength; + this._temp2.copyFrom(this._temp1); + } + + return totalLength; + + } + +}; + +Object.defineProperties(Hermite.prototype, { + + /** + * @name Phaser.Hermite#accuracy + * @property {number} accuracy - The amount of points to pre-calculate on the curve. + */ + accuracy: { + + enumerable: true, + + get: function () + { + return this._accuracy; + }, + + set: function (value) + { + if (value !== this._accuracy) + { + this._accuracy = value; + this.recalculate(); + } + } + + }, + + /** + * @name Phaser.Hermite#p1x + * @property {number} p1x - The x coordinate of the start of the curve. Setting this value will recalculate the curve. + */ + p1x: { + + enumerable: true, + + get: function () { + + return this._p1x; + + }, + + set: function (value) { + + if (value !== this._p1x) + { + this._p1x = value; + this.recalculate(); + } + + } + + }, + + /** + * @name Phaser.Hermite#p1y + * @property {number} p1y - The y coordinate of the start of the curve. Setting this value will recalculate the curve. + */ + p1y: { + + enumerable: true, + + get: function () { + + return this._p1y; + + }, + + set: function (value) { + + if (value !== this._p1y) + { + this._p1y = value; + this.recalculate(); + } + + } + + }, + + /** + * @name Phaser.Hermite#p2x + * @property {number} p2x - The x coordinate of the end of the curve. Setting this value will recalculate the curve. + */ + p2x: { + + enumerable: true, + + get: function () { + + return this._p2x; + + }, + + set: function (value) { + + if (value !== this._p2x) + { + this._p2x = value; + this.recalculate(); + } + + } + + }, + + /** + * @name Phaser.Hermite#p2y + * @property {number} p2y - The y coordinate of the end of the curve. Setting this value will recalculate the curve. + */ + p2y: { + + enumerable: true, + + get: function () { + + return this._p2y; + + }, + + set: function (value) { + + if (value !== this._p2y) + { + this._p2y = value; + this.recalculate(); + } + + } + + }, + + /** + * @name Phaser.Hermite#v1x + * @property {number} v1x - The x component of the tangent vector for the start of the curve. Setting this value will recalculate the curve. + */ + v1x: { + + enumerable: true, + + get: function () { + + return this._v1x; + + }, + + set: function (value) { + + if (value !== this._v1x) + { + this._v1x = value; + this.recalculate(); + } + + } + + }, + + /** + * @name Phaser.Hermite#v1y + * @property {number} v1y - The y component of the tangent vector for the start of the curve. Setting this value will recalculate the curve. + */ + v1y: { + + enumerable: true, + + get: function () { + + return this._v1y; + + }, + + set: function (value) { + + if (value !== this._v1y) + { + this._v1y = value; + this.recalculate(); + } + + } + + }, + + /** + * @name Phaser.Hermite#v2x + * @property {number} v2x - The x component of the tangent vector for the end of the curve. Setting this value will recalculate the curve. + */ + v2x: { + + enumerable: true, + + get: function () { + + return this._v2x; + + }, + + set: function (value) { + + if (value !== this._v2x) + { + this._v2x = value; + this.recalculate(); + } + + } + + }, + + /** + * @name Phaser.Hermite#v2y + * @property {number} v2y - The y component of the tangent vector for the end of the curve. Setting this value will recalculate the curve. + */ + v2y: { + + enumerable: true, + + get: function () { + + return this._v2y; + + }, + + set: function (value) { + + if (value !== this._v2y) + { + this._v2y = value; + this.recalculate(); + } + + } + + } + +}); + +module.exports = Hermite; diff --git a/v3/src/geom/hermite/index.js b/v3/src/geom/hermite/index.js new file mode 100644 index 000000000..6fecdc67e --- /dev/null +++ b/v3/src/geom/hermite/index.js @@ -0,0 +1,14 @@ +// Phaser.Geom.Hermite + +var Hermite = require('./Hermite'); + +Hermite.FindT = require('./FindT'); +Hermite.GetAngle = require('./GetAngle'); +Hermite.GetAngleWithDistance = require('./GetAngleWithDistance'); +Hermite.GetEntryTangent = require('./GetEntryTangent'); +Hermite.GetPoint = require('./GetPoint'); +Hermite.GetPointWithDistance = require('./GetPointWithDistance'); +Hermite.GetX = require('./GetX'); +Hermite.GetY = require('./GetY'); + +module.exports = Hermite; diff --git a/v3/src/geom/index.js b/v3/src/geom/index.js index 15316c105..a83400cd2 100644 --- a/v3/src/geom/index.js +++ b/v3/src/geom/index.js @@ -4,6 +4,7 @@ module.exports = { Circle: require('./circle'), Ellipse: require('./ellipse'), + Hermite: require('./hermite'), Intersects: require('./intersects'), Line: require('./line'), Point: require('./point'),