Slow Rush Studios logo,
    depicting an apprehensive-looking snail rushing forward

Slow Rush Studios

◂  Optimizing the Physics Bridge
News index
Particles, for real this time  ▸

Making Atoms Kinetic

Contents

This week I can confidently say that falling sand no longer completely crushes a moveable body that it falls onto! (It does still crush it a lot, which isn't the best, but we'll get to that.)

First, a small announcement: I've formally resigned from my day job! I had already been working on this fulltime via extended leave, but from March 2nd onwards it'll be official. That did mean that I spent entirely way too much time this week wrapping day job things up, but now that it's mostly done I can get back to focusing on making physics pixels move around semi-predictably.

Secondly, some terminology: I'm sick of writing "physics pixels", so let's call them "atoms". They're the smallest indivisible unit of our game, and we know for a fact that atoms cannot be divided further, so the shoe fits.

Now, back to our usual programming: let's talk about inertia, and our blatant disregard of it.

Where we left off

You might remember that falling sand existed in the "atom world", and we would derive shapes from them and tell the rigid body physics engine about them.

This all worked fine and dandy when you dropped a body onto the atoms, but if even one atom dropped onto a body, things were not so good:

A box falling onto a sand pile works great, but drop some sand onto the box and the box will flip out like crazy!

What's going wrong

Why does that happen? Well, games run game logic to update their state, usually 30-60 times a second. For each of those updates, the physics engine executes 2 steps:

  1. Move all bodies directly* to their expected end position for the update (according to its velocity), without regard for any resulting overlaps between colliding shapes.
  2. Find all pairs of overlapping collider shapes, and fix them by applying forces to the bodies the shapes are attached to, in proportion to their relative mass - which will eventually move the bodies apart from each other.

Some shapes don't have bodies attached to them, which then means that those shapes can't be moved by the physics engine - which basically means they have infinite mass.

And that's the case for the collider shapes we've been creating around all the atoms: they're not attached to any bodies, so they have infinite mass. But the atoms do move, so when an atom-collider-shape overlaps a moving body's collider shape, then the physics engine applies enough force to get the moving body the hell out of the way, because all the force to resolve the collision has to be applied to the moving body.

Relative to any existing velocity the moving body may have, that force ends up being huge, which means the inertia of the body is basically non-existent, which makes it behave super weirdly.

But! We can't just make the atom-colliders be attached to bodies, because then e.g. giving the top-left corner of a falling square of sand atoms a clockwise nudge would cause all those sand atoms to rotate clockwise, as if they cannot move with respect to each other.

Aside: Hence, "rigid body physics": the physics engine doesn't support bodies deforming. Soft body physics is a thing but performs worse and instead gives blob/jelly-like behaviour due to trying to keep the shape "together-ish", which is not what we want here either.

Dirty compromises

Game development is (I am rapidly concluding) all about choosing which dirty compromises you can live with. Here I decided to take a leaf out of Noita's book (that theme is going to continue for a while, I expect):

What if we introduced particles, which are like atoms, but instead of moving according to a set of "cellular automata" rules (brick moves down, sand moves down or diagonally, water can also go horizontally, etc), they can also move kinetically, according to their own velocity? Then, when an atom overlaps a body, we turn it into a particle and fling it into the air; when the particle hits an atom, we turn it back into an atom itself.

I also thought it would make the atom simulation look a feel lot better if atoms would transition into particles when falling, so that they'd pick up speed as they fell.

Thus began the wild goose chase.

The Wild Goose Chase

My first attempt at particle collision detection left something to be desired.

After I fixed the most egregious bugs** in my particle collision detection logic, I noticed a particularly annoying issue: what do you do if a particle ends up overlapping another particle at the time they both want to turn back into an atom? Only 1 atom can exist in each position of the grid.

(At this point it would have been wise to choose a cheap, easy and dirty compromise, like "particles just always move upwards until they find an empty space to become an atom again". But I didn't, so let's continue.)

I tried making the particles reverse and travel back along their previous velocity vector, so they find a spot on their old path.

Velocity is reversed whenever the particle hits something
Example of reflected velocity; even if the particle was moving faster, it would still stop at the same edge.

But reversing sometimes doesn't work at edges of the world: imagine you're traveling in reverse, but all the space between you and the edge of the world has been filled by other atoms.

So I tried making the particles bounce realistically, so they find a spot where they could plausibly have landed.

Velocity is reflected whenever the particle hits something
Example of bounced velocity; keep bouncing until you find an empty spot.

But bouncing also fails***: if you've bounced off the floor then hit a wall while also having other atoms fall down on top of you and having gravity pull you down, you've got nowhere to land.

Eventually I decided that the best way to sidestep this whole problem was to make it so that particles would not be allowed to overlap with each other at all, which would certainly solve the issue once and for all.

It did not.
(NB: in this clip, particles are rendered at half the size of atoms - makes it easier to tell them apart from atoms!)

After having accidentally built what could be confused for a spectrum visualizer, I realized that this approach was flawed because when an atom decides to become a particle via falling, it may do that in a space that's already occupied by a particle.

So I instead I greatly complicated my life by folding particles into atoms, so that atoms were in one of two modes: kinetic mode (with equivalent behavior to particles) or cellular mode (with equivalent behavior to the old style atoms). And only 1 atom, regardless of its mode, would ever be allowed to be in the same spot as another atom:

Kinetic atoms are rendered at half size; you can see there's no overlapping ever, but we have a new problem: flip flopping between modes

Unfortunately, atoms surrounded by other atoms now spent a lot of time flip-flopping between kinetic and cellular modes.

Which meant they didn't really do much else.

Even with kinetic atoms drawn the same as cellular atoms, you can see sand wasn't forming into piles properly, and water wasn't flowing smoothly.

I thought long and hard about whether I was using sane criteria for atoms swapping between modes. Just in case, I tried a few other sets of criteria, but they were no better.

So I scrapped the "mode" idea: now all atoms are kinetic (they have their position updated by velocity) and cellular (after moving kinetically, they follow rules about when to swap with neighbouring cells).

It's not perfect yet, but free-falling particles look much more realistic now.

And does it fix the original problem that led me down this path, where falling atoms would crush moving bodies? Nope!

But somewhere along the way I did implement a check that excludes atoms which moved last update from being included in the collider update, which makes it slightly less bad, and has the nice side effect of having sand atoms "rolling" downhill push the player away. A proper fix is still in the works!

Playable web build‎

This build has some new atom debug options (F2) for you to play with, for e.g. if you want to see atoms' velocities:

Click to focus, then play with keyboard and mouse. No mobile support! Give feedback.

Of course you can move with A/D, jump with W, draw different things, etc as before too.


* I am simplifying a bit more than usual here. This is discrete collision detection and (as you might expect from my 2 sentence explanation) can result in bodies going through each other without actually hitting each other if they are traveling sufficiently quickly. You can ask most physics engines to do "continuous" collision detection instead, which will check intermediate positions along the way, but it performs worse - and wouldn't fix the problem I'm discussing here anyway.

** One particularly fun bug: particles are tiny things expected to collide with other tiny things, and so they basically have to do continuous collision detection (see previous footnote above). But because they aren't part of the rigid body physics engine, I needed to implement that myself. So I lifted a Bresenham line drawing routine from another project and used that to iterate over all the integer coordinates between the particle's current position and its expected destination to find the furthest away space not occuppied by an atom... and then spent half an hour the next day debugging only to realize that my implementation would always draw lines from top left to bottom right, rather than from origin to destination - so when moving right to left, particles would always move to the closest empty space :/

*** Actually as I'm thinking about it more now, it probably would have worked if I made the particles disregard gravity, only bounce off atoms once (but allow infinite bounces off the world boundary) and travel an infinite distance, as they would eventually have left the blocked up area. They just wouldn't necessarily end up in a place that makes sense, as they might have tunneled through a whole mass of atoms, but most of the time it would probably be fine. C'est la vie!

◂  Optimizing the Physics Bridge
News index
Particles, for real this time  ▸