• Spline1D.js

  • ¶

    Camtull-Rom spline implementation
    Inspired by code from Tween.js

  • ¶

    Example use

    var points = [ 
      -2, 
      -1, 
       1, 
       2
    ];
    
    var spline = new Spline1D(points);
    
    spline.getPointAt(0.25);
    
  • ¶

    Reference

  • ¶

    Spline1D ( points, [ closed ] )

    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;
    }
  • ¶

    getPoint ( t )

    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 );
    }
  • ¶

    addPoint ( p )

    Adds point to the spline

    p - point to be added { Vec3 }

    Spline1D.prototype.addPoint = function ( p ) {
      this.dirtyLength = true;
      this.points.push(p)
    }
  • ¶

    getPointAt ( d )

    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);
    }
  • ¶

    getPointAtIndex ( i )

    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;
      }
    }
  • ¶

    getNumPoints ( )

    Return number of base points in the spline

    Spline1D.prototype.getNumPoints = function() {
      return this.points.length;
    }
  • ¶

    getLength ( )

    Returns the total length of the spline.

    Spline1D.prototype.getLength = function() {
      if (this.dirtyLength) {
        this.precalculateLength();
      }
      return this.length;
    }
  • ¶

    precalculateLength ( )

    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;
    }
  • ¶

    close ( )

    Closes the spline. It will form a closed now.

    Spline1D.prototype.close = function( ) {
      this.closed = true;
    }
  • ¶

    isClosed ( )

    Returns true if spline is closed (forms a closed) { Boolean }

    Spline1D.prototype.isClosed = function() {
      return this.closed;
    }
  • ¶

    interpolate ( p0, p1, p2, p3, t)

    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;