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