Fractal 3D contour πŸΎπŸ’―πŸ’

Quaternion Julia Fractals:
paulbourke.net/fractals/quatjulia/

Log in to post a comment.

// LL 2021

const turtle = new Turtle();

const precision = 750; // min=10 max=1500 step=1
const iterations = 16; // min=1 max=100 step=1
const scene = 1; // min=0 max=5 step=1
const scene0_radius = 1; // min=0 max=1 step=0.01
const scene0_angle = 0; // min=0 max=1 step=0.01
const fractal = 0; // min=0 max=1 step=1 (Square,Cube)
const cut = 0; // min=0 max=1 step=1 (No,Yes)
const trap = 0; // min=0 max=2 step=1 (No,Yes, Hide fractal)

function walk(i, t) {
    if (i==0) {
        turtle.jump(0, -95); turtle.circle(95);
        turtle.jump(0, -97); turtle.circle(97);
        setScene(t);
    }
    return contour(i, t, getFractal);
}

function setScene(t) {
    const scenes = [
            [0, 0, 0, 0],
            [-1, 0.2, 0, 0],
            [-0.213, -0.0410, -0.563, -0.560],
            [-0.291,-0.399,0.339,0.437],
            [-0.09, 0.27, 0.68, -0.27],
            [-0.125, -0.256, 0.847, 0.0895]
        ];

    scenes[0][0] = scene0_radius * 1 * Math.cos(scene0_angle);
    scenes[0][1] = scene0_radius * 2 * Math.sin(scene0_angle);
    scenes[0][2] = scene0_radius * 2 * Math.cos(scene0_angle * 2) * Math.sin(scene0_angle * 2);
    scenes[0][3] = scene0_radius * 3 * Math.cos(scene0_angle * 4) * Math.sin(scene0_angle * 4);

    t = (t * scenes.length + scene) % scenes.length;
    const a = Math.floor(t);
    const b = (a + 1) % scenes.length;
    const f = t - a;

    C = mix4(scenes[a], scenes[b], f);
}

function GetRayDir(uv2, p3, l3, z) {
    var f3 = normalize3(sub3(l3, p3));
    var r3 = normalize3(cross3([0,1,0], f3));
    var u3 = cross3(f3, r3);
    var c3 = mul3(f3, z);
    var i3 = add3(add3(c3, mul3(r3, uv2[0])), mul3(u3, uv2[1]));
    var d3 = normalize3(i3);
    return d3;
}

function getRay(p2) {
    // Convert to -1 to 1
    var uv2 = [ p2[0] / 100, p2[1] / 100 ];

    // Look at
    var l3 = [ 2, 0, 0 ];

    // Ray origin
    var ro3 = [ 4, 2, -7 ];

    // Ray direction
    var rd3 = GetRayDir(uv2, ro3, l3, 1.);
    
    return [ro3, rd3];
}

function sum4(a, b)  {
    return [a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]];
}

function mul4(a, f)  {
    return [a[0] * f, a[1] * f, a[2] * f, a[3] * f];
}

function qLength2(q) {
    return q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3];
}

function qSquare(q) {
    const x = q[0] * q[0] - q[1] * q[1] - q[2] * q[2] - q[3] * q[3];
    const y = 2.0 * q[0] * q[1];
    const z = 2.0 * q[0] * q[2];
    const w = 2.0 * q[0] * q[3];
    return [x, y, z, w];
}

function qCube(q) {
    const qx2 = q[0] * q[0];
    const qy2 = q[1] * q[1];
    const qz2 = q[2] * q[2];
    const qw2 = q[3] * q[3];

    const x = q[0] * (qx2 - 3.0 * qy2 - 3.0 * qz2 - 3.0 * qw2);
    const y = q[1] * (3.0 * qx2 - qy2 - qz2 - qw2);
    const z = q[2] * (3.0 * qx2 - qy2 - qz2 - qw2);
    const w = q[3] * (3.0 * qx2 - qy2 - qz2 - qw2);

    return [x, y, z, w];
}

function sdFractal(p3)
{
    const q3 = mul3(p3, 0.2);

    var z4 = [q3[0], -q3[1], q3[2], 0];
    var dz2 = 1.0;
    var m2  = 0;
    var i   = 0;
    var o   = 1000;

    for (i=0; i<iterations; i++) {
        if (fractal) {
            // z' = 3zΒ² -> |z'|Β² = 9|zΒ²|Β²
            dz2 *= 9 * qLength2(qSquare(z4));
            // z = zΒ³ + c
            z4 = sum4(qCube(z4), C);
        } else {
            // z' = 2z
            dz2 *= 4 * qLength2(z4);
            // z = zΒ² + c
            z4 = sum4(qSquare(z4), C);
        }

        m2 = qLength2(z4);		

        // Orbit trap
        o = Math.min(o, length3([z4[0] - 0.45, z4[2] - 0.55, 0]) - .05);

        if (m2 > 256) break;
    }
   
    // sdf(z) = log|z|Β·|z|/|dz| : https://iquilezles.org/www/articles/distancefractals/distancefractals.htm
    var d = 0.25 * Math.log(m2) * Math.sqrt(m2 / dz2);

    // Cut
    if (cut) d = Math.max(d, -q3[2]);

    // Trap
    if (trap == 1) d = Math.min(d, o);
    if (trap == 2) d = o;

    return [d, i];
}

const MAX_STEPS = 10000;
const MAX_DIST = 100;
const SURF_DIST = .0001;

function RayMarch(ro3, rd3)
{
	var dO = 0;
	var mat = -1;
    
    for (var i = 0; i < MAX_STEPS; i++) {
    	var p3 = add3(ro3, mul3(rd3, dO));
        var dS;
        [dS, mat] = sdFractal(p3);
        dO += dS;
        if (dO > MAX_DIST) return [MAX_DIST, -1];
        if (Math.abs(dS)<SURF_DIST) break;
    }
    
    return [dO, mat];
}

const round_clip = 1;

// p: 2D point in -100 to 100 range
function getFractal(in_p2) {
    if (Math.hypot(in_p2[0], in_p2[1]) > 95) return -1;

    // Ray direction
    var ro3, rd3;
    [ro3, rd3] = getRay(in_p2);

    // Get distance to intersection
    var dist, mat;
    [dist, mat] = RayMarch(ro3, rd3);

    if (dist >= MAX_DIST) return -1;

    return mat;
}

function rotX(x, y, a) { return Math.cos(a) * x - Math.sin(a) * y; }
function rotY(x, y, a) { return Math.sin(a) * x + Math.cos(a) * y; }

function length2(v2) { return Math.sqrt(v2[0]*v2[0] + v2[1]*v2[1]); }
function length3(v3) { return Math.sqrt(v3[0]*v3[0] + v3[1]*v3[1] + v3[2]*v3[2]); }
function normalize3(v3) { var l = length3(v3); if (l<0.00001) l=1; return [v3[0]/l, v3[1]/l, v3[2]/l]; }
function mul2(a2, f) { return [a2[0]*f, a2[1]*f]; }
function mul3(a3, f) { return [a3[0]*f, a3[1]*f, a3[2]*f]; }
function add3(a3, b3) { return [a3[0]+b3[0], a3[1]+b3[1], a3[2]+b3[2]]; }
function sub3(a3, b3) { return [a3[0]-b3[0], a3[1]-b3[1], a3[2]-b3[2]]; }
function cross3(a3, b3) { return [ a3[1] * b3[2] - a3[2] * b3[1], a3[2] * b3[0] - a3[0] * b3[2], a3[0] * b3[1] - a3[1] * b3[0] ]; }
function fract3(v3) { return [ v3[0] - Math.floor(v3[0]), v3[1] - Math.floor(v3[1]), v3[2] - Math.floor(v3[2]) ]; }
function abs3(v3) { return [ Math.abs(v3[0]), Math.abs(v3[1]), Math.abs(v3[2]) ]; }
function dot3(a3, b3) { return a3[0] * b3[0] + a3[1] * b3[1] + a3[2] * b3[2]; }
function floor3(v3) { return [ Math.floor(v3[0]), Math.floor(v3[1]), Math.floor(v3[2]) ]; }
function mul_mat2(v2, m22) { return [ v2[0] * m22[0] + v2[1] * m22[1], v2[0] * m22[2] + v2[1] * m22[3] ]; }
function rotation_mat22(a) { var s = Math.sin(a); var c = Math.cos(a); return [c, -s, s, c]; }
function clamp(x, min, max) { return Math.min(max, Math.max(min, x)); }
function smoothstep(edge0, edge1, x) { x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); return x * x * (3 - 2 * x); }
function tri3(x3) { return abs3(sub3(fract3(x3), .5)); }
function mix(x, y, a) { return x * (1-a) + y * a; }
function mix4(x, y, a) { return [ x[0] * (1-a) + y[0] * a, x[1] * (1-a) + y[1] * a, x[2] * (1-a) + y[2] * a, x[3] * (1-a) + y[3] * a ]; }
function smin(a, b , s) { var h = clamp( 0.5 + 0.5*(b-a)/s, 0. , 1.); return mix(b, a, h) - h*(1.0-h)*s; } // IQ's smooth minium function. 

//////////////////////////////////////////
// Contour utility by Lionel Lemarie 2021
// https://turtletoy.net/turtle/765d77abf4
function contour(i, t, zFunc) {
    if (i == 0) { cache = {}; }
    const r = 100 / precision;
    
    if (i >= precision*precision) return false;

    const xx = i % precision;
    const yy = (i / precision) | 0;
    
    const ci00 = (xx-1) + (yy-1) * precision;
    const ci01 = (xx-0) + (yy-1) * precision;
    const ci10 = (xx-1) + (yy-0) * precision;
    const ci11 = (xx-0) + (yy-0) * precision;

    const x0 = ((xx-1) / precision - 0.5) * 200;
    const y0 = ((yy-1) / precision - 0.5) * 200;
    const x1 = ((xx-0) / precision - 0.5) * 200;
    const y1 = ((yy-0) / precision - 0.5) * 200;

    const z00 = cache[ci00], z01 = cache[ci01], z10 = cache[ci10];
    const z11 = cache[ci11] = zFunc([x1, y1]);

    var lines = [];
    
    // A bit like marching cubes
    if (z00 != z01 && z00 != z10 && z00 != z11) lines = [[[1,0],[0,1]],[[0,1],[1,2]],[[1,2],[2,1]],[[2,1],[1,0]]];
    if (z00 == z01 && z00 == z10 && z00 != z11) lines = [[[2,1],[1,2]]];
    if (z00 == z01 && z00 == z11 && z00 != z10) lines = [[[0,1],[1,2]]];
    if (z00 == z01 && z10 == z11 && z00 != z10) lines = [[[0,1],[2,1]]];
    if (z00 == z10 && z00 == z11 && z00 != z01) lines = [[[1,0],[2,1]]];
    if (z00 == z10 && z01 == z11 && z00 != z01) lines = [[[1,0],[1,2]]];
    if (z00 == z11 && z01 == z10 && z00 != z01) lines = [[[1,0],[0,1]],[[2,1],[1,2]]];
    if (z01 == z10 && z01 == z11 && z00 != z01) lines = [[[1,0],[0,1]]];

    lines.forEach(l => {
        turtle.jump(r/2+x0+l[0][0]*r, r/2+y0+l[0][1]*r);
        turtle.goto(r/2+x0+l[1][0]*r, r/2+y0+l[1][1]*r);
    });

    return true;
}