playcanvas
Version:
PlayCanvas WebGL game engine
174 lines (171 loc) • 6.7 kB
JavaScript
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 };