Getting Started
Plasmodial Slime Simulation is a Unity project that models the behavior of plasmodial slime molds like physarum polycephalum. It was heavily inspired by Sebastian Lague's video, Coding Adventure: Ant and Slime Simulations.
Credits: This project was originally created by Praccho Muna-McQuay, Alaina Lin, and myself for our CSCI 1230 (Computer Graphics) final project at Brown University.
Features
- Support for up to four different slime species at once.
- Foraging behavior based on an attractor-field of food sources.
- Brushes to paint and erase food/slime on the simulation canvas.
- Starting seed library to begin the simulation, including a circle, big-bang, and starburst.
- Rapid simulation supporting over 1-million concurrent slime agents using parallel computations on the GPU.
- Customizable parameters for nearly every aspect of the simulation displayed on an interactive GUI.
Installation
- Install Unity 2022.3.14f1.
- Clone the project repository:
git clone https://github.com/starboi-63/plasmodial-slime
- Open the cloned project's root directory,
plasmodial-slime
, in Unity and double click theGUI
scene inAssets/Scenes
near the bottom of the screen.
Usage
Running the Simulation
To start the simulation, simply click the play
button at the top of the screen (shown below). Alternatively, click Build and Run
to execute the simulation outside of the editor.
Modifying Settings
Use the panel on the left to adjust simulation settings, including slime agent initialization patterns, species parameters, food sources, and brush settings. Each of these settings is described in detail below.
Demo
Once the simulation is running, you can freely interact with it in real-time by clicking and dragging with your mouse on the canvas.
How it Works
Slime Agents
The slime that appears in the simulation is composed of hundreds of thousands of individual entities called slime agents. These agents move according to a fixed set of rules that determine their behavior frame by frame.
As slime agents move, they deposit chemoattractant on the canvas which is detected by other nearby agents via three sensors (front-left, front, and front-right). This slime agent behavior is implemented based on Characteristics of pattern formation and evolution in approximations of physarum transport networks (Jones).
In particular, a slime agent is defined by the following properties:
1struct SlimeAgent {
2 float2 position; // (x,y) in canvas space
3 float angle; // direction agent faces in radians
4 int speciesID; // identifying index into species buffer
5 float hunger; // number in range [0-1]: 1 is full, 0 is starving
6};
7
8struct SpeciesSettings {
9 float sensorAngle; // angle offset of left/right sensors in radians
10 float rotationAngle; // should be bigger than sensorAngle to avoid convergence; adjusted by random offset in compute shader
11 int sensorDist; // distance from agent position to sensor
12 int sensorRadius; // radius of an individual sensor circle (similar to SW)
13 float velocity; // speed of agent
14 float trailWeight; // weight of deposited trail (i.e. chemoattractant)
15 float hungerDecayRate; // rate at which hunger increases
16 float4 color; // RGBA color of agent
17};
Visually, a slime agent of a particular species is represented by the following diagram from Jones:
It's important to note that we use circular sensors to detect deposited chemoattractant on the trail-map rather than the square sensors used in the original paper. Hence, our implementation uses a parameter called sensorRadius
to define the radius of an individual sensor circle instead of the Sensor Width (SW) in the diagram.
Each iteration of the simulation, an agent can either turn left, turn right, turn in a random direction, or stay facing in the same direction. The agent's movement is determined by comparing the chemoattractant levels detected by its three sensors. Chemoattractant deposited on the canvas is stored in an image texture called the trail-map which is updated every iteration.
The precise calculation of an agent's movement is given by the following snippet:
1// random weight for turning
2int rand = hash(agent.position.y * width + agent.position.x + hash(id.x + time * 100000));
3
4// sample the trail-map at the three sensor positions
5float leftSample = sampleTrail(agent, agentSettings.sensorAngle);
6float midSample = sampleTrail(agent, 0);
7float rightSample = sampleTrail(agent, -agentSettings.sensorAngle);
8
9// calculate turn weight and angle
10float turnWeight = scaleToRange01(rand);
11float turnAngle = agentSettings.rotationAngle;
12
13// determine agent movement based on sensor readings
14if ((midSample > leftSample) && (midSample > rightSample)) {
15 // don't turn
16 slimeAgents[id.x].angle += 0;
17} else if ((midSample < leftSample) && (midSample < rightSample)) {
18 // turn randomly
19 slimeAgents[id.x].angle += 2 * (turnWeight - 0.5) * turnAngle * dt;
20} else if (leftSample > rightSample) {
21 // turn left
22 slimeAgents[id.x].angle += turnWeight * turnAngle * dt;
23} else if (rightSample > leftSample) {
24 // turn right
25 slimeAgents[id.x].angle -= turnWeight * turnAngle * dt;
26} else {
27 // do nothing
28 slimeAgents[id.x].angle += 0;
29}
After updating the agent's angle, we calculate the new position of the agent based on its velocity, effectively stepping the agent forward in the direction it is facing similar to an Euler integration step.
Food Sources
Food sources in the simulation are discrete points on the canvas which create an attractor field that affects slime agent direction based on the inverse-square law. This food behavior is implemented based on Stepwise slime mould growth as a template for urban design (Kay, Mattacchione, Katrycz, Hatton).
A food source is defined by the following properties:
1struct FoodSource {
2 float2 position; // (x,y) in canvas space
3 float attractorStrength; // strength of attractor field
4 int amount; // amount of food left at the source
5};
While the simulation is running, each slime agent calculates the direction from its position, , to that of the nearest food source, , and adjusts its movement based on the attractor field strength, . If it's close enough to the food source, the agent will also consume the food and reset its hunger to full, allowing it to continue foraging away from the source.
We calculate the perceived food force on an agent, , by the following formula:
The implementation of this calculation is given below:
1// after finding the closest food source, calculate direction and distance
2float2 posToClosestFood = closestFood.position - agent.position;
3float dist = length(posToClosestFood);
4float2 dir = normalize(posToClosestFood);
5
6// if the agent is close enough to the food source, eat it
7if (dist < 1) {
8 if (foodDepletionEnabled && closestFood.amount > 0) {
9 // atomic decrement to avoid race conditions
10 InterlockedAdd(foodSources[closestFoodIndex].amount, -1);
11 }
12
13 if (foodSources[closestFoodIndex].amount <= 0) {
14 foodSources[closestFoodIndex].attractorStrength = 0;
15 }
16
17 // reset agent hunger to full
18 slimeAgents[agentIdx].hunger = 1.0;
19}
20
21return dir * (closestFood.attractorStrength / (dist * dist));
The angle of this scaled direction vector in radians, , is then blended with the slime agent's current angle, , based on the agent's hunger level. The agent's hunger level, , is a value between 0 and 1, where 1 is full and 0 is starving.
Let denote the blending constant. Then, the agent's new angle after food force is considered is:
Like before, here is the implementation of this calculation:
1// initialize force on agent to zero
2float2 forceOnAgent = float2(0.0, 0.0);
3
4// calculate attractive force from nearest food source (snippet above)
5if (numFoodSources > 0) {
6 forceOnAgent = foodForce(id.x);
7}
8
9// blend force with agent's current angle based on hunger level
10if (length(forceOnAgent) > 0) {
11 float forceComponent = length(forceOnAgent);
12 float hungerComponent = pow(1.0 - agent.hunger, 8);
13 float blendValue = clamp(forceComponent * hungerComponent, 0.0, 1.0);
14 slimeAgents[id.x].angle = (blendValue * atan2(forceOnAgent.y, forceOnAgent.x)) + ((1.0 - blendValue) * slimeAgents[id.x].angle);
15}
16
17// move agent forward in the direction it is facing
18step(id.x);
19
20// decay agent hunger
21slimeAgents[id.x].hunger = clamp(slimeAgents[id.x].hunger - agentSettings.hungerDecayRate * dt, 0.0, 1.0);
Project Structure
C# Files
In Assets/Scripts
:
SlimeSimulation.cs
: main entry point running the simulation.SimulationSettings.cs
: global settings for the simulation.SlimeAgent.cs
: definition of a single slime agent.ComputeUtilities.cs
: utilities for creating and writing to textures.FoodSource.cs
: definition of a single food source.
Compute Shaders
In Assets/Shaders
:
SlimeSimulation.compute
: multiple entry points (i.e. kernels) for slime agent logic and reading/writing from textures using parallel computations on the GPU.
Final Remarks
Thank you for reading this documentation! If you have any questions or feedback, please feel free to create an issue on the project repository. I hope you enjoy experimenting with the simulation and creating your own unique patterns with slime friends :).