Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Parallel

Collision Detection


SP95: Collision Detection

Collision Detection

Getting the most out of your collision tests

Dave Roberts

Dave is the author of PC Game Programming Explorer (Coriolis Group Books, 1994). He can be reached via CompuServe at 75572,1151.


Collision detection is fundamental to most fast-action arcade games. After all, the program has to know when a missile slams into an alien spaceship or when the giant frog leaps onto a player. If coded incorrectly, however, collision detection can take an inordinate amount of time, decrease the animation frame rate, and ruin an otherwise enjoyable game. In this article, I'll examine the basic collision-detection problem, describe situations that can cause collision-detection code to run slowly, and suggest ways to keep your code skipping along.

Explosive Growth

There are two main reasons why collision-detection code runs slowly: Either the code performs more collision tests than necessary or the individual collision tests themselves are slow. For the first case, imagine a game with two objects on the field of play. You want to determine if the objects have collided. With only two objects, collision detection is easy--you simply test whether object1 is touching object2 or object2 is touching object1. One of these tests is obviously redundant--if object1 is touching object2, then object2 is also touching object1.

A game with three objects requires three collision tests: object1 with object2, object1 with object3, and object2 with object3. With four objects the number of tests increases to six. Table 1 shows how many tests must be performed to check from 2 to 20 objects. The numbers were derived from the formula (n2--n)/2, where n is the number of objects; given n objects, you can determine which objects collide by exhaustively performing n2 tests. Since you don't compare an object with itself, you eliminate n tests, giving n2--n. And since testing object x with object y is the same as testing object y with object x, you eliminate half of the remaining tests, yielding the final formula.

O-notation describes how the run-time or memory requirements of a problem grow as the number of inputs or outputs, n, changes. In this case, n is the number of game objects for which you must calculate collision status, so the algorithm is O(n2). As n increases, the number of required tests increases in roughly the same way as n2, and the run time of the collision-testing code increases at a rate of roughly n2.

This means that having several objects active at once can slow your collision-testing code if you aren't careful. Doubling the number of objects on the screen roughly quadruples the number of tests you must perform. But it could be worse: Algorithms that are O(n3) or O(2n) increase run time much more quickly. Of course, it could also be better: An O(n) algorithm increases at a constant rate depending on how many inputs are fed to it. Doubling the number of inputs doubles the run time. An O(1) algorithm is pure joy because it runs at the same rate no matter how many inputs are fed to it.

Eliminating Collision Tests

Techniques for reducing the number of collision tests include game-rule elimination and eliminations based on spatial position such as axis sorting and the sector method.

Game rules typically dictate which objects are allowed to collide. Consequently, you can reduce the number of collision tests. For instance, imagine writing a game where the player controls a spaceship that shoots aliens. Since you're the programmer, you make the rules. You may decide that aliens can't collide with each other and that the player's ship can't collide with its own missiles. Alien missiles probably won't hit aliens. Player missiles won't hit other player missiles and alien missiles won't hit other alien missiles, although perhaps you should allow player missiles to hit other alien missiles.

Now assume that you have 1 player spaceship, 5 player missiles, 20 aliens, and 10 alien missiles all active in the game at a given point in time. That's 36 total objects, which would require 1296 tests (n2) if every object had to be compared with every other object. Our formula reduces that to 630 tests ((n2--n)/2), but taking the game rules into account reduces this number even further.

The game rules dictate that the player spaceship must be tested against each alien (20 tests) and each alien missile (10 tests); the player missiles must be tested against the aliens (100 tests) and the alien missiles (50 tests). That's it--180 tests total. Since aliens, player missiles, and alien missiles can't collide with objects of the same type, 450 tests (71 percent) of the 630 tests are eliminated.

The real advantage to this approach can be seen when you add one more alien to the screen. Using the original formula, this would require an increase of 36 tests; but because of the game rules, only six must be added (the alien tested against the player spaceship and each player missile).

Spatial-Test Elimination

Some game rules require that every object be tested with every other one. In such situations, it's best to use spatial-test elimination techniques.

Spatial-test elimination works by sorting game objects according to their position on the screen or play field (if the play field is larger than the screen itself). Objects are then tested for collisions with other objects near them. Objects farther away are not tested because they could not possibly be colliding.

Spatial elimination requires extra computation to sort the various objects and perform additional bookkeeping. The key is to make the bookkeeping run quickly and eliminate as many tests as possible. When bookkeeping saves more time than it adds, spatial elimination is worth the effort. It is better suited for games in which a large number of objects are active at once. If a game has only a few objects, there aren't many potential collision tests to eliminate, and the added bookkeeping can exceed the gain.

Axis Sorting

When many sprites are on the screen at one time, they are rarely at the same location. An easy way to eliminate collision tests is to sort the sprites in ascending order using each sprite's x- or y-axis coordinate as a key. To check for collisions, simply test a given sprite with the next few on the list. Stop testing sprites when you reach a sprite further down the list whose location has a key greater than the location of the first sprite, plus its width or height. For example, suppose you have five sprites (as in Table 2) sorted according to their x-coordinates. First, you'd test sprite #1 with sprite #2, then with sprite #3. At sprite #4, you'd stop because sprite #4 is located at x-coordinate 40 and sprite #1's right side is located at x-coordinate 19 (x-coordinate 10+width of 10--1). Since every sprite after sprite #4 has an x-coordinate greater than sprite #4, there is no need to check any further. You'd then continue with sprite #2, and so on, for a total of four tests. Listing One is an implementation of this method.

This method relies on the assumption that sprites are usually distributed across the screen. If the sprites are closely grouped together along the sorting axis, this method will degenerate to the (n2--n)/2 situation.

You could just as easily sort the sprites by y-coordinate. Since the y-resolution of the screen is usually less, however, using the x-coordinate tends to spread out the sprites. Before using either coordinate axis for this method, you should examine your situation to determine which axis will give you better results in general.

I haven't covered actual techniques for sorting the sprites because this is an implementation issue that varies depending on how the sprites in the game move. A common technique is to keep a doubly linked list of sprites in sorted order at all times. After the movement routine calculates the sprite's new position for the next animation frame, it moves the sprite forward or backward in the linked list to keep it sorted. Typically, a sprite's position will only move a few pixels per frame, leading to only one or two position changes relative to the other sprites in the linked list. Frequently, no position changes will be needed. Note that general-purpose sorting routines are not usually necessary or even beneficial. Most of the time, the linked list is nearly sorted anyway. General-purpose sorts like quicksort are overkill and can even show their worst-case running times when the objects are nearly sorted.

The Sector Method

The sector method divides the screen or play field into a grid of sectors. In the simplest case, the screen is divided into quadrants. You determine which sector each sprite is located in according to its position on the screen. You then perform standard collision testing among all the sprites within a given sector. Assuming that sprites are usually well distributed around the screen, most sectors will only contain a few sprites; some may not contain any. For instance, Figure 1 shows 6 sprites on a screen divided into 16 sectors. Sprites #1 and #2 are located in the same sector and would be tested against each other; the same goes for sprites #4 and #5. Sprite #3 is all alone in its sector and would not be tested against anything.

The primary difficulty of the sector method is in handling sprites that fall into more than one sector--sprite #6, for instance. Typically, the sprite is included in all sectors it covers. As long as sprites are smaller than sectors, a single sprite can only be located in one to four sectors. The locations of the sprite's corners determine in which sectors the sprite is located. If sprites can be larger than individual sectors, determining which sectors a sprite covers can involve more calculations.

Try to make sector widths and heights a power of 2 larger than the maximum size of the sprites. This approach makes determining a sprite's sector location much easier because you can simply divide its coordinate values by a power of 2. Dividing by a power of 2 is the same as left shifting the coordinate value, so no slow division instructions or routines need to be invoked. Sectors don't have to be square: The vertical size of a sector can be different than its horizontal size. This lets you optimize the number of sectors versus the sector size to more closely fit the needs of your particular game.

Hybrid Techniques

Sometimes no single technique can acceptably reduce the number of collision tests. This happens when many sprites cluster around each other. Using both game rules and spatial techniques can help out in these cases. For instance, if you're using the sector technique and find that many sprites end up in the same sectors, use game rules to reduce the number of collision tests among sprites within each sector.

Quick Tests

Generally, collision-testing methods are either bounding or pixel based. Bounding methods are fast, imperfect tests. Pixel-based methods are more precise, but much slower. Novice game programmers often make the mistake of using pixel-based methods for all collision tests. Even with aggressive test-reduction techniques, a pixel-based collision algorithm can slow a game down if it's used for every test.

Bounding methods don't compare actual sprite pixels with each other. Instead, a geometric object, typically a rectangle, is used to represent the sprite object in the test. The rectangle is sized to enclose just the pixels that make up the sprite. The advantage is that a few simple tests can determine whether two rectangles touch. Listing Two shows how to test two rectangles for overlap. The test determination takes no more than four comparisons. In fact, since logical expressions are evaluated in a short-circuit fashion in C, most tests will stop before the whole logical expression has been evaluated. The order of the tests in Listing Two is arbitrary. If objects in a specific game are typically above and below one another and collide horizontally, you can put the vertical tests first. Finally, the function call may take up much of the execution time in Listing Two. You can easily make CollisionTestRect into a C preprocessor macro using the ?: ternary operator. This allows the test to be expanded inline, saving the time required for a function call.

The only problem with bounding methods is that they are imperfect. Unless your sprite is shaped very much like a rectangle, for instance, some pixels enclosed by the bounding rectangle won't be a part of your sprite. Those pixels may be a part of your sprite bitmap, but they are transparent. Technically, these pixels are not part of the object but will be considered so for the purpose of testing collisions. Thus, a collision might be indicated between two objects when only a few transparent pixels have actually intersected.

Perfect collision detection requires pixel-based collision detection, in which the pixels of the bitmap are tested to see if they overlap in the current animation frame. Some methods use an auxiliary buffer, where sprites are drawn into the buffer as they are drawn to video memory and tests are performed as each pixel is drawn. This approach requires a lot of memory, however, and can slow down sprite-drawing code.

Another method uses much less memory by first calculating a collision map for each sprite. A collision map is a bitmap with one bit representing each pixel in the original bitmap, which could be larger, a 256-color byte-per-pixel bitmap, for instance. For each solid pixel in the original bitmap, the corresponding bit of the collision map is set to 1; for each transparent pixel, the corresponding bit is set to 0.

The program takes bytes from the two collision maps and aligns them according to the original sprites' positions using shifts and rolls. A logical AND is then performed between the two collision-map bytes. If any bits of the result are set to 1, then nontransparent pixels are colliding. Listing Three shows code that tests two collision maps.

Unfortunately, pixel-based collision testing runs slowly. It requires much more code than the bounding-rectangle test, and consequently, more run time. The ideal test would run as fast as the bounding rectangle test but retain the accuracy of the collision map test. Fortunately, this is possible.

Fast and Accurate

Quick elimination is the secret to fast collision detection. Most collision tests indicate a negative result. If a game runs at a frame rate of 20 frames per second (fps) and two objects collide about once a second, that's only one collision every 20 animation frames. Even if there are many objects on screen that collide fairly frequently, a given object doesn't collide with most of the others. Therefore, it makes no sense to use an accurate but slow collision-test method for every test. A bounding-rectangle collision test can determine that two objects aren't anywhere near each other as well as a pixel-based method, and is much faster. The only problem is that it will sometimes falsely indicate a collision when transparent pixels of the sprite within the bounding rectangle overlap.

Using both a bounding-rectangle test and a pixel-based test in tandem yields both speed and accuracy. First, the combined algorithm performs a bounding-rectangle test. If the sprites don't collide (the usual case), then the bounding-rectangle test indicates a negative result very quickly and the program proceeds to the next pair of sprites. If the bounding-rectangle test indicates a collision, however, the program performs a pixel-based test to determine whether the bounding-rectangle test was correct. The pixel-based test runs much more slowly than the bounding-rectangle test, but it is invoked infrequently.

Acknowledgments

The author gratefully acknowledges Coriolis Group Books for use of some material from the book PC Game Programming Explorer.

Table 1: Number of collision-detection tests that must be performed for a given number of objects.

<b>    
Objects      Collision Tests   </b>
    2            1
    3            3
    4            6
    5            10
    6            15
    7            21
    8            28
    9            36
    10           45
    15           105
    20           190
    

Table 2: Sprites sorted in ascending order by x-coordinate.

<b>    
   Sprite                    Sprite    
   Number    X-coordinate    Width     </b>
    1         10                10
    2         15                 5
    3         18                 8
    4         40                10
    5         45                10
    

Figure 1

Six sprites and their sector locations.

Listing One


typedef struct _SPRITE_DATA {
    struct _SPRITE_DATA * Next;
    int Top;    /* sprite location */
    int Left;
    int Width;  /* sprite dimensions */
    int Height;
} SPRITE_DATA;

/*
    Function: CollisionTestSorted
    Description:
        Tests a linked list of sorted sprites to see if they
        potentially overlap.  If so, they are collision tested.
*/
void CollisionTestSorted(SPRITE_DATA * SpriteList)
{
    SPRITE_DATA *s1, *s2;
    int s1Right;

    s1 = SpriteList;
    while (s1 != NULL) {
        s1Right = s1->Left + s1->Width - 1;
        /* Compare s1 with all following sprites until left edge */
        /* of a following sprite is located beyond the right */
        /* edge of s2. */
        s2 = s1->Next;
        while (s2 != NULL && (s1Right > s2->Left)) {
            CollisionTest(s1, s2);
            s2 = s2->Next;
        }
        s1 = s1->Next;
    }
}


Listing Two


typedef struct {
    int Left;
    int Top;
    int Right;
    int Bottom;
} RECT;

/*
    Function: CollisionTestRect
    Description:
        Tests two bounding rectangles to see if they overlap.
        Returns TRUE if so, FALSE otherwise.
*/
BOOL CollisionTestRect(RECT * r1, RECT * r2)
{
    if (r1->Left > r2->Right || r2->Left > r1->Right ||
        r1->Top > r2->Bottom || r2->Top > r1->Bottom) {
        return FALSE;
    }
    else {
        return TRUE;
    }
}


Listing Three


typedef unsigned int UINT16;
typedef unsigned char UINT8;

typedef struct {
    UINT16  Width;  /* sprite pixel width / 8 bits per pixel */
    UINT16  Height;
    UINT8   Data;       /* first byte of variable length data */
} COLLISION_MAP;

/*
    Function: CollisionTestBitmap
    Description:
        Tests two objects using COLLISION_MAPs.  The upper left corner
        of each object is specified with (x1, y1) and (x2, y2).
*/
BOOL CollisionTestBitmap
    (
    COLLISION_MAP far * Object1,
    COLLISION_MAP far * Object2,
    int x1,
    int y1,
    int x2,
    int y2
    )
{
    UINT8 far * Data1;
    UINT8 far * Data2;
    COLLISION_MAP far * SwapTemp;
    int DeltaX;
    int DeltaY;
    int Shift;
    int Skip;
    UINT16 WidthCounter1;
    UINT16 WidthCounter2;
    UINT16 HeightCounter1;
    UINT16 HeightCounter2;
    UINT8 Object1Data;
    UINT8 ShiftRegister;
    UINT8 OldObject2Data;
    UINT8 NewObject2Data;
    UINT8 FinalObject2Data;

    assert(Object1 != NULL);
    assert(Object2 != NULL);

    DeltaX = x2 - x1;
    DeltaY = y2 - y1;

    /* swap objects to make the algorithm work */
    if (DeltaX < 0) {
        SwapTemp    = Object1;
        Object1     = Object2;
        Object2     = SwapTemp;
        DeltaX      = -DeltaX;
        DeltaY      = -DeltaY;
    }

    Data1           = (UINT8 far *) &(Object1->Data);
    Data2           = (UINT8 far *) &(Object2->Data);

    HeightCounter1  = 0;
    HeightCounter2  = 0;

    /* skip rows off the object with the least Y-value */
    if (DeltaY > 0) {
        Data1 += Object1->Width * DeltaY;
        HeightCounter1 += DeltaY;
    }
    else if (DeltaY < 0) {
        Data2 += Object2->Width * -DeltaY;
        HeightCounter2 -= DeltaY;
    }

    Shift   = DeltaX % 8;   /* amount to shift object 2 data to right */
    Skip    = DeltaX / 8;   /* number of bytes to skip at beginning of */
                            /*   object 1 data line */

    while (HeightCounter1 < Object1->Height &&
        HeightCounter2 < Object2->Height) {

        /* potentially skip a few bytes 'cause obj 1 is to left of obj 2 */
        WidthCounter1   = Skip;
        Data1           += Skip;

        WidthCounter2   = 0;
        OldObject2Data  = 0;

        while (WidthCounter1 < Object1->Width &&
            WidthCounter2 < Object2->Width) {

            /* get data */
            Object1Data     = *Data1++;
            NewObject2Data  = *Data2++;
            /* shift object 2 data to correct delta X differential */
            ShiftRegister   = ((UINT16) OldObject2Data << 8) |
                (UINT16) NewObject2Data;
            ShiftRegister   >>= Shift;
            FinalObject2Data = ShiftRegister & 0xFF;

            /* return if we have a collision */
            if (Object1Data & FinalObject2Data) {
                return TRUE;
            }

            OldObject2Data = NewObject2Data;
            WidthCounter1++;
            WidthCounter2++;
        }

        /* correct pointers at end of line */
        Data1 += Object1->Width - WidthCounter1;
        Data2 += Object2->Width - WidthCounter2;

        HeightCounter1++;
        HeightCounter2++;
    }

    /* we got through all that with no collision */
    return FALSE;
}


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.