toosoon-utils
Version:
Utility functions & classes
149 lines (148 loc) • 4.49 kB
JavaScript
import { isCoincident } from '../../geometry';
import Curve from './Curve';
import LineCurve from './LineCurve';
/**
* Utility class for manipulating connected curves
*
* @exports
* @class PathCurve
* @extends Curve
*/
export default class PathCurve extends Curve {
type = 'PathCurve';
/**
* Array of curves composing the path
*/
curves = [];
/**
* Array of points composing the path
*/
points = [];
autoClose = false;
/**
* Add a curve to this curve path
*
* @param {Curve} curve Curve to add
*/
add(curve) {
this.curves.push(curve);
}
/**
* Interpolate a point on the curve path
*
* @param {number} t Normalized time value to interpolate
* @returns {Point|null} Interpolated coordinates on the curve
*/
getPoint(t) {
const d = t * this.getLength();
const curveLengths = this.getCurveLengths();
let i = 0;
while (i < curveLengths.length) {
if (curveLengths[i] >= d) {
const delta = curveLengths[i] - d;
const curve = this.curves[i];
const segmentLength = curve.getLength();
const u = segmentLength === 0 ? 0 : 1 - delta / segmentLength;
return curve.getPointAt(u);
}
i++;
}
console.warn(`PathCurve.getPoint()`, `No point found in curve.`, this);
return [0, 0];
}
/**
* Compute the curve shape into an array of points
*
* @param {number} [divisions=40] Number of divisions
* @returns {Point[]}
*/
getPoints(divisions = 40) {
const curves = this.curves;
const points = [];
let lastPoint = null;
for (let i = 0; i < curves.length; i++) {
const curve = curves[i];
const resolution = curve.type === 'EllipseCurve'
? divisions * 2
: curve.type === 'LineCurve'
? 1
: curve.type === 'SplineCurve'
? divisions * curve.points.length
: divisions;
const points = curve.getPoints(resolution);
for (let j = 0; j < points.length; j++) {
const point = points[j];
if (lastPoint && isCoincident(...lastPoint, ...point))
continue;
points.push(point);
lastPoint = point;
}
}
if (this.autoClose && !Curve.isClosed(points)) {
points.push(points[0]);
}
return points;
}
/**
* Compute the curve shape into an array of equi-spaced points across the entire curve path
*
* @param {number} [divisions=40] Number of divisions
* @returns {Point[]}
*/
getSpacedPoints(divisions = 40) {
const points = [];
for (let i = 0; i <= divisions; i++) {
points.push(this.getPoint(i / divisions));
}
if (this.autoClose && !Curve.isClosed(points)) {
points.push(points[0]);
}
return points;
}
/**
* Compute the total arc length of the curve path
*
* @returns {number}
*/
getLength() {
const lengths = this.getCurveLengths();
return lengths[lengths.length - 1];
}
/**
* Compute the cumulative curve lengths of the curve path
*
* @returns {number[]}
*/
getCurveLengths() {
if (this._cacheArcLengths.length === this.curves.length) {
return this._cacheArcLengths;
}
const lengths = [];
let sums = 0;
for (let i = 0, l = this.curves.length; i < l; i++) {
sums += this.curves[i].getLength();
lengths.push(sums);
}
this._cacheArcLengths = lengths;
return lengths;
}
/**
* Update the cached cumulative segment lengths
*/
updateArcLengths() {
this.needsUpdate = true;
this._cacheArcLengths = [];
this.getCurveLengths();
}
/**
* Add a line curve to close the curve path
* Add an instance of {@link LineCurve} to the path
*/
closePath() {
const startPoint = this.curves[0]?.getPoint(0);
const endPoint = this.curves[this.curves.length - 1]?.getPoint(1);
if (!isCoincident(...startPoint, ...endPoint)) {
this.add(new LineCurve(...endPoint, ...startPoint));
}
}
}