← Mike Higgins Home
Custom Code Experiment •

Conformal Map 4

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 4</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    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 });
  if (!gl) return;

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

  const FS_BASE = `#version 300 es
    precision highp float;
    in vec2 vUv;
    out vec4 fragColor;
    uniform float uTime;
    uniform float uAspect;
    #define PI 3.14159265359

    vec3 colorA = vec3(0.95, 0.45, 0.15);
    vec3 colorB = vec3(0.10, 0.08, 0.25);
    vec3 sunColor = vec3(1.0, 0.95, 0.6);
    vec3 rayColor = vec3(1.0, 0.7, 0.2);
    vec3 outlineCol = vec3(0.0, 0.0, 0.0);

    float sunriseSDF(vec2 p, float s, float rot) {
      float c = cos(-rot), si = sin(-rot);
      p = vec2(c*p.x - si*p.y, si*p.x + c*p.y);
      p /= s;

      vec2 sunCenter = vec2(0.0, -0.25);
      float sunDist = length(p - sunCenter) - 0.28;

      vec2 delta = p - sunCenter;
      float angle = atan(delta.y, delta.x);
      float radius = length(delta);

      float numRays = 12.0;
      float rayAngle = mod(angle + PI/numRays, 2.0*PI/numRays) - PI/numRays;

      float rayWidth = 0.15;
      float rayDist = abs(rayAngle) - rayWidth;
      float rayRadial = radius - 0.35;
      float combinedRay = max(rayDist, rayRadial);

      float d = min(sunDist, combinedRay);

      float horizon = p.y + 0.10;
      d = min(d, horizon);

      return d * s;
    }

    void main() {
      vec2 uv = vUv;

      float skew = 0.35;
      uv.x += uv.y * skew;

      float t = uTime * 0.35;

      vec2 scrollUv = uv + vec2(t * 0.01, t * 0.005);
      float cellSize = 0.65;
      vec2 gridUv = scrollUv / cellSize;
      vec2 gridIdx = floor(gridUv);
      vec2 cellUv = fract(gridUv) - 0.5;

      float checker = mod(gridIdx.x + gridIdx.y, 2.0);
      vec3 bgCol = mix(colorA, colorB, checker);
      vec3 col = bgCol;

      float shapeScale = 0.85;
      float rot = checker * PI + t * 0.5;

      float sSDF = sunriseSDF(cellUv, shapeScale, rot);

      vec3 shapeCol = mix(colorB, colorA, checker);

      float tMask = smoothstep(0.015, -0.015, sSDF);

      col = mix(col, shapeCol, tMask);

      float c2 = cos(-rot), s2 = sin(-rot);
      vec2 sunCenter = vec2(c2*0.0 - s2*(-0.25), s2*0.0 + c2*(-0.25)) * shapeScale;
      float sunDist = length(cellUv - sunCenter) - 0.24 * shapeScale;
      float sunMask = smoothstep(0.01, -0.01, sunDist);
      col = mix(col, sunColor, sunMask * tMask);

      vec2 delta = cellUv - sunCenter;
      float angle = atan(delta.y, delta.x);
      float rayPattern = sin(angle * 12.0 + t * 2.0) * 0.5 + 0.5;
      float rayGlow = smoothstep(0.3, 0.1, abs(sin(angle * 12.0))) * (1.0 - smoothstep(0.2, 0.45, length(delta)));
      col = mix(col, rayColor, rayGlow * tMask);

      float outline = smoothstep(0.025, 0.015, sSDF) - smoothstep(0.015, -0.015, sSDF);
      col = mix(col, outlineCol, outline * 0.95);

      vec2 edgeDist = abs(fract(gridUv) - 0.5);
      float lineW = 0.04;
      float lines = 1.0 - smoothstep(lineW - 0.005, lineW, min(edgeDist.x, edgeDist.y));
      col = mix(col, outlineCol, lines);

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

  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.14159265359
    #define TAU 6.28318530718

    void main() {
      vec2 uv = vUv;

      float skew = 0.35;
      uv.x += uv.y * skew;

      vec2 p = (uv * 2.0 - 1.0) * vec2(uAspect, 1.0);
      float r = length(p);
      if (r < 0.01) { fragColor = vec4(1.0, 0.95, 0.6, 1.0); return; }
      float theta = atan(p.y, p.x);
      float logr = log(r);
      float b = 0.22;
      float period = TAU * b;
      float animPhase = uTime * 0.15;
      float mappedLogR = logr - b * (theta + animPhase);
      mappedLogR = mod(mappedLogR, period * 1.6) - period * 0.12;
      float newR = exp(mappedLogR);
      float newTheta = theta + (logr - mappedLogR) / b;
      vec2 newP = newR * vec2(cos(newTheta), sin(newTheta));
      vec2 newUv = (newP / vec2(uAspect, 1.0)) * 0.5 + 0.5;

      fragColor = texture(uTex, fract(newUv));
    }
  `;

  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);
    return s;
  }

  function createProgram(vs, fs) {
    const p = gl.createProgram();
    gl.attachShader(p, compileShader(gl.VERTEX_SHADER, vs));
    gl.attachShader(p, compileShader(gl.FRAGMENT_SHADER, fs));
    gl.linkProgram(p);
    return p;
  }

  const progBase = createProgram(VS, FS_BASE);
  const progDroste = createProgram(VS, FS_DROSTE);
  const progCopy = createProgram(VS, FS_COPY);

  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);
    return { fbo, tex };
  }

  function resize() {
    const w = window.innerWidth, h = window.innerHeight;
    canvas.width = w; canvas.height = h;
    if (fboA) { gl.deleteFramebuffer(fboA.fbo); gl.deleteTexture(fboA.tex); }
    if (fboB) { gl.deleteFramebuffer(fboB.fbo); gl.deleteTexture(fboB.tex); }
    fboA = createFBO(w, h);
    fboB = createFBO(w, h);
  }
  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 W = canvas.width, H = canvas.height;
    const asp = W / H;

    gl.bindVertexArray(vao);
    gl.viewport(0, 0, W, H);

    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, asp);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    gl.useProgram(progDroste);
    gl.uniform1f(locDrosteTime, t);
    gl.uniform1f(locDrosteAspect, asp);
    gl.uniform1i(locDrosteTex, 0);
    gl.activeTexture(gl.TEXTURE0);

    let src = fboA, dst = fboB;
    for (let i = 0; i < 2; i++) {
      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);
      [src, dst] = [dst, src];
    }

    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>