Supershapes / Superformula

As seen here: paulbourke.net/geometry/supershape
Export to GIF as different frames to rotate in 3D.

Log in to post a comment.

// LL 2021

// Supershapes / Superformula: http://paulbourke.net/geometry/supershape/

// Variations:
// https://turtletoy.net/turtle/6976a7ffc8#p_m=10,p_n1=100,p_n2=100,p_n3=99.98,p_a=0.76,p_b=0.74,t_m=10,t_n1=100,t_n2=100,t_n3=100,t_a=0.3,t_b=0.42,density=446,scale=165.97,loops=2,opacity=0.47,mapping=0,noisy=0
// https://turtletoy.net/turtle/6976a7ffc8#p_m=1,p_n1=1,p_n2=1,p_n3=1,p_a=1.03,p_b=1.62,t_m=1,t_n1=7.85,t_n2=7.85,t_n3=8.34,t_a=1.67,t_b=1.67,density=1593,scale=31.46,loops=20,opacity=0.08,mapping=0,noisy=0
// https://turtletoy.net/turtle/6976a7ffc8#p_m=6,p_n1=-0.68,p_n2=22.77,p_n3=0.75,p_a=2.99,p_b=0.64,t_m=3,t_n1=65.9,t_n2=0.35,t_n3=49.59,t_a=1.05,t_b=0.28,density=818,scale=40,loops=1,opacity=-0.1,mapping=1,noisy=0
// https://turtletoy.net/turtle/6976a7ffc8#p_m=2,p_n1=9.29,p_n2=9.29,p_n3=9.66,p_a=1,p_b=1,t_m=4.8,t_n1=10.16,t_n2=10.16,t_n3=10.16,t_a=1.91,t_b=0.55,density=1081,scale=40,loops=2,opacity=0.1,mapping=0,noisy=0
// https://turtletoy.net/turtle/6976a7ffc8#p_m=10,p_n1=63.9,p_n2=63.41,p_n3=63.41,p_a=1,p_b=1,t_m=10,t_n1=19.99,t_n2=19.86,t_n3=19.86,t_a=1.91,t_b=0.55,density=1049,scale=29.24,loops=1,opacity=0.1,mapping=1,noisy=0

const turtle = new Turtle();

const p_m  = 0; // min=0 max=10 step=0.1
const p_n1 = 1; // min=-1 max=100 step=0.01
const p_n2 = 1; // min=-1 max=100 step=0.01
const p_n3 = 1; // min=-1 max=100 step=0.01
const p_a  = 1; // min=0.01 max=10 step=0.01
const p_b  = 1; // min=0.01 max=10 step=0.01
const t_m  = 0; // min=0 max=10 step=0.1
const t_n1 = 1; // min=-1 max=100 step=0.01
const t_n2 = 1; // min=-1 max=100 step=0.01
const t_n3 = 1; // min=-1 max=100 step=0.01
const t_a  = 1; // min=0.01 max=10 step=0.01
const t_b  = 1; // min=0.01 max=10 step=0.01

const density = 500; // min=1 max=2000 step=1
const scale = 40; // min=0 max=200 step=0.01
const loops = 1; // min=1 max=20 step=0.1
const opacity = 0.1; // min=-1 max=1 step=0.01
const mapping = 1; // min=0 max=1 step=1 (Spherical,Toroidal)
const noisy = 0; // min=0 max=1 step=1 (No,Yes)

const camera_angle = 0.14; /// min=0 max=1 step=0.01
const camera_height = 150; /// min=0 max=500 step=1
const camera_distance = 400;
const exaggeration = 1; /// min=0 max=2 step=0.01

const shuffle_i = 0; /// min=0 max=1 step=1 (No,Yes)
let shuffled_i;

Canvas.setpenopacity(opacity);

function walk(i, t) {
    if (i==0) {
        reset_random();
        init_shuffle();
        cameraPos = [camera_distance * Math.cos(Math.PI * 2 * (camera_angle + t)), camera_height, camera_distance * Math.sin(Math.PI * 2 * (camera_angle + t))];
        viewProjectionMatrix = setupCamera(cameraPos, cameraLookAt);
    }
    const ii = shuffled_i.shift();
    const ip = (ii % density) + (noisy ? random() : 0);
    const it = Math.floor(ii / density) + (noisy ? random() : 0);
    const phi = -Math.PI/2 + Math.PI * ip / density * loops * (mapping + 1);
    const theta = -Math.PI + Math.PI * 2 * it / density * loops;
    const p_r = getR(p_m, p_n1, p_n2, p_n3, p_a, p_b, phi);
    const t_r = getR(t_m, t_n1, t_n2, t_n3, t_a, t_b, theta);
    var x, y, z;
    if (mapping == 0) { // Spherical
        x = p_r * Math.cos(phi) * t_r * Math.cos(theta);
        y = p_r * Math.sin(phi) * t_r * Math.cos(theta);
        z = t_r * Math.sin(theta);
    } else { // Toroidal
        x = Math.cos(phi) * (p_r + t_r * Math.cos(theta));
        y = Math.sin(phi) * (p_r + t_r * Math.cos(theta));
        z = t_r * Math.sin(theta);
    }
    dot(x, y, z);

    return (i+1) < density * density;
}

function dot(x, y, z) {
    const max_r = 0.1, min_r = 0.1, step = 1;

    [x, y] = project(x * scale, y * scale, z * scale);

    if (Math.abs(x-max_r) <= 100 && Math.abs(y-max_r) <= 100) {
        for (var r = max_r; r >= min_r; r -= step) {
            turtle.jump(x, y-r);
            turtle.circle(r);
        }
    }
}

function getR(m, n1, n2, n3, a, b, angle)
{
    const t1 = Math.pow(Math.abs(Math.cos(m * angle / 4) / a), n2);
    const t2 = Math.pow(Math.abs(Math.sin(m * angle / 4) / b), n3);
    var r = Math.pow(t1 + t2, 1/n1);
    if (Math.abs(r) != 0) { r = 1 / r; }
    return r;
}

function project(x, y, z) {
    const p = transform4([x, z * -exaggeration, y, 1], viewProjectionMatrix);
    const s = 50;
    return [ p[0]/p[3]*s, -p[1]/p[3]*s ];
}

function init_shuffle() {
    shuffled_i = Array.from({length: density * density}, (_,id) => id);
    if (!shuffle_i) return;
    var current_index = shuffled_i.length,  random_index;
    while (current_index > 0) {
        random_index = Math.floor(mrandom() * current_index--);
        [ shuffled_i[current_index], shuffled_i[random_index] ] = [ shuffled_i[random_index], shuffled_i[current_index] ];
    }
}

////////////////////////////////////////////////////////////////
// Projection from reinder's https://turtletoy.net/turtle/b3acf08303
let cameraPos, viewProjectionMatrix;
const cameraLookAt = [0,0,0];
function setupCamera(t,e){const m=lookAt4m(t,e,[0,1,0]),n=perspective4m(.25,1);return multiply4m(n,m)}
function lookAt4m(o,n,r){const s=new Float32Array(16);n=normalize3(sub3(o,n)),r=normalize3(cross3(r,n));const t=normalize3(cross3(n,r));return s[0]=r[0],s[1]=t[0],s[2]=n[0],s[3]=0,s[4]=r[1],s[5]=t[1],s[6]=n[1],s[7]=0,s[8]=r[2],s[9]=t[2],s[10]=n[2],s[11]=0,s[12]=-(r[0]*o[0]+r[1]*o[1]+r[2]*o[2]),s[13]=-(t[0]*o[0]+t[1]*o[1]+t[2]*o[2]),s[14]=-(n[0]*o[0]+n[1]*o[1]+n[2]*o[2]),s[15]=1,s}
function perspective4m(t,n){const e=new Float32Array(16).fill(0,0);return e[5]=1/Math.tan(t/2),e[0]=e[5]/n,e[10]=e[11]=-1,e}
function multiply4m(t,r){const l=new Float32Array(16);for(let n=0;16>n;n+=4)for(let o=0;4>o;o++)l[n+o]=r[n+0]*t[0+o]+r[n+1]*t[4+o]+r[n+2]*t[8+o]+r[n+3]*t[12+o];return l}
function transform4(r,n){const t=new Float32Array(4);for(let o=0;4>o;o++)t[o]=n[o]*r[0]+n[o+4]*r[1]+n[o+8]*r[2]+n[o+12];return t}
function normalize3(a) { return scale3(a,1/len3(a)); }
function scale3(a,b) { return [a[0]*b,a[1]*b,a[2]*b]; }
function len3(a) { return Math.sqrt(dot3(a,a)); }
function sub3(a,b) { return [a[0]-b[0],a[1]-b[1],a[2]-b[2]]; }
function dot3(a,b) { return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; }
function cross3(a,b) { return [a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]]; }

// Cached random for animations
function mrandom() { return Math.random(); }
function random() { while (rand_index >= rand_cache.length) rand_cache.push(mrandom()); return rand_cache[rand_index++]; }
function reset_random() { rand_index = 0; }
const rand_cache = [];
var rand_index = 0;