Professional Documents
Culture Documents
OVERVIEW
Here's how the code works, roughly speaking. The screen is divided
into "sectors," defined by a regularly-spaced grid. All objects
(e.g., sprites) are placed into the appropriate sectors as determined
by the objects' upper-left corners. Then the objects in each sector
are tested for collision with one another, taking advantage of the
observation that overlapping objects will usually be classified into
the same sector. This isn't always the case, however, and the code
therefore makes well-behaved translations of the grid to ensure that
all collisions will be detected and that no false collisions will be
reported.
NOTES
The first thing to do when you get the code is to look at the
declaration of the "obj" structure. It represents an on-screen
object. For convenience's sake, I've made all my objects 30x30. That
way I can define the x and y data members to be the upper-left corner
of an object's bounding rectangle, and when I need the lower-right, I
calculate it by adding 30 to x and y. (That's the way I'd do it in a
shoot-'em-up, too. Each class of objects would have a different size
associated with it. E.g., for a bullet I'd add, say, 8 instead of 30
because they're smaller.)
I keep all the objects in a linked list, where the obj_link member is
the link between objects. The sector_link is especially important.
It is used to keep all the objects in a sector in a single linked
list. That's a key to making this collision detection technique
work quickly. Placing each object in its containing sector takes O(1)
time, with a low constant, to boot.
You may find it interesting that I've chosen to repeat the entire
sectorization and per-sector collision checking process four times.
That's how I get around the problems associated with overlapping
objects that are placed into adjacent sectors. Instead of testing for
collisions with objects in adjacent sectors, I just shift the entire
sector grid and repeat the process. Before you accuse me of being
insane for this "four-shifts" business, you should know that it's
asymptotically 20 times faster than testing the adjacent sectors, and
about 40 times faster for the most common "real world" cases. If
you're interested in my analysis, it's near the end of my notes.
Uninterested readers may feel free to skip it.
Assume that objects are randomly distributed over the screen and that
there are on average K objects in each sector. Recall that to test
for collisions in each sector, we use a brute-force technique that
requires n(n-1)/2 rectangle intersection operations (check it) for n
objects. Now we can compare the four-shifts method with the
test-adjacent-sectors method.
The four-shifts method needs to perform *no* tests when there's only a
single object in a sector---a very common case. The adjacent-sectors
method, on the other hand, needs an average of 36 tests to handle the
same situation.
THE CODE
======= begin
// Sector-based collision detection routines &
// timing code.
//
// Tom Moertel 21-Jun-94
//
// Results for a 25 MHz 68040 Macintosh (not
// exactly a screamer) and an 80 MHz PPC 601
// Power Macintosh 8100 (this one screams):
//
// tests/s
// object count -68K- -PPC-
//
// 0 611 7640
// 50 340 4020
// 100 189 2060
// 200 81 788
//
// where a "test" is defined to be a complete
// check of all objects, determining for each
// object whether it is involved in a collision
// (and if it is, with what other object).
//
// NOTES
//
// For this job I made all objects 30x30, but
// the code will work for arbitrarily-sized
// objects, with the restriction that objects
// are smaller than half of kSectorSize.
//
// This code is far from optimized. I didn't
// even bother to run it through a profiler.
// With a little work, it could probably be
// twice as fast.
//
// LEGAL STUFF
//
// Feel free to use this code in your own
// projects, but please give me credit.
//
// Copyright 1994 by Tom Moertel
// moertel@acm.org
//
// PORTING
//
// Most of the "real" code is portable C++,
// but the testing code uses some Mac-
// specific calls, namely Microseconds()
// and a few graphics and windowing calls.
// To port to the timing code to your platform,
// redifine Clock_us() to return the current
// state (count) of a fast internal clock in
// microseconds. The Macintosh drawing
// code will automaticaly compile out on
// non-Mac platforms, so if you want pretty
// pictures, you'll have to roll your own.
#include <iostream.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
enum
{
kMaxObjects = 200, // more than you're likely to need
kRectSize = 30, // each object is 30 x 30 pixels
kTBase = 1000000L, // timing is in microseconds
kTestLength = 30*kTBase,// 30 seconds per experiment
kCycleLength = 50 // inner timing loop cycles 50 times
};
// types
// sprite object
struct obj
{
scalar x, y; // coords
obj* sector_link; // link in sector list
obj* obj_link; // link in obj list
// ... other members ...
} ;
// module-scope globals
// forward declatations
//==================================================================
// test code
//==================================================================
void main()
{
srand((unsigned int) Clock_us());
_RunExperiment( 0, false);
_RunExperiment( 50, false);
_RunExperiment(100, false);
_RunExperiment(200, true ); // draw this one
}
{
long t0 = Clock_us();
_RandomizeObjects(numObjects);
endTime += Clock_us() - t0;
}
// test/timing loop
scalar i;
for (i = 0; i < kCycleLength && Clock_us() < endTime; i++)
_DetermineCollisions(), iterations++;
}
if (drawQ)
_ShowLastIteration(numObjects); // draw results
cout << (int) iterations << " in " << (int) totalTime
<< " us: ";
cout.precision(2);
cout << usec/iter << " us/iter, "
<< ((float)kTBase)*iter/usec << " iter/s" << endl;
}
//==================================================================
// sector code
//==================================================================
// define constants
//
// Note that to work properly, kSectorSize must be greater
// than twice the length of the largest side of any
// object's bounding box. E.g., if your objects are
// 30x30, then the sector size should be > 60 -- 64 would
// be an excellent choice.
enum {
kSectorSize = 64, // length of a sector's side in pixels
kLog2SectorSize = 6, // log2(kSectorSize): for shifting
kScreenWidth = 640,
kScreenHeight = 480,
o->sector_link = *thisSectorListHead;
*thisSectorListHead = o;
}
}
//==================================================================
// helpers
//==================================================================
if (collidedQ)
FrameRect(&r);
else
PaintRect(&r);
#endif // macintosh
}
while (!Button())
;
SetPort(savePort);
DisposeWindow(wind);
#endif // macintosh
}
(--o)->obj_link = NULL;
}
//==================================================================
// intersection code
//==================================================================
// local helpers
#ifdef BRAIN_DEAD_INLINING
#endif // BRAIN_DEAD_INLINING
Regards,
Tom Moertel Interests: Software Engineering,
Symbolic Mathematics,
MSA, CSG Technologies Division Algorithms,
thor@telerama.lm.com Itchy-Scratchy Theory.