kurt miller's homepage
( My development journal and homepage has moved to a new system here: kurtm.flipcode.com )

Octrees for Potential Colliders 01 November 2003, 4:09PM

I started work on a new collision system, but before I could really dive into that, I needed a way to minimize the number of triangles to check game entities against. The test maps in the game currently have about 20,000 triangles, a number which may increase significantly during development. To check against all of those each frame, for each entity, would of course be pure insanity. There are plenty of approaches to reducing the number of "potential colliders." The one I implemented involved the use of Octrees.

As you probably know, an octree is a hierarchical structure that starts with a bounding cube around your data set. The cube is then divided into 8 smaller cubes and triangles are checked against them for intersections. If any triangles intersect a cube, that cube divides up again into 8 smaller cubes. Division continues until a single cube is either less than a certain size S, or if it contains less than or equal to a suitable number of triangles for your purposes. Triangles themselves are stored only in the leaves of the tree. Note that when a triangle intersects more than one cube, you'd either put the triangle in both cubes (and compensate for this later), or clip the triangle into suitable pieces. Personally, I prefer to store multiple pointers to the same triangle rather than clip it, since I'm currently using the octree only for collisions (not visibility, etc), and would prefer not to alter the original data set.

Here's a screenshot of an example room containing a torus on the left, and a sphere on the right. The octree is rendered on top to show how the cubes follow the triangle detail. Incidentally, looking at my older engine's octree renders is partially what inspired me to write the voxel mesh converter I talked about in another update.



The reason this structure is useful when determining potential colliders is that it lets you quickly eliminate huge groups of triangles that aren't close to the source entity. If the bounding volume of your entity doesn't intersect a cube within the octree, that cube and all of its children can be disregarded, as it will never intersect any of them either. I set my triangle count threshold to 30. In the test data I mentioned above, using the octree reduces the number of potential colliders from 20,000 to the average range of 10-60. That number can be more or less, depending on how many octree nodes the entity's volume intersects simultaneously, but the results so far are definitely acceptable. Remember also to not check the same polygon more than once for collision, even though it may come up in the "potential colliders" list more than once if its associated with more than one octree node. You can store a checked flag to solve this.

Below is the stripped down code of my octree class. Its a first draft, but works correctly so far. Included you'll find a render function for debugging purposes (visualizing the octree nodes, not rendering the triangles), as well as the routine to build the list of potential colliders. The starting inputs the build function takes are the original triangle data set, the bounding cube of that data set, and the maximum number of triangles per leaf, which indicates when to stop the build process. In the build function, the doesTriangleIntersect method checks if a triangle intersects a box. If you're unsure of how to perform this test, I'd suggest checking out Tomas Akenine-Möller's web site, which has a lot of well-written material.

You can view or download the Octree implementation here as well.

//==============================================================================
//  Octree.h
//  Author: Kurt Miller
//
//  Copyright (C) 2003 Gradient Studios, Inc.  All Rights Reserved.
//==============================================================================
//
//  This class is a simple octree implementation.  Its presented for
//  illustrative purposes, but feel free to use or modify it however you'd like.
//
//==============================================================================

class Octree
{
public:

//==============================================================================
//  Octree::Octree()
//==============================================================================

    Octree()
    {
        for(int i=0; i<8; i++)
        {
            m_children[i] = NULL;
        }
    }

//==============================================================================
//  Octree::~Octree()
//==============================================================================

   ~Octree()
    {
        release();
    }

//==============================================================================
//  Octree::build()
//==============================================================================

    bool build(const std::vector<TriangleData> &tripool, 
               const BoundingBox &nodesize, int maxTriCount)
    {
        // set the node's bounding box;

        m_node = nodesize;

        int tricount = (int)tripool.size();

        // if there are fewer triangles than the threshold value,
        // this node becomes a leaf which stores the triangles;

        if(tricount < maxTriCount)
        {
            for(int i=0; i<tricount; i++)
            {
                m_triangles.push_back(tripool[i]);
            }

            return true;
        }

        // create the child cubes;

        BoundingBox childnodes[8];

        createChildCubes(childnodes);

        std::vector<TriangleData> childtris[8];

        // for each child, generate a list of triangles that
        // intersect the child cube;

        for(int i=0; i<tricount; i++)
        {
            for(int j=0; j<8; j++)
            {
                if(childnodes[j].doesTriangleIntersect(tripool[i]))
                {
                    childtris[j].push_back(tripool[i]);
                }
            }
        }

        // for any children with intersecting triangles, create and
        // recursively build the node;

        for(int i=0; i<8; i++)
        {
            if(childtris[i].size())
            {
                m_children[i] = new(Octree);

                if(m_children[i] == NULL)
                {
                    return false;
                }

                if(!m_children[i]->build(childtris[i], childnodes[i]))
                {
                    return false;
                }
            }
        }

        return true;
    }

//==============================================================================
//  Octree::buildCollisionList()
//==============================================================================

    bool buildCollisionList(std::vector<Triangle*> &tlist, const Sphere &ps)
    {
        // if the bounding sphere does not intersect this node,
        // we can disregard it and all of its children;

        if(!ps.doesBoundingBoxIntersect(m_node))
        {
            return true;
        }

        // if this node is a leaf node with triangles, add the children
        // to the potential collision list;  otherwise recursively check;

        if(m_triangles.size())
        {
            int tsize = (int)m_triangles.size();

            for(int i=0; i<tsize; i++)
            {
                tlist.push_back(m_triangles[i].src());
            }
        }
        else
        {
            for(int i=0; i<8; i++)
            {
                if(m_children[i] != NULL)
                {
                    m_children[i]->buildCollisionList(tlist, ps);
                }
            }
        }

        return true;
    }

//==============================================================================
//  Octree::render()
//==============================================================================

    void render()
    {
        for(int i=0; i<8; i++)
        {
            if(m_children[i] != NULL)
            {
                m_children[i]->render();
            }
        }

        // render only leaf nodes with triangles;

        if(m_triangles.size())
        {
            renderBoundingBox(m_node, COLOR_RGB(255, 255, 255));
        }
    }

//==============================================================================
//  Octree::release()
//==============================================================================

    void release()
    {
        for(int i=0; i<8; i++)
        {
            if(m_children[i] != NULL)
            {
                m_children[i]->release();

                delete(m_children[i]);
                m_children[i] = NULL;
            }
        }

        m_triangles.clear();
    }

private:

//==============================================================================
//  Octree::createChildCubes()
//==============================================================================

    void createChildCubes(BoundingBox *nodes)
    {
        Vector3D center = m_node.calcCenter();

        // top left, near;
        nodes[0].bmin().set(m_node.bmin().x(), center.y(), m_node.bmin().z());
        nodes[0].bmax().set(center.x(), m_node.bmax().y(), center.z());

        // top right, near;
        nodes[1].bmin().set(center.x(), center.y(), m_node.bmin().z());
        nodes[1].bmax().set(m_node.bmax().x(), m_node.bmax().y(), center.z());

        // bottom left, near;
        nodes[2].bmin().set(m_node.bmin().x(), m_node.bmin().y(), m_node.bmin().z());
        nodes[2].bmax().set(center.x(), center.y(), center.z());

        // bottom right, near;
        nodes[3].bmin().set(center.x(), m_node.bmin().y(), m_node.bmin().z());
        nodes[3].bmax().set(m_node.bmax().x(), center.y(), center.z());

        // top left, far;
        nodes[4].bmin().set(m_node.bmin().x(), center.y(), center.z());
        nodes[4].bmax().set(center.x(), m_node.bmax().y(), m_node.bmax().z());

        // top right, far;
        nodes[5].bmin().set(center.x(), center.y(), center.z());
        nodes[5].bmax().set(m_node.bmax().x(), m_node.bmax().y(), m_node.bmax().z());

        // bottom left, far;
        nodes[6].bmin().set(m_node.bmin().x(), m_node.bmin().y(), center.z());
        nodes[6].bmax().set(center.x(), center.y(), m_node.bmax().z());

        // bottom right, far;
        nodes[7].bmin().set(center.x(), m_node.bmin().y(), center.z());
        nodes[7].bmax().set(m_node.bmax().x(), center.y(), m_node.bmax().z());
    }


//==============================================================================
//  private member data;
//==============================================================================

    BoundingBox  m_node;
    Octree      *m_children[8];

    std::vector<TriangleData> m_triangles;
};

//==============================--end-of-file--=================================
 








Complete List Of Journal Updates:
  • Auto-Terrain Texturing (15 November 2004, 12:27PM)
  • On Orientation Interfaces (14 November 2004, 7:30PM)
  • Basic Terrain Generation (10 November 2004, 4:39AM)
  • Engine Tool Interface (07 November 2004, 2:37AM)
  • Render Buffer Ranges (02 November 2004, 5:15AM)
  • wxWidgets GUI Toolkit (29 October 2004, 2:31AM)
  • Generic Rendering Interfaces (27 October 2004, 6:55PM)
  • Source Documentation with Doxygen (25 October 2004, 6:34PM)
  • Signs of Life? (25 October 2004, 5:14PM)
  • Key Value Scripts (08 November 2003, 4:32PM)
  • Octrees for Potential Colliders (01 November 2003, 4:09PM)
  • Flexporter and Game Levels (31 October 2003, 12:14PM)
  • New Project and More Updates (31 October 2003, 6:14AM)
  • HSL Color Space (07 May 2003, 6:14AM)
  • A Couple Graphics Books (02 December 2002, 5:12PM)
  • Texture Detail Using Colored Triangles (02 November 2002, 3:26AM)
  • Voxel Mesh Creation and Rendering (27 October 2002, 11:30AM)
  • Development Journal (25 October 2002, 10:57AM)



  • Site Contents Copyright (c) 2002-2004 Kurt Miller. Please do not reprint this jibberish anywhere.