If this is your first time on this question, I suggest reading the pre-update part below first, then this part. Here's a synthesis of the problem, though:
Basically, I have a collision detection and resolution engine with a grid spatial partitioning system where order-of-collision and collision groups matter. One body at a time must move, then detect collision, then resolve collisions. If I move all the bodies at once, then generate possible collision pairs, it is obviously faster, but resolution breaks because order-of-collision isn't respected. If I move one body a time, I'm forced to get bodies to check collisions, and it becomes a n^2 problem. Put groups in the mix, and you can imagine why it gets very slow very fast with a lot of bodies.
Update: I've worked really hard on this, but couldn't manage to optimize anything.
I also discovered a big issue: my engine is order-of-collision dependent.
I tried an implementation of unique collision pair generation, which definitely speed up everything by a lot, but broke the order-of-collision.
Let me explain:
in my original design (not generating pairs), this happens:
- a single body moves
- after it has moved, it refreshes its cells and gets the bodies it collides against
- if it overlaps a body it needs to resolve against, resolve the collision
this means that if a body moves, and hits a wall (or any other body), only the body that has moved will resolve its collision and the other body will be unaffected.
This is the behavior I desire.
I understand it's not common for physics engines, but it has a lot of advantages for retro-style games.
in the usual grid design (generating unique pairs), this happens:
- all bodies move
- after all bodies have moved, refresh all cells
- generate unique collision pairs
- for each pair, handle collision detection and resolution
in this case a simultaneous move could have resulted in two bodies overlapping, and they will resolve at the same time - this effectively makes the bodies "push one another around", and breaks collision stability with multiple bodies
This behavior is common for physics engines, but it is not acceptable in my case.
I also found another issue, which is major (even if it's not likely to happen in a real-world situation):
- consider bodies of group A, B and W
- A collides and resolves against W and A
- B collides and resolves against W and B
- A does nothing against B
- B does nothing against A
there can be a situation where a lot of A bodies and B bodies occupy the same cell - in that case, there is a lot of unnecessary iteration between bodies that mustn't react to one another (or only detect collision but not resolve them).
For 100 bodies occupying the same cell, it's 100^100 iterations! This happens because unique pairs aren't being generated - but I can't generate unique pairs, otherwise I would get a behavior I do not desire.
Is there a way to optimize this kind of collision engine?
These are the guidelines that must be respected:
Order of collision is extremely important!
- Bodies must move one at a time, then check for collisions one at a time, and resolve after movement one at a time.
Bodies must have 3 group bitsets
- Groups: groups the body belongs to
- GroupsToCheck: groups the body must detect collision against
- GroupsNoResolve: groups the body must not resolve collision against
- There can be situations where I only want a collision to be detected but not resolved
Pre-update:
Foreword: I'm aware that optimizing this bottleneck is not a necessity - the engine is already very fast. I, however, for fun and educational purposes, would love to find a way to make the engine even faster.
I'm creating a general-purpose C++ 2D collision detection/response engine, with an emphasis on flexibility and speed.
Here's a very basic diagram of its architecture:
Basically, the main class is World
, which owns (manages memory) of a ResolverBase*
, a SpatialBase*
and a vector<Body*>
.
SpatialBase
is a pure virtual class which deals with broad-phase collision detection.
ResolverBase
is a pure virtual class which deals with collision resolution.
The bodies communicate to the World::SpatialBase*
with SpatialInfo
objects, owned by the bodies themselves.
There currenly is one spatial class: Grid : SpatialBase
, which is a basic fixed 2D grid. It has it's own info class, GridInfo : SpatialInfo
.
Here's how its architecture looks:
The Grid
class owns a 2D array of Cell*
. The Cell
class contains a collection of (not owned) Body*
: a vector<Body*>
which contains all the bodies that are in the cell.
GridInfo
objects also contain non-owning pointers to the cells the body is in.
As I previously said, the engine is based on groups.
Body::getGroups()
returns astd::bitset
of all the groups the body is part of.Body::getGroupsToCheck()
returns astd::bitset
of all the groups the body has to check collision against.
Bodies can occupy more than a single cell. GridInfo always stores non-owning pointers to the occupied cells.
After a single body moves, collision detection happens. I assume that all bodies are axis-aligned bounding boxes.
How broad-phase collision detection works:
Part 1: spatial info update
For each Body
body
:
- Top-leftmost occupied cell and bottom-rightmost occupied cells are calculated.
- If they differ from the previous cells,
body.gridInfo.cells
is cleared, and filled with all the cells the body occupies (2D for loop from the top-leftmost cell to the bottom-rightmost cell).
body
is now guaranteed to know what cells it occupies.
Part 2: actual collision checks
For each Body
body
:
body.gridInfo.handleCollisions
is called:
void GridInfo::handleCollisions(float mFrameTime)
{
static int paint{-1};
++paint;
for(const auto& c : cells)
for(const auto& b : c->getBodies())
{
if(b->paint == paint) continue;
base.handleCollision(mFrameTime, b);
b->paint = paint;
}
}
void Body::handleCollision(float mFrameTime, Body* mBody)
{
if(mBody == this || !mustCheck(*mBody) || !shape.isOverlapping(mBody->getShape())) return;
auto intersection(getMinIntersection(shape, mBody->getShape()));
onDetection({*mBody, mFrameTime, mBody->getUserData(), intersection});
mBody->onDetection({*this, mFrameTime, userData, -intersection});
if(!resolve || mustIgnoreResolution(*mBody)) return;
bodiesToResolve.push_back(mBody);
}
Collision is then resolved for every body in
bodiesToResolve
.That's it.
So, I've been trying to optimize this broad-phase collision detection for quite a while now. Every time I try something else than the current architecture/setup, something doesn't go as planned or I make assumption about the simulation that later are proven to be false.
My question is: how can I optimize the broad-phase of my collision engine?
Is there some kind of magic C++ optimization that can be applied here?
Can the architecture be redesigned in order to allow for more performance?
- Actual implementation: SSVSCollsion
- Body.h, Body.cpp
- World.h, World.cpp
- Grid.h, Grid.cpp
- Cell.h, Cell.cpp
- GridInfo.h, GridInfo.cpp
Callgrind output for latest version: http://txtup.co/rLJgz
getBodiesToCheck()
was called 5462334 times, and took 35,1% of the entire profiling time (Instruction read access time) \$\endgroup\$