UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

202 lines (184 loc) 5.82 kB
import { Curve } from './Curve.js'; import * as Curves from '../curves/Curves.js'; /** * A base class extending {@link Curve}. `CurvePath` is simply an * array of connected curves, but retains the API of a curve. * * @augments Curve */ class CurvePath extends Curve { /** * Constructs a new curve path. */ constructor() { super(); this.type = 'CurvePath'; /** * An array of curves defining the * path. * * @type {Array<Curve>} */ this.curves = []; /** * Whether the path should automatically be closed * by a line curve. * * @type {boolean} * @default false */ this.autoClose = false; } /** * Adds a curve to this curve path. * * @param {Curve} curve - The curve to add. */ add(curve) { this.curves.push(curve); } /** * Adds a line curve to close the path. * * @return {CurvePath} A reference to this curve path. */ closePath() { // Add a line curve if start and end of lines are not connected const startPoint = this.curves[0].getPoint(0); const endPoint = this.curves[this.curves.length - 1].getPoint(1); if (!startPoint.equals(endPoint)) { const lineType = startPoint.isVector2 === true ? 'LineCurve' : 'LineCurve3'; this.curves.push(new Curves[lineType](endPoint, startPoint)); } return this; } /** * This method returns a vector in 2D or 3D space (depending on the curve definitions) * for the given interpolation factor. * * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. * @return {?(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. */ getPoint(t, optionalTarget) { // To get accurate point with reference to // entire path distance at time t, // following has to be done: // 1. Length of each sub path have to be known // 2. Locate and identify type of curve // 3. Get t for the curve // 4. Return curve.getPointAt(t') const d = t * this.getLength(); const curveLengths = this.getCurveLengths(); let i = 0; // To think about boundaries points. while (i < curveLengths.length) { if (curveLengths[i] >= d) { const diff = curveLengths[i] - d; const curve = this.curves[i]; const segmentLength = curve.getLength(); const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; return curve.getPointAt(u, optionalTarget); } i++; } return null; // loop where sum != 0, sum > d , sum+1 <d } getLength() { // We cannot use the default THREE.Curve getPoint() with getLength() because in // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath // getPoint() depends on getLength const lens = this.getCurveLengths(); return lens[lens.length - 1]; } updateArcLengths() { // cacheLengths must be recalculated. this.needsUpdate = true; this.cacheLengths = null; this.getCurveLengths(); } /** * Returns list of cumulative curve lengths of the defined curves. * * @return {Array<number>} The curve lengths. */ getCurveLengths() { // Compute lengths and cache them // We cannot overwrite getLengths() because UtoT mapping uses it. // We use cache values if curves and cache array are same length if (this.cacheLengths && this.cacheLengths.length === this.curves.length) { return this.cacheLengths; } // Get length of sub-curve // Push sums into cached array 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.cacheLengths = lengths; return lengths; } getSpacedPoints(divisions = 40) { const points = []; for (let i = 0; i <= divisions; i++) { points.push(this.getPoint(i / divisions)); } if (this.autoClose) { points.push(points[0]); } return points; } getPoints(divisions = 12) { const points = []; let last; for (let i = 0, curves = this.curves; i < curves.length; i++) { const curve = curves[i]; const resolution = curve.isEllipseCurve ? divisions * 2 : curve.isLineCurve || curve.isLineCurve3 ? 1 : curve.isSplineCurve ? divisions * curve.points.length : divisions; const pts = curve.getPoints(resolution); for (let j = 0; j < pts.length; j++) { const point = pts[j]; if (last && last.equals(point)) continue; // ensures no consecutive points are duplicates points.push(point); last = point; } } if (this.autoClose && points.length > 1 && !points[points.length - 1].equals(points[0])) { points.push(points[0]); } return points; } copy(source) { super.copy(source); this.curves = []; for (let i = 0, l = source.curves.length; i < l; i++) { const curve = source.curves[i]; this.curves.push(curve.clone()); } this.autoClose = source.autoClose; return this; } toJSON() { const data = super.toJSON(); data.autoClose = this.autoClose; data.curves = []; for (let i = 0, l = this.curves.length; i < l; i++) { const curve = this.curves[i]; data.curves.push(curve.toJSON()); } return data; } fromJSON(json) { super.fromJSON(json); this.autoClose = json.autoClose; this.curves = []; for (let i = 0, l = json.curves.length; i < l; i++) { const curve = json.curves[i]; this.curves.push(new Curves[curve.type]().fromJSON(curve)); } return this; } } export { CurvePath };