Ticket #3204: territory_neighbours.2.diff

File territory_neighbours.2.diff, 16.8 KB (added by sanderd17, 9 years ago)
  • binaries/data/mods/public/simulation/components/TerritoryDecay.js

     
    3333    if (tileOwner != cmpOwnership.GetOwner())
    3434        return false;
    3535
    36     return cmpTerritoryManager.IsConnected(pos.x, pos.y);
     36    if (cmpTerritoryManager.IsConnected(pos.x, pos.y))
     37        return true;
     38
     39    var neighbours = cmpTerritoryManager.GetNeighbours(pos.x, pos.y, true);
     40    warn(uneval(neighbours));
     41    var neighbours = cmpTerritoryManager.GetNeighbours(pos.x, pos.y, false);
     42    warn(uneval(neighbours));
     43    return false;
    3744};
    3845
    3946TerritoryDecay.prototype.IsDecaying = function()
  • source/simulation2/components/CCmpTerritoryManager.cpp

     
    224224    }
    225225
    226226    virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z);
     227    virtual std::vector<player_id_t> GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected);
    227228    virtual bool IsConnected(entity_pos_t x, entity_pos_t z);
    228229
    229230    // To support lazy updates of territory render data,
     
    252253
    253254    void CalculateTerritories();
    254255
     256    u32 GetNeighbourBits(u16 i, u16 j, bool filterConnected);
     257
    255258    /**
    256259     * Updates @p grid based on the obstruction shapes of all entities with
    257260     * a TerritoryInfluence component. Grid cells are 0 if no influence,
     
    280283
    281284typedef PriorityQueueHeap<std::pair<u16, u16>, u32, std::greater<u32> > OpenQueue;
    282285
    283 static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal,
    284         Grid<u32>& grid, OpenQueue& queue, const Grid<u8>& costGrid)
    285 {
    286     u32 dg = falloff * costGrid.get(i, j);
    287     if (diagonal)
    288         dg = (dg * 362) / 256;
    289 
    290     // Stop if new cost g=pg-dg is not better than previous value for that tile
    291     // (arranged to avoid underflow if pg < dg)
    292     if (pg <= grid.get(i, j) + dg)
    293         return;
    294 
    295     u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor
    296 
    297     grid.set(i, j, g);
    298     OpenQueue::Item tile = { std::make_pair(i, j), g };
    299     queue.push(tile);
    300 }
    301 
    302286static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles, u32 falloff)
    303287{
    304288    u16 tilesW = grid.m_W;
     
    308292    {
    309293        OpenQueue::Item tile = openTiles.pop();
    310294
    311         // Process neighbours (if they're not off the edge of the map)
    312         u16 x = tile.id.first;
    313         u16 z = tile.id.second;
    314         if (x > 0)
    315             ProcessNeighbour(falloff, (u16)(x-1), z, tile.rank, false, grid, openTiles, costGrid);
    316         if (x < tilesW-1)
    317             ProcessNeighbour(falloff, (u16)(x+1), z, tile.rank, false, grid, openTiles, costGrid);
    318         if (z > 0)
    319             ProcessNeighbour(falloff, x, (u16)(z-1), tile.rank, false, grid, openTiles, costGrid);
    320         if (z < tilesH-1)
    321             ProcessNeighbour(falloff, x, (u16)(z+1), tile.rank, false, grid, openTiles, costGrid);
    322         if (x > 0 && z > 0)
    323             ProcessNeighbour(falloff, (u16)(x-1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);
    324         if (x > 0 && z < tilesH-1)
    325             ProcessNeighbour(falloff, (u16)(x-1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);
    326         if (x < tilesW-1 && z > 0)
    327             ProcessNeighbour(falloff, (u16)(x+1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);
    328         if (x < tilesW-1 && z < tilesH-1)
    329             ProcessNeighbour(falloff, (u16)(x+1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);
     295        // Process neighbours
     296        int x = tile.id.first;
     297        int z = tile.id.second;
     298        u32 pg = tile.rank;
     299        for (int i = -1; i < 2; ++i)
     300        {
     301            int ti = x + i;
     302            if (ti < 0 || ti >= tilesW)
     303                continue;
     304            for (int j = -1; j < 2; ++j)
     305            {
     306                if (i == 0 && j == 0)
     307                    continue;
     308                int tj = z + j;
     309                if (tj < 0 || tj >= tilesH)
     310                    continue;
     311
     312                u32 dg = falloff * costGrid.get(ti, tj);
     313                if (i * j != 0) // diagonal tile
     314                    dg = (dg * 362) / 256;
     315
     316                // Stop if new cost g=pg-dg is not better than previous value for that tile
     317                // (arranged to avoid underflow if pg < dg)
     318                if (pg <= grid.get(ti, tj) + dg)
     319                    continue;
     320
     321                u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor
     322
     323                grid.set(ti, tj, g);
     324                OpenQueue::Item tile = { std::make_pair(ti, tj), g };
     325                openTiles.push(tile);
     326            }
     327        }
    330328    }
    331329}
    332330
     
    382380    // Split influence entities into per-player lists, ignoring any with invalid properties
    383381    std::map<player_id_t, std::vector<entity_id_t> > influenceEntities;
    384382    std::vector<entity_id_t> rootInfluenceEntities;
    385     for (CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it)
     383    for (auto& it : influences)
    386384    {
    387385        // Ignore any with no weight or radius (to avoid divide-by-zero later)
    388         ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it->second);
     386        ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it.second);
    389387        if (cmpTerritoryInfluence->GetWeight() == 0 || cmpTerritoryInfluence->GetRadius() == 0)
    390388            continue;
    391389
    392         CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it->first);
     390        CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it.first);
    393391        if (!cmpOwnership)
    394392            continue;
    395393
     
    403401            continue;
    404402
    405403        // Ignore if invalid position
    406         CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it->first);
     404        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it.first);
    407405        if (!cmpPosition || !cmpPosition->IsInWorld())
    408406            continue;
    409407
    410         influenceEntities[owner].push_back(it->first);
     408        influenceEntities[owner].push_back(it.first);
    411409
    412410        if (cmpTerritoryInfluence->IsRoot())
    413             rootInfluenceEntities.push_back(it->first);
     411            rootInfluenceEntities.push_back(it.first);
    414412    }
    415413
    416414    // For each player, store the sum of influences on each tile
     
    418416    // TODO: this is a large waste of memory; we don't really need to store
    419417    // all the intermediate grids
    420418
    421     for (std::map<player_id_t, std::vector<entity_id_t> >::iterator it = influenceEntities.begin(); it != influenceEntities.end(); ++it)
     419    for (auto& it : influenceEntities)
    422420    {
    423421        Grid<u32> playerGrid(tilesW, tilesH);
     422        Grid<u32> entityGrid(tilesW, tilesH);
    424423
    425         std::vector<entity_id_t>& ents = it->second;
    426         for (std::vector<entity_id_t>::iterator eit = ents.begin(); eit != ents.end(); ++eit)
     424        std::vector<entity_id_t>& ents = it.second;
     425        // Compute the influence map of the current entity, then add it to the player grid
     426        for (auto& eit : ents)
    427427        {
    428             // Compute the influence map of the current entity, then add it to the player grid
    429 
    430             Grid<u32> entityGrid(tilesW, tilesH);
    431 
    432             CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *eit);
     428            CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), eit);
    433429            CFixedVector2D pos = cmpPosition->GetPosition2D();
    434430            u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);
    435431            u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);
    436432
    437             CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), *eit);
     433            CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), eit);
    438434            u32 weight = cmpTerritoryInfluence->GetWeight();
    439435            u32 radius = cmpTerritoryInfluence->GetRadius() / TERRAIN_TILE_SIZE;
    440             u32 falloff = weight / radius; // earlier check for GetRadius() == 0 prevents divide-by-zero
     436            u32 falloff = weight / radius + 1; // earlier check for GetRadius() == 0 prevents divide-by-zero
    441437
    442             // TODO: we should have some maximum value on weight, to avoid overflow
    443             // when doing all the sums
    444438
    445439            // Initialise the tile under the entity
    446440            entityGrid.set(i, j, weight);
     
    451445            // Expand influences outwards
    452446            FloodFill(entityGrid, influenceGrid, openTiles, falloff);
    453447
    454             // TODO: we should do a sparse grid and only add the non-zero regions, for performance
    455             playerGrid.add(entityGrid);
     448            // TODO: we should have overflow checks when adding, as the number of added
     449            // entities can be arbitrary large
     450            playerGrid.addRegion(entityGrid, i-radius, j-radius, i+radius, j+radius);
     451            entityGrid.reset();
    456452        }
    457453
    458         playerGrids.push_back(std::make_pair(it->first, playerGrid));
     454        playerGrids.emplace_back(it.first, playerGrid);
    459455    }
    460456
    461457    // Set m_Territories to the player ID with the highest influence for each tile
     
    467463            for (size_t k = 0; k < playerGrids.size(); ++k)
    468464            {
    469465                u32 w = playerGrids[k].second.get(i, j);
    470                 if (w > bestWeight)
    471                 {
    472                     player_id_t id = playerGrids[k].first;
    473                     m_Territories->set(i, j, (u8)id);
    474                     bestWeight = w;
    475                 }
     466                if (w <= bestWeight)
     467                    continue;
     468                m_Territories->set(i, j, (u8)playerGrids[k].first);
     469                bestWeight = w;
    476470            }
    477471        }
    478472    }
     
    479473
    480474    // Detect territories connected to a 'root' influence (typically a civ center)
    481475    // belonging to their player, and mark them with the connected flag
    482     for (std::vector<entity_id_t>::iterator it = rootInfluenceEntities.begin(); it != rootInfluenceEntities.end(); ++it)
     476    for (auto& it : rootInfluenceEntities)
    483477    {
    484478        // (These components must be valid else the entities wouldn't be added to this list)
    485         CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), *it);
    486         CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *it);
     479        CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it);
     480        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it);
    487481
    488482        CFixedVector2D pos = cmpPosition->GetPosition2D();
    489483        u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);
     
    499493
    500494        Grid<u8>& grid = *m_Territories;
    501495
    502         u16 maxi = (u16)(grid.m_W-1);
    503         u16 maxj = (u16)(grid.m_H-1);
    504 
    505496        std::vector<std::pair<u16, u16> > tileStack;
    506497
    507 #define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, owner | TERRITORY_CONNECTED_MASK); tileStack.push_back(std::make_pair(i, j)); )
    508 
    509         MARK_AND_PUSH(i, j);
    510498        while (!tileStack.empty())
    511499        {
    512             int ti = tileStack.back().first;
    513             int tj = tileStack.back().second;
     500            int x = tileStack.back().first;
     501            int z = tileStack.back().second;
    514502            tileStack.pop_back();
     503            for (int i = -1; i < 2; ++i)
     504            {
     505                int ti = x + i;
     506                if (ti < 0 || ti >= tilesW) // check grid bounds
     507                    continue;
     508                for (int j = -1; j < 2; ++j)
     509                {
     510                    if (i == 0 && j == 0) // don't process the same tile
     511                        continue;
     512                    int tj = z + j;
     513                    if (tj < 0 || tj >= tilesH) // check grid bounds
     514                        continue;
     515                    if (!grid.get(ti, tj) == owner)
     516                        continue;
     517                    grid.set(ti, tj, owner | TERRITORY_CONNECTED_MASK);
     518                    tileStack.emplace_back(ti, tj);
     519                }
     520            }
     521        }
     522    }
     523}
    515524
    516             if (ti > 0 && grid.get(ti-1, tj) == owner)
    517                 MARK_AND_PUSH(ti-1, tj);
    518             if (ti < maxi && grid.get(ti+1, tj) == owner)
    519                 MARK_AND_PUSH(ti+1, tj);
    520             if (tj > 0 && grid.get(ti, tj-1) == owner)
    521                 MARK_AND_PUSH(ti, tj-1);
    522             if (tj < maxj && grid.get(ti, tj+1) == owner)
    523                 MARK_AND_PUSH(ti, tj+1);
     525/**
     526 * Get a binary number representing the neighbours
     527 * of the territory of tile (i,j)
     528 * Every bit represents a player being a neighbour or not.
     529 *
     530 * Set the filterConnected to true to only return neighbours with connected
     531 * territory
     532 */
     533u32 CCmpTerritoryManager::GetNeighbourBits(u16 i, u16 j, bool filterConnected)
     534{
     535    if (!m_Territories)
     536        CalculateTerritories();
    524537
    525             if (ti > 0 && tj > 0 && grid.get(ti-1, tj-1) == owner)
    526                 MARK_AND_PUSH(ti-1, tj-1);
    527             if (ti > 0 && tj < maxj && grid.get(ti-1, tj+1) == owner)
    528                 MARK_AND_PUSH(ti-1, tj+1);
    529             if (ti < maxi && tj > 0 && grid.get(ti+1, tj-1) == owner)
    530                 MARK_AND_PUSH(ti+1, tj-1);
    531             if (ti < maxi && tj < maxj && grid.get(ti+1, tj+1) == owner)
    532                 MARK_AND_PUSH(ti+1, tj+1);
     538    player_id_t thisOwner = m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
     539
     540    u16 tilesW = m_Territories->m_W;
     541    u16 tilesH = m_Territories->m_H;
     542
     543    // use a flood-fill algorithm that fills up to the borders and remembers the owners
     544    Grid<bool>* markerGrid = new Grid<bool>(tilesW, tilesH);
     545    u32 neighbourBits = 0;
     546    std::vector<std::pair<u16, u16> > tileStack;
     547    tileStack.emplace_back(i, j);
     548    markerGrid->set(i, j, true);
     549    while (!tileStack.empty())
     550    {
     551        int x = tileStack.back().first;
     552        int z = tileStack.back().second;
     553        tileStack.pop_back();
     554        for (int i = -1; i < 2; ++i)
     555        {
     556            int ti = x + i;
     557            if (ti < 0 || ti >= tilesW)
     558                continue;
     559            for (int j = -1; j < 2; ++j)
     560            {
     561                if (i == 0 && j == 0)
     562                    continue;
     563                int tj = z + j;
     564                if (tj < 0 || tj >= tilesH)
     565                    continue;
     566
     567                if (markerGrid->get(ti, tj))
     568                    continue;
     569                player_id_t owner = m_Territories->get(ti, tj) & TERRITORY_PLAYER_MASK;
     570                if (owner == thisOwner)
     571                {
     572                    // expand the tile
     573                    tileStack.emplace_back(ti, tj);
     574                    markerGrid->set(ti, tj, true);
     575                }
     576                else if (owner == 0 || !filterConnected)
     577                    neighbourBits |= (1 << owner); // add gaia or unconnected player to the neighbours
     578                else if ((m_Territories->get(ti, tj) & TERRITORY_CONNECTED_MASK) != 0)
     579                    neighbourBits |= (1 << owner); // add connected player to the neighbours
     580            }
    533581        }
    534 
    535 #undef MARK_AND_PUSH
    536582    }
     583    return neighbourBits;
    537584}
    538585
    539586/**
     
    559606
    560607void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid)
    561608{
    562     for (CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it)
     609    for (auto& it : infls)
    563610    {
    564         ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it->second);
     611        ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it.second);
    565612
    566613        i32 cost = cmpTerritoryInfluence->GetCost();
    567614        if (cost == -1)
    568615            continue;
    569616
    570         CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), it->first);
     617        CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), it.first);
    571618        if (!cmpObstruction)
    572619            continue;
    573620
     
    722769    return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
    723770}
    724771
     772std::vector<player_id_t> CCmpTerritoryManager::GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected)
     773{
     774    if (!m_Territories)
     775        CalculateTerritories();
     776    u16 i, j;
     777    NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     778    u32 neighbourBits = GetNeighbourBits(i, j, filterConnected);
     779    std::vector<player_id_t> neighbours;
     780    player_id_t p = 0;
     781    while (neighbourBits)
     782    {
     783        if (neighbourBits & 1)
     784            neighbours.push_back(p);
     785        ++p;
     786        neighbourBits >>= 1;
     787    }
     788    return neighbours;
     789}
     790
    725791bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z)
    726792{
    727793    u16 i, j;
  • source/simulation2/components/ICmpTerritoryManager.cpp

     
    2323
    2424BEGIN_INTERFACE_WRAPPER(TerritoryManager)
    2525DEFINE_INTERFACE_METHOD_2("GetOwner", player_id_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t)
     26DEFINE_INTERFACE_METHOD_3("GetNeighbours", std::vector<player_id_t>, ICmpTerritoryManager, GetNeighbours, entity_pos_t, entity_pos_t, bool)
    2627DEFINE_INTERFACE_METHOD_2("IsConnected", bool, ICmpTerritoryManager, IsConnected, entity_pos_t, entity_pos_t)
    2728END_INTERFACE_WRAPPER(TerritoryManager)
  • source/simulation2/components/ICmpTerritoryManager.h

     
    4747    virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z) = 0;
    4848
    4949    /**
     50     * Get a list of neighbours of this area. Only return the neighbours that are also
     51     * connected on their own (see the IsConnected method).
     52     */
     53    virtual std::vector<player_id_t> GetNeighbours(entity_pos_t x, entity_pos_t z, bool filterConnected) = 0;
     54
     55    /**
    5056     * Get whether territory at given position is connected to a root object
    5157     * (civ center etc) owned by that territory's player.
    5258     */
  • source/simulation2/helpers/Grid.h

     
    9292            m_Data[i] += g.m_Data[i];
    9393    }
    9494
     95    // Add a region of a grid to this grid
     96    void addRegion(const Grid& g, int minI, int minJ, int maxI, int maxJ)
     97    {
     98#if GRID_BOUNDS_DEBUG
     99        ENSURE(g.m_W == m_W && g.m_H == m_H);
     100        ENSURE(minI <= maxI && minJ <= maxJ);
     101#endif
     102        minI = std::min(std::max(minI, 0), (int)g.m_W);
     103        maxI = std::min(std::max(maxI, 0), (int)g.m_W);
     104        minJ = std::min(std::max(minJ, 0), (int)g.m_H);
     105        maxJ = std::min(std::max(maxJ, 0), (int)g.m_H);
     106        for (int j = minJ; j <= maxJ; ++j)
     107        {
     108            for (int i = minI; i <= maxI; ++i)
     109            {
     110                m_Data[j*m_W + i] += g.m_Data[j*m_W + i];
     111            }
     112        }
     113    }
     114
    95115    void set(int i, int j, const T& value)
    96116    {
    97117#if GRID_BOUNDS_DEBUG