Ticket #2016: quadtree.diff

File quadtree.diff, 34.8 KB (added by Don, 11 years ago)

Contains updated quadtree code, plus an octree with almost identical interface.

  • CQuadtree.h

     
     1/* Copyright (C) 2013 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef INCLUDED_QUADTREE
     19#define INCLUDED_QUADTREE
     20
     21#define MAX_OBJECTS 4
     22
     23/*!
     24A templated, adaptive quadtree class
     25by Don Doumakes
     26
     27When initialized, the tree consists of a single node which contains no game
     28objects.  As game objects are added to a node, child nodes are added to
     29represent smaller and smaller subdivisions of the physical space, and the
     30game objects are distributed downward to the leaf nodes.  Thus the number
     31of game objects in a particular node is kept below an arbitrary maximum
     32(which is what makes this tree "adaptive").  As game objects are moved to
     33new locations or eliminated, the process is reversed, reducing the number
     34of nodes in the tree.
     35
     36The tree class is mostly unaware of implementation details of the game object
     37class.  However, it is useful for both classes to know:
     38- the x, y and z coordinates of the game object
     39- the tree node to which the game object is attached 
     40Therefore, the minimal game object class T must contain the following
     41declarations:
     42\verbatim
     43class T
     44{
     45private:
     46    int m_X;
     47    int m_Y;
     48    int m_Z;
     49    CQuadtree<T> * m_MyNode;
     50public:
     51    friend class CQuadtree<T>
     52};
     53\endverbatim
     54Fortunately, omitting these declarations is a compile-time error.
     55
     56To calculate the range between a given game object and its neighbors,
     57- Implement a rangefinding function in the game object class.
     58- Traverse the tree and call your rangefinding function from each node.
     59The siblings of a given node will be physically nearest, so if the
     60traversal begins at the parent node of "this node," then only the nearest
     61game objects will be checked.
     62*/
     63
     64template <class T> class CQuadtree
     65{
     66private:
     67    /// Game objects contained by this node, along with their coordinates
     68    T * m_GameObject[ MAX_OBJECTS ];
     69    /// Parent node
     70    CQuadtree<T> * m_Parent;
     71    /// Child nodes
     72    CQuadtree<T> * m_Child[2][2];
     73    /// Length of map edges
     74    int m_N;
     75    /// Bounds of this node
     76    int m_MinX, m_MaxX, m_MinY, m_MaxY;
     77
     78    /// Typedef for a pointer to a void member function of T
     79    typedef void ( T::* p_T_member )();
     80    /// Typedef for a pointer to a void member function of CQuadtree
     81    typedef void ( CQuadtree<T>::* p_CQuadtree_member )();
     82    /// Typedef for a pointer to an int member function of CQuadtree
     83    typedef int ( CQuadtree<T>::* p_CQuadtree_memberB )();
     84
     85    /*!
     86        Constructor for non-root nodes.
     87
     88        \param parent Parent node of this node
     89        \param x0 Minimum x value
     90        \param x1 Maximum x value
     91        \param y0 Minimum y value
     92        \param y1 Maximum y value
     93    */
     94    CQuadtree( CQuadtree<T> * parent, int x0, int x1, int y0, int y1 )
     95    {
     96        m_N = parent->m_N;
     97        m_Parent = parent;
     98        m_MinX = x0;
     99        m_MaxX = x1;
     100        m_MinY = y0;
     101        m_MaxY = y1;
     102        memset( m_Child, 0, sizeof( CQuadtree * ) * 4 );
     103        memset( m_GameObject, 0, sizeof( T * ) * MAX_OBJECTS );
     104    };
     105
     106public:
     107
     108    /*!
     109        Constructor for the root node.
     110        \param size Length of each edge of the game world cube.  Must be a power of two.
     111    */
     112    CQuadtree( int size ) : m_N( size ),
     113                            m_Parent( 0 ),
     114                            m_MinX( 0 ),
     115                            m_MaxX( size - 1 ),
     116                            m_MinY( 0 ),
     117                            m_MaxY( size - 1 )
     118    {
     119        if ( ( size && !( size & ( size - 1 ) ) ) == 0 )
     120            throw 0;
     121
     122        memset( m_Child, 0, sizeof( CQuadtree * ) * 4 );
     123        memset( m_GameObject, 0, sizeof( T * ) * MAX_OBJECTS );
     124
     125    };
     126
     127    /*!
     128        Getter for the parent node
     129    */
     130    CQuadtree<T> * Parent()
     131    {
     132        return m_Parent;
     133    };
     134
     135    /*!
     136        Depth-first traversal of the tree
     137
     138        \param doEet Pointer to a member function of the game object class.
     139    */
     140    void traverse( p_T_member doEet )
     141    {
     142        int i, j;
     143        for ( i = 0; i < 2; ++i )
     144            for ( j = 0; j < 2; ++j )
     145                if ( m_Child[i][j] )
     146                    // Traverse this child tree.
     147                    m_Child[i][j]->traverse( doEet );
     148   
     149        int k = 0;
     150        while ( k < MAX_OBJECTS && m_GameObject[k] )
     151        {
     152            // Call a function in the game object.
     153            ( m_GameObject[k]->*doEet )();
     154            k++;
     155        }
     156    };
     157
     158    /*!
     159        Depth-first traversal of the tree
     160
     161        \param doEet Pointer to a member function of this class.
     162    */
     163    void traverse( p_CQuadtree_member doEet )
     164    {
     165        int i, j;
     166        for ( i = 0; i < 2; ++i )
     167            for ( j = 0; j < 2; ++j )
     168                if ( m_Child[i][j] )
     169                    // Traverse this child tree.
     170                    m_Child[i][j]->traverse( doEet );
     171
     172        // Call a function in the CQuadtree class.
     173        ( this->*doEet )();
     174    };
     175
     176    /*!
     177        Breadth-first traversal of the tree
     178
     179        \param doEet Pointer to a member function of this class.
     180    */
     181    void traverse( p_CQuadtree_memberB doEet )
     182    {
     183        // Call a function in the CQuadtree class.
     184        ( this->*doEet )();
     185
     186        int i, j;
     187        for ( i = 0; i < 2; ++i )
     188            for ( j = 0; j < 2; ++j )
     189                if ( m_Child[i][j] )
     190                    // Traverse this child tree.
     191                    m_Child[i][j]->traverse( doEet );
     192    };
     193
     194    /*!
     195        Add a game object to this tree.
     196
     197        \param obj Game object to add.
     198        \return Pointer to the node that contains the game object.
     199    */
     200    CQuadtree<T> * addObject( T * obj )
     201    {
     202        // Bad input?
     203        if ( !obj )
     204            throw "Attempt to add null object to tree";
     205        if ( obj->m_X < 0 || obj->m_X > m_N - 1 ||
     206             obj->m_Y < 0 || obj->m_Y > m_N - 1 )
     207            throw "Object has invalid position.";
     208
     209        // Is this the right subtree?
     210        if ( obj->m_X < m_MinX || obj->m_X > m_MaxX ||
     211             obj->m_Y < m_MinY || obj->m_Y > m_MaxY )
     212        {
     213            // Nope.
     214            return m_Parent->addObject( obj );
     215        }
     216
     217        int j = 0;
     218        int k = 0;
     219        // Does this node have children?
     220        if ( m_Child[0][0] )
     221        {
     222            // Yes, so choose a child node
     223            // that includes (x,y).
     224            if ( obj->m_X < m_MinX + ( m_MaxX - m_MinX ) / 2 + 1 )
     225                j = 0;
     226            else
     227                j = 1;
     228            if ( obj->m_Y < m_MinY + ( m_MaxY - m_MinY ) / 2 + 1 )
     229                k = 0;
     230            else
     231                k = 1;
     232
     233            // Add the object to that node instead.
     234            return m_Child[j][k]->addObject( obj );
     235        }
     236
     237        int i = 0;
     238        while ( i < MAX_OBJECTS && m_GameObject[i] )
     239            i++;
     240        if ( i < MAX_OBJECTS )
     241        {
     242            m_GameObject[i] = obj;
     243            obj->m_MyNode = this;
     244            return this;
     245        }
     246        else if ( m_MinX == m_MaxX )
     247        {
     248            // If the size of this node is 1, stop
     249            // adding nodes.
     250            return 0;
     251        }
     252        else
     253        {
     254            // Create new leaf nodes.
     255
     256            /*
     257                Upper left
     258                x ranges from m_MinX to halfway between m_MinX and m_MaxX.
     259                y ranges from m_MinY to halfway between m_MinY and m_MaxY.
     260            */
     261            m_Child[0][0] = new CQuadtree( this,
     262                                    m_MinX,
     263                                    m_MinX + ( m_MaxX - m_MinX ) / 2,
     264                                    m_MinY,
     265                                    m_MinY + ( m_MaxY - m_MinY ) / 2 );
     266            /*
     267                Upper right
     268                x ranges from halfway between m_MinX and m_MaxX to m_MaxX.
     269                y ranges from m_MinY to halfway between m_MinY and m_MaxY.
     270            */
     271            m_Child[1][0] = new CQuadtree( this,
     272                                    m_MinX + ( m_MaxX - m_MinX ) / 2 + 1,
     273                                    m_MaxX,
     274                                    m_MinY,
     275                                    m_MinY + ( m_MaxY - m_MinY ) / 2 );
     276            /*
     277                Lower left
     278                x ranges from m_MinX to halfway between m_MinX and m_MaxX.
     279                y ranges from halfway between m_MinY and m_MaxY to m_MaxY.
     280            */
     281            m_Child[0][1] = new CQuadtree( this,
     282                                    m_MinX,
     283                                    m_MinX + ( m_MaxX - m_MinX ) / 2,
     284                                    m_MinY + ( m_MaxY - m_MinY ) / 2 + 1,
     285                                    m_MaxY );
     286            /*
     287                Lower right
     288                x ranges from halfway between m_MinX and m_MaxX to m_MaxX.
     289                y ranges from halfway between m_MinY and m_MaxY to m_MaxY.
     290            */
     291            m_Child[1][1] = new CQuadtree( this,
     292                                    m_MinX + ( m_MaxX - m_MinX ) / 2 + 1,
     293                                    m_MaxX,
     294                                    m_MinY + ( m_MaxY - m_MinY ) / 2 + 1,
     295                                    m_MaxY );
     296
     297            // Remove and re-add each existing game object at this node, to
     298            // distribute them to the child nodes.
     299            //
     300            // Unrolling this loop produced no discernable speedup
     301            // over 10000 operations.
     302            for ( i = 0; i < MAX_OBJECTS; ++i )
     303            {
     304                if ( !m_GameObject[i] )
     305                    continue;
     306                T * tmp = m_GameObject[i];
     307                removeObject( tmp );
     308                addObject( tmp );
     309            }
     310            return m_Child[j][k]->addObject( obj );
     311        }
     312    };
     313
     314    /*!
     315        Remove an object from this node.
     316        \param obj Game object to remove.
     317    */
     318    void removeObject( T * obj )
     319    {
     320        int i = 0;
     321        // Unrolling this loop produced no discernable speedup
     322        // over 10000 operations.
     323        for ( i = 0; i < MAX_OBJECTS; ++i )
     324            if ( m_GameObject[i] == obj )
     325                break;
     326   
     327        if ( i < MAX_OBJECTS )
     328        {
     329            m_GameObject[i] = 0;
     330            obj->m_MyNode = 0;
     331        }
     332    };
     333
     334    /*!
     335        If the children of this node contain fewer than MAX_OBJECTS game
     336        objects, put all those game objects in this node and delete the child
     337        nodes.
     338        \param node The node whose children may be merged.
     339    */
     340    void Merge()
     341    {
     342        int children = 0;
     343        int objects = 0;
     344        int i, j, k;
     345        for ( i = 0; i < 2; ++i )
     346        {
     347            for ( j = 0; j < 2; ++j )
     348            {
     349                if ( m_Child[i][j] )
     350                {
     351                    children++;
     352                    for ( k = 0; k < MAX_OBJECTS; ++k )
     353                        if ( m_Child[i][j]->m_GameObject[k] )
     354                            objects++;
     355                }
     356            }
     357        }
     358
     359        if ( children == 0 )
     360            return;
     361        if ( objects > MAX_OBJECTS )
     362            return;
     363
     364        // There are children and at most MAX_OBJECTS objects.
     365        // Merge child nodes into this one.
     366        for ( i = 0; i < 2; ++i )
     367        {
     368            for ( j = 0; j < 2; ++j )
     369            {
     370                if ( m_Child[i][j] )
     371                {
     372                    for ( k = 0; k < MAX_OBJECTS; ++k )
     373                        if ( m_Child[i][j]->m_GameObject[k] )
     374                        {
     375                            // Put the object on this node.
     376                            m_GameObject[objects-1] = m_Child[i][j]->m_GameObject[k];
     377                            m_GameObject[objects-1]->m_MyNode = this;
     378                            objects--;
     379                        }
     380                    delete m_Child[i][j];
     381                    m_Child[i][j] = 0;
     382                }
     383            }
     384        }
     385    };
     386
     387    void Debug()
     388    {
     389        std::cout << "Node " << std::hex << this << std::dec;
     390        std::cout << "  x:" << m_MinX << "," << m_MaxX;
     391        std::cout << "  y:" << m_MinY << "," << m_MaxY << "\n";
     392        int i;
     393        for ( i = 0; i < MAX_OBJECTS; ++i )
     394            if ( m_GameObject[i] )
     395                std::cout << "  Object " << std::hex << m_GameObject[i] << std::dec << "  " << m_GameObject[i]->m_X << "," << m_GameObject[i]->m_Y << "\n";
     396
     397        int j, k;
     398        for ( j = 0; j < 2; ++j )
     399            for ( k = 0; k < 2; ++k )
     400                if ( m_Child[j][k] )
     401                    std::cout << "  Child[" << j << "][" << k << "] = " << std::hex << m_Child[j][k] << std::dec << "\n";
     402        std::cout << "  Parent " << std::hex << m_Parent << std::dec << "\n";
     403    };
     404
     405};
     406
     407#endif
  • COctree.h

     
     1/* Copyright (C) 2013 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef INCLUDED_OCTREE
     19#define INCLUDED_OCTREE
     20
     21#define MAX_OBJECTS 4
     22
     23/*!
     24A templated, adaptive octree class
     25by Don Doumakes
     26
     27When initialized, the tree consists of a single node which contains no game
     28objects.  As game objects are added to a node, child nodes are added to
     29represent smaller and smaller subdivisions of the physical space, and the
     30game objects are distributed downward to the leaf nodes.  Thus the number
     31of game objects in a particular node is kept below an arbitrary maximum
     32(which is what makes this tree "adaptive").  As game objects are moved to
     33new locations or eliminated, the process is reversed, reducing the number
     34of nodes in the tree.
     35
     36The tree class is mostly unaware of implementation details of the game object
     37class.  However, it is useful for both classes to know:
     38- the x, y and z coordinates of the game object
     39- the tree node to which the game object is attached 
     40Therefore, the minimal game object class T must contain the following
     41declarations:
     42\verbatim
     43class T
     44{
     45private:
     46    int m_X;
     47    int m_Y;
     48    int m_Z;
     49    COctree<T> * m_MyNode;
     50public:
     51    friend class COctree<T>
     52};
     53\endverbatim
     54Fortunately, omitting these declarations is a compile-time error.
     55
     56To calculate the range between a given game object and its neighbors,
     57- Implement a rangefinding function in the game object class.
     58- Traverse the tree and call your rangefinding function from each node.
     59The siblings of a given node will be physically nearest, so if the
     60traversal begins at the parent node of "this node," then only the nearest
     61game objects will be checked.
     62*/
     63
     64template <class T> class COctree
     65{
     66private:
     67    /// Game objects contained by this node, along with their coordinates
     68    T * m_GameObject[ MAX_OBJECTS ];
     69    /// Parent node
     70    COctree<T> * m_Parent;
     71    /// Child nodes
     72    COctree<T> * m_Child[2][2][2];
     73    /// Length of map edges
     74    int m_N;
     75    /// Bounds of this node
     76    int m_MinX, m_MaxX, m_MinY, m_MaxY, m_MinZ, m_MaxZ;
     77
     78    /// Typedef for a pointer to a void member function of T
     79    typedef void ( T::* p_T_member )();
     80    /// Typedef for a pointer to a void member function of COctree
     81    typedef void ( COctree<T>::* p_COctree_member )();
     82    /// Typedef for a pointer to an int member function of COctree
     83    typedef int ( COctree<T>::* p_COctree_memberB )();
     84
     85    /*!
     86        Constructor for non-root nodes.
     87
     88        \param parent Parent node of this node
     89        \param x0 Minimum x value
     90        \param x1 Maximum x value
     91        \param y0 Minimum y value
     92        \param y1 Maximum y value
     93        \param z0 Minimum z value
     94        \param z1 Maximum z value
     95    */
     96    COctree( COctree<T> * parent, int x0, int x1, int y0, int y1, int z0, int z1 )
     97    {
     98        m_N = parent->m_N;
     99        m_Parent = parent;
     100        m_MinX = x0;
     101        m_MaxX = x1;
     102        m_MinY = y0;
     103        m_MaxY = y1;
     104        m_MinZ = z0;
     105        m_MaxZ = z1;
     106        memset( m_Child, 0, sizeof( COctree * ) * 4 );
     107        memset( m_GameObject, 0, sizeof( T * ) * MAX_OBJECTS );
     108    };
     109
     110public:
     111
     112    /*!
     113        Constructor for the root node.
     114        \param size Length of each edge of the game world cube.  Must be a power of two.
     115    */
     116    COctree( int size ) : m_N( size ),
     117                            m_Parent( 0 ),
     118                            m_MinX( 0 ),
     119                            m_MaxX( size - 1 ),
     120                            m_MinY( 0 ),
     121                            m_MaxY( size - 1 ),
     122                            m_MinZ( 0 ),
     123                            m_MaxZ( size - 1 )
     124    {
     125        if ( ( size && !( size & ( size - 1 ) ) ) == 0 )
     126            throw 0;
     127
     128        memset( m_Child, 0, sizeof( COctree * ) * 8 );
     129        memset( m_GameObject, 0, sizeof( T * ) * MAX_OBJECTS );
     130
     131    };
     132
     133    /*!
     134        Getter for the parent node
     135    */
     136    COctree<T> * Parent()
     137    {
     138        return m_Parent;
     139    };
     140
     141    /*!
     142        Depth-first traversal of the tree
     143
     144        \param doEet Pointer to a member function of the game object class.
     145    */
     146    void traverse( p_T_member doEet )
     147    {
     148        int i, j, k;
     149        for ( i = 0; i < 2; ++i )
     150            for ( j = 0; j < 2; ++j )
     151                for ( k = 0; k < 2; ++k )
     152                    if ( m_Child[i][j][k] )
     153                        // Traverse this child tree.
     154                        m_Child[i][j][k]->traverse( doEet );
     155   
     156        int m = 0;
     157        while ( m < MAX_OBJECTS && m_GameObject[m] )
     158        {
     159            // Call a function in the game object.
     160            ( m_GameObject[m]->*doEet )();
     161            m++;
     162        }
     163    };
     164
     165    /*!
     166        Depth-first traversal of the tree
     167
     168        \param doEet Pointer to a member function of this class.
     169    */
     170    void traverse( p_COctree_member doEet )
     171    {
     172        int i, j, k;
     173        for ( i = 0; i < 2; ++i )
     174            for ( j = 0; j < 2; ++j )
     175                for ( k = 0; k < 2; ++k )
     176                    if ( m_Child[i][j][k] )
     177                        // Traverse this child tree.
     178                        m_Child[i][j][k]->traverse( doEet );
     179
     180        // Call a function in the COctree class.
     181        ( this->*doEet )();
     182    };
     183
     184    /*!
     185        Breadth-first traversal of the tree
     186
     187        \param doEet Pointer to a member function of this class.
     188    */
     189    void traverse( p_COctree_memberB doEet )
     190    {
     191        // Call a function in the COctree class.
     192        ( this->*doEet )();
     193
     194        int i, j, k;
     195        for ( i = 0; i < 2; ++i )
     196            for ( j = 0; j < 2; ++j )
     197                for ( k = 0; k < 2; ++k )
     198                    if ( m_Child[i][j][k] )
     199                        // Traverse this child tree.
     200                        m_Child[i][j][k]->traverse( doEet );
     201    };
     202
     203    /*!
     204        Add a game object to this tree.
     205
     206        \param obj Game object to add.
     207        \return Pointer to the node that contains the game object.
     208    */
     209    COctree<T> * addObject( T * obj )
     210    {
     211        // Bad input?
     212        if ( !obj )
     213            throw "Attempt to add null object to tree";
     214        if ( obj->m_X < 0 || obj->m_X > m_N - 1 ||
     215             obj->m_Y < 0 || obj->m_Y > m_N - 1 ||
     216             obj->m_Z < 0 || obj->m_Z > m_N - 1 )
     217            throw "Object has invalid position.";
     218
     219        // Is this the right subtree?
     220        if ( obj->m_X < m_MinX || obj->m_X > m_MaxX ||
     221             obj->m_Y < m_MinY || obj->m_Y > m_MaxY ||
     222             obj->m_Z < m_MinZ || obj->m_Z > m_MaxZ )
     223        {
     224            // Nope.
     225            return m_Parent->addObject( obj );
     226        }
     227
     228        int j = 0;
     229        int k = 0;
     230        int m = 0;
     231        // Does this node have children?
     232        if ( m_Child[0][0][0] )
     233        {
     234            // Yes, so choose a child node
     235            // that includes (x,y,z).
     236            if ( obj->m_X < m_MinX + ( m_MaxX - m_MinX ) / 2 + 1 )
     237                j = 0;
     238            else
     239                j = 1;
     240            if ( obj->m_Y < m_MinY + ( m_MaxY - m_MinY ) / 2 + 1 )
     241                k = 0;
     242            else
     243                k = 1;
     244            if ( obj->m_Z < m_MinZ + ( m_MaxZ - m_MinZ ) / 2 + 1 )
     245                m = 0;
     246            else
     247                m = 1;
     248
     249            // Add the object to that node instead.
     250            return m_Child[j][k][m]->addObject( obj );
     251        }
     252
     253        int i = 0;
     254        while ( i < MAX_OBJECTS && m_GameObject[i] )
     255            i++;
     256        if ( i < MAX_OBJECTS )
     257        {
     258            m_GameObject[i] = obj;
     259            obj->m_MyNode = this;
     260            return this;
     261        }
     262        else if ( m_MinX == m_MaxX )
     263        {
     264            // If the size of this node is 1, stop
     265            // adding nodes.
     266            return 0;
     267        }
     268        else
     269        {
     270            // Create new leaf nodes.
     271
     272            /*
     273                Northwest bottom
     274                x ranges from m_MinX to halfway between m_MinX and m_MaxX.
     275                y ranges from m_MinY to halfway between m_MinY and m_MaxY.
     276                z ranges from m_MinZ to halfway between m_MinZ and m_MaxZ.
     277            */
     278            m_Child[0][0][0] = new COctree( this,
     279                                    m_MinX,
     280                                    m_MinX + ( m_MaxX - m_MinX ) / 2,
     281                                    m_MinY,
     282                                    m_MinY + ( m_MaxY - m_MinY ) / 2,
     283                                    m_MinZ,
     284                                    m_MinZ + ( m_MaxZ - m_MinZ ) / 2 );
     285            /*
     286                Northeast bottom
     287                x ranges from halfway between m_MinX and m_MaxX to m_MaxX.
     288                y ranges from m_MinY to halfway between m_MinY and m_MaxY.
     289                z ranges from m_MinZ to halfway between m_MinZ and m_MaxZ.
     290            */
     291            m_Child[1][0][0] = new COctree( this,
     292                                    m_MinX + ( m_MaxX - m_MinX ) / 2 + 1,
     293                                    m_MaxX,
     294                                    m_MinY,
     295                                    m_MinY + ( m_MaxY - m_MinY ) / 2,
     296                                    m_MinZ,
     297                                    m_MinZ + ( m_MaxZ - m_MinZ ) / 2 );
     298            /*
     299                Southwest bottom
     300                x ranges from m_MinX to halfway between m_MinX and m_MaxX.
     301                y ranges from halfway between m_MinY and m_MaxY to m_MaxY.
     302                z ranges from m_MinZ to halfway between m_MinZ and m_MaxZ.
     303            */
     304            m_Child[0][1][0] = new COctree( this,
     305                                    m_MinX,
     306                                    m_MinX + ( m_MaxX - m_MinX ) / 2,
     307                                    m_MinY + ( m_MaxY - m_MinY ) / 2 + 1,
     308                                    m_MaxY,
     309                                    m_MinZ,
     310                                    m_MinZ + ( m_MaxZ - m_MinZ ) / 2 );
     311            /*
     312                Southeast bottom
     313                x ranges from halfway between m_MinX and m_MaxX to m_MaxX.
     314                y ranges from halfway between m_MinY and m_MaxY to m_MaxY.
     315                z ranges from m_MinZ to halfway between m_MinZ and m_MaxZ.
     316            */
     317            m_Child[1][1][0] = new COctree( this,
     318                                    m_MinX + ( m_MaxX - m_MinX ) / 2 + 1,
     319                                    m_MaxX,
     320                                    m_MinY + ( m_MaxY - m_MinY ) / 2 + 1,
     321                                    m_MaxY,
     322                                    m_MinZ,
     323                                    m_MinZ + ( m_MaxZ - m_MinZ ) / 2 );
     324
     325            /*
     326                Northwest top
     327                x ranges from m_MinX to halfway between m_MinX and m_MaxX.
     328                y ranges from m_MinY to halfway between m_MinY and m_MaxY.
     329                z ranges from halfway between m_MinZ and m_MaxZ to m_MaxZ.
     330            */
     331            m_Child[0][0][1] = new COctree( this,
     332                                    m_MinX,
     333                                    m_MinX + ( m_MaxX - m_MinX ) / 2,
     334                                    m_MinY,
     335                                    m_MinY + ( m_MaxY - m_MinY ) / 2,
     336                                    m_MinZ + ( m_MaxZ - m_MinZ ) / 2 + 1,
     337                                    m_MaxZ );
     338            /*
     339                Northeast top
     340                x ranges from halfway between m_MinX and m_MaxX to m_MaxX.
     341                y ranges from m_MinY to halfway between m_MinY and m_MaxY.
     342                z ranges from halfway between m_MinZ and m_MaxZ to m_MaxZ.
     343            */
     344            m_Child[1][0][1] = new COctree( this,
     345                                    m_MinX + ( m_MaxX - m_MinX ) / 2 + 1,
     346                                    m_MaxX,
     347                                    m_MinY,
     348                                    m_MinY + ( m_MaxY - m_MinY ) / 2,
     349                                    m_MinZ + ( m_MaxZ - m_MinZ ) / 2 + 1,
     350                                    m_MaxZ );
     351            /*
     352                Southwest top
     353                x ranges from m_MinX to halfway between m_MinX and m_MaxX.
     354                y ranges from halfway between m_MinY and m_MaxY to m_MaxY.
     355                z ranges from halfway between m_MinZ and m_MaxZ to m_MaxZ.
     356            */
     357            m_Child[0][1][1] = new COctree( this,
     358                                    m_MinX,
     359                                    m_MinX + ( m_MaxX - m_MinX ) / 2,
     360                                    m_MinY + ( m_MaxY - m_MinY ) / 2 + 1,
     361                                    m_MaxY,
     362                                    m_MinZ + ( m_MaxZ - m_MinZ ) / 2 + 1,
     363                                    m_MaxZ );
     364            /*
     365                Southeast top
     366                x ranges from halfway between m_MinX and m_MaxX to m_MaxX.
     367                y ranges from halfway between m_MinY and m_MaxY to m_MaxY.
     368            */
     369            m_Child[1][1][1] = new COctree( this,
     370                                    m_MinX + ( m_MaxX - m_MinX ) / 2 + 1,
     371                                    m_MaxX,
     372                                    m_MinY + ( m_MaxY - m_MinY ) / 2 + 1,
     373                                    m_MaxY,
     374                                    m_MinZ + ( m_MaxZ - m_MinZ ) / 2 + 1,
     375                                    m_MaxZ );
     376
     377            // Remove and re-add each existing game object at this node, to
     378            // distribute them to the child nodes.
     379            for ( i = 0; i < MAX_OBJECTS; ++i )
     380            {
     381                if ( !m_GameObject[i] )
     382                    continue;
     383                T * tmp = m_GameObject[i];
     384                removeObject( tmp );
     385                addObject( tmp );
     386            }
     387            return m_Child[j][k][m]->addObject( obj );
     388        }
     389    };
     390
     391    /*!
     392        Remove an object from this node.
     393        \param obj Game object to remove.
     394    */
     395    void removeObject( T * obj )
     396    {
     397        int i = 0;
     398        // Unrolling this loop produced no discernable speedup
     399        // over 10000 operations.
     400        for ( i = 0; i < MAX_OBJECTS; ++i )
     401            if ( m_GameObject[i] == obj )
     402                break;
     403   
     404        if ( i < MAX_OBJECTS )
     405        {
     406            m_GameObject[i] = 0;
     407            obj->m_MyNode = 0;
     408        }
     409    };
     410
     411    /*!
     412        If the children of this node contain fewer than MAX_OBJECTS game
     413        objects, put all those game objects in this node and delete the child
     414        nodes.
     415        \param node The node whose children may be merged.
     416    */
     417    void Merge()
     418    {
     419        int children = 0;
     420        int objects = 0;
     421        int i, j, k, m;
     422        for ( i = 0; i < 2; ++i )
     423        {
     424            for ( j = 0; j < 2; ++j )
     425            {
     426                for ( k = 0; k < 2; ++k )
     427                {
     428                    if ( m_Child[i][j][k] )
     429                    {
     430                        children++;
     431                        for ( m = 0; m < MAX_OBJECTS; ++m )
     432                            if ( m_Child[i][j][k]->m_GameObject[m] )
     433                                objects++;
     434                    }
     435                }
     436            }
     437        }
     438
     439        if ( children == 0 )
     440            return;
     441        if ( objects > MAX_OBJECTS )
     442            return;
     443
     444        // There are children and at most MAX_OBJECTS objects.
     445        // Merge child nodes into this one.
     446        for ( i = 0; i < 2; ++i )
     447        {
     448            for ( j = 0; j < 2; ++j )
     449            {
     450                for ( k = 0; k < 2; ++k )
     451                {
     452                    if ( m_Child[i][j][k] )
     453                    {
     454                        for ( m = 0; m < MAX_OBJECTS; ++m )
     455                            if ( m_Child[i][j][k]->m_GameObject[m] )
     456                            {
     457                                // Put the object on this node.
     458                                m_GameObject[objects-1] = m_Child[i][j][k]->m_GameObject[m];
     459                                m_GameObject[objects-1]->m_MyNode = this;
     460                                objects--;
     461                            }
     462                        delete m_Child[i][j][k];
     463                        m_Child[i][j][k] = 0;
     464                    }
     465                }
     466            }
     467        }
     468    };
     469
     470    void Debug()
     471    {
     472        std::cout << "Node " << std::hex << this << std::dec;
     473        std::cout << "  x:" << m_MinX << "," << m_MaxX;
     474        std::cout << "  y:" << m_MinY << "," << m_MaxY << "\n";
     475        std::cout << "  z:" << m_MinZ << "," << m_MaxZ << "\n";
     476        int i;
     477        for ( i = 0; i < MAX_OBJECTS; ++i )
     478            if ( m_GameObject[i] )
     479                std::cout << "  Object " << std::hex << m_GameObject[i] << std::dec << "  ";
     480                std::cout << m_GameObject[i]->m_X << "," << m_GameObject[i]->m_Y << "," << m_GameObject[i]->m_Z <<"\n";
     481
     482        int j, k, m;
     483        for ( j = 0; j < 2; ++j )
     484            for ( k = 0; k < 2; ++k )
     485                for ( m = 0; m < 2; ++m )
     486                    if ( m_Child[j][k][m] )
     487                        std::cout << "  Child[" << j << "][" << k << "][" << m << "] = " << std::hex << m_Child[j][k][m] << std::dec << "\n";
     488        std::cout << "  Parent " << std::hex << m_Parent << std::dec << "\n";
     489    };
     490
     491};
     492
     493#endif
  • tests/test_CQuadtree.h

     
     1/* Copyright (C) 2009 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "lib/self_test.h"
     19#include "ps/CQuadtree.h"
     20
     21class GameObj;
     22
     23class GameObj
     24{
     25    int m_X, m_Y;
     26    int m_Foo;
     27    CQuadtree<GameObj> * m_MyNode;
     28
     29public:
     30    GameObj( int x, int y ) : m_X(x), m_Y(y), m_Foo(0), m_MyNode(0)
     31    {
     32    };
     33
     34    void IncrementFoo()
     35    {
     36        m_Foo++;
     37    };
     38
     39    void shout() { std::cout << std::hex << this << std::dec << " " << m_X << "," << m_Y << "\n"; };
     40
     41    int Foo()
     42    {
     43        return m_Foo;
     44    }
     45
     46    CQuadtree<GameObj> * GetNode()
     47    {
     48        return m_MyNode;
     49    }
     50
     51    friend class CQuadtree<GameObj>;
     52};
     53
     54class TestCQuadtree : public CxxTest::TestSuite
     55{
     56public:
     57    void test_constructor()
     58    {
     59        CQuadtree<GameObj> t( 4 );
     60        TS_ASSERT( t.Parent() == 0 );
     61    };
     62
     63    void test_add()
     64    {
     65        CQuadtree<GameObj> t( 4 );
     66        GameObj o1( 0,1 );
     67        GameObj o2( 2,3 );
     68        t.addObject( &o1 );
     69        t.addObject( &o2 );
     70        TS_ASSERT( o1.GetNode() == &t );
     71        TS_ASSERT( o2.GetNode() == &t );
     72    };
     73
     74    void test_remove()
     75    {
     76        CQuadtree<GameObj> t( 4 );
     77        GameObj o1( 0,1 );
     78        GameObj o2( 2,3 );
     79        t.addObject( &o1 );
     80        t.addObject( &o2 );
     81        t.removeObject( &o2 );
     82        TS_ASSERT( o1.GetNode() == &t );
     83        TS_ASSERT( o2.GetNode() == 0 );
     84    };
     85
     86    void test_traverse()
     87    {
     88        CQuadtree<GameObj> t( 4 );
     89        GameObj o1( 0,1 );
     90        t.addObject( &o1 );
     91        t.traverse( &GameObj::IncrementFoo );
     92        TS_ASSERT( o1.Foo() == 1 );
     93    };
     94
     95    void test_split()
     96    {
     97        CQuadtree<GameObj> t( 8 );
     98        int n = 0;
     99        GameObj * first = 0;
     100        GameObj * last = 0;
     101
     102        GameObj * o = new GameObj( 0, 0 );
     103        t.addObject( o );
     104        n++;
     105        first = o;
     106
     107        o = new GameObj( 1, 0 );
     108        t.addObject( o );
     109        n++;
     110
     111        // We don't know what MAX_OBJECTS is.
     112        if ( n < MAX_OBJECTS + 1 )
     113        {
     114            o = new GameObj( 0, 1 );
     115            t.addObject( o );
     116            n++;
     117        }
     118
     119        if ( n < MAX_OBJECTS + 1 )
     120        {
     121            o = new GameObj( 1, 1 );
     122            t.addObject( o );
     123            n++;
     124        }
     125
     126        if ( n < MAX_OBJECTS + 1 )
     127        {
     128            o = new GameObj( 0, 2 );
     129            t.addObject( o );
     130            n++;
     131        }
     132
     133        if ( n < MAX_OBJECTS + 1 )
     134        {
     135            o = new GameObj( 1, 2 );
     136            t.addObject( o );
     137            n++;
     138        }
     139
     140        if ( n < MAX_OBJECTS + 1 )
     141        {
     142            o = new GameObj( 2, 2 );
     143            t.addObject( o );
     144            n++;
     145        }
     146
     147        last = o;
     148
     149        TS_ASSERT( first );
     150        TS_ASSERT( last );
     151        TS_ASSERT( first->GetNode() != last->GetNode() );
     152    };
     153
     154//  void test_merge()
     155//  {
     156//      // This test assumes MAX_OBJECTS = 2.
     157//      CQuadtree<GameObj> t( 8 );
     158//
     159//      GameObj * o1 = new GameObj( 0, 0 );
     160//      t.addObject( o1 );
     161//
     162//      GameObj * o2 = new GameObj( 0, 1 );
     163//      t.addObject( o2 );
     164//
     165//      GameObj * o3 = new GameObj( 1, 1 );
     166//      t.addObject( o3 );
     167//      // splits, twice
     168//
     169//      o3->GetNode()->removeObject( o3 );
     170//
     171//      t.traverse( &CQuadtree<GameObj>::Merge );
     172//      //t.traverse( &CQuadtree<GameObj>::Debug );
     173//      TS_ASSERT( o1->GetNode() == o2->GetNode() );
     174//  };
     175
     176    void test_limit()
     177    {
     178        CQuadtree<GameObj> t( 4 );
     179
     180        // Fill up the root node.
     181        int i;
     182        for ( i = 0; i < MAX_OBJECTS; ++i )
     183        {
     184            GameObj * o = new GameObj( 0, 0 );
     185            t.addObject( o );
     186        }
     187        // Make it split.
     188        GameObj * o2 = new GameObj( 1, 0 );
     189        t.addObject( o2 );
     190        // Now there's a node for 0,0 that already has MAX_OBJECTS objects.
     191        // Never mind how crazy that is, for now.
     192
     193        // This one should fail because we can't split a node of size 1.
     194        GameObj * o3 = new GameObj( 0, 0 );
     195        CQuadtree<GameObj> * tmp = t.addObject( o3 );
     196
     197        TS_ASSERT( !tmp );
     198    };
     199
     200//  void test_speed()
     201//  {
     202//      int n = 128;
     203//      CQuadtree<GameObj> t(n);
     204//      int i, j;
     205//      int count;
     206//      time_t then = time( NULL );
     207//      for ( count = 0; count < 10000; ++count )
     208//      {
     209//          for ( i = 0; i < n; ++i )
     210//          {
     211//              for ( j = 0; j < n; ++j )
     212//              {
     213//                  GameObj * o = new GameObj( i, j );
     214//                  t.addObject( o );
     215//              }
     216//          }
     217//      }
     218//      time_t now = time( NULL );
     219//      printf( "\nelapsed time %ld sec\n", now - then );
     220//  };
     221};
  • tests/test_COctree.h

     
     1/* Copyright (C) 2009 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "lib/self_test.h"
     19#include "ps/COctree.h"
     20
     21class GameObj2;
     22
     23class GameObj2
     24{
     25    int m_X, m_Y, m_Z;
     26    int m_Foo;
     27    COctree<GameObj2> * m_MyNode;
     28
     29public:
     30    GameObj2( int x, int y, int z ) : m_X(x), m_Y(y), m_Z(z), m_Foo(0), m_MyNode(0)
     31    {
     32    };
     33
     34    void IncrementFoo()
     35    {
     36        m_Foo++;
     37    };
     38
     39    void shout() { std::cout << std::hex << this << std::dec << " " << m_X << "," << m_Y << "," << m_Z << "\n"; };
     40
     41    int Foo()
     42    {
     43        return m_Foo;
     44    };
     45
     46    COctree<GameObj2> * GetNode()
     47    {
     48        return m_MyNode;
     49    };
     50
     51    friend class COctree<GameObj2>;
     52};
     53
     54class TestCOctree : public CxxTest::TestSuite
     55{
     56public:
     57    void test_constructor()
     58    {
     59        COctree<GameObj2> t( 4 );
     60        TS_ASSERT( t.Parent() == 0 );
     61    };
     62
     63    void test_add()
     64    {
     65        COctree<GameObj2> t( 4 );
     66        GameObj2 o1( 0,1,0 );
     67        GameObj2 o2( 2,3,0 );
     68        t.addObject( &o1 );
     69        t.addObject( &o2 );
     70        TS_ASSERT( o1.GetNode() == &t );
     71        TS_ASSERT( o2.GetNode() == &t );
     72    };
     73
     74    void test_remove()
     75    {
     76        COctree<GameObj2> t( 4 );
     77        GameObj2 o1( 0,1,0 );
     78        GameObj2 o2( 2,3,0 );
     79        t.addObject( &o1 );
     80        t.addObject( &o2 );
     81        t.removeObject( &o2 );
     82        TS_ASSERT( o1.GetNode() == &t );
     83        TS_ASSERT( o2.GetNode() == 0 );
     84    };
     85
     86    void test_traverse()
     87    {
     88        COctree<GameObj2> t( 4 );
     89        GameObj2 o1( 0,1,0 );
     90        t.addObject( &o1 );
     91        t.traverse( &GameObj2::IncrementFoo );
     92        TS_ASSERT( o1.Foo() == 1 );
     93    };
     94
     95    void test_split()
     96    {
     97        COctree<GameObj2> t( 8 );
     98        int n = 0;
     99        GameObj2 * first = 0;
     100        GameObj2 * last = 0;
     101
     102        GameObj2 * o = new GameObj2( 0, 0, 0 );
     103        t.addObject( o );
     104        n++;
     105        first = o;
     106
     107        o = new GameObj2( 1, 0, 0 );
     108        t.addObject( o );
     109        n++;
     110
     111        // We don't know what MAX_OBJECTS is.
     112        if ( n < MAX_OBJECTS + 1 )
     113        {
     114            o = new GameObj2( 0, 1, 0 );
     115            t.addObject( o );
     116            n++;
     117        }
     118
     119        if ( n < MAX_OBJECTS + 1 )
     120        {
     121            o = new GameObj2( 1, 1, 0 );
     122            t.addObject( o );
     123            n++;
     124        }
     125
     126        if ( n < MAX_OBJECTS + 1 )
     127        {
     128            o = new GameObj2( 0, 2, 0 );
     129            t.addObject( o );
     130            n++;
     131        }
     132
     133        if ( n < MAX_OBJECTS + 1 )
     134        {
     135            o = new GameObj2( 1, 2, 0 );
     136            t.addObject( o );
     137            n++;
     138        }
     139
     140        if ( n < MAX_OBJECTS + 1 )
     141        {
     142            o = new GameObj2( 2, 2, 0 );
     143            t.addObject( o );
     144            n++;
     145        }
     146
     147        last = o;
     148
     149        TS_ASSERT( first );
     150        TS_ASSERT( last );
     151        TS_ASSERT( first->GetNode() != last->GetNode() );
     152    };
     153
     154//  void test_merge()
     155//  {
     156//      // This test assumes MAX_OBJECTS = 2.
     157//      COctree<GameObj2> t( 8 );
     158//
     159//      GameObj2 * o1 = new GameObj2( 0, 0, 0 );
     160//      t.addObject( o1 );
     161//
     162//      GameObj2 * o2 = new GameObj2( 0, 1, 0 );
     163//      t.addObject( o2 );
     164//
     165//      GameObj2 * o3 = new GameObj2( 1, 1, 0 );
     166//      t.addObject( o3 );
     167//      // splits, twice
     168//
     169//      o3->GetNode()->removeObject( o3 );
     170//
     171//      t.traverse( &COctree<GameObj2>::Merge );
     172//      //t.traverse( &COctree<GameObj2>::Debug );
     173//      TS_ASSERT( o1->GetNode() == o2->GetNode() );
     174//  };
     175
     176    void test_limit()
     177    {
     178        COctree<GameObj2> t( 4 );
     179
     180        // Fill up the root node.
     181        int i;
     182        for ( i = 0; i < MAX_OBJECTS; ++i )
     183        {
     184            GameObj2 * o = new GameObj2( 0, 0, 0 );
     185            t.addObject( o );
     186        }
     187        // Make it split.
     188        GameObj2 * o2 = new GameObj2( 1, 0, 0 );
     189        t.addObject( o2 );
     190        // Now there's a node for 0,0 that already has MAX_OBJECTS objects.
     191        // Never mind how crazy that is, for now.
     192
     193        // This one should fail because we can't split a node of size 1.
     194        GameObj2 * o3 = new GameObj2( 0, 0, 0 );
     195        COctree<GameObj2> * tmp = t.addObject( o3 );
     196
     197        TS_ASSERT( !tmp );
     198    };
     199
     200//  void test_speed()
     201//  {
     202//      int n = 128;
     203//      COctree<GameObj2> t(n);
     204//      int i, j, k;
     205//      int count;
     206//      time_t then = time( NULL );
     207//      for ( count = 0; count < 10000; ++count )
     208//      {
     209//          for ( i = 0; i < n; ++i )
     210//          {
     211//              for ( j = 0; j < n; ++j )
     212//              {
     213//                  for ( k = 0; k < n; ++k )
     214//                  {
     215//                      GameObj2 * o = new GameObj2( i, j, k );
     216//                      t.addObject( o );
     217//                  }
     218//              }
     219//          }
     220//      }
     221//      time_t now = time( NULL );
     222//      printf( "\nelapsed time %ld sec\n", now - then );
     223//  };
     224};