Skip to content

Instantly share code, notes, and snippets.

Created March 9, 2017 22:46
Show Gist options
  • Save anonymous/ace6ed8cc3130bcdd261ba9cd27b8ab9 to your computer and use it in GitHub Desktop.
Save anonymous/ace6ed8cc3130bcdd261ba9cd27b8ab9 to your computer and use it in GitHub Desktop.
Catmull-Rom Spline
<canvas id="canv" height="3000" width="3000"></canvas>
const tau = 1;
function matrix(...rows) {
this.rows = rows;
this.cols = [];
for (let i = 0; i < rows[0].length; i++) {
let col = [];
for (let j = 0; j < this.rows.length; j++) {
col.push(this.rows[j][i]);
}
this.cols.push(col);
}
}
function dot(v1, v2) {
var sum = 0;
for (let i = 0; i < v1.length; i++) {
sum += v1[i] * v2[i];
}
return sum;
}
matrix.prototype.print = function() {
for (let i = 0; i < this.rows.length; i++) {
var str = "[";
for (let j = 0; j < this.cols.length; j++) {
str += this.rows[i][j];
if (j < this.cols.length - 1) str += " ";
}
str += "]";
console.log(str);
}
}
matrix.prototype.mul = function(other) {
var result = [];
for (let i = 0; i < this.rows.length; i++) {
result.push([]);
for (let j = 0; j < other.cols.length; j++) {
result[i][j] = dot(this.rows[i], other.cols[j]);
}
}
return new matrix(...result);
}
matrix.prototype.scalarMul = function(scalar) {
for (let i = 0; i < this.rows.length; i++) {
for (let j = 0; j < this.cols.length; j++) {
this.rows[i][j] = this.rows[i][j] * scalar;
}
}
}
var catmullRomMatrix = new matrix([0, 2, 0, 0], [-tau, 0, tau, 0], [2*tau, tau-6, -2*(tau-3), -tau], [-tau, 4-tau, tau-4, tau]);
catmullRomMatrix.scalarMul(.5);
function spline(controlPoints){
this.points = controlPoints;
this.functionCache = [];
for (i = 0; i < this.points.length - 3; i++) {
this.functionCache.push([]);
}
}
spline.prototype.getHermiteFunction = function(p, index) {
if (this.functionCache[p][index] == undefined) {
this.functionCache[p][index] = catmullRomMatrix.mul(new matrix([this.points[p][index]],
[this.points[p + 1][index]], [this.points[p+2][index]], [this.points[p+3][index]]));
}
return this.functionCache[p][index];
}
spline.prototype.evaluate = function(rawT) {
var i = Math.floor(rawT);
var t = rawT % 1;
if (i + 3 >= this.points.length) return false;
var cx = this.getHermiteFunction(i, 0);
var cy = this.getHermiteFunction(i, 1);
return [cx.rows[0][0] + t * cx.rows[1][0] + t * t * cx.rows[2][0] + t * t * t * cx.rows[3][0],
cy.rows[0][0] + t * cy.rows[1][0] + t * t * cy.rows[2][0] + t * t * t * cy.rows[3][0]];
}
var initialTime = Date.now();
var canv = document.getElementById("canv");
var ctx = canv.getContext("2d");
var ctrlPoints = [[30, 60], [70, 130], [110, 50], [150, 150], [200, 220], [300, 100], [450, 200], [520, 100]];
var spline = new spline(ctrlPoints);
function draw() {
let t = Date.now() - initialTime;
t /= 500;
ctx.clearRect(0, 0, canv.width, canv.height);
renderSpline();
ctx.fillStyle = "rgb(200, 0, 1)";
ctrlPoints.forEach(function(point){
ctx.beginPath();
ctx.moveTo(point[0], point[1]);
ctx.arc(point[0], point[1], 5, 0, Math.PI * 2, true);
ctx.fill();
});
let splinePoint = spline.evaluate(t);
if (splinePoint == false) {
initialTime = Date.now();
} else {
ctx.fillStyle = "rgb(0, 200, 0)";
ctx.beginPath();
ctx.moveTo(splinePoint[0], splinePoint[1]);
ctx.arc(splinePoint[0], splinePoint[1], 5, 0, Math.PI * 2, true);
ctx.fill();
}
window.requestAnimationFrame(draw);
}
function renderSpline() {
let t = 0;
while (t < ctrlPoints.length) {
let splinePoint = spline.evaluate(t);
ctx.fillStyle = "rgb(50, 50, 200)";
ctx.beginPath();
ctx.moveTo(splinePoint[0], splinePoint[1]);
ctx.arc(splinePoint[0], splinePoint[1], 1, 0, Math.PI * 2, true);
ctx.fill();
t += .01;
}
}
renderSpline();
draw();
body{
margin: 0;
width:100%;
padding: 0;
overflow: hidden;
background:hsla(0,5%,10%,1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment