Source: math/Spline.js

/**
 *  @class Spline
 *  @memberof SQR
 *
 *  @description Represents a spline composed of multiple cubic beziers
 */
SQR.Spline = function() {

	// Bezier paths generated from segments/controlPoints in create() 
	var paths = [];

	// Points from the input source defining the path of the spline
	var segments = [];

	// Each segment has two control points (the "hands" coming out fo paths)
	var controlPoints = [];

	var s = {};

	var _tv1, _tv2;

	var getcontrolPoints = function(point, previous, next, c1, c2, smoothness) {
		var vab = _tv1.sub(point, previous).neg();
		var vcb = _tv2.sub(point, next);
		var d = (smoothness > 1) ? smoothness : smoothness * Math.min(vab.mag(), vcb.mag());
		c1.set().add(vab, vcb).norm().mul(d);
		c2.copyFrom(c1).neg();
		c1.add(c1, point);
		c2.add(c2, point);
	}

	s.addSegment = function(p) {
		var v;

		if(p.x !== undefined) {
			v = p;
		} else {
			var a = arguments, l = a.length;
			if(l == 2) v = new SQR.V2(a[0], a[1]);
			else if(l == 3) v = new SQR.V3(a[0], a[1], a[2]);
		}

		segments.push(v);
		controlPoints.push(v.clone(), v.clone());

		if(!_tv1) {
			_tv1 = segments[0].clone();
			_tv2 = segments[0].clone();
		}

		return s;
	}

	s.create = function(smoothness, close) {

		if(segments.length < 2) return segments;

		smoothness = (smoothness !== null) ? smoothness : 0.5;
		paths.length = 0;
		var firstPoint, firstControlPoint;

		var sg = segments, cp = controlPoints, sl = segments.length;
		
		for(var i = 0; i < sl; i++) {
			var si = sg[i];
			var c1 = cp[i * 2].set();
			var c2 = cp[i * 2 + 1].set();

			var a = (i == 0) ? sg[sl-1] : sg[i-1];
			var b = (i == sl-1) ? sg[0] : sg[i+1];

			getcontrolPoints(si, a, b, c1, c2, smoothness);
			cp.push(c1, c2);
		}

		for(var i = 0; i < sl-1; i++) {
			var a = sg[i];
			var b = (i == sl-1) ? sg[0] : sg[i+1];

			var c1 = (i == 0 && !close) ? a : cp[i * 2 + 1];
			var c2 = (i == sl-2 && !close) ? b : cp[i * 2 + 2];

			var c = new SQR.Bezier(a, c1, c2, b);
			paths.push(c);
		}

		if(close) {
			var c = new SQR.Bezier(sg[sl-1], cp[(sl-1)*2+1], cp[0], sg[0]);
			paths.push(c);
		}

		s.smoothness = smoothness;
		s.close = s.close;

		return s;
	}

	s.valueAt = function(t, v) {
		if(t == 1) t = 0.999999;
		t = t % 1;
		var tf = t * paths.length;
		return paths[tf | 0].valueAt(tf % 1, v);
	}

	s.bezierAt = function(t) {
		if(t == 1) t = 0.999999;
		t = t % 1;
		var tf = t * paths.length;
		return paths[tf | 0];
	}

	s.velocityAt = function(t, v) {
		if(t == 1) t = 0.999999;
		t = t % 1;
		var tf = t * paths.length;
		return paths[tf | 0].velocityAt(tf % 1, v);
	}

	s.matrixAt = function(t, m) {
		if(t == 0) t = SQR.EPSILON;
		if(t == 1) t = 1 - SQR.EPSILON;
		t = t % 1;
		var tf = t * paths.length;
		return paths[tf | 0].matrixAt(tf % 1, m);
	}

	Object.defineProperty(s, 'segments', {
		get: function() { 
			return segments; 
		}
	});

	Object.defineProperty(s, 'paths', {
		get: function() { 
			return paths; 
		}
	});

	return s;

}