@openhps/core
Version:
Open Hybrid Positioning System - Core component
202 lines (184 loc) • 5.82 kB
JavaScript
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 };