Simulating falling sand is no different from any other cellular automata. We have a big gird of cells and a set of rules that get applied to each cell every frame. You’ve probably heard of the Game of Life. In that cellular automata, the rules are based on the number of neighbors surrounding a cell. For falling sand, we mostly look at if there is empty space below a cell for it to move into. This creates the look of particles cascading down.

One of the more interesting aspects of cellular automata is how complex behavior can emerge from such simple rules. That sounds like an interesting project could be created from only a few lines of code! Let’s look at how you could go about making one of these in the Java version of Processing. You can download Processing here [from a link below] if you want to follow along.

**Java version**

We’ll start with the standard *setup* and *draw* functions. Before we fill these in, we should establish what our particles will be. For simplicity, color will determine the particle types, so let’s define some *color*s at the top.

color _EMPTY = color(0); color _SAND = color(255, 150, 50); voidsetup() { } voiddraw() { }

In *setup* we need to set the size and background. I like to use a size of 800x800 pixels, and we need to set the background to the empty particle color. We should also uncap the framerate from 60 to allow this to run faster if it can.

voidsetup() { size(800, 800); background(_EMPTY); frameRate(30); }

Each time the *draw* function is called we will step our simulation. Because we are only using color, let’s use the *pixels* array as the storage for our cells. Each frame we need to call *loadPixels* to copy the displayed frame into the *pixels* array. This first frame will be all *_EMPTY* because we called *background*.

voiddraw() { loadPixels();

After we load the pixels, we can iterate over them and check if they match a particle type that we know about.

for(int x = 0; x < width; x++) for(int y = 0; y < height; y++) { color cell = get(x, y); if(cell == _SAND) {

We want sand to form dune like shapes, so let’s encode a pyramid into its rules. We need to check directly below, down to the left, and down to the right.

By default, it should move down if possible, but if that cell is occupied, we’ll check the other two directions.

boolean down = isEmpty(x, y + 1); boolean left = isEmpty(x - 1, y + 1); boolean right = isEmpty(x + 1, y + 1); if(down) setCell(x, y + 1, _SAND); else if(left) setCell(x - 1, y + 1, _SAND); else if(right) setCell(x + 1, y + 1, _SAND); if(down || left || right) { setCell(x, y, _EMPTY); } }

This looks ok, but the order of *if* statements matters. Because we put the left check before right, if both spaces are open, the particle will always move left. This creates an artificial look, but we can easily fix it by adding a little randomness to shuffle the direction if both cells are empty.

if(left && right) { boolean rand = random(1) > .5; left = rand ? true : false; right = rand ? false : true; }

Finally, we can close the *for* loops and call *updatePixels* to copy our frame to the display.

```
}
updatePixels();
}
```

To draw particles on the screen with the mouse, we can add these lines after *loadPixels.*

if(mousePressed) { for(int x = 0; x < 1; x++) for(int y = 0; y < 1; y++) { setCell(mouseX + x, mouseY + y, _SAND); } }

You can create other particle types by adding more rules in a similar way. For example, water and a stationary barrier type are the most straight forward and where I would start. Water is like sand, but we check directly to the left and right instead of diagonal, which gives the effect of liquid flow. Barriers have no movement properties but fill space so the other types can’t move through them.

Common issues

I saved the helper functions for last to highlight two common issues. The biggest gotcha with these types of simulations is that the order of iteration effects the behavior dramatically, and if we aren’t careful, we could end up updating particles multiple times or even lose them entirely.

boolean inBounds(int x, int y) { return x >= 0 && y >= 0 && x < width && y < height; } boolean isEmpty(int x, int y) { return inBounds(x, y) && pixels[x + y * width] == _EMPTY; } void setCell(int x, int y, color cell) { pixels[x + y * width] = cell; }

We have four options for how we order the iteration. No matter which we pick, there will always be slight issues. Let’s see why.

If we iterate top to bottom, we end up updating the same particle multiple times. This results in a teleportation effect where a particle will only take a single frame to reach the floor. We could change the iteration to bottom to top, but then if we want particles that moves up, we run into the same issue. This highlights the fact that no matter which direction we pick, there will always be issues. Clearly, the solution must have more to it than just finding a magic ordering…

The band aid solution in this Processing version comes from the subtle details of how the pixel array works. In the helper functions we read and write to the *pixels* array, but in *draw* we use the *get* function. Even though the docs don’t say this, *get* must read from the displayed *pixels* array, which we don’t update until we call *updatePixels*. This allows us to dodge the issue of updating particles multiple times because nothing updates until the end of the frame from *get*’s perspective.

The second problem we can only mitigate. In *isEmpty* we don’t use *get *because we want information about the current frame as it updates. If we used *get*, two particles could both think a cell is empty, move in, and one would be lost. Even though we’ve somewhat fixed that issue, the order of iteration still matters. Now the order on the X axis determines who moves in first, so we still have inconsistent behavior.

Now that we have an idea of how the basics work, and common pitfalls, let’s jump over to C++ and see how these can be solved.

**C++ version**

I plan on expanding this over the next few videos, so I am going to try and make it more of a general sand framework. To start, we don’t want to edit the pixels directly because we want more properties than just color. Let’s make a *CellType* *enum* and store it in a *Cell* *struct* along with a *Color*. In the first version, sand and water both needed to check if the space directly below was free. This hints that the ways that the particles move doesn’t have to reflect their type. So, let’s keep two *enums* in each *Cell*, one for the movement properties and another the type. If we use a bit string for the properties*,* they can be combined to create more complex patterns.

enum class CellProperties { NONE = 0b00000000, MOVE_DOWN = 0b00000001, MOVE_DOWN_SIDE = 0b00000010, MOVE_SIDE = 0b00000100 }; enum class CellType { EMPTY, SAND, WATER, ROCK }; struct Cell { CellType Type = CellType::EMPTY; CellProperties Props = CellProperties::NONE; iw::Color Color; // rgba };

Side note

C++ requires

enum classesto explicitly define everything, so if we want to be able to use | and & for the bitwise checks, we need to add these two operators.inline CellProperties operator|( CellProperties a, CellProperties b) { return CellProperties(iw::val(a) | iw::val(b)); } inline auto operator&( CellProperties a, CellProperties b) { return iw::val(a) & iw::val(b); }iw::val is just a wrapper around a cast to the underlying type, int is the default for enums.

Now that we have defined our *Cell*, we need a way to store them. Let’s make a new class called *SandWorld* and store an array of *Cell*s in it. In the constructor we can specify a width, height, and scale in pixels. This will allow us to easily change the size of the cells.

class SandWorld { public: const size_t m_width = 0; const size_t m_height = 0; const double m_scale = 1; private: Cell* m_cells = nullptr; public: SandWorld( size_t width, size_t height, double scale) : m_width (width / scale) , m_height(height / scale) , m_scale(scale) { m_cells = new Cell[m_width * m_height]; } ~SandWorld() { delete[] m_cells; }

We’ll need some functions for getting the cells out of the world, let’s add two: one that takes x and y coordinates, and another that takes a flat index.

const Cell& GetCell(size_t index) { return m_cells[index]; } const Cell& GetCell(size_t x, size_t y) { return GetCell(GetIndex(x, y)); } size_t GetIndex(size_t x, size_t y) { return x + y * m_width; }

And to get us back to where we were, let’s add the same helper functions from before.

bool InBounds(size_t x, size_t y) { return x < m_width && y < m_height; } bool IsEmpty (size_t x, size_t y) { return InBounds(x, y) && GetCell(x, y).Type == CellType::EMPTY; } void SetCell( size_t x, size_t y, const Cell& cell) { m_cells[GetIndex(x, y)] = cell; }

Processing gave us two arrays to work with, but before we just add another one and call it a day, let’s think about a way to actually solve the issues that arise from the iteration ordering. The main problem is that moves are executed as they come, but really, we should gather all the possible moves, then execute them at the end to give each one a fair chance.

Let’s add a *vector* to the *SandWorld*, and make a new function called *MoveCell* that adds a move to the list.

std::vector<std::pair<size_t, size_t>> m_changes; // destination, source void MoveCell( size_t x, size_t y, size_t xto, size_t yto) { m_changes.emplace_back( GetIndex(xto, yto), GetIndex(x, y) ); }

After we finish iterating over our cells, we can apply the changes. First, we need to remove any changes that were filled between frames by the SetCell function.

void CommitCells() { // remove moves that have their destinations filled for (size_t i = 0; i < m_changes.size(); i++) { if (m_cells[m_changes[i].first].Type != CellType::EMPTY) { m_changes[i] = m_changes.back(); m_changes.pop_back(); i--; } }

Then we need to sort the list of moves by their destination. This is an unfortunate slowdown but allows us to add and choose moves quicker. We could use a *multimap*, but the slowdown from accessing the linked lists outweighs the sort.

// sort by destination std::sort(m_changes.begin(), m_changes.end(), [](auto& a, auto& b) { return a.first < b.first; } );

Then we can iterate over the sorted moves. Each time the destination changes, we’ll pick a random source to move from. This allows each particle to get a fair chance at moving into to a cell. Finally, we’ll clear the list.

// pick random source for each destination size_t iprev = 0; m_changes.emplace_back(-1, -1); // to catch final move for (size_t i = 0; i < m_changes.size() - 1; i++) { if (m_changes[i + 1].first != m_changes[i].first) { size_t rand = iprev + iw::randi(i - iprev); size_t dst = m_changes[rand].first; size_t src = m_changes[rand].second; m_cells[dst] = m_cells[src]; m_cells[src] = Cell(); iprev = i + 1; } } m_changes.clear(); } };

That’s it for the core of our little framework, let’s see how we can use it to make what we had in the Processing version. I am going to be using my own game engine for this; let me know in the comments if you want to follow along and I can package it up and write some documentation. But in this post I am going to skip over the non-sandy details, you can check out the full source here if you’re interested.

In an *Update* function, we’ll iterate over the cells in a similar way as before, but now that we have a bit string of movement properties, we can check each one until its respective *MoveX* function returns true.

SandWorld m_world = SandWorld(1920, 1080, 2); // in a class somewhere void Update() { // Update cells for (size_t x = 0; x < m_world.m_width; x++) for (size_t y = 0; y < m_world.m_height; y++) { const Cell& cell = m_world.GetCell(x, y); if (cell.Props & CellProperties::MOVE_DOWN && MoveDown (x, y, cell)) {} else if (cell.Props & CellProperties::MOVE_DOWN_SIDE && MoveDownSide(x, y, cell)) {} else if (cell.Props & CellProperties::MOVE_SIDE && MoveSide (x, y, cell)) {} } m_world.CommitCells(); // Update the sand texture // Draw the sand to the screen }

We can make a function for each movement property that will return true if it finds a valid move.

bool MoveDown( size_t x, size_t y, const Cell& cell) { bool down = m_world.IsEmpty(x, y - 1); if (down) { m_world.MoveCell(x, y, x, y - 1); } return down; } bool MoveDownSide( size_t x, size_t y, const Cell& cell) { bool downLeft = m_world.IsEmpty(x - 1, y - 1); bool downRight = m_world.IsEmpty(x + 1, y - 1); if (downLeft && downRight) { downLeft = iw::randf() > 0; downRight = !downLeft; } if (downLeft) m_world.MoveCell(x, y, x - 1, y - 1); else if (downRight) m_world.MoveCell(x, y, x + 1, y - 1); return downLeft || downRight; }

To draw particles with the mouse, we can create some defaults in an *Init* function…

Cell _EMPTY, _SAND, _WATER, _ROCK; // In same class void Initialize() { _EMPTY = { CellType::EMPTY, CellProperties::NONE, iw::Color::From255(0, 0, 0, 0) // 0 alpha allows for a background }; _SAND = { CellType::SAND, CellProperties::MOVE_DOWN | CellProperties::MOVE_DOWN_SIDE, iw::Color::From255(235, 200, 175) }; _WATER = { CellType::WATER, CellProperties::MOVE_DOWN | CellProperties::MOVE_SIDE, iw::Color::From255(175, 200, 235) }; _ROCK = { CellType::ROCK, CellProperties::NONE, iw::Color::From255(200, 200, 200) }; // Init a texture for sand... }

Then at the top of our update function, we’ll add these lines.

void Update() { if (Mouse::ButtonDown(iw::LMOUSE)) { vector2 pos = Mouse::ScreenPos() / m_world.m_scale; pos.y = m_world.m_height - pos.y; Cell placeMe = _EMPTY; if (Keyboard::KeyDown(iw::S)) placeMe = _SAND; else if (Keyboard::KeyDown(iw::W)) placeMe = _WATER; else if (Keyboard::KeyDown(iw::R)) placeMe = _ROCK; for (size_t x = pos.x; x < pos.x + 20; x++) for (size_t y = pos.y; y < pos.y + 20; y++) { if (!m_world.InBounds(x, y)) continue; m_world.SetCell(x, y, placeMe); } } // Update cells // Copy sand colors to a texture // Draw the texture on the screen }

The last thing I want to cover is about making games inside of these simulations. Let’s think about the most basic feature we need, then in future posts I’ll expand on this.

Having a player that moves around seems like a good place to start, so let’s think about how that might work. We’ll need some notion of a group of particles that can move together. Let’s make a *Tile* struct that holds a list of positions for its particles, and a position for the group itself. For now, we’ll just use the *_ROCK* particle for everything in the tile.

struct Tile { std::vector<std::pair<int, int>> Positions; int X = 0; int Y = 0; };

Before we update the world, we need to paste the tile particles in, so let’s put these lines in the *Update* function before the main loops. After we’ve called *CommitCells* and updated the texture, we can remove the tiles by setting their cells to *_EMPTY*.

std::vector<Tile> m_tiles; // in same class void Update() { // Draw cells with mouse for (Tile& tile : m_tiles) { for (auto [x, y] : tile.Positions) { x += tile.X; y += tile.Y; if (m_world.InBounds(x, y)) { // what happens if the cell is already full? m_world.SetCell(x, y, _ROCK); } } } // Update cells // Copy sand colors to a texture // Draw the texture on the screen for (Tile& tile : m_tiles) { for (auto [x, y] : tile.Positions) { x += tile.X; y += tile.Y; if (m_world.InBounds(x, y)) { // what happens if the cell is no longer part of the tile? m_world.SetCell(x, y, _EMPTY); } } } }

Finally, we can make a little ship and add some basic movement. In the *Init* function, we’ll make a *Tile* and add it to the list.

void Initialize() { Tile ship = { { {2,6}, {1,5}, {2,5}, {0,4}, {1,4}, {2,4}, {3,4}, {0,3}, {1,3}, {2,3}, {3,3}, {0,2}, {3,2}, {0,1}, {3,1}, {0,0}, {3,0}, }, 200, 200 }; m_tiles.push_back(ship); // Create default cells // Init a texture for sand... }

In the *Update* function, before the world updates, we can add these lines to move our ship. It’s important to move the tiles before pasting them in so they can be removed correctly at the end.

void Update() { // Draw cells with mouse if (Keyboard::KeyDown(iw::LEFT)) m_tiles[0].X -= 1; if (Keyboard::KeyDown(iw::RIGHT)) m_tiles[0].X += 1; if (Keyboard::KeyDown(iw::UP)) m_tiles[0].Y += 1; if (Keyboard::KeyDown(iw::DOWN)) m_tiles[0].Y -= 1; // Paste tiles // Update cells // Copy sand colors to a texture // Draw the texture on the screen // Remove tiles }

And that about covers it, we’re left with a decently quick simulation. On my computer this runs between 0.005 - .015 seconds per frame. Which is around 200 – 60 fps. Currently we can only use 1 thread, so if your computer has 4 cores we are only using 1/4^{th} or more likely only 1/8^{th} of its power

In the next post, I am going to cover how we could make the world bigger, and how multi-threading the simulation works. Then we’ll make a game with it, so stay tuned if this seems interesting!

]]>I call this algorithm an extension because its input is the internal state from GJK. If you recall, we were working with a simplex to try and surround the origin with a triangle in 2D or a tetrahedron in 3D. This algorithm takes the final simplex that contained the origin and finds the normal of collision, aka the shortest vector to nudge the shapes out of each other. The naive solution is to use the normal of the closest face to the origin, but remember, a simplex does not need to contain any of the original polygon’s faces, so we could end up with an incorrect normal.

Let’s jump into it, it’s called the Expanding Polytope Algorithm because it expands the simplex by adding vertices to it until we find the shortest normal from a face that is on the original polygon. For jargon's sake, the simplex becomes a polytope after we begin adding more vertices to it. How do we do that? Well we already have the tools from GJK, all we need to do is iterate over the faces and find the closest one. Then we’ll use the *Support* function to find if there is a point further in the direction of that normal. If there is one, the face of the polytope must be inside the original polygon and can be expanded. If so, we’ll add this supporting point and repeat.

In 2D this is easy, we can just add the point and the edges remain intact. In 3D we need to do a little repair job, which is where most of the annoyance comes from. Let’s start with the 2D version first, because it shows the algorithm’s main pieces, then we’ll see how to repair the 3D polytope.

**2D (JavaScript)**

I’m going to venture into a little p5.js for the 2D version because I’ve made a small demo with it that you can check out at https://winter.dev/lilapps/gjk if you want to mess around with it for yourself.

We’ll start with a function that takes in the simplex from GJK and the two shapes that we are testing.

The first step is to find the edge closest to the origin. We’ll do this by comparing the dot products between their normals and the vectors from the origin. We only need the closest one, so we’ll store it, along with its index and distance in variables, outside the loop to use later.

function EPA(polytope, shapeA, shapeB) { let minIndex = 0; let minDistance = Infinity; let minNormal; while (minDistance == Infinity) { for (let i = 0; i < polytope.length; i++) { let j = (i+1) % polytope.length; let vertexI = polytope[i].copy(); let vertexJ = polytope[j].copy(); let ij = vertexJ.sub(vertexI); let normal = createVector(ij.y, -ij.x).normalize(); let distance = normal.dot(vertexI); if (distance < 0) { distance *= -1; normal.mult(-1); } if (distance < minDistance) { minDistance = distance; minNormal = normal; minIndex = j; } }

Finding the normal of a vector in 2D is done by swapping the X and Y components and flipping one of their signs. The handedness, either right or left, depends on which component gets flipped. Depending on the winding order, the handedness that results in an outward facing normal changes, so it’s common to add a check to flip it if it’s wrong.

Once we have this normal, we can use the *Support* function to test for a point further out in its direction. If there is one, we’ll insert the support point between the vertices in the polytope and repeat. If there isn’t one, we know we have found the actual closest normal and can return it. Adding a small amount to the distance is useful to stop repeat collisions that would have moved the shapes an even smaller distance.

let support = support(shapeA, shapeB, minNormal); let sDistance = minNormal.dot(support); if(abs(sDistance - minDistance) > 0.001) { minDistance = Infinity; polytope.splice(minIndex, 0, support); } } return minNormal.mult(minDistance + 0.001); }

That’s it for the 2D version. Now that we’ve looked at the core of this algorithm, we can make the jump to 3D. The main difference between the two is that in 2D the edges are implied by the order of the vertices, but in 3D we need to explicitly define the faces because each vertex is shared between three or more faces.

**3D (C++)**

We’ll start with a function that looks very similar to the original GJK function, but this time it takes in the *Simplex* as well as the *Colliders*.

CollisionPoints EPA( const Simplex& simplex, const Collider* colliderA, const Collider* colliderB) {

In 3D we need to keep track of every face. I’ve seen some different techniques, but the most straightforward in my opinion is to treat it like a mesh and use an index. We’ll start with the polytope like we did in the 2D case, but now we’ll also store the faces in a separate list. The most common way to do this is to treat every three indices as a triangle.

This index makes it easy to calculate the normal once per face, instead of with every iteration like in the 2D version, so let’s split that into its own function so we can calculate them before we start.

std::vector<vector3> polytope(simplex.begin(), simplex.end()); std::vector<size_t> faces = { 0, 1, 2, 0, 3, 1, 0, 2, 3, 1, 3, 2 }; // list: vector4(normal, distance), index: min distance auto [normals, minFace] = GetFaceNormals(polytope, faces);

We’ll add the same main loop from before.

vector3 minNormal; float minDistance = FLT_MAX; while (minDistance == FLT_MAX) { minNormal = normals[minFace].xyz(); minDistance = normals[minFace].w; vector3 support = Support(colliderA, colliderB, minNormal); float sDistance = minNormal.dot(support); if (abs(sDistance - minDistance) > 0.001f) { minDistance = FLT_MAX;

When expanding the polytope in 3D, we cannot just add a vertex, we need to repair the faces as well. You would think that removing the face and adding three more would be enough, but when two faces result in the same support point being added, duplicate faces end up inside the polytope and cause incorrect results. The solution is to remove not just the current face, but every face that is pointing in the direction of the support point. To repair it afterwards, we’ll keep track of the unique edges and use those along with the support point’s index to make new faces.

std::vector<std::pair<size_t, size_t>> uniqueEdges; for (size_t i = 0; i < normals.size(); i++) { if (SameDirection(normals[i], support)) { size_t f = i * 3; AddIfUniqueEdge(uniqueEdges, faces, f, f + 1); AddIfUniqueEdge(uniqueEdges, faces, f + 1, f + 2); AddIfUniqueEdge(uniqueEdges, faces, f + 2, f ); faces[f + 2] = faces.back(); faces.pop_back(); faces[f + 1] = faces.back(); faces.pop_back(); faces[f ] = faces.back(); faces.pop_back(); normals[i] = normals.back(); normals.pop_back(); i--; } }

Now that we have a list of unique edges, we can add the new faces to a list and add the supporting point to the polytope. Storing the new faces in their own list allows us to calculate only the normals of these new faces.

std::vector<size_t> newFaces; for (auto [edgeIndex1, edgeIndex2] : uniqueEdges) { newFaces.push_back(edgeIndex1); newFaces.push_back(edgeIndex2); newFaces.push_back(polytope.size()); } polytope.push_back(support); auto [newNormals, newMinFace] = GetFaceNormals(polytope, newFaces);

After we calculate the new normals, we need to find the new closest face. To eke out a little bit more performance, we’ll only iterate over the old normals, then we’ll compare the closest one to the closest face of the new normals. Finally, we can add these new faces and normals to the end of their respective lists.

float oldMinDistance = FLT_MAX; for (size_t i = 0; i < normals.size(); i++) { if (normals[i].w < oldMinDistance) { oldMinDistance = normals[i].w; minFace = i; } } if (newNormals[newMinFace].w < oldMinDistance) { minFace = newMinFace + normals.size(); } faces .insert(faces .end(), newFaces .begin(), newFaces .end()); normals.insert(normals.end(), newNormals.begin(), newNormals.end()); } }

Once a supporting point isn’t found further from the closest face, we’ll return that face’s normal and its distance in a *CollisionPoints*.

CollisionPoints points; points.Normal = minNormal; points.PenetrationDepth = minDistance + 0.001f; points.HasCollision = true; return points; }

That’s it for the main piece of the algorithm.

Don’t think I forgot about the helper functions…

*GetFaceNormals* is just a slightly more complex version of the loop from the 2D version. Instead of *i* and *j*, we now get three vertices by first looking up their index in the *faces* list. In 3D the normal is found by taking the cross product of the vectors between the face’s vertices. The winding order is now controlled by the index, instead of where we put some negative sign. Even though it’s well defined, we don’t check when adding new faces, so we still need the check here. Determining the winding involves finding the normal so there is no reason to not have this check here.

I’ve chosen to pack the distance and normal into a single *vector4* to keep the code shorter.

std::pair<std::vector<vector4>, size_t> GetFaceNormals( const std::vector<vector3>& polytope, const std::vector<size_t>& faces) { std::vector<vector4> normals; size_t minTriangle = 0; float minDistance = FLT_MAX; for (size_t i = 0; i < faces.size(); i += 3) { vector3 a = polytope[faces[i ]]; vector3 b = polytope[faces[i + 1]]; vector3 c = polytope[faces[i + 2]]; vector3 normal = (b - a).cross(c - a).normalized(); float distance = normal.dot(a); if (distance < 0) { normal *= -1; distance *= -1; } normals.emplace_back(normal, distance); if (distance < minDistance) { minTriangle = i / 3; minDistance = distance; } } return { normals, minTriangle }; }

*AddIfUniqueEdge* tests if the reverse of an edge already exists in the list and if so, removes it. If you look at how the winding works out, if a neighboring face shares an edge, it will be in reverse order. Remember, we only want to store the edges that we are going to save because every edge gets removed first, then we repair.

void AddIfUniqueEdge( std::vector<std::pair<size_t, size_t>>& edges, const std::vector<size_t>& faces, size_t a, size_t b) { auto reverse = std::find( // 0--<--3 edges.begin(), // / \ B / A: 2-0 edges.end(), // / A \ / B: 0-2 std::make_pair(faces[b], faces[a]) // 1-->--2 ); if (reverse != edges.end()) { edges.erase(reverse); } else { edges.emplace_back(faces[a], faces[b]); } }

That’s it for this algorithm, this allows us to handle some interesting collisions in the physics engine.

Let’s look at some demos!

]]>When working in 3D everything is made out of meshes. In their simplest form these are big lists of positions that get fed into the GPU for rendering. These lists can be generated or loaded from disk. Most games save a large amount of data in models that make up generic objects like rocks and trees. These typically are generated but then get baked into a game's download and can never change.

Instead, imagine if in an engine you only had to define the bounds of objects and their types, then the engine generated them when needed. I think this would make rouge likes and single player games look more interesting many playthroughs in because the repeating rooms could use differently generated base assets.

That is an end goal for what it's worth...

Right now, to start with something I've compiled an incomplete list of the generation algorithms that I've needed. This only consists of primitive shapes for now, but I think it will still prove useful because every time I need another shape I spend a long time searching on forms but only end up with bits and pieces. To try and help alleviate the pain of making these in the future, I've cleaned up some popular algorithms and added comments that clear up the oddities within them.

Let me know if you have any suggestions, there are more shapes that I want to add but I also want to open it up for people to add their own algorithms. Then expand into more complex meshes, like a rock generator or foliage generation.

]]>The GJK algorithm is very useful and widely used, but no good visualization or concise explanations exist for some reason. A few years ago, I listened and relistened to a video by Casey Muratori that explains the algorithm in great detail and goes through some smart insights that give a good mental image of how it works. Yet I have found no full interactive visualizations. Let’s jump into it!

**Difference between spheres and polygons**

Testing for a collision between spheres is easy because there are only two points in the system. This leaves us with a single vector that we can compare against the sum of their radii to determine if there is a collision.

With polygons we cannot make such simplifications. They are made from multiple vertices, removing any apparent way of finding their distance and clear radius property to compare against. We need a smarter way of testing for a collision.

Like we subtracted the points in the sphere system, let’s see what happens if we subtract the vertices of the polygons.

Subtracting two polygons with the same number of vertices is straightforward, but if we want to support various polygons, we need to subtract each vertex from *every* vertex on the other polygon. Because there are multiple vertices, we are not left with a single vector, but many that form another polygon. This results in a cloud of A*B number vertices that we need to process further to select the outer convex hull from.

This outer hull is known as the Minkowski difference. It represents the distance between every point of the two polygons. We are going to use it to turn two polygons into one that we can analyze to detect a collision. **The key is that if the origin is inside the difference, there must have been two points that subtracted to 0; meaning there is overlap somewhere.**

Move around the B polygon to get an idea of how this works. - This works to various degrees on mobile

**Abstracting shapes into supporting points**

The Minkowski difference is nice for visualization, but far too expensive to compute in real time; we need a way to simplify it.

The GJK algorithm is only concerned with the outer hull of our cloud of vertices, so it would give a substantial speed up if we could cut down on the time spent finding them. Let’s think about what puts a vertex on the hull. If we look closer, notice that those vertices have the most extreme components. They got to their locations from subtraction between two other vertices, so for one to be the most extreme, it must have come from the most extreme vertices on the source polygons. If we define ‘most extreme’ as the furthest in some direction, we can play with the math to get this speed increase.

Finding the furthest vertex is done by iterating over the set of vertices and finding the one with the largest dot product in a direction. Let \vec{D} be the direction and A-B be the cloud of vertices.

\max\{\vec{D}\cdot{(A-B)}\}Computing A-B took A*B number of steps; making this function an O(n^2) operation. Luckily for us, we can distribute and never have to compute the full difference.

If we distribute the dot product and max function we’re left with this:

\max\{(\vec{D}\cdot{A})-(\vec{D}\cdot{B})\} \max\{\vec{D}\cdot{A}\}-\max\{(-\vec{D})\cdot{B}\}Now all we need is A+B steps; turning our quadratic time function into a linear one.

We need to reverse the direction for A when we distribute max because we want to retain the max value. We want the lest extreme vertex from B to subtract from the most extreme vertex from A.

These vertices are referred to as supporting points, and give a view into the Minkowski difference without ever calculating more than we need.

Let’s look at the implementation -- I’m going to continue to use the *Collider* structs from the last article for consistency, but will only include the new pieces from this article.

We’ll start by adding a function that finds the support point in a given direction. Let’s call it *FindFurthestPoint*. If we have other special types of colliders like spheres, capsules, or planes, we can override this function allowing them to be used with GJK as well.

struct Collider { virtual vector3 FindFurthestPoint( vector3 direction) const = 0; };

Next, we’ll make a *MeshCollider* with a list of vertices to act as our polygon. *FindFurthestPoint* needs to loop over each vertex and compare the distance along the direction. We’ll keep track of the max vertex & distance to compare. Once we have iterated over all the points, we’ll return the max point.

struct MeshCollider : Collider { private: std::vector<vector3> m_vertices; public: vector3 FindFurthestPoint( vector3 direction) const override { vector3 maxPoint; float maxDistance = -FLT_MAX; for (vector3 vertex : m_vertices) { float distance = vertex.dot(direction); if (distance > maxDistance) { maxDistance = distance; maxPoint = vertex; } } return maxPoint; } };

We can roll all of this into a function called *Support* that will take two colliders and a direction and return the vertex on the Minkowski difference.

vector3 Support( const Collider* colliderA, const Collider* colliderB, vector3 direction) { return colliderA->FindFurthestPoint( direction) - colliderB->FindFurthestPoint(-direction); }

With these functions, we have abstracted away not only any convex polygon, but any collider type that implements *FindFurthestPoint* into a single function that we can use in the algorithm.

**GJK: Surrounding the origin**

The goal of the GJK algorithm is to determine if the origin is within the Minkowski difference. This would be easy, but we’ve thrown out the complete difference for the sake of performance. We only have the *Support* function that gives us one vertex at a time. We need to iteratively search for and build up what’s referred to as a simplex around the origin.

A simplex is defined as a shape that has N+1 number of vertices with N being the number of dimensions. Practically, this represents the simplest shape that can ‘select’ a region in space. For example, in 2D a triangle is the simplest shape that can select an area containing a specific point. These shapes have simple tests that we can use to determine which vertex, edge, or face is closest to the origin. Depending on which feature is closest, we’ll remove, add, or swap points to make the simplex closer to the origin. If we find that the closest feature is already the closest possible, but the origin is not inside, we know there is no collision. Otherwise, if we find the origin inside the simplex we know there has been a collision.

We get the vertices for the simplex from the *Support* function, so we need to find the direction to the origin from the closest feature. Searching towards the origin allows the algorithm to converge quickly. Let’s look an example. We’ll start with an arbitrary vertex then add or remove vertices every iteration until we surround the origin or find it’s impossible.

We can see that there are two cases that we need to deal with: a line and triangle. We need one more case in the form of a tetrahedron to select a volume if we want 3D collision detection.

To represent the simplex, let’s make a wrapper struct around an *std::array*. This will allow us to keep track of the number of points, while keeping the memory on the stack for quick access.

struct Simplex { private: std::array<vector3, 4> m_points; unsigned m_size; public: Simplex() : m_points({ 0, 0, 0, 0 }) , m_size(0) {} Simplex& operator=(std::initializer_list<vector3> list) { for (auto v = list.begin(); v != list.end(); v++) { m_points[std::distance(list.begin(), v)] = *v; } m_size = list.size(); return *this; } void push_front(vector3 point) { m_points = { point, m_points[0], m_points[1], m_points[2] }; m_size = std::min(m_size + 1, 4u); } vector3& operator[](unsigned i) { return m_points[i]; } unsigned size() const { return m_size; } auto begin() const { return m_points.begin(); } auto end() const { return m_points.end() - (4 - m_size); } };

We need at least one vertex to start, so we’ll manually add it. The search direction for the first vertex doesn’t matter, but you may get less iterations with a smarter choice. I’m going to use unit x (1, 0, 0) for no particular reason.

bool GJK( const Collider* colliderA, const Collider* colliderB) { // Get initial support point in any direction vector3 support = Support(colliderA, colliderB, vector3::unit_x);

Now that we have one point, we can add it to the simplex and set the search direction towards the origin.

// Simplex is an array of points, max count is 4 Simplex points; points.push_front(support); // New direction is towards the origin vector3 direction = -support;

In a loop, we’ll add another point. The exit condition is that this new point is not in front of the search direction. This would exit if the direction finds a vertex that was already the furthest one along it.

while (true) { support = Support(colliderA, colliderB, direction); if (support.dot(direction) <= 0) { return false; // no collision } points.push_front(support);

Now that we have a line, we’ll feed it into a function that updates the simplex and search direction. It’ll return *true* or *false* to signify a collision.

if (NextSimplex(points, direction)) { return true; } } }

That’s all for the main loop. It’s dead simple in the world of algorithms, but the real work is in the *NextSimplex* function. We need a series of different checks for each shape of simplex to see what the new simplex should be and what direction we’ll search in next.

The *NextSimplex* function will act as a dispatcher to three other functions, one for each shape.

bool NextSimplex( Simplex& points, vector3& direction) { switch (points.size()) { case 2: return Line (points, direction); case 3: return Triangle (points, direction); case 4: return Tetrahedron(points, direction); } // never should be here return false; }

We can add one more helper function to help lessen the headache from these next functions.

bool SameDirection( const vector3& direction, const vector3& ao) { return direction.dot(ao) > 0; }

We’ll start with the line case. There are three possible regions that the origin could be in, but realistically only two. We started with point B, and searched in the direction of A, which means that the origin cannot be in the red region. This leaves us with one check between the vector AB and AO. If AO is inside the green region, we move on. If AO is in the blue region, we’ll come back to the line case, but B will be replaced.

bool Line( Simplex& points, vector3& direction) { vector3 a = points[0]; vector3 b = points[1]; vector3 ab = b - a; vector3 ao = - a; if (SameDirection(ab, ao)) { direction = ab.cross(ao).cross(ab); } else { points = { a }; direction = ao; } return false; }

In this case, AO is in the same direction as AB, so we know it’s in the green region. We’ll set the search direction pointing towards the origin and move on. In 2D, you would not need to use cross products, but in 3D the origin could be anywhere in a cylinder around the line, so we need them to get the correct direction.

The triangle case has seven regions, but again we can cull out some impossibilities. Yellow, red, and purple cannot have the origin because the new point we added was A, meaning that the origin cannot be in the direction of the BC face. That leaves us with four regions we need to check.

If the origin is outside the triangle on the AC face, we’ll check if it’s also in the direction of AC. If it is, then we’ll remove B from the simplex and move on, if not, we’ll do a line case between AB. If the origin was not in the direction of the AC face, we’ll check the AB face. If it’s there, we’ll do the same line case between AB. Finally, if both checks fail, we know it must be inside the triangle. In 2D we would be done and could return *true*, but in 3D we need to check if the origin is above or below the triangle and move on.

bool Triangle( Simplex& points, vector3& direction) { vector3 a = points[0]; vector3 b = points[1]; vector3 c = points[2]; vector3 ab = b - a; vector3 ac = c - a; vector3 ao = - a; vector3 abc = ab.cross(ac); if (SameDirection(abc.cross(ac), ao)) { if (SameDirection(ac, ao)) { points = { a, c }; direction = ac.cross(ao).cross(ac); } else { return Line(points = { a, b }, direction); } } else { if (SameDirection(ab.cross(abc), ao)) { return Line(points = { a, b }, direction); } else { if (SameDirection(abc, ao)) { direction = abc; } else { points = { a, c, b }; direction = -abc; } } } return false; }

The tetrahedron case is the most complex, but almost entirely made up of triangle cases. We don’t need to test for the origin below the tetrahedron for the same reason as before. We only need to determine which face, if any, the origin is in the direction of. If there is one, we’ll go back to the triangle case with that face as the simplex, but if not, we know it must be inside the tetrahedron and we’ll return *true*.

bool Tetrahedron( Simplex& points, vector3& direction) { vector3 a = points[0]; vector3 b = points[1]; vector3 c = points[2]; vector3 d = points[3]; vector3 ab = b - a; vector3 ac = c - a; vector3 ad = d - a; vector3 ao = - a; vector3 abc = ab.cross(ac); vector3 acd = ac.cross(ad); vector3 adb = ad.cross(ab); if (SameDirection(abc, ao)) { return Triangle(points = { a, b, c }, direction); } if (SameDirection(acd, ao)) { return Triangle(points = { a, c, d }, direction); } if (SameDirection(adb, ao)) { return Triangle(points = { a, d, b }, direction); } return true; }

With that final case, we have completed the GJK algorithm. As you can see it is not that complex looking at it from a geometric point of view. This algorithm only gives you a yes/no answer about a collision, so you cannot respond to it. In the next article I will cover an algorithm that uses the simplex and similar principles to find the collision normal then maybe get into rotational physics. Thanks for reading!

Here is a demo that will let you play around with the algorithm and let you inspect each iteration, here's the full version if you want a better look…

]]>Recently, he has been posting videos about huge aspects of his engine like physics and entity systems, which were what I really wanted to learn about by making myself, but he ended up using libraries instead of going through the internals! I am not against using libraries, but to use them for the fun stuff? I felt like it defeated the point of making a *custom* engine series.

There is an argument to be made about saving time, but this was the first C++ project that I was making and the goal from the start was to go through all the major pillars of an engine: input, graphics, physics, entities, and audio. I wanted to learn how those things worked along with C++ and code design in general.

I bet that some other people are interested in the details of how these systems work, and I want to learn how to explain code better, so I am going to try and make some videos going over the internals of these systems. They end up being much simpler than at first glance.

**Let’s start with the physics engine…**

Physics engines are responsible for figuring out where each object in a scene is over time. Objects can collide with one another, then choose to respond in several ways. It’s a generic problem that the user can configure at several different levels. Do they want a collider? Do they want to respond to collisions? Do they want to simulate dynamics? They could want dynamics, but not gravity. It’s a problem that calls for good planning and robust design.

I looked at how bullet and box2d went about sorting their engines and concluded that the way bullet went about it was solid. I boiled it down to just what was needed, and based my design around that. There are already some great articles going over the hard math involved, so I am going to focus on the design aspect instead because I haven’t seen anyone do that, and it’s also a real headache.

At the current moment, this physics engine is not fully featured, but in future articles I plan to build it out further. This article will not cover rotation, multiple contact point collisions, or constrained simulation. I think it will work out for the best as it’s easy to get overwhelmed, and I want to ease into those topics. With that out of the way, let’s dive into the different parts of a physics engine.

The problem can be split into 2 or 3 pieces, dynamics, collision detection, and collision response. I’ll start with dynamics because it is by far the simplest.

**Dynamics**

Dynamics is all about calculating where the new positions of objects are based on their velocity and acceleration. In high school you learn about the four kinematic equations along with Newton's three laws which describe the motion of objects. We’ll only be using the first and third kinematic equations, the others are more useful for analysis of situations, not simulation. That leaves us with:

v = v_0+at \Delta x = v_0t + \frac{1}{2}at^2We can give ourselves more control by using Newtons 2^{nd} law, subbing out acceleration giving us:

Each object needs to store these three properties: velocity, mass, and net force. Here we find the first decision we can make towards the design, net force could either be a list or a single vector. In school you make force diagrams and sum up the forces, implying that we should store a list. This would make it so you could set a force, but you would need to remove it later which could get annoying for the user. If we think about it further, net force is really the total force applied in a *single* frame, so we can use a vector and clear it at the end of each update. This allows the user to apply a force by adding it, but removing it is automatic. This shortens our code and gives a performance bump because there is no summation of forces, it’s a running total.

We’ll use this struct to store the object info for now.

struct Object { vector3 Position; // struct with 3 floats for x, y, z or i + j + k vector3 Velocity; vector3 Force; float Mass; };

We need a way to keep track of the objects we want to update. A classic approach is to have a *physics world* that has list of objects and a *step* function that loops over each one. Let’s see how that might look; I’ll omit header/cpp files for brevity.

class PhysicsWorld { private: std::vector<Object*> m_objects; vector3 m_gravity = vector3(0, -9.81f, 0); public: void AddObject (Object* object) { /* ... */ } void RemoveObject(Object* object) { /* ... */ } void Step( float dt) { for (Object* obj : m_objects) { obj->Force += obj->Mass * m_gravity; // apply a force obj->Velocity += obj->Force / obj->Mass * dt; obj->Position += obj->Velocity * dt; obj->Force = vector3(0, 0, 0); // reset net force at the end } } };

Note the use of pointers, this forces other systems to take care of the actual storing of objects, leaving the physics engine to worry about physics, not memory allocation.

With this you can simulate all sorts of stuff from objects flying through the sky to solar systems.

You can do a lot with this, but it’s the easy part to be honest, and that’s not what you came for…

**Collision detection**

Collision detection is more involved, but we can lighten the load by using some clever tricks. Let’s think about what needs to be found first. If we look at some examples of objects colliding, we notice that in most cases there is a point on each shape that is furthest inside the other.

This turns out to be all we need to respond to a collision. From those two points we can find the normal, and how deep the objects are inside one another. This is huge because it means that we can abstract the idea of different shapes away, and only worry about the points in the response.

Let’s jump into the code, we’ll need some helper structs that I’ll note first.

struct CollisionPoints { vector3 A; // Furthest point of A into B vector3 B; // Furthest point of B into A vector3 Normal; // B – A normalized float Depth; // Length of B – A bool HasCollision; }; struct Transform { // Describes an objects location vector3 Position; vector3 Scale; quaternion Rotation; };

Each shape will have a different type of collider to hold its properties and a base to allow them to be stored. Any type of collider should be able to test for a collision with any other type, so we’ll add functions in the base for each one. These functions will take *Transforms*, so the colliders can use relative coordinates. I’ll only demonstrate spheres and planes, but the code is repeatable for any number of colliders.

struct Collider { virtual CollisionPoints TestCollision( const Transform* transform, const Collider* collider, const Transform* colliderTransform) const = 0; virtual CollisionPoints TestCollision( const Transform* transform, const SphereCollider* sphere, const Transform* sphereTransform) const = 0; virtual CollisionPoints TestCollision( const Transform* transform, const PlaneCollider* plane, const Transform* planeTransform) const = 0; };

Let’s make both types of colliders at the same time too see how they interact. A sphere is defined as a point and a radius, and a plane is defined as a vector and a distance. We’ll override the functions from *Collider*, but won’t worry about the work for now.

We can choose per collider which other colliders it will detect by filling, or not filling, in these functions. In this case, we don’t want Plane v Plane collisions, so we return an empty *CollisionPoints*.

struct SphereCollider : Collider { vector3 Center; float Radius; CollisionPoints TestCollision( const Transform* transform, const Collider* collider, const Transform* colliderTransform) const override { return collider->TestCollision(colliderTransform, this, transform); } CollisionPoints TestCollision( const Transform* transform, const SphereCollider* sphere, const Transform* sphereTransform) const override { return algo::FindSphereSphereCollisionPoints( this, transform, sphere, sphereTransform); } CollisionPoints TestCollision( const Transform* transform, const PlaneCollider* plane, const Transform* planeTransform) const override { return algo::FindSpherePlaneCollisionPoints( this, transform, plane, planeTransform); } };

We can add a function for testing the base and use a technique called double dispatch. This takes advantage of the type system to determine both types of colliders for us by swapping the arguments, determining the first, then the second type through two calls of TestCollision. This saves us needing to know what type of colliders we are checking, which means we’ve fully abstracted away the notion of different shapes outside the collision detection.

struct PlaneCollider : Collider { vector3 Plane; float Distance; CollisionPoints TestCollision( const Transform* transform, const Collider* collider, const Transform* colliderTransform) const override { return collider->TestCollision(colliderTransform, this, transform); } CollisionPoints TestCollision( const Transform* transform, const SphereCollider* sphere, const Transform* sphereTransform) const override { // reuse sphere code return sphere->TestCollision(sphereTransform, this, transform); } CollisionPoints TestCollision( const Transform* transform, const PlaneCollider* plane, const Transform* planeTransform) const override { return {}; // No plane v plane } };

In cases like this, where there are many classes with a web of similar functions, it can be confusing as to where the actual code is located. *Sphere* v *Sphere* would obviously be in the Sphere.cpp file, but *Sphere* v *Plane* could be in either Sphere.cpp or Plane.cpp, there is no way to know without hunting, which gets annoying when there are many files.

To get around this lets let’s make an *algo* namespace and put the actual work in there. We’ll need a function for each pair of colliders we want to be able to check. I’ve made a *Sphere* v *Sphere*, *Sphere* v *Plane*, but not *Plane* v *Plane* because it’s not so useful. I’m not going to cover these functions here because they are not part of the design per se, but you can check out the source if you are interested.

namespace algo { CollisionPoints FindSphereSphereCollisionPoints( const SphereCollider* a, const Transform* ta, const SphereCollider* b, const Transform* tb); CollisionPoints FindSpherePlaneCollisionPoints( const SphereCollider* a, const Transform* ta, const PlaneCollider* b, const Transform* tb); }

You can use these colliders on their own, but most likely want to attach one to an object. We’ll replace *Position* with a *Transform* in the *Object*. We are still only using position in the dynamics but can use scale and rotation in the collision detection. There is a tricky decision to make here. I’m going to use a *Transform* pointer for now, but we’ll come back to this at the end and see why that might not be the best choice.

struct Object { float Mass; vector3 Velocity; vector3 Force; Collider* Collider; Transform* Transform; };

A good design practice is to separate all the different aspects of complex functions like *Step* into their own. This makes the code much more readable, so let’s add another function named *ResolveCollisions* in the physics world.

First, another helper struct…

struct Collision { Object* ObjA; Object* ObjB; CollisionPoints Points; };

Again, we have the physics world, I’ll compact the parts we have already looked at but it’s nice to have context.

class PhysicsWorld { private: std::vector<Object*> m_objects; vector3 m_gravity = vector3(0, -9.81f, 0); public: void AddObject (Object* object) { /* ... */ } void RemoveObject(Object* object) { /* ... */ } void Step( float dt) { ResolveCollisions(dt); for (Object* obj : m_objects) { /* ... */ } } void ResolveCollisions( float dt) { std::vector<Collision> collisions; for (Object* a : m_objects) { for (Object* b : m_objects) { if (a == b) break; if ( !a->Collider || !b->Collider) { continue; } CollisionPoints points = a->Collider->TestCollision( a->Transform, b->Collider, b->Transform); if (points.HasCollision) { collisions.emplace_back(a, b, points); } } } // Solve collisions } };

This is looking good, because of that double dispatch there is no need for anything other than a single call to TestCollision. Using a break in the for loop gives us the unique pairs, so we never check the same objects twice.

There is only one annoying caveat which is that because the order of the objects is unknown, sometimes you will get a Sphere v Plane check, but others will be a Plane v Sphere check. If we just called the algo function for Sphere v Plane, we would get the reverse answer, so we need to add some code in the plane collider to swap the order of the *CollisionPoints*.

CollisionPoints PlaneCollider::TestCollision( const Transform* transform, const SphereCollider* sphere, const Transform* sphereTransform) const { // reuse sphere code CollisionPoints points = sphere->TestCollision(sphereTransform, this, transform); vector3 T = points.A; // You could have an algo Plane v Sphere to do the swap points.A = points.B; points.B = T; points.Normal = -points.Normal; return points; }

Now that we have detected a collision, we need some way to react to it.

**Collision Response**

Because we have abstracted away the idea of different shapes into points, the collision response is almost pure math. The design is relatively simple compared to what we just went through; we’ll start with the idea of a solver. A solver is used to *solve* things about the physics world. That could be the impulse from a collision or raw position correction, really anything you choose to implement.

Let’s start with an interface.

class Solver { public: virtual void Solve( std::vector<Collision>& collisions, float dt) = 0; };

We’ll need another list in the physics world to store these, and functions to add and remove them. After we generate our list of collisions, we can feed it to each solver.

class PhysicsWorld { private: std::vector<Object*> m_objects; std::vector<Solver*> m_solvers; vector3 m_gravity = vector3(0, -9.81f, 0); public: void AddObject (Object* object) { /* ... */ } void RemoveObject(Object* object) { /* ... */ } void AddSolver (Solver* solver) { /* ... */ } void RemoveSolver(Solver* solver) { /* ... */ } void Step(float dt) { /* ... */ } void ResolveCollisions( float dt) { std::vector<Collision> collisions; for (Object* a : m_objects) { /* ... */ } for (Solver* solver : m_solvers) { solver->Solve(collisions, dt); } } };

In the last section the meat was in the design, this one leans much more towards what kinds of solvers you implement. I’ve made an impulse & position solver myself that seem to work for most situations. To keep this short, I won’t cover the math here, but you can check out the source for the impulse solver here, and the position solver here if you are interested.

Let’s see a demo!

**More Options**

The real power of a physics engines comes from the options that you give to the user. In this example there aren’t too many that can be changed, but we can start to think about the different options we want to add. In most games you want a mix of objects, some that simulate dynamics, and others that are static obstacles. There is also a need for triggers, objects that don’t go through the collision response, but fire off events for exterior systems to react to, like an end of level flag. Let’s go through some minor edits we can make to allow these settings to be easily configured.

The biggest change we can make is to distinguish between objects that simulate dynamics and ones that don’t. Because of how many more settings a dynamic object needs, let’s separate those out from what is necessary for collision detection. We can split *Object* into *CollisionObject* and *Rigidbody* structs. We’ll make *Rigidbody* inherit from *CollisionObject* to reuse the collider properties and allow us to store both types easily.

We are left with these two structs. A *dynamic_cast* could be used to figure out if a *CollisionObject* is really a *Rigidbody*, but will make code slightly longer, so I like to add a boolean flag even through it’s not considered best practice. We can also add a flag for the object to act as a trigger and a function for a callback. While we’re at it, let’s beef up the security by protecting the raw values.

struct CollisionObject { protected: Transform* m_transform; Collider* m_collider; bool m_isTrigger; bool m_isDynamic; std::function<void(Collision&, float)> m_onCollision; public: // getters & setters, no setter for isDynamic };

We can add many more settings to the *Rigidbody*. It’s useful if each object has its own gravity, friction, and bounciness. This opens the door to all sorts of physics based effects. In a game you could have an ability that changes the gravity in an area for a time. You could have some objects be bouncy and other like weight balls. A floor could be made of ice and be slippy for a harder challenge.

struct Rigidbody : CollisionObject { private: vector3 m_gravity; // Gravitational acceleration vector3 m_force; // Net force vector3 m_velocity; float m_mass; bool m_takesGravity; // If the rigidbody will take gravity from the world. float m_staticFriction; // Static friction coefficient float m_dynamicFriction; // Dynamic friction coefficient float m_restitution; // Elasticity of collisions (bounciness) public: // getters & setters };

Let’s split the *PhysicsWorld* into a *CollisionWorld* and a *DynamicsWorld* as well. We can move the *Step* function into the *DynamicsWorld*, and *ResolveCollisions* into the *CollisionWorld*. This saves someone who doesn’t want dynamics from sifting through functions that are useless to them.

We can make some edits to *ResolveCollisions* function to give triggers their correct functionality. Let’s split the function into its parts to keep it readable. Adding a callback to the world can be useful too if you want program wide events.

class CollisionWorld { protected: std::vector<CollisionObject*> m_objects; std::vector<Solver*> m_solvers; std::function<void(Collision&, float)> m_onCollision; public: void AddCollisionObject (CollisionObject* object) { /* ... */ } void RemoveCollisionObject(CollisionObject* object) { /* ... */ } void AddSolver (Solver* solver) { /* ... */ } void RemoveSolver(Solver* solver) { /* ... */ } void SetCollisionCallback(std::function<void(Collision&, float)>& callback) { /* ... */ } void SolveCollisions( std::vector<Collision>& collisions, float dt) { for (Solver* solver : m_solvers) { solver->Solve(collisions, dt); } } void SendCollisionCallbacks( std::vector<Collision>& collisions, float dt) { for (Collision& collision : collisions) { m_onCollision(collision, dt); auto& a = collision.ObjA->OnCollision(); auto& b = collision.ObjB->OnCollision(); if (a) a(collision, dt); if (b) b(collision, dt); } } void ResolveCollisions( float dt) { std::vector<Collision> collisions; std::vector<Collision> triggers; for (CollisionObject* a : m_objects) { for (CollisionObject* b : m_objects) { if (a == b) break; if ( !a->Col() || !b->Col()) { continue; } CollisionPoints points = a->Col()->TestCollision( a->Trans(), b->Col(), b->Trans()); if (points.HasCollision) { if ( a->IsTrigger() || b->IsTrigger()) { triggers.emplace_back(a, b, points); } else { collisions.emplace_back(a, b, points); } } } } SolveCollisions(collisions, dt); // Don't solve triggers SendCollisionCallbacks(collisions, dt); SendCollisionCallbacks(triggers, dt); } };

To keep the *Step* function readable, let’s split it up into pieces as well.

class DynamicsWorld : public CollisionWorld { private: vector3 m_gravity = vector3(0, -9.81f, 0); public: void AddRigidbody( Rigidbody* rigidbody) { if (rigidbody->TakesGravity()) { rigidbody->SetGravity(m_gravity); } AddCollisionObject(rigidbody); } void ApplyGravity() { for (CollisionObject* object : m_objects) { if (!object->IsDynamic()) continue; Rigidbody* rigidbody = (Rigidbody*)object; rigidbody->ApplyForce(rigidbody->Gravity() * rigidbody->Mass()); } } void MoveObjects( float dt) { for (CollisionObject* object : m_objects) { if (!object->IsDynamic()) continue; Rigidbody* rigidbody = (Rigidbody*)object; vector3 vel = rigidbody->Velocity() + rigidbody->Force() / rigidbody->Mass() * dt; rigidbody->SetVelocity(vel); vector3 pos = rigidbody->Position() + rigidbody->Velocity() * dt; rigidbody->SetPosition(pos); rigidbody->SetForce(vector3(0, 0, 0)); } } void Step( float dt) { ApplyGravity(); ResolveCollisions(dt); MoveObjects(dt); } };

Now we have a whole stack of options that the user can configure for many different scenarios with a simple yet powerful API.

There is one more option that I want to cover. The physics world has no need updating every frame. Say a game like CS:GO gets rendered at 300 fps. It’s not checking the physics every frame; it might run at 50 Hz instead. If the game only used the positions from the physics engine, objects would have .02 seconds between each update, causing a jittery look. And that’s an ideal rate, some games will only update at 20 Hz leaving .05 seconds between update!

To get around this, it is common to split the physics world from the rendered world. This is simply done by using a raw *Transform* instead of a pointer and having a system outside the physics interpolate the position every frame. Let’s see how we might implement this.

First, we’ll get rid of that pointer. We’ll need to add a last transform as well which will gets set just before the update in *MoveObjects*.

struct CollisionObject { protected: Transform m_transform; Transform m_lastTransform; Collider* m_collider; bool m_isTrigger; bool m_isStatic; bool m_isDynamic; std::function<void(Collision&, float)> m_onCollision; public: // Getters & setters for everything, no setter for isDynamic };

Because we used getters and setters, this won’t break any code outside the *CollisionObject*. We can make an exterior system that keeps track of how far it is into the physics update and use a linear interpolation between the last and current position. I’m not going to go into where to put this system, but it should update every frame rather than every physics update.

class PhysicsSmoothStepSystem { private: float accumulator = 0.0f; public: void Update() { for (Entity entity : GetAllPhysicsEntities()) { Transform* transform = entity.Get<Transform>(); CollisionObject* object = entity.Get<CollisionObject>(); Transform& last = object->LastTransform(); Transform& current = object->Transform(); transform->Position = lerp( last.Position, current.Position, accumulator / PhysicsUpdateRate() ); } accumulator += FrameDeltaTime(); } void PhysicsUpdate() { accumulator = 0.0f; } };

This system smoothy moves the objects between their positions in the physics engines every frame, removing all jittery artifacts from the movement.

And that’s the final result. I hope you can use the principles from this article to get a better idea of how to lay out complex systems in nimble ways, or even make your own engine. There is a lot more to cover, but I’ll leave it to a part 2 because this is getting long. Let me know what you thought, should I keep focusing on design, or dive deeper into the math behind the implementations?

Thanks for reading, I hope to catch you next time!

]]>There are major benefits that arise from this approach. The final code ends up more developed than what you would have been capable of writing at the beginning. You end up spending less time on the actual problem, because you only work on it when you have a good idea. And, after sitting with it for so long there’s a good chance that even if not fully correct, it will still move you in the right direction. Taking it slow leads to an *aha moment* in the future along with the insight required to solve the problem.

I was inspired to write this article from an extreme example in my own code around making a prefab class for my entity system. When I first wrote the class, I had the intention to create it right then and there, but quickly realized I didn’t have the knowledge to do so. It required doing type specific operations on generic pointers, with no types!

**A quick overview of an entity system & the root of the problem**

Entities are made from sets of components. Components are made up of data. Ideally, they would be Plain Old Data classes, but more complex components that store data in dynamic memory are eventually required. This could be in the form of lists, maps, or things like shared pointers.

When copying a class in C++ we make use of the copy constructor, which takes care of these special cases by calling the copy constructors of the underlying containers, resulting in a deep copy.

Prefabs need to store a list of components and their respected data to spawn an entity in the future. The only way to store multiple types in a single list is to use generic void pointers. There is a fundamental problem that comes up frequently when using this practice; because of the use of voids, we dropped all knowledge of what type of data we are pointing to. When it comes time to spawn the entity, each component’s data needs to be copied into the entity system, but without type information there is no way to know which copy constructor to call, so we can’t make deep copies.

**My process**

I took the first stab at my prefab class in February, but only completed it a couple days ago in June! This was a non-critical, time saver class, so I could afford not to worry about it for several months while working on other stuff. I’m glad I waited though because it opened my mind to this new way of thinking.

I made the first real attempt in early June but was stopped yet again by a lack of type information. The prefab needed to store void*s in a list and copy them into other void*s in the entity system. The simplest way to do this is with a memcpy which works great for POD classes, but when a component requires a deep copy, it will miss most of the real data. Components that make use of containers or inheritance don’t get copied correctly because most of the data is behind pointers.

I got stuck at this point because it seemed impossible to get type information back from the void*s. I knew I was really close to having the solution, I just needed a way to deep copy void*s, but I couldn’t think of any way of doing that, so I put it on pause again.

Cut to 3 weeks later; I was working on a bug in the physics engine where lambdas were not getting copied correctly and finally had that *aha moment*. Forget about copying lambdas, what if I could use lambdas to do the copying? In the entity system there is a Component class that stores metadata like the name and size. I could just add a lambda to it that would call the correct copy constructor. A lambda with a completely generic signature could get created in a template function and called whenever a component needs to get copied.

This solution requires that at some point you use a template function in the entity system. That’s a fair price when the alternatives would require the user to create at least one more function for each component, or add to some list, etc. I love solutions that are completely behind the scenes. Why should the user need to worry about things this low level if all they are trying to do is spawn entities?

Here is the code that creates the lambda. When you register a component, it calls GetCopyFunc and gets the lambda that copies for that component type. This seems like a powerful concept that I want to explore further, the idea that you can call a template function once and store information from it in generic types, making it so there is no need to template further. You can see my attempt at this in the entity system in my engine.

using
func_DeepCopy
= std::function<void(void*,
void*)>;

struct Component {

...

func_DeepCopy DeepCopyFunc;

...

};

template<typename _t>

func_DeepCopy GetCopyFunc() {

return [](void* ptr, void* data) {

_t* p = (_t*)ptr;

_t* d = (_t*)data;

new(p) _t(*d); // Placement new is used because the mem in p is not guaranteed to be anything, but is already allocated

};

}

struct Component {

...

func_DeepCopy DeepCopyFunc;

...

};

template<typename _t>

func_DeepCopy GetCopyFunc() {

return [](void* ptr, void* data) {

_t* p = (_t*)ptr;

_t* d = (_t*)data;

new(p) _t(*d); // Placement new is used because the mem in p is not guaranteed to be anything, but is already allocated

};

}

Even though this prefab system took 5 months, I only spent around an hour or so on it. I think that the result is much better than anything that I could have written months ago. Because I had to live without a prefab system for so long, I got a good idea for what it really needed to do, instead of wanting a prefab system because it’s cool and guessing at the use cases. I also avoided doing any research apart from a single google search for how to make that lambda above that landed me at https://stackoverflow.com, which shows some other constructors.

I think that this style of coding is very powerful, and I want to try and use it in other places in my code more frequently. It seems hard to do though because you need something to work at some point, so I guess I’ll leave it to future *aha’ed* me to see if it works...

After 2 months from the last playtest, the third demo is ready for review. This one brings the first real graphics to the game; with the addition of Voxel Cone Tracing there's a warm glow to the whole forest. Right now that's the biggest time sink per-frame so I am doing some more research about different variations of the technique. Because everything is one screen, there might be a screen space approx that looks good enough.

The final boss has been made to shoot out enemies as an attack, which I think is a cool idea, but needs to be balanced more. I've found a couple of strategies that work well, but you can cheese it easily if you know some tricks. Not too sure if that's a good or bad thing right now, I'm leaning towards the second because it should still pose some challenge. We'll leave it to the playtest though.

The side rooms have turned into more of item rooms than alt paths that teach you how to play, which was the original idea. But I think that it works better not to get to crazy with the path for the first world. . Maybe I'll come back and rework these but I think that it's good for now, the items that you get can help you if you have trouble and work to highlight the gameplay mechanics well.

There are the first hints at what the story is going to be. I'm going to try to tell it through lore and you'll need to piece it together. I was reading something, I'll link it if I can find it, where someone was talking about how telling a simple story through complex means is the worst way to do it, and I have to agree. But, I think that it'll work well and it follows with the themes that I want to focus on so I am going to go ahead with it and we'll see what happens.

You can download it and check it out at the top of the page or right here. Let me know if you don't trust it or what ever I'll bust out the hashes, but c'mon .

]]>