function Spline1D(points, closed) {
this.points = points || [];
this.dirtyLength = true;
this.closed = closed || false;
this.samplesCount = 2000;
}
var points = [
-2,
-1,
1,
2
];
var spline = new Spline1D(points);
spline.getPointAt(0.25);
points
- { Array of Vec3 } = [ ]closed
- is the spline a closed loop? { Boolean } = false
function Spline1D(points, closed) {
this.points = points || [];
this.dirtyLength = true;
this.closed = closed || false;
this.samplesCount = 2000;
}
Gets position based on t-value. It is fast, but resulting points will not be evenly distributed.
t
- { Number } <0, 1>
Spline1D.prototype.getPoint = function ( t ) {
if (this.closed) {
t = (t + 1 ) % 1;
}
else {
t = Math.max(0, Math.min(t, 1));
}
var points = this.points;
var len = this.closed ? points.length : points.length - 1;
var point = t * len;
var intPoint = Math.floor( point );
var weight = point - intPoint;
var c0, c1, c2, c3;
if (this.closed) {
c0 = (intPoint - 1 + points.length ) % points.length;
c1 = intPoint % points.length;
c2 = (intPoint + 1 ) % points.length;
c3 = (intPoint + 2 ) % points.length;
}
else {
c0 = intPoint == 0 ? intPoint : intPoint - 1;
c1 = intPoint;
c2 = intPoint > points.length - 2 ? intPoint : intPoint + 1;
c3 = intPoint > points.length - 3 ? intPoint : intPoint + 2;
}
return this.interpolate( points[ c0 ], points[ c1 ], points[ c2 ], points[ c3 ], weight );
}
Spline1D.prototype.addPoint = function ( p ) {
this.dirtyLength = true;
this.points.push(p)
}
Gets position based on d-th of total length of the curve. Precise but might be slow at the first use due to need to precalculate length.
d
- { Number } <0, 1>
Spline1D.prototype.getPointAt = function ( d ) {
if (this.closed) {
d = (d + 1 ) % 1;
}
else {
d = Math.max(0, Math.min(d, 1));
}
if (this.dirtyLength) {
this.precalculateLength();
}
TODO: try binary search
var k = 0;
for(var i=0; i<this.accumulatedLengthRatios.length; i++) {
if (this.accumulatedLengthRatios[i] > d - 1/this.samplesCount) {
k = this.accumulatedRatios[i];
break;
}
}
return this.getPoint(k);
}
Returns position of i-th point forming the curve
i
- { Number } <0, Spline1D.points.length)
Spline1D.prototype.getPointAtIndex = function ( i ) {
if (i < this.points.length) {
return this.points[i];
}
else {
return null;
}
}
Spline1D.prototype.getNumPoints = function() {
return this.points.length;
}
Spline1D.prototype.getLength = function() {
if (this.dirtyLength) {
this.precalculateLength();
}
return this.length;
}
Goes through all the segments of the curve and calculates total length and the ratio of each segment.
Spline1D.prototype.precalculateLength = function() {
var step = 1/this.samplesCount;
var k = 0;
var totalLength = 0;
this.accumulatedRatios = [];
this.accumulatedLengthRatios = [];
this.accumulatedLengths = [];
var point;
var prevPoint;
var k = 0;
for(var i=0; i<this.samplesCount; i++) {
prevPoint = point;
point = this.getPoint(k);
if (i > 0) {
var len = Math.sqrt(1 + (point - prevPoint)*(point - prevPoint));
totalLength += len;
}
this.accumulatedRatios.push(k);
this.accumulatedLengths.push(totalLength)
k += step;
}
for(var i=0; i<this.samplesCount; i++) {
this.accumulatedLengthRatios.push(this.accumulatedLengths[i] / totalLength);
}
this.length = totalLength;
this.dirtyLength = false;
}
Spline1D.prototype.close = function( ) {
this.closed = true;
}
Spline1D.prototype.isClosed = function() {
return this.closed;
}
Helper function to calculate Catmul-Rom spline equation
p0
- previous value { Number }p1
- current value { Number }p2
- next value { Number }p3
- next next value { Number }t
- parametric distance between p1 and p2 { Number } <0, 1>
Spline1D.prototype.interpolate = function(p0, p1, p2, p3, t) {
var v0 = ( p2 - p0 ) * 0.5;
var v1 = ( p3 - p1 ) * 0.5;
var t2 = t * t;
var t3 = t * t2;
return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;
}
module.exports = Spline1D;