three
Version:
JavaScript 3D library
297 lines (187 loc) • 5.73 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 };