UNPKG

toosoon-utils

Version:
169 lines (168 loc) 5.05 kB
/** * Utility abstract class for manipulating curves * * @exports * @class Curve * @abstract */ export default class Curve { isCurve = true; type = 'Curve'; /** * Amount of divisions when calculating the cumulative segment lengths of a curve */ arcLengthDivisions = 200; /** * Must be set to `true` if the curve parameters have changed */ needsUpdate = false; _cacheArcLengths = []; /** * Interpolate a point on this curve * * @param {number} u Normalized position value to interpolate * @returns {Vector} Interpolated coordinates on this curve */ getPointAt(u) { const t = this.getUtoTmapping(u); return this.getPoint(t); } /** * Compute this curve shape into an array of points * * @param {number} [divisions=5] Number of divisions * @returns {Vector[]} */ getPoints(divisions = 5) { const points = []; for (let i = 0; i <= divisions; i++) { points.push(this.getPoint(i / divisions)); } return points; } /** * Compute this curve shape into an array of equi-spaced points across the entire curve * * @param {number} [divisions=5] Number of divisions * @returns {Vector[]} */ getSpacedPoints(divisions = 5) { const points = []; for (let i = 0; i <= divisions; i++) { points.push(this.getPointAt(i / divisions)); } return points; } /** * Compute the total arc length of this curve * * @returns {number} */ getLength() { const lengths = this.getLengths(); return lengths[lengths.length - 1]; } /** * Compute the cumulative segment lengths of this curve * * @param {number} [divisions=this.arcLengthDivisions] Number of divisions * @returns {number[]} */ getLengths(divisions = this.arcLengthDivisions) { if (this._cacheArcLengths.length === divisions + 1 && !this.needsUpdate) { return this._cacheArcLengths; } this.needsUpdate = false; const lengths = [0]; let currentPoint; let lastPoint = this.getPoint(0); let sum = 0; for (let i = 1; i <= divisions; i++) { currentPoint = this.getPoint(i / divisions); sum += currentPoint.distanceTo(lastPoint); lengths.push(sum); lastPoint = currentPoint; } this._cacheArcLengths = lengths; return lengths; } /** * Update the cached cumulative segment lengths */ updateArcLengths() { this.needsUpdate = true; this.getLengths(); } /** * Re-map a normalized position value into normalized time * * @param {number} u Normalized position value to interpolate * @param {number} [targetArcLength] Distance on this curve * @returns {number} Updated interpolation value */ getUtoTmapping(u, targetArcLength) { const arcLengths = this.getLengths(); let i = 0; const length = arcLengths.length; targetArcLength = targetArcLength ?? u * arcLengths[length - 1]; let low = 0; let high = length - 1; let comparison; while (low <= high) { i = Math.floor(low + (high - low) / 2); 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 / (length - 1); } const lengthBefore = arcLengths[i]; const lengthAfter = arcLengths[i + 1]; const segmentLength = lengthAfter - lengthBefore; const segmentFraction = (targetArcLength - lengthBefore) / segmentLength; const t = (i + segmentFraction) / (length - 1); return t; } /** * Compute an unit vector tangent for the given normalized time value * * @param {number} t Normalized time value * @returns {Vector} Tangent vector */ getTangent(t) { const delta = 0.0001; let t0 = Math.max(0, t - delta); let t1 = Math.min(1, t + delta); const p0 = this.getPoint(t0); const p1 = this.getPoint(t1); return p1.clone().sub(p0).normalize(); } /** * Compute an unit vector tangent for the given normalized position value * * @param {number} u Normalized position value * @returns {Vector} Tangent vector */ getTangentAt(u) { const t = this.getUtoTmapping(u); return this.getTangent(t); } /** * Check if this curve is closed * * @returns {boolean} True if the curve is closed, false otherwise */ isClosed() { return this.getPoint(0).equals(this.getPoint(1)); } }