Ticket #2430: newSubdivision.patch

File newSubdivision.patch, 20.9 KB (added by wraitii, 10 years ago)
  • source/simulation2/helpers/Spatial_2.h

     
     1/* Copyright (C) 2010 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_SPATIAL2
     19#define INCLUDED_SPATIAL2
     20
     21#include "simulation2/system/Component.h"
     22#include "simulation2/serialization/SerializeTemplates.h"
     23#include "ps/CLogger.h"
     24
     25/**
     26 * A basic subdivision scheme for finding entities in range
     27 * Items are stored in vectors in fixed-size divisions.
     28 * Items have a size (min/max values of their axis-aligned bounding box)
     29 * If that size is higher than a subdivision's size, they're stored in the "general" vector
     30 * This means that if too many objects have a size that's big, it'll end up being slow
     31 * We want subdivisions to be as small as possible yet contain as many items as possible.
     32 * I have currently set it to "30", which is bigger than most buildings and entities.
     33 *
     34 * When adding, deleting or moving, you need to supply the position
     35 * the item was added at, otherwise it might check the wrong subdivision and fail.
     36 *
     37 * If a unit size were to change, it would need to be updated, but that doesn't happen for now.
     38 *
     39 * Using a template because why not.
     40 */
     41template<typename T>
     42class SpatialTree
     43{
     44    std::vector<T> m_OverSizedData;
     45    // fixed size array of subdivisions.
     46    std::vector<T>* m_SpatialDivisionsData;
     47    // number of columns in the above array
     48    size_t m_ArrayWidth;
     49    // size of subdivisions, used to deduce m_ArrayWidth
     50    int m_SubdivisionSize;
     51   
     52public:
     53    SpatialTree() : m_SubdivisionSize(30)
     54    {
     55    }
     56    ~SpatialTree()
     57    {
     58    }
     59    // copy constructor
     60    SpatialTree(const SpatialTree& other) : m_SubdivisionSize(30)
     61    {
     62        Create(other.m_ArrayWidth);
     63        std::copy(other.m_SpatialDivisionsData, other.m_SpatialDivisionsData+m_ArrayWidth*m_ArrayWidth, m_SpatialDivisionsData);
     64    }
     65    SpatialTree& operator=(const SpatialTree& other)
     66    {
     67        if (this != &other)
     68        {
     69            Reset(other.m_ArrayWidth);
     70            std::copy(other.m_SpatialDivisionsData, other.m_SpatialDivisionsData+m_ArrayWidth*m_ArrayWidth, m_SpatialDivisionsData);
     71        }
     72        return *this;
     73    }
     74   
     75    // Create a spatial tree of the map.
     76    void Create(size_t arrayWidth)
     77    {
     78        if (m_SpatialDivisionsData) delete[] m_SpatialDivisionsData;
     79        // figure the number of divisions we want
     80        m_ArrayWidth = arrayWidth;
     81        m_SpatialDivisionsData = new std::vector<T>[m_ArrayWidth*m_ArrayWidth];
     82        m_OverSizedData.clear();
     83    }
     84   
     85    void Reset(size_t x, size_t y)
     86    {
     87        if (y > x)
     88            Create(y/m_SubdivisionSize);
     89        else
     90            Create(x/m_SubdivisionSize);
     91    }
     92   
     93    /**
     94     * Equivalence test (ignoring order of items within each subdivision)
     95     */
     96    bool operator==(const SpatialTree& other)
     97    {
     98        if (m_ArrayWidth != other.m_ArrayWidth)
     99            return false;
     100        if (m_OverSizedData != other.m_OverSizedData)
     101            return false;
     102        for (size_t idx = 0; idx < m_ArrayWidth*m_ArrayWidth; ++idx)
     103            if (m_SpatialDivisionsData[idx] != other.m_SpatialDivisionsData[idx])
     104                return false;
     105        return true;
     106    }
     107   
     108    inline bool operator!=(const SpatialTree& rhs)
     109    {
     110        return !(*this == rhs);
     111    }
     112   
     113    /**
     114     * Add an item.
     115     * The item must not already be present.
     116     */
     117    void Add(const T item, CFixedVector2D position, u16 size)
     118    {
     119        if (size > m_SubdivisionSize)
     120        {
     121            if (std::find(m_OverSizedData.begin(),m_OverSizedData.end(), item) == m_OverSizedData.end())
     122                m_OverSizedData.push_back(item);
     123        } else {
     124            // find the proper subdivision.
     125            int X = (position.X / m_SubdivisionSize).ToInt_RoundToZero();
     126            int Y = (position.Y / m_SubdivisionSize).ToInt_RoundToZero();
     127            std::vector<T>& subdivision = m_SpatialDivisionsData[X+Y*m_ArrayWidth];
     128            if (std::find(subdivision.begin(),subdivision.end(), item) == subdivision.end())
     129            {
     130                // add it since it's unique.
     131                subdivision.push_back(item);
     132            }
     133        }
     134    }
     135   
     136    /**
     137     * Remove an item.
     138     * Position must be where we expect to find it, or we won't find it.
     139     */
     140    void Remove(const T item, CFixedVector2D position, u16 size)
     141    {
     142        if (size > m_SubdivisionSize)
     143        {
     144            typename std::vector<T>::iterator itr = std::find(m_OverSizedData.begin(),m_OverSizedData.end(), item);
     145            if (itr != m_OverSizedData.end())
     146                m_OverSizedData.erase(itr);
     147        } else {
     148            // find the proper subdivision.
     149            size_t X = (position.X / m_SubdivisionSize).ToInt_RoundToZero();
     150            size_t Y = (position.Y / m_SubdivisionSize).ToInt_RoundToZero();
     151            std::vector<T>& subdivision = m_SpatialDivisionsData[X+Y*m_ArrayWidth];
     152            typename std::vector<T>::iterator itr = std::find(subdivision.begin(),subdivision.end(), item);
     153            if (itr != subdivision.end())
     154            {
     155                subdivision.erase(itr);
     156            }
     157        }
     158    }
     159   
     160    /**
     161     * Equivalent to Remove() then Add(), but slightly faster.
     162     * In particular for big objects nothing needs to be done.
     163     */
     164    void Move(const T item, CFixedVector2D oldPosition, CFixedVector2D newPosition, u16 size)
     165    {
     166        if (size <= m_SubdivisionSize)
     167        {
     168            // find the proper subdivision.
     169            size_t oldX = (oldPosition.X / m_SubdivisionSize).ToInt_RoundToZero();
     170            size_t oldY = (oldPosition.Y / m_SubdivisionSize).ToInt_RoundToZero();
     171            size_t newX = (newPosition.X / m_SubdivisionSize).ToInt_RoundToZero();
     172            size_t newY = (newPosition.Y / m_SubdivisionSize).ToInt_RoundToZero();
     173            std::vector<T>& subdivision = m_SpatialDivisionsData[oldX+oldY*m_ArrayWidth];
     174            typename std::vector<T>::iterator itr = std::find(subdivision.begin(),subdivision.end(), item);
     175            if (itr != subdivision.end())
     176            {
     177                subdivision.erase(itr);
     178                // since we've found it, we'll assume the query is OK and add it directly.
     179                m_SpatialDivisionsData[newX+newY*m_ArrayWidth].push_back(item);
     180            }
     181        }
     182    }
     183   
     184    /**
     185     * Returns a list of item that are either in the square
     186     * or really quite close to it.
     187     * The data is garanteed to be unique but is unsorted.
     188     * It's the responsibility of the querier to do proper distance checking.
     189     */
     190    void GetInRange(std::vector<T>& out, CFixedVector2D posMin, CFixedVector2D posMax)
     191    {
     192        size_t minX = (posMin.X / m_SubdivisionSize).ToInt_RoundToZero();
     193        size_t maxX = (posMax.X / m_SubdivisionSize).ToInt_RoundToZero();
     194        size_t minY = (posMin.Y / m_SubdivisionSize).ToInt_RoundToZero();
     195        size_t maxY = (posMax.Y / m_SubdivisionSize).ToInt_RoundToZero();
     196        // Now expand the subdivisions by One so we make sure that we've got all elements potentially in range.
     197        // also abuse ternary operators because I like to pretend I'm a good C++ developer.
     198        minX > 0? --minX : 0;
     199        minX < m_ArrayWidth? ++maxX : 0;
     200        minY > 0? --minY : 0;
     201        minY < m_ArrayWidth? ++maxY : 0;
     202        // assume out is clean
     203        for (size_t Y = minY; Y < maxY; ++Y)
     204            for (size_t X = minX; X < maxX; ++X)
     205            {
     206                std::vector<T>& subdivision = m_SpatialDivisionsData[X+Y*m_ArrayWidth];
     207                if (subdivision.size() != 0)
     208                    out.insert(out.end(), subdivision.begin(), subdivision.end());
     209            }
     210    }
     211
     212    /**
     213     * Returns a sorted list of unique items that includes all items
     214     * within the given circular distance of the given point and a few more.
     215     */
     216    void GetNear(std::vector<T>& out, CFixedVector2D pos, entity_pos_t range)
     217    {
     218        // TODO: be cleverer and return a circular pattern of divisions,
     219        // not this square over-approximation
     220        CFixedVector2D r(range, range);
     221        GetInRange(out, pos - r, pos + r);
     222    }
     223   
     224    int GetDivisionSize() { return m_SubdivisionSize; };
     225    int GetWidth() { return m_ArrayWidth; };
     226};
     227
     228#endif // INCLUDED_SPATIAL2
  • source/simulation2/components/CCmpRangeManager.cpp

    Property changes on: source/simulation2/helpers/Spatial_2.h
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    2828#include "simulation2/components/ICmpVision.h"
    2929#include "simulation2/components/ICmpWaterManager.h"
    3030#include "simulation2/helpers/Render.h"
    31 #include "simulation2/helpers/Spatial.h"
    3231
    3332#include "graphics/Overlay.h"
    3433#include "graphics/Terrain.h"
     
    131130static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutlines;
    132131
    133132/**
    134  * Representation of an entity, with the data needed for queries.
    135  */
    136 struct EntityData
    137 {
    138     EntityData() : retainInFog(0), owner(-1), inWorld(0), flags(1) { }
    139     entity_pos_t x, z;
    140     entity_pos_t visionRange;
    141     u8 retainInFog; // boolean
    142     i8 owner;
    143     u8 inWorld; // boolean
    144     u8 flags; // See GetEntityFlagMask
    145 };
    146 
    147 cassert(sizeof(EntityData) == 16);
    148 
    149 /**
    150133 * Serialization helper template for Query
    151134 */
    152135struct SerializeQuery
     
    193176    template<typename S>
    194177    void operator()(S& serialize, const char* UNUSED(name), EntityData& value)
    195178    {
     179        serialize.NumberU32_Unbounded("id", value.id);
    196180        serialize.NumberFixed_Unbounded("x", value.x);
    197181        serialize.NumberFixed_Unbounded("z", value.z);
    198182        serialize.NumberFixed_Unbounded("vision", value.visionRange);
     183        serialize.NumberU16_Unbounded("size", value.size);
    199184        serialize.NumberU8("retain in fog", value.retainInFog, 0, 1);
    200185        serialize.NumberI8_Unbounded("owner", value.owner);
    201186        serialize.NumberU8("in world", value.inWorld, 0, 1);
     
    274259    std::map<tag_t, Query> m_Queries;
    275260    EntityMap<EntityData> m_EntityData;
    276261
    277     SpatialSubdivision m_Subdivision; // spatial index of m_EntityData
     262    SpatialTree<const EntityData*> m_Subdivision; // spatial index of m_EntityData
     263    std::vector<const EntityData*> queryResultVector;
    278264
    279265    // LOS state:
    280266    static const player_id_t MAX_LOS_PLAYER_ID = 16;
     
    328314        m_LosRevealAll.resize(MAX_LOS_PLAYER_ID+2,false);
    329315        m_SharedLosMasks.resize(MAX_LOS_PLAYER_ID+2,0);
    330316
     317        queryResultVector.reserve(12000);
     318       
    331319        m_LosCircular = false;
    332320        m_TerrainVerticesPerSide = 0;
    333321
     
    395383            if (!cmpPosition)
    396384                break;
    397385
    398             // The newly-created entity will have owner -1 and position out-of-world
    399             // (any initialisation of those values will happen later), so we can just
    400             // use the default-constructed EntityData here
    401             EntityData entdata;
    402 
     386            // initialization of position-related values happens later.
     387            EntityData entdata(ent);
     388           
    403389            // Store the LOS data, if any
    404390            CmpPtr<ICmpVision> cmpVision(GetSimContext(), ent);
    405391            if (cmpVision)
     
    407393                entdata.visionRange = cmpVision->GetRange();
    408394                entdata.retainInFog = (cmpVision->GetRetainInFog() ? 1 : 0);
    409395            }
     396           
     397            // Store the size data, if any
     398            CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), ent);
     399            if (cmpObstruction)
     400            {
     401                ICmpObstructionManager::ObstructionSquare out;
     402                cmpObstruction->GetObstructionSquare(out);
     403                size_t size = (out.hh.Square() + out.hw.Square()).Sqrt().ToInt_RoundToInfinity();
     404                // avoid overflow, it doesn't really matter as long as this is above subdivision size.
     405                if (size > 65530)
     406                    size = 65530;
     407                entdata.size = (u16)size;
     408            }
    410409
    411410            // Remember this entity
    412411            m_EntityData.insert(ent, entdata);
     
    429428                {
    430429                    CFixedVector2D from(it->second.x, it->second.z);
    431430                    CFixedVector2D to(msgData.x, msgData.z);
    432                     m_Subdivision.Move(ent, from, to);
     431                    m_Subdivision.Move(&it->second, from, to, it->second.size);
    433432                    LosMove(it->second.owner, it->second.visionRange, from, to);
    434433                }
    435434                else
    436435                {
    437436                    CFixedVector2D to(msgData.x, msgData.z);
    438                     m_Subdivision.Add(ent, to);
     437                    m_Subdivision.Add(&it->second, to, it->second.size);
    439438                    LosAdd(it->second.owner, it->second.visionRange, to);
    440439                }
    441440
     
    448447                if (it->second.inWorld)
    449448                {
    450449                    CFixedVector2D from(it->second.x, it->second.z);
    451                     m_Subdivision.Remove(ent, from);
     450                    m_Subdivision.Remove(&it->second, from, it->second.size);
    452451                    LosRemove(it->second.owner, it->second.visionRange, from);
    453452                }
    454453
     
    494493                break;
    495494
    496495            if (it->second.inWorld)
    497                 m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z));
     496                m_Subdivision.Remove(&it->second, CFixedVector2D(it->second.x, it->second.z), it->second.size);
    498497
    499498            // This will be called after Ownership's OnDestroy, so ownership will be set
    500499            // to -1 already and we don't have to do a LosRemove here
     
    573572
    574573        std::vector<std::vector<u16> > oldPlayerCounts = m_LosPlayerCounts;
    575574        std::vector<u32> oldStateRevealed = m_LosStateRevealed;
    576         SpatialSubdivision oldSubdivision = m_Subdivision;
     575        SpatialTree<const EntityData*> oldSubdivision = m_Subdivision;
    577576
    578577        ResetDerivedData(true);
    579578
     
    601600            debug_warn(L"inconsistent subdivs");
    602601    }
    603602
    604     SpatialSubdivision* GetSubdivision()
     603    SpatialTree<const EntityData*>* GetSubdivision()
    605604    {
    606         return & m_Subdivision;
     605        return &m_Subdivision;
    607606    }
    608607
    609608    // Reinitialise subdivisions and LOS data, based on entity data
     
    661660
    662661    void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
    663662    {
    664         // Use 8x8 tile subdivisions
    665         // (TODO: find the optimal number instead of blindly guessing)
    666         m_Subdivision.Reset(x1, z1, entity_pos_t::FromInt(8*TERRAIN_TILE_SIZE));
     663        m_Subdivision.Reset(x1.ToInt_RoundToInfinity(), z1.ToInt_RoundToInfinity());
    667664
    668665        for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
    669666        {
    670667            if (it->second.inWorld)
    671                 m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z));
     668                m_Subdivision.Add(&it->second, CFixedVector2D(it->second.x, it->second.z), it->second.size);
    672669        }
    673670    }
    674671
     
    939936            CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+
    940937                CFixedVector3D(entity_pos_t::Zero(), q.elevationBonus, entity_pos_t::Zero()) ;
    941938            // Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange
    942             SpatialQueryArray ents;
    943             m_Subdivision.GetNear(ents, pos, q.maxRange*2);
     939            queryResultVector.clear();
     940            m_Subdivision.GetNear(queryResultVector, pos, q.maxRange*2);
    944941
    945             for (int i = 0; i < ents.size(); ++i)
     942            for (size_t i = 0; i < queryResultVector.size(); ++i)
    946943            {
    947                 EntityMap<EntityData>::const_iterator it = m_EntityData.find(ents[i]);
    948                 ENSURE(it != m_EntityData.end());
     944                //EntityMap<EntityData>::const_iterator it = m_EntityData.find(ents[i]);
     945                //ENSURE(it != m_EntityData.end());
    949946
    950                 if (!TestEntityQuery(q, it->first, it->second))
     947                if (!TestEntityQuery(q, queryResultVector[i]->id, *queryResultVector[i]))
    951948                    continue;
    952949
    953                 CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), ents[i]);
     950                CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), queryResultVector[i]->id);
    954951                if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
    955952                    continue;
    956953                CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
    957954
    958955                // Restrict based on precise distance
    959956                if (!InParabolicRange(
    960                         CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
     957                        CFixedVector3D(queryResultVector[i]->x, secondPosition.Y, queryResultVector[i]->z)
    961958                            - pos3d,
    962959                        q.maxRange))
    963960                    continue;
    964961
    965962                if (!q.minRange.IsZero())
    966963                {
    967                     int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
     964                    int distVsMin = (CFixedVector2D(queryResultVector[i]->x, queryResultVector[i]->z) - pos).CompareLength(q.minRange);
    968965                    if (distVsMin < 0)
    969966                        continue;
    970967                }
    971968
    972                 r.push_back(it->first);
     969                r.push_back(queryResultVector[i]->id);
    973970            }
    974971        }
    975972        // check a regular range (i.e. not the entire world, and not parabolic)
    976973        else
    977974        {
    978975            // Get a quick list of entities that are potentially in range
    979             SpatialQueryArray ents;
    980             m_Subdivision.GetNear(ents, pos, q.maxRange);
     976            queryResultVector.clear();
     977            m_Subdivision.GetNear(queryResultVector, pos, q.maxRange);
    981978
    982             for (int i = 0; i < ents.size(); ++i)
     979            for (size_t i = 0; i < queryResultVector.size(); ++i)
    983980            {
    984                 EntityMap<EntityData>::const_iterator it = m_EntityData.find(ents[i]);
    985                 ENSURE(it != m_EntityData.end());
     981                //EntityMap<EntityData>::const_iterator it = m_EntityData.find(ents[i]);
     982                //ENSURE(it != m_EntityData.end());
    986983
    987                 if (!TestEntityQuery(q, it->first, it->second))
     984                if (!TestEntityQuery(q, queryResultVector[i]->id, *queryResultVector[i]))
    988985                    continue;
    989986
    990987                // Restrict based on precise distance
    991                 int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange);
     988                int distVsMax = (CFixedVector2D(queryResultVector[i]->x, queryResultVector[i]->z) - pos).CompareLength(q.maxRange);
    992989                if (distVsMax > 0)
    993990                    continue;
    994991
    995992                if (!q.minRange.IsZero())
    996993                {
    997                     int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
     994                    int distVsMin = (CFixedVector2D(queryResultVector[i]->x, queryResultVector[i]->z) - pos).CompareLength(q.minRange);
    998995                    if (distVsMin < 0)
    999996                        continue;
    1000997                }
    1001998
    1002                 r.push_back(it->first);
     999                r.push_back(queryResultVector[i]->id);
    10031000            }
    10041001        }
    10051002    }
     
    12641261            }
    12651262
    12661263            // render subdivision grid
    1267             float divSize = m_Subdivision.GetDivisionSize().ToFloat();
     1264            int divSize = m_Subdivision.GetDivisionSize();
    12681265            int width = m_Subdivision.GetWidth();
    1269             int height = m_Subdivision.GetHeight();
    12701266            for (int x = 0; x < width; ++x)
    12711267            {
    1272                 for (int y = 0; y < height; ++y)
     1268                for (int y = 0; y < width; ++y)
    12731269                {
    12741270                    m_DebugOverlayLines.push_back(SOverlayLine());
    12751271                    m_DebugOverlayLines.back().m_Color = subdivColour;
  • source/simulation2/components/ICmpRangeManager.h

     
    2424#include "simulation2/system/Interface.h"
    2525#include "simulation2/helpers/Position.h"
    2626#include "simulation2/helpers/Player.h"
    27 #include "simulation2/helpers/Spatial.h"
     27#include "simulation2/helpers/Spatial_2.h"
    2828
    2929#include "graphics/Terrain.h" // for TERRAIN_TILE_SIZE
    3030
     
    6565 * sent to the entity once per turn if anybody has entered or left the range since the last RangeUpdate.
    6666 * Queries can be disabled, in which case no message will be sent.
    6767 */
     68
     69/**
     70 * Representation of an entity, with the data needed for queries.
     71 */
     72struct EntityData
     73{
     74    EntityData() : retainInFog(0), owner(-1), inWorld(0), flags(1) { }
     75    EntityData(entity_id_t ID) : retainInFog(0), owner(-1), inWorld(0), flags(1), id(ID) { }
     76    entity_id_t id;
     77    entity_pos_t x, z;
     78    entity_pos_t visionRange;
     79    u16 size;
     80    u8 retainInFog; // boolean
     81    i8 owner;
     82    u8 inWorld; // boolean
     83    u8 flags; // See GetEntityFlagMask
     84};
     85
     86cassert(sizeof(EntityData) == 24);
     87
    6888class ICmpRangeManager : public IComponent
    6989{
    7090public:
     
    7797     * Access the spatial subdivision kept by the range manager.
    7898     * @return pointer to spatial subdivision structure.
    7999     */
    80     virtual SpatialSubdivision* GetSubdivision() = 0;
     100    virtual SpatialTree<const EntityData*>* GetSubdivision() = 0;
    81101
    82102    /**
    83103     * Set the bounds of the world.
  • source/simulation2/helpers/Selection.cpp

     
    2727#include "simulation2/components/ICmpTemplateManager.h"
    2828#include "simulation2/components/ICmpSelectable.h"
    2929#include "simulation2/components/ICmpVisual.h"
    30 #include "simulation2/helpers/Spatial.h"
    3130#include "ps/CLogger.h"
    3231#include "ps/Profiler2.h"
    3332
     
    4948    CFixedVector2D pos(fixed::FromFloat(pos3d.X), fixed::FromFloat(pos3d.Z));
    5049
    5150    // Get a rough group of entities using our approximated origin.
    52     SpatialQueryArray ents;
     51    std::vector<const EntityData*> ents;
    5352    cmpRangeManager->GetSubdivision()->GetNear(ents, pos, entity_pos_t::FromInt(range));
    5453
    5554    // Filter for relevent entities and calculate precise distances.
    5655    std::vector<std::pair<float, entity_id_t> > hits; // (dist^2, entity) pairs
    57     for (int i = 0; i < ents.size(); ++i)
     56    for (size_t i = 0; i < ents.size(); ++i)
    5857    {
    59         CmpPtr<ICmpSelectable> cmpSelectable(simulation, ents[i]);
     58        CmpPtr<ICmpSelectable> cmpSelectable(simulation, ents[i]->id);
    6059        if (!cmpSelectable)
    6160            continue;
    6261
     
    107106        CVector3D closest = origin + dir * (center - origin).Dot(dir);
    108107        dist2 = (closest - center).LengthSquared();
    109108
    110         hits.push_back(std::make_pair(dist2, ents[i]));
     109        hits.push_back(std::make_pair(dist2, ents[i]->id));
    111110    }
    112111
    113112    // Sort hits by distance