650 lines
31 KiB
Plaintext
650 lines
31 KiB
Plaintext
|
|
||
|
|
||
|
To appear in the Proceedings of the Computer Game Developer's Conference, 1997
|
||
|
|
||
|
|
||
|
Practical Collision Detection Lecture #177 Jonathan Blow
|
||
|
jon@bolt-action.com http://www.bolt-action.com
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
--- Target Audience ---
|
||
|
|
||
|
Programmers of realtime 3D applications who intend to implement sophisticated
|
||
|
collision detection schemes.
|
||
|
|
||
|
|
||
|
--- Abstract ---
|
||
|
|
||
|
Most modern 3D games require some form of collision detection. This paper
|
||
|
presents some basic brute force methods of collision detection between
|
||
|
polyhedra, successively refining the techniques until we are left with
|
||
|
a system that runs quickly. An implementation is examined in which
|
||
|
a game engine handles 32 players and hundreds of objects in realtime.
|
||
|
|
||
|
|
||
|
--- Modus Operandi ---
|
||
|
|
||
|
|
||
|
Over the past year, we have been experimenting with methods of collision
|
||
|
detection for use in games. The goal of this paper is to communicate the
|
||
|
experience we have gained. Being an exposition of "practice and experience",
|
||
|
this paper does not present any new work in that it does not plant a new
|
||
|
condominium tract amid the burgeoning fields of computer science. We hope
|
||
|
merely to provide observations and ideas that will be useful to others
|
||
|
exploring this subject. All the ideas presented here (and many more)
|
||
|
would be evident to anyone who sat down and experimented with collision
|
||
|
detection for a time. Our main goal is to save you some of that time.
|
||
|
This paper is constructed in a form that matches the evolution of our
|
||
|
collision detection system over time. In the first section we begin slowly,
|
||
|
taking small steps until a foundation is firmly established. We detail the
|
||
|
history of our progress, highlighting key discoveries. Finally, we speed
|
||
|
through some advanced concepts, and leave off with some jaded summaries of
|
||
|
the current state of collision detection research.
|
||
|
|
||
|
|
||
|
|
||
|
--- What Do You Want? ---
|
||
|
|
||
|
|
||
|
We wanted to make a game with "good collision detection", but we didn't
|
||
|
take much time to clarify to ourselves what precisely that meant. We knew
|
||
|
that we wanted objects to have the shapes of arbitrary polyhedra, and we
|
||
|
knew that we didn't want grossly inaccurate collision results: that is,
|
||
|
we never wanted to see objects bounce off empty space, and neither did we
|
||
|
want to see them interpenetrate.
|
||
|
|
||
|
|
||
|
|
||
|
--- A First Try ---
|
||
|
|
||
|
|
||
|
If two objects are sticking through each other, and they are both closed
|
||
|
polyhedra, then at least one face of one object will be penetrating at
|
||
|
least one face of the other object. If we can detect this case as soon
|
||
|
as it occurs, then "fix" the situation by moving the objects slightly so
|
||
|
that they no longer penetrate, then we should be done.
|
||
|
|
||
|
Therefore we decided that the logic governing object motion would proceed
|
||
|
in a loop like this:
|
||
|
|
||
|
|
||
|
|
||
|
while the game is running
|
||
|
foreach entity in the world
|
||
|
update(entity)
|
||
|
|
||
|
|
||
|
update(entity):
|
||
|
entity.old_position := entity.current_position
|
||
|
modify entity.current_position based on entity.velocity and other factors
|
||
|
if Colliding(entity) then entity.current_position := entity.old_position
|
||
|
|
||
|
|
||
|
Colliding(current_entity) -> bool:
|
||
|
foreach entity in the world
|
||
|
if entity != current_entity then
|
||
|
if Entities_Collide(current_entity, entity) then return true
|
||
|
return false
|
||
|
|
||
|
|
||
|
Entities_Collide(e1, e2) -> bool:
|
||
|
foreach polygon p1 in e1
|
||
|
foreach polygon p2 in e2
|
||
|
if polygons_intersect(p1, p2) then return true
|
||
|
return false
|
||
|
|
||
|
|
||
|
|
||
|
We have not defined _polygons_intersect_ here. Descriptions of how
|
||
|
to determine the intersections of two polygons can be found in [O94].
|
||
|
It is easier and faster to determine intersections of convex polygons
|
||
|
than nonconvex ones, so one may wish to compose one's models of convex
|
||
|
polygons (there are many good reasons to do this, most of which have to
|
||
|
do with graphics.)
|
||
|
|
||
|
When objects collided, we chose to move them back to their starting
|
||
|
positions, because if those were "safe" spots for the objects a moment ago,
|
||
|
they will probably still be safe. One alternative is to search through
|
||
|
space to find new positions for the objects, which is icky. But there
|
||
|
are complications in the method that we chose: for example, when an object
|
||
|
is moved back to its starting point, we must make sure that it is really
|
||
|
still safe; if not (because an object has moved into that space in the
|
||
|
meantime), we must move the offending object back to its own starting
|
||
|
position. The worse that can happen is that we have to revert every
|
||
|
object in the world to its starting point. For the most part, though,
|
||
|
the pseudocode above illustrates an extremely basic, extremely simple,
|
||
|
and extremely slow way of doing collision detection. Why is it so slow?
|
||
|
If we have two objects, each of which is composed of 100 polygons, then
|
||
|
when they collide, that loop body in Entities_Collide will be executed a
|
||
|
lot -- 1002 times in most cases. Finding whether two polygons intersect
|
||
|
is not the fastest operation known to man. On current-day computers,
|
||
|
doing it 10,000 times is far from instantaneous. And doing it 10,000
|
||
|
times for each pair of objects that may collide, updating the world many
|
||
|
times per second, becomes impossible. It's dreadfully slow, but it works
|
||
|
(with a few exceptions, which we'll talk about later). And something that
|
||
|
works is not a bad starting point.
|
||
|
|
||
|
|
||
|
--- The First Filters ---
|
||
|
|
||
|
|
||
|
One way to speed up a slow algorithm is to install "filters" which keep
|
||
|
the slow part from getting run when it doesn't need to, or which can come
|
||
|
up with an easy answer using inexpensive techniques. Immediately we put
|
||
|
into place two simple filters that most experienced programmers would take
|
||
|
as givens:
|
||
|
|
||
|
|
||
|
A) Segregation is Good
|
||
|
|
||
|
We divide the world into a regular grid that partitions objects into
|
||
|
smaller groups, with the goal of reducing the number of entity-entity
|
||
|
comparisons the system needs to make. Every time we move an object, we
|
||
|
compute which squares of the grid it overlaps. For our engine we chose
|
||
|
a two-dimensional grid, although it is a true 3D world, because the game
|
||
|
is played over a landscape and we do not expect many objects to be piled
|
||
|
on top of each other.
|
||
|
|
||
|
|
||
|
B) Bounding Spheres
|
||
|
|
||
|
Before doing the polyhedron-polyhedron check, we look at the distance
|
||
|
between the centers of the two objects. If they are further than the sum
|
||
|
of the two objects' radii, the objects cannot possibly interpenetrate.
|
||
|
To speed this up slightly, we can check the distance squared versus the
|
||
|
sum of the radii squared (this avoids a costly square root operation).
|
||
|
|
||
|
|
||
|
C) I Prefer Jersey
|
||
|
|
||
|
Actually, we originally had a third filter that ran before the bounding
|
||
|
sphere test, which looked at the Manhattan distance between the objects'
|
||
|
centers. This was not really worth the bother (It almost never saved any
|
||
|
execution time worth worrying about) so we deleted it.
|
||
|
|
||
|
|
||
|
|
||
|
--- Filling In the Gaps ---
|
||
|
|
||
|
|
||
|
The code discussed so far tests for interpenetration, but that might not
|
||
|
be adequate. I've never seen a realtime object simulator in which movement
|
||
|
wasn't discrete; that is to say, motion occurs by teleporting objects
|
||
|
from place to place. The illusion of smooth motion arises because the
|
||
|
distances by which the objects "jump" are very small. But the faster an
|
||
|
object is moving, the further it must jump during a fixed timestep. If an
|
||
|
object jumps far enough during one update, it could "miss" another object,
|
||
|
appearing to fly right through it (or part of one object could miss part
|
||
|
of another, causing strange things to happen). Our quick fix for this was
|
||
|
to create a "speedbox" around the object; the speedbox was a bounding box
|
||
|
that enclosed the full volume of space through which the object might pass
|
||
|
during one update. When testing for collisions we would use the speedbox's
|
||
|
shape instead of the object's actual shape. This violated our original
|
||
|
concern that collisions should be very accurate, but we waved our hands
|
||
|
and said that since the object was moving so quickly, nobody would see
|
||
|
what was going on anyway. See, games are cool because you can wimp out
|
||
|
like that, any time you want. The most drastic (and only theoretically
|
||
|
sound) alternative we know of is to represent shapes mathematically as
|
||
|
functions of their initial space occupancies, their velocities, and time,
|
||
|
and then solve huge sets of simultaneous equations to determine collision.
|
||
|
We do not expect this approach to be computationally feasible any time
|
||
|
this millennium. Divisiveness is Good
|
||
|
|
||
|
It's an ancient fact that, if two convex polyhedra do not intersect, one
|
||
|
can always find a dividing plane between them [Rab95]. Exploiting this
|
||
|
fact seemed like a good idea. We didn't want to limit our objects to
|
||
|
convex polyhedra, or have to express them as being composed of such.
|
||
|
But it is still true that, if objects are mostly convex, then you can
|
||
|
usually find a dividing plane between them (illustrated in figures 5-7).
|
||
|
So we wrote another filter called "plane divides entities", which would
|
||
|
heuristically try to find a dividing plane between two objects. And we
|
||
|
saw that it was good.
|
||
|
|
||
|
By this point we had built up a stack of "easy"-to-compute filters that
|
||
|
would happen before the final collision detection, which became known as the
|
||
|
"hard case". But the hard case was, so to speak, still too hard. And though
|
||
|
the filters were very helpful, there were cases when they just failed to
|
||
|
apply. When two objects got very close to each other in ways that left no
|
||
|
easily-found dividing plane between them, the game slowed down unacceptably.
|
||
|
We knew about BSP trees, so we cracked open some references and began writing
|
||
|
some new hard-case code. We will not attempt to explain BSP trees fully in
|
||
|
this paper. Instead, we refer the reader to [Chin95], [FV90], and [Wade95],
|
||
|
and provide a brief explanation for the sake of context. BSP trees consist
|
||
|
of a hierarchy of planes, where each plane divides a region of space into
|
||
|
two halfspaces. We can use them to describe a solid object by adopting the
|
||
|
convention that each separating plane of a leaf node describes a portion
|
||
|
of the object's surface, where one of the plane's halfspaces represents
|
||
|
the inside of the object, and the other represents space that is outside.
|
||
|
Binary tree organizations of n nodes can often allow search operations to
|
||
|
complete in O(log(n)) time, and the collision detection we are trying to
|
||
|
perform is fundamentally a search. Intuitively, if we use BSP trees well
|
||
|
when testing two objects for intersection, we should be able to handle a
|
||
|
"hard case" collision test in something like O(log(n)2) time, where n is
|
||
|
a typical number of polygons contained by an entity. We can employ BSP
|
||
|
trees to speed up collision detection by using their spatial-partitioning
|
||
|
properties to reject polygons early. If we are detecting a collision
|
||
|
between two entities A and B, and if we know that A lies entirely on one
|
||
|
side of some plane P that cuts through B, then we need only test A against
|
||
|
the parts of B that are on the same side of the plane as A. (In a sense,
|
||
|
this is a more sophisticated version of the Plane Divides Entities test,
|
||
|
where the dividing plane eliminates only part of an object, rather than
|
||
|
the whole thing.) BSP trees provide us with a myriad of such planes.
|
||
|
So if we recurse down the BSP tree of B, finding whether A's bounding
|
||
|
sphere intersects each separating plane (a very cheap operation in itself),
|
||
|
we can ignore many polygons of B that have no chance of colliding with A.
|
||
|
For each polygon of B that passes this filter, we will call a procedure
|
||
|
that compares it with every polygon in A. Some sample C++ source code to
|
||
|
do this is presented below:
|
||
|
|
||
|
struct Polyhedron {
|
||
|
List *faces; // All polygons contained in this object
|
||
|
Point center; // Center of the object
|
||
|
float radius; // Radius of its bounding sphere
|
||
|
BSP_Node *bsp_tree; // BSP tree representing this object
|
||
|
};
|
||
|
|
||
|
enum flag_value {
|
||
|
TOUCHES_POS_HALFSPACE = 0x1,
|
||
|
TOUCHES_NEG_HALFSPACE = 0x2,
|
||
|
SLICED = 0x3 // Touches both halfspaces
|
||
|
};
|
||
|
|
||
|
struct BSP_Node {
|
||
|
float a, b, c, d; // Coefficients of plane equation (ax + by + cz + d = 0)
|
||
|
List *polygons; // Polygons that are coplanar with said plane
|
||
|
BSP_Node *positive, *negative; // Contents of each halfspace
|
||
|
};
|
||
|
|
||
|
bool object_hits_world(BSP_Node *node, Polyhedron *solid) {
|
||
|
if (node == NULL) return false;
|
||
|
|
||
|
int status = classify(node, solid->center, solid->radius);
|
||
|
|
||
|
if (status == SLICED) {
|
||
|
if (test_solid_against_polygons(node->polygons, solid))
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (status & TOUCHES_NEG_HALFSPACE) {
|
||
|
if (object_hits_world(node->negative, solid)) return true;
|
||
|
}
|
||
|
|
||
|
if (status & TOUCHES_POS_HALFSPACE) {
|
||
|
if (object_hits_world(node->positive, solid)) return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int classify(BSP_Node *node, Point center, float radius) {
|
||
|
float distance = (center.x * node->a) + (center.y * node->b)
|
||
|
+ (center.z * node->c) + node->d;
|
||
|
|
||
|
int status = 0;
|
||
|
|
||
|
if (distance - radius <= 0.0) status |= TOUCHES_NEG_HALFSPACE;
|
||
|
if (distance + radius >= 0.0) status |= TOUCHES_POS_HALFSPACE;
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
bool test_solid_against_polygons(List *polygons, Polyhedron *solid) {
|
||
|
Polygon *face1, *face2; Foreach(polygons, face1, {
|
||
|
Foreach(solid->faces, face2, {
|
||
|
if (polygons_intersect(face1, face2)) return true;
|
||
|
});
|
||
|
});
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
We can go further than this: after selecting potentially colliding polygons
|
||
|
from B, rather than testing them against all of A, we can drop them down A's
|
||
|
BSP tree, eliminating collision tests with much of A. By the time we're done
|
||
|
with all that, we should end up performing relatively few polygon-polygon
|
||
|
intersection tests. In our implementation, we choose the smaller of two
|
||
|
potentially colliding objects as A, and the larger as B (judging by the
|
||
|
radii of their bounding spheres). The reasoning behind this was that the
|
||
|
smaller object was more likely to fit between the larger's partitioning
|
||
|
planes, thus reducing the number of polygons considered (see figure 8).
|
||
|
|
||
|
To facilitate the testing of individual polygons from B against A's BSP
|
||
|
tree, we chose to store a center point and bounding radius on each polygon
|
||
|
of each object model. This is a controversial choice as it increases
|
||
|
memory usage, and as a bounding sphere is a fairly pessimistic bounding
|
||
|
volume for a polygon.
|
||
|
|
||
|
|
||
|
--- Early Hit Detection ---
|
||
|
|
||
|
|
||
|
By now things were a lot faster than what we'd started with, but of
|
||
|
course we still wanted to make them faster. We figured that, say, if
|
||
|
you're moving toward a wall and you collide with it, and you're going at
|
||
|
a reasonable speed, you won't jump too far through the wall -- chances are
|
||
|
that some vertices from your object will actually end up inside the wall
|
||
|
(this is illustrated in figure 9.)
|
||
|
|
||
|
So we added a new test that classified the necessary vertices of each
|
||
|
object into the other object; if a vertex from A, for example, ends up on
|
||
|
the interior of B, you know that they have collided, without performing any
|
||
|
polygon intersection tests. Though this test is certainly not sufficient to
|
||
|
determine a collision (again, see the figure), it is faster than comparing
|
||
|
polygons. Eventually we took this test back out. It did speed up the
|
||
|
detection of a hit between two objects, but this had a negligible effect
|
||
|
on the speed of the game as a whole, for one simple reason: objects almost
|
||
|
never collide with each other. For example, suppose you throw a big rock
|
||
|
at your friend's face. The rock will travel toward your friend for some
|
||
|
time, with little computation being performed because of culling by the
|
||
|
early collision filters. But there will come a time when the rock is very
|
||
|
close to your friend, and if he's flinging out his arms in desperation to
|
||
|
(unsuccessfully) shield himself from the rock, it will be difficult to
|
||
|
find a simple dividing plane between your friend and the rock. Therefore,
|
||
|
there will be many update cycles during which the rock is close to your
|
||
|
friend, but hasn't hit him yet. After the rock hits your friend's head
|
||
|
(if he's not a total klutz he'll have at least managed to turn his face
|
||
|
away so that he will not require much plastic surgery), the rock will
|
||
|
bounce off and travel back in something like the opposite direction.
|
||
|
As you can see, there will be many update cycles during which the rock is
|
||
|
close to your friend but not hitting, and only one during which it hits
|
||
|
(figure 10). If the "miss" cycles are much more expensive than the
|
||
|
"hit" cycle, it doesn't matter how much faster the "hit" test becomes. This
|
||
|
is a basic principle of optimization that we failed initially to think about,
|
||
|
and so perhaps we deserve a few rocks ourselves.
|
||
|
|
||
|
|
||
|
--- Aha! ---
|
||
|
|
||
|
|
||
|
But then we did do something that sped up the general case, which was
|
||
|
to perform all those BSP tests substituting a bounding box shape for
|
||
|
A instead of its true shape. Only if some parts of B collide with A's
|
||
|
bounding box do we go and do the full test against A. This does not take
|
||
|
too much computation (relatively), and it speeds up the great majority of
|
||
|
the near-miss cases that occur in our particular game. Finally, we added a
|
||
|
simple "bounding box safety" test that ran before Plane Divides Entities,
|
||
|
which quickly checked to see whether the two objects' bounding boxes
|
||
|
overlapped. (This test is similar to the OBB testing discussed later in
|
||
|
this paper, but with only one bounding box per object.) Accuracy is a Virtue
|
||
|
|
||
|
We have presented a few ways of using bounding volumes and dividing planes,
|
||
|
but when using these one must be very careful not to be bitten by the evil
|
||
|
spectre of Numerical Roundoff Error. To illustrate this, we'll look at
|
||
|
the bounding sphere test. We might initially compute the bounding sphere
|
||
|
for some object like this:
|
||
|
|
||
|
longest_radius := 0.0
|
||
|
|
||
|
foreach vertex in the object
|
||
|
vertex_distance := distance from vertex to object's origin if
|
||
|
vertex_distance > longest_radius
|
||
|
then longest_radius := vertex_distance
|
||
|
|
||
|
When we perform this computation, we will end up with a number that is
|
||
|
somewhat close to the correct radius of the bounding sphere (though it
|
||
|
will usually not be exact, not even to the precision of whatever numerical
|
||
|
representation we are using, because error will accumulate through the
|
||
|
compound mathematical steps we perform to find 'vertex_distance'.
|
||
|
|
||
|
Let's say, for the sake of argument, that the bounding radius we compute
|
||
|
ends up being a little bit smaller than the right answer. You can see how,
|
||
|
if the bounding sphere is too small, a bounding sphere filter test could
|
||
|
decide that there is no collision, when in fact there should have been
|
||
|
(because the little bits of the object that are poking through the sphere
|
||
|
have collided).
|
||
|
|
||
|
It's much worse than that, though. To detect a collision between two
|
||
|
objects, we want them to be in the same coordinate system, which means we
|
||
|
have to push at least one of them through a transformation matrix. Unless
|
||
|
we are using very, very precise and picky math for representing the matrix
|
||
|
(game programmers generally won't, for reasons of speed and schedule)
|
||
|
then by the time a vertex has been transformed, all hell has broken loose
|
||
|
in terms of numerical accuracy (especially since the matrix isn't very
|
||
|
accurate to begin with -- think about all the operations you perform to
|
||
|
compose a matrix and you'll understand.) If this error ends up pushing the
|
||
|
vertex further away from the center of the object during transformation,
|
||
|
the discrepancy between conclusions reported by the bounding sphere filter
|
||
|
and by the hard case will grow accordingly.
|
||
|
|
||
|
Collision detectors that are supposed to work together but end up
|
||
|
contradicting each other are very bad, especially if you're trying to
|
||
|
maintain any kind of system invariants. For our system, we decided that
|
||
|
it must always be true, when the server is in the steady state, that no
|
||
|
objects interpenetrate. Yet if the bounding sphere test causes a collision
|
||
|
to be ignored, we might find two objects interpenetrating at the beginning
|
||
|
of an object update. This Is Bad. It still happens in our system; we
|
||
|
deal with it by calling an emergency routine that removes an object from
|
||
|
the world and then tries to put it back in an arbitrary place as close to
|
||
|
its last position as possible.
|
||
|
|
||
|
|
||
|
|
||
|
--- They Are Not For You ---
|
||
|
|
||
|
|
||
|
"What do you want?" can be a very difficult question to answer. In our case
|
||
|
it turned out that, back in the beginning when we asked ourselves what we
|
||
|
wanted, our answer was insufficient. All along while writing our collision
|
||
|
detection routines we assumed that we would be able to just plug in some
|
||
|
equation-solvers and have some really neat looking physics, rather than
|
||
|
the "reverse the object's velocity" kind of bouncing we had started with.
|
||
|
It turned out that, in order to maintain a physical simulation system in
|
||
|
which awful computational mistakes did not happen, we needed to be very
|
||
|
accurate about where and when collisions took place -- otherwise objects
|
||
|
would get stuck, or they would bounce themselves further into the object
|
||
|
they were supposed to be repelling, and then go flying off into outer
|
||
|
space at ten times the speed of light. Or worse. To find the exact time
|
||
|
of collision, we would perform a binary search in the time domain -- if
|
||
|
the timestep started at time t, and we found that objects A and B were
|
||
|
colliding at time (t+1), then we would backtrack them both to time (t+0.5)
|
||
|
and test them again. If they're still colliding, we go further backward
|
||
|
in time; otherwise we go forward again. We stop when we find the earliest
|
||
|
time at which A and B were still not colliding, down to a resolution of t
|
||
|
that we find personally satisfying. Once we find the collision time, we
|
||
|
compare A and B to find the closest features of each object to the other.
|
||
|
Each feature that we find to be close enough to the other object, we
|
||
|
consider to be colliding, and pass that information to the physics system.
|
||
|
|
||
|
|
||
|
|
||
|
--- Let's Rock ---
|
||
|
|
||
|
|
||
|
Now we'll examine the effectiveness of the various filters in a simple
|
||
|
game situation. The numbers were acquired in the following situation: the
|
||
|
author joins a game server and flies a hovertank, using it to drop a cargo
|
||
|
box onto the landscape. When the box hits the landscape, it disappears
|
||
|
and deploys a repair pad, appearing in its place. The repair pad pivots
|
||
|
and slides against the ground until it comes to rest. (Once the pad comes
|
||
|
to rest, it is marked as sleeping, which means that no collision detection
|
||
|
or physics routines will act on it until it is upset by an outside force.)
|
||
|
The hovertank then turns around and touches down on the repair pad (in
|
||
|
a concave area, such that there are no dividing planes), then takes off
|
||
|
again and flies away.
|
||
|
|
||
|
|
||
|
Object Type # of Polygons # of polygons
|
||
|
(before BSP cuts) (after BSP cuts)
|
||
|
------------------------------------------------------
|
||
|
|
||
|
hovertank 97 111
|
||
|
cargo box 12 12
|
||
|
repair pad 69 76
|
||
|
|
||
|
|
||
|
|
||
|
Here is a listing of collision tests, the number of times during the
|
||
|
sample run that they were effective (came up with a definitive answer about
|
||
|
the status of a collision), the percent of all cases for which they were
|
||
|
effective, and the average amount of time taken for one test (timings taken
|
||
|
from a Pentium 166 running Linux, program compiled with gcc -ggdb -O3 -m486).
|
||
|
|
||
|
|
||
|
|
||
|
# of definitive
|
||
|
Collision Test answers Average Running Time
|
||
|
---------------------------------------------------------------
|
||
|
|
||
|
bounding spheres 3800 tiny
|
||
|
bounding box safety 1911 26.13us
|
||
|
plane divides entities 93 231.94us
|
||
|
BSP hard case 842 450.98us
|
||
|
|
||
|
|
||
|
|
||
|
This diagram shows Plane Divides Entities to be a fairly ineffective,
|
||
|
yet expensive, test. This is true, though it did not appear that way
|
||
|
to us originally. Before we added Bounding Box Safety, PDE was much
|
||
|
more effective than shown above (though it was still just as expensive).
|
||
|
However, once it was added, Bounding Box Safety stole most of the easily
|
||
|
filterable collisions away from PDE. At present it looks like we will end
|
||
|
up junking PDE. Furthermore, if [Got96] is as good as they say, Bounding
|
||
|
Box Safety will get even faster.
|
||
|
|
||
|
Note the high percentage of the time that the system resorted to using the
|
||
|
BSP hard case; in some sense our test run is a pessimistic measurement
|
||
|
since during landing the hovertank becomes intertwined with the repair
|
||
|
pad, much more messily positioned than the average player is during the
|
||
|
majority of gameplay. Extrapolating from these performance numbers,
|
||
|
our system would slow down uncomfortably if all 32 players in a full game
|
||
|
decided to land on complex structures at once. However, we believe that
|
||
|
the system meets current needs.
|
||
|
|
||
|
The code for the collision detection algorithms themselves is not optimized;
|
||
|
we spent all our time dealing with the software engineering intricacies of
|
||
|
putting together a modern game. It seems likely that simple optimizations
|
||
|
could speed up each test by several times, though we feel it would be
|
||
|
more worthwhile to modify the algorithms.
|
||
|
|
||
|
|
||
|
|
||
|
--- Conclusions, Current Work, and Future Concepts ---
|
||
|
|
||
|
|
||
|
The use of easy filters and BSP trees has sped up our system drastically
|
||
|
beyond the simple brute force approach, but there is plenty room for further
|
||
|
drastic improvement. BSP trees require a fair amount of computation to
|
||
|
construct, and once built, they cannot easily be modified except in very
|
||
|
special cases. Because of this, BSP trees are not very useful for objects
|
||
|
that change shape. (They can be used for precomputed 3D mesh animations;
|
||
|
you just precompute one BSP tree per frame of animation. That whole
|
||
|
technique, however, has unappealing limitations.) Several researchers
|
||
|
have gone about creating high-performance collision detection systems.
|
||
|
Here we summarize a few such systems and provide our own comments.
|
||
|
|
||
|
Philip M. Hubbard describes an algorithm in [Hub96] that uses a hierarchy
|
||
|
of spheres to represent an object. This is appealing at first because
|
||
|
sphere-sphere intersection is very cheap. Hubbard engages in expensive
|
||
|
precomputation to find a good fit of spheres for a given object, which
|
||
|
makes the algorithm less attractive for simulating non-rigid bodies. Also,
|
||
|
many spheres are required to represent complex objects with reasonable
|
||
|
accuracy, and the memory required to store said spheres becomes large.
|
||
|
Finally, it does not seem that this algorithm will perform well for objects
|
||
|
that are tightly intertwined -- though the results presented in Hubbard's
|
||
|
paper are hard to interpret, as they are unclear at best.
|
||
|
|
||
|
[Pon] and [Lin] describe methods for detecting collisions by tracking
|
||
|
the closest sets of features between potentially colliding objects.
|
||
|
Such methods are appealing because they might scale up very well: if we
|
||
|
can isolate the features of an object which are candidates for collision,
|
||
|
and we can navigate the objects as they rotate to incrementally consider
|
||
|
new features, then we do not even need to transform most of an object's
|
||
|
geometry to test it for collision in common cases. These methods generally
|
||
|
involve building Voronoi diagrams for the shapes in question -- an expensive
|
||
|
procedure which again limits us to rigid bodies, and which is further
|
||
|
troublesome because numerical accuracy is a notorious problem in the
|
||
|
construction of a 3D Voronoi diagram.
|
||
|
|
||
|
More recently, [Got] et al propose using hierarchies of Oriented Bounding
|
||
|
Boxes (OBBs) and testing for intersection between these boxes using a quick
|
||
|
test based on a separating axis theorem (which is another way of saying
|
||
|
that there always exists a separating plane between two convex polyhedra).
|
||
|
The basic ideas bear similarity to Hubbard's, though the methods for
|
||
|
approximating shapes and testing intersections are completely different.
|
||
|
OBB-trees will generally fit a shape better than Hubbard's spheres for
|
||
|
a given number of subdivisions, but they require much more memory, so a
|
||
|
sphere-approximation system could use deeper trees within the same budget.
|
||
|
The performance figures that Gottschalk et al present are impressive,
|
||
|
though the examples they present in the paper include non-rigid objects and
|
||
|
they neglect to show performance numbers for recomputation of the OBB-trees.
|
||
|
|
||
|
|
||
|
|
||
|
--- Where do we go now? ---
|
||
|
|
||
|
|
||
|
Speaking for ourselves, it seems likely that in the next major revision
|
||
|
of our engine we will abandon BSP trees and instead use a hierarchy of
|
||
|
bounding volumes. Rather than require the bounding volumes to fit shapes
|
||
|
as closely as Gottschalk or Hubbard, we will probably use looser-fitting
|
||
|
volumes with correspondingly shallower trees. Each bounding volume would
|
||
|
contain a list of the features it encloses; once we had determined that
|
||
|
some set of leaf-node volumes between two objects intersect, we would
|
||
|
perform n2 collision detection between pairs of offending volumes (though
|
||
|
at this point n would be very small.) Both spheres and axis- aligned
|
||
|
bounding boxes are appealing candidates for such volumes, since they do
|
||
|
not necessitate the successive matrix operations required by OBBs.
|
||
|
|
||
|
Hierarchies of bounding volumes could be made to work quite well for dynamic
|
||
|
shapes; if a moving feature is allowed to stretch the bounding volume that
|
||
|
contains it, and if that stretching propagates itself up to the root of the
|
||
|
hierarchy, then a moving shape will always have a valid bounding hierarchy
|
||
|
which is quickly computable. (Continued validity would be assured so long
|
||
|
as we never allow a bounding volume to shrink past its original dimensions.)
|
||
|
The bounding hierarchy for a moving object might slowly degrade over time,
|
||
|
but subtrees of the bounding hierarchy could be incrementally recomputed
|
||
|
to maintain a tight fit at little computational cost.
|
||
|
|
||
|
Hierarchies which store shape features are also a natural fit to our needs,
|
||
|
since the data structure would be equally useful for determining collision
|
||
|
and finding closest feature sets, and in fact the procedute which tests
|
||
|
two objects for collision might return sets of closest features as an
|
||
|
incidental side-effect. This would be very nice compared to our current
|
||
|
closest-feature routine, which is very slow.
|
||
|
|
||
|
|
||
|
|
||
|
--- References ---
|
||
|
|
||
|
|
||
|
[Chin95] Norman Chin, "A Walk Through BSP Trees", Graphics Gems V,
|
||
|
Associated Press, 1995.
|
||
|
|
||
|
[FV90] J. Foley, A. van Dam, S. Feiner, and J. Hughes,
|
||
|
_Computer Graphics Principles and Practice_, 2nd Ed,
|
||
|
Addison-Wesley Systems Programming Series, 1990.
|
||
|
See especially section 12.6.4.
|
||
|
|
||
|
[Got96] S. Gottschalk, M. Lin and D. Manocha, "OBB-Tree:
|
||
|
A Hierarchical Structure for Rapid Interference Detection',
|
||
|
Siggraph '96.
|
||
|
|
||
|
[Hub96] Philip M. Hubbard, "Approximating Polyhedra with Spheres
|
||
|
for Time-Critical Collision Detection", ACM Transactions
|
||
|
on Graphics, Vol. 15, No.3, July 1996.
|
||
|
Available at http://siesta.cs.wustl.edu/~pmh/research.html.
|
||
|
|
||
|
[Lin] Ming C. Lin and Dinesh Manocha, "Efficient Contact
|
||
|
Determination Between Geometric Models". Available at
|
||
|
http://www.cs.unc.edu/~manocha/collision.html.
|
||
|
|
||
|
[O94] J. O'Rourke, "Computational Geometry in C", Cambridge
|
||
|
University Press, 1994. ISBN 0-521-44592-2 paperback,
|
||
|
ISBN 0-521-44034-3 hardback.
|
||
|
|
||
|
[Pon] Madhav K. Ponamgi, Dinesh Manocha, and Ming C. Lin,
|
||
|
"Incremental algorithms for collision detection between solid
|
||
|
models", Available at http://www.cs.unc.edu/~manocha/collision.html.
|
||
|
|
||
|
[Rab94] Rich Rabbitz, "Fast Collision Detection of Moving Convex
|
||
|
Polyhedra", Graphics Gems IV, AP Professional, 1994.
|
||
|
|
||
|
[Wade97] Bretton Wade, "BSP Tree Frequently Asked Questions".
|
||
|
Available at
|
||
|
http://rtfm.mit.edu/pub/usenet/news.answers/graphics/bsptree-faq
|
||
|
(non-authoritative copy).
|