Ollin

Differential growth

A line of connected nodes grows and folds into organic, brain-coral structure. On each step, three forces nudge every node, and an edge that stretches too far splits into new nodes. The line therefore gets longer and buckles, the way a leaf margin, a coral, or a convoluted cortex does.

Unlike the one-shot generators, circle packing and L-systems, this is a stateful stepper. You build a DifferentialGrowth once, hold it, and advance it each frame. It is seeded, so the same seed grows the same form. The evolving line is ordinary geometry (nodes and contour), so it feeds stroking, filling, the shape booleans, hatching, and SVG export.

Contents

Building and stepping

DifferentialGrowth is a class. You create it once, usually with the ring or line factory below, and then step it in draw(). nodes is the current line, and contour wraps that line as a Contour.

final class Coral: Sketch {
    // Built once, seeded, held across frames:
    let growth = DifferentialGrowth.ring(center: Vector2(540, 540), radius: 70,
                                         count: 40, seed: 7, maxNodes: 6000)

    override func draw() {
        growth.step(4)                      // a few sub-steps per frame
        background(.black)
        noFill(); stroke(.white); strokeWeight(2); strokeJoin(.round)
        drawPolyline(growth.nodes, closed: true)
    }
}

step() advances one step, and step(n) runs n steps at once, so more steps per frame grow the form faster. The form evolves through draw(), so it animates on its own. A small ring at the start becomes a dense coral as it runs. For a still image at a chosen moment, export a specific --frame.

Five small panels showing the same ring at step 0, 80, 180, 320, and 500: a circle wobbles, then folds into a dense meandering coral-like blob

Seed factories: ring and line

Two starting shapes are built in. Both take the same tuning parameters, which are described below.

DifferentialGrowth.ring(center: Vector2, radius: Double, count: Int = 24, seed: Int = 0, …) -> DifferentialGrowth
DifferentialGrowth.line(from: Vector2, to: Vector2, count: Int = 8, seed: Int = 0, …) -> DifferentialGrowth
  • ring seeds a closed circle that buckles into folds as it grows. This is the classic differential-growth shape.
  • line seeds a straight open line with its endpoints pinned, so it grows into a meandering folded ribbon between them.

To grow from your own outline, build the stepper directly from a point list with DifferentialGrowth(nodes: myPoints, closed: true, seed: 7).

Tuning the forces

A handful of parameters set the look. You can pass each one to a factory, and each one is also a var on the instance.

ParameterEffect
maxSegmentLengthan edge longer than this splits and inserts a node. Smaller values give finer, denser folds
repulsionRadiushow far a node's push reaches. Larger values give looser, rounder folds
attractionpull toward the path-neighbors, which holds the line together
repulsionpush from nearby nodes, which drives the buckling
alignmentpull toward the neighbors' midpoint, which smooths the curve
jittera small random nudge each step, so a symmetric ring breaks and folds
growthRateextra nodes added at random edges each step, for faster and more asymmetric growth
maxNodesa ceiling on the node count. Growth stops and the line settles once it is reached
hasFixedEndsfor an open path, whether the two endpoints are pinned where they started
boundsan optional rectangle the nodes stay inside

Repulsion is broad-phased through a uniform spatial hash, so the cost stays close to linear in the node count. See the DifferentialGrowth example.


Related: Circle packing and L-systems, the one-shot generative-geometry siblings; Geometry, the Contour type and the shape booleans the line feeds; Physics, the other stateful, stepped simulation.

↑↓ move ↩ open