Supershapes / Superformula #2

As seen here: paulbourke.net/geometry/supershape
Now with polygons.

Log in to post a comment.

// Forked from "Supershapes / Superformula" by llemarie
// https://turtletoy.net/turtle/6976a7ffc8

// LL 2021

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

// Variations:
// https://turtletoy.net/turtle/df87f9e91d#p_m=4,p_n1=100,p_n2=100,p_n3=100,p_a=1,p_b=1,t_m=4,t_n1=100,t_n2=100,t_n3=100,t_a=1,t_b=1,density=200,scale=40,loops=2.3,edges=6,opacity=0.5,mapping=0,noisy=1,checkerboard=0
// https://turtletoy.net/turtle/df87f9e91d#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=200,scale=29.24,loops=1,opacity=0.5,mapping=1,noisy=0
// https://turtletoy.net/turtle/df87f9e91d#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=200,scale=40,loops=1,edges=15,opacity=0.5,mapping=1,noisy=0,checkerboard=0
// https://turtletoy.net/turtle/df87f9e91d#p_m=3,p_n1=99.24,p_n2=99.24,p_n3=98.99,p_a=1,p_b=1,t_m=3,t_n1=98.74,t_n2=98.86,t_n3=98.86,t_a=1.91,t_b=0.55,density=322,scale=40,loops=2,edges=15,opacity=0.5,mapping=0,noisy=1,checkerboard=1
// https://turtletoy.net/turtle/df87f9e91d#p_m=8.8,p_n1=21.85,p_n2=39.64,p_n3=3.94,p_a=0.47,p_b=1.8,t_m=10.7,t_n1=17.37,t_n2=4.06,t_n3=4.06,t_a=1.83,t_b=1.3,density=196,scale=40.57,loops=1,edges=3,opacity=0.5,mapping=1,noisy=0,checkerboard=0
// https://turtletoy.net/turtle/df87f9e91d#p_m=17.6,p_n1=0.58,p_n2=8.29,p_n3=1,p_a=1,p_b=1,t_m=0,t_n1=1,t_n2=1,t_n3=1,t_a=1,t_b=1,density=200,scale=37.12,loops=1,edges=5,opacity=0.54,mapping=1,noisy=0,checkerboard=0

const turtle = new Turtle();

const p_m  = 0; // min=0 max=100 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=100 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 = 100; // min=1 max=200 step=1
const scale = 40; // min=0 max=200 step=0.01
const loops = 1; // min=1 max=20 step=0.1
const edges = 6; // min=0 max=15 step=1
const opacity = 0.5; // 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 checkerboard = 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

Canvas.setpenopacity(opacity);

let quads;
const style = 1; /// min=0 max=1 step=1 (Lines (fast),Polygons (slow))
let polygons;

function walk(i, t) {
    if (i==0) {
        reset_random();
        polygons = new Polygons();
        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);

        quads = [];
        for (var j=0; j<density*density; j++) {
            if (checkerboard) if ((j&1)^(((j/density)|0)&1)) continue;
            const points = [];
            for (var iy=0; iy<2; iy++) {
                for (var ix=0; ix<2; ix++) {
                    const ip = ((j+ix) % density) + (noisy ? random() : 0)*0.1;
                    const it = Math.floor(j / density + iy) + (noisy ? random() : 0)*0.1;
                    const phi = -Math.PI/2 + Math.PI * ip / density * loops * (mapping + 1);
                    const theta = -Math.PI + Math.PI * 2 * it / density * loops;
                    points.push(getPoint(phi, theta));
                }
            }
            quads.push(points);
        }
        
        sortQuads();
    }

    const quad = quads.shift();
    drawQuad(quad);
    
    if (quads.length == 0) {
        drawPoints([[-110, -110], [110, -110], [110, 110], [-110, 110]], 0.25)
    }

    return quads.length > 0;
}

function getPoint(phi, theta) {
    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);
    }
    return [x, y, z];
}

function dot(x, y, z) {
    const max_r = 0.5, min_r = 0.1, step = 0.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 sortQuads() {
    for (var i1=0; i1<quads.length; i1++) {
        var min_i = i1;
        var min_dist = getDistance(quads[i1]);
        for (var i2=i1+1; i2<quads.length; i2++) {
            const dist = getDistance(quads[i2]);
            if (dist < min_dist) {
                min_i = i2;
                min_dist = dist;
            }
        }
        if (i1 != min_i) {
            [quads[i1], quads[min_i]] = [quads[min_i], quads[i1]]
        }
    }
}

function getDistance(quad) {
    return Math.min(quad[0][2], Math.min(quad[1][2], Math.min(quad[2][2], quad[3][2]))); // TODO: that's not even close to correct.
}

function drawQuad(quad) {
    var x, y;
    const points = [];
    [0, 1, 3, 2, 0].forEach(j => {
        [x, y] = project(quad[j][0] * scale, quad[j][1] * scale, quad[j][2] * scale);
        points.push([x, y]);
    });
    drawPoints(points);
}

function drawPoints(dpoints, hatching=0) {
    if (style == 0) {
        turtle.jump(dpoints[dpoints.length-1]);
        dpoints.forEach(p=>turtle.goto(p));
    } else {
        const p1 = polygons.create();
        p1.addPoints(...dpoints);
        //p1.addOutline();
        if (edges&1) p1.addSegments(p1.cp[0], p1.cp[1]);
        if (edges&2) p1.addSegments(p1.cp[1], p1.cp[2]);
        if (edges&4) p1.addSegments(p1.cp[2], p1.cp[3]);
        if (edges&8) p1.addSegments(p1.cp[3], p1.cp[0]);
        if (hatching) p1.addHatching(-Math.PI / 4, hatching);
        polygons.draw(turtle, p1, true);
    }
}

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 ];
}

////////////////////////////////////////////////////////////////
// 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;

////////////////////////////////////////////////////////////////
// Polygon Clipping utility code - Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/a5befa1f8d
////////////////////////////////////////////////////////////////
function Polygons(){let t=[];const s=class{constructor(){this.cp=[],this.dp=[],this.aabb=[]}addPoints(...t){let s=1e5,e=-1e5,h=1e5,i=-1e5;(this.cp=[...this.cp,...t]).forEach(t=>{s=Math.min(s,t[0]),e=Math.max(e,t[0]),h=Math.min(h,t[1]),i=Math.max(i,t[1])}),this.aabb=[(s+e)/2,(h+i)/2,(e-s)/2,(i-h)/2]}addSegments(...t){t.forEach(t=>this.dp.push(t))}addOutline(){for(let t=0,s=this.cp.length;t<s;t++)this.dp.push(this.cp[t],this.cp[(t+1)%s])}draw(t){for(let s=0,e=this.dp.length;s<e;s+=2)t.jump(this.dp[s]),t.goto(this.dp[s+1])}addHatching(t,e){const h=new s;h.cp.push([-1e5,-1e5],[1e5,-1e5],[1e5,1e5],[-1e5,1e5]);const i=Math.sin(t)*e,n=Math.cos(t)*e,a=200*Math.sin(t),p=200*Math.cos(t);for(let t=.5;t<150/e;t++)h.dp.push([i*t+p,n*t-a],[i*t-p,n*t+a]),h.dp.push([-i*t+p,-n*t-a],[-i*t-p,-n*t+a]);h.boolean(this,!1),this.dp=[...this.dp,...h.dp]}inside(t){let s=0;for(let e=0,h=this.cp.length;e<h;e++)this.segment_intersect(t,[.13,-1e3],this.cp[e],this.cp[(e+1)%h])&&s++;return 1&s}boolean(t,s=!0){if(s&&Math.abs(this.aabb[0]-t.aabb[0])-(t.aabb[2]+this.aabb[2])>=0&&Math.abs(this.aabb[1]-t.aabb[1])-(t.aabb[3]+this.aabb[3])>=0)return this.dp.length>0;const e=[];for(let h=0,i=this.dp.length;h<i;h+=2){const i=this.dp[h],n=this.dp[h+1],a=[];for(let s=0,e=t.cp.length;s<e;s++){const h=this.segment_intersect(i,n,t.cp[s],t.cp[(s+1)%e]);!1!==h&&a.push(h)}if(0===a.length)s===!t.inside(i)&&e.push(i,n);else{a.push(i,n);const h=n[0]-i[0],p=n[1]-i[1];a.sort((t,s)=>(t[0]-i[0])*h+(t[1]-i[1])*p-(s[0]-i[0])*h-(s[1]-i[1])*p);for(let h=0;h<a.length-1;h++)(a[h][0]-a[h+1][0])**2+(a[h][1]-a[h+1][1])**2>=.001&&s===!t.inside([(a[h][0]+a[h+1][0])/2,(a[h][1]+a[h+1][1])/2])&&e.push(a[h],a[h+1])}}return(this.dp=e).length>0}segment_intersect(t,s,e,h){const i=(h[1]-e[1])*(s[0]-t[0])-(h[0]-e[0])*(s[1]-t[1]);if(0===i)return!1;const n=((h[0]-e[0])*(t[1]-e[1])-(h[1]-e[1])*(t[0]-e[0]))/i,a=((s[0]-t[0])*(t[1]-e[1])-(s[1]-t[1])*(t[0]-e[0]))/i;return n>=0&&n<=1&&a>=0&&a<=1&&[t[0]+n*(s[0]-t[0]),t[1]+n*(s[1]-t[1])]}};return{list:()=>t,create:()=>new s,draw:(s,e,h=!0)=>{for(let s=0;s<t.length&&e.boolean(t[s]);s++);e.draw(s),h&&t.push(e)}}}