src/viewer/scene/paths/Path.js
import {utils} from "../utils.js";
import {Curve} from "./Curve.js"
/**
* @desc A complex curved path constructed from various {@link Curve} subtypes.
*
* * A Path can be constructed from these {@link Curve} subtypes: {@link SplineCurve}, {@link CubicBezierCurve} and {@link QuadraticBezierCurve}.
* * You can sample a {@link Path#point} and a {@link Curve#tangent} vector on a Path for any given value of {@link Path#t} in the range ````[0..1]````.
* * When you set {@link Path#t} on a Path, its {@link Path#point} and {@link Curve#tangent} properties will update accordingly.
*/
class Path extends Curve {
/**
* @constructor
* @param {Component} [owner] Owner component. When destroyed, the owner will destroy this SectionPlane as well.
* @param {*} [cfg] Path configuration
* @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.
* @param {String []} [cfg.paths=[]] IDs or instances of {{#crossLink "path"}}{{/crossLink}} subtypes to add to this Path.
* @param {Number} [cfg.t=0] Current position on this Path, in range between 0..1.
*/
constructor(owner, cfg = {}) {
super(owner, cfg);
this._cachedLengths = [];
this._dirty = true;
this._curves = []; // Array of child Curve components
this._t = 0;
this._dirtySubs = []; // Subscriptions to "dirty" events from child Curve components
this._destroyedSubs = []; // Subscriptions to "destroyed" events from child Curve components
this.curves = cfg.curves || []; // Add initial curves
this.t = cfg.t; // Set initial progress
}
/**
* Adds a {@link Curve} to this Path.
*
* @param {Curve} curve The {@link Curve} to add.
*/
addCurve(curve) {
this._curves.push(curve);
this._dirty = true;
}
/**
* Sets the {@link Curve}s in this Path.
*
* Default value is ````[]````.
*
* @param {{Array of Spline, Path, QuadraticBezierCurve or CubicBezierCurve}} value.
*/
set curves(value) {
value = value || [];
var curve;
// Unsubscribe from events on old curves
var i;
var len;
for (i = 0, len = this._curves.length; i < len; i++) {
curve = this._curves[i];
curve.off(this._dirtySubs[i]);
curve.off(this._destroyedSubs[i]);
}
this._curves = [];
this._dirtySubs = [];
this._destroyedSubs = [];
var self = this;
function curveDirty() {
self._dirty = true;
}
function curveDestroyed() {
var id = this.id;
for (i = 0, len = self._curves.length; i < len; i++) {
if (self._curves[i].id === id) {
self._curves = self._curves.slice(i, i + 1);
self._dirtySubs = self._dirtySubs.slice(i, i + 1);
self._destroyedSubs = self._destroyedSubs.slice(i, i + 1);
self._dirty = true;
return;
}
}
}
for (i = 0, len = value.length; i < len; i++) {
curve = value[i];
if (utils.isNumeric(curve) || utils.isString(curve)) {
// ID given for curve - find the curve component
var id = curve;
curve = this.scene.components[id];
if (!curve) {
this.error("Component not found: " + _inQuotes(id));
continue;
}
}
var type = curve.type;
if (type !== "xeokit.SplineCurve" &&
type !== "xeokit.Path" &&
type !== "xeokit.CubicBezierCurve" &&
type !== "xeokit.QuadraticBezierCurve") {
this.error("Component " + _inQuotes(curve.id)
+ " is not a xeokit.SplineCurve, xeokit.Path or xeokit.QuadraticBezierCurve");
continue;
}
this._curves.push(curve);
this._dirtySubs.push(curve.on("dirty", curveDirty));
this._destroyedSubs.push(curve.once("destroyed", curveDestroyed));
}
this._dirty = true;
}
/**
* Gets the {@link Curve}s in this Path.
*
* @returns {{Array of Spline, Path, QuadraticBezierCurve or CubicBezierCurve}} the {@link Curve}s in this path.
*/
get curves() {
return this._curves;
}
/**
* Sets the current point of progress along this Path.
*
* Automatically clamps to range ````[0..1]````.
*
* Default value is ````0````.
*
* @param {Number} value The current point of progress.
*/
set t(value) {
value = value || 0;
this._t = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);
}
/**
* Gets the current point of progress along this Path.
*
* Default value is ````0````.
*
* @returns {Number} The current point of progress.
*/
get t() {
return this._t;
}
/**
* Gets point on this Path corresponding to the current value of {@link Path#t}.
*
* @returns {Number[]} The point.
*/
get point() {
return this.getPoint(this._t);
}
/**
* Length of this Path, which is the cumulative length of all {@link Curve}s currently in {@link Path#curves}.
*
* @return {Number} Length of this path.
*/
get length() {
var lens = this._getCurveLengths();
return lens[lens.length - 1];
}
/**
* Gets a point on this Path corresponding to the given progress position.
*
* @param {Number} t Indicates point of progress along this curve, in the range [0..1].
* @returns {Number[]}
*/
getPoint(t) {
var d = t * this.length;
var curveLengths = this._getCurveLengths();
var i = 0, diff, curve;
while (i < curveLengths.length) {
if (curveLengths[i] >= d) {
diff = curveLengths[i] - d;
curve = this._curves[i];
var u = 1 - diff / curve.length;
return curve.getPointAt(u);
}
i++;
}
return null;
}
_getCurveLengths() {
if (!this._dirty) {
return this._cachedLengths;
}
var lengths = [];
var sums = 0;
var i, il = this._curves.length;
for (i = 0; i < il; i++) {
sums += this._curves[i].length;
lengths.push(sums);
}
this._cachedLengths = lengths;
this._dirty = false;
return lengths;
}
_getJSON() {
var curveIds = [];
for (var i = 0, len = this._curves.length; i < len; i++) {
curveIds.push(this._curves[i].id);
}
return {
curves: curveIds,
t: this._t
};
}
/**
* Destroys this Path.
*/
destroy() {
super.destroy();
var i;
var len;
var curve;
for (i = 0, len = this._curves.length; i < len; i++) {
curve = this._curves[i];
curve.off(this._dirtySubs[i]);
curve.off(this._destroyedSubs[i]);
}
}
}
export {Path}