← Mike Higgins Home
Custom Code Experiment •

Conformal Map 5

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>Conformal Map 6</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      user-select: none;
    }
    html, body {
      width: 100%;
      height: 100%;
      overflow: hidden;
      background: #000;
    }
    canvas {
      display: block;
      width: 100%;
      height: 100%;
    }
  </style>
</head>
<body>
<canvas id="c"></canvas>
<script>
(function(){
  const canvas = document.getElementById('c');
  const gl = canvas.getContext('webgl2', { antialias: true, depth: false });
  if (!gl) {
    console.warn("WebGL2 not supported");
    return;
  }

  // ---------- VERTEX SHADER ----------
  const VS = `#version 300 es
    in vec2 aPos;
    out vec2 vUv;
    void main() {
      vUv = aPos * 0.5 + 0.5;
      gl_Position = vec4(aPos, 0.0, 1.0);
    }
  `;

  // ======================= BASE SHADER: HYPERBOLIC ROSE + PHYLLOTACTIC FLORAL =======================
  const FS_BASE = `#version 300 es
    precision highp float;
    in vec2 vUv;
    out vec4 fragColor;
    uniform float uTime;
    uniform float uAspect;

    #define PI 3.141592653589793
    #define TAU 6.283185307179586
    #define PHI 1.618033988749895

    // luminous palette
    vec3 palA = vec3(0.98, 0.45, 0.28);      // coral dawn
    vec3 palB = vec3(0.15, 0.18, 0.55);      // deep indigo
    vec3 coreGlow = vec3(1.0, 0.85, 0.35);   // golden core
    vec3 rayTint = vec3(1.0, 0.68, 0.22);    // amber rays
    vec3 darkOutline = vec3(0.04, 0.02, 0.12);

    // signed distance-like field: generalized rose + radial resonance
    float floralDistance(vec2 p, float cellSeed, float timeShift) {
      float r = length(p);
      float theta = atan(p.y, p.x);

      // dynamic petal count based on cell seed
      float petalCount = 7.0 + floor(mod(cellSeed * 17.0, 5.0));  // 7..11 petals
      float petalAmp = 0.13 + 0.07 * sin(cellSeed * TAU + timeShift);
      float roseRadius = 0.32 + petalAmp * cos(petalCount * theta + timeShift * 1.2);

      // hyperbolic spiral twist (inverse radius modulation)
      float spiralTwist = 0.05 * sin(6.0 * theta - timeShift * 2.3) / (r + 0.12);
      float dynamicRadius = roseRadius + spiralTwist;

      // distance to rose contour
      float dRose = abs(r - dynamicRadius) - 0.022;

      // inner mandala: sinc-inspired radial harmonics
      float innerRim = 0.19 + 0.03 * sin(theta * 14.0 + timeShift) * cos(r * 28.0);
      float dInner = abs(r - innerRim) - 0.012;

      // outer Fibonacci spirals
      float fibRing = 0.52 + 0.045 * sin(theta * 9.0 - timeShift * 1.6) * cos(r * 42.0 - timeShift);
      float dFib = abs(r - fibRing) - 0.018;

      float d = min(dRose, dInner);
      d = min(d, dFib);

      // ray tendrils: angular interference
      float rayPattern = sin(petalCount * theta * 1.4 + timeShift) * cos(theta * 4.0 - timeShift);
      float radialWidth = 0.07 * (1.0 - smoothstep(0.18, 0.68, r));
      float rayDist = abs(rayPattern) * radialWidth - 0.006;
      d = min(d, rayDist);

      // outer boundary clipping
      float horizon = r - 0.75;
      d = min(d, horizon);

      return d;
    }

    void main() {
      vec2 uv = vUv;

      // non-linear shear (time-variant)
      float skew = 0.42 + 0.05 * sin(uTime * 0.27);
      uv.x += uv.y * skew;

      float t = uTime * 0.31;

      // tiling with organic scroll
      float cellSize = 0.72;
      vec2 scrollUv = uv + vec2(t * 0.012, t * 0.008);
      vec2 gridUv = scrollUv / cellSize;
      vec2 gridIdx = floor(gridUv);
      vec2 cellUv = fract(gridUv) - 0.5;

      // per-cell pseudo-random seed for morphological variety
      float cellSeed = fract(sin(gridIdx.x * 127.1 + gridIdx.y * 311.7) * 43758.5453);
      float rotSeed = fract(sin(gridIdx.y * 219.3 + gridIdx.x * 173.9) * 271.8);

      // dynamic background: checkerboard with chromatic drift
      float checker = mod(gridIdx.x + gridIdx.y, 2.0);
      vec3 bgCol = mix(palA, palB, checker + 0.12 * sin(gridIdx.x * 0.83 + gridIdx.y * 0.64));

      // per-cell rotation influenced by hyperbolic sine
      float angleRot = (rotSeed * TAU + t * 0.48) + sin(gridIdx.x * 0.53) * 0.7;
      float cRot = cos(angleRot);
      float sRot = sin(angleRot);
      vec2 rotatedUv = vec2(
        cellUv.x * cRot - cellUv.y * sRot,
        cellUv.x * sRot + cellUv.y * cRot
      );

      // scale morphing per tile
      float morphScale = 0.88 + 0.28 * sin(cellSeed * TAU * 2.1 + t);
      vec2 finalP = rotatedUv / morphScale;

      // distance to dynamic floral shape
      float shapeDist = floralDistance(finalP, cellSeed, t + cellSeed * 6.28);
      float mask = smoothstep(0.028, -0.012, shapeDist);

      // shape color: iridescent mixture
      vec3 shapeColor = mix(palB, palA, checker + 0.25 * sin(cellSeed * TAU * 4.0));
      shapeColor = mix(shapeColor, coreGlow, 0.45 + 0.3 * cos(gridIdx.x * 0.76 + gridIdx.y * 0.62 + t));

      vec3 col = mix(bgCol, shapeColor, mask);

      // inner golden core
      float coreRadius = length(finalP) - 0.13;
      float coreMask = smoothstep(0.024, -0.014, coreRadius);
      col = mix(col, coreGlow, coreMask * mask);

      // dynamic rays using radial cardinal sine
      float angleLocal = atan(finalP.y, finalP.x);
      float rayHarmonic = sin(angleLocal * 12.0 + t * 2.5) * cos(angleLocal * 5.0 - t * 1.6);
      float rayIntensity = smoothstep(0.22, 0.58, length(finalP)) * (0.75 + 0.45 * rayHarmonic);
      rayIntensity *= (1.0 - smoothstep(0.01, 0.38, coreRadius)) * 0.9;
      rayIntensity = clamp(rayIntensity, 0.0, 0.68);
      col = mix(col, rayTint, rayIntensity * mask);

      // glowing outlines: Moiré edge enhancement
      float edgeGlow = smoothstep(0.032, 0.014, shapeDist) - smoothstep(0.014, -0.012, shapeDist);
      edgeGlow += 0.35 * abs(sin(angleLocal * 26.0 - t * 7.2)) * (1.0 - mask);
      col = mix(col, darkOutline, edgeGlow * 0.85);

      // relativistic grid lines
      vec2 edgeDist = abs(fract(gridUv) - 0.5);
      float lineWidth = 0.043 + 0.012 * sin(gridIdx.x * 2.4 + gridIdx.y * 1.9 + t);
      float lines = 1.0 - smoothstep(lineWidth - 0.006, lineWidth, min(edgeDist.x, edgeDist.y));
      lines *= 0.85;
      col = mix(col, vec3(0.96, 0.91, 0.55), lines * 0.65);

      // chromatic aberration (fixed: proper float scalar)
      float chromaRipple = sin(uv.y * 38.0 + t * 5.2) * 0.25 + cos(uv.x * 32.0 - t * 4.1) * 0.2;
      col += vec3(0.06, 0.03, 0.11) * chromaRipple * (1.0 - mask) * 0.5;

      fragColor = vec4(col, 1.0);
    }
  `;

  // =========== RECURSIVE CONFORMAL MAP: HYPERBOLIC TORSION + GOLDEN SPIRAL REMIX ===========
  const FS_DROSTE = `#version 300 es
    precision highp float;
    in vec2 vUv;
    out vec4 fragColor;
    uniform sampler2D uTex;
    uniform float uTime;
    uniform float uAspect;

    #define PI 3.141592653589793
    #define TAU 6.283185307179586

    void main() {
      vec2 uv = vUv;
      vec2 p = (uv * 2.0 - 1.0) * vec2(uAspect, 1.0);
      float r = length(p);
      if (r < 0.008) {
        fragColor = vec4(1.0, 0.82, 0.45, 1.0);
        return;
      }
      float theta = atan(p.y, p.x);

      // Non-Archimedean spiral with hyperbolic tangent modulation
      float logr = log(r);
      float dynamicTwist = 0.285 + 0.065 * sin(theta * 3.7 - uTime * 0.85);
      float warpFactor = tanh(logr * 1.15) * 0.35;

      // breathing period influenced by golden ratio
      float period = 0.71 * (1.0 + 0.06 * sin(uTime * 0.21));
      float animPhase = uTime * 0.175;

      // Möbius-like recursive mapping
      float mappedLogR = logr - dynamicTwist * theta + warpFactor * cos(theta * 6.0 - uTime);
      mappedLogR = mod(mappedLogR + animPhase, period) - period * 0.485;

      float newR = exp(mappedLogR + dynamicTwist * theta - warpFactor * cos(theta * 6.0 - uTime));
      float newTheta = theta + 0.125 * sin(5.0 * mappedLogR + uTime * 0.9) * cos(theta * 2.3);

      vec2 newP = newR * vec2(cos(newTheta), sin(newTheta));
      vec2 newUv = (newP / vec2(uAspect, 1.0)) * 0.5 + 0.5;

      vec4 sampled = texture(uTex, fract(newUv));

      // subtle radial fade to increase depth
      float depthFade = clamp(1.25 - r * 0.7, 0.45, 1.0);
      sampled.rgb *= depthFade;

      fragColor = sampled;
    }
  `;

  // simple copy shader for final output
  const FS_COPY = `#version 300 es
    precision highp float;
    in vec2 vUv;
    out vec4 fragColor;
    uniform sampler2D uTex;
    void main() {
      fragColor = texture(uTex, vUv);
    }
  `;

  function compileShader(type, src) {
    const s = gl.createShader(type);
    gl.shaderSource(s, src);
    gl.compileShader(s);
    if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
      console.error(gl.getShaderInfoLog(s));
      gl.deleteShader(s);
      return null;
    }
    return s;
  }

  function createProgram(vsSrc, fsSrc) {
    const vs = compileShader(gl.VERTEX_SHADER, vsSrc);
    const fs = compileShader(gl.FRAGMENT_SHADER, fsSrc);
    if (!vs || !fs) return null;
    const prog = gl.createProgram();
    gl.attachShader(prog, vs);
    gl.attachShader(prog, fs);
    gl.linkProgram(prog);
    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
      console.error(gl.getProgramInfoLog(prog));
      return null;
    }
    gl.deleteShader(vs);
    gl.deleteShader(fs);
    return prog;
  }

  const progBase = createProgram(VS, FS_BASE);
  const progDroste = createProgram(VS, FS_DROSTE);
  const progCopy = createProgram(VS, FS_COPY);
  if (!progBase || !progDroste || !progCopy) throw new Error("Shader compilation failed");

  // geometry setup
  const vao = gl.createVertexArray();
  gl.bindVertexArray(vao);
  const vbo = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);
  const aPos = gl.getAttribLocation(progBase, 'aPos');
  gl.enableVertexAttribArray(aPos);
  gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);

  let fboA, fboB;
  function createFBO(w, h) {
    const tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    const fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
    const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
    if (status !== gl.FRAMEBUFFER_COMPLETE) console.warn("FBO incomplete", status);
    return { fbo, tex };
  }

  let screenW, screenH;
  function resize() {
    screenW = window.innerWidth;
    screenH = window.innerHeight;
    canvas.width = screenW;
    canvas.height = screenH;
    if (fboA) {
      gl.deleteFramebuffer(fboA.fbo);
      gl.deleteTexture(fboA.tex);
      gl.deleteFramebuffer(fboB.fbo);
      gl.deleteTexture(fboB.tex);
    }
    fboA = createFBO(screenW, screenH);
    fboB = createFBO(screenW, screenH);
  }
  window.addEventListener('resize', resize);
  resize();

  const locBaseTime = gl.getUniformLocation(progBase, 'uTime');
  const locBaseAspect = gl.getUniformLocation(progBase, 'uAspect');
  const locDrosteTime = gl.getUniformLocation(progDroste, 'uTime');
  const locDrosteAspect = gl.getUniformLocation(progDroste, 'uAspect');
  const locDrosteTex = gl.getUniformLocation(progDroste, 'uTex');
  const locCopyTex = gl.getUniformLocation(progCopy, 'uTex');

  const startTime = performance.now();

  function draw() {
    requestAnimationFrame(draw);
    const t = (performance.now() - startTime) * 0.001;
    const aspect = screenW / screenH;

    gl.bindVertexArray(vao);
    gl.viewport(0, 0, screenW, screenH);

    // ----- 1. Render base fractal tile into FBO A -----
    gl.bindFramebuffer(gl.FRAMEBUFFER, fboA.fbo);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.useProgram(progBase);
    gl.uniform1f(locBaseTime, t);
    gl.uniform1f(locBaseAspect, aspect);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    // ----- 2. Recursive conformal mapping (Droste remix with hyperbolic torsion) -----
    gl.useProgram(progDroste);
    gl.uniform1f(locDrosteTime, t);
    gl.uniform1f(locDrosteAspect, aspect);
    gl.uniform1i(locDrosteTex, 0);
    gl.activeTexture(gl.TEXTURE0);

    let src = fboA, dst = fboB;
    for (let iter = 0; iter < 2; iter++) {
      gl.bindFramebuffer(gl.FRAMEBUFFER, dst.fbo);
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.bindTexture(gl.TEXTURE_2D, src.tex);
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
      // swap
      let temp = src;
      src = dst;
      dst = temp;
    }

    // ----- 3. Final present to screen -----
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.useProgram(progCopy);
    gl.uniform1i(locCopyTex, 0);
    gl.bindTexture(gl.TEXTURE_2D, src.tex);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  }

  draw();
})();
</script>
</body>
</html>