Space Colonization: Growing Mycelium and Vein Networks

After reading this you will be able to grow a branching network with the space-colonization algorithm, predict how the attractor count, growth step and reach reshape it, and read the finished tree the way a botanist reads leaf venation.

What the algorithm does

Fungal hyphae, plant roots and leaf veins all face one geometric task: reach every corner of a region with a connected, non-crossing network that wastes as little material as possible. The space-colonization algorithm of Runions, Lane and Prusinkiewicz (2007) solves that task with a single rule repeated hundreds of times.

Start by scattering a cloud of nutrient attractors through a shape (a disk, a leaf outline, a ring). Place one seed node. Every step, each attractor looks for the nearest network tip within its reach and tugs on it. Each tip that has been chosen by at least one attractor moves one step in the average direction of its pulls. When a tip comes within the absorb radius of an attractor, that attractor is consumed and stops pulling. Growth halts when no attractor can reach any tip, so the network fills the space and then stops on its own.

The result looks alive because it is not drawn, it is grown. No branch is placed by hand. The same rule that fills a disk with a fungal mat fills a leaf with veins, because both problems are the same problem.

When to reach for this model

Use space colonization when you want a branching structure whose overall shape is set by where the resources are, not by a fixed grammar. It is the right model for leaf venation, root systems, fungal mats, river deltas run in reverse, and vascular trees inside an organ.

Reach for something else when the branching pattern is self-similar and rule-driven rather than space-driven. A fern frond, where each subunit repeats the whole at smaller scale, is better described by L-System Plants. If you want a real forager that also finds shortest paths and prunes weak routes, look at Slime Mould Networks or Ant Colony Pheromone Trails, which reinforce edges over time. Space colonization never removes a branch once grown; it only adds.

The attractors are a modelling device, not a claim about biology. Real hyphae do not sense a fixed lattice of pre-placed food points. The attractor cloud is a compact way to encode "here is unfilled space worth reaching," and it happens to reproduce vein spacing and branch angles well.

The growth rule as math

Let the network be a set of nodes. Each step has three parts: association, growth, and absorption.

Association. For each attractor a, find the single closest node v whose distance is within the reach radius d_i. That attractor is now associated with that one node.

Growth. A node v that has a non-empty set of associated attractors A(v) grows one new node in the average of the unit directions toward those attractors.

\hat{n} = \frac{\sum_{a \in A(v)} \frac{a - v}{\lVert a - v \rVert}}{\left\lVert \sum_{a \in A(v)} \frac{a - v}{\lVert a - v \rVert} \right \rVert}

Here a - v is the vector from node to attractor, and dividing by its length \lVert a - v \rVert makes it a unit vector, so a far attractor and a near one pull equally hard in direction. The sum of unit vectors is renormalised to give a single unit heading \hat{n}. The new node sits at

v' = v + D \, \hat{n}

where D is the growth step. A larger step covers ground faster and produces coarser, straighter branches; a smaller step gives finer, more sinuous filaments and more nodes.

Absorption. After growth, any attractor within the kill (absorb) radius d_k of any node is removed. Choosing d_k as a small multiple of D keeps growth from stalling: if the kill radius is smaller than one step, tips can orbit an attractor without ever consuming it.

Why branches split

Branching is automatic. When a node's associated attractors fall into two well-separated clusters, the averaged direction \hat{n} points into the gap between them, which satisfies neither cluster. In the next steps the two child nodes each capture one cluster, and the single tip has become a fork. No explicit "branch now" rule is needed.

Vein-order thickness

A grown skeleton is a tree of one-pixel lines until you assign width. This tool sizes each branch by counting descendants. Let c(v) be the number of nodes downstream of v back toward the tips. A tip has c = 1. A branch width follows a taper

w(v) = w_{\text{tip}} \cdot c(v)^{1/n}

with exponent n \approx 2 to 3. This mirrors Murray's law, which states that in an efficient transport network a parent conduit's cubed radius equals the sum of its children's cubed radii:

r_{\text{parent}}^3 = \sum_{\text{children}} r_{\text{child}}^3

So a fork of two equal tips of radius r gives a parent radius of r \cdot 2^{1/3} \approx 1.26\,r, and the trunk swells geometrically toward the seed. That is why the tips glow pale and thin while the trunk reads as thick amber.

Worked example with the demo defaults

Filling a disk from one seed

Load the tool and press the demo button, which uses the field defaults: a disk shape, roughly 1200 attractors, growth step 4 pixels, reach radius 40 pixels, absorb radius 8 pixels, one central seed. Here is what happens in the first few steps and over the whole run.

  1. Step 0. One seed at the disk centre. All 1200 attractors within 40 px of the seed associate with it. Their unit vectors point outward in every direction, so they nearly cancel: \hat{n} points toward whichever side is slightly denser. The first node steps 4 px that way.
  2. Steps 1 to 20. The single filament advances into the densest local pocket. Behind it, absorbed attractors within 8 px vanish, clearing a channel roughly 16 px wide.
  3. First fork. Once the tip has cleared its immediate neighbourhood, remaining attractors lie in two directions. The averaged pull splits, and within a few steps two tips grow apart. From here the count of tips roughly doubles every time a cluster divides.
  4. Filling. With reach 40 and step 4, each tip "sees" attractors up to 10 steps ahead, so it steers smoothly rather than lurching. Absorption at 8 px (two steps) keeps consumption ahead of arrival, so growth never stalls.
  5. Termination. When every surviving attractor is beyond 40 px of every tip, or all are absorbed, the run stops. For 1200 attractors in a 400 px disk this typically leaves a few hundred nodes and a mat that reaches the rim.

The chart below shows how the number of live attractors falls as the network eats through them. Early on one tip absorbs slowly; once forking multiplies the tips, absorption accelerates, then tapers as only rim stragglers remain.

Absorption starts slow with one tip, speeds up as branches multiply near step 40 to 70, then flattens as only hard-to-reach attractors survive.

Reading the finished network

Three features tell you whether your parameters were sensible.

Vein spacing
The typical gap between parallel branches equals roughly twice the absorb radius. At 8 px you should see filaments about 16 px apart. If the mat looks solid, your absorb radius is too small for the attractor density.
Branch angle
Forks driven by two equal clusters open near 60 to 90 degrees. Very acute forks mean the reach is large relative to spacing, so tips commit to a direction late.
Coverage
A good run leaves few orphaned attractors. Many survivors near the rim mean reach was too short to bridge the last gap, or the seed sat too far from that region.

With reach 40 and step 4 on a disk of 1200 attractors, growth fills the disk in about 145 steps and leaves near-zero orphans. Halving the reach to 20 leaves clusters unreached near the rim; doubling the step to 8 gives coarser, straighter branches with wider spacing.

Common mistakes

The most common failure is an absorb radius smaller than the growth step. If d_k \lt D, a tip can step past an attractor without ever landing inside its kill radius, so the same attractor keeps pulling and the tip oscillates in place. Keep d_k at roughly 1.5 \times D to 2 \times D.

A reach radius that is too short is the second trap. If reach is smaller than the average spacing between attractors, most attractors see no tip at all and growth dies after a few steps, leaving a stub. Set reach to at least a few times the attractor spacing.

Cranking the attractor count without shrinking the step produces a dense but blobby mat, because many attractors share one tip and their pulls average to a fuzzy centre. If you want fine detail, raise the attractor count and lower the step together. Finally, remember this model only adds. It cannot reroute or prune, so it will not find shortest paths the way a real forager does.

Related tools

If branching by fixed rules interests you more than branching by space, grow ferns and trees with L-System Plants. To watch networks that actively prune and reroute, compare Slime Mould Networks and Ant Colony Pheromone Trails. For other emergent-pattern models in the same spirit, see Boids Flocking Simulator, Firefly Synchronization and Lenia Continuous Life. For population and selection dynamics rather than spatial growth, try Genetic Drift Simulator and Evolving Creatures, and for a single-cell excitable system see Neuron Action Potential.

Frequently asked questions

Why do branches never cross?

Each attractor associates with exactly one nearest tip, so two tips rarely chase the same point. As a tip advances it absorbs the attractors ahead of it, clearing the region another tip would have grown into. The cleared corridor keeps neighbours apart, which is why the mat looks non-overlapping without any explicit collision rule.

How many attractors should I use?

Match the count to the area and the detail you want. In a 400 px disk, about 1200 attractors with an 8 px absorb radius gives roughly 16 px vein spacing. Doubling the count to 2400 without changing the radius roughly halves spacing and adds fine sub-branches. Very high counts slow the run because association is checked for every attractor each step.

Does the seed position matter?

Yes. Growth reaches outward from the seed, so a central seed fills a disk symmetrically while a seed at the rim grows a fan. A leaf shape looks natural with the seed at the petiole (the stalk end), because real venation branches from that point.

Why does the trunk get so thick?

Width is assigned by counting descendants and tapering by a root of that count, following Murray's law. Every tip contributes 1, and those counts sum back toward the seed, so the trunk carries the whole tree's worth of descendants. A fork of two equal tips widens the parent by a factor of about 1.26, and this compounds along every path to the root.

Can I grow the same shape twice?

Only if the attractor positions are the same. The pattern depends entirely on where the random attractors land, so a fresh scatter gives a different network even with identical sliders. Fix the random placement to reproduce a run exactly.