The Foundations of Digital Particle Simulations

Creating a falling sand simulation requires a shift in perspective from traditional object-oriented programming to a grid-based cellular automata (CA) model. Unlike standard animations where objects move freely in space, this simulation treats every pixel—or a small group of pixels—as a discrete cell with a specific state. Drawing inspiration from titles like Noita and web experiments like Sandspiel by Max Bittker, we utilize the p5.js library to manage a two-dimensional array representing the canvas.
Each cell in our grid contains a value: a zero indicates an empty space, while a non-zero value represents a grain of sand. The complexity of the simulation arises not from the particles themselves, but from the rules applied to these cells every frame. By iterating through the grid and evaluating the neighbors of each cell, we can recreate the behavior of physical matter.
Key insight: In cellular automata, the 'movement' of a particle is actually the transfer of a state from one grid coordinate to another based on conditional logic.
To ensure the simulation runs smoothly, we must maintain a distinction between the current state of the grid and the state for the next frame. Modifying the grid while simultaneously reading from it can lead to unintended race conditions where a single grain of sand 'teleports' across multiple cells in a single frame. Therefore, we utilize a dual-grid system where calculations are performed on the current frame and results are stored in a buffer for the next generation.
- Initialize a 2D array to store cell states.
- Define a constant resolution (e.g., 10px squares) to balance detail and performance.
- Use nested loops to iterate through every column and row index.
- Assign state values based on user interaction or initial conditions.
Implementing Core Gravity and Movement Rules

The most fundamental rule of a falling sand simulation is vertical gravity. If a cell contains sand and the cell immediately below it is empty (state 0), the sand must move down. This is achieved by setting the current cell to empty and the target cell to sand in the next generation's grid. However, simple vertical gravity only results in stacks of single pixels that do not resemble real sand behavior.
To simulate the way sand forms piles and slopes, we introduce diagonal movement. If the cell directly below is occupied, the simulation checks the bottom-left and bottom-right neighbors. If one of these is empty, the grain slides into that spot. This transition from vertical-only logic to diagonal logic is what creates the characteristic 45-degree slopes seen in nature.
| Movement Type | Condition | Result |
|---|---|---|
| Vertical Fall | Cell below is empty (0) | Moves directly down |
| Sloping Move | Cell below is full; diagonal is empty | Moves to the empty diagonal |
| Static State | All adjacent lower cells are full | Remains in current position |
Caution: When implementing diagonal movement, it is vital to handle edge cases where the 'neighbor' might be outside the bounds of the array, which would otherwise trigger an error.
To prevent the simulation from having a visual bias—such as sand always sliding to the right before checking the left—we introduce stochastic logic. By using a random function to decide whether to check the bottom-left or bottom-right first, the simulation achieves a natural, symmetrical distribution of sand as it falls. This randomness mimics the chaotic interactions found in real-world physical systems.
Enhancing Interactivity and Visual Aesthetics
A professional-grade simulation must feel responsive and visually polished. Instead of placing a single grain of sand per mouse click, we can implement a brush effect. By using a nested loop around the mouse coordinates, we can spawn a cluster of particles within a defined radius. Adding a layer of probability to this brush (e.g., a 75% chance to spawn a grain at each pixel in the radius) creates a more textured and organic feel when 'painting' with sand.

