Here we go:
Once upon a time, PCs dominantly ran a very simple operating system called DOS. One of the games written for DOS is Scorched Earth, "the mother of all games". The game looked something like this:
Scorched earth, among a bunch of other games that I do not recall at the moment, have "sand simulation". When your tank goes and shoots a hill, some of the "sand" may end up hanging in the air, so all the pixels that have space below them are moved down. This process is repeated until the "sand" doesn't move anymore.
The movement can be simply "if space below, fill below pixel and clear this one", or it can be more complicated like moving diagonally if there's space, etc. I've actually written a programming tutorial about this, so if you want more details, dig in.
I've actually made a tAAt new year demo with this idea before, but that was over a decade ago and it's really 2D simulation there too. This time I figured I'd go 3D.
First off, I made a prototype that uses glDrawElementsInstanced() to render all the cubes with a single call. The cube positions are fed to the vertex shader using glVertexAttribDivisor(). Instead of feeding a transform matrix I simply feed the x,y,z positions (and r,g,b colors) of each instance. The boxes don't need to rotate or scale individually, so that simpler.
While prototyping, things looked more or less like this:
After getting the cubes to render, the next thing was to get the physics to work. In 2D, the movement "down" (or down diagonally) is trivial. When rotated, it gets a bit trickier, but it's still pretty simple. In 3D, things get a bit more confusing.
So you have six directions to go (assuming no diagonals). Which way to move? This is what I spent several iterations or prototypes on, until I reached a version that looks good enough(tm).
First off, instead of worrying about the order of particles to evaluate, I evaluate them in random order. This avoids cases where all the particles move in a row, or only one particle manages to move in a cycle.
Then I took the magnitude of the gravity vector's axes. If an axis' magnitude is relatively low, the axis is discarded completely. This avoids some random movement.
Next up, I go through the remaining axis in the order of magnitude, biggest first. If there's space to move in that axis in the direction of the gravity, we move. For all axis except the most major one a slight random is added to introduce a bit of chaos to the movement.
And that's primarily it. The new particle coordinates are fed through a heavy low pass filter so that the cubes do not only move in discrete steps, but slide smoothly to the target coordinates. If a particle moves several times in the same direction in subsequent cycles, the low pass filter will also cause it look like the particle was accelerating and decelerating.
Once that was done, I added texture to the particles. There's two textures - one for the "front" and one for the "back" of the pile of particles, and I simply interpolate the colors between these textures for the inner layers.
Then I added a cube to show the container within which the particles are bouncing, rendering it twice for back and front faces and the particles in between. For shading, I added a shadow map. Finally, I added the writer (which is actually opengl 1.0 stuff; the rest is opengl 3.3. Still works!)
There were a lot of ideas that could have been tried but I never did. I experimented in making more but smaller particles, but that didn't look significantly better (since making the cubes quarter size means that there must be 8 times more particles.. and that didn't look all that different). Some ideas tossed around included adding centrifugal force by adjusting the gravity vector per particle, which would have flattened the particles to the walls of the cube when the cube spun faster; changing the shape of the container, since the particles don't really care what kind of voxel space they reside in; adding special particles that "taint" the particles they collide with, to keep colors of the particles changing; fading the colors of the particles in time to show new images after being shuffled; etc.
I guess that's about it. Let's hope this year is better than the previous one.
Comments, questions, etc. appreciated.