Slime Mold Simulation
Emergent Behavior from Simple Rules
This project simulates the behavior of slime mold (Physarum polycephalum) using simple agent-based rules. Thousands of particles follow pheromone trails, creating beautiful organic patterns that emerge naturally from local interactions.
Inspiration
Physarum polycephalum is a single-celled organism that exhibits remarkable problem-solving abilities. Despite having no brain, it can find the shortest path between food sources and has even been used to model efficient transportation networks.
This simulation recreates its behavior computationally, demonstrating how complex patterns can emerge from simple rules.
A massive thanks to the incredible work of Sebastian Lague. He has provided me with so much inspiration. His original implementation can be found here and was the main inspiration for this project.
The Agent Model
Each "slime" agent is surprisingly simple - it only knows two things:
- Position: Where it is on the canvas (x, y coordinates)
- Angle: Which direction it's facing
Agent Class
class Agent {
PVector position;
float angle;
Agent(float x, float y, float a) {
position = new PVector(x, y);
angle = a;
}
}
That's it! We just position and direction.
The Simulation Loop
Each frame, every agent performs three steps:
- Sense - Check pheromone levels at three sensors
- Steer - Turn toward the direction with highest concentration
- Move - Take a step forward and deposit pheromone
Main Loop
void draw() {
background(0);
// Agent loop
for (Agent a : agents) {
steer(a); // Decide direction
int index = a.update(); // Move forward
displayArray[index] = 255; // Deposit pheromone
}
processDisplay(); // Diffuse and evaporate
updateDisplay(); // Render to screen
}
Sensing: The Three-Sensor Model
Each agent has three "sensors" that detect pheromone concentration:
- Forward sensor: Directly ahead of the agent
- Left sensor: Offset by
sensorAngleto the left - Right sensor: Offset by
sensorAngleto the right
Sensing Function
float sense(Agent a, float sensorAngleOff) {
// Calculate sensor position
float sensorAngle = a.angle + sensorAngleOff;
PVector sensorDir = new PVector(cos(sensorAngle), sin(sensorAngle));
PVector sensorCenter = sensorDir.mult(sensorDst).add(a.position.copy());
// Sum pheromone in sensor area
float sum = 0;
for (int xOff = -sensorSize; xOff <= sensorSize; xOff++) {
for (int yOff = -sensorSize; yOff <= sensorSize; yOff++) {
PVector sample = new PVector(sensorCenter.x + xOff,
sensorCenter.y + yOff);
if (inBounds(sample)) {
sum += displayArray[getIndex(sample)];
}
}
}
return sum;
}
The sensor samples a small area (determined by sensorSize) at a distance
sensorDst away from the agent.
Steering: Following the Trail
The steering logic compares the three sensor readings and decides which way to turn:
Steering Decision Tree
void steer(Agent a) {
float weightForward = sense(a, 0);
float weightLeft = sense(a, sensorAngle);
float weightRight = sense(a, -sensorAngle);
float randomSteer = random(1);
// If forward is strongest, go straight
if (weightForward > weightLeft && weightForward > weightRight) {
a.angle += 0;
}
// If forward is weakest, turn randomly
else if (weightForward < weightLeft && weightForward < weightRight) {
a.angle += 2 * (randomSteer - 0.5) * turnSpeed * dt;
}
// If left is stronger, turn left
else if (weightLeft > weightRight) {
a.angle -= randomSteer * turnSpeed * dt;
}
// If right is stronger, turn right
else if (weightLeft < weightRight) {
a.angle += randomSteer * turnSpeed * dt;
}
}
Notice the randomness! Even when turning, agents add some random variation. This prevents the system from getting stuck in repetitive patterns.
Pheromone Dynamics
The pheromone field is where the magic happens. It's a 2D array where each cell stores a brightness value (0-255). Each frame, two processes occur:
Diffusion
Pheromones spread to neighboring cells using a 3×3 blur:
// Average of 9 neighboring cells
float blur = sum / 9;
// Blend original with blurred value
float diffuse = lerp(originalValue, blur, diffuseSpeed * dt);
Evaporation
Pheromones gradually fade over time:
// Reduce brightness, but don't go negative
float evap = max(0, diffuse - evapSpeed * dt);
The balance between diffusion and evaporation is critical. Too much diffusion creates uniform blur. Too much evaporation erases trails before agents can follow them.
Key Parameters
| Parameter | Value | Effect |
|---|---|---|
colonySize |
5000 | Number of agents in simulation |
sensorAngle |
6 | Angle offset for left/right sensors (radians) |
sensorDst |
10 | How far ahead sensors look (pixels) |
turnSpeed |
0.5 | Maximum turning rate per frame |
diffuseSpeed |
0.05 | Rate of pheromone spreading |
evapSpeed |
0.8 | Rate of pheromone fading |
Emergent Patterns
The simulation produces several characteristic patterns:
Network Formation
Agents naturally organize into branching networks as they reinforce successful paths and abandon dead ends.
Pulsing Behavior
Dense regions can exhibit pulsing as agents cycle between following and dispersing.
Self-Avoiding Paths
With the right parameters, agents spread out to explore while maintaining connectivity.
Why It Works
The simulation demonstrates stigmergy - indirect coordination through environmental modification. Agents don't communicate directly; they leave traces that influence future behavior of themselves and others.
This principle appears throughout nature: ant colonies, termite mounds, and even human systems like footpaths through parks ("desire lines").
Technical Details
- Platform: Processing (Java-based creative coding)
- Renderer: P2D (hardware-accelerated 2D)
- Resolution: 600×600 pixels
- Agents: 5,000 simultaneous
- Boundaries: Agents bounce and randomize direction at edges