Whirlpool (palette sweep)

Rather than hard-code one of the palettes, cycle through all five and wipe between them with a sweep transition. Three pieces, shown via #+attr_diff as a changeset from the palettes shader:

  • The palettes page’s palette(int i, vec2 q) dispatcher is already in place; here the index comes from iTime rather than the @select selector, so the palette advances on its own instead of waiting for a pick.
  • iTime / HOLD is split by floor / fract into a current palette index and how far we are through its slot. The next index is just current + 1 (mod 5), so the cycle wraps seamlessly: the palette a slot ends on is the one the next slot begins with.
  • fract holds still for the first part of each slot, then a smoothstep ramps a moving boundary across a sweep coordinate swp, mixing the next palette in behind an fwidth-antialiased edge. Here swp is a diagonal wipe; swap it for uv.x (vertical) or an angle for a rotating, radar-style wipe.
Diff: whirlpool-palettes-shader → whirlpool-palette-sweep-shader
--- whirlpool-palettes-shader
+++ whirlpool-palette-sweep-shader
@@ -1,37 +1,46 @@
 // no function pointers in GLSL ES — palettes inline, dispatched by index
 // each maps the antialiased quadrant q (q.x = x-bit, q.y = y-bit) to a color
 vec3 palette(int i, vec2 q){
   if(i == 0) return vec3(q, 0.0);                             // square
   if(i == 1) return vec3(q, q.x + q.y - 2.0*q.x*q.y);         // tetra
   if(i == 2) return vec3(q, 1.0 - (q.x + q.y - 2.0*q.x*q.y)); // rgbw
   if(i == 3) return vec3((2.0*q.x + q.y) / 3.0);              // gray
   return vec3(1.0 - q.x, 1.0 - q.y, 1.0);                     // cmy
 }

-// a controls-panel dropdown picks the palette by index (default 1 = tetra)
-// @select iPalette 1 square tetra rgbw gray cmy
-uniform int iPalette;
-
 void mainImage( out vec4 fragColor, in vec2 fragCoord )
 {
   vec2 uv = fragCoord/iResolution.xy; // <0,1>

   vec2 p = uv - 0.5; // recenter so the swirl pivots on the middle
   float r = length(p); // distance from the center
   float a = atan(p.y, p.x); // current angle

   // twist harder near the center; wrap the time term to one full turn so
   // cos/sin never see a large argument (one revolution looks identical)
   a += 6.0 * exp(-3.0 * r) + mod(iTime * 0.3, 6.2831853);

   // rebuild swirled coordinates back in <0,1>
   vec2 sw = vec2(cos(a), sin(a)) * r + 0.5;

-  vec3 col = vec3(0); // start with black
   vec2 aa = fwidth(sw); // pixel footprint of the swirled coords
   vec2 e = smoothstep(0.5 - aa, 0.5 + aa, sw); // antialiased quadrant edge
-  col = palette(iPalette, e); // palette chosen live from the Settings panel
+
+  // cycle the 5 palettes, one sweep wipe per slot
+  const float HOLD = 2.5; // seconds per palette (hold + wipe)
+  float T = iTime / HOLD;
+  int i0 = int(mod(floor(T), 5.0)); // current palette
+  int i1 = int(mod(floor(T) + 1.0, 5.0)); // next palette
+  float sweep = smoothstep(0.6, 1.0, fract(T)); // hold, then wipe the last 40%
+
+  // diagonal wipe coord — try uv.x, or atan(p.y,p.x)/6.2831853+0.5
+  float swp = (uv.x + uv.y) * 0.5;
+  float band = fwidth(swp) * 1.5; // antialias the wipe edge
+  // 1 once the sweep has passed this pixel
+  float toNew = smoothstep(swp - band, swp + band, sweep);
+
+  vec3 col = mix(palette(i0, e), palette(i1, e), toNew);

   // Output to screen
   fragColor = vec4(col,1.0);
 }
// settings
theme:
fx: