Making a falling sand simulator
jason.today
jason.today
The idea is that a single iteration divides the world into 2x2 squares and then applies effects sequentially within each square, but not between the squares. This means each square can be processed independently. In the next iteration, the division into squares shifts right and down by one cell each direction. This does mean you need more steps than in a sequential implementation, but I found it to be quite a principled approach to parallelizing cellular automata when I first read about it. One interesting side effect of this design is that falling particles end up being separated by blank space, as shown here: https://futhark-lang.org/static/falling-sand-2016.12.04.webm I wonder if that is fixable. (Although at larger scale it's not particularly visible: https://sigkill.dk/junk/diving-beet.webm)
Unfortunately the demo video in the link you posted seems to be private, so I might be misunderstanding.
There are some diagonal interactions that are not directly expressible in this way, but they can propagate through the non-diagonal neighbours instead.
Thanks for reminding me.
A nice optimization trick is to make the cell buffers 2 cells wider and taller (or two times whatever the neighborhood radius is), and then before each generation you update the "gutter" by copying just the wrapped (or mirrored) pixels. Then your run the rule on the inset rectangle, and the code (in the inner loop) doesn't have to do bounds checking, and can assume there's a valid cell to read in all directions. That saves a hell of a lot of tests and branches in the inner loop.
Also, you you can define the Margolus neighborhood in terms of the Moore neighborhood + horizontal phase (even/odd column x) + vertical phase (even/odd row y) + time phase (even/odd time). With that information the rule can tell if it's at an even or odd step, and which of the four squares of the grid it's in.
That's how the CAM6 worked in hardware: it used the x/y/time phases as additional bits to determine the Margolus neighborhood lookup table index.
https://github.com/SimHacker/CAM6/blob/master/javascript/CAM...
Here's how my CAM6 emulator computes the CAM6-hardware-compatible Margolus lookup table index, based on the 9 Moore neighbors + phaseTime, phaseX, and phaseY:
function getTableIndexUnrotated(
nw, n, ne,
w, c, e,
sw, s, se,
phaseTime,
phaseX,
phaseY) {
// 0 1 2 3 4 5 6 7 8 9
// c0 c1 cw0 ccw0 opp0 cw1 ccw1 opp1 pha0 pha1
// 0x1 0x2 0x4 0x8 0x10 0x20 0x40 0x80 0x100 0x200
var cw, ccw, opp;
if (phaseTime) {
return (
// c c'
(c & 0x03) |
// cw cw'
(cw = (phaseX
? (phaseY
? (e & 0x03)
: (n & 0x03))
: (phaseY
? (s & 0x03)
: (w & 0x03))),
(((cw & 0x01) << 2) |
((cw & 0x02) << 4))) |
// ccw ccw'
(ccw = (phaseX
? (phaseY
? (s & 0x03)
: (e & 0x03))
: (phaseY
? (w & 0x03)
: (n & 0x03))),
(((ccw & 0x01) << 3) |
((ccw & 0x02) << 5))) |
// opp opp'
(opp = (phaseX
? (phaseY
? (se & 0x03)
: (ne & 0x03))
: (phaseY
? (sw & 0x03)
: (nw & 0x03))),
(((opp & 0x01) << 4) |
((opp & 0x02) << 6))) |
// pha pha'
0x200);
} else {
return (
// c c'
(c & 0x03) |
// cw cw'
(cw = (phaseX
? (phaseY
? (w & 0x03)
: (s & 0x03))
: (phaseY
? (n & 0x03)
: (e & 0x03))),
(((cw & 0x01) << 2) |
((cw & 0x02) << 4))) |
// ccw ccw'
(ccw = (phaseX
? (phaseY
? (n & 0x03)
: (w & 0x03))
: (phaseY
? (e & 0x03)
: (s & 0x03))),
(((ccw & 0x01) << 3) |
((ccw & 0x02) << 5))) |
// opp opp'
(opp = (phaseX
? (phaseY
? (nw & 0x03)
: (sw & 0x03))
: (phaseY
? (ne & 0x03)
: (se & 0x03))),
(((opp & 0x01) << 4) |
((opp & 0x02) << 6))) |
// pha pha'
0x100);
}
};edit: I don't have a good video of this atm, but just to demonstrate it: https://www.youtube.com/watch?v=rItnlDmGALU
it's not visualized in the video, but the pixels also have temperature, and materials a sort of stickyness (basically sand that takes longer to form into a pyramid), that's about it.
>the division into squares shifts right and down by one cell each direction.
The MFM non-deterministic equivalent of the Margolus neighborhood's alternating x,y offset is to randomly execute non-overlapping neighborhoods in parallel. That has the same effect of incrementally diffusing information evenly in all directions over time, just not perfectly, synchronously, symmetrically, and deterministically like the Margolus neighborhood does.
Moveable Feast Machine rules make it much easy to implement particle based simulations than with traditional cellular automata rules, which compute just the center cell. Instead, the MFM rules can simply "pick up and move" any number of particles within the wider neighborhood.
They can implement all kinds of interesting chemical and biological behaviors between locally interacting particles, like holding molecules of different particles together with atomic bonds, and constructing membranes like cell walls that separate and contain and transport other particles, and implementing magical computational "force fields" and "tractor beams" and "traffic lights" that route other particles around. They're even good for simulating computational DNA-like behaviors:
https://www.youtube.com/watch?v=DauJ51CTIq8
>Programming the Movable Feast Machine with λ-Codons
>λ-Codons provide a mechanism for describing arbitrary computations in the Movable Feast Machine (MFM). A collection of λ-Codon molecules describe the computation by a series of primitive functions. Evaluator particles carry along a stack of memory (which initially contains the input to the program) and visit the λ-Codons, which they interpret as functions and apply to their stacks. When the program completes, they transmute into output particles and carry the answer to the output terminals (left).
The Moveable Feast Machine is similar to cellular automata, but different in some important ways, that make it extremely robust and fault tolerant:
It's a "Robust First" asynchronous distributed fault tolerant cellular-automata-like computer architecture.
Robust programs running on massively parallel unreliable hardware can actually tolerate hardware failure and repair themselves. The Demon Hoard Sort algorithm is an inherently robust sorting algorithm for the Moveable Feast Machine.
http://movablefeastmachine.org/
The "Distributed City Generation" video demonstrates a Movable Feast Machine rule that builds a self-healing city that fills all available space with urban sprawl, with cars that drive between buildings, and city streets that adaptively learn how to route the cars to their nearest destinations, and the city even repairs itself after disasters!
https://www.youtube.com/watch?v=XkSXERxucPc
Here's some more info:
https://news.ycombinator.com/item?id=14236973
JavaScript implementation:
https://github.com/walpolea/MFM-JS
Robust First Wiki:
For fun, I've written a Game Of Life implementation in GLSL and even though it is naively implemented it will run at thousands of iterations per second at 4K resolution on a 6 year old desktop gpu.
[1]: https://www.youtube.com/watch?v=prXuyMCgbTc "Exploring the Tech and Design of Noita"
One more step: self-organized criticality... At a 'critical angle', any additional grains of sand can trigger an avalanche. (Be careful though, this is a deeply fascinating rabbit hole!)
Here's a nice paper: https://www.researchgate.net/publication/252164461_Self-orga...
And an article: https://www.jstor.org/stable/24936753
As you noted at the bottom to reach out if we want to see more like this - yes please, I think that would be great.
Coincidentally I’ve been curious about writing something like this since playing a game with very primitive materials and physics with my son a few years ago. This made me realize we could actually build something quite easily, so thanks!
In the post you wrote, as some people already mentioned, the sand distributes first to one side and then the other. It would be cool to get something non-deterministic happening instead.
Performance improvements are always very interesting to me, regardless of the context. That’s a definite yes.
Otherwise I suppose other materials would be great. How could you make a vine grow? Or a tree? Far too complex? I’m not sure but I’d enjoy the hell out of learning how.
I see the final result here has a left bias, just press and hold to see it stacking towards left before right. So the next fix could be a random choice.
Another issue is that it forms a perfect diagonal, which isn't the most pleasing to look at. It can be addressed by randomly allowing for stacks of two granules and allowing spreading further than just one position to the left or right.
I figured where I left it was appropriate for this post, but I'm happy to write follow-ups for other particle types and the kind of improvement you mentioned!
One fix I found is relatively straightforward:
class FallingGrid2 extends FallingGrid {
// Draw from the end of the list to the beginning
update() {
+ const direction = Math.random() > 0.5 ? 'ltr' : 'rtl';
for (let i = this.grid.length - this.width - 1; i > 0; i--) {
- this.updatePixel(i);
+ if (direction === 'rtl') {
+ this.updatePixel(i);
+ } else {
+ const offset = i % this.width;
+ const flipped = this.width - offset;
+ this.updatePixel(i - offset + flipped);
+ }
}
}
}
Cheers for making the source so easily hackable :) updatePixel(i) {
const below = i + this.width;
const belowLeft = below - 1;
const belowRight = below + 1;
// flag to indicate if we checked the left side already
let checkedLeft = false;
if (this.isEmpty(below)) {
swap(i, below);
// check the left side first with 50 % prob (ltr)
} else if (Math.random() <= 0.5 && this.isEmpty(belowLeft)) {
swap(i, belowLeft);
// indicate we checked the left side already
checkedLeft = true;
// check the right side first with 50 % prob
} else if (this.isEmpty(belowRight)) {
swap(i, belowRight);
// if we haven't checked the left side earlier, check it now (rtl)
} else if (!checkedLeft && window.sandgrid[belowLeft] == 1)
swap(i, belowLeft);
}3rd last line the "window.sandgrid[belowLeft] == 1" needs to be "this.isEmpty(belowLeft)" instead
Other than that I'm not sure what would be causing the weird behaviour. I use this and it seems to work just fine.
EDIT: I'm out of quota. For games like this it's pretty hard. You can move it into a fragment shader or maybe mark certain rectangles in the canvas as "empty" (there's a very fast algorithm for cellular automata which pretty much has the same issue.) There are no "easy" solutions though.
It reminds me a bit of old basic on Amiga/commodore systems where getting something drawn on the screen and making a beep was trivial and built into the environment.
Doing it from the bottom up solves this problem, however I think in a general sense (eg. with different sources of gravity it would become more tricky!
What's the answer to this? How do you mix smoke and sand?
You can try this yourself by using dev tools to modify the code and check the position above instead of below.
Edit: I found a great intro to the cellular automata approach:
https://blog.macuyiko.com/post/2017/an-exploration-of-cellul...
https://blog.macuyiko.com/post/2017/an-exploration-of-cellul...
https://blog.macuyiko.com/post/2018/an-exploration-of-cellul...
https://blog.macuyiko.com/post/2020/an-exploration-of-cellul...
Kudos to the OP article. Although it doesn't go very far the presentation style is very clean and appealing.
I remember fighting burning sand 2 triggers to make a little cursor that would follow your mouse and totally failing at it.
And writing my own little planetary simulator- not one of the popular ones but I gave it my best shot.
I'd love to see a new "game engine" like BS2, but modernized and cleaned up and easier to code on. Powder toy is awesome but not nearly as hackable and isn't super performant.
Would love to see this made into a series. Maybe implement different elements or optimisations to existing mechanics.
Don't let the ideas from the past slip through your fingers like sand through an hourglass. ;)
Abelian sandpile model
https://en.wikipedia.org/wiki/Abelian_sandpile_model
>The Abelian sandpile model (ASM) is the more popular name of the original Bak–Tang–Wiesenfeld model (BTW). BTW model was the first discovered example of a dynamical system displaying self-organized criticality. It was introduced by Per Bak, Chao Tang and Kurt Wiesenfeld in a 1987 paper.
>Three years later Deepak Dhar invented that the BTW sandpile model indeed follows the abelian dynamics and therefore referred to this model as the Abelian sandpile model.
>The model is a cellular automaton. In its original formulation, each site on a finite grid has an associated value that corresponds to the slope of the pile. This slope builds up as "grains of sand" (or "chips") are randomly placed onto the pile, until the slope exceeds a specific threshold value at which time that site collapses transferring sand into the adjacent sites, increasing their slope. Bak, Tang, and Wiesenfeld considered process of successive random placement of sand grains on the grid; each such placement of sand at a particular site may have no effect, or it may cause a cascading reaction that will affect many sites.
>Dhar has shown that the final stable sandpile configuration after the avalanche is terminated, is independent of the precise sequence of topplings that is followed during the avalanche. As a direct consequence of this fact, it is shown that if two sand grains are added to the stable configuration in two different orders, e.g., first at site A and then at site B, and first at B and then at A, the final stable configuration of sand grains turns out to be exactly the same. When a sand grain is added to a stable sandpile configuration, it results in an avalanche which finally stops leading to another stable configuration. Dhar proposed that the addition of a sand grain can be looked upon as an operator, when it acts on one stable configuration, it produces another stable configuration. Dhar showed that all such addition operators form an abelian group, hence the name Abelian sandpile model.
>The model has since been studied on the infinite lattice, on other (non-square) lattices, and on arbitrary graphs (including directed multigraphs). It is closely related to the dollar game, a variant of the chip-firing game introduced by Biggs.
https://www.youtube.com/watch?v=1MtEUErz7Gg
>Sandpiles - Numberphile
>Luis David Garcia-Puente discusses sandpiles, and how they produce amazing "fractal zeroes". Dr Garcia-Puente is an associate professor at Sam Houston State University and was interviewed while attending an MSRI-UP summer program.
https://www.youtube.com/watch?v=diGjw5tghYU
>Coding Challenge #107: Sandpiles
>In this coding challenge, inspired by Numberphile I visualize "sandpiles" with Processing (Java).