Whirlpool (looping transport)
The palette sweep already repeats — the five palettes cycle every 5 * HOLD units of iTime — but the engine doesn’t know that, so it just lets
iTime run forever. Tell it the period with a @loop directive and two things
change: iTime wraps to [0, DURATION), and the controls panel grows a
transport scrubber across the top. Drag it (or focus it and arrow-key) to
seek anywhere in the loop — accrual pauses while you scrub and resumes from
wherever you let go, video-player style.
For the loop to be seamless the whole frame must be periodic, so this changeset
(shown via #+attr_diff from the palette sweep) also pins the swirl to exactly
one revolution per loop — its old iTime * 0.3 drift never closed the circle.
The period is then just the palette cycle, LOOP = 5 * HOLD = 12.5:
--- whirlpool-palette-sweep-shader
+++ whirlpool-loop-shader
@@ -1,46 +1,52 @@
// 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
}
+// iTime is periodic: wrap it and show a transport scrubber in the panel.
+// 12.5 = 5 palettes * HOLD (2.5) — one full palette cycle.
+// @loop 12.5
+
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);
+ const float HOLD = 2.5; // seconds per palette (hold + wipe)
+ const float LOOP = 5.0 * HOLD; // one full cycle of all five palettes
+
+ // twist harder near the center; spin exactly one full turn per loop so the
+ // rotation closes seamlessly (the old iTime*0.3 never landed on a whole turn)
+ a += 6.0 * exp(-3.0 * r) + mod(iTime * (6.2831853 / LOOP), 6.2831853);
// rebuild swirled coordinates back in <0,1>
vec2 sw = vec2(cos(a), sin(a)) * r + 0.5;
vec2 aa = fwidth(sw); // pixel footprint of the swirled coords
vec2 e = smoothstep(0.5 - aa, 0.5 + aa, sw); // antialiased quadrant edge
// 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);
}
|
|
// 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
}
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;
vec2 aa = fwidth(sw); // pixel footprint of the swirled coords
vec2 e = smoothstep(0.5 - aa, 0.5 + aa, sw); // antialiased quadrant edge
// 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);
}