The Bend
The story behind The Bend
Inspired by Meander on Wikipedia
Built with Pure WebGL2 · Instanced Thick-Line Rendering · Spatial Hash Intersection Detection
Techniques Curvature-proportional erosion · Gaussian-smoothed turning angle · Segment-segment intersection via spatial hashing · Arc-length re-spacing · Near-miss glow detection · Oxbow ghost loops
Direction A perfectly straight line is unstable. Straightness is the anomaly; meandering is the natural state.
Result A living river that grows its own bends, cuts through its own neck, and leaves ghost loops as evidence of the paths it abandoned
The Story
A river does not meander because the terrain is uneven. The terrain becomes uneven because the river meanders.
This is the counterintuitive physics of fluvial geomorphology. A perfectly straight channel flowing over a perfectly flat plain will not stay straight. Any perturbation — a fallen branch, a patch of softer sediment, an asymmetry too small to see — introduces a slight bend. Water flows faster on the outside of that bend, eroding the outer bank. Sediment deposits on the slower inner bank. The bend deepens. A deeper bend means faster outer flow, which means more erosion, which means a deeper bend. The feedback loop is self-amplifying: curvature breeds curvature.
This is not a flaw in the system. It is the system. The straight channel is the unstable equilibrium — a pencil balanced on its tip. The meander is the attractor. Given any perturbation at all, the river will find its way to sinuosity. It has no choice. The math demands it.
The meander grows until the bends become so extreme that the river loops back and nearly touches itself. When the neck between two approaching bends finally breaks through, the river takes the shortcut. The abandoned loop is severed — a cutoff. What was the active channel becomes a crescent-shaped lake, disconnected from the flow. In geography, these are called oxbow lakes. They are the ghosts of paths the river used to take, slowly silting in over decades, visible from the air as scars on the floodplain. Every landscape shaped by a river is a palimpsest of abandoned trajectories.
The Take
The experience begins with a single straight line spanning the screen — a river at time zero. For a moment, nothing happens. Then small perturbations appear, either from the auto-seeding or from the viewer tapping the screen. The perturbations are tiny. The physics are not.
Curvature-proportional erosion amplifies each wobble into a growing meander. The line develops sinuous curves that deepen over time, the bends migrating and evolving in real time. The viewer can tap anywhere to introduce new perturbations — Gaussian impulses pushed perpendicular to the curve — and watch the feedback loop transform them into full meanders within seconds. One tap becomes a bend. The bend becomes a loop. The math does the rest.
When a meander grows extreme enough that two parts of the curve approach each other, a near-miss glow appears — the curve lit in amber where it almost touches itself. The tension builds visually: you can see the cutoff coming. When the curve finally intersects itself, the loop is excised in a dramatic cutoff event. The severed loop becomes a ghost — a translucent remnant that drifts slowly and fades over fifteen seconds, an oxbow lake dissolving into the landscape’s memory. A burst of cyan particles marks the moment of severance.
The narrative text arrives in phases. “A river begins as a straight line.” Then, as the bends develop: “Any deviation amplifies itself.” As the feedback becomes visible: “Greater curvature. More erosion. Greater curvature.” After the first cutoff: “What was the path is now a ghost.” After the third: “The bend is not an error. It is how the world is shaped.”
The text is positioned to stay out of the simulation’s way — titles centered, observations at the top, reflections at the bottom — fading in and out on timed intervals or triggered by cutoff events. The narrative is the physics, stated plainly.
The Tech
Parametric Curve Simulation: Curvature-Proportional Erosion
The river is a parametric curve of 400 points in normalized [0,1] space. Each frame, the simulation computes the turning angle at each interior point — the change in direction between consecutive segments — as a proxy for signed curvature. Raw turning angles on a discrete curve are numerically unstable, so the computation runs on a Gaussian-smoothed copy of the curve (sigma=5, radius ~12 points), while the actual displacement is applied to the original unsmoothed positions. This separation — smooth for measurement, raw for movement — prevents grid-scale oscillations without over-damping the meander evolution.
The erosion rule is anti-diffusive: each point moves along its local normal proportional to the negative of the turning angle. Where the curve bends left, points migrate further left. Where it bends right, further right. This is the positive feedback loop: curvature drives displacement in the direction that increases curvature. A saturation term (1 / (1 + |curvature| * 5)) prevents runaway spiraling at extreme bends — growth slows as curvature increases, matching the physical reality that deeply curved channels develop equilibrium profiles.
Tiny noise perturbations (0.0003 amplitude, perpendicular to the local tangent) break symmetry continuously, ensuring the simulation never settles into a static state. The endpoints are pinned at x=0.05 and x=0.95, both at y=0.5 — the river enters and exits at fixed points, but everything between is free to wander.
Arc-Length Re-Spacing
Curvature-proportional erosion distorts the spacing between points — tight bends accumulate points while gentle sections stretch thin. Without correction, the curve loses resolution exactly where it needs it most. After each simulation step, the points are redistributed at uniform arc-length intervals: compute cumulative arc length, then linearly interpolate new positions at evenly spaced parametric values. This maintains consistent resolution everywhere along the curve, preventing the quality degradation that would otherwise make cutoff detection unreliable.
Self-Intersection Detection: Spatial Hashing
Every frame, the simulation checks whether the curve crosses itself. Brute-force segment-segment testing on 400 points would require ~80,000 pair checks. Instead, each segment is inserted into a spatial hash grid (cell size 0.05 in normalized space). Only segments sharing a hash cell are tested against each other, with an adjacency skip of 8 indices to avoid false positives from neighboring segments. The actual intersection test is the standard 2D segment-segment formula using the cross product to compute parameters t and u along each segment, with a hit registered when both fall in [0,1].
When an intersection is detected and the enclosed loop contains at least 12 points (the CUTOFF_MIN_LOOP threshold), the cutoff fires: the loop is excised from the active curve, converted into a ghost shape, and the splice point is smoothed to prevent a sharp kink. If the cutoff removes enough points to drop below 200, the remaining curve is subdivided by inserting midpoints between consecutive pairs, then re-spaced — maintaining simulation quality even after dramatic amputations.
Ghost Loops and Particle Bursts
Excised loops become ghost objects — arrays of points rendered as translucent thick lines in a muted blue-slate color (#596b80), starting at 40% opacity and fading linearly to zero over 15 seconds. Each ghost drifts slowly downward (0.0005 units per second) to suggest settling, sedimentation, the weight of an abandoned path sinking into the landscape. Ghosts also exert a faint gravitational pull on the active curve — points within 0.04 units of a ghost point receive a tiny velocity impulse toward the ghost, simulating the tendency of rivers to re-occupy old channels.
Cutoff events spawn 40 cyan particles at the intersection point. Each particle launches in a random direction at 0.15–0.55 units/second with a slight upward bias, subject to gravity (0.3 units/s^2) and gentle drag (0.99 per frame). Particles live 1–2 seconds, fading with a quadratic curve. They are rendered as instanced quads with additive blending, producing a brief luminous burst that marks the moment of severance.
Near-Miss Glow
Before a cutoff occurs, the curve often approaches itself without quite touching. The near-miss detector scans all point pairs separated by at least CUTOFF_MIN_LOOP indices in curve parameter but within 0.03 units in Euclidean distance. When found, a glow intensity (proportional to proximity) is spread to nearby points via a Gaussian kernel (radius 3, sigma ~2). The fragment shader blends the curve color from its default pale blue (#d1deeb) toward amber (#73a6c7) based on glow intensity squared, creating a visible tension where the curve is about to cut through itself. The glow appears before the event, building anticipation.
Instanced Thick-Line Rendering
WebGL has no native thick-line support. Each curve segment is rendered as an instanced quad — four vertices (start-top, start-bottom, end-top, end-bottom) expanded along the segment normal in pixel space. The vertex shader converts normalized positions to pixel coordinates, computes the perpendicular direction, offsets each vertex by half the line width along that perpendicular, then converts back to clip space. This produces resolution-independent thick lines with correct aspect ratio handling. The fragment shader applies anti-aliasing via smoothstep on the cross-quad interpolation (|v_side|), softening the edges from 65% to 100% of the half-width.
The active curve, ghost loops, and line rendering all share the same shader program and VAO — only the instance data, line width, and color uniforms change between draw calls. A single background pass renders the slate-dark field with animated film grain (hash-based noise at 0.03 intensity), and particle rendering uses a separate instanced program with additive blending.
Interaction: Gaussian Impulse Perturbation
Tapping or clicking the screen finds the closest curve point by 2D distance, then applies a Gaussian impulse (sigma=12 points) perpendicular to the local tangent, directed toward the tap position. The impulse is projected onto the curve’s normal direction, so tapping above the curve pushes it up and tapping below pushes it down. The impulse has both an immediate position displacement (for visual feedback) and a velocity component (damped at 0.92 per frame) that continues pushing over subsequent frames. Pointer events handle both mouse and touch input uniformly.
If the viewer does not interact within 2.5 seconds, the simulation auto-seeds 3–5 asymmetric perturbations at random positions along the curve with varying amplitudes (0.01–0.03) and spread widths (10–30 points). Ongoing micro-perturbations (1% probability per frame) keep the river alive even without user input.
This blog post was AI generated with Claude Code. Authored by Artificial Noodles.