// Flow family: spiral arms winding into a slowly wandering core. // // Differential rotation is the point — the inner arms turn faster than the outer // ones, so the arms wind up over time and the image is never twice the same. // Distinct from Curl Flow (isotropic advection with no centre) and Kaleido // Tunnel (rigid radial symmetry receding): this has one centre, real shear, and // no symmetry at all. // // The core is drawn in the track's signature form, so the eye of a hexagonal // track's vortex is a hexagon. export const vortexDrift = { name: 'Vortex Drift', family: 'flow', kind: 'fragment', consumes: ['cast', 'ink'], traits: ['shape', 'camera', 'style'], params: { arms: { type: 'int', range: [1, 7], default: 3, uniform: 'u_arms', bias: 'density' }, winding: { type: 'float', range: [0.5, 7], default: 2.5, uniform: 'u_winding', bias: 'density' }, shear: { type: 'float', range: [0.1, 2.5], default: 1.0, uniform: 'u_shear' }, speed: { type: 'float', range: [0.02, 0.6], default: 0.15, uniform: 'u_speed', bias: 'motion', rate: true }, turbulence:{ type: 'float', range: [0, 1.2], default: 0.4, uniform: 'u_turbulence' }, core: { type: 'float', range: [0.0, 0.4], default: 0.12, uniform: 'u_core', bias: 'energy' }, falloff: { type: 'float', range: [0.2, 2.0], default: 0.8, uniform: 'u_falloff' }, palette: { type: 'palette', count: 5 }, }, reactive: { core: { feature: 'beat', amount: 0.3, response: 'spike' }, turbulence: { feature: 'bandMid', amount: 0.3, response: 'smooth' }, }, shader: ` vec4 scene(vec2 uv, vec2 p) { float t = u_time * u_speed + u_seed; p = sigCamera(p); // The eye wanders, so the composition never sits still even when the arms do. vec2 eye = vec2(sin(t * 0.6) * 0.22, cos(t * 0.47) * 0.16); vec2 q = p - eye; float r = max(length(q), 1e-4); float a = atan(q.y, q.x); // Differential rotation: angular speed falls off with radius, so the arms // shear. Bounded rather than 1/r, or the core would spin arbitrarily fast. float omega = u_shear / (0.35 + r * 1.6); float wound = a + omega * t * 2.0 + log(r + 0.25) * u_winding; // Turbulence breaks the arms into filaments instead of clean spokes. wound += (fbm(q * 3.0 + t * 0.4, 4) - 0.5) * u_turbulence * 2.0; float arms = max(float(u_arms), 1.0); float band = sin(wound * arms) * 0.5 + 0.5; band = pow(band, 1.8); // Density falls off outward, so the frame has a subject. float envelope = exp(-r * u_falloff); vec3 col = pal(0) * 0.06; col += palRamp(wound * 0.08 + r * 0.2) * band * envelope * 0.9; col += pal(4) * pow(band, 4.0) * envelope * 0.5; // The eye itself, in the signature form. if (u_core > 0.004) { float d = castMain(q / u_core) * u_core; col = mix(col, pal(0) * 0.05, smoothstep(0.004, -0.01, d)); col += pal(3) * inkStroke(d) * 0.8; col += pal(4) * exp(-max(d, 0.0) * 18.0) * 0.35; } return vec4(inkValue(col), 1.0); } `, }; export default vortexDrift;