zachery.lol/routes/root/fractal-gl.js
Zachery Aaron Shores-Chmielewski d10f539e06 fix: fractal animation on webgl, targeted builds
Fractal animation (Newton's fractal) now animates smoothly. Rebuilds now can be targeted based on which module was changed.
2026-02-15 12:39:10 +07:00

152 lines
4.7 KiB
JavaScript

// WebGL Newton fractal renderer (z^3 - 1, ocean palette)
// Ported from crates/fractal-engine/src/{newton.rs, color.rs}
function initFractal(canvasId) {
var canvas = document.getElementById(canvasId);
if (!canvas) return;
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
if (!gl) {
// Fallback: ocean-themed CSS gradient
canvas.style.background =
'radial-gradient(ellipse at center, #0d3842 0%, #0c2e58 40%, #060a10 100%)';
return;
}
// --- Shaders ---
var VERT_SRC = [
'attribute vec2 a_pos;',
'void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }'
].join('\n');
var FRAG_SRC = [
'precision highp float;',
'uniform float u_time;',
'uniform vec2 u_resolution;',
'',
'void main() {',
' vec2 uv = gl_FragCoord.xy / u_resolution;',
' float aspect = u_resolution.x / u_resolution.y;',
'',
' // Map to complex plane (view_size=3.0, centered)',
' float zr = (uv.x - 0.5) * 3.0 * aspect;',
' float zi = (uv.y - 0.5) * 3.0;',
'',
' // Rotate by time (animation)',
' float ct = cos(u_time);',
' float st = sin(u_time);',
' float tmp = zr * ct - zi * st;',
' zi = zr * st + zi * ct;',
' zr = tmp;',
'',
' // Newton iteration for z^3 - 1',
' int rootIndex = 0;',
' int iters = 0;',
' for (int i = 0; i < 32; i++) {',
' iters = i;',
' float zr2 = zr*zr - zi*zi;',
' float zi2 = 2.0*zr*zi;',
' float zr3 = zr*zr2 - zi*zi2;',
' float zi3 = zr*zi2 + zi*zr2;',
'',
' float d0 = (zr-1.0)*(zr-1.0) + zi*zi;',
' float d1 = (zr+0.5)*(zr+0.5) + (zi-0.866025)*(zi-0.866025);',
' float d2 = (zr+0.5)*(zr+0.5) + (zi+0.866025)*(zi+0.866025);',
'',
' if (d0 < 1e-6) { rootIndex = 0; break; }',
' if (d1 < 1e-6) { rootIndex = 1; break; }',
' if (d2 < 1e-6) { rootIndex = 2; break; }',
'',
' float nr = zr3 - 1.0;',
' float ni = zi3;',
' float dr = 3.0*zr2;',
' float di = 3.0*zi2;',
' float denom = dr*dr + di*di;',
' if (denom < 1e-12) break;',
' zr -= (nr*dr + ni*di) / denom;',
' zi -= (ni*dr - nr*di) / denom;',
' }',
'',
' // Fallback: closest root',
' float d0 = (zr-1.0)*(zr-1.0) + zi*zi;',
' float d1 = (zr+0.5)*(zr+0.5) + (zi-0.866025)*(zi-0.866025);',
' float d2 = (zr+0.5)*(zr+0.5) + (zi+0.866025)*(zi+0.866025);',
' if (d1 < d0 && d1 < d2) rootIndex = 1;',
' else if (d2 < d0) rootIndex = 2;',
'',
' // Ocean palette',
' float brightness = max(1.0 - float(iters)/32.0 * 0.7, 0.3);',
' vec3 color;',
' if (rootIndex == 0) color = vec3(0.05, 0.25, 0.55);',
' else if (rootIndex == 1) color = vec3(0.0, 0.45, 0.50);',
' else color = vec3(0.15, 0.55, 0.65);',
'',
' gl_FragColor = vec4(color * brightness, 1.0);',
'}'
].join('\n');
// --- Compile helpers ---
function compile(type, src) {
var s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
console.error('Shader compile error:', gl.getShaderInfoLog(s));
return null;
}
return s;
}
var vs = compile(gl.VERTEX_SHADER, VERT_SRC);
var fs = compile(gl.FRAGMENT_SHADER, FRAG_SRC);
if (!vs || !fs) return;
var prog = gl.createProgram();
gl.attachShader(prog, vs);
gl.attachShader(prog, fs);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
console.error('Program link error:', gl.getProgramInfoLog(prog));
return;
}
gl.useProgram(prog);
// --- Full-screen triangle ---
var buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW);
var aPos = gl.getAttribLocation(prog, 'a_pos');
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
var uTime = gl.getUniformLocation(prog, 'u_time');
var uRes = gl.getUniformLocation(prog, 'u_resolution');
// --- Resize ---
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
gl.viewport(0, 0, canvas.width, canvas.height);
}
resize();
window.addEventListener('resize', resize);
// --- Animation loop ---
var startTime = performance.now();
var animationSpeed = 0.0001;
function frame() {
var t = (performance.now() - startTime) * animationSpeed;
gl.uniform1f(uTime, t);
gl.uniform2f(uRes, canvas.width, canvas.height);
gl.drawArrays(gl.TRIANGLES, 0, 3);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}