UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

174 lines (171 loc) 6.7 kB
import { CURVE_STEP, CURVE_LINEAR, CURVE_SMOOTHSTEP, CURVE_SPLINE } from './constants.js'; import { math } from './math.js'; /** * @import { Curve } from './curve.js' */ /** * A class for evaluating a curve at a specific time. * * @ignore */ class CurveEvaluator { /** * Create a new CurveEvaluator instance. * * @param {Curve} curve - The curve to evaluate. * @param {number} time - The initial time to evaluate the curve at. Defaults to 0. */ constructor(curve, time = 0){ /** @private */ this._left = -Infinity; /** @private */ this._right = Infinity; /** @private */ this._recip = 0; /** @private */ this._p0 = 0; /** @private */ this._p1 = 0; /** @private */ this._m0 = 0; /** @private */ this._m1 = 0; this._curve = curve; this._reset(time); } /** * Evaluate the curve at the given time. Specify forceReset if the underlying curve keys have * changed since the last evaluation. * * @param {number} time - Time to evaluate the curve at. * @param {boolean} [forceReset] - Force reset of the curve. * @returns {number} The evaluated value. */ evaluate(time, forceReset = false) { if (forceReset || time < this._left || time >= this._right) { this._reset(time); } let result; const type = this._curve.type; if (type === CURVE_STEP) { // step result = this._p0; } else { // calculate normalized t const t = this._recip === 0 ? 0 : (time - this._left) * this._recip; if (type === CURVE_LINEAR) { // linear result = math.lerp(this._p0, this._p1, t); } else if (type === CURVE_SMOOTHSTEP) { // smoothstep result = math.lerp(this._p0, this._p1, t * t * (3 - 2 * t)); } else { // curve result = this._evaluateHermite(this._p0, this._p1, this._m0, this._m1, t); } } return result; } /** * Calculate weights for the curve interval at the given time. * * @param {number} time - Time to evaluate the curve at. * @private */ _reset(time) { const keys = this._curve.keys; const len = keys.length; if (!len) { // curve is empty this._left = -Infinity; this._right = Infinity; this._recip = 0; this._p0 = this._p1 = this._m0 = this._m1 = 0; } else { if (time < keys[0][0]) { // iterator falls to the left of the start of the curve this._left = -Infinity; this._right = keys[0][0]; this._recip = 0; this._p0 = this._p1 = keys[0][1]; this._m0 = this._m1 = 0; } else if (time >= keys[len - 1][0]) { // iterator falls to the right of the end of the curve this._left = keys[len - 1][0]; this._right = Infinity; this._recip = 0; this._p0 = this._p1 = keys[len - 1][1]; this._m0 = this._m1 = 0; } else { // iterator falls within the bounds of the curve // perform a linear search for the key just left of the current time. // (TODO: for cases where the curve has more than 'n' keys it will // be more efficient to perform a binary search here instead. Which is // straight forward thanks to the sorted list of knots). let index = 0; while(time >= keys[index + 1][0]){ index++; } this._left = keys[index][0]; this._right = keys[index + 1][0]; const diff = 1.0 / (this._right - this._left); this._recip = isFinite(diff) ? diff : 0; this._p0 = keys[index][1]; this._p1 = keys[index + 1][1]; if (this._curve.type === CURVE_SPLINE) { this._calcTangents(keys, index); } } } } /** * Calculate tangents for the hermite curve. * * @param {number[][]} keys - The keys of the curve. * @param {number} index - The key index of the key to calculate the tangents for. * @private */ _calcTangents(keys, index) { let a; const b = keys[index]; const c = keys[index + 1]; let d; if (index === 0) { a = [ keys[0][0] + (keys[0][0] - keys[1][0]), keys[0][1] + (keys[0][1] - keys[1][1]) ]; } else { a = keys[index - 1]; } if (index === keys.length - 2) { d = [ keys[index + 1][0] + (keys[index + 1][0] - keys[index][0]), keys[index + 1][1] + (keys[index + 1][1] - keys[index][1]) ]; } else { d = keys[index + 2]; } if (this._curve.type === CURVE_SPLINE) { // calculate tangent scale (due to non-uniform knot spacing) const s1_ = 2 * (c[0] - b[0]) / (c[0] - a[0]); const s2_ = 2 * (c[0] - b[0]) / (d[0] - b[0]); this._m0 = this._curve.tension * (isFinite(s1_) ? s1_ : 0) * (c[1] - a[1]); this._m1 = this._curve.tension * (isFinite(s2_) ? s2_ : 0) * (d[1] - b[1]); } else { // original tangent scale calc const s1 = (c[0] - b[0]) / (b[0] - a[0]); const s2 = (c[0] - b[0]) / (d[0] - c[0]); const a_ = b[1] + (a[1] - b[1]) * (isFinite(s1) ? s1 : 0); const d_ = c[1] + (d[1] - c[1]) * (isFinite(s2) ? s2 : 0); const tension = this._curve.tension; this._m0 = tension * (c[1] - a_); this._m1 = tension * (d_ - b[1]); } } /** * Evaluate the hermite curve at the given time. * * @param {number} p0 - The first key. * @param {number} p1 - The second key. * @param {number} m0 - The first tangent. * @param {number} m1 - The second tangent. * @param {number} t - Time to evaluate the curve at. * @returns {number} The value of the hermite curve at the given time. * @private */ _evaluateHermite(p0, p1, m0, m1, t) { const t2 = t * t; const twot = t + t; const omt = 1 - t; const omt2 = omt * omt; return p0 * ((1 + twot) * omt2) + m0 * (t * omt2) + p1 * (t2 * (3 - twot)) + m1 * (t2 * (t - 1)); } } export { CurveEvaluator };