jacquardSnapshot

← snapshot

11523 bytes
/**
 * Shader recipes for the orb lab.
 *
 * Every entry supplies a `map` (the distance field) and optionally a `shade`
 * (how a hit is coloured). They are injected into one shared raymarching
 * template, so the variants differ in the thing that actually matters — the
 * geometry and its light — rather than in boilerplate.
 *
 * Available to every snippet: `uTime`, `uLevel` (0..1 speech), `uAttract`
 * (0..1 interaction), `uLean` (pointer), `thread` (linear provenance colour),
 * `tint`, and the helpers below.
 */

export const GLSL_PRELUDE = /* glsl */ `
mat2 rot(float a){ float c=cos(a), s=sin(a); return mat2(c,-s,s,c); }

float sdSphere(vec3 p, float r){ return length(p) - r; }
float sdBox(vec3 p, vec3 b){ vec3 q=abs(p)-b; return length(max(q,0.0))+min(max(q.x,max(q.y,q.z)),0.0); }
float sdTorus(vec3 p, vec2 t){ vec2 q=vec2(length(p.xz)-t.x, p.y); return length(q)-t.y; }
float sdOcta(vec3 p, float s){ p=abs(p); return (p.x+p.y+p.z-s)*0.5773502691; }
float sdCyl(vec3 p, float h, float r){ vec2 d=abs(vec2(length(p.xz),p.y))-vec2(r,h); return min(max(d.x,d.y),0.0)+length(max(d,0.0)); }
float smin(float a, float b, float k){ float h=clamp(0.5+0.5*(b-a)/k,0.0,1.0); return mix(b,a,h)-k*h*(1.0-h); }

float hash(vec3 p){ return fract(sin(dot(p, vec3(127.1,311.7,74.7)))*43758.5453); }

/** Self-similar displacement: doubling frequency, halving amplitude. */
float fbm(vec3 p, float spin){
  float amp = 0.5, sum = 0.0;
  for (int i = 0; i < 5; i++){
    sum += amp * sin(p.x) * sin(p.y) * sin(p.z);
    p = p * 2.02;
    p.xy = rot(spin) * p.xy;
    p.yz = rot(spin * 0.7) * p.yz;
    amp *= 0.5;
  }
  return sum;
}

/** n-fold mirrored polar fold — kaleidoscope mirrors. */
vec3 foldN(vec3 p, float n){
  float a = atan(p.z, p.x), r = length(p.xz);
  float seg = 6.28318530718 / n;
  a = mod(a + seg*0.5, seg) - seg*0.5;
  a = abs(a);
  return vec3(cos(a)*r, p.y, sin(a)*r);
}

float sigilRing(vec3 p, float radius, float thick, float n, float phase){
  float a = atan(p.z, p.x) + phase;
  float coarse = 0.5 + 0.5*cos(a*n);
  float fine   = 0.5 + 0.5*cos(a*n*3.0);
  float notch  = coarse * (0.72 + 0.28*fine);
  vec2 q = vec2(length(p.xz) - (radius + notch*0.055), p.y);
  return length(q) - thick * (0.18 + 1.45*notch);
}
`;

export interface GlslVariant {
  id: string;
  label: string;
  blurb: string;
  /** GLSL body of `vec2 mapAll(vec3 p)` — returns (distance, material). */
  map: string;
  /** Optional GLSL body run on a hit; sets `col`. */
  shade?: string;
  /** Camera distance from origin. */
  dist?: number;
  /** Marching steps; raise for thin or volumetric fields. */
  steps?: number;
  /** Step relaxation — lower for displaced or folded fields. */
  relax?: number;
  /**
   * Smallest step the march will take. Surface fields want this tiny so they
   * can settle onto a hit; volumetric fields want it large, because they never
   * hit anything and would otherwise crawl a fraction of the way across the
   * scene before running out of steps.
   */
  minStep?: number;
}

export const GLSL_VARIANTS: GlslVariant[] = [
  {
    id: "soft-mandala",
    label: "Soft mandala",
    blurb: "Glowing core inside three counter-rotating sigil rings.",
    dist: 4.15,
    relax: 0.85,
    map: `
      vec3 q = foldN(p, 3.0) * 2.1 + vec3(0.0, uTime*0.22, 0.0);
      float amp = 0.17 + uLevel*0.22 + uAttract*0.07;
      float dc = (length(p) - (0.92 + amp*fbm(q, 0.55))) * 0.55;
      float dr = 1e9;
      for (int i=0;i<3;i++){
        float fi=float(i);
        vec3 r=p; float dir = mod(fi,2.0)<0.5 ? 1.0 : -1.0;
        float sp = uTime*(0.22+fi*0.16)*dir + fi*1.7;
        r.yz = rot(0.42+fi*0.62)*r.yz;
        r.xz = rot(sp)*r.xz;
        dr = min(dr, sigilRing(r, 1.32+fi*0.29, 0.030, 3.0*(1.0+fi), sp*2.0));
      }
      return dc < dr ? vec2(dc, 0.0) : vec2(dr, 1.0);`,
  },
  {
    id: "voice-bloom",
    label: "Voice bloom",
    blurb: "A single breathing sphere, all bloom and no edges.",
    dist: 3.1,
    relax: 0.85,
    map: `
      vec3 q = foldN(p, 3.0)*2.3 + vec3(0.0, uTime*0.3, 0.0);
      float amp = 0.16 + uLevel*0.30 + uAttract*0.06;
      return vec2((length(p) - (1.0 + amp*fbm(q, 0.6)))*0.55, 0.0);`,
  },
  {
    id: "mandala-only",
    label: "Mandala",
    blurb: "Rings alone — an armillary sigil with nothing at the centre.",
    dist: 4.3,
    relax: 0.9,
    map: `
      float dr = 1e9;
      for (int i=0;i<5;i++){
        float fi=float(i);
        vec3 r=p; float dir = mod(fi,2.0)<0.5 ? 1.0 : -1.0;
        float sp = uTime*(0.18+fi*0.13)*dir + fi*1.1;
        r.yz = rot(0.2+fi*0.42)*r.yz;
        r.xz = rot(sp)*r.xz;
        dr = min(dr, sigilRing(r, 0.75+fi*0.32, 0.024+uLevel*0.012, 6.0+fi*4.0, sp*2.0));
      }
      return vec2(dr, 1.0);`,
  },
  {
    id: "menger",
    label: "Menger lattice",
    blurb: "A punched card in three dimensions: holes all the way through.",
    dist: 4.2,
    relax: 0.85,
    map: `
      // The canonical sponge. Folding the space first (as a kaleidoscope
      // would) breaks the distance bound and shatters the silhouette, so the
      // rotation is applied to the whole solid instead.
      vec3 q = p;
      q.xz = rot(uTime*0.14)*q.xz;
      q.xy = rot(0.62 + uLevel*0.10)*q.xy;
      float d = sdBox(q, vec3(1.05));
      // Three levels, not four: a fourth makes the finest holes 1/27 of the
      // cube, which stops reading as perforation and starts reading as grain.
      float s = 1.0;
      for (int m=0;m<3;m++){
        vec3 a = mod(q*s, 2.0) - 1.0;
        s *= 3.0;
        vec3 r = abs(1.0 - 3.0*abs(a));
        float c = (min(max(r.x,r.y), min(max(r.y,r.z), max(r.z,r.x))) - 1.0)/s;
        d = max(d, c);
      }
      return vec2(d, 0.0);`,
  },
  {
    id: "mandelbulb",
    label: "Mandelbulb",
    blurb: "The classic power-eight bulb, breathing with the voice.",
    dist: 3.0,
    steps: 96,
    relax: 0.75,
    map: `
      vec3 z = p; float dr = 1.0; float r = 0.0;
      float power = 7.0 + sin(uTime*0.2)*1.0 + uLevel*1.5;
      for (int i=0;i<7;i++){
        r = length(z);
        if (r > 2.0) break;
        float th = acos(clamp(z.z/r,-1.0,1.0));
        float ph = atan(z.y, z.x);
        dr = pow(r, power-1.0)*power*dr + 1.0;
        float zr = pow(r, power);
        th *= power; ph *= power;
        z = zr*vec3(sin(th)*cos(ph), sin(ph)*sin(th), cos(th)) + p;
      }
      return vec2(0.5*log(max(r,1e-6))*r/dr, 0.0);`,
  },
  {
    id: "gyroid",
    label: "Gyroid weave",
    blurb: "A minimal surface that looks woven because, mathematically, it is.",
    dist: 3.4,
    relax: 0.6,
    map: `
      vec3 q = p; q.xz = rot(uTime*0.1)*q.xz;
      float f = 3.0 + uLevel*1.2;
      float g = dot(sin(q*f), cos(q.zxy*f));
      float shell = length(p) - 1.35;
      return vec2(max(shell, abs(g)*0.28 - 0.06), 0.0);`,
  },
  {
    id: "kifs-tetra",
    label: "KIFS tetra",
    blurb: "Sierpinski folding — the sharpest, most crystalline option.",
    dist: 4.4,
    relax: 0.7,
    map: `
      // Sierpinski tetrahedron by nearest-vertex folding. Each pass scales by
      // two toward the closest of the four corners, so the scale factor has to
      // be divided back out at the end for the distance to stay honest.
      vec3 v1 = vec3( 1.0,  1.0,  1.0);
      vec3 v2 = vec3(-1.0, -1.0,  1.0);
      vec3 v3 = vec3( 1.0, -1.0, -1.0);
      vec3 v4 = vec3(-1.0,  1.0, -1.0);
      vec3 q = p;
      q.xz = rot(uTime*0.15)*q.xz;
      q.yz = rot(0.35)*q.yz;
      float s = 1.0;
      for (int i=0;i<9;i++){
        vec3 c = v1; float best = length(q - v1);
        float d2 = length(q - v2); if (d2 < best){ c = v2; best = d2; }
        float d3 = length(q - v3); if (d3 < best){ c = v3; best = d3; }
        float d4 = length(q - v4); if (d4 < best){ c = v4; }
        q = 2.0*q - c;
        s *= 2.0;
      }
      return vec2(length(q)/s - (0.004 + uLevel*0.010), 0.0);`,
  },
  {
    id: "wire-globe",
    label: "Wire globe",
    blurb: "A latitude/longitude cage — the loom's warp, closed into a sphere.",
    dist: 3.3,
    relax: 0.8,
    map: `
      vec3 q = p; q.xz = rot(uTime*0.16)*q.xz;
      float R = 1.15 + uLevel*0.06;
      float shell = abs(length(q) - R) - 0.012;
      float lat = abs(sin(asin(clamp(q.y/max(length(q),1e-4),-1.0,1.0))*10.0));
      float lon = abs(sin(atan(q.z,q.x)*12.0));
      float cage = min(lat, lon);
      return vec2(max(shell, cage*0.32 - 0.05), 1.0);`,
  },
  {
    id: "tunnel",
    label: "Kaleido shards",
    blurb: "The mirror dimension: eight blades folded around a lit core.",
    dist: 3.9,
    steps: 90,
    relax: 0.55,
    map: `
      vec3 q = p;
      q.xz = rot(uTime*0.12)*q.xz;
      q = foldN(q, 8.0);
      q.x -= 1.05;
      // Spin each blade about its own radial axis. Rotating in xy instead
      // tips them all outward and the ring collapses into a crown.
      q.yz = rot(uTime*0.35)*q.yz;
      float blade = sdBox(q, vec3(0.05, 0.40 + uLevel*0.20, 0.20));
      float core  = sdSphere(p, 0.46 + uLevel*0.12);
      float ring  = sdTorus(p, vec2(1.05, 0.020));
      float solid = min(blade, core);
      return ring < solid ? vec2(ring, 1.0) : vec2(solid, 0.0);`,
  },
  {
    id: "plasma-veil",
    label: "Plasma veil",
    blurb: "Volumetric cloud — no surface at all, only density.",
    dist: 3.2,
    steps: 120,
    relax: 1.0,
    // Nothing is ever hit here, so the march must stride rather than settle.
    minStep: 0.045,
    map: `
      vec3 q = p*2.2;
      q.y -= uTime*0.35;
      q.xz = rot(uTime*0.15)*q.xz;
      float n = fbm(q, 0.75);
      float shell = length(p) - (1.28 + uLevel*0.14);
      // Density, not surface: the marcher reads how far *inside* it is, so the
      // noise has to shift that depth rather than carve a boundary.
      float dens = 0.03 - n*(0.17 + uLevel*0.09);
      return vec2(max(shell, dens), 2.0);`,
  },
  {
    id: "torus-weave",
    label: "Torus weave",
    blurb: "Interlocking rings that pass over and under each other.",
    dist: 3.9,
    relax: 0.85,
    map: `
      float d = 1e9;
      for (int i=0;i<4;i++){
        float fi = float(i);
        vec3 q = p;
        q.xz = rot(uTime*0.18 + fi*1.5708)*q.xz;
        q.yz = rot(1.0 + fi*0.4)*q.yz;
        d = smin(d, sdTorus(q, vec2(1.0 + sin(uTime*0.3+fi)*0.06, 0.075)), 0.12);
      }
      return vec2(d, 1.0);`,
  },
  {
    id: "ribbon-orbit",
    label: "Ribbon orbit",
    blurb: "Flat ribbons of thread streaming around a small core.",
    dist: 3.8,
    relax: 0.8,
    map: `
      float dc = length(p) - (0.34 + uLevel*0.10);
      float dr = 1e9;
      for (int i=0;i<3;i++){
        float fi=float(i);
        vec3 q=p;
        q.yz = rot(0.5+fi*0.7)*q.yz;
        q.xz = rot(uTime*(0.3+fi*0.12)+fi*2.1)*q.xz;
        float wob = sin(atan(q.z,q.x)*3.0 + uTime)*0.10;
        vec2 c = vec2(length(q.xz) - (1.05+fi*0.22+wob), q.y);
        dr = min(dr, max(abs(c.x)-0.008, abs(c.y)-0.16));
      }
      return dc < dr ? vec2(dc,0.0) : vec2(dr,1.0);`,
  },
  {
    id: "liquid-metal",
    label: "Liquid metal",
    blurb: "A mirror-smooth droplet, rippling with what it hears.",
    dist: 3.1,
    relax: 0.75,
    shade: `
      vec3 refl = reflect(rd, n);
      float band = 0.5 + 0.5*sin(refl.y*6.0 + uTime*0.6);
      col = mix(thread*0.35, tint, band);
      col += vec3(1.0)*pow(1.0-clamp(dot(n,-rd),0.0,1.0), 4.0)*0.6;`,
    map: `
      vec3 q = p*2.6 + vec3(0.0, uTime*0.4, 0.0);
      float amp = 0.09 + uLevel*0.16;
      float d = length(p) - (1.0 + amp*fbm(q, 0.4));
      return vec2(d*0.5, 0.0);`,
  },
];