Centripetal Catmull-Rom Splines

Centripetal Catmull-Rom Splines utility code.

Note that you have to move the Leonardo turtle four times to draw your first line!

#spline #utility

Log in to post a comment.

// Centripetal Catmull-Rom Splines. Created by Reinder Nijhoff 2022 - @reindernijhoff
// The MIT License
//
// https://turtletoy.net/turtle/01e218e32f
//
// Catmull-Rom Splines utility code. Based on: 
// https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html
//
// Note that you have to move the Leonardo turtle four times to draw your first line!
//

Canvas.setpenopacity(.75);

const turtle = new Leonardo();

const alpha = 0.5; // min=0, max=1, step=0.001
const tension = 0; // min=0, max=1, step=0.001
const shape = 1; // min=0, max=1, step=1 (Box, Circle)
const border = 0; // min=-50, max=100, step=1
const iterations = 20; // min=4, max=2000, step=1

turtle.alpha = alpha;
turtle.tension = tension;

function walk(i) {
    if (shape === 0) {
        const x = Math.random() * 2 - 1;        
        const y = Math.random() * 2 - 1;
        turtle.goto(x*(100-border), y*(100-border));
    } else {
        const a = Math.PI * Math.random() * 2;
        const r = Math.random() ** 2;
        turtle.goto(Math.sin(a)*(100-border)*r, Math.cos(a)*(100-border)*r);
    }
    return i < iterations;
}


////////////////////////////////////////////////////////////////
// Centripetal Catmull-Rom Splines utility code. Created by Reinder Nijhoff 2022
// https://turtletoy.net/turtle/01e218e32f
// https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html
////////////////////////////////////////////////////////////////

function Leonardo(x, y) {
    function scl2(a,b)   { return [a[0]*b, a[1]*b]; }
    function add2(a,b)   { return [a[0]+b[0], a[1]+b[1]]; }
    function sub2(a,b)   { return [a[0]-b[0], a[1]-b[1]]; }
    function len2(a)     { return Math.sqrt(a[0]**2 + a[1]**2); }
    function dist2(a, b) { return len2(sub2(a,b)); }

    class Leonardo extends Turtle {
        constructor(x, y) {
            super(x, y);
            this.alpha = 0;
            this.tension = 0;
        }
        
        goto(x, y) {
            const p = Array.isArray(x) ? [...x] : [x, y];
            this.path = this.path ? this.path : [];
            this.path.push(p);
            if (this.isdown() && this.path.length >= 4) {
                this.path = this.path.slice(-4);
                this.catmullRomSpline(...this.path);
            } else if (!this.isdown()) {
                this.path = [p];
            }
        }
        
        catmullRomSpline(p0, p1, p2, p3) {
            const subdiv = dist2(p1, p2)|0 + 1;
            
            const t01 = Math.pow(dist2(p0, p1), this.alpha);
            const t12 = Math.pow(dist2(p1, p2), this.alpha);
            const t23 = Math.pow(dist2(p2, p3), this.alpha);
            
            const m1 = scl2( add2(sub2(p2, p1), scl2( sub2(scl2( sub2(p1, p0), 1 / t01), scl2( sub2(p2, p0), 1 / (t01 + t12))), t12)), 1 - this.tension);
            const m2 = scl2( add2(sub2(p2, p1), scl2( sub2(scl2( sub2(p3, p2), 1 / t23), scl2( sub2(p3, p1), 1 / (t12 + t23))), t12)), 1 - this.tension);
                
            const a = add2( add2( scl2(sub2(p1, p2), 2), m1), m2);
            const b = sub2( sub2( sub2( scl2(sub2(p1, p2), -3), m1), m1), m2);
            
            if (this.isdown() && (this.x() != p1[0] || this.y() != p1[1])) {
                this.penup();
                super.goto(p1);
                this.pendown();
            }
            
            for (let i=0; i<subdiv; i++) {
                const t = i/subdiv;
                super.goto(add2( add2( add2 ( scl2(a, t * t * t), scl2(b, t * t)), scl2(m1, t)), p1));
            }
            super.goto(p2);
        }
    }
    return new Leonardo(x,y);
}