Ticket #3204: territory_neighbours.2.diff
File territory_neighbours.2.diff, 16.8 KB (added by , 9 years ago) |
---|
-
binaries/data/mods/public/simulation/components/TerritoryDecay.js
33 33 if (tileOwner != cmpOwnership.GetOwner()) 34 34 return false; 35 35 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; 37 44 }; 38 45 39 46 TerritoryDecay.prototype.IsDecaying = function() -
source/simulation2/components/CCmpTerritoryManager.cpp
224 224 } 225 225 226 226 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); 227 228 virtual bool IsConnected(entity_pos_t x, entity_pos_t z); 228 229 229 230 // To support lazy updates of territory render data, … … 252 253 253 254 void CalculateTerritories(); 254 255 256 u32 GetNeighbourBits(u16 i, u16 j, bool filterConnected); 257 255 258 /** 256 259 * Updates @p grid based on the obstruction shapes of all entities with 257 260 * a TerritoryInfluence component. Grid cells are 0 if no influence, … … 280 283 281 284 typedef PriorityQueueHeap<std::pair<u16, u16>, u32, std::greater<u32> > OpenQueue; 282 285 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 tile291 // (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 predecessor296 297 grid.set(i, j, g);298 OpenQueue::Item tile = { std::make_pair(i, j), g };299 queue.push(tile);300 }301 302 286 static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles, u32 falloff) 303 287 { 304 288 u16 tilesW = grid.m_W; … … 308 292 { 309 293 OpenQueue::Item tile = openTiles.pop(); 310 294 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 } 330 328 } 331 329 } 332 330 … … 382 380 // Split influence entities into per-player lists, ignoring any with invalid properties 383 381 std::map<player_id_t, std::vector<entity_id_t> > influenceEntities; 384 382 std::vector<entity_id_t> rootInfluenceEntities; 385 for ( CComponentManager::InterfaceList::iterator it = influences.begin(); it != influences.end(); ++it)383 for (auto& it : influences) 386 384 { 387 385 // 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); 389 387 if (cmpTerritoryInfluence->GetWeight() == 0 || cmpTerritoryInfluence->GetRadius() == 0) 390 388 continue; 391 389 392 CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it ->first);390 CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), it.first); 393 391 if (!cmpOwnership) 394 392 continue; 395 393 … … 403 401 continue; 404 402 405 403 // Ignore if invalid position 406 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it ->first);404 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), it.first); 407 405 if (!cmpPosition || !cmpPosition->IsInWorld()) 408 406 continue; 409 407 410 influenceEntities[owner].push_back(it ->first);408 influenceEntities[owner].push_back(it.first); 411 409 412 410 if (cmpTerritoryInfluence->IsRoot()) 413 rootInfluenceEntities.push_back(it ->first);411 rootInfluenceEntities.push_back(it.first); 414 412 } 415 413 416 414 // For each player, store the sum of influences on each tile … … 418 416 // TODO: this is a large waste of memory; we don't really need to store 419 417 // all the intermediate grids 420 418 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) 422 420 { 423 421 Grid<u32> playerGrid(tilesW, tilesH); 422 Grid<u32> entityGrid(tilesW, tilesH); 424 423 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) 427 427 { 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); 433 429 CFixedVector2D pos = cmpPosition->GetPosition2D(); 434 430 u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1); 435 431 u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1); 436 432 437 CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), *eit);433 CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), eit); 438 434 u32 weight = cmpTerritoryInfluence->GetWeight(); 439 435 u32 radius = cmpTerritoryInfluence->GetRadius() / TERRAIN_TILE_SIZE; 440 u32 falloff = weight / radius ; // earlier check for GetRadius() == 0 prevents divide-by-zero436 u32 falloff = weight / radius + 1; // earlier check for GetRadius() == 0 prevents divide-by-zero 441 437 442 // TODO: we should have some maximum value on weight, to avoid overflow443 // when doing all the sums444 438 445 439 // Initialise the tile under the entity 446 440 entityGrid.set(i, j, weight); … … 451 445 // Expand influences outwards 452 446 FloodFill(entityGrid, influenceGrid, openTiles, falloff); 453 447 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(); 456 452 } 457 453 458 playerGrids. push_back(std::make_pair(it->first, playerGrid));454 playerGrids.emplace_back(it.first, playerGrid); 459 455 } 460 456 461 457 // Set m_Territories to the player ID with the highest influence for each tile … … 467 463 for (size_t k = 0; k < playerGrids.size(); ++k) 468 464 { 469 465 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; 476 470 } 477 471 } 478 472 } … … 479 473 480 474 // Detect territories connected to a 'root' influence (typically a civ center) 481 475 // 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) 483 477 { 484 478 // (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); 487 481 488 482 CFixedVector2D pos = cmpPosition->GetPosition2D(); 489 483 u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1); … … 499 493 500 494 Grid<u8>& grid = *m_Territories; 501 495 502 u16 maxi = (u16)(grid.m_W-1);503 u16 maxj = (u16)(grid.m_H-1);504 505 496 std::vector<std::pair<u16, u16> > tileStack; 506 497 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);510 498 while (!tileStack.empty()) 511 499 { 512 int ti= tileStack.back().first;513 int tj= tileStack.back().second;500 int x = tileStack.back().first; 501 int z = tileStack.back().second; 514 502 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 } 515 524 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 */ 533 u32 CCmpTerritoryManager::GetNeighbourBits(u16 i, u16 j, bool filterConnected) 534 { 535 if (!m_Territories) 536 CalculateTerritories(); 524 537 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 } 533 581 } 534 535 #undef MARK_AND_PUSH536 582 } 583 return neighbourBits; 537 584 } 538 585 539 586 /** … … 559 606 560 607 void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid) 561 608 { 562 for ( CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it)609 for (auto& it : infls) 563 610 { 564 ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it ->second);611 ICmpTerritoryInfluence* cmpTerritoryInfluence = static_cast<ICmpTerritoryInfluence*>(it.second); 565 612 566 613 i32 cost = cmpTerritoryInfluence->GetCost(); 567 614 if (cost == -1) 568 615 continue; 569 616 570 CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), it ->first);617 CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), it.first); 571 618 if (!cmpObstruction) 572 619 continue; 573 620 … … 722 769 return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK; 723 770 } 724 771 772 std::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 725 791 bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z) 726 792 { 727 793 u16 i, j; -
source/simulation2/components/ICmpTerritoryManager.cpp
23 23 24 24 BEGIN_INTERFACE_WRAPPER(TerritoryManager) 25 25 DEFINE_INTERFACE_METHOD_2("GetOwner", player_id_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t) 26 DEFINE_INTERFACE_METHOD_3("GetNeighbours", std::vector<player_id_t>, ICmpTerritoryManager, GetNeighbours, entity_pos_t, entity_pos_t, bool) 26 27 DEFINE_INTERFACE_METHOD_2("IsConnected", bool, ICmpTerritoryManager, IsConnected, entity_pos_t, entity_pos_t) 27 28 END_INTERFACE_WRAPPER(TerritoryManager) -
source/simulation2/components/ICmpTerritoryManager.h
47 47 virtual player_id_t GetOwner(entity_pos_t x, entity_pos_t z) = 0; 48 48 49 49 /** 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 /** 50 56 * Get whether territory at given position is connected to a root object 51 57 * (civ center etc) owned by that territory's player. 52 58 */ -
source/simulation2/helpers/Grid.h
92 92 m_Data[i] += g.m_Data[i]; 93 93 } 94 94 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 95 115 void set(int i, int j, const T& value) 96 116 { 97 117 #if GRID_BOUNDS_DEBUG