Ticket #930: pathfinder-incremental-grid-update-v2.patch

File pathfinder-incremental-grid-update-v2.patch, 29.4 KB (added by Sylvain Gadrat, 13 years ago)

Improve performances, diff from the revision r10023

  • simulation2/helpers/Grid.h

     
    1919#define INCLUDED_GRID
    2020
    2121#include <cstring>
     22#include <vector>
    2223
    2324#ifdef NDEBUG
    2425#define GRID_BOUNDS_DEBUG 0
     
    2728#endif
    2829
    2930/**
     31 * Can be used to observe modifications of a Grid
     32 */
     33class GridObserver {
     34public:
     35    typedef std::vector< std::pair<u16, u16> >::const_iterator const_iterator;
     36
     37    // Methods modifying the observer itself
     38    GridObserver() : m_Reseted(false) {
     39        m_Modified.reserve(3000); //FIXME 3000 ? Needs some estimation.
     40    }
     41    void clear() {
     42        m_Modified.clear();
     43        m_Reseted = false;
     44    }
     45
     46    // Methods indicating changes on the observed grid
     47    void modifiy(u16 i, u16 j) {
     48        //TODO Need check to avoid doubles
     49        m_Modified.push_back(std::make_pair(i, j));
     50    }
     51    void reset() {
     52        m_Reseted = true;
     53    }
     54
     55    // Methods to read observed modifications
     56    bool reseted() const {
     57        return m_Reseted;
     58    }
     59    const_iterator begin() const {
     60        return m_Modified.begin();
     61    }
     62    const_iterator end() const {
     63        return m_Modified.end();
     64    }
     65
     66private:
     67    std::vector< std::pair<u16, u16> > m_Modified;
     68    bool m_Reseted;
     69};
     70
     71/**
    3072 * Basic 2D array, intended for storing tile data, plus support for lazy updates
    3173 * by ICmpObstructionManager.
    3274 * @c T must be a POD type that can be initialised with 0s.
     
    3577class Grid
    3678{
    3779public:
    38     Grid() : m_W(0), m_H(0), m_Data(NULL), m_DirtyID(0)
     80    Grid() : m_W(0), m_H(0), m_Data(NULL), m_DirtyID(0), m_Observer(NULL)
    3981    {
    4082    }
    4183
    42     Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL), m_DirtyID(0)
     84    Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL), m_DirtyID(0), m_Observer(NULL)
    4385    {
    4486        if (m_W || m_H)
    4587            m_Data = new T[m_W * m_H];
     
    65107            }
    66108            else
    67109                m_Data = NULL;
     110            m_Observer = NULL; //TODO determine if we have to keep the old observer (and call m_Observer->reset()), share the observer with g or set it to NULL
    68111        }
    69112        return *this;
    70113    }
     
    76119
    77120    void reset()
    78121    {
    79         if (m_Data)
     122        if (m_Data) {
    80123            memset(m_Data, 0, m_W*m_H*sizeof(T));
     124            if (m_Observer)
     125                m_Observer->reset();
     126        }
    81127    }
    82128
    83129    void set(int i, int j, const T& value)
     
    86132        ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H);
    87133#endif
    88134        m_Data[j*m_W + i] = value;
     135        if (m_Observer)
     136            m_Observer->modifiy(i, j);
    89137    }
    90138
    91139    T& get(int i, int j) const
     
    100148    T* m_Data;
    101149
    102150    size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated
     151    GridObserver* m_Observer;
    103152};
    104153
    105154/**
  • simulation2/components/CCmpPathfinder_Common.h

     
    201201    u16 m_MapSize; // tiles per side
    202202    Grid<TerrainTile>* m_Grid; // terrain/passability information
    203203    Grid<u8>* m_ObstructionGrid; // cached obstruction information (TODO: we shouldn't bother storing this, it's redundant with LSBs of m_Grid)
     204    GridObserver m_ObstructionChanges; // changes occured in m_ObstructionGrid when rasterise
    204205    bool m_TerrainDirty; // indicates if m_Grid has been updated since terrain changed
    205206   
    206207    // For responsiveness we will process some moves in the same turn they were generated in
  • simulation2/components/CCmpPathfinder.cpp

     
    3535#include "simulation2/components/ICmpWaterManager.h"
    3636#include "simulation2/serialization/SerializeTemplates.h"
    3737
     38#include "lib/timer.h"
     39TIMER_ADD_CLIENT(tc_PathfinderUpdateGrid);
     40
    3841// Default cost to move a single tile is a fairly arbitrary number, which should be big
    3942// enough to be precise when multiplied/divided and small enough to never overflow when
    4043// summing the cost of a whole path.
     
    328331
    329332    CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
    330333
     334    //GridObserver obstructionChanges;
     335    m_ObstructionChanges.clear();
     336    m_ObstructionGrid->m_Observer = &m_ObstructionChanges;
    331337    bool obstructionsDirty = cmpObstructionManager->Rasterise(*m_ObstructionGrid);
     338    m_ObstructionGrid->m_Observer = NULL;
    332339
    333340    if (obstructionsDirty && !m_TerrainDirty)
    334341    {
    335342        PROFILE("UpdateGrid obstructions");
     343        TIMER_ACCRUE(tc_PathfinderUpdateGrid);
    336344
    337345        // Obstructions changed - we need to recompute passability
    338346        // Since terrain hasn't changed we only need to update the obstruction bits
     
    343351        // then TILE_OUTOFBOUNDS will change and we can't use this fast path, but
    344352        // currently it'll just set obstructionsDirty and we won't notice
    345353
    346         for (u16 j = 0; j < m_MapSize; ++j)
    347         {
    348             for (u16 i = 0; i < m_MapSize; ++i)
     354        if (m_ObstructionChanges.reseted()) {
     355            for (u16 j = 0; j < m_MapSize; ++j)
    349356            {
     357                for (u16 i = 0; i < m_MapSize; ++i)
     358                {
     359                    TerrainTile& t = m_Grid->get(i, j);
     360
     361                    u8 obstruct = m_ObstructionGrid->get(i, j);
     362
     363                    if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
     364                        t |= 1;
     365                    else
     366                        t &= ~1;
     367
     368                    if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION)
     369                        t |= 2;
     370                    else
     371                        t &= ~2;
     372                }
     373            }
     374        }else {
     375            GridObserver::const_iterator it  = m_ObstructionChanges.begin();
     376            GridObserver::const_iterator end = m_ObstructionChanges.end();
     377            while (it != end) {
     378                u16 i = it->first;
     379                u16 j = it->second;
     380
     381                //FIXME ugly copy/paste
    350382                TerrainTile& t = m_Grid->get(i, j);
    351383
    352384                u8 obstruct = m_ObstructionGrid->get(i, j);
     
    360392                    t |= 2;
    361393                else
    362394                    t &= (TerrainTile)~2;
     395
     396                ++it;
    363397            }
    364398        }
    365399
     
    368402    else if (obstructionsDirty || m_TerrainDirty)
    369403    {
    370404        PROFILE("UpdateGrid full");
     405        TIMER_ACCRUE(tc_PathfinderUpdateGrid);
    371406
    372407        // Obstructions or terrain changed - we need to recompute passability
    373408        // TODO: only bother recomputing the region that has actually changed
  • simulation2/components/CCmpObstructionManager.cpp

     
    3333#include "ps/Profile.h"
    3434#include "renderer/Scene.h"
    3535
     36#include "lib/timer.h"
     37TIMER_ADD_CLIENT(tc_Rasterize);
     38
    3639// Externally, tags are opaque non-zero positive integers.
    3740// Internally, they are tagged (by shape) indexes into shape lists.
    3841// idx must be non-zero.
     
    6770    ICmpObstructionManager::flags_t flags;
    6871};
    6972
     73template <class S>
     74class ShapeHistory {
     75public:
     76    typedef typename std::map<size_t, S>::const_iterator const_iterator;
     77
     78    ShapeHistory(size_t baseId = 1)
     79    : m_HistoryBase(baseId)
     80    {
     81    }
     82    bool isInHistory(size_t id) const {
     83        return id >= m_HistoryBase;
     84    }
     85    void add(size_t id, S const & e) {
     86        m_Modifications.insert(std::make_pair(id, e));
     87        if (m_Modifications.size() > 10) {
     88            m_HistoryBase = m_Modifications.begin()->first + 1;
     89            m_Modifications.erase(m_Modifications.begin());
     90        }
     91    }
     92    void reset(size_t id) {
     93        m_Modifications.clear();
     94        m_HistoryBase = id;
     95    }
     96    const_iterator end() const {
     97        return m_Modifications.end();
     98    }
     99    const_iterator get(size_t id) const {
     100        return m_Modifications.lower_bound(id);
     101    }
     102
     103private:
     104    std::map<size_t, S> m_Modifications;
     105    size_t m_HistoryBase;
     106};
     107
    70108/**
    71109 * Serialization helper template for UnitShape
    72110 */
     
    149187        m_StaticShapeNext = 1;
    150188
    151189        m_DirtyID = 1; // init to 1 so default-initialised grids are considered dirty
     190        m_StaticHistory.reset(m_DirtyID);
     191        m_UnitHistory.reset(m_DirtyID);
    152192
     193        //m_ExpandPathfinding = entity_pos_t::FromInt(CELL_SIZE / 2);
     194        // Actually that's bad because units get stuck when the A* pathfinder thinks they're
     195        // blocked on all sides, so it's better to underestimate
     196        m_ExpandPathfinding = entity_pos_t::FromInt(0);
     197
     198        // For AI building foundation planning, we want to definitely block all
     199        // potentially-obstructed tiles (so we don't blindly build on top of an obstruction),
     200        // so we need to expand by at least 1/sqrt(2) of a tile
     201        m_ExpandFoundation = (entity_pos_t::FromInt(CELL_SIZE) * 3) / 4;
     202
    153203        m_PassabilityCircular = false;
    154204
    155205        m_WorldX0 = m_WorldZ0 = m_WorldX1 = m_WorldZ1 = entity_pos_t::Zero();
     
    250300        UnitShape shape = { ent, x, z, r, flags, group };
    251301        u32 id = m_UnitShapeNext++;
    252302        m_UnitShapes[id] = shape;
    253         MakeDirtyUnit(flags);
     303        MakeDirtyUnit(shape);
     304        RegisterShapeTiles(shape, id);
    254305
    255306        m_UnitSubdivision.Add(id, CFixedVector2D(x - r, z - r), CFixedVector2D(x + r, z + r));
    256307
     
    267318        StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags };
    268319        u32 id = m_StaticShapeNext++;
    269320        m_StaticShapes[id] = shape;
    270         MakeDirtyStatic(flags);
     321        MakeDirtyStatic(shape);
     322        RegisterShapeTiles(shape, id);
    271323
    272324        CFixedVector2D center(x, z);
    273325        CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(w/2, h/2));
     
    303355        {
    304356            UnitShape& shape = m_UnitShapes[TAG_TO_INDEX(tag)];
    305357
     358            MakeDirtyUnit(shape);
     359            UnregisterShapeTiles(shape, TAG_TO_INDEX(tag));
     360
    306361            m_UnitSubdivision.Move(TAG_TO_INDEX(tag),
    307362                CFixedVector2D(shape.x - shape.r, shape.z - shape.r),
    308363                CFixedVector2D(shape.x + shape.r, shape.z + shape.r),
     
    312367            shape.x = x;
    313368            shape.z = z;
    314369
    315             MakeDirtyUnit(shape.flags);
     370            MakeDirtyUnit(shape);
     371            RegisterShapeTiles(shape, TAG_TO_INDEX(tag));
    316372        }
    317373        else
    318374        {
     
    323379
    324380            StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)];
    325381
     382            MakeDirtyStatic(shape);
     383            UnregisterShapeTiles(shape, TAG_TO_INDEX(tag));
     384
    326385            CFixedVector2D fromBbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh));
    327386            CFixedVector2D toBbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(shape.hw, shape.hh));
    328387            m_StaticSubdivision.Move(TAG_TO_INDEX(tag),
     
    336395            shape.u = u;
    337396            shape.v = v;
    338397
    339             MakeDirtyStatic(shape.flags);
     398            MakeDirtyStatic(shape);
     399            RegisterShapeTiles(shape, TAG_TO_INDEX(tag));
    340400        }
    341401    }
    342402
     
    378438                CFixedVector2D(shape.x - shape.r, shape.z - shape.r),
    379439                CFixedVector2D(shape.x + shape.r, shape.z + shape.r));
    380440
    381             MakeDirtyUnit(shape.flags);
     441            MakeDirtyUnit(shape);
     442            UnregisterShapeTiles(shape, TAG_TO_INDEX(tag));
    382443            m_UnitShapes.erase(TAG_TO_INDEX(tag));
    383444        }
    384445        else
     
    389450            CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh));
    390451            m_StaticSubdivision.Remove(TAG_TO_INDEX(tag), center - bbHalfSize, center + bbHalfSize);
    391452
    392             MakeDirtyStatic(shape.flags);
     453            MakeDirtyStatic(shape);
     454            UnregisterShapeTiles(shape, TAG_TO_INDEX(tag));
    393455            m_StaticShapes.erase(TAG_TO_INDEX(tag));
    394456        }
    395457    }
     
    444506    // if a grid has a lower DirtyID then it needs to be updated.
    445507
    446508    size_t m_DirtyID;
     509    ShapeHistory<StaticShape> m_StaticHistory;
     510    ShapeHistory<UnitShape> m_UnitHistory;
    447511
     512    // For tile-based pathfinding:
     513    // Since we only count tiles whose centers are inside the square,
     514    // we maybe want to expand the square a bit so we're less likely to think there's
     515    // free space between buildings when there isn't. But this is just a random guess
     516    // and needs to be tweaked until everything works nicely.
     517    entity_pos_t m_ExpandPathfinding;
     518    entity_pos_t m_ExpandFoundation;
     519
     520    std::multimap<u32, u32> m_StaticShapesInfluence; // key : a tile index ((i << 16) + j) ; value : index of a static shape influencing the tile
     521    std::multimap<u32, u32> m_UnitShapesInfluence; // key : a tile index ((i << 16) + j) ; value : index of a unit shape influencing the tile
     522
    448523    /**
     524     * Register tiles that are affected by the shape
     525     */
     526    void RegisterShapeTiles(StaticShape const & shape, u32 id);
     527    void RegisterShapeTiles(UnitShape const & shape, u32 id);
     528
     529    /**
     530     * Unregister tiles that were affected by the shape
     531     */
     532    void UnregisterShapeTiles(StaticShape const & shape, u32 id);
     533    void UnregisterShapeTiles(UnitShape const & shape, u32 id);
     534
     535    /**
     536     * Update obstruction flags of a tile
     537     */
     538    void UpdateTile(u16 i, u16 j, Grid<u8>& grid);
     539
     540    /**
     541     * Update obstruction flags of tiles affected by the shape
     542     */
     543    void UpdateTilesUnderShape(StaticShape const & shape, Grid<u8>& grid, entity_pos_t const & expand);
     544    void UpdateTilesUnderShape(UnitShape const & shape, Grid<u8>& grid, entity_pos_t const & expand);
     545
     546
     547    /**
    449548     * Mark all previous Rasterise()d grids as dirty, and the debug display.
    450549     * Call this when the world bounds have changed.
    451550     */
    452551    void MakeDirtyAll()
    453552    {
    454553        ++m_DirtyID;
     554        m_StaticHistory.reset(m_DirtyID);
     555        m_UnitHistory.reset(m_DirtyID);
    455556        m_DebugOverlayDirty = true;
    456557    }
    457558
     
    468569     * Mark all previous Rasterise()d grids as dirty, if they depend on this shape.
    469570     * Call this when a static shape has changed.
    470571     */
    471     void MakeDirtyStatic(flags_t flags)
     572    void MakeDirtyStatic(StaticShape const & shape)
    472573    {
    473         if (flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))
     574        if (shape.flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION)) {
     575            m_StaticHistory.add(m_DirtyID, shape);
    474576            ++m_DirtyID;
     577        }
    475578
    476579        m_DebugOverlayDirty = true;
    477580    }
     
    480583     * Mark all previous Rasterise()d grids as dirty, if they depend on this shape.
    481584     * Call this when a unit shape has changed.
    482585     */
    483     void MakeDirtyUnit(flags_t flags)
     586    void MakeDirtyUnit(UnitShape const & shape)
    484587    {
    485         if (flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))
     588        if (shape.flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION)) {
     589            m_UnitHistory.add(m_DirtyID, shape);
    486590            ++m_DirtyID;
     591        }
    487592
    488593        m_DebugOverlayDirty = true;
    489594    }
     
    703808    z = entity_pos_t::FromInt(j*(int)CELL_SIZE + (int)CELL_SIZE/2);
    704809}
    705810
    706 bool CCmpObstructionManager::Rasterise(Grid<u8>& grid)
     811/**
     812 * Modify tiles on the shape's range
     813 */
     814static void ModifyTilesUnderShape(StaticShape const & shape, Grid<u8>& grid, entity_pos_t const & expand, ICmpObstructionManager::TileObstruction modificator)
    707815{
    708     if (!IsDirty(grid))
    709         return false;
     816    CFixedVector2D center(shape.x, shape.z);
     817    CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     818    CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
    710819
    711     PROFILE("Rasterise");
     820    u16 i0, j0, i1, j1;
     821    NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
     822    NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
     823    for (u16 j = j0; j <= j1; ++j)
     824    {
     825        for (u16 i = i0; i <= i1; ++i)
     826        {
     827            entity_pos_t x, z;
     828            TileCenter(i, j, x, z);
     829            if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
     830                grid.set(i, j, grid.get(i, j) | modificator);
     831        }
     832    }
     833}
     834static void ModifyTilesUnderShape(UnitShape const & shape, Grid<u8>& grid, entity_pos_t const & expand, ICmpObstructionManager::TileObstruction modificator)
     835{
     836    CFixedVector2D center(shape.x, shape.z);
     837    entity_pos_t r = shape.r + expand;
    712838
    713     grid.m_DirtyID = m_DirtyID;
     839    u16 i0, j0, i1, j1;
     840    NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
     841    NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
     842    for (u16 j = j0; j <= j1; ++j)
     843        for (u16 i = i0; i <= i1; ++i)
     844            grid.set(i, j, grid.get(i, j) | modificator);
     845}
    714846
    715     // TODO: this is all hopelessly inefficient
    716     // What we should perhaps do is have some kind of quadtree storing Shapes so it's
    717     // quick to invalidate and update small numbers of tiles
     847static bool TileIsUnderShape(u16 i, u16 j, StaticShape const & shape, Grid<u8>& grid, entity_pos_t const & expand)
     848{
     849    CFixedVector2D center(shape.x, shape.z);
     850    CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     851    CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
    718852
    719     grid.reset();
     853    u16 i0, j0, i1, j1;
     854    NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
     855    NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
     856    if (j >= j0 && j <= j1 && i >= i0 && i <= i1) {
     857        entity_pos_t x, z;
     858        TileCenter(i, j, x, z);
     859        if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
     860            return true;
     861    }
     862    return false;
     863}
     864static bool TileIsUnderShape(u16 i, u16 j, UnitShape const & shape, Grid<u8>& grid, entity_pos_t const & expand)
     865{
     866    CFixedVector2D center(shape.x, shape.z);
     867    entity_pos_t r = shape.r + expand;
    720868
    721     // For tile-based pathfinding:
    722     // Since we only count tiles whose centers are inside the square,
    723     // we maybe want to expand the square a bit so we're less likely to think there's
    724     // free space between buildings when there isn't. But this is just a random guess
    725     // and needs to be tweaked until everything works nicely.
    726     //entity_pos_t expandPathfinding = entity_pos_t::FromInt(CELL_SIZE / 2);
    727     // Actually that's bad because units get stuck when the A* pathfinder thinks they're
    728     // blocked on all sides, so it's better to underestimate
    729     entity_pos_t expandPathfinding = entity_pos_t::FromInt(0);
     869    u16 i0, j0, i1, j1;
     870    NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
     871    NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
     872    return (j >= j0 && j <= j1 && i >= i0 && i <= i1);
     873}
    730874
    731     // For AI building foundation planning, we want to definitely block all
    732     // potentially-obstructed tiles (so we don't blindly build on top of an obstruction),
    733     // so we need to expand by at least 1/sqrt(2) of a tile
    734     entity_pos_t expandFoundation = (entity_pos_t::FromInt(CELL_SIZE) * 3) / 4;
     875void CCmpObstructionManager::RegisterShapeTiles(StaticShape const & shape, u32 id)
     876{
     877    if (shape.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
     878    {
     879        entity_pos_t expand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     880        CFixedVector2D center(shape.x, shape.z);
     881        CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     882        CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
    735883
    736     for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it)
     884        u16 i0 = ((center.X - halfBound.X) / (int)CELL_SIZE).ToInt_RoundToZero();
     885        u16 j0 = ((center.Y - halfBound.Y) / (int)CELL_SIZE).ToInt_RoundToZero();
     886        u16 i1 = ((center.X + halfBound.X) / (int)CELL_SIZE).ToInt_RoundToZero();
     887        u16 j1 = ((center.Y + halfBound.Y) / (int)CELL_SIZE).ToInt_RoundToZero();
     888        for (u16 j = j0; j <= j1; ++j)
     889        {
     890            for (u16 i = i0; i <= i1; ++i)
     891            {
     892                entity_pos_t x, z;
     893                TileCenter(i, j, x, z);
     894                if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
     895                {
     896                    u32 key = (i << 16) + j;
     897                    m_StaticShapesInfluence.insert(std::make_pair(key, id));
     898                }
     899            }
     900        }
     901    }
     902}
     903
     904void CCmpObstructionManager::RegisterShapeTiles(UnitShape const & shape, u32 id)
     905{
     906    if (shape.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
    737907    {
    738         CFixedVector2D center(it->second.x, it->second.z);
     908        entity_pos_t expand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     909        CFixedVector2D center(shape.x, shape.z);
     910        entity_pos_t r = shape.r + expand;
    739911
    740         if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     912        u16 i0 = ((center.X - r) / (int)CELL_SIZE).ToInt_RoundToZero();
     913        u16 j0 = ((center.Y - r) / (int)CELL_SIZE).ToInt_RoundToZero();
     914        u16 i1 = ((center.X + r) / (int)CELL_SIZE).ToInt_RoundToZero();
     915        u16 j1 = ((center.Y + r) / (int)CELL_SIZE).ToInt_RoundToZero();
     916        for (u16 j = j0; j <= j1; ++j)
    741917        {
    742             CFixedVector2D halfSize(it->second.hw + expandPathfinding, it->second.hh + expandPathfinding);
    743             CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(it->second.u, it->second.v, halfSize);
     918            for (u16 i = i0; i <= i1; ++i)
     919            {
     920                u32 key = (i << 16) + j;
     921                m_UnitShapesInfluence.insert(std::make_pair(key, id));
     922            }
     923        }
     924    }
     925}
    744926
    745             u16 i0, j0, i1, j1;
    746             NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
    747             NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
    748             for (u16 j = j0; j <= j1; ++j)
     927void CCmpObstructionManager::UnregisterShapeTiles(StaticShape const & shape, u32 id)
     928{
     929    if (shape.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
     930    {
     931        entity_pos_t expand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     932        CFixedVector2D center(shape.x, shape.z);
     933        CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     934        CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
     935
     936        u16 i0 = ((center.X - halfBound.X) / (int)CELL_SIZE).ToInt_RoundToZero();
     937        u16 j0 = ((center.Y - halfBound.Y) / (int)CELL_SIZE).ToInt_RoundToZero();
     938        u16 i1 = ((center.X + halfBound.X) / (int)CELL_SIZE).ToInt_RoundToZero();
     939        u16 j1 = ((center.Y + halfBound.Y) / (int)CELL_SIZE).ToInt_RoundToZero();
     940        for (u16 j = j0; j <= j1; ++j)
     941        {
     942            for (u16 i = i0; i <= i1; ++i)
    749943            {
    750                 for (u16 i = i0; i <= i1; ++i)
     944                entity_pos_t x, z;
     945                TileCenter(i, j, x, z);
     946                if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
    751947                {
    752                     entity_pos_t x, z;
    753                     TileCenter(i, j, x, z);
    754                     if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, it->second.u, it->second.v, halfSize))
    755                         grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_PATHFINDING);
     948                    typedef std::multimap<u32, u32>::iterator iterator_t;
     949                    u32 key = (i << 16) + j;
     950                    std::pair<iterator_t, iterator_t> range = m_StaticShapesInfluence.equal_range(key);
     951                    while (range.first != range.second)
     952                    {
     953                        if (range.first->second == id) {
     954                            iterator_t to_erase = range.first;
     955                            ++range.first;
     956                            m_StaticShapesInfluence.erase(to_erase);
     957                        }else {
     958                            ++range.first;
     959                        }
     960                    }
    756961                }
    757962            }
    758963        }
     964    }
     965}
    759966
    760         if (it->second.flags & FLAG_BLOCK_FOUNDATION)
     967void CCmpObstructionManager::UnregisterShapeTiles(UnitShape const & shape, u32 id)
     968{
     969    if (shape.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
     970    {
     971        entity_pos_t expand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     972        CFixedVector2D center(shape.x, shape.z);
     973        entity_pos_t r = shape.r + expand;
     974
     975        u16 i0 = ((center.X - r) / (int)CELL_SIZE).ToInt_RoundToZero();
     976        u16 j0 = ((center.Y - r) / (int)CELL_SIZE).ToInt_RoundToZero();
     977        u16 i1 = ((center.X + r) / (int)CELL_SIZE).ToInt_RoundToZero();
     978        u16 j1 = ((center.Y + r) / (int)CELL_SIZE).ToInt_RoundToZero();
     979        for (u16 j = j0; j <= j1; ++j)
    761980        {
    762             CFixedVector2D halfSize(it->second.hw + expandFoundation, it->second.hh + expandFoundation);
    763             CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(it->second.u, it->second.v, halfSize);
    764 
    765             u16 i0, j0, i1, j1;
    766             NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
    767             NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
    768             for (u16 j = j0; j <= j1; ++j)
     981            for (u16 i = i0; i <= i1; ++i)
    769982            {
    770                 for (u16 i = i0; i <= i1; ++i)
     983                typedef std::multimap<u32, u32>::iterator iterator_t;
     984                u32 key = (i << 16) + j;
     985                std::pair<iterator_t, iterator_t> range = m_UnitShapesInfluence.equal_range(key);
     986                while (range.first != range.second)
    771987                {
    772                     entity_pos_t x, z;
    773                     TileCenter(i, j, x, z);
    774                     if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, it->second.u, it->second.v, halfSize))
    775                         grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_FOUNDATION);
     988                    if (range.first->second == id) {
     989                        iterator_t to_erase = range.first;
     990                        ++range.first;
     991                        m_UnitShapesInfluence.erase(to_erase);
     992                    }else {
     993                        ++range.first;
     994                    }
    776995                }
    777996            }
    778997        }
    779998    }
     999}
    7801000
    781     for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it)
     1001void CCmpObstructionManager::UpdateTile(u16 i, u16 j, Grid<uint8_t>& grid)
     1002{
     1003    typedef std::multimap<u32, u32>::const_iterator iterator_t;
     1004    u8 tile = 0;
     1005    u8 full = (TILE_OBSTRUCTED_PATHFINDING | TILE_OBSTRUCTED_FOUNDATION);
     1006
     1007    u32 key = (i << 16) + j;
     1008    std::pair<iterator_t, iterator_t> range = m_StaticShapesInfluence.equal_range(key);
     1009    while (range.first != range.second)
    7821010    {
    783         CFixedVector2D center(it->second.x, it->second.z);
     1011        StaticShape const & shape = m_StaticShapes[range.first->second];
     1012        if (shape.flags & FLAG_BLOCK_PATHFINDING) {
     1013            if (TileIsUnderShape(i, j, shape, grid, m_ExpandPathfinding)) {
     1014                tile |= TILE_OBSTRUCTED_PATHFINDING;
     1015            }
     1016        }
     1017        if (shape.flags & FLAG_BLOCK_FOUNDATION) {
     1018            if (TileIsUnderShape(i, j, shape, grid, m_ExpandFoundation)) {
     1019                tile |= TILE_OBSTRUCTED_FOUNDATION;
     1020            }
     1021        }
    7841022
    785         if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     1023        if (tile == full) {
     1024            break;
     1025        }
     1026
     1027        ++range.first;
     1028    }
     1029
     1030    range = m_UnitShapesInfluence.equal_range(key);
     1031    while (tile != full && range.first != range.second)
     1032    {
     1033        UnitShape const & shape = m_UnitShapes[range.first->second];
     1034        if (shape.flags & FLAG_BLOCK_PATHFINDING) {
     1035            if (TileIsUnderShape(i, j, shape, grid, m_ExpandPathfinding)) {
     1036                tile |= TILE_OBSTRUCTED_PATHFINDING;
     1037            }
     1038        }
     1039        if (shape.flags & FLAG_BLOCK_FOUNDATION) {
     1040            if (TileIsUnderShape(i, j, shape, grid, m_ExpandFoundation)) {
     1041                tile |= TILE_OBSTRUCTED_FOUNDATION;
     1042            }
     1043        }
     1044
     1045        ++range.first;
     1046    }
     1047
     1048    grid.set(i, j, tile);
     1049}
     1050
     1051void CCmpObstructionManager::UpdateTilesUnderShape(StaticShape const & shape, Grid<u8>& grid, entity_pos_t const & expand)
     1052{
     1053    CFixedVector2D center(shape.x, shape.z);
     1054    CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     1055    CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
     1056
     1057    u16 i0, j0, i1, j1;
     1058    NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
     1059    NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
     1060    for (u16 j = j0; j <= j1; ++j)
     1061    {
     1062        for (u16 i = i0; i <= i1; ++i)
    7861063        {
    787             entity_pos_t r = it->second.r + expandPathfinding;
     1064            entity_pos_t x, z;
     1065            TileCenter(i, j, x, z);
     1066            if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
     1067            {
     1068                UpdateTile(i, j, grid);
     1069            }
     1070        }
     1071    }
     1072}
     1073void CCmpObstructionManager::UpdateTilesUnderShape(UnitShape const & shape, Grid<u8>& grid, entity_pos_t const & expand)
     1074{
     1075    CFixedVector2D center(shape.x, shape.z);
     1076    entity_pos_t r = shape.r + expand;
    7881077
    789             u16 i0, j0, i1, j1;
    790             NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
    791             NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
    792             for (u16 j = j0; j <= j1; ++j)
    793                 for (u16 i = i0; i <= i1; ++i)
    794                     grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_PATHFINDING);
     1078    u16 i0, j0, i1, j1;
     1079    NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
     1080    NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
     1081    for (u16 j = j0; j <= j1; ++j)
     1082        for (u16 i = i0; i <= i1; ++i)
     1083            UpdateTile(i, j, grid);
     1084}
     1085
     1086bool CCmpObstructionManager::Rasterise(Grid<u8>& grid)
     1087{
     1088    if (!IsDirty(grid))
     1089        return false;
     1090
     1091    PROFILE("Rasterise");
     1092    TIMER_ACCRUE(tc_Rasterize);
     1093
     1094    if (!m_StaticHistory.isInHistory(grid.m_DirtyID) || !m_UnitHistory.isInHistory(grid.m_DirtyID)) {
     1095        // Some pieces of history are missing, we need to recompute the entire grid
     1096        grid.reset();
     1097        for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it)
     1098        {
     1099            if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     1100                ModifyTilesUnderShape(it->second, grid, m_ExpandPathfinding, TILE_OBSTRUCTED_PATHFINDING);
     1101
     1102            if (it->second.flags & FLAG_BLOCK_FOUNDATION)
     1103                ModifyTilesUnderShape(it->second, grid, m_ExpandFoundation, TILE_OBSTRUCTED_FOUNDATION);
    7951104        }
    7961105
    797         if (it->second.flags & FLAG_BLOCK_FOUNDATION)
     1106        for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it)
    7981107        {
    799             entity_pos_t r = it->second.r + expandFoundation;
     1108            if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     1109                ModifyTilesUnderShape(it->second, grid, m_ExpandPathfinding, TILE_OBSTRUCTED_PATHFINDING);
    8001110
    801             u16 i0, j0, i1, j1;
    802             NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
    803             NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
    804             for (u16 j = j0; j <= j1; ++j)
    805                 for (u16 i = i0; i <= i1; ++i)
    806                     grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_FOUNDATION);
     1111            if (it->second.flags & FLAG_BLOCK_FOUNDATION)
     1112                ModifyTilesUnderShape(it->second, grid, m_ExpandFoundation, TILE_OBSTRUCTED_FOUNDATION);
    8071113        }
     1114    }else {
     1115        // We'll recompute only needed tiles according to history
     1116
     1117        entity_pos_t maxExpand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     1118
     1119        // Compute tiles under modified static shapes
     1120        ShapeHistory<StaticShape>::const_iterator srmit = m_StaticHistory.get(grid.m_DirtyID);
     1121        ShapeHistory<StaticShape>::const_iterator srmend = m_StaticHistory.end();
     1122        while (srmit != srmend) {
     1123            if (srmit->second.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION)) {
     1124                UpdateTilesUnderShape(srmit->second, grid, maxExpand);
     1125            }
     1126
     1127            ++srmit;
     1128        }
     1129
     1130        // Reset tiles under removed unit shapes
     1131        ShapeHistory<UnitShape>::const_iterator urmit = m_UnitHistory.get(grid.m_DirtyID);
     1132        ShapeHistory<UnitShape>::const_iterator urmend = m_UnitHistory.end();
     1133        while (urmit != urmend) {
     1134            if (urmit->second.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION)) {
     1135                UpdateTilesUnderShape(urmit->second, grid, maxExpand);
     1136            }
     1137
     1138            ++urmit;
     1139        }
    8081140    }
    8091141
     1142    grid.m_DirtyID = m_DirtyID;
     1143
    8101144    // Any tiles outside or very near the edge of the map are impassable
    8111145
    8121146    // WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this