A 2D Pixel Physics Simulator with Cellular Automata written in Rust
I've been exploring compute shaders for cellular automata for the past 3 months in my Sandbox project with deformable objects and customizable matter. Inspired by the likes of Sandspiel and Noita. Time to share some of the progress and get that dear feedback!
I use the awesome Vulkano for rendering and computation, and Rapier for simple physics. Contour is used for the initial shapes, but rapier forms the physics colliders from it. Hecs is used as well. And you might recognize Egui as gui :). I gotta say, I'm starting to be pretty happy with the rust ecosystem overall.
How?, you might ask:
- Write pixel objects (which keep track of their pixels each frame) to the grid canvas. Newly inserted objects form their shapes from
.pngimages. - Step Cellular Automata simulation (how matter reacts with neighbors etc.)
- Read pixel objects from grid and track if they changed. If they did, deform them by calculating their shape again and saving new images from the new parts.
- Step Physics
One interesting approach I've taken is to use compute shaders for the Cellular Automata simulation. I'm not sure if I'd recommend it, but I managed to do it. It's hard due to the massive parallelism of the GPUs, because you need to think like this: "If I need to decide the state of each cell in grid simultaneously, what will each cell be next frame / step". And cellular automata is kind of dependent in such a way that each cell depends on the states of neighbors. How though?
- Double buffering
- I use many passes that do only one inspection. E.g. If cell is empty and above falls, cell will become above. But also, if cell is one that falls and below is empty, it will become one below.
- Currently there is no "gravity" for cellular automata, but matter has strict rules. But perhaps one day I'll make the matter more particle-like with velocity (nah, just learn and move on).
- Liquid was the hardest, and I spent a long time on it even trying SPH quickly, but then decided that the code remains simpler if all matter types get the same handling. It looks alright, though it is computationally intensive requiring more passes than other types.
GPU computation for non-graphics is also hard due to the dependencies between CPU state and GPU state... and the amount of data between, and you can certainly tank the fps by drawing large lines in the Sandbox, but it's good enough for the time being.
Probably no point in trying to run this without an external GPU!
I intend to make a game next extracting parts and learnings from this, though this little Sandbox will remain its own project, perhaps even a testing ground for ideas.