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

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

Cosmetic regarding coding conventions, diff from the revision r10175

  • source/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
     34{
     35public:
     36    typedef std::vector< std::pair<u16, u16> >::const_iterator const_iterator;
     37
     38    // Methods modifying the observer itself
     39    GridObserver() : m_Reseted(false)
     40    {
     41        m_Modified.reserve(3000); //FIXME 3000 ? Needs some estimation.
     42    }
     43    void clear()
     44    {
     45        m_Modified.clear();
     46        m_Reseted = false;
     47    }
     48
     49    // Methods indicating changes on the observed grid
     50    void modifiy(u16 i, u16 j)
     51    {
     52        //TODO Need check to avoid doubles
     53        m_Modified.push_back(std::make_pair(i, j));
     54    }
     55    void reset()
     56    {
     57        m_Reseted = true;
     58    }
     59
     60    // Methods to read observed modifications
     61    bool reseted() const
     62    {
     63        return m_Reseted;
     64    }
     65    const_iterator begin() const
     66    {
     67        return m_Modified.begin();
     68    }
     69    const_iterator end() const
     70    {
     71        return m_Modified.end();
     72    }
     73
     74private:
     75    std::vector< std::pair<u16, u16> > m_Modified;
     76    bool m_Reseted;
     77};
     78
     79/**
    3080 * Basic 2D array, intended for storing tile data, plus support for lazy updates
    3181 * by ICmpObstructionManager.
    3282 * @c T must be a POD type that can be initialised with 0s.
     
    3585class Grid
    3686{
    3787public:
    38     Grid() : m_W(0), m_H(0), m_Data(NULL), m_DirtyID(0)
     88    Grid() : m_W(0), m_H(0), m_Data(NULL), m_DirtyID(0), m_Observer(NULL)
    3989    {
    4090    }
    4191
    42     Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL), m_DirtyID(0)
     92    Grid(u16 w, u16 h) : m_W(w), m_H(h), m_Data(NULL), m_DirtyID(0), m_Observer(NULL)
    4393    {
    4494        if (m_W || m_H)
    4595            m_Data = new T[m_W * m_H];
     
    65115            }
    66116            else
    67117                m_Data = NULL;
     118            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
    68119        }
    69120        return *this;
    70121    }
     
    77128    void reset()
    78129    {
    79130        if (m_Data)
     131        {
    80132            memset(m_Data, 0, m_W*m_H*sizeof(T));
     133            if (m_Observer)
     134                m_Observer->reset();
     135        }
    81136    }
    82137
    83138    void set(int i, int j, const T& value)
     
    86141        ENSURE(0 <= i && i < m_W && 0 <= j && j < m_H);
    87142#endif
    88143        m_Data[j*m_W + i] = value;
     144        if (m_Observer)
     145            m_Observer->modifiy(i, j);
    89146    }
    90147
    91148    T& get(int i, int j) const
     
    100157    T* m_Data;
    101158
    102159    size_t m_DirtyID; // if this is < the id maintained by ICmpObstructionManager then it needs to be updated
     160    GridObserver* m_Observer;
    103161};
    104162
    105163/**
  • source/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
  • source/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)
     354        if (m_ObstructionChanges.reseted())
    347355        {
    348             for (u16 i = 0; i < m_MapSize; ++i)
     356            for (u16 j = 0; j < m_MapSize; ++j)
    349357            {
     358                for (u16 i = 0; i < m_MapSize; ++i)
     359                {
     360                    TerrainTile& t = m_Grid->get(i, j);
     361
     362                    u8 obstruct = m_ObstructionGrid->get(i, j);
     363
     364                    if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
     365                        t |= 1;
     366                    else
     367                        t &= ~1;
     368
     369                    if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION)
     370                        t |= 2;
     371                    else
     372                        t &= ~2;
     373                }
     374            }
     375        }
     376        else
     377        {
     378            for (GridObserver::const_iterator it = m_ObstructionChanges.begin(); it != m_ObstructionChanges.end(); ++it) {
     379                u16 i = it->first;
     380                u16 j = it->second;
     381
     382                //FIXME ugly copy/paste
    350383                TerrainTile& t = m_Grid->get(i, j);
    351384
    352385                u8 obstruct = m_ObstructionGrid->get(i, j);
     
    368401    else if (obstructionsDirty || m_TerrainDirty)
    369402    {
    370403        PROFILE("UpdateGrid full");
     404        TIMER_ACCRUE(tc_PathfinderUpdateGrid);
    371405
    372406        // Obstructions or terrain changed - we need to recompute passability
    373407        // TODO: only bother recomputing the region that has actually changed
  • source/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
     75{
     76public:
     77    typedef typename std::map<size_t, S>::const_iterator const_iterator;
     78
     79    ShapeHistory(size_t baseId = 1) : m_HistoryBase(baseId)
     80    {
     81    }
     82    bool isInHistory(size_t id) const
     83    {
     84        return id >= m_HistoryBase;
     85    }
     86    void add(size_t id, S const & e)
     87    {
     88        m_Modifications.insert(std::make_pair(id, e));
     89        if (m_Modifications.size() > 10) {
     90            m_HistoryBase = m_Modifications.begin()->first + 1;
     91            m_Modifications.erase(m_Modifications.begin());
     92        }
     93    }
     94    void reset(size_t id)
     95    {
     96        m_Modifications.clear();
     97        m_HistoryBase = id;
     98    }
     99    const_iterator end() const
     100    {
     101        return m_Modifications.end();
     102    }
     103    const_iterator get(size_t id) const
     104    {
     105        return m_Modifications.lower_bound(id);
     106    }
     107
     108private:
     109    std::map<size_t, S> m_Modifications;
     110    size_t m_HistoryBase;
     111};
     112
    70113/**
    71114 * Serialization helper template for UnitShape
    72115 */
     
    149192        m_StaticShapeNext = 1;
    150193
    151194        m_DirtyID = 1; // init to 1 so default-initialised grids are considered dirty
     195        m_StaticHistory.reset(m_DirtyID);
     196        m_UnitHistory.reset(m_DirtyID);
    152197
     198        //m_ExpandPathfinding = entity_pos_t::FromInt(CELL_SIZE / 2);
     199        // Actually that's bad because units get stuck when the A* pathfinder thinks they're
     200        // blocked on all sides, so it's better to underestimate
     201        m_ExpandPathfinding = entity_pos_t::FromInt(0);
     202
     203        // For AI building foundation planning, we want to definitely block all
     204        // potentially-obstructed tiles (so we don't blindly build on top of an obstruction),
     205        // so we need to expand by at least 1/sqrt(2) of a tile
     206        m_ExpandFoundation = (entity_pos_t::FromInt(CELL_SIZE) * 3) / 4;
     207
    153208        m_PassabilityCircular = false;
    154209
    155210        m_WorldX0 = m_WorldZ0 = m_WorldX1 = m_WorldZ1 = entity_pos_t::Zero();
     
    250305        UnitShape shape = { ent, x, z, r, flags, group };
    251306        u32 id = m_UnitShapeNext++;
    252307        m_UnitShapes[id] = shape;
    253         MakeDirtyUnit(flags);
     308        MakeDirtyUnit(shape);
     309        RegisterShapeTiles(shape, id);
    254310
    255311        m_UnitSubdivision.Add(id, CFixedVector2D(x - r, z - r), CFixedVector2D(x + r, z + r));
    256312
     
    267323        StaticShape shape = { ent, x, z, u, v, w/2, h/2, flags };
    268324        u32 id = m_StaticShapeNext++;
    269325        m_StaticShapes[id] = shape;
    270         MakeDirtyStatic(flags);
     326        MakeDirtyStatic(shape);
     327        RegisterShapeTiles(shape, id);
    271328
    272329        CFixedVector2D center(x, z);
    273330        CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(w/2, h/2));
     
    303360        {
    304361            UnitShape& shape = m_UnitShapes[TAG_TO_INDEX(tag)];
    305362
     363            MakeDirtyUnit(shape);
     364            UnregisterShapeTiles(shape, TAG_TO_INDEX(tag));
     365
    306366            m_UnitSubdivision.Move(TAG_TO_INDEX(tag),
    307367                CFixedVector2D(shape.x - shape.r, shape.z - shape.r),
    308368                CFixedVector2D(shape.x + shape.r, shape.z + shape.r),
     
    312372            shape.x = x;
    313373            shape.z = z;
    314374
    315             MakeDirtyUnit(shape.flags);
     375            MakeDirtyUnit(shape);
     376            RegisterShapeTiles(shape, TAG_TO_INDEX(tag));
    316377        }
    317378        else
    318379        {
     
    323384
    324385            StaticShape& shape = m_StaticShapes[TAG_TO_INDEX(tag)];
    325386
     387            MakeDirtyStatic(shape);
     388            UnregisterShapeTiles(shape, TAG_TO_INDEX(tag));
     389
    326390            CFixedVector2D fromBbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh));
    327391            CFixedVector2D toBbHalfSize = Geometry::GetHalfBoundingBox(u, v, CFixedVector2D(shape.hw, shape.hh));
    328392            m_StaticSubdivision.Move(TAG_TO_INDEX(tag),
     
    336400            shape.u = u;
    337401            shape.v = v;
    338402
    339             MakeDirtyStatic(shape.flags);
     403            MakeDirtyStatic(shape);
     404            RegisterShapeTiles(shape, TAG_TO_INDEX(tag));
    340405        }
    341406    }
    342407
     
    378443                CFixedVector2D(shape.x - shape.r, shape.z - shape.r),
    379444                CFixedVector2D(shape.x + shape.r, shape.z + shape.r));
    380445
    381             MakeDirtyUnit(shape.flags);
     446            MakeDirtyUnit(shape);
     447            UnregisterShapeTiles(shape, TAG_TO_INDEX(tag));
    382448            m_UnitShapes.erase(TAG_TO_INDEX(tag));
    383449        }
    384450        else
     
    389455            CFixedVector2D bbHalfSize = Geometry::GetHalfBoundingBox(shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh));
    390456            m_StaticSubdivision.Remove(TAG_TO_INDEX(tag), center - bbHalfSize, center + bbHalfSize);
    391457
    392             MakeDirtyStatic(shape.flags);
     458            MakeDirtyStatic(shape);
     459            UnregisterShapeTiles(shape, TAG_TO_INDEX(tag));
    393460            m_StaticShapes.erase(TAG_TO_INDEX(tag));
    394461        }
    395462    }
     
    444511    // if a grid has a lower DirtyID then it needs to be updated.
    445512
    446513    size_t m_DirtyID;
     514    ShapeHistory<StaticShape> m_StaticHistory;
     515    ShapeHistory<UnitShape> m_UnitHistory;
    447516
     517    // For tile-based pathfinding:
     518    // Since we only count tiles whose centers are inside the square,
     519    // we maybe want to expand the square a bit so we're less likely to think there's
     520    // free space between buildings when there isn't. But this is just a random guess
     521    // and needs to be tweaked until everything works nicely.
     522    entity_pos_t m_ExpandPathfinding;
     523    entity_pos_t m_ExpandFoundation;
     524
     525    std::multimap<u32, u32> m_StaticShapesInfluence; // key : a tile index ((i << 16) + j) ; value : index of a static shape influencing the tile
     526    std::multimap<u32, u32> m_UnitShapesInfluence; // key : a tile index ((i << 16) + j) ; value : index of a unit shape influencing the tile
     527
    448528    /**
     529     * Register tiles that are affected by the shape
     530     */
     531    void RegisterShapeTiles(StaticShape const & shape, u32 id);
     532    void RegisterShapeTiles(UnitShape const & shape, u32 id);
     533
     534    /**
     535     * Unregister tiles that were affected by the shape
     536     */
     537    void UnregisterShapeTiles(StaticShape const & shape, u32 id);
     538    void UnregisterShapeTiles(UnitShape const & shape, u32 id);
     539
     540    /**
     541     * Update obstruction flags of a tile
     542     */
     543    void UpdateTile(u16 i, u16 j, Grid<u8>& grid);
     544
     545    /**
     546     * Update obstruction flags of tiles affected by the shape
     547     */
     548    void UpdateTilesUnderShape(StaticShape const & shape, Grid<u8>& grid, entity_pos_t const & expand);
     549    void UpdateTilesUnderShape(UnitShape const & shape, Grid<u8>& grid, entity_pos_t const & expand);
     550
     551
     552    /**
    449553     * Mark all previous Rasterise()d grids as dirty, and the debug display.
    450554     * Call this when the world bounds have changed.
    451555     */
    452556    void MakeDirtyAll()
    453557    {
    454558        ++m_DirtyID;
     559        m_StaticHistory.reset(m_DirtyID);
     560        m_UnitHistory.reset(m_DirtyID);
    455561        m_DebugOverlayDirty = true;
    456562    }
    457563
     
    468574     * Mark all previous Rasterise()d grids as dirty, if they depend on this shape.
    469575     * Call this when a static shape has changed.
    470576     */
    471     void MakeDirtyStatic(flags_t flags)
     577    void MakeDirtyStatic(StaticShape const & shape)
    472578    {
    473         if (flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))
     579        if (shape.flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))
     580        {
     581            m_StaticHistory.add(m_DirtyID, shape);
    474582            ++m_DirtyID;
     583        }
    475584
    476585        m_DebugOverlayDirty = true;
    477586    }
     
    480589     * Mark all previous Rasterise()d grids as dirty, if they depend on this shape.
    481590     * Call this when a unit shape has changed.
    482591     */
    483     void MakeDirtyUnit(flags_t flags)
     592    void MakeDirtyUnit(UnitShape const & shape)
    484593    {
    485         if (flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))
     594        if (shape.flags & (FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))
     595        {
     596            m_UnitHistory.add(m_DirtyID, shape);
    486597            ++m_DirtyID;
     598        }
    487599
    488600        m_DebugOverlayDirty = true;
    489601    }
     
    703815    z = entity_pos_t::FromInt(j*(int)CELL_SIZE + (int)CELL_SIZE/2);
    704816}
    705817
    706 bool CCmpObstructionManager::Rasterise(Grid<u8>& grid)
     818/**
     819 * Modify tiles on the shape's range
     820 */
     821static void ModifyTilesUnderShape(StaticShape const & shape, Grid<u8>& grid, entity_pos_t const & expand, ICmpObstructionManager::TileObstruction modificator)
    707822{
    708     if (!IsDirty(grid))
    709         return false;
     823    CFixedVector2D center(shape.x, shape.z);
     824    CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     825    CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
    710826
    711     PROFILE("Rasterise");
     827    u16 i0, j0, i1, j1;
     828    NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
     829    NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
     830    for (u16 j = j0; j <= j1; ++j)
     831    {
     832        for (u16 i = i0; i <= i1; ++i)
     833        {
     834            entity_pos_t x, z;
     835            TileCenter(i, j, x, z);
     836            if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
     837                grid.set(i, j, grid.get(i, j) | modificator);
     838        }
     839    }
     840}
     841static void ModifyTilesUnderShape(UnitShape const & shape, Grid<u8>& grid, entity_pos_t const & expand, ICmpObstructionManager::TileObstruction modificator)
     842{
     843    CFixedVector2D center(shape.x, shape.z);
     844    entity_pos_t r = shape.r + expand;
    712845
    713     grid.m_DirtyID = m_DirtyID;
     846    u16 i0, j0, i1, j1;
     847    NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
     848    NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
     849    for (u16 j = j0; j <= j1; ++j)
     850        for (u16 i = i0; i <= i1; ++i)
     851            grid.set(i, j, grid.get(i, j) | modificator);
     852}
    714853
    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
     854static bool TileIsUnderShape(u16 i, u16 j, StaticShape const & shape, Grid<u8>& grid, entity_pos_t const & expand)
     855{
     856    CFixedVector2D center(shape.x, shape.z);
     857    CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     858    CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
    718859
    719     grid.reset();
     860    u16 i0, j0, i1, j1;
     861    NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
     862    NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
     863    if (j >= j0 && j <= j1 && i >= i0 && i <= i1) {
     864        entity_pos_t x, z;
     865        TileCenter(i, j, x, z);
     866        if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
     867            return true;
     868    }
     869    return false;
     870}
     871static bool TileIsUnderShape(u16 i, u16 j, UnitShape const & shape, Grid<u8>& grid, entity_pos_t const & expand)
     872{
     873    CFixedVector2D center(shape.x, shape.z);
     874    entity_pos_t r = shape.r + expand;
    720875
    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);
     876    u16 i0, j0, i1, j1;
     877    NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
     878    NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
     879    return (j >= j0 && j <= j1 && i >= i0 && i <= i1);
     880}
    730881
    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;
     882void CCmpObstructionManager::RegisterShapeTiles(StaticShape const & shape, u32 id)
     883{
     884    if (shape.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
     885    {
     886        entity_pos_t expand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     887        CFixedVector2D center(shape.x, shape.z);
     888        CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     889        CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
    735890
    736     for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it)
     891        u16 i0 = ((center.X - halfBound.X) / (int)CELL_SIZE).ToInt_RoundToZero();
     892        u16 j0 = ((center.Y - halfBound.Y) / (int)CELL_SIZE).ToInt_RoundToZero();
     893        u16 i1 = ((center.X + halfBound.X) / (int)CELL_SIZE).ToInt_RoundToZero();
     894        u16 j1 = ((center.Y + halfBound.Y) / (int)CELL_SIZE).ToInt_RoundToZero();
     895        for (u16 j = j0; j <= j1; ++j)
     896        {
     897            for (u16 i = i0; i <= i1; ++i)
     898            {
     899                entity_pos_t x, z;
     900                TileCenter(i, j, x, z);
     901                if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
     902                {
     903                    u32 key = (i << 16) + j;
     904                    m_StaticShapesInfluence.insert(std::make_pair(key, id));
     905                }
     906            }
     907        }
     908    }
     909}
     910
     911void CCmpObstructionManager::RegisterShapeTiles(UnitShape const & shape, u32 id)
     912{
     913    if (shape.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
    737914    {
    738         CFixedVector2D center(it->second.x, it->second.z);
     915        entity_pos_t expand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     916        CFixedVector2D center(shape.x, shape.z);
     917        entity_pos_t r = shape.r + expand;
    739918
    740         if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     919        u16 i0 = ((center.X - r) / (int)CELL_SIZE).ToInt_RoundToZero();
     920        u16 j0 = ((center.Y - r) / (int)CELL_SIZE).ToInt_RoundToZero();
     921        u16 i1 = ((center.X + r) / (int)CELL_SIZE).ToInt_RoundToZero();
     922        u16 j1 = ((center.Y + r) / (int)CELL_SIZE).ToInt_RoundToZero();
     923        for (u16 j = j0; j <= j1; ++j)
    741924        {
    742             CFixedVector2D halfSize(it->second.hw + expandPathfinding, it->second.hh + expandPathfinding);
    743             CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(it->second.u, it->second.v, halfSize);
     925            for (u16 i = i0; i <= i1; ++i)
     926            {
     927                u32 key = (i << 16) + j;
     928                m_UnitShapesInfluence.insert(std::make_pair(key, id));
     929            }
     930        }
     931    }
     932}
    744933
    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)
     934void CCmpObstructionManager::UnregisterShapeTiles(StaticShape const & shape, u32 id)
     935{
     936    if (shape.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
     937    {
     938        entity_pos_t expand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     939        CFixedVector2D center(shape.x, shape.z);
     940        CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     941        CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
     942
     943        u16 i0 = ((center.X - halfBound.X) / (int)CELL_SIZE).ToInt_RoundToZero();
     944        u16 j0 = ((center.Y - halfBound.Y) / (int)CELL_SIZE).ToInt_RoundToZero();
     945        u16 i1 = ((center.X + halfBound.X) / (int)CELL_SIZE).ToInt_RoundToZero();
     946        u16 j1 = ((center.Y + halfBound.Y) / (int)CELL_SIZE).ToInt_RoundToZero();
     947        for (u16 j = j0; j <= j1; ++j)
     948        {
     949            for (u16 i = i0; i <= i1; ++i)
    749950            {
    750                 for (u16 i = i0; i <= i1; ++i)
     951                entity_pos_t x, z;
     952                TileCenter(i, j, x, z);
     953                if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
    751954                {
    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);
     955                    typedef std::multimap<u32, u32>::iterator iterator_t;
     956                    u32 key = (i << 16) + j;
     957                    std::pair<iterator_t, iterator_t> range = m_StaticShapesInfluence.equal_range(key);
     958                    while (range.first != range.second)
     959                    {
     960                        if (range.first->second == id) {
     961                            iterator_t to_erase = range.first;
     962                            ++range.first;
     963                            m_StaticShapesInfluence.erase(to_erase);
     964                        }else {
     965                            ++range.first;
     966                        }
     967                    }
    756968                }
    757969            }
    758970        }
     971    }
     972}
    759973
    760         if (it->second.flags & FLAG_BLOCK_FOUNDATION)
     974void CCmpObstructionManager::UnregisterShapeTiles(UnitShape const & shape, u32 id)
     975{
     976    if (shape.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
     977    {
     978        entity_pos_t expand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     979        CFixedVector2D center(shape.x, shape.z);
     980        entity_pos_t r = shape.r + expand;
     981
     982        u16 i0 = ((center.X - r) / (int)CELL_SIZE).ToInt_RoundToZero();
     983        u16 j0 = ((center.Y - r) / (int)CELL_SIZE).ToInt_RoundToZero();
     984        u16 i1 = ((center.X + r) / (int)CELL_SIZE).ToInt_RoundToZero();
     985        u16 j1 = ((center.Y + r) / (int)CELL_SIZE).ToInt_RoundToZero();
     986        for (u16 j = j0; j <= j1; ++j)
    761987        {
    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)
     988            for (u16 i = i0; i <= i1; ++i)
    769989            {
    770                 for (u16 i = i0; i <= i1; ++i)
     990                typedef std::multimap<u32, u32>::iterator iterator_t;
     991                u32 key = (i << 16) + j;
     992                std::pair<iterator_t, iterator_t> range = m_UnitShapesInfluence.equal_range(key);
     993                while (range.first != range.second)
    771994                {
    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);
     995                    if (range.first->second == id) {
     996                        iterator_t to_erase = range.first;
     997                        ++range.first;
     998                        m_UnitShapesInfluence.erase(to_erase);
     999                    }else {
     1000                        ++range.first;
     1001                    }
    7761002                }
    7771003            }
    7781004        }
    7791005    }
     1006}
    7801007
    781     for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it)
     1008void CCmpObstructionManager::UpdateTile(u16 i, u16 j, Grid<uint8_t>& grid)
     1009{
     1010    typedef std::multimap<u32, u32>::const_iterator iterator_t;
     1011    u8 tile = 0;
     1012    u8 full = (TILE_OBSTRUCTED_PATHFINDING | TILE_OBSTRUCTED_FOUNDATION);
     1013
     1014    u32 key = (i << 16) + j;
     1015    std::pair<iterator_t, iterator_t> range = m_StaticShapesInfluence.equal_range(key);
     1016    while (range.first != range.second)
    7821017    {
    783         CFixedVector2D center(it->second.x, it->second.z);
     1018        StaticShape const & shape = m_StaticShapes[range.first->second];
     1019        if (shape.flags & FLAG_BLOCK_PATHFINDING)
     1020            if (TileIsUnderShape(i, j, shape, grid, m_ExpandPathfinding))
     1021                tile |= TILE_OBSTRUCTED_PATHFINDING;
     1022        if (shape.flags & FLAG_BLOCK_FOUNDATION)
     1023            if (TileIsUnderShape(i, j, shape, grid, m_ExpandFoundation))
     1024                tile |= TILE_OBSTRUCTED_FOUNDATION;
    7841025
    785         if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     1026        if (tile == full)
     1027            break;
     1028
     1029        ++range.first;
     1030    }
     1031
     1032    range = m_UnitShapesInfluence.equal_range(key);
     1033    while (tile != full && range.first != range.second)
     1034    {
     1035        UnitShape const & shape = m_UnitShapes[range.first->second];
     1036        if (shape.flags & FLAG_BLOCK_PATHFINDING)
     1037            if (TileIsUnderShape(i, j, shape, grid, m_ExpandPathfinding))
     1038                tile |= TILE_OBSTRUCTED_PATHFINDING;
     1039        if (shape.flags & FLAG_BLOCK_FOUNDATION)
     1040            if (TileIsUnderShape(i, j, shape, grid, m_ExpandFoundation))
     1041                tile |= TILE_OBSTRUCTED_FOUNDATION;
     1042
     1043        ++range.first;
     1044    }
     1045
     1046    grid.set(i, j, tile);
     1047}
     1048
     1049void CCmpObstructionManager::UpdateTilesUnderShape(StaticShape const & shape, Grid<u8>& grid, entity_pos_t const & expand)
     1050{
     1051    CFixedVector2D center(shape.x, shape.z);
     1052    CFixedVector2D halfSize(shape.hw + expand, shape.hh + expand);
     1053    CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
     1054
     1055    u16 i0, j0, i1, j1;
     1056    NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
     1057    NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
     1058    for (u16 j = j0; j <= j1; ++j)
     1059    {
     1060        for (u16 i = i0; i <= i1; ++i)
    7861061        {
    787             entity_pos_t r = it->second.r + expandPathfinding;
     1062            entity_pos_t x, z;
     1063            TileCenter(i, j, x, z);
     1064            if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, shape.u, shape.v, halfSize))
     1065                UpdateTile(i, j, grid);
     1066        }
     1067    }
     1068}
     1069void CCmpObstructionManager::UpdateTilesUnderShape(UnitShape const & shape, Grid<u8>& grid, entity_pos_t const & expand)
     1070{
     1071    CFixedVector2D center(shape.x, shape.z);
     1072    entity_pos_t r = shape.r + expand;
    7881073
    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);
     1074    u16 i0, j0, i1, j1;
     1075    NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
     1076    NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
     1077    for (u16 j = j0; j <= j1; ++j)
     1078        for (u16 i = i0; i <= i1; ++i)
     1079            UpdateTile(i, j, grid);
     1080}
     1081
     1082bool CCmpObstructionManager::Rasterise(Grid<u8>& grid)
     1083{
     1084    if (!IsDirty(grid))
     1085        return false;
     1086
     1087    PROFILE("Rasterise");
     1088    TIMER_ACCRUE(tc_Rasterize);
     1089
     1090    if (!m_StaticHistory.isInHistory(grid.m_DirtyID) || !m_UnitHistory.isInHistory(grid.m_DirtyID))
     1091    {
     1092        // Some pieces of history are missing, we need to recompute the entire grid
     1093        grid.reset();
     1094        for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it)
     1095        {
     1096            if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     1097                ModifyTilesUnderShape(it->second, grid, m_ExpandPathfinding, TILE_OBSTRUCTED_PATHFINDING);
     1098
     1099            if (it->second.flags & FLAG_BLOCK_FOUNDATION)
     1100                ModifyTilesUnderShape(it->second, grid, m_ExpandFoundation, TILE_OBSTRUCTED_FOUNDATION);
    7951101        }
    7961102
    797         if (it->second.flags & FLAG_BLOCK_FOUNDATION)
     1103        for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it)
    7981104        {
    799             entity_pos_t r = it->second.r + expandFoundation;
     1105            if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     1106                ModifyTilesUnderShape(it->second, grid, m_ExpandPathfinding, TILE_OBSTRUCTED_PATHFINDING);
    8001107
    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);
     1108            if (it->second.flags & FLAG_BLOCK_FOUNDATION)
     1109                ModifyTilesUnderShape(it->second, grid, m_ExpandFoundation, TILE_OBSTRUCTED_FOUNDATION);
    8071110        }
    8081111    }
     1112    else
     1113    {
     1114        // We'll recompute only needed tiles according to history
    8091115
     1116        entity_pos_t maxExpand = std::max(m_ExpandPathfinding, m_ExpandFoundation);
     1117
     1118        // Compute tiles under modified static shapes
     1119        for (ShapeHistory<StaticShape>::const_iterator it = m_StaticHistory.get(grid.m_DirtyID); it != m_StaticHistory.end(); ++it)
     1120        {
     1121            if (it->second.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
     1122                UpdateTilesUnderShape(it->second, grid, maxExpand);
     1123        }
     1124
     1125        // Reset tiles under removed unit shapes
     1126        for (ShapeHistory<UnitShape>::const_iterator it = m_UnitHistory.get(grid.m_DirtyID); it != m_UnitHistory.end(); ++it)
     1127        {
     1128            if (it->second.flags & (FLAG_BLOCK_PATHFINDING | FLAG_BLOCK_FOUNDATION))
     1129                UpdateTilesUnderShape(it->second, grid, maxExpand);
     1130        }
     1131    }
     1132
     1133    grid.m_DirtyID = m_DirtyID;
     1134
    8101135    // Any tiles outside or very near the edge of the map are impassable
    8111136
    8121137    // WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this