Ticket #1756: pathperf7.patch
File pathperf7.patch, 269.1 KB (added by , 11 years ago) |
---|
-
new file inaries/data/mods/public/art/textures/terrain/types/special/grid_subdiv.xml
diff --git a/binaries/data/mods/public/art/textures/terrain/types/special/grid_subdiv.xml b/binaries/data/mods/public/art/textures/terrain/types/special/grid_subdiv.xml new file mode 100644 index 0000000..c597566
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 3 <Terrains> 4 <Terrain angle="0.0" size="4.0"/> 5 </Terrains> 6 <?xml version="1.0" encoding="utf-8"?> 7 8 <Terrains> 9 <Terrain angle="0.0" size="4.0"/> 10 </Terrains> -
new file inaries/data/mods/public/art/textures/terrain/types/special/textures.xml
diff --git a/binaries/data/mods/public/art/textures/terrain/types/special/textures.xml b/binaries/data/mods/public/art/textures/terrain/types/special/textures.xml new file mode 100644 index 0000000..77fc102
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <Textures> 3 <File pattern="grid_subdiv.png" format="rgba"/> 4 </Textures> 5 <?xml version="1.0" encoding="utf-8"?> 6 <Textures> 7 <File pattern="grid_subdiv.png" format="rgba"/> 8 </Textures> -
binaries/data/mods/public/simulation/data/pathfinder.xml
diff --git a/binaries/data/mods/public/simulation/data/pathfinder.xml b/binaries/data/mods/public/simulation/data/pathfinder.xml index 47eb0eb..8ca761c 100644
a b 6 6 7 7 <PassabilityClasses> 8 8 9 <!-- Unit pathfinding classes: -->10 9 <unrestricted/> 10 11 <!-- Unit pathfinding classes: --> 11 12 <default> 12 13 <MaxWaterDepth>2</MaxWaterDepth> 13 14 <MaxTerrainSlope>1.0</MaxTerrainSlope> 15 <Clearance>1.0</Clearance> 14 16 </default> 17 <siege-large> 18 <MaxWaterDepth>2</MaxWaterDepth> 19 <MaxTerrainSlope>1.0</MaxTerrainSlope> 20 <Clearance>4.0</Clearance> 21 </siege-large> 22 <default-no-clearance> 23 <MaxWaterDepth>2</MaxWaterDepth> 24 <MaxTerrainSlope>1.0</MaxTerrainSlope> 25 <Clearance>0.0</Clearance> 26 </default-no-clearance> 15 27 <ship> 16 28 <MinWaterDepth>1</MinWaterDepth> 29 <Clearance>12.0</Clearance> 17 30 </ship> 31 <ship-small> 32 <MinWaterDepth>1</MinWaterDepth> 33 <Clearance>4.0</Clearance> 34 </ship-small> 35 18 36 <!-- Building construction classes: 19 37 * Land is used for most buildings, which must be away 20 38 from water and not on cliffs or mountains. 21 39 * Shore is used for docks, which must be near water and 22 40 land, yet shallow enough for builders to approach. 41 42 (These should not use <Clearance>, because the foundation placement 43 checker already does precise obstruction-shape-based checking.) 23 44 --> 24 45 <building-land> 25 46 <MaxWaterDepth>0</MaxWaterDepth> 26 <MinShoreDistance> 1.0</MinShoreDistance>47 <MinShoreDistance>4.0</MinShoreDistance> 27 48 <MaxTerrainSlope>1.0</MaxTerrainSlope> 28 49 </building-land> 29 50 <building-shore> 30 <MaxShoreDistance> 2.0</MaxShoreDistance>51 <MaxShoreDistance>8.0</MaxShoreDistance> 31 52 <MaxTerrainSlope>1.25</MaxTerrainSlope> 32 53 </building-shore> 33 54 55 <!-- Territory growth influences: --> 56 <territory> 57 <MaxWaterDepth>2</MaxWaterDepth> 58 <MaxTerrainSlope>1.0</MaxTerrainSlope> 59 </territory> 60 34 61 </PassabilityClasses> 35 36 <!--37 Warning: Movement costs are a subtle tradeoff between38 pathfinding accuracy and computation cost. Be extremely39 careful if you change them.40 (Speeds are safer to change, but ought to be kept roughly41 in sync with costs.)42 -->43 <MovementClasses>44 <default Speed="1.0" Cost="1.08"/>45 <city Speed="1.0" Cost="1.0">46 <UnitClasses>47 <infantry Speed="1.4" Cost="0.6"/>48 </UnitClasses>49 </city>50 </MovementClasses>51 62 </Pathfinder> -
binaries/data/mods/public/simulation/templates/special/formation.xml
diff --git a/binaries/data/mods/public/simulation/templates/special/formation.xml b/binaries/data/mods/public/simulation/templates/special/formation.xml index 63775bb..def59e2 100644
a b 22 22 <FormationController>true</FormationController> 23 23 <WalkSpeed>1.0</WalkSpeed> 24 24 <PassabilityClass>default</PassabilityClass> 25 <CostClass>default</CostClass>26 25 </UnitMotion> 27 26 </Entity> -
binaries/data/mods/public/simulation/templates/template_structure.xml
diff --git a/binaries/data/mods/public/simulation/templates/template_structure.xml b/binaries/data/mods/public/simulation/templates/template_structure.xml index bfe0e5e..d4cc256 100644
a b 68 68 <LineDashColour r="158" g="11" b="15"/> 69 69 <LineStartCap>square</LineStartCap> 70 70 <LineEndCap>round</LineEndCap> 71 <LineCostClass>default</LineCostClass>72 71 <LinePassabilityClass>default</LinePassabilityClass> 73 72 </RallyPointRenderer> 74 73 <Selectable> -
binaries/data/mods/public/simulation/templates/template_unit.xml
diff --git a/binaries/data/mods/public/simulation/templates/template_unit.xml b/binaries/data/mods/public/simulation/templates/template_unit.xml index 18f582b..14b39e8 100644
a b 109 109 <DecayTime>0.2</DecayTime> 110 110 </Run> 111 111 <PassabilityClass>default</PassabilityClass> 112 <CostClass>default</CostClass>113 112 </UnitMotion> 114 113 <Vision> 115 114 <Range>10</Range> -
binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml index b20e3ab..9afd836 100644
a b 118 118 <Run> 119 119 <Speed>18.75</Speed> 120 120 </Run> 121 <CostClass>infantry</CostClass>122 121 </UnitMotion> 123 122 <Vision> 124 123 <Range>60</Range> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml index afee030..fe13a95 100644
a b 43 43 </Rates> 44 44 </ResourceGatherer> 45 45 <UnitMotion> 46 <PassabilityClass>ship-small</PassabilityClass> 46 47 <WalkSpeed>8.5</WalkSpeed> 47 48 </UnitMotion> 48 49 <Vision> -
binaries/data/mods/public/simulation/templates/units/hele_mechanical_siege_tower.xml
diff --git a/binaries/data/mods/public/simulation/templates/units/hele_mechanical_siege_tower.xml b/binaries/data/mods/public/simulation/templates/units/hele_mechanical_siege_tower.xml index b97c3fe..c5172b4 100644
a b Garrison up to 20 units inside for massive firepower.</Tooltip> 8 8 <History>When Demetrius Poliorcetes besieged Salamis, in Cyprus, he instructed that a machine be constructed, which he called "the taker of cities." Its form was that of a square tower, each side 90 cubits high and 45 wide. It rested on four wheels, each eight cubits high. It was divided into nine stories, the lower of which contained machines for throwing great stones, the middle large catapults for throwing spears, and the highest, other machines for throwing smaller stones, together with smaller catapults. It was manned with 200 soldiers, besides those that moved it by pushing the parallel beams at the bottom.</History> 9 9 <Icon>units/hele_mechanical_siege_tower.png</Icon> 10 10 </Identity> 11 <Obstruction> 12 <Unit radius="3.75"/> 13 </Obstruction> 14 <UnitMotion> 15 <PassabilityClass>siege-large</PassabilityClass> 16 </UnitMotion> 11 17 <Vision> 12 18 <Range>88</Range> 13 19 </Vision> -
source/graphics/Terrain.cpp
diff --git a/source/graphics/Terrain.cpp b/source/graphics/Terrain.cpp index d46ccb8..8413559 100644
a b fixed CTerrain::GetSlopeFixed(ssize_t i, ssize_t j) const 338 338 return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE; 339 339 } 340 340 341 fixed CTerrain::GetExactSlopeFixed(fixed x, fixed z) const 342 { 343 // Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1) 344 const ssize_t xi = clamp((ssize_t)(x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), (ssize_t)0, m_MapSize-2); 345 const ssize_t zi = clamp((ssize_t)(z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), (ssize_t)0, m_MapSize-2); 346 347 const fixed one = fixed::FromInt(1); 348 349 const fixed xf = clamp((x / (int)TERRAIN_TILE_SIZE) - fixed::FromInt(xi), fixed::Zero(), one); 350 const fixed zf = clamp((z / (int)TERRAIN_TILE_SIZE) - fixed::FromInt(zi), fixed::Zero(), one); 351 352 u16 h00 = m_Heightmap[zi*m_MapSize + xi]; 353 u16 h01 = m_Heightmap[(zi+1)*m_MapSize + xi]; 354 u16 h10 = m_Heightmap[zi*m_MapSize + (xi+1)]; 355 u16 h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)]; 356 357 u16 delta; 358 if (GetTriangulationDir(xi, zi)) 359 { 360 if (xf + zf <= one) 361 { 362 // Lower-left triangle (don't use h11) 363 delta = std::max(std::max(h00, h01), h10) - 364 std::min(std::min(h00, h01), h10); 365 } 366 else 367 { 368 // Upper-right triangle (don't use h00) 369 delta = std::max(std::max(h01, h10), h11) - 370 std::min(std::min(h01, h10), h11); 371 } 372 } 373 else 374 { 375 if (xf <= zf) 376 { 377 // Upper-left triangle (don't use h10) 378 delta = std::max(std::max(h00, h01), h11) - 379 std::min(std::min(h00, h01), h11); 380 } 381 else 382 { 383 // Lower-right triangle (don't use h01) 384 delta = std::max(std::max(h00, h10), h11) - 385 std::min(std::min(h00, h10), h11); 386 } 387 } 388 389 // Compute fractional slope (being careful to avoid intermediate overflows) 390 return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE; 391 } 392 341 393 float CTerrain::GetFilteredGroundLevel(float x, float z, float radius) const 342 394 { 343 395 // convert to [0,1] interval -
source/graphics/Terrain.h
diff --git a/source/graphics/Terrain.h b/source/graphics/Terrain.h index 7019c94..93f1728 100644
a b public: 87 87 fixed GetExactGroundLevelFixed(fixed x, fixed z) const; 88 88 float GetFilteredGroundLevel(float x, float z, float radius) const; 89 89 90 // get the approximate slope (0 = horizontal, 0.5 = 30 degrees, 1.0 = 45 degrees, etc) 90 // get the approximate slope of a tile 91 // (0 = horizontal, 0.5 = 30 degrees, 1.0 = 45 degrees, etc) 91 92 fixed GetSlopeFixed(ssize_t i, ssize_t j) const; 92 93 94 // get the precise slope of a point, accounting for triangulation direction 95 fixed GetExactSlopeFixed(fixed x, fixed z) const; 96 93 97 // Returns true if the triangulation diagonal for tile (i, j) 94 98 // should be in the direction (1,-1); false if it should be (1,1) 95 99 bool GetTriangulationDir(ssize_t i, ssize_t j) const; -
source/graphics/TerritoryBoundary.cpp
diff --git a/source/graphics/TerritoryBoundary.cpp b/source/graphics/TerritoryBoundary.cpp index da0fcd2..070a98e 100644
a b 21 21 #include <algorithm> // for reverse 22 22 23 23 #include "graphics/Terrain.h" 24 #include "simulation2/components/ICmpObstructionManager.h" 24 25 #include "simulation2/components/ICmpTerritoryManager.h" 25 26 26 27 std::vector<STerritoryBoundary> CTerritoryBoundaryCalculator::ComputeBoundaries(const Grid<u8>* territory) … … std::vector<STerritoryBoundary> CTerritoryBoundaryCalculator::ComputeBoundaries( 123 124 u16 maxi = (u16)(grid.m_W-1); 124 125 u16 maxj = (u16)(grid.m_H-1); 125 126 127 // Size of a territory tile in metres 128 float territoryTileSize = (ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE).ToFloat(); 129 126 130 while (true) 127 131 { 128 points.push_back((CVector2D(ci, cj) + edgeOffsets[cdir]) * TERRAIN_TILE_SIZE);132 points.push_back((CVector2D(ci, cj) + edgeOffsets[cdir]) * territoryTileSize); 129 133 130 134 // Given that we're on an edge on a continuous boundary and aiming anticlockwise, 131 135 // we can either carry on straight or turn left or turn right, so examine each -
source/graphics/TerritoryTexture.cpp
diff --git a/source/graphics/TerritoryTexture.cpp b/source/graphics/TerritoryTexture.cpp index 2c303ca..ba0f20c 100644
a b 25 25 #include "ps/Profile.h" 26 26 #include "renderer/Renderer.h" 27 27 #include "simulation2/Simulation2.h" 28 #include "simulation2/components/ICmpObstructionManager.h" 28 29 #include "simulation2/components/ICmpPlayer.h" 29 30 #include "simulation2/components/ICmpPlayerManager.h" 30 31 #include "simulation2/components/ICmpTerrain.h" … … void CTerritoryTexture::ConstructTexture(int unit) 92 93 if (!cmpTerrain) 93 94 return; 94 95 95 m_MapSize = cmpTerrain->GetVerticesPerSide() - 1; 96 // Convert size from terrain tiles to territory tiles 97 m_MapSize = cmpTerrain->GetTilesPerSide() * ICmpObstructionManager::NAVCELLS_PER_TILE / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE; 96 98 97 99 m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize); 98 100 -
source/gui/GUIManager.h
diff --git a/source/gui/GUIManager.h b/source/gui/GUIManager.h index 0edfd14..828e4c0 100644
a b private: 145 145 CStrW name; 146 146 boost::unordered_set<VfsPath> inputs; // for hotloading 147 147 148 JSContext* cx;149 148 CScriptValRooted initData; // data to be passed to the init() function 150 149 151 150 shared_ptr<CGUI> gui; // the actual GUI page -
source/maths/Brush.cpp
diff --git a/source/maths/Brush.cpp b/source/maths/Brush.cpp index 90f27f7..e9a1105 100644
a b void CBrush::Intersect(const CFrustum& frustum, CBrush& result) const 379 379 380 380 ENSURE(prev == &result); 381 381 } 382 382 383 std::vector<CVector3D> CBrush::GetVertices() const 383 384 { 384 385 return m_Vertices; -
source/maths/FixedVector2D.h
diff --git a/source/maths/FixedVector2D.h b/source/maths/FixedVector2D.h index 739469e..445a622 100644
a b public: 79 79 return CFixedVector2D(X*n, Y*n); 80 80 } 81 81 82 /// Scalar division by an integer. Must not have n == 0. 83 CFixedVector2D operator/(int n) const 84 { 85 return CFixedVector2D(X/n, Y/n); 86 } 87 82 88 /** 83 89 * Multiply by a CFixed. Likely to overflow if both numbers are large, 84 90 * so we use an ugly name instead of operator* to make it obvious. -
source/renderer/TerrainOverlay.cpp
diff --git a/source/renderer/TerrainOverlay.cpp b/source/renderer/TerrainOverlay.cpp index 688a6be..37f9efb 100644
a b void TerrainTextureOverlay::RenderAfterWater() 331 331 332 332 g_Renderer.GetTerrainRenderer().RenderTerrainOverlayTexture(matrix); 333 333 } 334 335 SColor4ub TerrainTextureOverlay::GetColor(size_t idx, u8 alpha) const 336 { 337 static u8 colors[][3] = { 338 { 255, 0, 0 }, 339 { 0, 255, 0 }, 340 { 0, 0, 255 }, 341 { 255, 255, 0 }, 342 { 255, 0, 255 }, 343 { 0, 255, 255 }, 344 { 255, 255, 255 }, 345 346 { 127, 0, 0 }, 347 { 0, 127, 0 }, 348 { 0, 0, 127 }, 349 { 127, 127, 0 }, 350 { 127, 0, 127 }, 351 { 0, 127, 127 }, 352 { 127, 127, 127}, 353 354 { 255, 127, 0 }, 355 { 127, 255, 0 }, 356 { 255, 0, 127 }, 357 { 127, 0, 255}, 358 { 0, 255, 127 }, 359 { 0, 127, 255}, 360 { 255, 127, 127}, 361 { 127, 255, 127}, 362 { 127, 127, 255}, 363 364 { 127, 255, 255 }, 365 { 255, 127, 255 }, 366 { 255, 255, 127 }, 367 }; 368 369 size_t c = idx % ARRAY_SIZE(colors); 370 return SColor4ub(colors[c][0], colors[c][1], colors[c][2], alpha); 371 } -
source/renderer/TerrainOverlay.h
diff --git a/source/renderer/TerrainOverlay.h b/source/renderer/TerrainOverlay.h index 5379ba2..bb6be01 100644
a b 26 26 #include "lib/ogl.h" 27 27 28 28 struct CColor; 29 struct SColor4ub; 29 30 class CTerrain; 30 31 class CSimContext; 31 32 … … protected: 186 187 */ 187 188 virtual void BuildTextureRGBA(u8* data, size_t w, size_t h) = 0; 188 189 190 /** 191 * Returns an arbitrary color, for subclasses that want to distinguish 192 * different integers visually. 193 */ 194 SColor4ub GetColor(size_t idx, u8 alpha) const; 195 189 196 private: 190 197 void RenderAfterWater(); 191 198 -
source/simulation2/components/CCmpObstruction.cpp
diff --git a/source/simulation2/components/CCmpObstruction.cpp b/source/simulation2/components/CCmpObstruction.cpp index 4fa0033..ca5eb92 100644
a b public: 551 551 { 552 552 std::vector<entity_id_t> ret; 553 553 554 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());555 if (!cmpPosition)556 return ret; // error557 558 if (!cmpPosition->IsInWorld())559 return ret; // no obstruction560 561 CFixedVector2D pos = cmpPosition->GetPosition2D();562 563 554 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); 564 555 if (!cmpObstructionManager) 565 556 return ret; // error 566 557 567 // required precondition to use SkipControlGroupsRequireFlagObstructionFilter568 if (m_ControlGroup == INVALID_ENTITY)569 {570 LOGERROR(L"[CmpObstruction] Cannot test for unit or structure obstructions; primary control group must be valid");571 return ret;572 }573 574 558 flags_t flags = 0; 575 559 bool invertMatch = false; 576 560 … … public: 593 577 flags = ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_PATHFINDING; 594 578 } 595 579 596 // Ignore collisions within the same control group, or with other shapes that don't match the filter. 597 // Note that, since the control group for each entity defaults to the entity's ID, this is typically 598 // equivalent to only ignoring the entity's own shape and other shapes that don't match the filter. 599 SkipControlGroupsRequireFlagObstructionFilter filter(invertMatch, 600 m_ControlGroup, m_ControlGroup2, flags); 601 602 if (m_Type == STATIC) 603 cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret); 604 else if (m_Type == UNIT) 605 cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, &ret); 606 else 607 cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret); 580 ICmpObstructionManager::ObstructionSquare square; 581 if (!GetObstructionSquare(square)) 582 return ret; // error 583 584 cmpObstructionManager->GetUnitsOnObstruction(square, ret); 585 //if (m_Type == STATIC) 586 // cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret); 587 //else if (m_Type == UNIT) 588 // cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, &ret); 589 //else 590 // cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret); 608 591 609 592 return ret; 610 593 } -
source/simulation2/components/CCmpObstructionManager.cpp
diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp index 2f5eed0..5bccbe7 100644
a b 22 22 23 23 #include "simulation2/MessageTypes.h" 24 24 #include "simulation2/helpers/Geometry.h" 25 #include "simulation2/helpers/Rasterize.h" 25 26 #include "simulation2/helpers/Render.h" 26 27 #include "simulation2/helpers/Spatial.h" 27 28 #include "simulation2/serialization/SerializeTemplates.h" … … public: 435 436 virtual bool TestStaticShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, std::vector<entity_id_t>* out); 436 437 virtual bool TestUnitShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, std::vector<entity_id_t>* out); 437 438 438 virtual bool Rasterise(Grid<u8>& grid);439 virtual void Rasterize(Grid<u16>& grid, entity_pos_t expand, ICmpObstructionManager::flags_t requireMask, u16 setMask); 439 440 virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares); 440 virtual bool FindMostImportantObstruction(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, ObstructionSquare& square);441 virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out); 441 442 442 443 virtual void SetPassabilityCircular(bool enabled) 443 444 { … … private: 505 506 m_DebugOverlayDirty = true; 506 507 } 507 508 508 /** 509 * Test whether a Rasterise()d grid is dirty and needs updating 510 */ 511 template<typename T> 512 bool IsDirty(const Grid<T>& grid) 509 virtual bool NeedUpdate(size_t* dirtyID) 513 510 { 514 return grid.m_DirtyID < m_DirtyID; 511 ENSURE(*dirtyID <= m_DirtyID); 512 if (*dirtyID != m_DirtyID) 513 { 514 *dirtyID = m_DirtyID; 515 return true; 516 } 517 return false; 515 518 } 516 519 517 520 /** … … bool CCmpObstructionManager::TestUnitShape(const IObstructionTestFilter& filter, 703 706 } 704 707 705 708 /** 706 * Compute the tile indexes on the grid nearest to a given point 707 */ 708 static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h) 709 { 710 i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, w-1); 711 j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, h-1); 712 } 713 714 /** 715 * Returns the position of the center of the given tile 709 * Compute the navcell indexes on the grid nearest to a given point 716 710 */ 717 static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)711 static void NearestNavcell(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h) 718 712 { 719 x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);720 z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);713 i = (u16)clamp((x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero(), 0, w-1); 714 j = (u16)clamp((z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero(), 0, h-1); 721 715 } 722 716 723 bool CCmpObstructionManager::Rasterise(Grid<u8>& grid)717 void CCmpObstructionManager::Rasterize(Grid<u16>& grid, entity_pos_t expand, ICmpObstructionManager::flags_t requireMask, u16 setMask) 724 718 { 725 if (!IsDirty(grid)) 726 return false; 719 PROFILE3("Rasterize"); 727 720 728 PROFILE("Rasterise"); 721 // Since m_DirtyID is only updated for pathfinding/foundation blocking shapes, 722 // NeedUpdate+Rasterise will only be accurate for that subset of shapes. 723 // (If we ever want to support rasterizing more shapes, we need to improve 724 // the dirty-detection system too.) 725 ENSURE(!(requireMask & ~(FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION))); 729 726 730 grid.m_DirtyID = m_DirtyID; 727 // Cells are only marked as blocked if the whole cell is strictly inside the shape. 728 // (That ensures the shape's geometric border is always reachable.) 731 729 732 // TODO: this is all hopelessly inefficient 733 // What we should perhaps do is have some kind of quadtree storing Shapes so it's 734 // quick to invalidate and update small numbers of tiles 730 // TODO: it might be nice to rasterize with rounded corners for large 'expand' values. 735 731 736 grid.reset(); 737 738 // For tile-based pathfinding: 739 // Since we only count tiles whose centers are inside the square, 740 // we maybe want to expand the square a bit so we're less likely to think there's 741 // free space between buildings when there isn't. But this is just a random guess 742 // and needs to be tweaked until everything works nicely. 743 //entity_pos_t expandPathfinding = entity_pos_t::FromInt(TERRAIN_TILE_SIZE / 2); 744 // Actually that's bad because units get stuck when the A* pathfinder thinks they're 745 // blocked on all sides, so it's better to underestimate 746 entity_pos_t expandPathfinding = entity_pos_t::FromInt(0); 747 748 // For AI building foundation planning, we want to definitely block all 749 // potentially-obstructed tiles (so we don't blindly build on top of an obstruction), 750 // so we need to expand by at least 1/sqrt(2) of a tile 751 entity_pos_t expandFoundation = (entity_pos_t::FromInt(TERRAIN_TILE_SIZE) * 3) / 4; 732 // (This could be implemented much more efficiently.) 752 733 753 734 for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it) 754 735 { 755 CFixedVector2D center(it->second.x, it->second.z); 756 757 if (it->second.flags & FLAG_BLOCK_PATHFINDING) 736 const StaticShape& shape = it->second; 737 if (shape.flags & requireMask) 758 738 { 759 CFixedVector2D halfSize(it->second.hw + expandPathfinding, it->second.hh + expandPathfinding); 760 CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(it->second.u, it->second.v, halfSize); 761 762 u16 i0, j0, i1, j1; 763 NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H); 764 NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H); 765 for (u16 j = j0; j <= j1; ++j) 766 { 767 for (u16 i = i0; i <= i1; ++i) 768 { 769 entity_pos_t x, z; 770 TileCenter(i, j, x, z); 771 if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, it->second.u, it->second.v, halfSize)) 772 grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_PATHFINDING); 773 } 774 } 775 } 776 777 if (it->second.flags & FLAG_BLOCK_FOUNDATION) 778 { 779 CFixedVector2D halfSize(it->second.hw + expandFoundation, it->second.hh + expandFoundation); 780 CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(it->second.u, it->second.v, halfSize); 781 782 u16 i0, j0, i1, j1; 783 NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H); 784 NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H); 785 for (u16 j = j0; j <= j1; ++j) 739 ObstructionSquare square = { shape.x, shape.z, shape.u, shape.v, shape.hw, shape.hh }; 740 SimRasterize::Spans spans; 741 SimRasterize::RasterizeRectWithClearance(spans, square, expand, ICmpObstructionManager::NAVCELL_SIZE); 742 for (size_t k = 0; k < spans.size(); ++k) 786 743 { 787 for (u16 i = i0; i <= i1; ++i) 744 i16 j = spans[k].j; 745 if (j >= 0 && j <= grid.m_H) 788 746 { 789 entity_pos_t x, z;790 TileCenter(i, j, x, z);791 if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, it->second.u, it->second.v, halfSize))792 grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_FOUNDATION);747 i16 i0 = std::max(spans[k].i0, (i16)0); 748 i16 i1 = std::min(spans[k].i1, (i16)grid.m_W); 749 for (i16 i = i0; i < i1; ++i) 750 grid.set(i, j, grid.get(i, j) | setMask); 793 751 } 794 752 } 795 753 } … … bool CCmpObstructionManager::Rasterise(Grid<u8>& grid) 799 757 { 800 758 CFixedVector2D center(it->second.x, it->second.z); 801 759 802 if (it->second.flags & FLAG_BLOCK_PATHFINDING) 803 { 804 entity_pos_t r = it->second.r + expandPathfinding; 805 806 u16 i0, j0, i1, j1; 807 NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H); 808 NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H); 809 for (u16 j = j0; j <= j1; ++j) 810 for (u16 i = i0; i <= i1; ++i) 811 grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_PATHFINDING); 812 } 813 814 if (it->second.flags & FLAG_BLOCK_FOUNDATION) 760 if (it->second.flags & requireMask) 815 761 { 816 entity_pos_t r = it->second.r + expand Foundation;762 entity_pos_t r = it->second.r + expand; 817 763 818 764 u16 i0, j0, i1, j1; 819 Nearest Tile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);820 Nearest Tile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);821 for (u16 j = j0 ; j <=j1; ++j)822 for (u16 i = i0 ; i <=i1; ++i)823 grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_FOUNDATION);765 NearestNavcell(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H); 766 NearestNavcell(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H); 767 for (u16 j = j0+1; j < j1; ++j) 768 for (u16 i = i0+1; i < i1; ++i) 769 grid.set(i, j, grid.get(i, j) | setMask); 824 770 } 825 771 } 826 827 // Any tiles outside or very near the edge of the map are impassable828 829 // WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this830 const u16 edgeSize = 3; // number of tiles around the edge that will be off-world831 832 u8 edgeFlags = TILE_OBSTRUCTED_PATHFINDING | TILE_OBSTRUCTED_FOUNDATION | TILE_OUTOFBOUNDS;833 834 if (m_PassabilityCircular)835 {836 for (u16 j = 0; j < grid.m_H; ++j)837 {838 for (u16 i = 0; i < grid.m_W; ++i)839 {840 // Based on CCmpRangeManager::LosIsOffWorld841 // but tweaked since it's tile-based instead.842 // (We double all the values so we can handle half-tile coordinates.)843 // This needs to be slightly tighter than the LOS circle,844 // else units might get themselves lost in the SoD around the edge.845 846 ssize_t dist2 = (i*2 + 1 - grid.m_W)*(i*2 + 1 - grid.m_W)847 + (j*2 + 1 - grid.m_H)*(j*2 + 1 - grid.m_H);848 849 if (dist2 >= (grid.m_W - 2*edgeSize) * (grid.m_H - 2*edgeSize))850 grid.set(i, j, edgeFlags);851 }852 }853 }854 else855 {856 u16 i0, j0, i1, j1;857 NearestTile(m_WorldX0, m_WorldZ0, i0, j0, grid.m_W, grid.m_H);858 NearestTile(m_WorldX1, m_WorldZ1, i1, j1, grid.m_W, grid.m_H);859 860 for (u16 j = 0; j < grid.m_H; ++j)861 for (u16 i = 0; i < i0+edgeSize; ++i)862 grid.set(i, j, edgeFlags);863 for (u16 j = 0; j < grid.m_H; ++j)864 for (u16 i = (u16)(i1-edgeSize+1); i < grid.m_W; ++i)865 grid.set(i, j, edgeFlags);866 for (u16 j = 0; j < j0+edgeSize; ++j)867 for (u16 i = (u16)(i0+edgeSize); i < i1-edgeSize+1; ++i)868 grid.set(i, j, edgeFlags);869 for (u16 j = (u16)(j1-edgeSize+1); j < grid.m_H; ++j)870 for (u16 i = (u16)(i0+edgeSize); i < i1-edgeSize+1; ++i)871 grid.set(i, j, edgeFlags);872 }873 874 return true;875 772 } 876 773 877 774 void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) … … void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter 923 820 } 924 821 } 925 822 926 bool CCmpObstructionManager::FindMostImportantObstruction(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, ObstructionSquare& square)823 void CCmpObstructionManager::GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out) 927 824 { 928 std::vector<ObstructionSquare> squares; 929 930 CFixedVector2D center(x, z); 931 932 // First look for obstructions that are covering the exact target point 933 GetObstructionsInRange(filter, x, z, x, z, squares); 934 // Building squares are more important but returned last, so check backwards 935 for (std::vector<ObstructionSquare>::reverse_iterator it = squares.rbegin(); it != squares.rend(); ++it) 936 { 937 CFixedVector2D halfSize(it->hw, it->hh); 938 if (Geometry::PointIsInSquare(CFixedVector2D(it->x, it->z) - center, it->u, it->v, halfSize)) 939 { 940 square = *it; 941 return true; 942 } 943 } 944 945 // Then look for obstructions that cover the target point when expanded by r 946 // (i.e. if the target is not inside an object but closer than we can get to it) 947 948 GetObstructionsInRange(filter, x-r, z-r, x+r, z+r, squares); 949 // Building squares are more important but returned last, so check backwards 950 for (std::vector<ObstructionSquare>::reverse_iterator it = squares.rbegin(); it != squares.rend(); ++it) 951 { 952 CFixedVector2D halfSize(it->hw + r, it->hh + r); 953 if (Geometry::PointIsInSquare(CFixedVector2D(it->x, it->z) - center, it->u, it->v, halfSize)) 954 { 955 square = *it; 956 return true; 957 } 958 } 959 960 return false; 825 PROFILE3("GetUnitsOnObstruction"); 826 827 // Ideally we'd want to find all units s.t. the RasterizeRectWithClearance 828 // of the building's shape with the unit's clearance covers the navcell 829 // the unit is on. 830 // But ObstructionManager doesn't actually know the clearance values 831 // (only the radiuses), so we can't easily do that properly. 832 // Instead, cheat and just ignore the clearance entirely; 833 // this might allow players to build buildings so a unit ends up on 834 // an impassable tile, which is slightly bad and might let players cheat, 835 // so TODO: fix this properly. 836 837 SimRasterize::Spans spans; 838 SimRasterize::RasterizeRectWithClearance(spans, square, fixed::Zero(), ICmpObstructionManager::NAVCELL_SIZE); 839 840 for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it) 841 { 842 // Check whether the unit's center is on a navcell that's in 843 // any of the spans 844 845 u16 i = (it->second.x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToNegInfinity(); 846 u16 j = (it->second.z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToNegInfinity(); 847 848 for (size_t k = 0; k < spans.size(); ++k) 849 { 850 if (j == spans[k].j && spans[k].i0 <= i && i < spans[k].i1) 851 out.push_back(it->second.entity); 852 } 853 } 854 //TODO Should we expand by r here? 961 855 } 962 856 963 857 void CCmpObstructionManager::RenderSubmit(SceneCollector& collector) -
source/simulation2/components/CCmpPathfinder.cpp
diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp index 0df667c..a4097d0 100644
a b 33 33 #include "simulation2/components/ICmpObstructionManager.h" 34 34 #include "simulation2/components/ICmpTerrain.h" 35 35 #include "simulation2/components/ICmpWaterManager.h" 36 #include "simulation2/helpers/Rasterize.h" 36 37 #include "simulation2/serialization/SerializeTemplates.h" 37 38 38 39 // Default cost to move a single tile is a fairly arbitrary number, which should be big … … void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode)) 46 47 { 47 48 m_MapSize = 0; 48 49 m_Grid = NULL; 49 m_ObstructionGrid = NULL;50 m_ObstructionGridDirtyID = 0; 50 51 m_TerrainDirty = true; 51 52 m_NextAsyncTicket = 1; 52 53 53 54 m_DebugOverlay = NULL; 54 55 m_DebugGrid = NULL; 56 m_DebugGridJPS = NULL; 55 57 m_DebugPath = NULL; 56 58 59 PathfinderHierInit(); 60 57 61 m_SameTurnMovesCount = 0; 58 62 59 63 // Since this is used as a system component (not loaded from an entity template), … … void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode)) 86 90 { 87 91 std::string name = it->first; 88 92 ENSURE((int)m_PassClasses.size() <= PASS_CLASS_BITS); 89 pass_class_t mask = (pass_class_t)(1u << (m_PassClasses.size() + 2));93 pass_class_t mask = PASS_CLASS_MASK_FROM_INDEX(m_PassClasses.size()); 90 94 m_PassClasses.push_back(PathfinderPassability(mask, it->second)); 91 95 m_PassClassMasks[name] = mask; 92 96 } 93 94 95 const CParamNode::ChildrenMap& moveClasses = externalParamNode.GetChild("Pathfinder").GetChild("MovementClasses").GetChildren();96 97 // First find the set of unit classes used by any terrain classes,98 // and assign unique tags to terrain classes99 std::set<std::string> unitClassNames;100 unitClassNames.insert("default"); // must always have costs for default101 102 {103 size_t i = 0;104 for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it)105 {106 std::string terrainClassName = it->first;107 m_TerrainCostClassTags[terrainClassName] = (cost_class_t)i;108 ++i;109 110 const CParamNode::ChildrenMap& unitClasses = it->second.GetChild("UnitClasses").GetChildren();111 for (CParamNode::ChildrenMap::const_iterator uit = unitClasses.begin(); uit != unitClasses.end(); ++uit)112 unitClassNames.insert(uit->first);113 }114 }115 116 // For each terrain class, set the costs for every unit class,117 // and assign unique tags to unit classes118 {119 size_t i = 0;120 for (std::set<std::string>::const_iterator nit = unitClassNames.begin(); nit != unitClassNames.end(); ++nit)121 {122 m_UnitCostClassTags[*nit] = (cost_class_t)i;123 ++i;124 125 std::vector<u32> costs;126 std::vector<fixed> speeds;127 128 for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it)129 {130 // Default to the general costs for this terrain class131 fixed cost = it->second.GetChild("@Cost").ToFixed();132 fixed speed = it->second.GetChild("@Speed").ToFixed();133 // Check for specific cost overrides for this unit class134 const CParamNode& unitClass = it->second.GetChild("UnitClasses").GetChild(nit->c_str());135 if (unitClass.IsOk())136 {137 cost = unitClass.GetChild("@Cost").ToFixed();138 speed = unitClass.GetChild("@Speed").ToFixed();139 }140 costs.push_back((cost * DEFAULT_MOVE_COST).ToInt_RoundToZero());141 speeds.push_back(speed);142 }143 144 m_MoveCosts.push_back(costs);145 m_MoveSpeeds.push_back(speeds);146 }147 }148 97 } 149 98 150 99 void CCmpPathfinder::Deinit() … … void CCmpPathfinder::Deinit() 152 101 SetDebugOverlay(false); // cleans up memory 153 102 ResetDebugPath(); 154 103 155 delete m_Grid; 156 delete m_ObstructionGrid; 104 PathfinderHierDeinit(); 105 106 SAFE_DELETE(m_Grid); 157 107 } 158 108 159 109 struct SerializeLongRequest … … struct SerializeLongRequest 166 116 serialize.NumberFixed_Unbounded("z0", value.z0); 167 117 SerializeGoal()(serialize, "goal", value.goal); 168 118 serialize.NumberU16_Unbounded("pass class", value.passClass); 169 serialize.NumberU8_Unbounded("cost class", value.costClass);170 119 serialize.NumberU32_Unbounded("notify", value.notify); 171 120 } 172 121 }; … … void CCmpPathfinder::RenderSubmit(SceneCollector& collector) 235 184 { 236 185 for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i) 237 186 collector.Submit(&m_DebugOverlayShortPathLines[i]); 238 }239 187 240 241 fixed CCmpPathfinder::GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, u8 costClass) 242 { 243 UpdateGrid(); 244 245 u16 i, j; 246 NearestTile(x0, z0, i, j); 247 TerrainTile tileTag = m_Grid->get(i, j); 248 return m_MoveSpeeds.at(costClass).at(GET_COST_CLASS(tileTag)); 188 PathfinderHierRenderSubmit(collector); 249 189 } 250 190 191 251 192 ICmpPathfinder::pass_class_t CCmpPathfinder::GetPassabilityClass(const std::string& name) 252 193 { 253 194 if (m_PassClassMasks.find(name) == m_PassClassMasks.end()) … … std::map<std::string, ICmpPathfinder::pass_class_t> CCmpPathfinder::GetPassabili 264 205 return m_PassClassMasks; 265 206 } 266 207 267 ICmpPathfinder::cost_class_t CCmpPathfinder::GetCostClass(const std::string& name)208 const PathfinderPassability* CCmpPathfinder::GetPassabilityFromMask(pass_class_t passClass) 268 209 { 269 if (m_UnitCostClassTags.find(name) == m_UnitCostClassTags.end()) 270 { 271 LOGERROR(L"Invalid unit cost class name '%hs'", name.c_str()); 272 return m_UnitCostClassTags["default"]; 273 } 274 275 return m_UnitCostClassTags[name]; 276 } 277 278 fixed CCmpPathfinder::DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal) 279 { 280 switch (goal.type) 281 { 282 case CCmpPathfinder::Goal::POINT: 283 return (pos - CFixedVector2D(goal.x, goal.z)).Length(); 284 285 case CCmpPathfinder::Goal::CIRCLE: 286 return ((pos - CFixedVector2D(goal.x, goal.z)).Length() - goal.hw).Absolute(); 287 288 case CCmpPathfinder::Goal::SQUARE: 289 { 290 CFixedVector2D halfSize(goal.hw, goal.hh); 291 CFixedVector2D d(pos.X - goal.x, pos.Y - goal.z); 292 return Geometry::DistanceToSquare(d, goal.u, goal.v, halfSize); 293 } 294 295 default: 296 debug_warn(L"invalid type"); 297 return fixed::Zero(); 298 } 210 for (size_t i = 0; i < m_PassClasses.size(); ++i) 211 if (m_PassClasses[i].m_Mask == passClass) 212 return &m_PassClasses[i]; 213 return NULL; 299 214 } 300 215 301 216 const Grid<u16>& CCmpPathfinder::GetPassabilityGrid() … … const Grid<u16>& CCmpPathfinder::GetPassabilityGrid() 304 219 return *m_Grid; 305 220 } 306 221 307 void CCmpPathfinder::UpdateGrid() 222 /** 223 * Given a grid of passable/impassable navcells (based on some passability mask), 224 * computes a new grid where a navcell is impassable (per that mask) if 225 * it is <=clearance navcells away from an impassable navcell in the original grid. 226 * The results are ORed onto the original grid. 227 * 228 * This is used for adding clearance onto terrain-based navcell passability. 229 * 230 * TODO: might be nicer to get rounded corners by measuring clearances as 231 * Euclidean distances; currently it effectively does dist=max(dx,dy) instead. 232 */ 233 static void ExpandImpassableCells(Grid<u16>& grid, u16 clearance, ICmpPathfinder::pass_class_t mask) 308 234 { 309 CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); 310 if (!cmpTerrain) 311 return; // error 235 PROFILE3("ExpandImpassableCells"); 312 236 313 // If the terrain was resized then delete the old grid data 314 if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide()) 315 { 316 SAFE_DELETE(m_Grid); 317 SAFE_DELETE(m_ObstructionGrid); 318 m_TerrainDirty = true; 319 } 237 u16 w = grid.m_W; 238 u16 h = grid.m_H; 320 239 321 // Initialise the terrain data when first needed 322 if (!m_Grid) 240 // First expand impassable cells horizontally into a temporary 1-bit grid 241 Grid<u8> tempGrid(w, h); 242 for (u16 j = 0; j < h; ++j) 323 243 { 324 m_MapSize = cmpTerrain->GetTilesPerSide(); 325 m_Grid = new Grid<TerrainTile>(m_MapSize, m_MapSize); 326 m_ObstructionGrid = new Grid<u8>(m_MapSize, m_MapSize); 327 } 244 // New cell (i,j) is blocked if (i',j) blocked for any i-clearance <= i' <= i+clearance 328 245 329 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); 246 // Count the number of blocked cells around i=0 247 u16 numBlocked = 0; 248 for (u16 i = 0; i <= clearance && i < w; ++i) 249 if (!IS_PASSABLE(grid.get(i, j), mask)) 250 ++numBlocked; 330 251 331 bool obstructionsDirty = cmpObstructionManager->Rasterise(*m_ObstructionGrid); 252 for (u16 i = 0; i < w; ++i) 253 { 254 // Store a flag if blocked by at least one nearby cell 255 if (numBlocked) 256 tempGrid.set(i, j, 1); 257 258 // Slide the numBlocked window along: 259 // remove the old i-clearance value, add the new (i+1)+clearance 260 // (avoiding overflowing the grid) 261 if (i >= clearance && !IS_PASSABLE(grid.get(i-clearance, j), mask)) 262 --numBlocked; 263 if (i+1+clearance < w && !IS_PASSABLE(grid.get(i+1+clearance, j), mask)) 264 ++numBlocked; 265 } 266 } 332 267 333 if (obstructionsDirty && !m_TerrainDirty)268 for (u16 i = 0; i < w; ++i) 334 269 { 335 PROFILE("UpdateGrid obstructions"); 336 337 // Obstructions changed - we need to recompute passability 338 // Since terrain hasn't changed we only need to update the obstruction bits 339 // and can skip the rest of the data 270 // New cell (i,j) is blocked if (i,j') blocked for any j-clearance <= j' <= j+clearance 340 271 341 // TODO: if ObstructionManager::SetPassabilityCircular was called at runtime 342 // (which should probably never happen, but that's not guaranteed), 343 // then TILE_OUTOFBOUNDS will change and we can't use this fast path, but 344 // currently it'll just set obstructionsDirty and we won't notice 272 // Count the number of blocked cells around j=0 273 u16 numBlocked = 0; 274 for (u16 j = 0; j <= clearance && j < h; ++j) 275 if (tempGrid.get(i, j)) 276 ++numBlocked; 345 277 346 for (u16 j = 0; j < m_MapSize; ++j)278 for (u16 j = 0; j < h; ++j) 347 279 { 348 for (u16 i = 0; i < m_MapSize; ++i) 349 { 350 TerrainTile& t = m_Grid->get(i, j); 351 352 u8 obstruct = m_ObstructionGrid->get(i, j); 353 354 if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING) 355 t |= 1; 356 else 357 t &= (TerrainTile)~1; 358 359 if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION) 360 t |= 2; 361 else 362 t &= (TerrainTile)~2; 363 } 280 // Add the mask if blocked by at least one nearby cell 281 if (numBlocked) 282 grid.set(i, j, grid.get(i, j) | mask); 283 284 // Slide the numBlocked window along: 285 // remove the old j-clearance value, add the new (j+1)+clearance 286 // (avoiding overflowing the grid) 287 if (j >= clearance && tempGrid.get(i, j-clearance)) 288 --numBlocked; 289 if (j+1+clearance < h && tempGrid.get(i, j+1+clearance)) 290 ++numBlocked; 364 291 } 365 366 ++m_Grid->m_DirtyID;367 292 } 368 else if (obstructionsDirty || m_TerrainDirty) 369 { 370 PROFILE("UpdateGrid full"); 293 } 371 294 372 // Obstructions or terrain changed - we need to recompute passability 373 // TODO: only bother recomputing the region that has actually changed 295 Grid<u16> CCmpPathfinder::ComputeShoreGrid() 296 { 297 PROFILE3("ComputeShoreGrid"); 374 298 375 299 CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); 376 300 377 378 301 // TOOD: these bits should come from ICmpTerrain 302 CTerrain& terrain = GetSimContext().GetTerrain(); 379 303 380 // avoid integer overflow in intermediate calculation 381 const u16 shoreMax = 32767; 382 383 // First pass - find underwater tiles 384 Grid<bool> waterGrid(m_MapSize, m_MapSize); 385 for (u16 j = 0; j < m_MapSize; ++j) 304 // avoid integer overflow in intermediate calculation 305 const u16 shoreMax = 32767; 306 307 // First pass - find underwater tiles 308 Grid<u8> waterGrid(m_MapSize, m_MapSize); 309 for (u16 j = 0; j < m_MapSize; ++j) 310 { 311 for (u16 i = 0; i < m_MapSize; ++i) 386 312 { 387 for (u16 i = 0; i < m_MapSize; ++i) 388 { 389 fixed x, z; 390 TileCenter(i, j, x, z); 391 392 bool underWater = cmpWaterManager && (cmpWaterManager->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z)); 393 waterGrid.set(i, j, underWater); 394 } 313 fixed x, z; 314 TileCenter(i, j, x, z); 315 316 bool underWater = cmpWaterManager && (cmpWaterManager->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z)); 317 waterGrid.set(i, j, underWater ? 1 : 0); 395 318 } 396 // Second pass - find shore tiles 397 Grid<u16> shoreGrid(m_MapSize, m_MapSize); 398 for (u16 j = 0; j < m_MapSize; ++j) 319 } 320 321 // Second pass - find shore tiles 322 Grid<u16> shoreGrid(m_MapSize, m_MapSize); 323 for (u16 j = 0; j < m_MapSize; ++j) 324 { 325 for (u16 i = 0; i < m_MapSize; ++i) 399 326 { 400 for (u16 i = 0; i < m_MapSize; ++i) 327 // Find a land tile 328 if (!waterGrid.get(i, j)) 401 329 { 402 // Find a land tile 403 if (!waterGrid.get(i, j)) 330 if ((i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1)) 331 || (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1)) 332 || (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1)) 333 ) 334 { // If it's bordered by water, it's a shore tile 335 shoreGrid.set(i, j, 0); 336 } 337 else 404 338 { 405 if ((i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1)) 406 || (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1)) 407 || (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1)) 408 ) 409 { // If it's bordered by water, it's a shore tile 410 shoreGrid.set(i, j, 0); 411 } 412 else 413 { 414 shoreGrid.set(i, j, shoreMax); 415 } 339 shoreGrid.set(i, j, shoreMax); 416 340 } 417 341 } 418 342 } 343 } 419 344 420 // Expand influences on land to find shore distance 421 for (u16 y = 0; y < m_MapSize; ++y) 345 // Expand influences on land to find shore distance 346 for (u16 y = 0; y < m_MapSize; ++y) 347 { 348 u16 min = shoreMax; 349 for (u16 x = 0; x < m_MapSize; ++x) 422 350 { 423 u16 min = shoreMax; 424 for (u16 x = 0; x < m_MapSize; ++x) 351 if (!waterGrid.get(x, y)) 425 352 { 426 if (!waterGrid.get(x, y)) 427 { 428 u16 g = shoreGrid.get(x, y); 429 if (g > min) 430 shoreGrid.set(x, y, min); 431 else if (g < min) 432 min = g; 353 u16 g = shoreGrid.get(x, y); 354 if (g > min) 355 shoreGrid.set(x, y, min); 356 else if (g < min) 357 min = g; 433 358 434 ++min; 435 } 359 ++min; 436 360 } 437 for (u16 x = m_MapSize; x > 0; --x) 361 } 362 for (u16 x = m_MapSize; x > 0; --x) 363 { 364 if (!waterGrid.get(x-1, y)) 438 365 { 439 if (!waterGrid.get(x-1, y)) 440 { 441 u16 g = shoreGrid.get(x-1, y); 442 if (g > min) 443 shoreGrid.set(x-1, y, min); 444 else if (g < min) 445 min = g; 366 u16 g = shoreGrid.get(x-1, y); 367 if (g > min) 368 shoreGrid.set(x-1, y, min); 369 else if (g < min) 370 min = g; 446 371 447 ++min; 448 } 372 ++min; 449 373 } 450 374 } 451 for (u16 x = 0; x < m_MapSize; ++x) 375 } 376 for (u16 x = 0; x < m_MapSize; ++x) 377 { 378 u16 min = shoreMax; 379 for (u16 y = 0; y < m_MapSize; ++y) 452 380 { 453 u16 min = shoreMax; 454 for (u16 y = 0; y < m_MapSize; ++y) 381 if (!waterGrid.get(x, y)) 455 382 { 456 if (!waterGrid.get(x, y)) 457 { 458 u16 g = shoreGrid.get(x, y); 459 if (g > min) 460 shoreGrid.set(x, y, min); 461 else if (g < min) 462 min = g; 383 u16 g = shoreGrid.get(x, y); 384 if (g > min) 385 shoreGrid.set(x, y, min); 386 else if (g < min) 387 min = g; 463 388 464 ++min; 465 } 389 ++min; 466 390 } 467 for (u16 y = m_MapSize; y > 0; --y) 391 } 392 for (u16 y = m_MapSize; y > 0; --y) 393 { 394 if (!waterGrid.get(x, y-1)) 468 395 { 469 if (!waterGrid.get(x, y-1)) 470 { 471 u16 g = shoreGrid.get(x, y-1); 472 if (g > min) 473 shoreGrid.set(x, y-1, min); 474 else if (g < min) 475 min = g; 396 u16 g = shoreGrid.get(x, y-1); 397 if (g > min) 398 shoreGrid.set(x, y-1, min); 399 else if (g < min) 400 min = g; 476 401 477 ++min; 478 } 402 ++min; 479 403 } 480 404 } 405 } 406 407 return shoreGrid; 408 } 409 410 void CCmpPathfinder::ComputeTerrainPassabilityGrid(const Grid<u16>& shoreGrid) 411 { 412 PROFILE3("terrain passability"); 413 414 CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); 481 415 482 // Apply passability classes to terrain 483 for (u16 j = 0; j < m_MapSize; ++j) 416 CTerrain& terrain = GetSimContext().GetTerrain(); 417 418 // Compute initial terrain-dependent passability 419 for (int j = 0; j < m_MapSize * ICmpObstructionManager::NAVCELLS_PER_TILE; ++j) 420 { 421 for (int i = 0; i < m_MapSize * ICmpObstructionManager::NAVCELLS_PER_TILE; ++i) 484 422 { 485 for (u16 i = 0; i < m_MapSize; ++i) 423 // World-space coordinates for this navcell 424 fixed x, z; 425 NavcellCenter(i, j, x, z); 426 427 // Terrain-tile coordinates for this navcell 428 int itile = i / ICmpObstructionManager::NAVCELLS_PER_TILE; 429 int jtile = j / ICmpObstructionManager::NAVCELLS_PER_TILE; 430 431 // Gather all the data potentially needed to determine passability: 432 433 fixed height = terrain.GetExactGroundLevelFixed(x, z); 434 435 fixed water; 436 if (cmpWaterManager) 437 water = cmpWaterManager->GetWaterLevel(x, z); 438 439 fixed depth = water - height; 440 441 //fixed slope = terrain.GetExactSlopeFixed(x, z); 442 // Exact slopes give kind of weird output, so just use rough tile-based slopes 443 fixed slope = terrain.GetSlopeFixed(itile, jtile); 444 445 // Get world-space coordinates from shoreGrid (which uses terrain tiles) 446 fixed shoredist = fixed::FromInt(shoreGrid.get(itile, jtile)) * (int)TERRAIN_TILE_SIZE; 447 448 // Compute the passability for every class for this cell: 449 450 NavcellData t = 0; 451 for (size_t n = 0; n < m_PassClasses.size(); ++n) 486 452 { 487 fixed x, z; 488 TileCenter(i, j, x, z); 453 if (!m_PassClasses[n].IsPassable(depth, slope, shoredist)) 454 t |= m_PassClasses[n].m_Mask; 455 } 489 456 490 TerrainTile t = 0; 457 m_Grid->set(i, j, t); 458 } 459 } 460 } 491 461 492 u8 obstruct = m_ObstructionGrid->get(i, j); 462 void CCmpPathfinder::UpdateGrid() 463 { 464 CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); 465 if (!cmpTerrain) 466 return; // error 467 468 // If the terrain was resized then delete the old grid data 469 if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide()) 470 { 471 SAFE_DELETE(m_Grid); 472 m_TerrainDirty = true; 473 } 474 475 // Initialise the terrain data when first needed 476 if (!m_Grid) 477 { 478 m_MapSize = cmpTerrain->GetTilesPerSide(); 479 m_Grid = new Grid<NavcellData>(m_MapSize * ICmpObstructionManager::NAVCELLS_PER_TILE, m_MapSize * ICmpObstructionManager::NAVCELLS_PER_TILE); 480 m_ObstructionGridDirtyID = 0; 481 } 493 482 494 fixed height = terrain.GetExactGroundLevelFixed(x, z);483 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); 495 484 496 fixed water; 497 if (cmpWaterManager) 498 water = cmpWaterManager->GetWaterLevel(x, z); 485 bool obstructionsDirty = cmpObstructionManager->NeedUpdate(&m_ObstructionGridDirtyID); 499 486 500 fixed depth = water - height; 487 // TODO: for performance, it'd be nice if we could get away with not 488 // recomputing all the terrain passability when only an obstruction has 489 // changed. But that's not supported yet, so recompute everything after 490 // every change: 491 if (obstructionsDirty || m_TerrainDirty) 492 { 493 PROFILE3("UpdateGrid full"); 501 494 502 fixed slope = terrain.GetSlopeFixed(i, j); 495 // Obstructions or terrain changed - we need to recompute passability 496 // TODO: only bother recomputing the region that has actually changed 503 497 504 fixed shoredist = fixed::FromInt(shoreGrid.get(i, j));498 Grid<u16> shoreGrid = ComputeShoreGrid(); 505 499 506 if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING) 507 t |= 1; 500 ComputeTerrainPassabilityGrid(shoreGrid); 508 501 509 if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION) 510 t |= 2; 502 if (1) // XXX: if circular 503 { 504 PROFILE3("off-world passability"); 511 505 512 if (obstruct & ICmpObstructionManager::TILE_OUTOFBOUNDS) 513 { 514 // If out of bounds, nobody is allowed to pass 515 for (size_t n = 0; n < m_PassClasses.size(); ++n) 516 t |= m_PassClasses[n].m_Mask; 517 } 518 else 506 // WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this 507 const int edgeSize = 3 * ICmpObstructionManager::NAVCELLS_PER_TILE; // number of tiles around the edge that will be off-world 508 509 NavcellData edgeMask = 0; 510 for (size_t n = 0; n < m_PassClasses.size(); ++n) 511 edgeMask |= m_PassClasses[n].m_Mask; 512 513 int w = m_Grid->m_W; 514 int h = m_Grid->m_H; 515 for (int j = 0; j < h; ++j) 516 { 517 for (int i = 0; i < w; ++i) 519 518 { 520 for (size_t n = 0; n < m_PassClasses.size(); ++n) 521 { 522 if (!m_PassClasses[n].IsPassable(depth, slope, shoredist)) 523 t |= m_PassClasses[n].m_Mask; 524 } 525 } 519 // Based on CCmpRangeManager::LosIsOffWorld 520 // but tweaked since it's tile-based instead. 521 // (We double all the values so we can handle half-tile coordinates.) 522 // This needs to be slightly tighter than the LOS circle, 523 // else units might get themselves lost in the SoD around the edge. 524 525 int dist2 = (i*2 + 1 - w)*(i*2 + 1 - w) 526 + (j*2 + 1 - h)*(j*2 + 1 - h); 526 527 527 std::string moveClass = terrain.GetMovementClass(i, j); 528 if (m_TerrainCostClassTags.find(moveClass) != m_TerrainCostClassTags.end()) 529 t |= COST_CLASS_MASK(m_TerrainCostClassTags[moveClass]); 528 if (dist2 >= (w - 2*edgeSize) * (h - 2*edgeSize)) 529 m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask); 530 } 531 } 532 } 530 533 531 m_Grid->set(i, j, t); 534 // Expand the impassability grid, for any class with non-zero clearance, 535 // so that we can stop units getting too close to impassable navcells 536 for (size_t n = 0; n < m_PassClasses.size(); ++n) 537 { 538 if (m_PassClasses[n].m_HasClearance) 539 { 540 // TODO: if multiple classes have the same clearance, we should 541 // only bother doing this once for them all 542 int clearance = (m_PassClasses[n].m_Clearance / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToInfinity(); 543 if (clearance > 0) 544 ExpandImpassableCells(*m_Grid, clearance, m_PassClasses[n].m_Mask); 532 545 } 533 546 } 534 547 548 // Add obstructions onto the grid, for any class with (possibly zero) clearance 549 for (size_t n = 0; n < m_PassClasses.size(); ++n) 550 { 551 // TODO: if multiple classes have the same clearance, we should 552 // only bother running Rasterize once for them all 553 if (m_PassClasses[n].m_HasClearance) 554 cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses[n].m_Clearance, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, m_PassClasses[n].m_Mask); 555 } 556 535 557 m_TerrainDirty = false; 536 558 537 559 ++m_Grid->m_DirtyID; 560 561 PathfinderHierReload(); 562 563 PathfinderJPSMakeDirty(); 538 564 } 539 565 } 540 566 … … void CCmpPathfinder::UpdateGrid() 542 568 543 569 // Async path requests: 544 570 545 u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify)571 u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify) 546 572 { 547 AsyncLongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, costClass,notify };573 AsyncLongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, notify }; 548 574 m_AsyncLongPathRequests.push_back(req); 549 575 return req.ticket; 550 576 } 551 577 552 u32 CCmpPathfinder::ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify)578 u32 CCmpPathfinder::ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify) 553 579 { 554 580 AsyncShortPathRequest req = { m_NextAsyncTicket++, x0, z0, r, range, goal, passClass, avoidMovingUnits, group, notify }; 555 581 m_AsyncShortPathRequests.push_back(req); … … void CCmpPathfinder::ProcessLongRequests(const std::vector<AsyncLongPathRequest> 580 606 { 581 607 const AsyncLongPathRequest& req = longRequests[i]; 582 608 Path path; 583 ComputePath(req.x0, req.z0, req.goal, req.passClass, req.costClass, path); 609 #if PATHFIND_USE_JPS 610 ComputePathJPS(req.x0, req.z0, req.goal, req.passClass, path); 611 #else 612 ComputePath(req.x0, req.z0, req.goal, req.passClass, path); 613 #endif 584 614 CMessagePathResult msg(req.ticket, path); 585 615 GetSimContext().GetComponentManager().PostMessage(req.notify, msg); 586 616 } … … void CCmpPathfinder::ProcessSameTurnMoves() 656 686 } 657 687 } 658 688 689 ////////////////////////////////////////////////////////// 690 659 691 ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter, 660 692 entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass) 661 693 { … … ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckUnitPlacement(const IObst 667 699 if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL)) 668 700 return ICmpObstruction::FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION; 669 701 670 // Test against terrain :702 // Test against terrain and static obstructions: 671 703 672 UpdateGrid(); 704 u16 i, j; 705 NearestNavcell(x, z, i, j); 706 if (!IS_PASSABLE(m_Grid->get(i, j), passClass)) 707 return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; 708 709 // (Static obstructions will be redundantly tested against in both the 710 // obstruction-shape test and navcell-passability test, which is slightly 711 // inefficient but shouldn't affect behaviour) 673 712 674 u16 i0, j0, i1, j1;675 NearestTile(x - r, z - r, i0, j0);676 NearestTile(x + r, z + r, i1, j1);677 for (u16 j = j0; j <= j1; ++j)678 {679 for (u16 i = i0; i <= i1; ++i)680 {681 if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))682 {683 return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS;684 }685 }686 }687 713 return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; 688 714 } 689 715 … … ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const I 708 734 if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square)) 709 735 return ICmpObstruction::FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION; 710 736 711 // Expand bounds by 1/sqrt(2) tile (multiply by TERRAIN_TILE_SIZE since we want world coordinates)712 entity_pos_t expand = entity_pos_t::FromInt(2).Sqrt().Multiply(entity_pos_t::FromInt(TERRAIN_TILE_SIZE / 2));713 CFixedVector2D halfSize(square.hw + expand, square.hh + expand);714 CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize);737 entity_pos_t expand; 738 const PathfinderPassability* passability = GetPassabilityFromMask(passClass); 739 if (passability && passability->m_HasClearance) 740 expand = passability->m_Clearance; 715 741 716 u16 i0, j0, i1, j1; 717 NearestTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0); 718 NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1); 719 for (u16 j = j0; j <= j1; ++j) 742 SimRasterize::Spans spans; 743 SimRasterize::RasterizeRectWithClearance(spans, square, expand, ICmpObstructionManager::NAVCELL_SIZE); 744 for (size_t k = 0; k < spans.size(); ++k) 720 745 { 721 for (u16 i = i0; i <= i1; ++i) 746 i16 i0 = spans[k].i0; 747 i16 i1 = spans[k].i1; 748 i16 j = spans[k].j; 749 750 // Fail if any span extends outside the grid 751 if (i0 < 0 || i1 > m_Grid->m_W || j < 0 || j > m_Grid->m_H) 752 return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; 753 754 // Fail if any span includes an impassable tile 755 for (i16 i = i0; i < i1; ++i) 756 if (!IS_PASSABLE(m_Grid->get(i, j), passClass)) 757 return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; 758 759 } 760 761 return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; 762 } 763 764 ////////////////////////////////////////////////////////// 765 766 void CCmpPathfinder::ComputePathOffImpassable(u16 i0, u16 j0, pass_class_t passClass, Path& path) 767 { 768 u16 iGoal = i0; 769 u16 jGoal = j0; 770 this->PathfinderHierFindNearestPassableNavcell(iGoal, jGoal, passClass); 771 772 int ip = iGoal; 773 int jp = jGoal; 774 775 // Reconstruct the path (in reverse) 776 while (ip != i0 || jp != j0) 777 { 778 entity_pos_t x, z; 779 NavcellCenter(ip, jp, x, z); 780 Waypoint w = { x, z }; 781 path.m_Waypoints.push_back(w); 782 783 // Move diagonally/horizontally/vertically towards the start navcell 784 785 if (ip > i0) 786 ip--; 787 else if (ip < i0) 788 ip++; 789 790 if (jp > j0) 791 jp--; 792 else if (jp < j0) 793 jp++; 794 } 795 } 796 797 void CCmpPathfinder::NormalizePathWaypoints(Path& path) 798 { 799 if (path.m_Waypoints.empty()) 800 return; 801 802 // Given the current list of waypoints, add intermediate waypoints 803 // in a straight line between them, so that the maximum gap between 804 // waypoints is within the (fairly arbitrary) limit 805 const fixed MAX_WAYPOINT_SEPARATION = ICmpObstructionManager::NAVCELL_SIZE * 4; 806 807 std::vector<Waypoint>& waypoints = path.m_Waypoints; 808 std::vector<Waypoint> newWaypoints; 809 810 newWaypoints.push_back(waypoints.front()); 811 for (size_t k = 1; k < waypoints.size(); ++k) 812 { 813 CFixedVector2D prev(waypoints[k-1].x, waypoints[k-1].z); 814 CFixedVector2D curr(waypoints[k].x, waypoints[k].z); 815 fixed dist = (curr - prev).Length(); 816 if (dist > MAX_WAYPOINT_SEPARATION) 722 817 { 723 entity_pos_t x, z; 724 TileCenter(i, j, x, z); 725 if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize) 726 && !IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass)) 818 int segments = (dist / MAX_WAYPOINT_SEPARATION).ToInt_RoundToInfinity(); 819 for (int i = 1; i < segments; ++i) 727 820 { 728 return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; 821 CFixedVector2D p = prev + ((curr - prev)*i) / segments; 822 Waypoint wp = { p.X, p.Y }; 823 newWaypoints.push_back(wp); 729 824 } 730 825 } 826 newWaypoints.push_back(waypoints[k]); 731 827 } 732 828 733 return ICmpObstruction::FOUNDATION_CHECK_SUCCESS;829 path.m_Waypoints.swap(newWaypoints); 734 830 } -
source/simulation2/components/CCmpPathfinder_Common.h
diff --git a/source/simulation2/components/CCmpPathfinder_Common.h b/source/simulation2/components/CCmpPathfinder_Common.h index a5e4a74..9f39729 100644
a b 34 34 #include "graphics/Overlay.h" 35 35 #include "graphics/Terrain.h" 36 36 #include "maths/MathUtil.h" 37 #include "ps/CLogger.h" 38 #include "simulation2/components/ICmpObstructionManager.h" 37 39 #include "simulation2/helpers/Geometry.h" 38 40 #include "simulation2/helpers/Grid.h" 39 41 40 42 class PathfinderOverlay; 41 43 class SceneCollector; 42 44 struct PathfindTile; 45 struct PathfindTileJPS; 46 class JumpPointCache; 47 class CCmpPathfinder_Hier; 43 48 44 49 #ifdef NDEBUG 45 50 #define PATHFIND_DEBUG 0 … … struct PathfindTile; 47 52 #define PATHFIND_DEBUG 1 48 53 #endif 49 54 55 #define PATHFIND_USE_JPS 1 56 50 57 /* 51 58 * For efficient pathfinding we want to try hard to minimise the per-tile search cost, 52 59 * so we precompute the tile passability flags and movement costs for the various different … … struct PathfindTile; 64 71 * the class passabilities) so that it can be ignored when doing the accurate short paths. 65 72 * We use another bit to indicate tiles near obstructions that block construction, 66 73 * for the AI to plan safe building spots. 67 *68 * To handle movement costs, we have an arbitrary number of unit cost classes (e.g. "infantry", "camel"),69 * and a small number of terrain cost classes (e.g. "grass", "steep grass", "road", "sand"),70 * and a cost mapping table between the classes (e.g. camels are fast on sand).71 * We need log2(|terrain cost classes|) bits per tile to represent costs.72 *73 * We could have one passability bitmap per class, and another array for cost classes,74 * but instead (for no particular reason) we'll pack them all into a single u16 array.75 *76 * We handle dynamic updates currently by recomputing the entire array, which is stupid;77 * it should only bother updating the region that has changed.78 74 */ 79 75 class PathfinderPassability 80 76 { … … public: 107 103 else 108 104 m_MaxShore = std::numeric_limits<fixed>::max(); 109 105 106 if (node.GetChild("Clearance").IsOk()) 107 { 108 m_HasClearance = true; 109 m_Clearance = node.GetChild("Clearance").ToFixed(); 110 111 if (!(m_Clearance % ICmpObstructionManager::NAVCELL_SIZE).IsZero()) 112 { 113 // If clearance isn't an integer number of navcells then we'll 114 // probably get weird behaviour when expanding the navcell grid 115 // by clearance, vs expanding static obstructions by clearance 116 LOGWARNING(L"Pathfinder passability class has clearance %f, should be multiple of %f", 117 m_Clearance.ToFloat(), ICmpObstructionManager::NAVCELL_SIZE.ToFloat()); 118 } 119 } 120 else 121 { 122 m_HasClearance = false; 123 m_Clearance = fixed::Zero(); 124 } 110 125 } 111 126 112 127 bool IsPassable(fixed waterdepth, fixed steepness, fixed shoredist) … … public: 115 130 } 116 131 117 132 ICmpPathfinder::pass_class_t m_Mask; 133 134 bool m_HasClearance; // whether static obstructions are impassable 135 fixed m_Clearance; // min distance from static obstructions 136 118 137 private: 119 138 fixed m_MinDepth; 120 139 fixed m_MaxDepth; … … private: 123 142 fixed m_MaxShore; 124 143 }; 125 144 126 typedef u16 TerrainTile; 127 // 1 bit for pathfinding obstructions, 128 // 1 bit for construction obstructions (used by AI), 129 // PASS_CLASS_BITS for terrain passability (allowing PASS_CLASS_BITS classes), 130 // COST_CLASS_BITS for movement costs (allowing 2^COST_CLASS_BITS classes) 131 132 const int PASS_CLASS_BITS = 10; 133 const int COST_CLASS_BITS = 16 - (PASS_CLASS_BITS + 2); 134 #define IS_TERRAIN_PASSABLE(item, classmask) (((item) & (classmask)) == 0) 135 #define IS_PASSABLE(item, classmask) (((item) & ((classmask) | 1)) == 0) 136 #define GET_COST_CLASS(item) ((item) >> (PASS_CLASS_BITS + 2)) 137 #define COST_CLASS_MASK(id) ( (TerrainTile) ((id) << (PASS_CLASS_BITS + 2)) ) 145 typedef u16 NavcellData; // 1 bit per passability class (up to PASS_CLASS_BITS) 146 const int PASS_CLASS_BITS = 16; 147 #define IS_PASSABLE(item, classmask) (((item) & (classmask)) == 0) 148 #define PASS_CLASS_MASK_FROM_INDEX(id) ((pass_class_t)(1u << (id))) 138 149 139 150 typedef SparseGrid<PathfindTile> PathfindTileGrid; 151 typedef SparseGrid<PathfindTileJPS> PathfindTileJPSGrid; 152 153 /** 154 * Represents the 2D coordinates of a tile. 155 * The i/j components are packed into a single u32, since we usually use these 156 * objects for equality comparisons and the VC2010 optimizer doesn't seem to automatically 157 * compare two u16s in a single operation. 158 */ 159 struct TileID 160 { 161 TileID() { } 162 163 TileID(u16 i, u16 j) : data((i << 16) | j) { } 164 165 bool operator==(const TileID& b) const 166 { 167 return data == b.data; 168 } 169 170 /// Returns lexicographic order over (i,j) 171 bool operator<(const TileID& b) const 172 { 173 return data < b.data; 174 } 175 176 u16 i() const { return data >> 16; } 177 u16 j() const { return data & 0xFFFF; } 178 179 private: 180 u32 data; 181 }; 182 183 /** 184 * Represents the cost of a path consisting of horizontal/vertical and 185 * diagonal movements over a uniform-cost grid. 186 * Maximum path length before overflow is about 45K steps. 187 */ 188 struct PathCost 189 { 190 PathCost() : data(0) { } 191 192 /// Construct from a number of horizontal/vertical and diagonal steps 193 PathCost(u16 hv, u16 d) 194 : data(hv*65536 + d*92682) // 2^16 * sqrt(2) == 92681.9 195 { 196 } 197 198 /// Construct for horizontal/vertical movement of given number of steps 199 static PathCost horizvert(u16 n) 200 { 201 return PathCost(n, 0); 202 } 203 204 /// Construct for diagonal movement of given number of steps 205 static PathCost diag(u16 n) 206 { 207 return PathCost(0, n); 208 } 209 210 PathCost operator+(const PathCost& a) const 211 { 212 PathCost c; 213 c.data = data + a.data; 214 return c; 215 } 216 217 bool operator<=(const PathCost& b) const { return data <= b.data; } 218 bool operator< (const PathCost& b) const { return data < b.data; } 219 bool operator>=(const PathCost& b) const { return data >= b.data; } 220 bool operator> (const PathCost& b) const { return data > b.data; } 221 222 u32 ToInt() 223 { 224 return data; 225 } 226 227 private: 228 u32 data; 229 }; 230 231 140 232 141 233 struct AsyncLongPathRequest 142 234 { 143 235 u32 ticket; 144 236 entity_pos_t x0; 145 237 entity_pos_t z0; 146 ICmpPathfinder::Goal goal;238 PathGoal goal; 147 239 ICmpPathfinder::pass_class_t passClass; 148 ICmpPathfinder::cost_class_t costClass;149 240 entity_id_t notify; 150 241 }; 151 242 … … struct AsyncShortPathRequest 156 247 entity_pos_t z0; 157 248 entity_pos_t r; 158 249 entity_pos_t range; 159 ICmpPathfinder::Goal goal;250 PathGoal goal; 160 251 ICmpPathfinder::pass_class_t passClass; 161 252 bool avoidMovingUnits; 162 253 entity_id_t group; … … public: 184 275 std::map<std::string, pass_class_t> m_PassClassMasks; 185 276 std::vector<PathfinderPassability> m_PassClasses; 186 277 187 std::map<std::string, cost_class_t> m_TerrainCostClassTags;188 std::map<std::string, cost_class_t> m_UnitCostClassTags;189 std::vector<std::vector<u32> > m_MoveCosts; // costs[unitClass][terrainClass]190 std::vector<std::vector<fixed> > m_MoveSpeeds; // speeds[unitClass][terrainClass]191 192 278 // Dynamic state: 193 279 194 280 std::vector<AsyncLongPathRequest> m_AsyncLongPathRequests; … … public: 199 285 // Lazily-constructed dynamic state (not serialized): 200 286 201 287 u16 m_MapSize; // tiles per side 202 Grid< TerrainTile>* m_Grid; // terrain/passability information203 Grid<u8>* m_ObstructionGrid; // cached obstruction information (TODO: we shouldn't bother storing this, it's redundant with LSBs of m_Grid)288 Grid<NavcellData>* m_Grid; // terrain/passability information 289 size_t m_ObstructionGridDirtyID; // dirty ID for ICmpObstructionManager::NeedUpdate 204 290 bool m_TerrainDirty; // indicates if m_Grid has been updated since terrain changed 205 291 292 std::map<pass_class_t, shared_ptr<JumpPointCache> > m_JumpPointCache; // for JPS pathfinder 293 294 // Interface to the hierarchical pathfinder. 295 // (This is hidden behind proxy methods to keep the code 296 // slightly better encapsulated.) 297 CCmpPathfinder_Hier* m_PathfinderHier; 298 void PathfinderHierInit(); 299 void PathfinderHierDeinit(); 300 void PathfinderHierReload(); 301 void PathfinderHierRenderSubmit(SceneCollector& collector); 302 bool PathfinderHierMakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass); 303 void PathfinderHierFindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass); 304 305 void PathfinderJPSMakeDirty(); 306 206 307 // For responsiveness we will process some moves in the same turn they were generated in 207 308 208 309 u16 m_MaxSameTurnMoves; // max number of moves that can be created and processed in the same turn … … public: 210 311 // Debugging - output from last pathfind operation: 211 312 212 313 PathfindTileGrid* m_DebugGrid; 314 PathfindTileJPSGrid* m_DebugGridJPS; 213 315 u32 m_DebugSteps; 316 double m_DebugTime; 317 PathGoal m_DebugGoal; 214 318 Path* m_DebugPath; 215 319 PathfinderOverlay* m_DebugOverlay; 216 320 pass_class_t m_DebugPassClass; … … public: 236 340 237 341 virtual std::map<std::string, pass_class_t> GetPassabilityClasses(); 238 342 239 virtual cost_class_t GetCostClass(const std::string& name);343 const PathfinderPassability* GetPassabilityFromMask(pass_class_t passClass); 240 344 241 345 virtual const Grid<u16>& GetPassabilityGrid(); 242 346 243 virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& ret); 347 virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, Path& ret); 348 349 virtual void ComputePathJPS(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, Path& ret); 350 351 /** 352 * Same kind of interface as ICmpPathfinder::ComputePath, but works when the 353 * unit is starting on an impassable navcell. Returns a path heading directly 354 * to the nearest passable navcell. 355 */ 356 void ComputePathOffImpassable(u16 i0, u16 j0, pass_class_t passClass, Path& ret); 357 358 /** 359 * Given a path with an arbitrary collection of waypoints, updates the 360 * waypoints to be nicer. (In particular, the current implementation 361 * ensures a consistent maximum distance between adjacent waypoints. 362 * Might be nice to improve it to smooth the paths, etc.) 363 */ 364 void NormalizePathWaypoints(Path& path); 244 365 245 virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify);366 virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify); 246 367 247 virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& ret);368 virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, Path& ret); 248 369 249 virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t controller, entity_id_t notify);370 virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t controller, entity_id_t notify); 250 371 251 virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass);372 virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass); 252 373 253 374 virtual void ResetDebugPath(); 254 375 255 376 virtual void SetDebugOverlay(bool enabled); 256 377 257 virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, cost_class_t costClass); 378 virtual void GetDebugData(u32& steps, double& time, Grid<u8>& grid); 379 380 virtual void GetDebugDataJPS(u32& steps, double& time, Grid<u8>& grid); 258 381 259 virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal& goal);382 virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const PathGoal& goal); 260 383 261 384 virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass); 262 385 … … public: 273 396 virtual void ProcessSameTurnMoves(); 274 397 275 398 /** 276 * Returns the tile containing the given position399 * Compute the navcell indexes on the grid nearest to a given point 277 400 */ 278 void Nearest Tile(entity_pos_t x, entity_pos_t z, u16& i, u16& j)401 void NearestNavcell(entity_pos_t x, entity_pos_t z, u16& i, u16& j) 279 402 { 280 i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, m_MapSize-1);281 j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, m_MapSize-1);403 i = (u16)clamp((x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToNegInfinity(), 0, m_MapSize*ICmpObstructionManager::NAVCELLS_PER_TILE - 1); 404 j = (u16)clamp((z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToNegInfinity(), 0, m_MapSize*ICmpObstructionManager::NAVCELLS_PER_TILE - 1); 282 405 } 283 406 284 407 /** … … public: 286 409 */ 287 410 static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z) 288 411 { 412 cassert(TERRAIN_TILE_SIZE % 2 == 0); 289 413 x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2); 290 414 z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2); 291 415 } 292 416 293 static fixed DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal); 417 static void NavcellCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z) 418 { 419 x = entity_pos_t::FromInt(i*2 + 1).Multiply(ICmpObstructionManager::NAVCELL_SIZE / 2); 420 z = entity_pos_t::FromInt(j*2 + 1).Multiply(ICmpObstructionManager::NAVCELL_SIZE / 2); 421 } 294 422 295 423 /** 296 424 * Regenerates the grid based on the current obstruction list, if necessary 297 425 */ 298 426 void UpdateGrid(); 299 427 428 Grid<u16> ComputeShoreGrid(); 429 430 void ComputeTerrainPassabilityGrid(const Grid<u16>& shoreGrid); 431 300 432 void RenderSubmit(SceneCollector& collector); 301 433 }; 302 434 -
new file source/simulation2/components/CCmpPathfinder_Hier.cpp
diff --git a/source/simulation2/components/CCmpPathfinder_Hier.cpp b/source/simulation2/components/CCmpPathfinder_Hier.cpp new file mode 100644 index 0000000..dd55aff
- + 1 /* Copyright (C) 2012 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 20 #include "CCmpPathfinder_Common.h" 21 22 #include "renderer/Scene.h" 23 #include "renderer/TerrainOverlay.h" 24 #include "simulation2/helpers/Render.h" 25 26 class PathfinderHierOverlay; 27 28 /** 29 * Hierarchical pathfinder. 30 * 31 * Currently this doesn't actually find shortest paths, it just deals with 32 * connectivity. 33 * 34 * The navcell-grid representation of the map is split into fixed-size chunks. 35 * Within a chunks, each maximal set of adjacently-connected passable navcells 36 * is defined as a region. 37 * Each region is a vertex in the hierarchical pathfinder's graph. 38 * When two regions in adjacent chunks are connected by passable navcells, 39 * the graph contains an edge between the corresponding two vertexes. 40 * (There will never be an edge between two regions in the same chunk.) 41 * 42 * Since regions are typically fairly large, it is possible to determine 43 * connectivity between any two navcells by mapping them onto their appropriate 44 * region and then doing a relatively small graph search. 45 */ 46 class CCmpPathfinder_Hier 47 { 48 NONCOPYABLE(CCmpPathfinder_Hier); 49 50 typedef ICmpPathfinder::pass_class_t pass_class_t; 51 52 public: 53 struct RegionID 54 { 55 u8 ci, cj; // chunk ID 56 u16 r; // unique-per-chunk local region ID 57 58 RegionID(u8 ci, u8 cj, u16 r) : ci(ci), cj(cj), r(r) { } 59 60 bool operator<(RegionID b) const 61 { 62 // Sort by chunk ID, then by per-chunk region ID 63 if (ci < b.ci) 64 return true; 65 if (b.ci < ci) 66 return false; 67 if (cj < b.cj) 68 return true; 69 if (b.cj < cj) 70 return false; 71 return r < b.r; 72 } 73 }; 74 75 CCmpPathfinder_Hier(CCmpPathfinder& pathfinder); 76 ~CCmpPathfinder_Hier(); 77 78 void Init(const std::vector<PathfinderPassability>& passClasses, Grid<NavcellData>* grid); 79 80 RegionID Get(u16 i, u16 j, pass_class_t passClass); 81 82 /** 83 * Updates @p goal so that it's guaranteed to be reachable from the navcell 84 * @p i0, @p j0 (which is assumed to be on a passable navcell). 85 * If any part of the goal area is already reachable then 86 * nothing is changed; otherwise the goal is replaced with a point goal 87 * at the nearest reachable navcell to the original goal's center. 88 * Returns true if the goal was replaced. 89 */ 90 bool MakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass); 91 92 /** 93 * Updates @p i0, @p j0 (which is assumed to be an impassable navcell) 94 * to the nearest passable navcell. 95 */ 96 void FindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass); 97 98 private: 99 static const u8 CHUNK_SIZE = 64; // number of navcells per side 100 101 struct Chunk 102 { 103 u8 m_ChunkI, m_ChunkJ; // chunk ID 104 u16 m_NumRegions; // number of local region IDs (starting from 1) 105 u16 m_Regions[CHUNK_SIZE][CHUNK_SIZE]; // local region ID per navcell 106 107 cassert(CHUNK_SIZE*CHUNK_SIZE/2 < 65536); // otherwise we could overflow m_NumRegions with a checkerboard pattern 108 109 void InitRegions(int ci, int cj, Grid<NavcellData>* grid, pass_class_t passClass); 110 111 RegionID Get(int i, int j); 112 113 void RegionCenter(u16 r, int& i, int& j) const; 114 115 bool RegionContainsGoal(u16 r, const PathGoal& goal) const; 116 117 void RegionNavcellNearest(u16 r, int iGoal, int jGoal, int& iBest, int& jBest, u32& dist2Best) const; 118 119 void FloodFill(int i0, int j0, u16 r); 120 }; 121 122 typedef std::map<RegionID, std::set<RegionID> > EdgesMap; 123 124 void FindEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges); 125 126 void AddDebugEdges(pass_class_t passClass); 127 128 void FindReachableRegions(RegionID from, std::set<RegionID>& reachable, pass_class_t passClass); 129 130 void FindPassableRegions(std::set<RegionID>& regions, pass_class_t passClass); 131 132 /** 133 * Updates @p iGoal and @p jGoal to the navcell that is the nearest to the 134 * initial goal coordinates, in one of the given @p regions. 135 * (Assumes @p regions is non-empty.) 136 */ 137 void FindNearestNavcellInRegions(const std::set<RegionID>& regions, u16& iGoal, u16& jGoal, pass_class_t passClass); 138 139 Chunk& GetChunk(u8 ci, u8 cj, pass_class_t passClass) 140 { 141 return m_Chunks[passClass].at(cj * m_ChunksW + ci); 142 } 143 144 PathfinderHierOverlay* m_DebugOverlay; 145 146 u16 m_ChunksW, m_ChunksH; 147 std::map<pass_class_t, std::vector<Chunk> > m_Chunks; 148 149 std::map<pass_class_t, EdgesMap> m_Edges; 150 151 public: 152 CCmpPathfinder& m_Pathfinder; 153 std::vector<SOverlayLine> m_DebugOverlayLines; 154 }; 155 156 class PathfinderHierOverlay : public TerrainTextureOverlay 157 { 158 public: 159 CCmpPathfinder_Hier& m_PathfinderHier; 160 161 PathfinderHierOverlay(CCmpPathfinder_Hier& pathfinderHier) : 162 TerrainTextureOverlay(ICmpObstructionManager::NAVCELLS_PER_TILE), m_PathfinderHier(pathfinderHier) 163 { 164 } 165 166 virtual void BuildTextureRGBA(u8* data, size_t w, size_t h) 167 { 168 ICmpPathfinder::pass_class_t passClass = m_PathfinderHier.m_Pathfinder.GetPassabilityClass("default"); 169 170 for (size_t j = 0; j < h; ++j) 171 { 172 for (size_t i = 0; i < w; ++i) 173 { 174 SColor4ub color; 175 176 CCmpPathfinder_Hier::RegionID rid = m_PathfinderHier.Get(i, j, passClass); 177 if (rid.r == 0) 178 color = SColor4ub(0, 0, 0, 0); 179 else if (rid.r == 0xFFFF) 180 color = SColor4ub(255, 0, 255, 255); 181 else 182 color = GetColor(rid.r + rid.ci*5 + rid.cj*7, 127); 183 184 *data++ = color.R; 185 *data++ = color.G; 186 *data++ = color.B; 187 *data++ = color.A; 188 } 189 } 190 } 191 }; 192 193 void CCmpPathfinder_Hier::Chunk::InitRegions(int ci, int cj, Grid<NavcellData>* grid, pass_class_t passClass) 194 { 195 ENSURE(ci < 256 && cj < 256); // avoid overflows 196 m_ChunkI = ci; 197 m_ChunkJ = cj; 198 199 memset(m_Regions, 0, sizeof(m_Regions)); 200 201 int i0 = ci * CHUNK_SIZE; 202 int j0 = cj * CHUNK_SIZE; 203 int i1 = std::min(i0 + CHUNK_SIZE, (int)grid->m_W); 204 int j1 = std::min(j0 + CHUNK_SIZE, (int)grid->m_H); 205 206 // Start by filling the grid with 0 for blocked, 207 // and a 0xFFFF placeholder for unblocked 208 for (int j = j0; j < j1; ++j) 209 { 210 for (int i = i0; i < i1; ++i) 211 { 212 if (IS_PASSABLE(grid->get(i, j), passClass)) 213 m_Regions[j-j0][i-i0] = 0xFFFF; 214 else 215 m_Regions[j-j0][i-i0] = 0; 216 } 217 } 218 219 // Scan for tiles with the 0xFFFF placeholder, and then floodfill 220 // the new region this tile belongs to 221 int r = 0; 222 for (int j = 0; j < CHUNK_SIZE; ++j) 223 { 224 for (int i = 0; i < CHUNK_SIZE; ++i) 225 { 226 if (m_Regions[j][i] == 0xFFFF) 227 FloodFill(i, j, ++r); 228 } 229 } 230 231 m_NumRegions = r; 232 } 233 234 /** 235 * Flood-fill a connected area of navcells that are currently set to 236 * region 0xFFFF, starting at chunk-local coords (i0,j0), 237 * and assign them to region r. 238 */ 239 void CCmpPathfinder_Hier::Chunk::FloodFill(int i0, int j0, u16 r) 240 { 241 std::vector<std::pair<u8, u8> > stack; 242 stack.push_back(std::make_pair(i0, j0)); 243 244 while (!stack.empty()) 245 { 246 int i = stack.back().first; 247 int j = stack.back().second; 248 stack.pop_back(); 249 m_Regions[j][i] = r; 250 251 if (i > 0 && m_Regions[j][i-1] == 0xFFFF) 252 stack.push_back(std::make_pair(i-1, j)); 253 if (j > 0 && m_Regions[j-1][i] == 0xFFFF) 254 stack.push_back(std::make_pair(i, j-1)); 255 if (i < CHUNK_SIZE-1 && m_Regions[j][i+1] == 0xFFFF) 256 stack.push_back(std::make_pair(i+1, j)); 257 if (j < CHUNK_SIZE-1 && m_Regions[j+1][i] == 0xFFFF) 258 stack.push_back(std::make_pair(i, j+1)); 259 } 260 } 261 262 /** 263 * Returns a RegionID for the given global navcell coords 264 * (which must be inside this chunk); 265 */ 266 CCmpPathfinder_Hier::RegionID CCmpPathfinder_Hier::Chunk::Get(int i, int j) 267 { 268 ENSURE(i < CHUNK_SIZE && j < CHUNK_SIZE); 269 return RegionID(m_ChunkI, m_ChunkJ, m_Regions[j][i]); 270 } 271 272 /** 273 * Return the global navcell coords that correspond roughly to the 274 * center of the given region in this chunk. 275 * (This is not guaranteed to be actually inside the region.) 276 */ 277 void CCmpPathfinder_Hier::Chunk::RegionCenter(u16 r, int& i_out, int& j_out) const 278 { 279 // Find the mean of i,j coords of navcells in this region: 280 281 u32 si = 0, sj = 0; // sum of i,j coords 282 u32 n = 0; // number of navcells in region 283 284 cassert(CHUNK_SIZE < 256); // conservative limit to ensure si and sj don't overflow 285 286 for (int j = 0; j < CHUNK_SIZE; ++j) 287 { 288 for (int i = 0; i < CHUNK_SIZE; ++i) 289 { 290 if (m_Regions[j][i] == r) 291 { 292 si += i; 293 sj += j; 294 n += 1; 295 } 296 } 297 } 298 299 // Avoid divide-by-zero 300 if (n == 0) 301 n = 1; 302 303 i_out = m_ChunkI * CHUNK_SIZE + si / n; 304 j_out = m_ChunkJ * CHUNK_SIZE + sj / n; 305 } 306 307 /** 308 * Returns whether any navcell in the given region is inside the goal. 309 */ 310 bool CCmpPathfinder_Hier::Chunk::RegionContainsGoal(u16 r, const PathGoal& goal) const 311 { 312 // Inefficiently check every single navcell: 313 for (u16 j = 0; j < CHUNK_SIZE; ++j) 314 { 315 for (u16 i = 0; i < CHUNK_SIZE; ++i) 316 { 317 if (m_Regions[j][i] == r) 318 { 319 if (goal.NavcellContainsGoal(m_ChunkI * CHUNK_SIZE + i, m_ChunkJ * CHUNK_SIZE + j)) 320 return true; 321 } 322 } 323 } 324 325 return false; 326 } 327 328 /** 329 * Returns the global navcell coords, and the squared distance to the goal 330 * navcell, of whichever navcell inside the given region is closest to 331 * that goal. 332 */ 333 void CCmpPathfinder_Hier::Chunk::RegionNavcellNearest(u16 r, int iGoal, int jGoal, int& iBest, int& jBest, u32& dist2Best) const 334 { 335 iBest = 0; 336 jBest = 0; 337 dist2Best = std::numeric_limits<u32>::max(); 338 339 for (int j = 0; j < CHUNK_SIZE; ++j) 340 { 341 for (int i = 0; i < CHUNK_SIZE; ++i) 342 { 343 if (m_Regions[j][i] == r) 344 { 345 u32 dist2 = (i + m_ChunkI*CHUNK_SIZE - iGoal)*(i + m_ChunkI*CHUNK_SIZE - iGoal) 346 + (j + m_ChunkJ*CHUNK_SIZE - jGoal)*(j + m_ChunkJ*CHUNK_SIZE - jGoal); 347 348 if (dist2 < dist2Best) 349 { 350 iBest = i + m_ChunkI*CHUNK_SIZE; 351 jBest = j + m_ChunkJ*CHUNK_SIZE; 352 dist2Best = dist2; 353 } 354 } 355 } 356 } 357 } 358 359 CCmpPathfinder_Hier::CCmpPathfinder_Hier(CCmpPathfinder& pathfinder) : 360 m_Pathfinder(pathfinder) 361 { 362 m_DebugOverlay = NULL; 363 // m_DebugOverlay = new PathfinderHierOverlay(*this); 364 } 365 366 CCmpPathfinder_Hier::~CCmpPathfinder_Hier() 367 { 368 SAFE_DELETE(m_DebugOverlay); 369 } 370 371 void CCmpPathfinder_Hier::Init(const std::vector<PathfinderPassability>& passClasses, Grid<NavcellData>* grid) 372 { 373 PROFILE3("hier init"); 374 375 // Divide grid into chunks with round-to-positive-infinity 376 m_ChunksW = (grid->m_W + CHUNK_SIZE - 1) / CHUNK_SIZE; 377 m_ChunksH = (grid->m_H + CHUNK_SIZE - 1) / CHUNK_SIZE; 378 379 ENSURE(m_ChunksW < 256 && m_ChunksH < 256); // else the u8 Chunk::m_ChunkI will overflow 380 381 m_Chunks.clear(); 382 m_Edges.clear(); 383 384 for (size_t n = 0; n < passClasses.size(); ++n) 385 { 386 pass_class_t passClass = passClasses[n].m_Mask; 387 388 // Compute the regions within each chunk 389 m_Chunks[passClass].resize(m_ChunksW*m_ChunksH); 390 for (int cj = 0; cj < m_ChunksH; ++cj) 391 { 392 for (int ci = 0; ci < m_ChunksW; ++ci) 393 { 394 m_Chunks[passClass].at(cj*m_ChunksW + ci).InitRegions(ci, cj, grid, passClass); 395 } 396 } 397 398 // Construct the search graph over the regions 399 400 EdgesMap& edges = m_Edges[passClass]; 401 402 for (int cj = 0; cj < m_ChunksH; ++cj) 403 { 404 for (int ci = 0; ci < m_ChunksW; ++ci) 405 { 406 FindEdges(ci, cj, passClass, edges); 407 } 408 } 409 } 410 411 if (m_DebugOverlay) 412 { 413 PROFILE("debug overlay"); 414 m_DebugOverlayLines.clear(); 415 AddDebugEdges(m_Pathfinder.GetPassabilityClass("default")); 416 } 417 } 418 419 /** 420 * Find edges between regions in this chunk and the adjacent below/left chunks. 421 */ 422 void CCmpPathfinder_Hier::FindEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges) 423 { 424 std::vector<Chunk>& chunks = m_Chunks[passClass]; 425 426 Chunk& a = chunks.at(cj*m_ChunksW + ci); 427 428 // For each edge between chunks, we loop over every adjacent pair of 429 // navcells in the two chunks. If they are both in valid regions 430 // (i.e. are passable navcells) then add a graph edge between those regions. 431 // (We don't need to test for duplicates since EdgesMap already uses a 432 // std::set which will drop duplicate entries.) 433 434 if (ci > 0) 435 { 436 Chunk& b = chunks.at(cj*m_ChunksW + (ci-1)); 437 for (int j = 0; j < CHUNK_SIZE; ++j) 438 { 439 RegionID ra = a.Get(0, j); 440 RegionID rb = b.Get(CHUNK_SIZE-1, j); 441 if (ra.r && rb.r) 442 { 443 edges[ra].insert(rb); 444 edges[rb].insert(ra); 445 } 446 } 447 } 448 449 if (cj > 0) 450 { 451 Chunk& b = chunks.at((cj-1)*m_ChunksW + ci); 452 for (int i = 0; i < CHUNK_SIZE; ++i) 453 { 454 RegionID ra = a.Get(i, 0); 455 RegionID rb = b.Get(i, CHUNK_SIZE-1); 456 if (ra.r && rb.r) 457 { 458 edges[ra].insert(rb); 459 edges[rb].insert(ra); 460 } 461 } 462 } 463 464 } 465 466 /** 467 * Debug visualisation of graph edges between regions. 468 */ 469 void CCmpPathfinder_Hier::AddDebugEdges(pass_class_t passClass) 470 { 471 const EdgesMap& edges = m_Edges[passClass]; 472 const std::vector<Chunk>& chunks = m_Chunks[passClass]; 473 474 for (EdgesMap::const_iterator it = edges.begin(); it != edges.end(); ++it) 475 { 476 for (std::set<RegionID>::const_iterator rit = it->second.begin(); rit != it->second.end(); ++rit) 477 { 478 // Draw a line between the two regions' centers 479 480 int i0, j0, i1, j1; 481 chunks[it->first.cj * m_ChunksW + it->first.ci].RegionCenter(it->first.r, i0, j0); 482 chunks[rit->cj * m_ChunksW + rit->ci].RegionCenter(rit->r, i1, j1); 483 484 CFixedVector2D a, b; 485 m_Pathfinder.NavcellCenter(i0, j0, a.X, a.Y); 486 m_Pathfinder.NavcellCenter(i1, j1, b.X, b.Y); 487 488 // Push the endpoints inwards a little to avoid overlaps 489 CFixedVector2D d = b - a; 490 d.Normalize(entity_pos_t::FromInt(1)); 491 a += d; 492 b -= d; 493 494 std::vector<float> xz; 495 xz.push_back(a.X.ToFloat()); 496 xz.push_back(a.Y.ToFloat()); 497 xz.push_back(b.X.ToFloat()); 498 xz.push_back(b.Y.ToFloat()); 499 500 m_DebugOverlayLines.push_back(SOverlayLine()); 501 m_DebugOverlayLines.back().m_Color = CColor(1.0, 1.0, 1.0, 1.0); 502 SimRender::ConstructLineOnGround(m_Pathfinder.GetSimContext(), xz, m_DebugOverlayLines.back(), true); 503 } 504 } 505 } 506 507 CCmpPathfinder_Hier::RegionID CCmpPathfinder_Hier::Get(u16 i, u16 j, pass_class_t passClass) 508 { 509 int ci = i / CHUNK_SIZE; 510 int cj = j / CHUNK_SIZE; 511 ENSURE(ci < m_ChunksW && cj < m_ChunksH); 512 return m_Chunks[passClass][cj*m_ChunksW + ci].Get(i % CHUNK_SIZE, j % CHUNK_SIZE); 513 } 514 515 bool CCmpPathfinder_Hier::MakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass) 516 { 517 RegionID source = Get(i0, j0, passClass); 518 519 // Find everywhere that's reachable 520 std::set<RegionID> reachableRegions; 521 FindReachableRegions(source, reachableRegions, passClass); 522 523 // debug_printf(L"\nReachable from (%d,%d): ", i0, j0); 524 // for (std::set<RegionID>::iterator it = reachableRegions.begin(); it != reachableRegions.end(); ++it) 525 // debug_printf(L"[%d,%d,%d], ", it->ci, it->cj, it->r); 526 // debug_printf(L"\n"); 527 528 // Check whether any reachable region contains the goal 529 for (std::set<RegionID>::const_iterator it = reachableRegions.begin(); it != reachableRegions.end(); ++it) 530 { 531 // Skip region if its chunk doesn't contain the goal area 532 entity_pos_t x0 = ICmpObstructionManager::NAVCELL_SIZE * (it->ci * CHUNK_SIZE); 533 entity_pos_t z0 = ICmpObstructionManager::NAVCELL_SIZE * (it->cj * CHUNK_SIZE); 534 entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE * CHUNK_SIZE; 535 entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE * CHUNK_SIZE; 536 if (!goal.RectContainsGoal(x0, z0, x1, z1)) 537 continue; 538 539 // If the region contains the goal area, the goal is reachable 540 // and we don't need to move it 541 if (GetChunk(it->ci, it->cj, passClass).RegionContainsGoal(it->r, goal)) 542 return false; 543 } 544 545 // The goal area wasn't reachable, 546 // so find the navcell that's nearest to the goal's center 547 548 u16 iGoal, jGoal; 549 m_Pathfinder.NearestNavcell(goal.x, goal.z, iGoal, jGoal); 550 551 FindNearestNavcellInRegions(reachableRegions, iGoal, jGoal, passClass); 552 553 // Construct a new point goal at the nearest reachable navcell 554 PathGoal newGoal; 555 newGoal.type = PathGoal::POINT; 556 m_Pathfinder.NavcellCenter(iGoal, jGoal, newGoal.x, newGoal.z); 557 goal = newGoal; 558 559 return true; 560 } 561 562 void CCmpPathfinder_Hier::FindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass) 563 { 564 std::set<RegionID> regions; 565 FindPassableRegions(regions, passClass); 566 FindNearestNavcellInRegions(regions, i, j, passClass); 567 } 568 569 void CCmpPathfinder_Hier::FindNearestNavcellInRegions(const std::set<RegionID>& regions, u16& iGoal, u16& jGoal, pass_class_t passClass) 570 { 571 // Find the navcell in the given regions that's nearest to the goal navcell: 572 // * For each region, record the (squared) minimal distance to the goal point 573 // * Sort regions by that underestimated distance 574 // * For each region, find the actual nearest navcell 575 // * Stop when the underestimated distances are worse than the best real distance 576 577 std::vector<std::pair<u32, RegionID> > regionDistEsts; // pair of (distance^2, region) 578 579 for (std::set<RegionID>::const_iterator it = regions.begin(); it != regions.end(); ++it) 580 { 581 int i0 = it->ci * CHUNK_SIZE; 582 int j0 = it->cj * CHUNK_SIZE; 583 int i1 = i0 + CHUNK_SIZE - 1; 584 int j1 = j0 + CHUNK_SIZE - 1; 585 586 // Pick the point in the chunk nearest the goal 587 int iNear = Clamp((int)iGoal, i0, i1); 588 int jNear = Clamp((int)jGoal, j0, j1); 589 590 int dist2 = (iNear - iGoal)*(iNear - iGoal) 591 + (jNear - jGoal)*(jNear - jGoal); 592 593 regionDistEsts.push_back(std::make_pair(dist2, *it)); 594 } 595 596 // Sort by increasing distance (tie-break on RegionID) 597 std::sort(regionDistEsts.begin(), regionDistEsts.end()); 598 599 int iBest = iGoal; 600 int jBest = jGoal; 601 u32 dist2Best = std::numeric_limits<u32>::max(); 602 603 for (size_t n = 0; n < regionDistEsts.size(); ++n) 604 { 605 RegionID region = regionDistEsts[n].second; 606 607 if (regionDistEsts[n].first >= dist2Best) 608 break; 609 610 int i, j; 611 u32 dist2; 612 GetChunk(region.ci, region.cj, passClass).RegionNavcellNearest(region.r, iGoal, jGoal, i, j, dist2); 613 614 if (dist2 < dist2Best) 615 { 616 iBest = i; 617 jBest = j; 618 dist2Best = dist2; 619 } 620 } 621 622 iGoal = iBest; 623 jGoal = jBest; 624 } 625 626 void CCmpPathfinder_Hier::FindReachableRegions(RegionID from, std::set<RegionID>& reachable, pass_class_t passClass) 627 { 628 // Flood-fill the region graph, starting at 'from', 629 // collecting all the regions that are reachable via edges 630 631 std::vector<RegionID> open; 632 open.push_back(from); 633 reachable.insert(from); 634 635 while (!open.empty()) 636 { 637 RegionID curr = open.back(); 638 open.pop_back(); 639 640 const std::set<RegionID>& neighbours = m_Edges[passClass][curr]; 641 for (std::set<RegionID>::const_iterator it = neighbours.begin(); it != neighbours.end(); ++it) 642 { 643 // Add to the reachable set; if this is the first time we added 644 // it then also add it to the open list 645 if (reachable.insert(*it).second) 646 open.push_back(*it); 647 } 648 } 649 } 650 651 void CCmpPathfinder_Hier::FindPassableRegions(std::set<RegionID>& regions, pass_class_t passClass) 652 { 653 // Construct a set of all regions of all chunks for this pass class 654 655 const std::vector<Chunk>& chunks = m_Chunks[passClass]; 656 for (size_t c = 0; c < chunks.size(); ++c) 657 { 658 // Skip the impassable region r=0 659 for (int r = 1; r < chunks[c].m_NumRegions; ++r) 660 regions.insert(RegionID(chunks[c].m_ChunkI, chunks[c].m_ChunkJ, r)); 661 } 662 } 663 664 void CCmpPathfinder::PathfinderHierInit() 665 { 666 m_PathfinderHier = new CCmpPathfinder_Hier(*this); 667 } 668 669 void CCmpPathfinder::PathfinderHierDeinit() 670 { 671 SAFE_DELETE(m_PathfinderHier); 672 } 673 674 void CCmpPathfinder::PathfinderHierReload() 675 { 676 m_PathfinderHier->Init(m_PassClasses, m_Grid); 677 } 678 679 void CCmpPathfinder::PathfinderHierRenderSubmit(SceneCollector& collector) 680 { 681 for (size_t i = 0; i < m_PathfinderHier->m_DebugOverlayLines.size(); ++i) 682 collector.Submit(&m_PathfinderHier->m_DebugOverlayLines[i]); 683 } 684 685 bool CCmpPathfinder::PathfinderHierMakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass) 686 { 687 return m_PathfinderHier->MakeGoalReachable(i0, j0, goal, passClass); 688 } 689 690 void CCmpPathfinder::PathfinderHierFindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass) 691 { 692 m_PathfinderHier->FindNearestPassableNavcell(i, j, passClass); 693 } 694 No newline at end of file -
new file source/simulation2/components/CCmpPathfinder_JPS.cpp
diff --git a/source/simulation2/components/CCmpPathfinder_JPS.cpp b/source/simulation2/components/CCmpPathfinder_JPS.cpp new file mode 100644 index 0000000..c52f2e2
- + 1 /* Copyright (C) 2012 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 20 #include "CCmpPathfinder_Common.h" 21 22 #include "lib/bits.h" 23 #include "lib/sysdep/rtl.h" 24 #include "ps/Profile.h" 25 #include "renderer/TerrainOverlay.h" 26 #include "simulation2/helpers/PriorityQueue.h" 27 28 #define PATHFIND_STATS 1 29 30 #define USE_JUMPPOINT_CACHE 1 31 32 #define ACCEPT_DIAGONAL_GAPS 0 33 34 typedef PriorityQueueHeap<TileID, PathCost> PriorityQueue; 35 36 /** 37 * Jump point cache. 38 * 39 * The JPS algorithm wants to efficiently either find the first jump point 40 * in some direction from some cell (not counting the cell itself), 41 * if it is reachable without crossing any impassable cells; 42 * or know that there is no such reachable jump point. 43 * The jump point is always on a passable cell. 44 * We cache that data to allow fast lookups, which helps performance 45 * significantly (especially on sparse maps). 46 * Recalculation might be expensive but the underlying passability data 47 * changes relatively rarely. 48 * 49 * To allow the algorithm to detect goal cells, we want to treat them as 50 * jump points too. (That means the algorithm will push those cells onto 51 * its open queue, and will eventually pop a goal cell and realise it's done.) 52 * (Goals might be circles/squares/etc, not just a single cell.) 53 * But the goal generally changes for every path request, so we can't cache 54 * it like the normal jump points. 55 * Instead, if there's no jump point from some cell then we'll cache the 56 * first impassable cell as an 'obstruction jump point' 57 * (with a flag to distinguish from a real jump point), and then the caller 58 * can test whether the goal includes a cell that's closer than the first 59 * (obstruction or real) jump point, 60 * and treat the goal cell as a jump point in that case. 61 * 62 * We only ever need to find the jump point relative to a passable cell; 63 * the cache is allowed to return bogus values for impassable cells. 64 */ 65 class JumpPointCache 66 { 67 /** 68 * Simple space-inefficient row storage. 69 */ 70 struct RowRaw 71 { 72 std::vector<u16> data; 73 74 size_t GetMemoryUsage() const 75 { 76 return data.capacity() * sizeof(u16); 77 } 78 79 RowRaw(int length) 80 { 81 data.resize(length); 82 } 83 84 /** 85 * Set cells x0 <= x < x1 to have jump point x1. 86 */ 87 void SetRange(int x0, int x1, bool obstruction) 88 { 89 ENSURE(0 <= x0 && x0 <= x1 && x1 < (int)data.size()); 90 for (int x = x0; x < x1; ++x) 91 data[x] = (x1 << 1) | (obstruction ? 1 : 0); 92 } 93 94 /** 95 * Returns the coordinate of the next jump point xp (where x < xp), 96 * and whether it's an obstruction point or jump point. 97 */ 98 void Get(int x, int& xp, bool& obstruction) 99 { 100 ENSURE(0 <= x && x < (int)data.size()); 101 xp = data[x] >> 1; 102 obstruction = data[x] & 1; 103 } 104 105 void Finish() { } 106 }; 107 108 struct RowTree 109 { 110 /** 111 * Represents an interval [u15 x0, u16 x1) 112 * with a boolean obstruction flag, 113 * packed into a single u32. 114 */ 115 struct Interval 116 { 117 Interval() : data(0) { } 118 119 Interval(int x0, int x1, bool obstruction) 120 { 121 ENSURE(0 <= x0 && x0 < 0x8000); 122 ENSURE(0 <= x1 && x1 < 0x10000); 123 data = ((u32)x0 << 17) | (u32)(obstruction ? 0x10000 : 0) | (u32)x1; 124 } 125 126 int x0() { return data >> 17; } 127 int x1() { return data & 0xFFFF; } 128 bool obstruction() { return (data & 0x10000) != 0; } 129 130 u32 data; 131 }; 132 133 std::vector<Interval> data; 134 135 size_t GetMemoryUsage() const 136 { 137 return data.capacity() * sizeof(Interval); 138 } 139 140 RowTree(int UNUSED(length)) 141 { 142 } 143 144 void SetRange(int x0, int x1, bool obstruction) 145 { 146 ENSURE(0 <= x0 && x0 <= x1); 147 Interval iv(x0, x1, obstruction); 148 data.push_back(iv); 149 } 150 151 /** 152 * Recursive helper function for Finish(). 153 * Given two ranges [x0, pivot) and [pivot, x1) in the sorted array 'data', 154 * the pivot element is added onto the binary tree (stored flattened in an 155 * array), and then each range is split into two sub-ranges with a pivot in 156 * the middle (to ensure the tree remains balanced) and ConstructTree recurses. 157 */ 158 void ConstructTree(std::vector<Interval>& tree, size_t x0, size_t pivot, size_t x1, size_t idx_tree) 159 { 160 ENSURE(x0 < data.size()); 161 ENSURE(x1 <= data.size()); 162 ENSURE(x0 <= pivot); 163 ENSURE(pivot < x1); 164 ENSURE(idx_tree < tree.size()); 165 166 tree[idx_tree] = data[pivot]; 167 168 if (x0 < pivot) 169 ConstructTree(tree, x0, (x0 + pivot) / 2, pivot, (idx_tree << 1) + 1); 170 if (pivot+1 < x1) 171 ConstructTree(tree, pivot+1, (pivot + x1) / 2, x1, (idx_tree << 1) + 2); 172 } 173 174 void Finish() 175 { 176 // Convert the sorted interval list into a balanced binary tree 177 178 std::vector<Interval> tree; 179 180 if (!data.empty()) 181 { 182 size_t depth = ceil_log2(data.size() + 1); 183 tree.resize((1 << depth) - 1); 184 ConstructTree(tree, 0, data.size() / 2, data.size(), 0); 185 } 186 187 data.swap(tree); 188 } 189 190 void Get(int x, int& xp, bool& obstruction) 191 { 192 // Search the binary tree for an interval which contains x 193 int i = 0; 194 while (true) 195 { 196 ENSURE(i < (int)data.size()); 197 Interval interval = data[i]; 198 if (x < interval.x0()) 199 i = (i << 1) + 1; 200 else if (x >= interval.x1()) 201 i = (i << 1) + 2; 202 else 203 { 204 ENSURE(interval.x0() <= x && x < interval.x1()); 205 xp = interval.x1(); 206 obstruction = interval.obstruction(); 207 return; 208 } 209 } 210 } 211 }; 212 213 // Pick one of the row implementations 214 typedef RowRaw Row; 215 216 public: 217 int m_Width; 218 int m_Height; 219 std::vector<Row> m_JumpPointsRight; 220 std::vector<Row> m_JumpPointsLeft; 221 std::vector<Row> m_JumpPointsUp; 222 std::vector<Row> m_JumpPointsDown; 223 224 /** 225 * Compute the cached obstruction/jump points for each cell, 226 * in a single direction. By default the code assumes the rightwards 227 * (+i) direction; set 'transpose' to switch to upwards (+j), 228 * and/or set 'mirror' to reverse the direction. 229 */ 230 void ComputeRows(std::vector<Row>& rows, 231 const Grid<NavcellData>& terrain, ICmpPathfinder::pass_class_t passClass, 232 bool transpose, bool mirror) 233 { 234 int w = terrain.m_W; 235 int h = terrain.m_H; 236 237 if (transpose) 238 std::swap(w, h); 239 240 // Check the terrain passability, adjusted for transpose/mirror 241 #define TERRAIN_IS_PASSABLE(i, j) \ 242 IS_PASSABLE( \ 243 mirror \ 244 ? (transpose ? terrain.get((j), w-1-(i)) : terrain.get(w-1-(i), (j))) \ 245 : (transpose ? terrain.get((j), (i)) : terrain.get((i), (j))) \ 246 , passClass) 247 248 rows.reserve(h); 249 for (int j = 0; j < h; ++j) 250 rows.push_back(Row(w)); 251 252 for (int j = 1; j < h - 1; ++j) 253 { 254 // Find the first passable cell. 255 // Then, find the next jump/obstruction point after that cell, 256 // and store that point for the passable range up to that cell, 257 // then repeat. 258 259 int i = 0; 260 while (i < w) 261 { 262 // Restart the 'while' loop until we reach a passable cell 263 if (!TERRAIN_IS_PASSABLE(i, j)) 264 { 265 ++i; 266 continue; 267 } 268 269 // i is now a passable cell; find the next jump/obstruction point. 270 // (We assume the map is surrounded by impassable cells, so we don't 271 // need to explicitly check for world bounds here.) 272 273 int i0 = i; 274 while (true) 275 { 276 ++i; 277 278 // Check if we hit an obstructed tile 279 if (!TERRAIN_IS_PASSABLE(i, j)) 280 { 281 rows[j].SetRange(i0, i, true); 282 break; 283 } 284 285 // Check if we reached a jump point 286 #if ACCEPT_DIAGONAL_GAPS 287 if ((!TERRAIN_IS_PASSABLE(i, j-1) && TERRAIN_IS_PASSABLE(i+1, j-1)) || 288 (!TERRAIN_IS_PASSABLE(i, j+1) && TERRAIN_IS_PASSABLE(i+1, j+1))) 289 #else 290 if ((!TERRAIN_IS_PASSABLE(i-1, j-1) && TERRAIN_IS_PASSABLE(i, j-1)) || 291 (!TERRAIN_IS_PASSABLE(i-1, j+1) && TERRAIN_IS_PASSABLE(i, j+1))) 292 #endif 293 { 294 rows[j].SetRange(i0, i, false); 295 break; 296 } 297 } 298 } 299 300 rows[j].Finish(); 301 } 302 } 303 304 void reset(const Grid<NavcellData>* terrain, ICmpPathfinder::pass_class_t passClass) 305 { 306 PROFILE3("JumpPointCache reset"); 307 TIMER(L"JumpPointCache reset"); 308 309 m_Width = terrain->m_W; 310 m_Height = terrain->m_H; 311 312 ComputeRows(m_JumpPointsRight, *terrain, passClass, false, false); 313 ComputeRows(m_JumpPointsLeft, *terrain, passClass, false, true); 314 ComputeRows(m_JumpPointsUp, *terrain, passClass, true, false); 315 ComputeRows(m_JumpPointsDown, *terrain, passClass, true, true); 316 } 317 318 size_t GetMemoryUsage() const 319 { 320 size_t bytes = 0; 321 for (int i = 0; i < m_Width; ++i) 322 { 323 bytes += m_JumpPointsUp[i].GetMemoryUsage(); 324 bytes += m_JumpPointsDown[i].GetMemoryUsage(); 325 } 326 for (int j = 0; j < m_Height; ++j) 327 { 328 bytes += m_JumpPointsRight[j].GetMemoryUsage(); 329 bytes += m_JumpPointsLeft[j].GetMemoryUsage(); 330 } 331 return bytes; 332 } 333 334 /** 335 * Returns the next jump point (or goal point) to explore, 336 * at (ip, j) where i < ip. 337 * Returns i if there is no such point. 338 */ 339 int GetJumpPointRight(int i, int j, const PathGoal& goal) 340 { 341 int ip; 342 bool obstruction; 343 m_JumpPointsRight[j].Get(i, ip, obstruction); 344 // Adjust ip to be a goal cell, if there is one closer than the jump point; 345 // and then return the new ip if there is a goal, 346 // or the old ip if there is a (non-obstruction) jump point 347 if (goal.NavcellRectContainsGoal(i+1, j, ip-1, j, &ip, NULL) || !obstruction) 348 return ip; 349 return i; 350 } 351 352 int GetJumpPointLeft(int i, int j, const PathGoal& goal) 353 { 354 int mip; // mirrored value, because m_JumpPointsLeft is generated from a mirrored map 355 bool obstruction; 356 m_JumpPointsLeft[j].Get(m_Width-1 - i, mip, obstruction); 357 int ip = m_Width-1 - mip; 358 if (goal.NavcellRectContainsGoal(i-1, j, ip+1, j, &ip, NULL) || !obstruction) 359 return ip; 360 return i; 361 } 362 363 int GetJumpPointUp(int i, int j, const PathGoal& goal) 364 { 365 int jp; 366 bool obstruction; 367 m_JumpPointsUp[i].Get(j, jp, obstruction); 368 if (goal.NavcellRectContainsGoal(i, j+1, i, jp-1, NULL, &jp) || !obstruction) 369 return jp; 370 return j; 371 } 372 373 int GetJumpPointDown(int i, int j, const PathGoal& goal) 374 { 375 int mjp; // mirrored value 376 bool obstruction; 377 m_JumpPointsDown[i].Get(m_Height-1 - j, mjp, obstruction); 378 int jp = m_Height-1 - mjp; 379 if (goal.NavcellRectContainsGoal(i, j-1, i, jp+1, NULL, &jp) || !obstruction) 380 return jp; 381 return j; 382 } 383 }; 384 385 /** 386 * Tile data for A* computation. 387 * (We store an array of one of these per terrain tile, so it ought to be optimised for size) 388 */ 389 struct PathfindTileJPS 390 { 391 public: 392 enum { 393 STATUS_UNEXPLORED = 0, 394 STATUS_OPEN = 1, 395 STATUS_CLOSED = 2 396 }; 397 398 bool IsUnexplored() { return GetStatus() == STATUS_UNEXPLORED; } 399 bool IsOpen() { return GetStatus() == STATUS_OPEN; } 400 bool IsClosed() { return GetStatus() == STATUS_CLOSED; } 401 void SetStatusOpen() { SetStatus(STATUS_OPEN); } 402 void SetStatusClosed() { SetStatus(STATUS_CLOSED); } 403 404 // Get pi,pj coords of predecessor to this tile on best path, given i,j coords of this tile 405 int GetPredI(int i) { return i + GetPredDI(); } 406 int GetPredJ(int j) { return j + GetPredDJ(); } 407 408 PathCost GetCost() const { return g; } 409 void SetCost(PathCost cost) { g = cost; } 410 411 private: 412 PathCost g; // cost to reach this tile 413 u32 data; // 2-bit status; 15-bit PredI; 15-bit PredJ; packed for storage efficiency 414 415 public: 416 u8 GetStatus() const 417 { 418 return data & 3; 419 } 420 421 void SetStatus(u8 s) 422 { 423 ASSERT(s < 4); 424 data &= ~3; 425 data |= (s & 3); 426 } 427 428 int GetPredDI() const 429 { 430 return (i32)data >> 17; 431 } 432 433 int GetPredDJ() const 434 { 435 return ((i32)data << 15) >> 17; 436 } 437 438 // Set the pi,pj coords of predecessor, given i,j coords of this tile 439 void SetPred(int pi, int pj, int i, int j) 440 { 441 int di = pi - i; 442 int dj = pj - j; 443 ASSERT(-16384 <= di && di < 16384); 444 ASSERT(-16384 <= dj && dj < 16384); 445 data &= 3; 446 data |= (((u32)di & 0x7FFF) << 17) | (((u32)dj & 0x7FFF) << 2); 447 } 448 }; 449 450 ////////////////////////////////////////////////////////// 451 452 struct PathfinderStateJPS 453 { 454 u32 steps; // number of algorithm iterations 455 456 PathGoal goal; 457 458 u16 iGoal, jGoal; // goal tile 459 460 ICmpPathfinder::pass_class_t passClass; 461 462 PriorityQueue open; 463 // (there's no explicit closed list; it's encoded in PathfindTile) 464 465 PathfindTileJPSGrid* tiles; 466 Grid<NavcellData>* terrain; 467 468 PathCost hBest; // heuristic of closest discovered tile to goal 469 u16 iBest, jBest; // closest tile 470 471 JumpPointCache* jpc; 472 473 #if PATHFIND_STATS 474 // Performance debug counters 475 size_t numProcessed; 476 size_t numImproveOpen; 477 size_t numAddToOpen; 478 size_t sumOpenSize; 479 #endif 480 }; 481 482 // Calculate heuristic cost from tile i,j to goal 483 // (This ought to be an underestimate for correctness) 484 static PathCost CalculateHeuristic(int i, int j, int iGoal, int jGoal) 485 { 486 int di = abs(i - iGoal); 487 int dj = abs(j - jGoal); 488 int diag = std::min(di, dj); 489 return PathCost(di-diag + dj-diag, diag); 490 } 491 492 493 // Do the A* processing for a neighbour tile i,j. 494 // TODO: it'd be nice to not duplicate so much code with CCmpPathfinder_Tile.cpp 495 static void ProcessNeighbour(int pi, int pj, int i, int j, PathCost pg, PathfinderStateJPS& state) 496 { 497 #if PATHFIND_STATS 498 state.numProcessed++; 499 #endif 500 501 // Reject impassable tiles 502 if (!IS_PASSABLE(state.terrain->get(i, j), state.passClass)) 503 return; 504 505 PathfindTileJPS& n = state.tiles->get(i, j); 506 507 if (n.IsClosed()) 508 return; 509 510 PathCost dg; 511 if (pi == i) 512 dg = PathCost::horizvert(abs(pj - j)); 513 else if (pj == j) 514 dg = PathCost::horizvert(abs(pi - i)); 515 else 516 { 517 ASSERT(abs((int)pi - (int)i) == abs((int)pj - (int)j)); // must be 45 degrees 518 dg = PathCost::diag(abs((int)pi - (int)i)); 519 } 520 521 PathCost g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor 522 523 PathCost h = CalculateHeuristic(i, j, state.iGoal, state.jGoal); 524 525 // If this is a new tile, compute the heuristic distance 526 if (n.IsUnexplored()) 527 { 528 // Remember the best tile we've seen so far, in case we never actually reach the target 529 if (h < state.hBest) 530 { 531 state.hBest = h; 532 state.iBest = i; 533 state.jBest = j; 534 } 535 } 536 else 537 { 538 // If we've already seen this tile, and the new path to this tile does not have a 539 // better cost, then stop now 540 if (g >= n.GetCost()) 541 return; 542 543 // Otherwise, we have a better path. 544 545 // If we've already added this tile to the open list: 546 if (n.IsOpen()) 547 { 548 // This is a better path, so replace the old one with the new cost/parent 549 PathCost gprev = n.GetCost(); 550 n.SetCost(g); 551 n.SetPred(pi, pj, i, j); 552 state.open.promote(TileID(i, j), gprev + h, g + h); 553 #if PATHFIND_STATS 554 state.numImproveOpen++; 555 #endif 556 return; 557 } 558 } 559 560 // Add it to the open list: 561 n.SetStatusOpen(); 562 n.SetCost(g); 563 n.SetPred(pi, pj, i, j); 564 PriorityQueue::Item t = { TileID(i, j), g + h }; 565 state.open.push(t); 566 #if PATHFIND_STATS 567 state.numAddToOpen++; 568 #endif 569 } 570 571 #define PASSABLE(i, j) IS_PASSABLE(state.terrain->get(i, j), state.passClass) 572 573 /* 574 * In the JPS algorithm, after a tile is taken off the open queue, 575 * we don't process every adjacent neighbour (as in standard A*). 576 * Instead we only move in a subset of directions (depending on the 577 * direction from the predecessor); and instead of moving by a single 578 * cell, we move up to the next jump point in that direction. 579 * The AddJumped... functions do this by calling ProcessNeighbour 580 * on the jump point (if any) in a certain direction. 581 * The HasJumped... functions return whether there is any jump point 582 * in that direction. 583 */ 584 585 #if USE_JUMPPOINT_CACHE 586 587 // Use the jump-point cache to find the jump points: 588 589 static void AddJumpedHoriz(int i, int j, int di, PathCost g, PathfinderStateJPS& state) 590 { 591 int jump; 592 if (di > 0) 593 jump = state.jpc->GetJumpPointRight(i, j, state.goal); 594 else 595 jump = state.jpc->GetJumpPointLeft(i, j, state.goal); 596 597 if (jump != i) 598 ProcessNeighbour(i, j, jump, j, g, state); 599 } 600 601 static bool HasJumpedHoriz(int i, int j, int di, PathfinderStateJPS& state) 602 { 603 int jump; 604 if (di > 0) 605 jump = state.jpc->GetJumpPointRight(i, j, state.goal); 606 else 607 jump = state.jpc->GetJumpPointLeft(i, j, state.goal); 608 609 return (jump != i); 610 } 611 612 static void AddJumpedVert(int i, int j, int dj, PathCost g, PathfinderStateJPS& state) 613 { 614 int jump; 615 if (dj > 0) 616 jump = state.jpc->GetJumpPointUp(i, j, state.goal); 617 else 618 jump = state.jpc->GetJumpPointDown(i, j, state.goal); 619 620 if (jump != j) 621 ProcessNeighbour(i, j, i, jump, g, state); 622 } 623 624 static bool HasJumpedVert(int i, int j, int dj, PathfinderStateJPS& state) 625 { 626 int jump; 627 if (dj > 0) 628 jump = state.jpc->GetJumpPointUp(i, j, state.goal); 629 else 630 jump = state.jpc->GetJumpPointDown(i, j, state.goal); 631 632 return (jump != j); 633 } 634 635 #else // USE_JUMPPOINT_CACHE 636 637 // Find the jump points by scanning along the map: 638 639 static void AddJumpedHoriz(int i, int j, int di, PathCost g, PathfinderStateJPS& state) 640 { 641 ASSERT(di == 1 || di == -1); 642 int ni = i + di; 643 while (true) 644 { 645 if (!PASSABLE(ni, j)) 646 break; 647 648 if ((ni == state.iGoal && j == state.jGoal) || // XXX 649 #if ACCEPT_DIAGONAL_GAPS 650 (!PASSABLE(ni, j-1) && PASSABLE(ni+di, j-1)) || 651 (!PASSABLE(ni, j+1) && PASSABLE(ni+di, j+1))) 652 #else 653 (!PASSABLE(ni-di, j-1) && PASSABLE(ni, j-1)) || 654 (!PASSABLE(ni-di, j+1) && PASSABLE(ni, j+1))) 655 #endif 656 { 657 ProcessNeighbour(i, j, ni, j, g, state); 658 break; 659 } 660 661 ni += di; 662 } 663 } 664 665 static bool HasJumpedHoriz(int i, int j, int di, PathfinderStateJPS& state) 666 { 667 ASSERT(di == 1 || di == -1); 668 int ni = i + di; 669 while (true) 670 { 671 if (!PASSABLE(ni, j)) 672 return false; 673 674 if ((ni == state.iGoal && j == state.jGoal) || // XXX 675 #if ACCEPT_DIAGONAL_GAPS 676 (!PASSABLE(ni, j-1) && PASSABLE(ni+di, j-1)) || 677 (!PASSABLE(ni, j+1) && PASSABLE(ni+di, j+1))) 678 #else 679 (!PASSABLE(ni-di, j-1) && PASSABLE(ni, j-1)) || 680 (!PASSABLE(ni-di, j+1) && PASSABLE(ni, j+1))) 681 #endif 682 { 683 return true; 684 } 685 686 ni += di; 687 } 688 } 689 690 static void AddJumpedVert(int i, int j, int dj, PathCost g, PathfinderStateJPS& state) 691 { 692 ASSERT(dj == 1 || dj == -1); 693 int nj = j + dj; 694 while (true) 695 { 696 if (!PASSABLE(i, nj)) 697 break; 698 699 if ((i == state.iGoal && nj == state.jGoal) || 700 #if ACCEPT_DIAGONAL_GAPS 701 (!PASSABLE(i-1, nj) && PASSABLE(i-1, nj+dj)) || 702 (!PASSABLE(i+1, nj) && PASSABLE(i+1, nj+dj))) 703 #else 704 (!PASSABLE(i-1, nj-dj) && PASSABLE(i-1, nj)) || 705 (!PASSABLE(i+1, nj-dj) && PASSABLE(i+1, nj))) 706 #endif 707 { 708 ProcessNeighbour(i, j, i, nj, g, state); 709 break; 710 } 711 712 nj += dj; 713 } 714 } 715 716 static bool HasJumpedVert(int i, int j, int dj, PathfinderStateJPS& state) 717 { 718 ASSERT(dj == 1 || dj == -1); 719 int nj = j + dj; 720 while (true) 721 { 722 if (!PASSABLE(i, nj)) 723 return false; 724 725 if ((i == state.iGoal && nj == state.jGoal) || 726 #if ACCEPT_DIAGONAL_GAPS 727 (!PASSABLE(i-1, nj) && PASSABLE(i-1, nj+dj)) || 728 (!PASSABLE(i+1, nj) && PASSABLE(i+1, nj+dj))) 729 #else 730 (!PASSABLE(i-1, nj-dj) && PASSABLE(i-1, nj)) || 731 (!PASSABLE(i+1, nj-dj) && PASSABLE(i+1, nj))) 732 #endif 733 { 734 return true; 735 } 736 737 nj += dj; 738 } 739 } 740 741 #endif // USE_JUMPPOINT_CACHE 742 743 744 /* 745 * We don't cache diagonal jump points - they're usually so frequent that 746 * a linear search is about as cheap and avoids the setup cost and memory cost. 747 */ 748 static void AddJumpedDiag(int i, int j, int di, int dj, PathCost g, PathfinderStateJPS& state) 749 { 750 // ProcessNeighbour(i, j, i + di, j + dj, g, state); 751 // return; 752 753 ASSERT(di == 1 || di == -1); 754 ASSERT(dj == 1 || dj == -1); 755 756 int ni = i + di; 757 int nj = j + dj; 758 while (true) 759 { 760 // Stop if we hit an obstructed cell 761 if (!PASSABLE(ni, nj)) 762 return; 763 764 // Stop if moving onto this cell caused us to 765 // touch the corner of an obstructed cell 766 #if !ACCEPT_DIAGONAL_GAPS 767 if (!PASSABLE(ni - di, nj) || !PASSABLE(ni, nj - dj)) 768 return; 769 #endif 770 771 // Process this cell if it's at the goal 772 if (state.goal.NavcellContainsGoal(ni, nj)) 773 { 774 ProcessNeighbour(i, j, ni, nj, g, state); 775 return; 776 } 777 778 #if ACCEPT_DIAGONAL_GAPS 779 if ((!PASSABLE(ni - di, nj) && PASSABLE(ni - di, nj + dj)) || 780 (!PASSABLE(ni, nj - dj) && PASSABLE(ni + di, nj - dj))) 781 { 782 ProcessNeighbour(i, j, ni, nj, g, state); 783 return; 784 } 785 #endif 786 787 if (HasJumpedHoriz(ni, nj, di, state) || HasJumpedVert(ni, nj, dj, state)) 788 { 789 ProcessNeighbour(i, j, ni, nj, g, state); 790 return; 791 } 792 793 ni += di; 794 nj += dj; 795 } 796 } 797 798 799 void CCmpPathfinder::PathfinderJPSMakeDirty() 800 { 801 m_JumpPointCache.clear(); 802 } 803 804 void CCmpPathfinder::ComputePathJPS(entity_pos_t x0, entity_pos_t z0, const PathGoal& origGoal, pass_class_t passClass, Path& path) 805 { 806 UpdateGrid(); 807 808 PathfinderStateJPS state = { 0 }; 809 810 state.jpc = m_JumpPointCache[passClass].get(); 811 #if USE_JUMPPOINT_CACHE 812 if (!state.jpc) 813 { 814 state.jpc = new JumpPointCache; 815 state.jpc->reset(m_Grid, passClass); 816 debug_printf(L"PATHFINDER: JPC memory: %d kB\n", (int)state.jpc->GetMemoryUsage() / 1024); 817 m_JumpPointCache[passClass] = shared_ptr<JumpPointCache>(state.jpc); 818 } 819 #endif 820 821 PROFILE3("ComputePathJPS"); 822 TIMER(L"ComputePathJPS"); 823 double time = timer_Time(); 824 825 // Convert the start coordinates to tile indexes 826 u16 i0, j0; 827 NearestNavcell(x0, z0, i0, j0); 828 829 if (!IS_PASSABLE(m_Grid->get(i0, j0), passClass)) 830 { 831 // The JPS pathfinder requires units to be on passable tiles 832 // (otherwise it might crash), so handle the supposedly-invalid 833 // state specially 834 ComputePathOffImpassable(i0, j0, passClass, path); 835 return; 836 } 837 838 state.goal = origGoal; 839 PathfinderHierMakeGoalReachable(i0, j0, state.goal, passClass); 840 841 // If we're already at the goal tile, then move directly to the exact goal coordinates 842 // XXX: this seems bogus for non-point goals, it should be the point on the current cell nearest the goal 843 if (state.goal.NavcellContainsGoal(i0, j0)) 844 { 845 Waypoint w = { state.goal.x, state.goal.z }; 846 path.m_Waypoints.push_back(w); 847 return; 848 } 849 850 NearestNavcell(state.goal.x, state.goal.z, state.iGoal, state.jGoal); 851 852 state.passClass = passClass; 853 854 state.steps = 0; 855 856 state.tiles = new PathfindTileJPSGrid(m_Grid->m_W, m_Grid->m_H); 857 state.terrain = m_Grid; 858 859 state.iBest = i0; 860 state.jBest = j0; 861 state.hBest = CalculateHeuristic(i0, j0, state.iGoal, state.jGoal); 862 863 PriorityQueue::Item start = { TileID(i0, j0), PathCost() }; 864 state.open.push(start); 865 state.tiles->get(i0, j0).SetStatusOpen(); 866 state.tiles->get(i0, j0).SetPred(i0, j0, i0, j0); 867 state.tiles->get(i0, j0).SetCost(PathCost()); 868 869 while (1) 870 { 871 ++state.steps; 872 873 // If we ran out of tiles to examine, give up 874 if (state.open.empty()) 875 break; 876 877 #if PATHFIND_STATS 878 state.sumOpenSize += state.open.size(); 879 #endif 880 881 // Move best tile from open to closed 882 PriorityQueue::Item curr = state.open.pop(); 883 u16 i = curr.id.i(); 884 u16 j = curr.id.j(); 885 state.tiles->get(i, j).SetStatusClosed(); 886 887 // If we've reached the destination, stop 888 if (state.goal.NavcellContainsGoal(i, j)) 889 { 890 state.iBest = i; 891 state.jBest = j; 892 state.hBest = PathCost(); 893 break; 894 } 895 896 PathfindTileJPS tile = state.tiles->get(i, j); 897 PathCost g = tile.GetCost(); 898 899 // Get the direction of the predecessor tile from this tile 900 int dpi = tile.GetPredDI(); 901 int dpj = tile.GetPredDJ(); 902 dpi = (dpi < 0 ? -1 : dpi > 0 ? 1 : 0); 903 dpj = (dpj < 0 ? -1 : dpj > 0 ? 1 : 0); 904 905 if (dpi != 0 && dpj == 0) 906 { 907 // Moving horizontally from predecessor 908 #if ACCEPT_DIAGONAL_GAPS 909 if (!IS_PASSABLE(state.terrain->get(i, j-1), state.passClass)) 910 AddJumpedDiag(i, j, -dpi, -1, g, state); 911 if (!IS_PASSABLE(state.terrain->get(i, j+1), state.passClass)) 912 AddJumpedDiag(i, j, -dpi, +1, g, state); 913 #else 914 if (!IS_PASSABLE(state.terrain->get(i + dpi, j-1), state.passClass)) 915 { 916 AddJumpedDiag(i, j, -dpi, -1, g, state); 917 AddJumpedVert(i, j, -1, g, state); 918 } 919 if (!IS_PASSABLE(state.terrain->get(i + dpi, j+1), state.passClass)) 920 { 921 AddJumpedDiag(i, j, -dpi, +1, g, state); 922 AddJumpedVert(i, j, +1, g, state); 923 } 924 #endif 925 AddJumpedHoriz(i, j, -dpi, g, state); 926 } 927 else if (dpi == 0 && dpj != 0) 928 { 929 // Moving vertically from predecessor 930 #if ACCEPT_DIAGONAL_GAPS 931 if (!IS_PASSABLE(state.terrain->get(i-1, j), state.passClass)) 932 AddJumpedDiag(i, j, -1, -dpj, g, state); 933 if (!IS_PASSABLE(state.terrain->get(i+1, j), state.passClass)) 934 AddJumpedDiag(i, j, +1, -dpj, g, state); 935 #else 936 if (!IS_PASSABLE(state.terrain->get(i-1, j + dpj), state.passClass)) 937 { 938 AddJumpedDiag(i, j, -1, -dpj, g, state); 939 AddJumpedHoriz(i, j, -1, g, state); 940 } 941 if (!IS_PASSABLE(state.terrain->get(i+1, j + dpj), state.passClass)) 942 { 943 AddJumpedDiag(i, j, +1, -dpj, g, state); 944 AddJumpedHoriz(i, j, +1, g, state); 945 } 946 #endif 947 AddJumpedVert(i, j, -dpj, g, state); 948 } 949 else if (dpi != 0 && dpj != 0) 950 { 951 // Moving diagonally from predecessor 952 #if ACCEPT_DIAGONAL_GAPS 953 if (!IS_PASSABLE(state.terrain->get(i + dpi, j), state.passClass)) 954 AddJumpedDiag(i, j, dpi, -dpj, g, state); 955 if (!IS_PASSABLE(state.terrain->get(i, j + dpj), state.passClass)) 956 AddJumpedDiag(i, j, -dpi, dpj, g, state); 957 #endif 958 AddJumpedHoriz(i, j, -dpi, g, state); 959 AddJumpedVert(i, j, -dpj, g, state); 960 AddJumpedDiag(i, j, -dpi, -dpj, g, state); 961 } 962 else 963 { 964 // No predecessor, i.e. the start tile 965 // Start searching in every direction 966 967 // XXX - check passability? 968 969 ProcessNeighbour(i, j, (u16)(i-1), (u16)(j-1), g, state); 970 ProcessNeighbour(i, j, (u16)(i+1), (u16)(j-1), g, state); 971 ProcessNeighbour(i, j, (u16)(i-1), (u16)(j+1), g, state); 972 ProcessNeighbour(i, j, (u16)(i+1), (u16)(j+1), g, state); 973 ProcessNeighbour(i, j, (u16)(i-1), j, g, state); 974 ProcessNeighbour(i, j, (u16)(i+1), j, g, state); 975 ProcessNeighbour(i, j, i, (u16)(j-1), g, state); 976 ProcessNeighbour(i, j, i, (u16)(j+1), g, state); 977 } 978 } 979 980 // Reconstruct the path (in reverse) 981 u16 ip = state.iBest, jp = state.jBest; 982 while (ip != i0 || jp != j0) 983 { 984 PathfindTileJPS& n = state.tiles->get(ip, jp); 985 entity_pos_t x, z; 986 NavcellCenter(ip, jp, x, z); 987 Waypoint w = { x, z }; 988 path.m_Waypoints.push_back(w); 989 990 // Follow the predecessor link 991 ip = n.GetPredI(ip); 992 jp = n.GetPredJ(jp); 993 } 994 995 NormalizePathWaypoints(path); 996 997 // Save this grid for debug display 998 m_DebugTime = timer_Time() - time; 999 delete m_DebugGridJPS; 1000 m_DebugGridJPS = state.tiles; 1001 m_DebugSteps = state.steps; 1002 m_DebugGoal = state.goal; 1003 1004 PROFILE2_ATTR("from: (%d, %d)", i0, j0); 1005 PROFILE2_ATTR("to: (%d, %d)", state.iGoal, state.jGoal); 1006 PROFILE2_ATTR("reached: (%d, %d)", state.iBest, state.jBest); 1007 PROFILE2_ATTR("steps: %d", state.steps); 1008 1009 #if PATHFIND_STATS 1010 debug_printf(L"PATHFINDER: steps=%d avgo=%d proc=%d impo=%d addo=%d\n", state.steps, state.sumOpenSize/state.steps, state.numProcessed, state.numImproveOpen, state.numAddToOpen); 1011 #endif 1012 } 1013 1014 void CCmpPathfinder::GetDebugDataJPS(u32& steps, double& time, Grid<u8>& grid) 1015 { 1016 steps = m_DebugSteps; 1017 time = m_DebugTime; 1018 1019 if (!m_DebugGridJPS) 1020 return; 1021 1022 u16 iGoal, jGoal; 1023 NearestNavcell(m_DebugGoal.x, m_DebugGoal.z, iGoal, jGoal); 1024 1025 grid = Grid<u8>(m_DebugGridJPS->m_W, m_DebugGridJPS->m_H); 1026 for (u16 j = 0; j < grid.m_H; ++j) 1027 { 1028 for (u16 i = 0; i < grid.m_W; ++i) 1029 { 1030 if (i == iGoal && j == jGoal) 1031 continue; 1032 PathfindTileJPS t = m_DebugGridJPS->get(i, j); 1033 grid.set(i, j, (t.IsOpen() ? 1 : 0) | (t.IsClosed() ? 2 : 0)); 1034 } 1035 } 1036 } -
source/simulation2/components/CCmpPathfinder_Tile.cpp
diff --git a/source/simulation2/components/CCmpPathfinder_Tile.cpp b/source/simulation2/components/CCmpPathfinder_Tile.cpp index 21e5463..6c4dbbe 100644
a b 1 /* Copyright (C) 201 0Wildfire Games.1 /* Copyright (C) 2012 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … 30 30 #include "renderer/TerrainOverlay.h" 31 31 #include "simulation2/helpers/PriorityQueue.h" 32 32 33 typedef PriorityQueueHeap<std::pair<u16, u16>, u32> PriorityQueue; 33 #define PATHFIND_STATS 1 34 34 35 #define PATHFIND_STATS 0 36 37 #define USE_DIAGONAL_MOVEMENT 1 38 39 // Heuristic cost to move between adjacent tiles. 40 // This should be similar to DEFAULT_MOVE_COST. 41 const u32 g_CostPerTile = 256; 35 typedef PriorityQueueHeap<TileID, PathCost> PriorityQueue; 42 36 43 37 /** 44 38 * Tile data for A* computation. 45 * (We store an array of one of these per terrain tile, so it ought to be optimised for size)39 * (We store an array of one of these per terrain navcell, so it ought to be optimized for size) 46 40 */ 47 41 struct PathfindTile 48 42 { … … public: 63 57 u16 GetPredI(u16 i) { return (u16)(i + dpi); } 64 58 u16 GetPredJ(u16 j) { return (u16)(j + dpj); } 65 59 // Set the pi,pj coords of predecessor, given i,j coords of this tile 66 void SetPred(u16 pi _, u16 pj_, u16 i, u16 j)60 void SetPred(u16 pi, u16 pj, u16 i, u16 j) 67 61 { 68 dpi = (i8)((int)pi_ - (int)i);69 dpj = (i8)((int)pj_ - (int)j);70 62 #if PATHFIND_DEBUG 71 63 // predecessor must be adjacent 72 ENSURE(pi _-i == -1 || pi_-i == 0 || pi_-i == 1);73 ENSURE(pj _-j == -1 || pj_-j == 0 || pj_-j == 1);64 ENSURE(pi-i == -1 || pi-i == 0 || pi-i == 1); 65 ENSURE(pj-j == -1 || pj-j == 0 || pj-j == 1); 74 66 #endif 67 dpi = (i8)((int)pi - (int)i); 68 dpj = (i8)((int)pj - (int)j); 75 69 } 76 70 71 PathCost GetCost() const { return g; } 72 void SetCost(PathCost cost) { g = cost; } 73 77 74 private: 75 i8 dpi, dpj; // values are in {-1, 0, 1}, pointing to an adjacent tile 78 76 u8 status; // this only needs 2 bits 79 i8 dpi, dpj; // these only really need 2 bits in total 80 public: 81 u32 cost; // g (cost to this tile) 82 u32 h; // h (heuristic cost to goal) (TODO: is it really better for performance to store this instead of recomputing?) 77 PathCost g; // cost to reach this tile 83 78 79 public: 84 80 #if PATHFIND_DEBUG 85 81 u32 GetStep() { return step; } 86 82 void SetStep(u32 s) { step = s; } … … private: 97 93 * Terrain overlay for pathfinder debugging. 98 94 * Renders a representation of the most recent pathfinding operation. 99 95 */ 100 class PathfinderOverlay : public Terrain Overlay96 class PathfinderOverlay : public TerrainTextureOverlay 101 97 { 102 NONCOPYABLE(PathfinderOverlay);103 98 public: 104 99 CCmpPathfinder& m_Pathfinder; 105 100 106 PathfinderOverlay(CCmpPathfinder& pathfinder) 107 : TerrainOverlay(pathfinder.GetSimContext()), m_Pathfinder(pathfinder)101 PathfinderOverlay(CCmpPathfinder& pathfinder) : 102 TerrainTextureOverlay(ICmpObstructionManager::NAVCELLS_PER_TILE), m_Pathfinder(pathfinder) 108 103 { 109 104 } 110 105 111 virtual void StartRender()106 virtual void BuildTextureRGBA(u8* data, size_t w, size_t h) 112 107 { 108 // Ensure m_Pathfinder.m_Grid is up-to-date 113 109 m_Pathfinder.UpdateGrid(); 114 }115 116 virtual void ProcessTile(ssize_t i, ssize_t j)117 {118 if (m_Pathfinder.m_Grid && !IS_PASSABLE(m_Pathfinder.m_Grid->get((int)i, (int)j), m_Pathfinder.m_DebugPassClass))119 RenderTile(CColor(1, 0, 0, 0.6f), false);120 110 121 if (m_Pathfinder.m_DebugGrid) 111 // Grab the debug data for the most recently generated path 112 u32 steps; 113 double time; 114 Grid<u8> debugGrid; 115 if (m_Pathfinder.m_DebugGridJPS) 116 m_Pathfinder.GetDebugDataJPS(steps, time, debugGrid); 117 else if (m_Pathfinder.m_DebugGrid) 118 m_Pathfinder.GetDebugData(steps, time, debugGrid); 119 120 // Render navcell passability 121 u8* p = data; 122 for (size_t j = 0; j < h; ++j) 122 123 { 123 PathfindTile& n = m_Pathfinder.m_DebugGrid->get((int)i, (int)j); 124 125 float c = clamp((float)n.GetStep() / (float)m_Pathfinder.m_DebugSteps, 0.f, 1.f); 126 127 if (n.IsOpen()) 128 RenderTile(CColor(1, 1, c, 0.6f), false); 129 else if (n.IsClosed()) 130 RenderTile(CColor(0, 1, c, 0.6f), false); 124 for (size_t i = 0; i < w; ++i) 125 { 126 SColor4ub color(0, 0, 0, 0); 127 if (!IS_PASSABLE(m_Pathfinder.m_Grid->get((int)i, (int)j), m_Pathfinder.m_DebugPassClass)) 128 color = SColor4ub(255, 0, 0, 127); 129 130 if (debugGrid.m_W && debugGrid.m_H) 131 { 132 u8 n = debugGrid.get((int)i, (int)j); 133 134 if (n == 1) 135 color = SColor4ub(255, 255, 0, 127); 136 else if (n == 2) 137 color = SColor4ub(0, 255, 0, 127); 138 139 if (m_Pathfinder.m_DebugGoal.NavcellContainsGoal(i, j)) 140 color = SColor4ub(0, 0, 255, 127); 141 } 142 143 *p++ = color.R; 144 *p++ = color.G; 145 *p++ = color.B; 146 *p++ = color.A; 147 } 131 148 } 132 }133 149 134 virtual void EndRender() 135 { 136 if (m_Pathfinder.m_DebugPath) 150 // Render the most recently generated path 151 if (m_Pathfinder.m_DebugPath && !m_Pathfinder.m_DebugPath->m_Waypoints.empty()) 137 152 { 138 std::vector<ICmpPathfinder::Waypoint>& wp = m_Pathfinder.m_DebugPath->m_Waypoints; 139 for (size_t n = 0; n < wp.size(); ++n) 153 std::vector<ICmpPathfinder::Waypoint>& waypoints = m_Pathfinder.m_DebugPath->m_Waypoints; 154 u16 ip = 0, jp = 0; 155 for (size_t k = 0; k < waypoints.size(); ++k) 140 156 { 141 157 u16 i, j; 142 m_Pathfinder.NearestTile(wp[n].x, wp[n].z, i, j); 143 RenderTileOutline(CColor(1, 1, 1, 1), 2, false, i, j); 158 m_Pathfinder.NearestNavcell(waypoints[k].x, waypoints[k].z, i, j); 159 if (k == 0) 160 { 161 ip = i; 162 jp = j; 163 } 164 else 165 { 166 bool firstCell = true; 167 do 168 { 169 if (data[(jp*w + ip)*4+3] == 0) 170 { 171 data[(jp*w + ip)*4+0] = 0xFF; 172 data[(jp*w + ip)*4+1] = 0xFF; 173 data[(jp*w + ip)*4+2] = 0xFF; 174 data[(jp*w + ip)*4+3] = firstCell ? 0xA0 : 0x60; 175 } 176 ip = ip < i ? ip+1 : ip > i ? ip-1 : ip; 177 jp = jp < j ? jp+1 : jp > j ? jp-1 : jp; 178 firstCell = false; 179 } 180 while (ip != i || jp != j); 181 } 144 182 } 145 183 } 146 184 } … … void CCmpPathfinder::SetDebugOverlay(bool enabled) 159 197 } 160 198 } 161 199 162 void CCmpPathfinder::SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass)200 void CCmpPathfinder::SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass) 163 201 { 164 202 if (!m_DebugOverlay) 165 203 return; 166 204 167 delete m_DebugGrid; 168 m_DebugGrid = NULL; 205 SAFE_DELETE(m_DebugGrid); 169 206 delete m_DebugPath; 170 207 m_DebugPath = new Path(); 171 ComputePath(x0, z0, goal, passClass, costClass, *m_DebugPath); 208 #if PATHFIND_USE_JPS 209 ComputePathJPS(x0, z0, goal, passClass, *m_DebugPath); 210 #else 211 ComputePath(x0, z0, goal, passClass, *m_DebugPath); 212 #endif 172 213 m_DebugPassClass = passClass; 173 214 } 174 215 175 216 void CCmpPathfinder::ResetDebugPath() 176 217 { 177 delete m_DebugGrid; 178 m_DebugGrid = NULL; 179 delete m_DebugPath; 180 m_DebugPath = NULL; 218 SAFE_DELETE(m_DebugGrid); 219 SAFE_DELETE(m_DebugPath); 181 220 } 182 221 183 222 184 223 ////////////////////////////////////////////////////////// 185 224 186 187 225 struct PathfinderState 188 226 { 189 227 u32 steps; // number of algorithm iterations 190 228 191 229 u16 iGoal, jGoal; // goal tile 192 u16 rGoal; // radius of goal (around tile center)193 230 194 231 ICmpPathfinder::pass_class_t passClass; 195 std::vector<u32> moveCosts;196 232 197 233 PriorityQueue open; 198 234 // (there's no explicit closed list; it's encoded in PathfindTile) 199 235 200 236 PathfindTileGrid* tiles; 201 Grid<TerrainTile>* terrain; 202 203 bool ignoreImpassable; // allows us to escape if stuck in patches of impassability 237 Grid<NavcellData>* terrain; 204 238 205 u32hBest; // heuristic of closest discovered tile to goal239 PathCost hBest; // heuristic of closest discovered tile to goal 206 240 u16 iBest, jBest; // closest tile 207 241 208 242 #if PATHFIND_STATS … … struct PathfinderState 215 249 #endif 216 250 }; 217 251 218 static bool AtGoal(u16 i, u16 j, const ICmpPathfinder::Goal& goal) 219 { 220 // Allow tiles slightly more than sqrt(2) from the actual goal, 221 // i.e. adjacent diagonally to the target tile 222 fixed tolerance = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*3/2); 223 224 entity_pos_t x, z; 225 CCmpPathfinder::TileCenter(i, j, x, z); 226 fixed dist = CCmpPathfinder::DistanceToGoal(CFixedVector2D(x, z), goal); 227 return (dist < tolerance); 228 } 229 230 // Calculate heuristic cost from tile i,j to destination 252 // Calculate heuristic cost from tile i,j to goal 231 253 // (This ought to be an underestimate for correctness) 232 static u32 CalculateHeuristic(u16 i, u16 j, u16 iGoal, u16 jGoal, u16 rGoal) 233 { 234 #if USE_DIAGONAL_MOVEMENT 235 CFixedVector2D pos (fixed::FromInt(i), fixed::FromInt(j)); 236 CFixedVector2D goal (fixed::FromInt(iGoal), fixed::FromInt(jGoal)); 237 fixed dist = (pos - goal).Length(); 238 // TODO: the heuristic could match the costs better - it's not really Euclidean movement 239 240 fixed rdist = dist - fixed::FromInt(rGoal); 241 rdist = rdist.Absolute(); 242 243 // To avoid overflows on large distances we have to convert to int before multiplying 244 // by the full tile cost, which means we lose some accuracy over short distances, 245 // so do a partial multiplication here. 246 // (This will overflow if sqrt(2)*tilesPerSide*premul >= 32768, so 247 // premul=32 means max tilesPerSide=724) 248 const int premul = 32; 249 cassert(g_CostPerTile % premul == 0); 250 return (rdist * premul).ToInt_RoundToZero() * (g_CostPerTile / premul); 251 252 #else 253 return (abs((int)i - (int)iGoal) + abs((int)j - (int)jGoal)) * g_CostPerTile; 254 #endif 255 } 256 257 // Calculate movement cost from predecessor tile pi,pj to tile i,j 258 static u32 CalculateCostDelta(u16 pi, u16 pj, u16 i, u16 j, PathfindTileGrid* tempGrid, u32 tileCost) 254 static PathCost CalculateHeuristic(int i, int j, int iGoal, int jGoal) 259 255 { 260 u32 dg = tileCost; 261 262 #if USE_DIAGONAL_MOVEMENT 263 // XXX: Probably a terrible hack: 264 // For simplicity, we only consider horizontally/vertically adjacent neighbours, but 265 // units can move along arbitrary lines. That results in ugly square paths, so we want 266 // to prefer diagonal paths. 267 // Instead of solving this nicely, I'll just special-case 45-degree and 30-degree lines 268 // by checking the three predecessor tiles (which'll be in the closed set and therefore 269 // likely to be reasonably stable) and reducing the cost, and use a Euclidean heuristic. 270 // At least this makes paths look a bit nicer for now... 271 272 PathfindTile& p = tempGrid->get(pi, pj); 273 u16 ppi = p.GetPredI(pi); 274 u16 ppj = p.GetPredJ(pj); 275 if (ppi != i && ppj != j) 276 dg = (dg << 16) / 92682; // dg*sqrt(2)/2 277 else 278 { 279 PathfindTile& pp = tempGrid->get(ppi, ppj); 280 int di = abs(i - pp.GetPredI(ppi)); 281 int dj = abs(j - pp.GetPredJ(ppj)); 282 if ((di == 1 && dj == 2) || (di == 2 && dj == 1)) 283 dg = (dg << 16) / 79742; // dg*(sqrt(5)-sqrt(2)) 284 } 285 #endif 286 287 return dg; 256 int di = abs(i - iGoal); 257 int dj = abs(j - jGoal); 258 int diag = std::min(di, dj); 259 return PathCost(di-diag + dj-diag, diag); 288 260 } 289 261 290 262 // Do the A* processing for a neighbour tile i,j. 291 static void ProcessNeighbour( u16 pi, u16 pj, u16 i, u16 j, u32pg, PathfinderState& state)263 static void ProcessNeighbour(int pi, int pj, int i, int j, PathCost pg, PathfinderState& state) 292 264 { 293 265 #if PATHFIND_STATS 294 266 state.numProcessed++; 295 267 #endif 296 268 269 PathfindTile& n = state.tiles->get(i, j); 270 271 if (n.IsClosed()) 272 return; 273 297 274 // Reject impassable tiles 298 TerrainTile tileTag = state.terrain->get(i, j); 299 if (!IS_PASSABLE(tileTag, state.passClass) && !state.ignoreImpassable) 275 if (!IS_PASSABLE(state.terrain->get(i, j), state.passClass)) 300 276 return; 277 // Also, diagonal moves are only allowed if the adjacent tiles 278 // are also unobstructed 279 if (pi != i && pj != j) 280 { 281 if (!IS_PASSABLE(state.terrain->get(pi, j), state.passClass)) 282 return; 283 if (!IS_PASSABLE(state.terrain->get(i, pj), state.passClass)) 284 return; 285 } 301 286 302 u32 dg = CalculateCostDelta(pi, pj, i, j, state.tiles, state.moveCosts.at(GET_COST_CLASS(tileTag))); 287 PathCost dg; 288 if (pi == i || pj == j) 289 dg = PathCost::horizvert(1); 290 else 291 dg = PathCost::diag(1); 303 292 304 u32g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor293 PathCost g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor 305 294 306 Path findTile& n = state.tiles->get(i, j);295 PathCost h = CalculateHeuristic(i, j, state.iGoal, state.jGoal); 307 296 308 297 // If this is a new tile, compute the heuristic distance 309 298 if (n.IsUnexplored()) 310 299 { 311 n.h = CalculateHeuristic(i, j, state.iGoal, state.jGoal, state.rGoal);312 300 // Remember the best tile we've seen so far, in case we never actually reach the target 313 if ( n.h < state.hBest)301 if (h < state.hBest) 314 302 { 315 state.hBest = n.h;303 state.hBest = h; 316 304 state.iBest = i; 317 305 state.jBest = j; 318 306 } … … static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderSta 321 309 { 322 310 // If we've already seen this tile, and the new path to this tile does not have a 323 311 // better cost, then stop now 324 if (g >= n. cost)312 if (g >= n.GetCost()) 325 313 return; 326 314 327 315 // Otherwise, we have a better path. … … static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderSta 330 318 if (n.IsOpen()) 331 319 { 332 320 // This is a better path, so replace the old one with the new cost/parent 333 n.cost = g; 321 PathCost gprev = n.GetCost(); 322 n.SetCost(g); 334 323 n.SetPred(pi, pj, i, j); 335 324 n.SetStep(state.steps); 336 state.open.promote( std::make_pair(i, j), g + n.h);325 state.open.promote(TileID(i, j), gprev + h, g + h); 337 326 #if PATHFIND_STATS 338 327 state.numImproveOpen++; 339 328 #endif 340 329 return; 341 330 } 342 331 332 #if PATHFIND_STATS 343 333 // If we've already found the 'best' path to this tile: 344 334 if (n.IsClosed()) 345 335 { 346 336 // This is a better path (possible when we use inadmissible heuristics), so reopen it 347 #if PATHFIND_STATS 337 // by falling through 348 338 state.numImproveClosed++; 349 #endif350 // (fall through)351 339 } 352 340 } 341 #endif 353 342 354 343 // Add it to the open list: 355 344 n.SetStatusOpen(); 356 n. cost = g;345 n.SetCost(g); 357 346 n.SetPred(pi, pj, i, j); 358 347 n.SetStep(state.steps); 359 PriorityQueue::Item t = { std::make_pair(i, j), g + n.h };348 PriorityQueue::Item t = { TileID(i, j), g + h }; 360 349 state.open.push(t); 361 350 #if PATHFIND_STATS 362 351 state.numAddToOpen++; 363 352 #endif 364 353 } 365 354 366 void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& path)355 void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const PathGoal& origGoal, pass_class_t passClass, Path& path) 367 356 { 368 357 UpdateGrid(); 369 358 370 359 PROFILE3("ComputePath"); 360 TIMER(L"ComputePath"); 361 double time = timer_Time(); 371 362 372 363 PathfinderState state = { 0 }; 373 364 374 365 // Convert the start/end coordinates to tile indexes 375 366 u16 i0, j0; 376 NearestTile(x0, z0, i0, j0); 377 NearestTile(goal.x, goal.z, state.iGoal, state.jGoal); 367 NearestNavcell(x0, z0, i0, j0); 368 369 // To be consistent with the JPS pathfinder (which requires passable source navcell), 370 // and to let us guarantee the goal is reachable from the source, we switch to 371 // the escape-from-impassability mode if currently on an impassable navcell 372 if (!IS_PASSABLE(m_Grid->get(i0, j0), passClass)) 373 { 374 ComputePathOffImpassable(i0, j0, passClass, path); 375 return; 376 } 377 378 // Adjust the goal so that it's reachable from the source navcell 379 PathGoal goal = origGoal; 380 PathfinderHierMakeGoalReachable(i0, j0, goal, passClass); 378 381 379 382 // If we're already at the goal tile, then move directly to the exact goal coordinates 380 if (AtGoal(i0, j0, goal)) 383 // XXX: this seems bogus for non-point goals, it should be the point on the current cell nearest the goal 384 if (goal.NavcellContainsGoal(i0, j0)) 381 385 { 382 386 Waypoint w = { goal.x, goal.z }; 383 387 path.m_Waypoints.push_back(w); 384 388 return; 385 389 } 386 390 387 // If the target is a circle, we want to aim for the edge of it (so e.g. if we're inside 388 // a large circle then the heuristics will aim us directly outwards); 389 // otherwise just aim at the center point. (We'll never try moving outwards to a square shape.) 390 if (goal.type == Goal::CIRCLE) 391 state.rGoal = (u16)(goal.hw / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(); 392 else 393 state.rGoal = 0; 391 // Store the navcell at the goal center, for A* heuristics 392 NearestNavcell(goal.x, goal.z, state.iGoal, state.jGoal); 394 393 395 394 state.passClass = passClass; 396 state.moveCosts = m_MoveCosts.at(costClass);397 395 398 396 state.steps = 0; 399 397 400 state.tiles = new PathfindTileGrid(m_ MapSize, m_MapSize);398 state.tiles = new PathfindTileGrid(m_Grid->m_W, m_Grid->m_H); 401 399 state.terrain = m_Grid; 402 400 403 401 state.iBest = i0; 404 402 state.jBest = j0; 405 state.hBest = CalculateHeuristic(i0, j0, state.iGoal, state.jGoal , state.rGoal);403 state.hBest = CalculateHeuristic(i0, j0, state.iGoal, state.jGoal); 406 404 407 PriorityQueue::Item start = { std::make_pair(i0, j0), 0};405 PriorityQueue::Item start = { TileID(i0, j0), PathCost() }; 408 406 state.open.push(start); 409 407 state.tiles->get(i0, j0).SetStatusOpen(); 410 408 state.tiles->get(i0, j0).SetPred(i0, j0, i0, j0); 411 state.tiles->get(i0, j0).cost = 0; 412 413 // To prevent units getting very stuck, if they start on an impassable tile 414 // surrounded entirely by impassable tiles, we ignore the impassability 415 state.ignoreImpassable = !IS_PASSABLE(state.terrain->get(i0, j0), state.passClass); 409 state.tiles->get(i0, j0).SetCost(PathCost()); 416 410 417 411 while (1) 418 412 { 419 413 ++state.steps; 420 414 421 // Hack to avoid spending ages computing giant paths, particularly when422 // the destination is unreachable423 if (state.steps > 40000)424 break;425 426 415 // If we ran out of tiles to examine, give up 427 416 if (state.open.empty()) 428 417 break; … … void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g 433 422 434 423 // Move best tile from open to closed 435 424 PriorityQueue::Item curr = state.open.pop(); 436 u16 i = curr.id. first;437 u16 j = curr.id. second;425 u16 i = curr.id.i(); 426 u16 j = curr.id.j(); 438 427 state.tiles->get(i, j).SetStatusClosed(); 439 428 440 429 // If we've reached the destination, stop 441 if ( AtGoal(i, j, goal))430 if (goal.NavcellContainsGoal(i, j)) 442 431 { 443 432 state.iBest = i; 444 433 state.jBest = j; 445 state.hBest = 0;434 state.hBest = PathCost(); 446 435 break; 447 436 } 448 437 449 // As soon as we find an escape route from the impassable terrain, 450 // take it and forbid any further use of impassable tiles 451 if (state.ignoreImpassable) 452 { 453 if (i > 0 && IS_PASSABLE(state.terrain->get(i-1, j), state.passClass)) 454 state.ignoreImpassable = false; 455 else if (i < m_MapSize-1 && IS_PASSABLE(state.terrain->get(i+1, j), state.passClass)) 456 state.ignoreImpassable = false; 457 else if (j > 0 && IS_PASSABLE(state.terrain->get(i, j-1), state.passClass)) 458 state.ignoreImpassable = false; 459 else if (j < m_MapSize-1 && IS_PASSABLE(state.terrain->get(i, j+1), state.passClass)) 460 state.ignoreImpassable = false; 461 } 462 463 u32 g = state.tiles->get(i, j).cost; 464 if (i > 0) 465 ProcessNeighbour(i, j, (u16)(i-1), j, g, state); 466 if (i < m_MapSize-1) 467 ProcessNeighbour(i, j, (u16)(i+1), j, g, state); 468 if (j > 0) 469 ProcessNeighbour(i, j, i, (u16)(j-1), g, state); 470 if (j < m_MapSize-1) 471 ProcessNeighbour(i, j, i, (u16)(j+1), g, state); 438 PathCost g = state.tiles->get(i, j).GetCost(); 439 440 // Try all 8 neighbors 441 ProcessNeighbour(i, j, i-1, j-1, g, state); 442 ProcessNeighbour(i, j, i+1, j-1, g, state); 443 ProcessNeighbour(i, j, i-1, j+1, g, state); 444 ProcessNeighbour(i, j, i+1, j+1, g, state); 445 ProcessNeighbour(i, j, i-1, j, g, state); 446 ProcessNeighbour(i, j, i+1, j, g, state); 447 ProcessNeighbour(i, j, i, j-1, g, state); 448 ProcessNeighbour(i, j, i, j+1, g, state); 472 449 } 473 450 474 451 // Reconstruct the path (in reverse) … … void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g 477 454 { 478 455 PathfindTile& n = state.tiles->get(ip, jp); 479 456 entity_pos_t x, z; 480 TileCenter(ip, jp, x, z);457 NavcellCenter(ip, jp, x, z); 481 458 Waypoint w = { x, z }; 482 459 path.m_Waypoints.push_back(w); 483 460 … … void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g 486 463 jp = n.GetPredJ(jp); 487 464 } 488 465 466 NormalizePathWaypoints(path); 467 489 468 // Save this grid for debug display 469 m_DebugTime = timer_Time() - time; 490 470 delete m_DebugGrid; 491 471 m_DebugGrid = state.tiles; 492 472 m_DebugSteps = state.steps; 473 m_DebugGoal = goal; 493 474 494 475 PROFILE2_ATTR("from: (%d, %d)", i0, j0); 495 476 PROFILE2_ATTR("to: (%d, %d)", state.iGoal, state.jGoal); … … void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& g 497 478 PROFILE2_ATTR("steps: %u", state.steps); 498 479 499 480 #if PATHFIND_STATS 500 printf("PATHFINDER: steps=%d avgo=%d proc=%d impc=%d impo=%d addo=%d\n", state.steps, state.sumOpenSize/state.steps, state.numProcessed, state.numImproveClosed, state.numImproveOpen, state.numAddToOpen);481 debug_printf(L"PATHFINDER: steps=%d avgo=%d proc=%d impc=%d impo=%d addo=%d\n", state.steps, state.sumOpenSize/state.steps, state.numProcessed, state.numImproveClosed, state.numImproveOpen, state.numAddToOpen); 501 482 #endif 502 483 } 484 485 void CCmpPathfinder::GetDebugData(u32& steps, double& time, Grid<u8>& grid) 486 { 487 steps = m_DebugSteps; 488 time = m_DebugTime; 489 490 if (!m_DebugGrid) 491 return; 492 493 grid = Grid<u8>(m_DebugGrid->m_W, m_DebugGrid->m_H); 494 for (u16 j = 0; j < grid.m_H; ++j) 495 { 496 for (u16 i = 0; i < grid.m_W; ++i) 497 { 498 PathfindTile t = m_DebugGrid->get(i, j); 499 grid.set(i, j, (t.IsOpen() ? 1 : 0) | (t.IsClosed() ? 2 : 0)); 500 } 501 } 502 } -
source/simulation2/components/CCmpPathfinder_Vertex.cpp
diff --git a/source/simulation2/components/CCmpPathfinder_Vertex.cpp b/source/simulation2/components/CCmpPathfinder_Vertex.cpp index b2f0171..4b85e7a 100644
a b struct Vertex 101 101 }; 102 102 103 103 // Obstruction edges (paths will not cross any of these). 104 // When used in the 'edges' list, defines the two points of the edge. 105 // When used in the 'edgesAA' list, defines the opposing corners of an axis-aligned square 106 // (from which four individual edges can be trivially computed), requiring p0 <= p1 104 // Defines the two points of the edge. 107 105 struct Edge 108 106 { 109 107 CFixedVector2D p0, p1; 110 108 }; 111 109 110 // Axis-aligned obstruction squares (paths will not cross any of these). 111 // Defines the opposing corners of an axis-aligned square 112 // (from which four individual edges can be trivially computed), requiring p0 <= p1 113 struct Square 114 { 115 CFixedVector2D p0, p1; 116 }; 117 112 118 // Axis-aligned obstruction edges. 113 119 // p0 defines one end; c1 is either the X or Y coordinate of the other end, 114 120 // depending on the context in which this is used. … … inline static bool CheckVisibilityTop(CFixedVector2D a, CFixedVector2D b, const 284 290 } 285 291 286 292 287 static CFixedVector2D NearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal)293 static CFixedVector2D NearestPointOnGoal(CFixedVector2D pos, const PathGoal& goal) 288 294 { 289 295 CFixedVector2D g(goal.x, goal.z); 290 296 291 297 switch (goal.type) 292 298 { 293 case CCmpPathfinder::Goal::POINT:299 case PathGoal::POINT: 294 300 { 295 301 return g; 296 302 } 297 303 298 case CCmpPathfinder::Goal::CIRCLE: 304 case PathGoal::CIRCLE: 305 case PathGoal::INVERTED_CIRCLE: 299 306 { 300 307 CFixedVector2D d = pos - g; 301 308 if (d.IsZero()) … … static CFixedVector2D NearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinde 304 311 return g + d; 305 312 } 306 313 307 case CCmpPathfinder::Goal::SQUARE: 314 case PathGoal::SQUARE: 315 case PathGoal::INVERTED_SQUARE: 308 316 { 309 317 CFixedVector2D halfSize(goal.hw, goal.hh); 310 318 CFixedVector2D d = pos - g; … … static CFixedVector2D NearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinde 317 325 } 318 326 } 319 327 320 CFixedVector2D CCmpPathfinder::GetNearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal)328 CFixedVector2D CCmpPathfinder::GetNearestPointOnGoal(CFixedVector2D pos, const PathGoal& goal) 321 329 { 322 330 return NearestPointOnGoal(pos, goal); 323 331 // (It's intentional that we don't put the implementation inside this … … CFixedVector2D CCmpPathfinder::GetNearestPointOnGoal(CFixedVector2D pos, const C 327 335 328 336 typedef PriorityQueueHeap<u16, fixed> PriorityQueue; 329 337 330 struct TileEdge 331 { 332 u16 i, j; 333 enum { TOP, BOTTOM, LEFT, RIGHT } dir; 334 }; 335 336 static void AddTerrainEdges(std::vector<Edge>& edgesAA, std::vector<Vertex>& vertexes, 337 u16 i0, u16 j0, u16 i1, u16 j1, fixed r, 338 ICmpPathfinder::pass_class_t passClass, const Grid<TerrainTile>& terrain) 338 /** 339 * Add edges and vertexes to represent the boundaries between passable and impassable 340 * navcells (for impassable terrain and for static obstruction shapes). 341 * Navcells i0 <= i <= i1, j0 <= j <= j1 will be considered. 342 */ 343 static void AddTerrainEdges(std::vector<Edge>& edges, std::vector<Vertex>& vertexes, 344 int i0, int j0, int i1, int j1, 345 ICmpPathfinder::pass_class_t passClass, const Grid<NavcellData>& grid) 339 346 { 340 347 PROFILE("AddTerrainEdges"); 341 348 342 std::vector<TileEdge> tileEdges; 349 // Clamp the coordinates so we won't attempt to sample outside of the grid. 350 // (This assumes the outermost ring of navcells (which are always impassable) 351 // won't have a boundary with any passable navcells. TODO: is that definitely 352 // safe enough?) 343 353 344 // Find all edges between tiles of differently passability statuses 345 for (u16 j = j0; j <= j1; ++j) 354 i0 = clamp(i0, 1, grid.m_W-2); 355 j0 = clamp(j0, 1, grid.m_H-2); 356 i1 = clamp(i1, 1, grid.m_W-2); 357 j1 = clamp(j1, 1, grid.m_H-2); 358 359 for (int j = j0; j <= j1; ++j) 346 360 { 347 for ( u16i = i0; i <= i1; ++i)361 for (int i = i0; i <= i1; ++i) 348 362 { 349 if (!IS_TERRAIN_PASSABLE(terrain.get(i, j), passClass)) 363 if (IS_PASSABLE(grid.get(i, j), passClass)) 364 continue; 365 366 if (IS_PASSABLE(grid.get(i+1, j), passClass) && IS_PASSABLE(grid.get(i, j+1), passClass) && IS_PASSABLE(grid.get(i+1, j+1), passClass)) 350 367 { 351 bool any = false; // whether we're adding any edges of this tile 368 Vertex vert; 369 vert.status = Vertex::UNEXPLORED; 370 vert.quadOutward = QUADRANT_ALL; 371 vert.quadInward = QUADRANT_BL; 372 vert.p = CFixedVector2D(fixed::FromInt(i+1)+EDGE_EXPAND_DELTA, fixed::FromInt(j+1)+EDGE_EXPAND_DELTA).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 373 vertexes.push_back(vert); 374 } 352 375 353 if (j > 0 && IS_TERRAIN_PASSABLE(terrain.get(i, j-1), passClass)) 354 { 355 TileEdge e = { i, j, TileEdge::BOTTOM }; 356 tileEdges.push_back(e); 357 any = true; 358 } 376 if (IS_PASSABLE(grid.get(i-1, j), passClass) && IS_PASSABLE(grid.get(i, j+1), passClass) && IS_PASSABLE(grid.get(i-1, j+1), passClass)) 377 { 378 Vertex vert; 379 vert.status = Vertex::UNEXPLORED; 380 vert.quadOutward = QUADRANT_ALL; 381 vert.quadInward = QUADRANT_BR; 382 vert.p = CFixedVector2D(fixed::FromInt(i)-EDGE_EXPAND_DELTA, fixed::FromInt(j+1)+EDGE_EXPAND_DELTA).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 383 vertexes.push_back(vert); 384 } 359 385 360 if (j < terrain.m_H-1 && IS_TERRAIN_PASSABLE(terrain.get(i, j+1), passClass)) 361 { 362 TileEdge e = { i, j, TileEdge::TOP }; 363 tileEdges.push_back(e); 364 any = true; 365 } 386 if (IS_PASSABLE(grid.get(i+1, j), passClass) && IS_PASSABLE(grid.get(i, j-1), passClass) && IS_PASSABLE(grid.get(i+1, j-1), passClass)) 387 { 388 Vertex vert; 389 vert.status = Vertex::UNEXPLORED; 390 vert.quadOutward = QUADRANT_ALL; 391 vert.quadInward = QUADRANT_TL; 392 vert.p = CFixedVector2D(fixed::FromInt(i+1)+EDGE_EXPAND_DELTA, fixed::FromInt(j)-EDGE_EXPAND_DELTA).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 393 vertexes.push_back(vert); 394 } 366 395 367 if (i > 0 && IS_TERRAIN_PASSABLE(terrain.get(i-1, j), passClass)) 368 { 369 TileEdge e = { i, j, TileEdge::LEFT }; 370 tileEdges.push_back(e); 371 any = true; 372 } 396 if (IS_PASSABLE(grid.get(i-1, j), passClass) && IS_PASSABLE(grid.get(i, j-1), passClass) && IS_PASSABLE(grid.get(i-1, j-1), passClass)) 397 { 398 Vertex vert; 399 vert.status = Vertex::UNEXPLORED; 400 vert.quadOutward = QUADRANT_ALL; 401 vert.quadInward = QUADRANT_TR; 402 vert.p = CFixedVector2D(fixed::FromInt(i)-EDGE_EXPAND_DELTA, fixed::FromInt(j)-EDGE_EXPAND_DELTA).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 403 vertexes.push_back(vert); 404 } 405 } 406 } 407 408 // XXX rewrite this stuff 409 410 for (int j = j0; j < j1; ++j) 411 { 412 std::vector<u16> segmentsR; 413 std::vector<u16> segmentsL; 414 415 for (int i = i0; i <= i1; ++i) 416 { 417 bool a = IS_PASSABLE(grid.get(i, j+1), passClass); 418 bool b = IS_PASSABLE(grid.get(i, j), passClass); 419 if (a && !b) 420 segmentsL.push_back(i); 421 if (b && !a) 422 segmentsR.push_back(i); 423 } 373 424 374 if (i < terrain.m_W-1 && IS_TERRAIN_PASSABLE(terrain.get(i+1, j), passClass)) 425 if (!segmentsR.empty()) 426 { 427 segmentsR.push_back(0); // sentinel value to simplify the loop 428 u16 ia = segmentsR[0]; 429 u16 ib = ia + 1; 430 for (size_t n = 1; n < segmentsR.size(); ++n) 431 { 432 if (segmentsR[n] == ib) 433 ++ib; 434 else 375 435 { 376 TileEdge e = { i, j, TileEdge::RIGHT }; 377 tileEdges.push_back(e); 378 any = true; 436 CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(ia), fixed::FromInt(j+1)).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 437 CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(ib), fixed::FromInt(j+1)).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 438 Edge e = { v0, v1 }; 439 edges.push_back(e); 440 441 ia = segmentsR[n]; 442 ib = ia + 1; 379 443 } 444 } 445 } 380 446 381 // If we want to add any edge, then add the whole square to the axis-aligned-edges list. 382 // (The inner edges are redundant but it's easier than trying to split the squares apart.) 383 if (any) 447 if (!segmentsL.empty()) 448 { 449 segmentsL.push_back(0); // sentinel value to simplify the loop 450 u16 ia = segmentsL[0]; 451 u16 ib = ia + 1; 452 for (size_t n = 1; n < segmentsL.size(); ++n) 453 { 454 if (segmentsL[n] == ib) 455 ++ib; 456 else 384 457 { 385 CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);386 CFixedVector2D v1 = CFixedVector2D(fixed::FromInt( (i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);458 CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(ib), fixed::FromInt(j+1)).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 459 CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(ia), fixed::FromInt(j+1)).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 387 460 Edge e = { v0, v1 }; 388 edgesAA.push_back(e); 461 edges.push_back(e); 462 463 ia = segmentsL[n]; 464 ib = ia + 1; 389 465 } 390 466 } 391 467 } 392 468 } 393 469 394 // TODO: maybe we should precompute these terrain edges since they'll rarely change? 395 396 // TODO: for efficiency (minimising the A* search space), we should coalesce adjoining edges 397 398 // Add all the tile outer edges to the search vertex lists 399 for (size_t n = 0; n < tileEdges.size(); ++n) 470 for (int i = i0; i < i1; ++i) 400 471 { 401 u16 i = tileEdges[n].i; 402 u16 j = tileEdges[n].j; 403 CFixedVector2D v0, v1; 404 Vertex vert; 405 vert.status = Vertex::UNEXPLORED; 406 vert.quadOutward = QUADRANT_ALL; 472 std::vector<u16> segmentsU; 473 std::vector<u16> segmentsD; 407 474 408 switch (tileEdges[n].dir) 409 { 410 case TileEdge::BOTTOM: 411 { 412 v0 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r); 413 v1 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r); 414 vert.p.X = v0.X - EDGE_EXPAND_DELTA; vert.p.Y = v0.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TR; vertexes.push_back(vert); 415 vert.p.X = v1.X + EDGE_EXPAND_DELTA; vert.p.Y = v1.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TL; vertexes.push_back(vert); 416 break; 417 } 418 case TileEdge::TOP: 475 for (int j = j0; j <= j1; ++j) 419 476 { 420 v0 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r); 421 v1 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r); 422 vert.p.X = v0.X + EDGE_EXPAND_DELTA; vert.p.Y = v0.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BL; vertexes.push_back(vert); 423 vert.p.X = v1.X - EDGE_EXPAND_DELTA; vert.p.Y = v1.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BR; vertexes.push_back(vert); 424 break; 477 bool a = IS_PASSABLE(grid.get(i+1, j), passClass); 478 bool b = IS_PASSABLE(grid.get(i, j), passClass); 479 if (a && !b) 480 segmentsU.push_back(j); 481 if (b && !a) 482 segmentsD.push_back(j); 425 483 } 426 case TileEdge::LEFT: 484 485 if (!segmentsU.empty()) 427 486 { 428 v0 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r); 429 v1 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r); 430 vert.p.X = v0.X - EDGE_EXPAND_DELTA; vert.p.Y = v0.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BR; vertexes.push_back(vert); 431 vert.p.X = v1.X - EDGE_EXPAND_DELTA; vert.p.Y = v1.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TR; vertexes.push_back(vert); 432 break; 487 segmentsU.push_back(0); // sentinel value to simplify the loop 488 u16 ja = segmentsU[0]; 489 u16 jb = ja + 1; 490 for (size_t n = 1; n < segmentsU.size(); ++n) 491 { 492 if (segmentsU[n] == jb) 493 ++jb; 494 else 495 { 496 CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(ja)).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 497 CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(jb)).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 498 Edge e = { v0, v1 }; 499 edges.push_back(e); 500 501 ja = segmentsU[n]; 502 jb = ja + 1; 503 } 504 } 433 505 } 434 case TileEdge::RIGHT: 506 507 if (!segmentsD.empty()) 435 508 { 436 v0 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r); 437 v1 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r); 438 vert.p.X = v0.X + EDGE_EXPAND_DELTA; vert.p.Y = v0.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TL; vertexes.push_back(vert); 439 vert.p.X = v1.X + EDGE_EXPAND_DELTA; vert.p.Y = v1.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BL; vertexes.push_back(vert); 440 break; 441 } 509 segmentsD.push_back(0); // sentinel value to simplify the loop 510 u16 ja = segmentsD[0]; 511 u16 jb = ja + 1; 512 for (size_t n = 1; n < segmentsD.size(); ++n) 513 { 514 if (segmentsD[n] == jb) 515 ++jb; 516 else 517 { 518 CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(jb)).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 519 CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(ja)).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 520 Edge e = { v0, v1 }; 521 edges.push_back(e); 522 523 ja = segmentsD[n]; 524 jb = ja + 1; 525 } 526 } 442 527 } 443 528 } 444 529 } 445 530 446 531 static void SplitAAEdges(CFixedVector2D a, 447 const std::vector<Edge>& edgesAA, 532 const std::vector<Edge>& edges, 533 const std::vector<Square>& squares, 534 std::vector<Edge>& edgesUnaligned, 448 535 std::vector<EdgeAA>& edgesLeft, std::vector<EdgeAA>& edgesRight, 449 536 std::vector<EdgeAA>& edgesBottom, std::vector<EdgeAA>& edgesTop) 450 537 { 451 edgesLeft.reserve( edgesAA.size());452 edgesRight.reserve( edgesAA.size());453 edgesBottom.reserve( edgesAA.size());454 edgesTop.reserve( edgesAA.size());538 edgesLeft.reserve(squares.size()); 539 edgesRight.reserve(squares.size()); 540 edgesBottom.reserve(squares.size()); 541 edgesTop.reserve(squares.size()); 455 542 456 for (size_t i = 0; i < edgesAA.size(); ++i)543 for (size_t i = 0; i < squares.size(); ++i) 457 544 { 458 if (a.X <= edgesAA[i].p0.X)545 if (a.X <= squares[i].p0.X) 459 546 { 460 EdgeAA e = { edgesAA[i].p0, edgesAA[i].p1.Y };547 EdgeAA e = { squares[i].p0, squares[i].p1.Y }; 461 548 edgesLeft.push_back(e); 462 549 } 463 if (a.X >= edgesAA[i].p1.X)550 if (a.X >= squares[i].p1.X) 464 551 { 465 EdgeAA e = { edgesAA[i].p1, edgesAA[i].p0.Y };552 EdgeAA e = { squares[i].p1, squares[i].p0.Y }; 466 553 edgesRight.push_back(e); 467 554 } 468 if (a.Y <= edgesAA[i].p0.Y)555 if (a.Y <= squares[i].p0.Y) 469 556 { 470 EdgeAA e = { edgesAA[i].p0, edgesAA[i].p1.X };557 EdgeAA e = { squares[i].p0, squares[i].p1.X }; 471 558 edgesBottom.push_back(e); 472 559 } 473 if (a.Y >= edgesAA[i].p1.Y)560 if (a.Y >= squares[i].p1.Y) 474 561 { 475 EdgeAA e = { edgesAA[i].p1, edgesAA[i].p0.X };562 EdgeAA e = { squares[i].p1, squares[i].p0.X }; 476 563 edgesTop.push_back(e); 477 564 } 478 565 } 566 567 for (size_t i = 0; i < edges.size(); ++i) 568 { 569 if (edges[i].p0.X == edges[i].p1.X) 570 { 571 if (edges[i].p1.Y < edges[i].p0.Y) 572 { 573 if (!(a.X <= edges[i].p0.X)) 574 continue; 575 EdgeAA e = { edges[i].p1, edges[i].p0.Y }; 576 edgesLeft.push_back(e); 577 } 578 else 579 { 580 if (!(a.X >= edges[i].p0.X)) 581 continue; 582 EdgeAA e = { edges[i].p1, edges[i].p0.Y }; 583 edgesRight.push_back(e); 584 } 585 } 586 else if (edges[i].p0.Y == edges[i].p1.Y) 587 { 588 if (edges[i].p0.X < edges[i].p1.X) 589 { 590 if (!(a.Y <= edges[i].p0.Y)) 591 continue; 592 EdgeAA e = { edges[i].p0, edges[i].p1.X }; 593 edgesBottom.push_back(e); 594 } 595 else 596 { 597 if (!(a.Y >= edges[i].p0.Y)) 598 continue; 599 EdgeAA e = { edges[i].p0, edges[i].p1.X }; 600 edgesTop.push_back(e); 601 } 602 } 603 else 604 { 605 edgesUnaligned.push_back(edges[i]); 606 } 607 } 479 608 } 480 609 481 610 /** 482 * Functor for sorting edge s by approximate proximity to a fixed point.611 * Functor for sorting edge-squares by approximate proximity to a fixed point. 483 612 */ 484 struct EdgeSort613 struct SquareSort 485 614 { 486 615 CFixedVector2D src; 487 EdgeSort(CFixedVector2D src) : src(src) { }488 bool operator()(const Edge& a, const Edge& b)616 SquareSort(CFixedVector2D src) : src(src) { } 617 bool operator()(const Square& a, const Square& b) 489 618 { 490 619 if ((a.p0 - src).CompareLength(b.p0 - src) < 0) 491 620 return true; … … struct EdgeSort 495 624 496 625 void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 497 626 entity_pos_t x0, entity_pos_t z0, entity_pos_t r, 498 entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& path)627 entity_pos_t range, const PathGoal& goal, pass_class_t passClass, Path& path) 499 628 { 500 629 UpdateGrid(); // TODO: only need to bother updating if the terrain changed 501 630 502 631 PROFILE3("ComputeShortPath"); 503 // ScopeTimer UID__(L"ComputeShortPath");632 TIMER(L"ComputeShortPath"); 504 633 505 634 m_DebugOverlayShortPathLines.clear(); 506 635 … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 511 640 m_DebugOverlayShortPathLines.back().m_Color = CColor(1, 0, 0, 1); 512 641 switch (goal.type) 513 642 { 514 case CCmpPathfinder::Goal::POINT:643 case PathGoal::POINT: 515 644 { 516 645 SimRender::ConstructCircleOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), 0.2f, m_DebugOverlayShortPathLines.back(), true); 517 646 break; 518 647 } 519 case CCmpPathfinder::Goal::CIRCLE: 648 case PathGoal::CIRCLE: 649 case PathGoal::INVERTED_CIRCLE: 520 650 { 521 651 SimRender::ConstructCircleOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), goal.hw.ToFloat(), m_DebugOverlayShortPathLines.back(), true); 522 652 break; 523 653 } 524 case CCmpPathfinder::Goal::SQUARE: 654 case PathGoal::SQUARE: 655 case PathGoal::INVERTED_SQUARE: 525 656 { 526 657 float a = atan2f(goal.v.X.ToFloat(), goal.v.Y.ToFloat()); 527 658 SimRender::ConstructSquareOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), goal.hw.ToFloat()*2, goal.hh.ToFloat()*2, a, m_DebugOverlayShortPathLines.back(), true); … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 533 664 // List of collision edges - paths must never cross these. 534 665 // (Edges are one-sided so intersections are fine in one direction, but not the other direction.) 535 666 std::vector<Edge> edges; 536 std::vector< Edge> edgesAA; // axis-aligned squares667 std::vector<Square> edgeSquares; // axis-aligned squares; equivalent to 4 edges 537 668 538 669 // Create impassable edges at the max-range boundary, so we can't escape the region 539 670 // where we're meant to be searching … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 574 705 // Add terrain obstructions 575 706 { 576 707 u16 i0, j0, i1, j1; 577 Nearest Tile(rangeXMin, rangeZMin, i0, j0);578 Nearest Tile(rangeXMax, rangeZMax, i1, j1);579 AddTerrainEdges(edges AA, vertexes, i0, j0, i1, j1, r, passClass, *m_Grid);708 NearestNavcell(rangeXMin, rangeZMin, i0, j0); 709 NearestNavcell(rangeXMax, rangeZMax, i1, j1); 710 AddTerrainEdges(edges, vertexes, i0, j0, i1, j1, passClass, *m_Grid); 580 711 } 581 712 582 713 // Find all the obstruction squares that might affect us … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 586 717 587 718 // Resize arrays to reduce reallocations 588 719 vertexes.reserve(vertexes.size() + squares.size()*4); 589 edge sAA.reserve(edgesAA.size() + squares.size()); // (assume most squares are AA)720 edgeSquares.reserve(edgeSquares.size() + squares.size()); // (assume most squares are AA) 590 721 591 722 // Convert each obstruction square into collision edges and search graph vertexes 592 723 for (size_t i = 0; i < squares.size(); ++i) … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 615 746 616 747 // Add the edges: 617 748 618 CFixedVector2D h0(squares[i].hw + r, squares[i].hh + r);749 CFixedVector2D h0(squares[i].hw + r, squares[i].hh + r); 619 750 CFixedVector2D h1(squares[i].hw + r, -(squares[i].hh + r)); 620 751 621 752 CFixedVector2D ev0(center.X - h0.Dot(u), center.Y + h0.Dot(v)); … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 624 755 CFixedVector2D ev3(center.X + h1.Dot(u), center.Y - h1.Dot(v)); 625 756 if (aa) 626 757 { 627 Edge e = { ev1, ev3 };628 edge sAA.push_back(e);758 Square e = { ev1, ev3 }; 759 edgeSquares.push_back(e); 629 760 } 630 761 else 631 762 { … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 645 776 646 777 ENSURE(vertexes.size() < 65536); // we store array indexes as u16 647 778 779 // Render the debug overlay 648 780 if (m_DebugOverlay) 649 781 { 650 // Render the obstruction edges 782 #define PUSH_POINT(p) xz.push_back(p.X.ToFloat()); xz.push_back(p.Y.ToFloat()); 783 // Render the vertexes as little Pac-Man shapes to indicate quadrant direction 784 for (size_t i = 0; i < vertexes.size(); ++i) 785 { 786 m_DebugOverlayShortPathLines.push_back(SOverlayLine()); 787 m_DebugOverlayShortPathLines.back().m_Color = CColor(1, 1, 0, 1); 788 789 float x = vertexes[i].p.X.ToFloat(); 790 float z = vertexes[i].p.Y.ToFloat(); 791 792 float a0 = 0, a1 = 0; 793 // Get arc start/end angles depending on quadrant (if any) 794 if (vertexes[i].quadInward == QUADRANT_BL) { a0 = -0.25f; a1 = 0.50f; } 795 else if (vertexes[i].quadInward == QUADRANT_TR) { a0 = 0.25f; a1 = 1.00f; } 796 else if (vertexes[i].quadInward == QUADRANT_TL) { a0 = -0.50f; a1 = 0.25f; } 797 else if (vertexes[i].quadInward == QUADRANT_BR) { a0 = 0.00f; a1 = 0.75f; } 798 799 if (a0 == a1) 800 SimRender::ConstructCircleOnGround(GetSimContext(), x, z, 0.5f, 801 m_DebugOverlayShortPathLines.back(), true); 802 else 803 SimRender::ConstructClosedArcOnGround(GetSimContext(), x, z, 0.5f, 804 a0 * ((float)M_PI*2.0f), a1 * ((float)M_PI*2.0f), 805 m_DebugOverlayShortPathLines.back(), true); 806 } 807 808 // Render the edges 651 809 for (size_t i = 0; i < edges.size(); ++i) 652 810 { 653 811 m_DebugOverlayShortPathLines.push_back(SOverlayLine()); 654 812 m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1); 655 813 std::vector<float> xz; 656 xz.push_back(edges[i].p0.X.ToFloat()); 657 xz.push_back(edges[i].p0.Y.ToFloat()); 658 xz.push_back(edges[i].p1.X.ToFloat()); 659 xz.push_back(edges[i].p1.Y.ToFloat()); 814 PUSH_POINT(edges[i].p0); 815 PUSH_POINT(edges[i].p1); 816 817 // Add an arrowhead to indicate the direction 818 CFixedVector2D d = edges[i].p1 - edges[i].p0; 819 d.Normalize(fixed::FromInt(1)/8); 820 CFixedVector2D p2 = edges[i].p1 - d*2; 821 CFixedVector2D p3 = p2 + d.Perpendicular(); 822 CFixedVector2D p4 = p2 - d.Perpendicular(); 823 PUSH_POINT(p3); 824 PUSH_POINT(p4); 825 PUSH_POINT(edges[i].p1); 826 660 827 SimRender::ConstructLineOnGround(GetSimContext(), xz, m_DebugOverlayShortPathLines.back(), true); 661 828 } 829 #undef PUSH_POINT 662 830 663 for (size_t i = 0; i < edgesAA.size(); ++i) 831 // Render the axis-aligned squares 832 for (size_t i = 0; i < edgeSquares.size(); ++i) 664 833 { 665 834 m_DebugOverlayShortPathLines.push_back(SOverlayLine()); 666 835 m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1); 667 836 std::vector<float> xz; 668 xz.push_back(edgesAA[i].p0.X.ToFloat()); 669 xz.push_back(edgesAA[i].p0.Y.ToFloat()); 670 xz.push_back(edgesAA[i].p0.X.ToFloat()); 671 xz.push_back(edgesAA[i].p1.Y.ToFloat()); 672 xz.push_back(edgesAA[i].p1.X.ToFloat()); 673 xz.push_back(edgesAA[i].p1.Y.ToFloat()); 674 xz.push_back(edgesAA[i].p1.X.ToFloat()); 675 xz.push_back(edgesAA[i].p0.Y.ToFloat()); 676 xz.push_back(edgesAA[i].p0.X.ToFloat()); 677 xz.push_back(edgesAA[i].p0.Y.ToFloat()); 837 Square s = edgeSquares[i]; 838 xz.push_back(s.p0.X.ToFloat()); 839 xz.push_back(s.p0.Y.ToFloat()); 840 xz.push_back(s.p0.X.ToFloat()); 841 xz.push_back(s.p1.Y.ToFloat()); 842 xz.push_back(s.p1.X.ToFloat()); 843 xz.push_back(s.p1.Y.ToFloat()); 844 xz.push_back(s.p1.X.ToFloat()); 845 xz.push_back(s.p0.Y.ToFloat()); 846 xz.push_back(s.p0.X.ToFloat()); 847 xz.push_back(s.p0.Y.ToFloat()); 678 848 SimRender::ConstructLineOnGround(GetSimContext(), xz, m_DebugOverlayShortPathLines.back(), true); 679 849 } 680 850 } … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 714 884 715 885 // Sort the edges so ones nearer this vertex are checked first by CheckVisibility, 716 886 // since they're more likely to block the rays 717 std::sort(edge sAA.begin(), edgesAA.end(), EdgeSort(vertexes[curr.id].p));887 std::sort(edgeSquares.begin(), edgeSquares.end(), SquareSort(vertexes[curr.id].p)); 718 888 889 std::vector<Edge> edgesUnaligned; 719 890 std::vector<EdgeAA> edgesLeft; 720 891 std::vector<EdgeAA> edgesRight; 721 892 std::vector<EdgeAA> edgesBottom; 722 893 std::vector<EdgeAA> edgesTop; 723 SplitAAEdges(vertexes[curr.id].p, edgesAA, edgesLeft, edgesRight, edgesBottom, edgesTop); 894 SplitAAEdges(vertexes[curr.id].p, edges, edgeSquares, edgesUnaligned, edgesLeft, edgesRight, edgesBottom, edgesTop); 895 //debug_printf(L"edges: e=%d aa=%d; u=%d l=%d r=%d b=%d t=%d\n", edges.size(), edgeSquares.size(), edgesUnaligned.size(), edgesLeft.size(), edgesRight.size(), edgesBottom.size(), edgesTop.size()); 724 896 725 897 // Check the lines to every other vertex 726 898 for (size_t n = 0; n < vertexes.size(); ++n) … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 768 940 CheckVisibilityRight(vertexes[curr.id].p, npos, edgesRight) && 769 941 CheckVisibilityBottom(vertexes[curr.id].p, npos, edgesBottom) && 770 942 CheckVisibilityTop(vertexes[curr.id].p, npos, edgesTop) && 771 CheckVisibility(vertexes[curr.id].p, npos, edges );943 CheckVisibility(vertexes[curr.id].p, npos, edgesUnaligned); 772 944 773 945 /* 774 946 // Render the edges that we examine … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 792 964 // Add it to the open list: 793 965 vertexes[n].status = Vertex::OPEN; 794 966 vertexes[n].g = g; 795 vertexes[n].h = DistanceToGoal(npos, goal);967 vertexes[n].h = goal.DistanceToPoint(npos); 796 968 vertexes[n].pred = curr.id; 797 969 798 970 // If this is an axis-aligned shape, the path must continue in the same quadrant … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 823 995 continue; 824 996 825 997 // Otherwise, we have a better path, so replace the old one with the new cost/parent 998 fixed gprev = vertexes[n].g; 826 999 vertexes[n].g = g; 827 1000 vertexes[n].pred = curr.id; 828 1001 … … void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter, 834 1007 if (n == GOAL_VERTEX_ID) 835 1008 vertexes[n].p = npos; // remember the new best goal position 836 1009 837 open.promote((u16)n, g + vertexes[n].h);1010 open.promote((u16)n, gprev + vertexes[n].h, g + vertexes[n].h); 838 1011 } 839 1012 } 840 1013 } … … bool CCmpPathfinder::CheckMovement(const IObstructionTestFilter& filter, 854 1027 entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, 855 1028 pass_class_t passClass) 856 1029 { 1030 // Test against dynamic obstructions first 1031 857 1032 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); 858 1033 if (!cmpObstructionManager) 859 1034 return false; … … bool CCmpPathfinder::CheckMovement(const IObstructionTestFilter& filter, 861 1036 if (cmpObstructionManager->TestLine(filter, x0, z0, x1, z1, r)) 862 1037 return false; 863 1038 864 // Test against terrain: 1039 // Test against the passability grid. 1040 // This should ignore r, and just check that the line (x0,z0)-(x1,z1) 1041 // does not intersect any impassable navcells. 1042 // We shouldn't allow lines between diagonally-adjacent navcells. 1043 // It doesn't matter whether we allow lines precisely along the edge 1044 // of an impassable navcell. 865 1045 866 // (TODO: this could probably be a tiny bit faster by not reusing all the vertex computation code) 1046 // To rasterise the line: 1047 // If the line is (e.g.) aiming up-right, then we start at the navcell 1048 // containing the start point and the line must either end in that navcell 1049 // or else exit along the top edge or the right edge (or through the top-right corner, 1050 // which we'll arbitrary treat as the horizontal edge). 1051 // So we jump into the adjacent navcell across that edge, and continue. 867 1052 868 UpdateGrid(); 1053 // To handle the special case of units that are stuck on impassable cells, 1054 // we allow them to move from an impassable to a passable cell (but not 1055 // vice versa). 869 1056 870 std::vector<Edge> edgesAA; 871 std::vector<Vertex> vertexes; 1057 UpdateGrid(); 872 1058 873 1059 u16 i0, j0, i1, j1; 874 NearestTile(std::min(x0, x1) - r, std::min(z0, z1) - r, i0, j0); 875 NearestTile(std::max(x0, x1) + r, std::max(z0, z1) + r, i1, j1); 876 AddTerrainEdges(edgesAA, vertexes, i0, j0, i1, j1, r, passClass, *m_Grid); 877 878 CFixedVector2D a(x0, z0); 879 CFixedVector2D b(x1, z1); 880 881 std::vector<EdgeAA> edgesLeft; 882 std::vector<EdgeAA> edgesRight; 883 std::vector<EdgeAA> edgesBottom; 884 std::vector<EdgeAA> edgesTop; 885 SplitAAEdges(a, edgesAA, edgesLeft, edgesRight, edgesBottom, edgesTop); 886 887 bool visible = 888 CheckVisibilityLeft(a, b, edgesLeft) && 889 CheckVisibilityRight(a, b, edgesRight) && 890 CheckVisibilityBottom(a, b, edgesBottom) && 891 CheckVisibilityTop(a, b, edgesTop); 892 893 return visible; 1060 NearestNavcell(x0, z0, i0, j0); 1061 NearestNavcell(x1, z1, i1, j1); 1062 1063 // Find which direction the line heads in 1064 int di = (i0 < i1 ? +1 : i1 < i0 ? -1 : 0); 1065 int dj = (j0 < j1 ? +1 : j1 < j0 ? -1 : 0); 1066 1067 u16 i = i0; 1068 u16 j = j0; 1069 1070 // debug_printf(L"(%f,%f)..(%f,%f) [%d,%d]..[%d,%d]\n", x0.ToFloat(), z0.ToFloat(), x1.ToFloat(), z1.ToFloat(), i0, j0, i1, j1); 1071 1072 bool currentlyOnImpassable = !IS_PASSABLE(m_Grid->get(i0, j0), passClass); 1073 1074 while (true) 1075 { 1076 // Fail if we're moving onto an impassable navcell 1077 bool passable = IS_PASSABLE(m_Grid->get(i, j), passClass); 1078 if (passable) 1079 currentlyOnImpassable = false; 1080 else if (!currentlyOnImpassable) 1081 return false; 1082 1083 // Succeed if we're at the target 1084 if (i == i1 && j == j1) 1085 return true; 1086 1087 // If we can only move horizontally/vertically, then just move in that direction 1088 if (di == 0) 1089 { 1090 j += dj; 1091 continue; 1092 } 1093 else if (dj == 0) 1094 { 1095 i += di; 1096 continue; 1097 } 1098 1099 // Otherwise we need to check which cell to move into: 1100 1101 // Check whether the line intersects the horizontal (top/bottom) edge of 1102 // the current navcell. 1103 // Horizontal edge is (i, j + (dj>0?1:0)) .. (i + 1, j + (dj>0?1:0)) 1104 // Since we already know the line is moving from this navcell into a different 1105 // navcell, we simply need to test that the edge's endpoints are not both on the 1106 // same side of the line. 1107 1108 entity_pos_t xia = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 1109 entity_pos_t xib = entity_pos_t::FromInt(i+1).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 1110 entity_pos_t zj = entity_pos_t::FromInt(j + (dj+1)/2).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 1111 1112 CFixedVector2D perp = CFixedVector2D(x1 - x0, z1 - z0).Perpendicular(); 1113 entity_pos_t dota = (CFixedVector2D(xia, zj) - CFixedVector2D(x0, z0)).Dot(perp); 1114 entity_pos_t dotb = (CFixedVector2D(xib, zj) - CFixedVector2D(x0, z0)).Dot(perp); 1115 1116 // debug_printf(L"(%f,%f)-(%f,%f) :: %f %f\n", xia.ToFloat(), zj.ToFloat(), xib.ToFloat(), zj.ToFloat(), dota.ToFloat(), dotb.ToFloat()); 1117 1118 if ((dota < entity_pos_t::Zero() && dotb < entity_pos_t::Zero()) || 1119 (dota > entity_pos_t::Zero() && dotb > entity_pos_t::Zero())) 1120 { 1121 // Horizontal edge is fully on one side of the line, so the line doesn't 1122 // intersect it, so we should move across the vertical edge instead 1123 i += di; 1124 } 1125 else 1126 { 1127 j += dj; 1128 } 1129 } 894 1130 } -
source/simulation2/components/CCmpRallyPointRenderer.cpp
diff --git a/source/simulation2/components/CCmpRallyPointRenderer.cpp b/source/simulation2/components/CCmpRallyPointRenderer.cpp index 8612c15..4ba7828 100644
a b class CCmpRallyPointRenderer : public ICmpRallyPointRenderer 69 69 { 70 70 // import some types for less verbosity 71 71 typedef ICmpPathfinder::Path Path; 72 typedef ICmpPathfinder::Goal Goal;72 typedef PathGoal Goal; 73 73 typedef ICmpPathfinder::Waypoint Waypoint; 74 74 typedef ICmpRangeManager::CLosQuerier CLosQuerier; 75 75 typedef SOverlayTexturedLine::LineCapType LineCapType; … … protected: 115 115 std::wstring m_LineTexturePath; 116 116 std::wstring m_LineTextureMaskPath; 117 117 std::string m_LinePassabilityClass; ///< Pathfinder passability class to use for computing the (long-range) marker line path. 118 std::string m_LineCostClass; ///< Pathfinder cost class to use for computing the (long-range) marker line path.119 118 120 119 CTexturePtr m_Texture; 121 120 CTexturePtr m_TextureMask; … … public: 141 140 "<LineEndCap>square</LineEndCap>" 142 141 "<LineColour r='20' g='128' b='240'></LineColour>" 143 142 "<LineDashColour r='158' g='11' b='15'></LineDashColour>" 144 "<LineCostClass>default</LineCostClass>"145 143 "<LinePassabilityClass>default</LinePassabilityClass>" 146 144 "</a:example>" 147 145 "<element name='MarkerTemplate' a:help='Template name for the rally point marker entity (typically a waypoint flag actor)'>" … … public: 196 194 "</element>" 197 195 "<element name='LinePassabilityClass' a:help='The pathfinder passability class to use for computing the rally point marker line path'>" 198 196 "<text />" 199 "</element>"200 "<element name='LineCostClass' a:help='The pathfinder cost class to use for computing the rally point marker line path'>"201 "<text />"202 197 "</element>"; 203 198 } 204 199 … … void CCmpRallyPointRenderer::Init(const CParamNode& paramNode) 440 435 m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString(); 441 436 m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString()); 442 437 m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString()); 443 m_LineCostClass = paramNode.GetChild("LineCostClass").ToUTF8();444 438 m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8(); 445 439 446 440 // --------------------------------------------------------------------------------------------- … … void CCmpRallyPointRenderer::RecomputeRallyPointPath(size_t index, CmpPtr<ICmpPo 612 606 pathStartY, 613 607 goal, 614 608 cmpPathfinder->GetPassabilityClass(m_LinePassabilityClass), 615 cmpPathfinder->GetCostClass(m_LineCostClass),616 609 path 617 610 ); 618 611 -
source/simulation2/components/CCmpRangeManager.cpp
diff --git a/source/simulation2/components/CCmpRangeManager.cpp b/source/simulation2/components/CCmpRangeManager.cpp index 9c74edf..047338b 100644
a b 22 22 23 23 #include "simulation2/MessageTypes.h" 24 24 #include "simulation2/components/ICmpPosition.h" 25 #include "simulation2/components/ICmpObstructionManager.h" 25 26 #include "simulation2/components/ICmpTerritoryManager.h" 26 27 #include "simulation2/components/ICmpVision.h" 27 28 #include "simulation2/helpers/Render.h" … … public: 1072 1073 if (!cmpTerritoryManager || !cmpTerritoryManager->NeedUpdate(&m_TerritoriesDirtyID)) 1073 1074 return; 1074 1075 1076 PROFILE3("UpdateTerritoriesLos"); 1077 1075 1078 const Grid<u8>& grid = cmpTerritoryManager->GetTerritoryGrid(); 1076 ENSURE(grid.m_W == m_TerrainVerticesPerSide-1 && grid.m_H == m_TerrainVerticesPerSide-1);1077 1079 1078 // For each tile, if it is owned by a valid player then update the LOS 1079 // for every vertex around that tile, to mark them as explored 1080 // Territory data is stored per territory-tile (typically a multiple of terrain-tiles). 1081 // LOS data is stored per terrain-tile vertex. 1082 1083 // For each territory-tile, if it is owned by a valid player then update the LOS 1084 // for every vertex inside/around that tile, to mark them as explored. 1085 1086 // Currently this code doesn't support territory-tiles smaller than terrain-tiles 1087 // (it will get scale==0 and break), or a non-integer multiple, so check that first 1088 cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE >= ICmpObstructionManager::NAVCELLS_PER_TILE); 1089 cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE % ICmpObstructionManager::NAVCELLS_PER_TILE == 0); 1090 1091 int scale = ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / ICmpObstructionManager::NAVCELLS_PER_TILE; 1092 1093 ENSURE(grid.m_W*scale == m_TerrainVerticesPerSide-1 && grid.m_H*scale == m_TerrainVerticesPerSide-1); 1080 1094 1081 1095 for (u16 j = 0; j < grid.m_H; ++j) 1082 1096 { … … public: 1085 1099 u8 p = grid.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK; 1086 1100 if (p > 0 && p <= MAX_LOS_PLAYER_ID) 1087 1101 { 1088 m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1))); 1089 m_LosState[i+1 + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1))); 1090 m_LosState[i + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1))); 1091 m_LosState[i+1 + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1))); 1102 for (int dj = 0; dj <= scale; ++dj) 1103 for (int di = 0; di <= scale; ++di) 1104 m_LosState[(i*scale+di) + (j*scale+dj)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1))); 1092 1105 } 1093 1106 } 1094 1107 } -
source/simulation2/components/CCmpTerritoryManager.cpp
diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp index c8b1ec7..dc75fe1 100644
a b 48 48 49 49 class CCmpTerritoryManager; 50 50 51 class TerritoryOverlay : public Terrain Overlay51 class TerritoryOverlay : public TerrainTextureOverlay 52 52 { 53 53 NONCOPYABLE(TerritoryOverlay); 54 54 public: 55 55 CCmpTerritoryManager& m_TerritoryManager; 56 56 57 57 TerritoryOverlay(CCmpTerritoryManager& manager); 58 virtual void StartRender(); 59 virtual void ProcessTile(ssize_t i, ssize_t j); 58 virtual void BuildTextureRGBA(u8* data, size_t w, size_t h); 60 59 }; 61 60 62 61 class CCmpTerritoryManager : public ICmpTerritoryManager … … public: 269 268 270 269 REGISTER_COMPONENT_TYPE(TerritoryManager) 271 270 271 /** 272 * Compute the tile indexes on the grid nearest to a given point 273 */ 274 static void NearestTerritoryTile(entity_pos_t x, entity_pos_t z, int& i, int& j, int w, int h) 275 { 276 i = clamp((x / (ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE)).ToInt_RoundToNegInfinity(), 0, w-1); 277 j = clamp((z / (ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE)).ToInt_RoundToNegInfinity(), 0, h-1); 278 } 279 280 /** 281 * Returns the position of the center of the given tile 282 */ 283 static void TerritoryTileCenter(int i, int j, entity_pos_t& x, entity_pos_t& z) 284 { 285 x = entity_pos_t::FromInt(i*2 + 1).Multiply(ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / 2); 286 z = entity_pos_t::FromInt(j*2 + 1).Multiply(ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / 2); 287 } 288 272 289 /* 273 290 We compute the territory influence of an entity with a kind of best-first search, 274 291 storing an 'open' list of tiles that have not yet been processed, … … adjusted by terrain movement cost), and repeating until all tiles are processed. 279 296 280 297 typedef PriorityQueueHeap<std::pair<u16, u16>, u32, std::greater<u32> > OpenQueue; 281 298 282 static void ProcessNeighbour(u32 falloff, u16 i, u16j, u32 pg, bool diagonal,299 static void ProcessNeighbour(u32 falloff, int i, int j, u32 pg, bool diagonal, 283 300 Grid<u32>& grid, OpenQueue& queue, const Grid<u8>& costGrid) 284 301 { 285 302 u32 dg = falloff * costGrid.get(i, j); … … static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal, 294 311 u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor 295 312 296 313 grid.set(i, j, g); 297 OpenQueue::Item tile = { std::make_pair( i,j), g };314 OpenQueue::Item tile = { std::make_pair((u16)i, (u16)j), g }; 298 315 queue.push(tile); 299 316 } 300 317 301 318 static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles, u32 falloff) 302 319 { 303 u16tilesW = grid.m_W;304 u16tilesH = grid.m_H;320 int tilesW = grid.m_W; 321 int tilesH = grid.m_H; 305 322 306 323 while (!openTiles.empty()) 307 324 { 308 325 OpenQueue::Item tile = openTiles.pop(); 309 326 310 327 // Process neighbours (if they're not off the edge of the map) 311 u16x = tile.id.first;312 u16z = tile.id.second;328 int x = tile.id.first; 329 int z = tile.id.second; 313 330 if (x > 0) 314 ProcessNeighbour(falloff, (u16)(x-1), z, tile.rank, false, grid, openTiles, costGrid);331 ProcessNeighbour(falloff, x-1, z, tile.rank, false, grid, openTiles, costGrid); 315 332 if (x < tilesW-1) 316 ProcessNeighbour(falloff, (u16)(x+1), z, tile.rank, false, grid, openTiles, costGrid);333 ProcessNeighbour(falloff, x+1, z, tile.rank, false, grid, openTiles, costGrid); 317 334 if (z > 0) 318 ProcessNeighbour(falloff, x, (u16)(z-1), tile.rank, false, grid, openTiles, costGrid);335 ProcessNeighbour(falloff, x, z-1, tile.rank, false, grid, openTiles, costGrid); 319 336 if (z < tilesH-1) 320 ProcessNeighbour(falloff, x, (u16)(z+1), tile.rank, false, grid, openTiles, costGrid);337 ProcessNeighbour(falloff, x, z+1, tile.rank, false, grid, openTiles, costGrid); 321 338 if (x > 0 && z > 0) 322 ProcessNeighbour(falloff, (u16)(x-1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);339 ProcessNeighbour(falloff, x-1, z-1, tile.rank, true, grid, openTiles, costGrid); 323 340 if (x > 0 && z < tilesH-1) 324 ProcessNeighbour(falloff, (u16)(x-1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);341 ProcessNeighbour(falloff, x-1, z+1, tile.rank, true, grid, openTiles, costGrid); 325 342 if (x < tilesW-1 && z > 0) 326 ProcessNeighbour(falloff, (u16)(x+1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);343 ProcessNeighbour(falloff, x+1, z-1, tile.rank, true, grid, openTiles, costGrid); 327 344 if (x < tilesW-1 && z < tilesH-1) 328 ProcessNeighbour(falloff, (u16)(x+1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);345 ProcessNeighbour(falloff, x+1, z+1, tile.rank, true, grid, openTiles, costGrid); 329 346 } 330 347 } 331 348 … … void CCmpTerritoryManager::CalculateTerritories() 343 360 if (!cmpTerrain->IsLoaded()) 344 361 return; 345 362 346 u16 tilesW = cmpTerrain->GetTilesPerSide(); 347 u16 tilesH = cmpTerrain->GetTilesPerSide(); 363 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); 364 ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("territory"); 365 ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted"); 366 367 const Grid<u16>& passGrid = cmpPathfinder->GetPassabilityGrid(); 368 369 // Downsample the passability data to count the number of impassable 370 // navcells per territory tile 371 // (TODO: do we really want to average the passability per territory tile, 372 // instead of doing min/max/etc?) 373 374 int tilesW = passGrid.m_W / NAVCELLS_PER_TERRITORY_TILE; 375 int tilesH = passGrid.m_H / NAVCELLS_PER_TERRITORY_TILE; 348 376 349 377 m_Territories = new Grid<u8>(tilesW, tilesH); 350 378 351 // Compute terrain-passability-dependent costs per tile352 Grid<u 8> influenceGrid(tilesW, tilesH);379 // Downsample passability grid horizontally first 380 Grid<u16> tempPassGrid(passGrid.m_W / NAVCELLS_PER_TERRITORY_TILE, passGrid.m_H); 353 381 354 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); 355 ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default"); 356 ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted"); 382 cassert(NAVCELLS_PER_TERRITORY_TILE < 16); // else we might overflow the counters 357 383 358 const Grid<u16>& passGrid = cmpPathfinder->GetPassabilityGrid(); 359 for (u16 j = 0; j < tilesH; ++j) 384 for (int j = 0; j < tempPassGrid.m_H; ++j) 360 385 { 361 for ( u16 i = 0; i < tilesW; ++i)386 for (int i = 0; i < tempPassGrid.m_W; ++i) 362 387 { 363 u16 g = passGrid.get(i, j); 388 u32 count = 0; 389 for (u16 di = 0; di < NAVCELLS_PER_TERRITORY_TILE; ++di) 390 { 391 u16 g = passGrid.get(i*NAVCELLS_PER_TERRITORY_TILE + di, j); 392 if (g & passClassUnrestricted) 393 count += 65536; // off the world; force maximum cost 394 else if (g & passClassDefault) 395 count += 1; 396 } 397 tempPassGrid.set(i, j, std::min((u32)65535, count)); // clamp to avoid overflow 398 } 399 } 400 401 // Compute terrain-passability-dependent costs per tile 402 403 Grid<u8> influenceGrid(tilesW, tilesH); 404 for (int j = 0; j < tilesH; ++j) 405 { 406 for (int i = 0; i < tilesW; ++i) 407 { 408 // Downsample vertically 409 u32 count = 0; 410 for (int dj = 0; dj < NAVCELLS_PER_TERRITORY_TILE; ++dj) 411 count += tempPassGrid.get(i, j*NAVCELLS_PER_TERRITORY_TILE + dj); 412 364 413 u8 cost; 365 if ( g & passClassUnrestricted)366 cost = 255; // off the world; use maximum cost367 else if (g & passClassDefault)368 cost = m_ImpassableCost;414 if (count >= 65535) 415 { 416 cost = 255; // at least partially off the world; use maximum cost 417 } 369 418 else 370 cost = 1; 419 { 420 // Compute average cost of the cells within this tile 421 u32 totalCells = NAVCELLS_PER_TERRITORY_TILE*NAVCELLS_PER_TERRITORY_TILE; 422 cost = (m_ImpassableCost*count + 1*(totalCells-count)) / totalCells; 423 } 371 424 influenceGrid.set(i, j, cost); 372 425 } 373 426 } … … void CCmpTerritoryManager::CalculateTerritories() 430 483 431 484 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *eit); 432 485 CFixedVector2D pos = cmpPosition->GetPosition2D(); 433 u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);434 u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);486 int i, j; 487 NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH); 435 488 436 489 CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), *eit); 437 490 u32 weight = cmpTerritoryInfluence->GetWeight(); 438 u32 radius = cmpTerritoryInfluence->GetRadius() / TERRAIN_TILE_SIZE;491 u32 radius = (fixed::FromInt(cmpTerritoryInfluence->GetRadius()) / (ICmpObstructionManager::NAVCELL_SIZE * NAVCELLS_PER_TERRITORY_TILE)).ToInt_RoundToNegInfinity(); 439 492 u32 falloff = weight / radius; // earlier check for GetRadius() == 0 prevents divide-by-zero 440 493 441 494 // TODO: we should have some maximum value on weight, to avoid overflow … … void CCmpTerritoryManager::CalculateTerritories() 451 504 FloodFill(entityGrid, influenceGrid, openTiles, falloff); 452 505 453 506 // TODO: we should do a sparse grid and only add the non-zero regions, for performance 454 for ( u16j = 0; j < entityGrid.m_H; ++j)455 for ( u16i = 0; i < entityGrid.m_W; ++i)507 for (int j = 0; j < entityGrid.m_H; ++j) 508 for (int i = 0; i < entityGrid.m_W; ++i) 456 509 playerGrid.set(i, j, playerGrid.get(i, j) + entityGrid.get(i, j)); 457 510 } 458 511 … … void CCmpTerritoryManager::CalculateTerritories() 460 513 } 461 514 462 515 // Set m_Territories to the player ID with the highest influence for each tile 463 for ( u16j = 0; j < tilesH; ++j)516 for (int j = 0; j < tilesH; ++j) 464 517 { 465 for ( u16i = 0; i < tilesW; ++i)518 for (int i = 0; i < tilesW; ++i) 466 519 { 467 520 u32 bestWeight = 0; 468 521 for (size_t k = 0; k < playerGrids.size(); ++k) … … void CCmpTerritoryManager::CalculateTerritories() 487 540 CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *it); 488 541 489 542 CFixedVector2D pos = cmpPosition->GetPosition2D(); 490 u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);491 u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);543 int i, j; 544 NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH); 492 545 493 546 u8 owner = (u8)cmpOwnership->GetOwner(); 494 547 … … void CCmpTerritoryManager::CalculateTerritories() 500 553 501 554 Grid<u8>& grid = *m_Territories; 502 555 503 u16 maxi = (u16)(grid.m_W-1);504 u16 maxj = (u16)(grid.m_H-1);556 int maxi = grid.m_W-1; 557 int maxj = grid.m_H-1; 505 558 506 559 std::vector<std::pair<u16, u16> > tileStack; 507 560 508 #define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, owner | TERRITORY_CONNECTED_MASK); tileStack.push_back(std::make_pair( i, j)); )561 #define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, owner | TERRITORY_CONNECTED_MASK); tileStack.push_back(std::make_pair((u16)(i), (u16)(j))); ) 509 562 510 563 MARK_AND_PUSH(i, j); 511 564 while (!tileStack.empty()) … … void CCmpTerritoryManager::CalculateTerritories() 537 590 } 538 591 } 539 592 540 /**541 * Compute the tile indexes on the grid nearest to a given point542 */543 static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)544 {545 i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, w-1);546 j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, h-1);547 }548 549 /**550 * Returns the position of the center of the given tile551 */552 static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)553 {554 x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);555 z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);556 }557 558 // TODO: would be nice not to duplicate those two functions from CCmpObstructionManager.cpp559 560 561 593 void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid) 562 594 { 563 595 for (CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it) … … void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& 579 611 CFixedVector2D halfSize(square.hw, square.hh); 580 612 CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize); 581 613 582 u16i0, j0, i1, j1;583 NearestT ile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0, grid.m_W, grid.m_H);584 NearestT ile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1, grid.m_W, grid.m_H);585 for ( u16j = j0; j <= j1; ++j)614 int i0, j0, i1, j1; 615 NearestTerritoryTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0, grid.m_W, grid.m_H); 616 NearestTerritoryTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1, grid.m_W, grid.m_H); 617 for (int j = j0; j <= j1; ++j) 586 618 { 587 for ( u16i = i0; i <= i1; ++i)619 for (int i = i0; i <= i1; ++i) 588 620 { 589 621 entity_pos_t x, z; 590 T ileCenter(i, j, x, z);622 TerritoryTileCenter(i, j, x, z); 591 623 if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize)) 592 624 grid.set(i, j, (u8)cost); 593 625 } … … void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector) 714 746 715 747 player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z) 716 748 { 717 u16 i, j;718 749 CalculateTerritories(); 719 750 if (!m_Territories) 720 751 return 0; 721 752 722 NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H); 753 int i, j; 754 NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H); 723 755 return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK; 724 756 } 725 757 726 758 bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z) 727 759 { 728 u16 i, j;729 760 CalculateTerritories(); 730 761 if (!m_Territories) 731 762 return false; 732 763 733 NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H); 764 int i, j; 765 NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H); 734 766 return (m_Territories->get(i, j) & TERRITORY_CONNECTED_MASK) != 0; 735 767 } 736 768 737 TerritoryOverlay::TerritoryOverlay(CCmpTerritoryManager& manager) 738 : TerrainOverlay(manager.GetSimContext()), m_TerritoryManager(manager) 739 { } 740 741 void TerritoryOverlay::StartRender() 769 TerritoryOverlay::TerritoryOverlay(CCmpTerritoryManager& manager) : 770 TerrainTextureOverlay((float)ICmpObstructionManager::NAVCELLS_PER_TILE / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE), 771 m_TerritoryManager(manager) 742 772 { 743 m_TerritoryManager.CalculateTerritories();744 773 } 745 774 746 void TerritoryOverlay:: ProcessTile(ssize_t i, ssize_t j)775 void TerritoryOverlay::BuildTextureRGBA(u8* data, size_t w, size_t h) 747 776 { 748 if (!m_TerritoryManager.m_Territories) 749 return; 750 751 u8 id = (m_TerritoryManager.m_Territories->get((int) i, (int) j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK); 777 m_TerritoryManager.CalculateTerritories(); 752 778 753 float a = 0.2f; 754 switch (id) 779 for (size_t j = 0; j < h; ++j) 755 780 { 756 case 0: break; 757 case 1: RenderTile(CColor(1, 0, 0, a), false); break; 758 case 2: RenderTile(CColor(0, 1, 0, a), false); break; 759 case 3: RenderTile(CColor(0, 0, 1, a), false); break; 760 case 4: RenderTile(CColor(1, 1, 0, a), false); break; 761 case 5: RenderTile(CColor(0, 1, 1, a), false); break; 762 case 6: RenderTile(CColor(1, 0, 1, a), false); break; 763 default: RenderTile(CColor(1, 1, 1, a), false); break; 781 for (size_t i = 0; i < w; ++i) 782 { 783 SColor4ub color; 784 785 u8 id = (m_TerritoryManager.m_Territories->get((int)i, (int)j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK); 786 color = GetColor(id, 64); 787 788 *data++ = color.R; 789 *data++ = color.G; 790 *data++ = color.B; 791 *data++ = color.A; 792 } 764 793 } 765 794 } -
source/simulation2/components/CCmpUnitMotion.cpp
diff --git a/source/simulation2/components/CCmpUnitMotion.cpp b/source/simulation2/components/CCmpUnitMotion.cpp index 4985e9c..c1cf2d3 100644
a b 38 38 #include "ps/Profile.h" 39 39 #include "renderer/Scene.h" 40 40 41 // For debugging; units will start going straight to the target 42 // instead of calling the pathfinder 43 #define DISABLE_PATHFINDER 0 44 41 45 /** 42 46 * When advancing along the long path, and picking a new waypoint to move 43 47 * towards, we'll pick one that's up to this far from the unit's current … … static const entity_pos_t CHECK_TARGET_MOVEMENT_AT_MAX_DIST = entity_pos_t::From 98 102 static const CColor OVERLAY_COLOUR_LONG_PATH(1, 1, 1, 1); 99 103 static const CColor OVERLAY_COLOUR_SHORT_PATH(1, 0, 0, 1); 100 104 101 static const entity_pos_t g_GoalDelta = entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/4; // for extending the goal outwards/inwards a little bit105 static const entity_pos_t g_GoalDelta = ICmpObstructionManager::NAVCELL_SIZE; // for extending the goal outwards/inwards a little bit 102 106 103 107 class CCmpUnitMotion : public ICmpUnitMotion 104 108 { … … public: 123 127 fixed m_WalkSpeed; // in metres per second 124 128 fixed m_RunSpeed; 125 129 ICmpPathfinder::pass_class_t m_PassClass; 126 ICmpPathfinder::cost_class_t m_CostClass;127 130 128 131 // Dynamic state: 129 132 … … public: 236 239 ICmpPathfinder::Path m_LongPath; 237 240 ICmpPathfinder::Path m_ShortPath; 238 241 239 ICmpPathfinder::Goal m_FinalGoal;242 PathGoal m_FinalGoal; 240 243 241 244 static std::string GetSchema() 242 245 { … … public: 245 248 "<a:example>" 246 249 "<WalkSpeed>7.0</WalkSpeed>" 247 250 "<PassabilityClass>default</PassabilityClass>" 248 "<CostClass>infantry</CostClass>"249 251 "</a:example>" 250 252 "<element name='FormationController'>" 251 253 "<data type='boolean'/>" … … public: 266 268 "</optional>" 267 269 "<element name='PassabilityClass' a:help='Identifies the terrain passability class (values are defined in special/pathfinder.xml)'>" 268 270 "<text/>" 269 "</element>"270 "<element name='CostClass' a:help='Identifies the movement speed/cost class (values are defined in special/pathfinder.xml)'>"271 "<text/>"272 271 "</element>"; 273 272 } 274 273 … … public: 298 297 if (cmpPathfinder) 299 298 { 300 299 m_PassClass = cmpPathfinder->GetPassabilityClass(paramNode.GetChild("PassabilityClass").ToUTF8()); 301 m_CostClass = cmpPathfinder->GetCostClass(paramNode.GetChild("CostClass").ToUTF8());302 300 } 303 301 304 302 CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), GetEntityId()); … … public: 312 310 313 311 m_TargetEntity = INVALID_ENTITY; 314 312 315 m_FinalGoal.type = ICmpPathfinder::Goal::POINT;313 m_FinalGoal.type = PathGoal::POINT; 316 314 317 315 m_DebugOverlayEnabled = false; 318 316 } … … private: 568 566 * Might go in a straight line immediately, or might start an asynchronous 569 567 * path request. 570 568 */ 571 void BeginPathing(CFixedVector2D from, const ICmpPathfinder::Goal& goal);569 void BeginPathing(CFixedVector2D from, const PathGoal& goal); 572 570 573 571 /** 574 572 * Start an asynchronous long path query. 575 573 */ 576 void RequestLongPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal);574 void RequestLongPath(CFixedVector2D from, const PathGoal& goal); 577 575 578 576 /** 579 577 * Start an asynchronous short path query. 580 578 */ 581 void RequestShortPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal, bool avoidMovingUnits);579 void RequestShortPath(CFixedVector2D from, const PathGoal& goal, bool avoidMovingUnits); 582 580 583 581 /** 584 582 * Select a next long waypoint, given the current unit position. … … void CCmpUnitMotion::Move(fixed dt) 824 822 // Find the speed factor of the underlying terrain 825 823 // (We only care about the tile we start on - it doesn't matter if we're moving 826 824 // partially onto a much slower/faster tile) 827 fixed terrainSpeed = cmpPathfinder->GetMovementSpeed(pos.X, pos.Y, m_CostClass); 825 // TODO: Terrain-dependent speeds are not currently supported 826 fixed terrainSpeed = fixed::FromInt(1); 828 827 829 828 fixed maxSpeed = basicSpeed.Multiply(terrainSpeed); 830 829 … … bool CCmpUnitMotion::TryGoingStraightToTargetEntity(CFixedVector2D from) 1025 1024 return false; 1026 1025 1027 1026 // Move the goal to match the target entity's new position 1028 ICmpPathfinder::Goal goal = m_FinalGoal;1027 PathGoal goal = m_FinalGoal; 1029 1028 goal.x = targetPos.X; 1030 1029 goal.z = targetPos.Y; 1031 1030 // (we ignore changes to the target's rotation, since only buildings are … … ControlGroupMovementObstructionFilter CCmpUnitMotion::GetObstructionFilter(bool 1147 1146 1148 1147 1149 1148 1150 void CCmpUnitMotion::BeginPathing(CFixedVector2D from, const ICmpPathfinder::Goal& goal)1149 void CCmpUnitMotion::BeginPathing(CFixedVector2D from, const PathGoal& goal) 1151 1150 { 1152 1151 // Cancel any pending path requests 1153 1152 m_ExpectedPathTicket = 0; … … void CCmpUnitMotion::BeginPathing(CFixedVector2D from, const ICmpPathfinder::Goa 1160 1159 if (cmpObstruction) 1161 1160 cmpObstruction->SetMovingFlag(true); 1162 1161 1162 #if DISABLE_PATHFINDER 1163 { 1164 CmpPtr<ICmpPathfinder> cmpPathfinder (GetSimContext(), SYSTEM_ENTITY); 1165 CFixedVector2D goalPos = cmpPathfinder->GetNearestPointOnGoal(from, m_FinalGoal); 1166 m_LongPath.m_Waypoints.clear(); 1167 m_ShortPath.m_Waypoints.clear(); 1168 ICmpPathfinder::Waypoint wp = { goalPos.X, goalPos.Y }; 1169 m_ShortPath.m_Waypoints.push_back(wp); 1170 m_PathState = PATHSTATE_FOLLOWING; 1171 return; 1172 } 1173 #endif 1174 1163 1175 // If we're aiming at a target entity and it's close and we can reach 1164 1176 // it in a straight line, then we'll just go along the straight line 1165 1177 // instead of computing a path. … … void CCmpUnitMotion::BeginPathing(CFixedVector2D from, const ICmpPathfinder::Goa 1181 1193 RequestLongPath(from, goal); 1182 1194 } 1183 1195 1184 void CCmpUnitMotion::RequestLongPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal)1196 void CCmpUnitMotion::RequestLongPath(CFixedVector2D from, const PathGoal& goal) 1185 1197 { 1186 1198 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); 1187 1199 if (!cmpPathfinder) 1188 1200 return; 1189 1201 1190 cmpPathfinder->SetDebugPath(from.X, from.Y, goal, m_PassClass , m_CostClass);1202 cmpPathfinder->SetDebugPath(from.X, from.Y, goal, m_PassClass); 1191 1203 1192 m_ExpectedPathTicket = cmpPathfinder->ComputePathAsync(from.X, from.Y, goal, m_PassClass, m_CostClass,GetEntityId());1204 m_ExpectedPathTicket = cmpPathfinder->ComputePathAsync(from.X, from.Y, goal, m_PassClass, GetEntityId()); 1193 1205 } 1194 1206 1195 void CCmpUnitMotion::RequestShortPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal, bool avoidMovingUnits)1207 void CCmpUnitMotion::RequestShortPath(CFixedVector2D from, const PathGoal& goal, bool avoidMovingUnits) 1196 1208 { 1197 1209 CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); 1198 1210 if (!cmpPathfinder) … … bool CCmpUnitMotion::PickNextLongWaypoint(const CFixedVector2D& pos, bool avoidM 1227 1239 1228 1240 // Now we need to recompute a short path to the waypoint 1229 1241 1230 ICmpPathfinder::Goal goal;1242 PathGoal goal; 1231 1243 if (m_LongPath.m_Waypoints.empty()) 1232 1244 { 1233 1245 // This was the last waypoint - head for the exact goal … … bool CCmpUnitMotion::PickNextLongWaypoint(const CFixedVector2D& pos, bool avoidM 1236 1248 else 1237 1249 { 1238 1250 // Head for somewhere near the waypoint (but allow some leeway in case it's obstructed) 1239 goal.type = ICmpPathfinder::Goal::CIRCLE;1251 goal.type = PathGoal::CIRCLE; 1240 1252 goal.hw = SHORT_PATH_GOAL_RADIUS; 1241 1253 goal.x = targetX; 1242 1254 goal.z = targetZ; … … bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos 1262 1274 1263 1275 CFixedVector2D pos = cmpPosition->GetPosition2D(); 1264 1276 1265 ICmpPathfinder::Goal goal; 1277 PathGoal goal; 1278 goal.x = x; 1279 goal.z = z; 1266 1280 1267 1281 if (minRange.IsZero() && maxRange.IsZero()) 1268 1282 { 1269 // Handle the non-ranged mode: 1270 1271 // Check whether this point is in an obstruction 1283 // Non-ranged movement: 1272 1284 1273 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); 1274 if (!cmpObstructionManager) 1275 return false; 1276 1277 ICmpObstructionManager::ObstructionSquare obstruction; 1278 if (cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(true), x, z, m_Radius, obstruction)) 1279 { 1280 // If we're aiming inside a building, then aim for the outline of the building instead 1281 // TODO: if we're aiming at a unit then maybe a circle would look nicer? 1282 1283 goal.type = ICmpPathfinder::Goal::SQUARE; 1284 goal.x = obstruction.x; 1285 goal.z = obstruction.z; 1286 goal.u = obstruction.u; 1287 goal.v = obstruction.v; 1288 goal.hw = obstruction.hw + m_Radius + g_GoalDelta; // nudge the goal outwards so it doesn't intersect the building itself 1289 goal.hh = obstruction.hh + m_Radius + g_GoalDelta; 1290 } 1291 else 1292 { 1293 // Unobstructed - head directly for the goal 1294 goal.type = ICmpPathfinder::Goal::POINT; 1295 goal.x = x; 1296 goal.z = z; 1297 } 1285 // Head directly for the goal 1286 goal.type = PathGoal::POINT; 1298 1287 } 1299 1288 else 1300 1289 { 1290 // Ranged movement: 1291 1301 1292 entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length(); 1302 1293 1303 entity_pos_t goalDistance;1304 1294 if (distance < minRange) 1305 1295 { 1306 goalDistance = minRange + g_GoalDelta; 1296 // Too close to target - move outwards to a circle 1297 // that's slightly larger than the min range 1298 goal.type = PathGoal::INVERTED_CIRCLE; 1299 goal.hw = minRange + g_GoalDelta; 1307 1300 } 1308 1301 else if (maxRange >= entity_pos_t::Zero() && distance > maxRange) 1309 1302 { 1310 goalDistance = maxRange - g_GoalDelta; 1303 // Too far from target - move outwards to a circle 1304 // that's slightly smaller than the max range 1305 goal.type = PathGoal::CIRCLE; 1306 goal.hw = maxRange - g_GoalDelta; 1307 1308 // If maxRange was abnormally small, 1309 // collapse the circle into a point 1310 if (goal.hw <= entity_pos_t::Zero()) 1311 goal.type = PathGoal::POINT; 1311 1312 } 1312 1313 else 1313 1314 { … … bool CCmpUnitMotion::MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos 1315 1316 FaceTowardsPointFromPos(pos, x, z); 1316 1317 return false; 1317 1318 } 1318 1319 // TODO: what happens if goalDistance < 0? (i.e. we probably can never get close enough to the target)1320 1321 goal.type = ICmpPathfinder::Goal::CIRCLE;1322 goal.x = x;1323 goal.z = z;1324 1325 // Formerly added m_Radius, but it seems better to go by the mid-point.1326 goal.hw = goalDistance;1327 1319 } 1328 1320 1329 1321 m_State = STATE_INDIVIDUAL_PATH; … … bool CCmpUnitMotion::IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t 1349 1341 bool hasObstruction = false; 1350 1342 CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); 1351 1343 ICmpObstructionManager::ObstructionSquare obstruction; 1352 if (cmpObstructionManager)1353 hasObstruction = cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(true), x, z, m_Radius, obstruction);1344 //TODO if (cmpObstructionManager) 1345 // hasObstruction = cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(true), x, z, m_Radius, obstruction); 1354 1346 1355 1347 if (minRange.IsZero() && maxRange.IsZero() && hasObstruction) 1356 1348 { … … bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange 1422 1414 if (cmpObstruction) 1423 1415 hasObstruction = cmpObstruction->GetObstructionSquare(obstruction); 1424 1416 1417 if (!hasObstruction) 1418 { 1419 // The target didn't have an obstruction or obstruction shape, so treat it as a point instead 1420 1421 CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target); 1422 if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld()) 1423 return false; 1424 1425 CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D(); 1426 1427 return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange); 1428 } 1429 1425 1430 /* 1426 1431 * If we're starting outside the maxRange, we need to move closer in. 1427 1432 * If we're starting inside the minRange, we need to move further out. … … bool CCmpUnitMotion::MoveToTargetRange(entity_id_t target, entity_pos_t minRange 1447 1452 * (Those units should set minRange to 0 so they'll never be considered *too* close.) 1448 1453 */ 1449 1454 1450 if (hasObstruction) 1455 CFixedVector2D halfSize(obstruction.hw, obstruction.hh); 1456 PathGoal goal; 1457 goal.x = obstruction.x; 1458 goal.z = obstruction.z; 1459 1460 entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize); 1461 1462 if (distance < minRange) 1451 1463 { 1452 CFixedVector2D halfSize(obstruction.hw, obstruction.hh); 1453 ICmpPathfinder::Goal goal; 1454 goal.x = obstruction.x; 1455 goal.z = obstruction.z; 1464 // Too close to the square - need to move away 1456 1465 1457 entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);1466 // TODO: maybe we should do the ShouldTreatTargetAsCircle thing here? 1458 1467 1459 if (distance < minRange) 1460 { 1461 // Too close to the square - need to move away 1468 entity_pos_t goalDistance = minRange + g_GoalDelta; 1462 1469 1463 // TODO: maybe we should do the ShouldTreatTargetAsCircle thing here? 1470 goal.type = PathGoal::SQUARE; 1471 goal.u = obstruction.u; 1472 goal.v = obstruction.v; 1473 entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself 1474 goal.hw = obstruction.hw + delta; 1475 goal.hh = obstruction.hh + delta; 1476 } 1477 else if (maxRange < entity_pos_t::Zero() || distance < maxRange) 1478 { 1479 // We're already in range - no need to move anywhere 1480 FaceTowardsPointFromPos(pos, goal.x, goal.z); 1481 return false; 1482 } 1483 else 1484 { 1485 // We might need to move closer: 1464 1486 1465 entity_pos_t goalDistance = minRange + g_GoalDelta; 1487 // Circumscribe the square 1488 entity_pos_t circleRadius = halfSize.Length(); 1466 1489 1467 goal.type = ICmpPathfinder::Goal::SQUARE; 1468 goal.u = obstruction.u; 1469 goal.v = obstruction.v; 1470 entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself 1471 goal.hw = obstruction.hw + delta; 1472 goal.hh = obstruction.hh + delta; 1473 } 1474 else if (maxRange < entity_pos_t::Zero() || distance < maxRange) 1475 { 1476 // We're already in range - no need to move anywhere 1477 FaceTowardsPointFromPos(pos, goal.x, goal.z); 1478 return false; 1479 } 1480 else 1490 if (ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius)) 1481 1491 { 1482 // We might need to move closer:1492 // The target is small relative to our range, so pretend it's a circle 1483 1493 1484 // Circumscribe the square 1485 entity_pos_t circleRadius = halfSize.Length(); 1494 // Note that the distance to the circle will always be less than 1495 // the distance to the square, so the previous "distance < maxRange" 1496 // check is still valid (though not sufficient) 1497 entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius; 1486 1498 1487 if ( ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius))1499 if (circleDistance < maxRange) 1488 1500 { 1489 // The target is small relative to our range, so pretend it's a circle 1490 1491 // Note that the distance to the circle will always be less than 1492 // the distance to the square, so the previous "distance < maxRange" 1493 // check is still valid (though not sufficient) 1494 entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius; 1495 1496 if (circleDistance < maxRange) 1497 { 1498 // We're already in range - no need to move anywhere 1499 FaceTowardsPointFromPos(pos, goal.x, goal.z); 1500 return false; 1501 } 1502 1503 entity_pos_t goalDistance = maxRange - g_GoalDelta; 1504 1505 goal.type = ICmpPathfinder::Goal::CIRCLE; 1506 goal.hw = circleRadius + goalDistance; 1501 // We're already in range - no need to move anywhere 1502 FaceTowardsPointFromPos(pos, goal.x, goal.z); 1503 return false; 1507 1504 } 1508 else1509 {1510 // The target is large relative to our range, so treat it as a square and1511 // get close enough that the diagonals come within range1512 1505 1513 entity_pos_t goalDistance = (maxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)1506 entity_pos_t goalDistance = maxRange - g_GoalDelta; 1514 1507 1515 goal.type = ICmpPathfinder::Goal::SQUARE; 1516 goal.u = obstruction.u; 1517 goal.v = obstruction.v; 1518 entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself 1519 goal.hw = obstruction.hw + delta; 1520 goal.hh = obstruction.hh + delta; 1521 } 1508 goal.type = PathGoal::CIRCLE; 1509 goal.hw = circleRadius + goalDistance; 1522 1510 } 1511 else 1512 { 1513 // The target is large relative to our range, so treat it as a square and 1514 // get close enough that the diagonals come within range 1523 1515 1524 m_State = STATE_INDIVIDUAL_PATH; 1525 m_TargetEntity = target; 1526 m_TargetOffset = CFixedVector2D(); 1527 m_TargetMinRange = minRange; 1528 m_TargetMaxRange = maxRange; 1529 m_FinalGoal = goal; 1530 1531 BeginPathing(pos, goal); 1516 entity_pos_t goalDistance = (maxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2) 1532 1517 1533 return true; 1518 goal.type = PathGoal::SQUARE; 1519 goal.u = obstruction.u; 1520 goal.v = obstruction.v; 1521 entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself 1522 goal.hw = obstruction.hw + delta; 1523 goal.hh = obstruction.hh + delta; 1524 } 1534 1525 } 1535 else1536 {1537 // The target didn't have an obstruction or obstruction shape, so treat it as a point instead1538 1526 1539 CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target); 1540 if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld()) 1541 return false; 1527 m_State = STATE_INDIVIDUAL_PATH; 1528 m_TargetEntity = target; 1529 m_TargetOffset = CFixedVector2D(); 1530 m_TargetMinRange = minRange; 1531 m_TargetMaxRange = maxRange; 1532 m_FinalGoal = goal; 1542 1533 1543 CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();1534 BeginPathing(pos, goal); 1544 1535 1545 return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange); 1546 } 1536 return true; 1547 1537 } 1548 1538 1549 1539 bool CCmpUnitMotion::IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) … … void CCmpUnitMotion::MoveToFormationOffset(entity_id_t target, entity_pos_t x, e 1620 1610 1621 1611 CFixedVector2D pos = cmpPosition->GetPosition2D(); 1622 1612 1623 ICmpPathfinder::Goal goal;1624 goal.type = ICmpPathfinder::Goal::POINT;1613 PathGoal goal; 1614 goal.type = PathGoal::POINT; 1625 1615 goal.x = pos.X; 1626 1616 goal.z = pos.Y; 1627 1617 -
source/simulation2/components/ICmpObstructionManager.cpp
diff --git a/source/simulation2/components/ICmpObstructionManager.cpp b/source/simulation2/components/ICmpObstructionManager.cpp index ba4e21c..b348212 100644
a b 21 21 22 22 #include "simulation2/system/InterfaceScripted.h" 23 23 24 #include "graphics/Terrain.h" 25 26 const fixed ICmpObstructionManager::NAVCELL_SIZE = fixed::FromInt(TERRAIN_TILE_SIZE) / NAVCELLS_PER_TILE; 27 24 28 BEGIN_INTERFACE_WRAPPER(ObstructionManager) 25 29 DEFINE_INTERFACE_METHOD_1("SetPassabilityCircular", void, ICmpObstructionManager, SetPassabilityCircular, bool) 26 30 DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpObstructionManager, SetDebugOverlay, bool) -
source/simulation2/components/ICmpObstructionManager.h
diff --git a/source/simulation2/components/ICmpObstructionManager.h b/source/simulation2/components/ICmpObstructionManager.h index 2dcef8b..4de5e39 100644
a b class IObstructionTestFilter; 48 48 * Units can be marked as either moving or stationary, which simply determines whether 49 49 * certain filters include or exclude them. 50 50 * 51 * The @c Rasteri se function approximates the current set of shapes onto a 2D grid,51 * The @c Rasterize function approximates the current set of shapes onto a 2D grid, 52 52 * for use with tile-based pathfinding. 53 53 */ 54 54 class ICmpObstructionManager : public IComponent 55 55 { 56 56 public: 57 57 /** 58 * The pathfinders operate primarily over a navigation grid (a uniform-cost 59 * 2D passability grid, with horizontal/vertical (not diagonal) connectivity). 60 * This is based on the terrain tile passability, plus the rasterized shapes of 61 * obstructions, all expanded outwards by the radius of the units. 62 * Since units are much smaller than terrain tiles, the nav grid should be 63 * higher resolution than the tiles. 64 * We therefore split each terrain tile into NxN "nav cells" (for some integer N, 65 * preferably a power of two). 66 */ 67 static const int NAVCELLS_PER_TILE = 4; 68 69 /** 70 * Size of a navcell in metres ( = TERRAIN_TILE_SIZE / NAVCELLS_PER_TILE) 71 */ 72 static const fixed NAVCELL_SIZE; 73 74 /** 58 75 * External identifiers for shapes. 59 76 * (This is a struct rather than a raw u32 for type-safety.) 60 77 */ … … public: 205 222 std::vector<entity_id_t>* out) = 0; 206 223 207 224 /** 208 * Bit-flags for Rasteri se.225 * Bit-flags for Rasterize. 209 226 */ 210 227 enum TileObstruction 211 228 { … … public: 215 232 }; 216 233 217 234 /** 218 * Convert the current set of shapes onto a grid. 219 * Tiles that are intersected by a pathfind-blocking shape will have TILE_OBSTRUCTED_PATHFINDING set; 220 * tiles that are intersected by a foundation-blocking shape will also have TILE_OBSTRUCTED_FOUNDATION; 221 * tiles that are outside the world bounds will also have TILE_OUTOFBOUNDS; 222 * others will be set to 0. 223 * This is very cheap if the grid has been rasterised before and the set of shapes has not changed. 224 * @param grid the grid to be updated 225 * @return true if any changes were made to the grid, false if it was already up-to-date 235 * Convert the current set of shapes onto a navcell grid. 236 * Shapes are expanded by the clearance radius @p expand. 237 * Only shapes with at least one of the flags from @p requireMask will be considered. 238 * @p setMask will be ORed onto the @p grid value for all navcells 239 * that are wholly enclosed by an expanded shape. 240 */ 241 virtual void Rasterize(Grid<u16>& grid, entity_pos_t expand, ICmpObstructionManager::flags_t requireMask, u16 setMask) = 0; 242 243 /** 244 * Returns whether obstructions have changed such that Rasterize will 245 * return different data, since the last call to NeedUpdate with the same 246 * @p dirtyID handle. 247 * This should be first called with @p dirtyID pointing to 0; the function 248 * will return true and update the pointed-at value to indicate the current 249 * state. Pass the same pointer to subsequent calls, and the function either 250 * will return false (if nothing relevant has changed), or will update the 251 * value and return true (in which case you should call Rasterize again). 226 252 */ 227 virtual bool Rasterise(Grid<u8>& grid) = 0;253 virtual bool NeedUpdate(size_t* dirtyID) = 0; 228 254 229 255 /** 230 256 * Standard representation for all types of shapes, for use with geometry processing code. … … public: 248 274 virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) = 0; 249 275 250 276 /** 251 * Find a single obstruction that blocks a unit at the given point with the given radius.252 * Static obstructions (buildings) are more important than unit obstructions, and253 * obstructions that cover the given point are more important than those that only cover254 * the point expanded by the radius.277 * Returns the entity IDs of all unit shapes that intersect the given 278 * obstruction square. 279 * (TODO: This currently ignores the clearance/radius values of units, 280 * so it just uses the unexpanded obstruction shapes, which is not ideal.) 255 281 */ 256 virtual bool FindMostImportantObstruction(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, ObstructionSquare& square) = 0;282 virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out) = 0; 257 283 258 284 /** 259 285 * Get the obstruction square representing the given shape. … … public: 344 370 { 345 371 if (group == m_Group || (group2 != INVALID_ENTITY && group2 == m_Group)) 346 372 return false; 373 374 // If an obstruction already blocks tile-based pathfinding, 375 // it will be handled as part of the terrain passability handling 376 // and doesn't need to be matched by this filter 377 if (flags & ICmpObstructionManager::FLAG_BLOCK_PATHFINDING) 378 return false; 379 347 380 if (!(flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT)) 348 381 return false; 382 349 383 if ((flags & ICmpObstructionManager::FLAG_MOVING) && !m_AvoidMoving) 350 384 return false; 385 351 386 return true; 352 387 } 353 388 }; -
source/simulation2/components/ICmpPathfinder.h
diff --git a/source/simulation2/components/ICmpPathfinder.h b/source/simulation2/components/ICmpPathfinder.h index e75713a..de96cb9 100644
a b 21 21 #include "simulation2/system/Interface.h" 22 22 23 23 #include "simulation2/components/ICmpObstruction.h" 24 #include "simulation2/helpers/PathGoal.h" 24 25 #include "simulation2/helpers/Position.h" 25 26 26 27 #include "maths/FixedVector2D.h" … … template<typename T> class Grid; 39 40 * accounts for terrain costs but ignore units, and a 'short' vertex-based pathfinder that 40 41 * provides precise paths and avoids other units. 41 42 * 42 * Both use the same concept of a Goal: either a point, circle or square.43 * Both use the same concept of a PathGoal: either a point, circle or square. 43 44 * (If the starting point is inside the goal shape then the path will move outwards 44 45 * to reach the shape's outline.) 45 46 * … … class ICmpPathfinder : public IComponent 49 50 { 50 51 public: 51 52 typedef u16 pass_class_t; 52 typedef u8 cost_class_t;53 54 struct Goal55 {56 enum Type {57 POINT,58 CIRCLE,59 SQUARE60 } type;61 entity_pos_t x, z; // position of center62 CFixedVector2D u, v; // if SQUARE, then orthogonal unit axes63 entity_pos_t hw, hh; // if SQUARE, then half width & height; if CIRCLE, then hw is radius64 };65 53 66 54 struct Waypoint 67 55 { … … public: 88 76 */ 89 77 virtual pass_class_t GetPassabilityClass(const std::string& name) = 0; 90 78 91 /**92 * Get the tag for a given movement cost class name.93 * Logs an error and returns something acceptable if the name is unrecognised.94 */95 virtual cost_class_t GetCostClass(const std::string& name) = 0;96 97 79 virtual const Grid<u16>& GetPassabilityGrid() = 0; 98 80 99 81 /** … … public: 101 83 * The waypoints correspond to the centers of horizontally/vertically adjacent tiles 102 84 * along the path. 103 85 */ 104 virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& ret) = 0; 86 virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, Path& ret) = 0; 87 88 /** 89 * Equivalent to ComputePath, but using the JPS pathfinder. 90 */ 91 virtual void ComputePathJPS(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, Path& ret) = 0; 105 92 106 93 /** 107 94 * Asynchronous version of ComputePath. … … public: 109 96 * Returns a unique non-zero number, which will match the 'ticket' in the result, 110 97 * so callers can recognise each individual request they make. 111 98 */ 112 virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify) = 0;99 virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify) = 0; 113 100 114 101 /** 115 102 * If the debug overlay is enabled, render the path that will computed by ComputePath. 116 103 */ 117 virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass) = 0;104 virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass) = 0; 118 105 119 106 /** 120 107 * Compute a precise path from the given point to the goal, and return the set of waypoints. … … public: 122 109 * a unit of radius 'r' will be able to follow the path with no collisions. 123 110 * The path is restricted to a box of radius 'range' from the starting point. 124 111 */ 125 virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& ret) = 0;112 virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, Path& ret) = 0; 126 113 127 114 /** 128 115 * Asynchronous version of ComputeShortPath (using ControlGroupObstructionFilter). … … public: 130 117 * Returns a unique non-zero number, which will match the 'ticket' in the result, 131 118 * so callers can recognise each individual request they make. 132 119 */ 133 virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify) = 0; 134 135 /** 136 * Find the speed factor (typically around 1.0) for a unit of the given cost class 137 * at the given position. 138 */ 139 virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, cost_class_t costClass) = 0; 120 virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify) = 0; 140 121 141 122 /** 142 123 * Returns the coordinates of the point on the goal that is closest to pos in a straight line. 143 124 */ 144 virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal& goal) = 0;125 virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const PathGoal& goal) = 0; 145 126 146 127 /** 147 128 * Check whether the given movement line is valid and doesn't hit any obstructions … … public: 181 162 */ 182 163 virtual void ProcessSameTurnMoves() = 0; 183 164 165 /** 166 * Returns some stats about the last ComputePath. 167 */ 168 virtual void GetDebugData(u32& steps, double& time, Grid<u8>& grid) = 0; 169 170 /** 171 * Returns some stats about the last ComputePathJPS. 172 */ 173 virtual void GetDebugDataJPS(u32& steps, double& time, Grid<u8>& grid) = 0; 174 184 175 DECLARE_INTERFACE_TYPE(Pathfinder) 185 176 }; 186 177 -
source/simulation2/components/ICmpTerritoryManager.h
diff --git a/source/simulation2/components/ICmpTerritoryManager.h b/source/simulation2/components/ICmpTerritoryManager.h index 575755e..f96a630 100644
a b class ICmpTerritoryManager : public IComponent 29 29 public: 30 30 virtual bool NeedUpdate(size_t* dirtyID) = 0; 31 31 32 /** 33 * Number of pathfinder navcells per territory tile. 34 * Passability data is stored per navcell, but we probably don't need that much 35 * resolution, and a lower resolution can make the boundary lines look prettier 36 * and will take less memory, so we downsample the passability data. 37 */ 38 static const int NAVCELLS_PER_TERRITORY_TILE = 8; 39 32 40 static const int TERRITORY_PLAYER_MASK = 0x3F; 33 41 static const int TERRITORY_CONNECTED_MASK = 0x40; 34 42 static const int TERRITORY_PROCESSED_MASK = 0x80; //< For internal use; marks a tile as processed. -
source/simulation2/components/ICmpUnitMotion.h
diff --git a/source/simulation2/components/ICmpUnitMotion.h b/source/simulation2/components/ICmpUnitMotion.h index bf42673..1688741 100644
a b 1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2012 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify … … public: 38 38 39 39 /** 40 40 * Attempt to walk into range of a to a given point, or as close as possible. 41 * The range is measured from the center of the unit. 41 42 * If the unit is already in range, or cannot move anywhere at all, or if there is 42 43 * some other error, then returns false. 43 44 * Otherwise, returns true and sends a MotionChanged message after starting to move, … … public: 60 61 61 62 /** 62 63 * Attempt to walk into range of a given target entity, or as close as possible. 64 * The range is measured between approximately the edges of the unit and the target, so that 65 * maxRange=0 is not unreachably close to the target. 63 66 * If the unit is already in range, or cannot move anywhere at all, or if there is 64 67 * some other error, then returns false. 65 68 * Otherwise, returns true and sends a MotionChanged message after starting to move, -
source/simulation2/components/tests/test_Pathfinder.h
diff --git a/source/simulation2/components/tests/test_Pathfinder.h b/source/simulation2/components/tests/test_Pathfinder.h index 9267761..748a0e6 100644
a b 26 26 #include "lib/timer.h" 27 27 #include "lib/tex/tex.h" 28 28 #include "ps/Loader.h" 29 #include "ps/Pyrogenesis.h" 29 30 #include "simulation2/Simulation2.h" 30 31 31 32 class TestCmpPathfinder : public CxxTest::TestSuite … … public: 85 86 ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, x1, z1 }; 86 87 87 88 ICmpPathfinder::Path path; 88 cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), cmp->GetCostClass("default"),path);89 cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), path); 89 90 for (size_t i = 0; i < path.m_Waypoints.size(); ++i) 90 91 printf("%d: %f %f\n", (int)i, path.m_Waypoints[i].x.ToDouble(), path.m_Waypoints[i].z.ToDouble()); 91 92 #endif … … public: 99 100 entity_pos_t z0 = entity_pos_t::FromInt(rand() % 512); 100 101 entity_pos_t x1 = x0 + entity_pos_t::FromInt(rand() % 64); 101 102 entity_pos_t z1 = z0 + entity_pos_t::FromInt(rand() % 64); 102 ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, x1, z1 };103 PathGoal goal = { PathGoal::POINT, x1, z1 }; 103 104 104 105 ICmpPathfinder::Path path; 105 cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), cmp->GetCostClass("default"),path);106 cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), path); 106 107 } 107 108 108 109 t = timer_Time() - t; … … public: 133 134 } 134 135 135 136 NullObstructionFilter filter; 136 ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, range, range };137 PathGoal goal = { PathGoal::POINT, range, range }; 137 138 ICmpPathfinder::Path path; 138 139 cmpPathfinder->ComputeShortPath(filter, range/3, range/3, fixed::FromInt(2), range, goal, 0, path); 139 140 for (size_t i = 0; i < path.m_Waypoints.size(); ++i) 140 141 printf("# %d: %f %f\n", (int)i, path.m_Waypoints[i].x.ToFloat(), path.m_Waypoints[i].z.ToFloat()); 141 142 } 143 144 template<typename T> 145 void DumpGrid(std::ostream& stream, const Grid<T>& grid, int mask) 146 { 147 for (u16 j = 0; j < grid.m_H; ++j) 148 { 149 for (u16 i = 0; i < grid.m_W; ) 150 { 151 if (!(grid.get(i, j) & mask)) 152 { 153 i++; 154 continue; 155 } 156 157 u16 i0 = i; 158 for (i = i0+1; ; ++i) 159 { 160 if (i >= grid.m_W || !(grid.get(i, j) & mask)) 161 { 162 stream << " <rect x='" << i0 << "' y='" << j << "' width='" << (i-i0) << "' height='1'/>\n"; 163 break; 164 } 165 } 166 } 167 } 168 } 169 170 #define USE_JPS 1 171 172 void test_perf2() 173 { 174 CTerrain terrain; 175 176 CSimulation2 sim2(NULL, &terrain); 177 sim2.LoadDefaultScripts(); 178 sim2.ResetState(); 179 180 CMapReader* mapReader = new CMapReader(); // it'll call "delete this" itself 181 182 LDR_BeginRegistering(); 183 mapReader->LoadMap(L"maps/scenarios/Peloponnese.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL, 184 &sim2, &sim2.GetSimContext(), -1, false); 185 LDR_EndRegistering(); 186 TS_ASSERT_OK(LDR_NonprogressiveLoad()); 187 188 sim2.Update(0); 189 190 std::ofstream stream(OsString("perf2.html").c_str(), std::ofstream::out | std::ofstream::trunc); 191 192 CmpPtr<ICmpObstructionManager> cmpObstructionManager(sim2, SYSTEM_ENTITY); 193 CmpPtr<ICmpPathfinder> cmpPathfinder(sim2, SYSTEM_ENTITY); 194 195 ICmpPathfinder::pass_class_t obstructionsMask = cmpPathfinder->GetPassabilityClass("default") | ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING; 196 const Grid<u16>& obstructions = cmpPathfinder->GetPassabilityGrid(); 197 198 int scale = 1; 199 stream << "<!DOCTYPE html>\n"; 200 stream << "<style>\n"; 201 stream << ".passability rect { fill: black; }\n"; 202 stream << ".astar-open rect { fill: rgba(255,255,0,0.75); }\n"; 203 stream << ".astar-closed rect { fill: rgba(255,0,0,0.75); }\n"; 204 // stream << ".astar-closed rect { fill: rgba(0,255,0,0.75); }\n"; 205 stream << ".path { stroke: rgba(0,0,255,0.75); stroke-width: 1; fill: none; }\n"; 206 stream << "</style>\n"; 207 stream << "<svg width='" << obstructions.m_W*scale << "' height='" << obstructions.m_H*scale << "'>\n"; 208 stream << "<g transform='translate(0 " << obstructions.m_H*scale << ") scale(" << scale << " -" << scale << ")'>\n"; 209 210 stream << " <g class='passability'>\n"; 211 DumpGrid(stream, obstructions, obstructionsMask); 212 stream << " </g>\n"; 213 214 DumpPath(stream, 128*4, 256*4, 128*4, 384*4, cmpPathfinder); 215 // RepeatPath(500, 128*4, 256*4, 128*4, 384*4, cmpPathfinder); 216 // // RepeatPath(100, 128*4, 256*4, 128*4, 384*4, cmpPathfinder); 217 // 218 // DumpPath(stream, 128*4, 204*4, 192*4, 204*4, cmpPathfinder); 219 // 220 // DumpPath(stream, 128*4, 230*4, 32*4, 230*4, cmpPathfinder); 221 222 stream << "</g>\n"; 223 stream << "</svg>\n"; 224 } 225 226 void test_perf3() 227 { 228 CTerrain terrain; 229 230 CSimulation2 sim2(NULL, &terrain); 231 sim2.LoadDefaultScripts(); 232 sim2.ResetState(); 233 234 CMapReader* mapReader = new CMapReader(); // it'll call "delete this" itself 235 236 LDR_BeginRegistering(); 237 mapReader->LoadMap(L"maps/scenarios/pathfindtest7.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL, 238 &sim2, &sim2.GetSimContext(), -1, false); 239 LDR_EndRegistering(); 240 TS_ASSERT_OK(LDR_NonprogressiveLoad()); 241 242 sim2.Update(0); 243 244 std::ofstream stream(OsString("perf3.html").c_str(), std::ofstream::out | std::ofstream::trunc); 245 246 CmpPtr<ICmpObstructionManager> cmpObstructionManager(sim2, SYSTEM_ENTITY); 247 CmpPtr<ICmpPathfinder> cmpPathfinder(sim2, SYSTEM_ENTITY); 248 249 ICmpPathfinder::pass_class_t obstructionsMask = cmpPathfinder->GetPassabilityClass("default") | ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING; 250 const Grid<u16>& obstructions = cmpPathfinder->GetPassabilityGrid(); 251 252 int scale = 31; 253 stream << "<!DOCTYPE html>\n"; 254 stream << "<style>\n"; 255 stream << ".passability rect { fill: black; }\n"; 256 stream << ".astar-open rect { fill: rgba(255,255,0,0.75); }\n"; 257 stream << ".astar-closed rect { fill: rgba(255,0,0,0.75); }\n"; 258 stream << ".path { stroke: rgba(0,0,255,0.75); stroke-width: " << (1.0f / scale) << "; fill: none; }\n"; 259 stream << "</style>\n"; 260 stream << "<svg width='" << obstructions.m_W*scale << "' height='" << obstructions.m_H*scale << "'>\n"; 261 stream << "<defs><pattern id='grid' width='1' height='1' patternUnits='userSpaceOnUse'>\n"; 262 stream << "<rect fill='none' stroke='#888' stroke-width='" << (1.0f / scale) << "' width='" << scale << "' height='" << scale << "'/>\n"; 263 stream << "</pattern></defs>\n"; 264 stream << "<g transform='translate(0 " << obstructions.m_H*scale << ") scale(" << scale << " -" << scale << ")'>\n"; 265 266 stream << " <g class='passability'>\n"; 267 DumpGrid(stream, obstructions, obstructionsMask); 268 stream << " </g>\n"; 269 270 for (int j = 160; j < 190; ++j) 271 for (int i = 220; i < 290; ++i) 272 DumpPath(stream, 230, 178, i, j, cmpPathfinder); 273 274 stream << "<rect fill='url(#grid)' width='100%' height='100%'/>\n"; 275 stream << "</g>\n"; 276 stream << "</svg>\n"; 277 } 278 279 void DumpPath(std::ostream& stream, int i0, int j0, int i1, int j1, CmpPtr<ICmpPathfinder>& cmpPathfinder) 280 { 281 entity_pos_t x0 = entity_pos_t::FromInt(i0); 282 entity_pos_t z0 = entity_pos_t::FromInt(j0); 283 entity_pos_t x1 = entity_pos_t::FromInt(i1); 284 entity_pos_t z1 = entity_pos_t::FromInt(j1); 285 286 PathGoal goal = { PathGoal::POINT, x1, z1 }; 287 288 ICmpPathfinder::Path path; 289 #if USE_JPS 290 cmpPathfinder->ComputePathJPS(x0, z0, goal, cmpPathfinder->GetPassabilityClass("default"), path); 291 #else 292 cmpPathfinder->ComputePath(x0, z0, goal, cmpPathfinder->GetPassabilityClass("default"), path); 293 #endif 294 295 u32 debugSteps; 296 double debugTime; 297 Grid<u8> debugGrid; 298 #if USE_JPS 299 cmpPathfinder->GetDebugDataJPS(debugSteps, debugTime, debugGrid); 300 #else 301 cmpPathfinder->GetDebugData(debugSteps, debugTime, debugGrid); 302 #endif 303 // stream << " <g style='visibility:hidden'>\n"; 304 305 stream << " <g>\n"; 306 // stream << " <g class='astar-open'>\n"; 307 // DumpGrid(stream, debugGrid, 1); 308 // stream << " </g>\n"; 309 // stream << " <g class='astar-closed'>\n"; 310 // DumpGrid(stream, debugGrid, 2); 311 // stream << " </g>\n"; 312 // stream << " <g class='astar-closed'>\n"; 313 // DumpGrid(stream, debugGrid, 3); 314 // stream << " </g>\n"; 315 stream << " </g>\n"; 316 317 stream << " <polyline"; 318 stream << " onmouseover='this.previousElementSibling.style.visibility=\"visible\"' onmouseout='this.previousElementSibling.style.visibility=\"hidden\"'"; 319 double length = 0; 320 for (ssize_t i = 0; i < (ssize_t)path.m_Waypoints.size()-1; ++i) 321 { 322 double dx = (path.m_Waypoints[i+1].x - path.m_Waypoints[i].x).ToDouble(); 323 double dz = (path.m_Waypoints[i+1].z - path.m_Waypoints[i].z).ToDouble(); 324 ENSURE(dx == 0 || dz == 0 || abs(dx) == abs(dz)); 325 length += sqrt(dx*dx + dz*dz); 326 } 327 stream << " title='length: " << length << "; tiles explored: " << debugSteps << "; time: " << debugTime*1000 << " msec'"; 328 stream << " class='path' points='"; 329 for (size_t i = 0; i < path.m_Waypoints.size(); ++i) 330 stream << path.m_Waypoints[i].x.ToDouble()*ICmpObstructionManager::NAVCELLS_PER_TILE/TERRAIN_TILE_SIZE << "," << path.m_Waypoints[i].z.ToDouble()*ICmpObstructionManager::NAVCELLS_PER_TILE/TERRAIN_TILE_SIZE << " "; 331 stream << "'/>\n"; 332 } 333 334 void RepeatPath(int n, int i0, int j0, int i1, int j1, CmpPtr<ICmpPathfinder>& cmpPathfinder) 335 { 336 entity_pos_t x0 = entity_pos_t::FromInt(i0); 337 entity_pos_t z0 = entity_pos_t::FromInt(j0); 338 entity_pos_t x1 = entity_pos_t::FromInt(i1); 339 entity_pos_t z1 = entity_pos_t::FromInt(j1); 340 341 PathGoal goal = { PathGoal::POINT, x1, z1 }; 342 343 double t = timer_Time(); 344 for (int i = 0; i < n; ++i) 345 { 346 ICmpPathfinder::Path path; 347 #if USE_JPS 348 cmpPathfinder->ComputePathJPS(x0, z0, goal, cmpPathfinder->GetPassabilityClass("default"), path); 349 #else 350 cmpPathfinder->ComputePath(x0, z0, goal, cmpPathfinder->GetPassabilityClass("default"), path); 351 #endif 352 } 353 t = timer_Time() - t; 354 debug_printf(L"### RepeatPath %fms each (%fs total)\n", 1000*t / n, t); 355 } 356 142 357 }; -
source/simulation2/helpers/Geometry.cpp
diff --git a/source/simulation2/helpers/Geometry.cpp b/source/simulation2/helpers/Geometry.cpp index 5b713f0..e7ae462 100644
a b float Geometry::ChordToCentralAngle(const float chordLength, const float radius) 50 50 return acosf(1.f - SQR(chordLength)/(2.f*SQR(radius))); // cfr. law of cosines 51 51 } 52 52 53 fixed Geometry::DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize )53 fixed Geometry::DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize, bool countInsideAsZero) 54 54 { 55 55 /* 56 56 * Relative to its own coordinate system, we have a square like: … … fixed Geometry::DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedV 92 92 fixed closest = (dv.Absolute() - hh).Absolute(); // horizontal edges 93 93 94 94 if (-hh < dv && dv < hh) // region I 95 closest = std::min(closest, (du.Absolute() - hw).Absolute()); // vertical edges 95 { 96 if (countInsideAsZero) 97 closest = fixed::Zero(); 98 else 99 closest = std::min(closest, (du.Absolute() - hw).Absolute()); // vertical edges 100 } 96 101 97 102 return closest; 98 103 } -
source/simulation2/helpers/Geometry.h
diff --git a/source/simulation2/helpers/Geometry.h b/source/simulation2/helpers/Geometry.h index e8af00e..33c7930 100644
a b namespace Geometry 32 32 { 33 33 34 34 /** 35 * Checks if a point is inside the given rotated square or rectangle. 35 * Checks if a point is inside the given rotated rectangle. 36 * Points precisely on an edge are considered to be inside. 36 37 * 37 * @note Currently assumes the @p u and @p v vectors are perpendicular. 38 * @param point point vector of the point that is to be tested relative to the origin (center) of the shape. 39 * @param u rotated X axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated, 40 * this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta), as 41 * the absolute Z axis points down in the unit circle. 42 * @param v rotated Z axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated, 43 * this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta), as 44 * the absolute Z axis points down in the unit circle. 45 * @param halfSize Holds half the dimensions of the shape along the u and v vectors, respectively. 38 * The rectangle is defined by the four vertexes 39 * (+/-u*halfSize.X +/-v*halfSize.Y). 46 40 * 47 * @return true if @p point is inside the square with rotated X axis unit vector @p u and rotated Z axis unit vector @p v, 48 * and half dimensions specified by @p halfSizes. 41 * The @p u and @p v vectors must be perpendicular. 49 42 */ 50 bool PointIsInSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); 43 bool PointIsInSquare(CFixedVector2D point, 44 CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); 51 45 46 /** 47 * Returns a vector (bx,by) such that every point inside 48 * the given rotated rectangle has coordinates 49 * (x,y) with -bx <= x <= bx, -by <= y < by. 50 * 51 * The rectangle is defined by the four vertexes 52 * (+/-u*halfSize.X +/-v*halfSize.Y). 53 */ 52 54 CFixedVector2D GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); 53 55 54 fixed DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);55 56 56 /** 57 * Given a circle of radius @p radius, and a chord of length @p chordLength on this circle, computes the central angle formed by 58 * connecting the chord's endpoints to the center of the circle. 59 * 60 * @param radius Radius of the circle; must be strictly positive. 57 * Returns the minimum Euclidean distance from the given point to 58 * any point on the boundary of the given rotated rectangle. 59 * 60 * If @p countInsideAsZero is true, and the point is inside the rectangle, 61 * it will return 0. 62 * If @p countInsideAsZero is false, the (positive) distance to the boundary 63 * will be returned regardless of where the point is. 64 * 65 * The rectangle is defined by the four vertexes 66 * (+/-u*halfSize.X +/-v*halfSize.Y). 67 * 68 * The @p u and @p v vectors must be perpendicular and unit length. 61 69 */ 62 float ChordToCentralAngle(const float chordLength, const float radius); 70 fixed DistanceToSquare(CFixedVector2D point, 71 CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize, 72 bool countInsideAsZero = false); 63 73 64 74 /** 65 * Find point closest to the given point on the edge of the given square or rectangle. 75 * Returns a point on the boundary of the given rotated rectangle 76 * that is closest (or equally closest) to the given point 77 * in Euclidean distance. 78 * 79 * The rectangle is defined by the four vertexes 80 * (+/-u*halfSize.X +/-v*halfSize.Y). 66 81 * 67 * @note Currently assumes the @p u and @p v vectors are perpendicular. 68 * @param point point vector of the point we want to get the nearest edge point for, relative to the origin (center) of the shape. 69 * @param u rotated X axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated, 70 * this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta). 71 * @param v rotated Z axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated, 72 * this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta). 73 * @param halfSize Holds half the dimensions of the shape along the u and v vectors, respectively. 82 * The @p u and @p v vectors must be perpendicular and unit length. 83 */ 84 CFixedVector2D NearestPointOnSquare(CFixedVector2D point, 85 CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); 86 87 /** 88 * Given a circle of radius @p radius, and a chord of length @p chordLength 89 * on this circle, computes the central angle formed by 90 * connecting the chord's endpoints to the center of the circle. 74 91 * 75 * @return point that is closest to @p point on the edge of the square specified by orientation unit vectors @p u and @p v and half 76 * dimensions @p halfSize, relative to the center of the square 92 * @param radius Radius of the circle; must be strictly positive. 77 93 */ 78 CFixedVector2D NearestPointOnSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);94 float ChordToCentralAngle(const float chordLength, const float radius); 79 95 80 96 bool TestRaySquare(CFixedVector2D a, CFixedVector2D b, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); 81 97 -
source/simulation2/helpers/Grid.h
diff --git a/source/simulation2/helpers/Grid.h b/source/simulation2/helpers/Grid.h index fc087a2..cb80e14 100644
a b public: 48 48 49 49 Grid(const Grid& g) 50 50 { 51 m_Data = NULL; 51 52 *this = g; 52 53 } 53 54 … … public: 58 59 m_W = g.m_W; 59 60 m_H = g.m_H; 60 61 m_DirtyID = g.m_DirtyID; 62 delete[] m_Data; 61 63 if (g.m_Data) 62 64 { 63 65 m_Data = new T[m_W * m_H]; -
new file source/simulation2/helpers/PathGoal.cpp
diff --git a/source/simulation2/helpers/PathGoal.cpp b/source/simulation2/helpers/PathGoal.cpp new file mode 100644 index 0000000..6985c9f
- + 1 /* Copyright (C) 2012 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 20 #include "PathGoal.h" 21 22 #include "graphics/Terrain.h" 23 #include "simulation2/components/ICmpObstructionManager.h" 24 #include "simulation2/helpers/Geometry.h" 25 26 static bool NavcellContainsCircle(int i, int j, fixed x, fixed z, fixed r, bool inside) 27 { 28 // Accept any navcell (i,j) that contains a point which is inside[/outside] 29 // (or on the edge of) the circle 30 31 // Get world-space bounds of navcell 32 entity_pos_t x0 = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 33 entity_pos_t z0 = entity_pos_t::FromInt(j).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 34 entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE; 35 entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE; 36 37 if (inside) 38 { 39 // Get the point inside the navcell closest to (x,z) 40 entity_pos_t nx = Clamp(x, x0, x1); 41 entity_pos_t nz = Clamp(z, z0, z1); 42 // Check if that point is inside the circle 43 return (CFixedVector2D(nx, nz) - CFixedVector2D(x, z)).CompareLength(r) <= 0; 44 } 45 else 46 { 47 // If any corner of the navcell is outside the circle, return true. 48 // Otherwise, since the circle is convex, there cannot be any other point 49 // in the navcell that is outside the circle. 50 return ( 51 (CFixedVector2D(x0, z0) - CFixedVector2D(x, z)).CompareLength(r) >= 0 52 || (CFixedVector2D(x1, z0) - CFixedVector2D(x, z)).CompareLength(r) >= 0 53 || (CFixedVector2D(x0, z1) - CFixedVector2D(x, z)).CompareLength(r) >= 0 54 || (CFixedVector2D(x1, z1) - CFixedVector2D(x, z)).CompareLength(r) >= 0 55 ); 56 } 57 } 58 59 static bool NavcellContainsSquare(int i, int j, 60 fixed x, fixed z, CFixedVector2D u, CFixedVector2D v, fixed hw, fixed hh, 61 bool inside) 62 { 63 // Accept any navcell (i,j) that contains a point which is inside[/outside] 64 // (or on the edge of) the square 65 66 // Get world-space bounds of navcell 67 entity_pos_t x0 = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 68 entity_pos_t z0 = entity_pos_t::FromInt(j).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 69 entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE; 70 entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE; 71 72 if (inside) 73 { 74 // Get the point inside the navcell closest to (x,z) 75 entity_pos_t nx = Clamp(x, x0, x1); 76 entity_pos_t nz = Clamp(z, z0, z1); 77 // Check if that point is inside the circle 78 return Geometry::PointIsInSquare(CFixedVector2D(nx - x, nz - z), u, v, CFixedVector2D(hw, hh)); 79 } 80 else 81 { 82 // If any corner of the navcell is outside the square, return true. 83 // Otherwise, since the square is convex, there cannot be any other point 84 // in the navcell that is outside the square. 85 return ( 86 Geometry::PointIsInSquare(CFixedVector2D(x0 - x, z0 - z), u, v, CFixedVector2D(hw, hh)) 87 || Geometry::PointIsInSquare(CFixedVector2D(x1 - x, z0 - z), u, v, CFixedVector2D(hw, hh)) 88 || Geometry::PointIsInSquare(CFixedVector2D(x0 - x, z1 - z), u, v, CFixedVector2D(hw, hh)) 89 || Geometry::PointIsInSquare(CFixedVector2D(x1 - x, z1 - z), u, v, CFixedVector2D(hw, hh)) 90 ); 91 } 92 } 93 94 bool PathGoal::NavcellContainsGoal(int i, int j) const 95 { 96 switch (type) 97 { 98 case POINT: 99 { 100 // Only accept a single navcell 101 int gi = (x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero(); 102 int gj = (z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero(); 103 return gi == i && gj == j; 104 } 105 case CIRCLE: 106 return NavcellContainsCircle(i, j, x, z, hw, true); 107 case INVERTED_CIRCLE: 108 return NavcellContainsCircle(i, j, x, z, hw, false); 109 case SQUARE: 110 return NavcellContainsSquare(i, j, x, z, u, v, hw, hh, true); 111 case INVERTED_SQUARE: 112 return NavcellContainsSquare(i, j, x, z, u, v, hw, hh, false); 113 default: 114 { 115 debug_warn(L"invalid type"); 116 return false; 117 } 118 } 119 } 120 121 bool PathGoal::NavcellRectContainsGoal(int i0, int j0, int i1, int j1, int* gi, int* gj) const 122 { 123 // Get min/max to simplify range checks 124 int imin = std::min(i0, i1); 125 int imax = std::max(i0, i1); 126 int jmin = std::min(j0, j1); 127 int jmax = std::max(j0, j1); 128 129 // Direction to iterate from ij0 towards ij1 130 int di = i1 < i0 ? -1 : +1; 131 int dj = j1 < j0 ? -1 : +1; 132 133 switch (type) 134 { 135 case POINT: 136 { 137 // Calculate the navcell that contains the point goal 138 int i = (x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero(); 139 int j = (z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero(); 140 // If that goal navcell is in the given range, return it 141 if (imin <= i && i <= imax && jmin <= j && j <= jmax) 142 { 143 if (gi) *gi = i; 144 if (gj) *gj = j; 145 return true; 146 } 147 return false; 148 } 149 case CIRCLE: 150 { 151 // Loop over all navcells in the given range (starting at (i0,j0) since 152 // this function is meant to find the goal navcell nearest to there 153 // assuming jmin==jmax || imin==imax), 154 // and check whether any point in each navcell is within the goal circle. 155 // (TODO: this is pretty inefficient.) 156 for (int j = j0; jmin <= j && j <= jmax; j += dj) 157 { 158 for (int i = i0; imin <= i && i <= imax; i += di) 159 { 160 entity_pos_t x0 = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 161 entity_pos_t z0 = entity_pos_t::FromInt(j).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 162 entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE; 163 entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE; 164 entity_pos_t nx = Clamp(x, x0, x1); 165 entity_pos_t nz = Clamp(z, z0, z1); 166 if ((CFixedVector2D(nx, nz) - CFixedVector2D(x, z)).CompareLength(hw) <= 0) 167 { 168 if (gi) *gi = i; 169 if (gj) *gj = j; 170 return true; 171 } 172 } 173 } 174 return false; 175 } 176 case SQUARE: 177 { 178 // Loop over all navcells in the given range (starting at (i0,j0) since 179 // this function is meant to find the goal navcell nearest to there 180 // assuming jmin==jmax || imin==imax), 181 // and check whether any point in each navcell is within the goal square. 182 // (TODO: this is pretty inefficient.) 183 for (int j = j0; jmin <= j && j <= jmax; j += dj) 184 { 185 for (int i = i0; imin <= i && i <= imax; i += di) 186 { 187 entity_pos_t x0 = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 188 entity_pos_t z0 = entity_pos_t::FromInt(j).Multiply(ICmpObstructionManager::NAVCELL_SIZE); 189 entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE; 190 entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE; 191 entity_pos_t nx = Clamp(x, x0, x1); 192 entity_pos_t nz = Clamp(z, z0, z1); 193 if (Geometry::PointIsInSquare(CFixedVector2D(nx - x, nz - z), u, v, CFixedVector2D(hw, hh))) 194 { 195 if (gi) *gi = i; 196 if (gj) *gj = j; 197 return true; 198 } 199 } 200 } 201 return false; 202 } 203 case INVERTED_CIRCLE: 204 { 205 return false; 206 } 207 case INVERTED_SQUARE: 208 { 209 // Haven't bothered implementing these, since they're not needed by the 210 // current pathfinder design 211 debug_warn(L"PathGoal::NavcellRectContainsGoal doesn't support inverted shapes"); 212 return false; 213 } 214 default: 215 { 216 debug_warn(L"invalid type"); 217 return false; 218 } 219 } 220 } 221 222 bool PathGoal::RectContainsGoal(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) const 223 { 224 switch (type) 225 { 226 case POINT: 227 { 228 return x0 <= x && x <= x1 && z0 <= z && z <= z1; 229 } 230 case CIRCLE: 231 { 232 entity_pos_t nx = Clamp(x, x0, x1); 233 entity_pos_t nz = Clamp(z, z0, z1); 234 return (CFixedVector2D(nx, nz) - CFixedVector2D(x, z)).CompareLength(hw) <= 0; 235 } 236 case SQUARE: 237 { 238 entity_pos_t nx = Clamp(x, x0, x1); 239 entity_pos_t nz = Clamp(z, z0, z1); 240 return Geometry::PointIsInSquare(CFixedVector2D(nx - x, nz - z), u, v, CFixedVector2D(hw, hh)); 241 } 242 case INVERTED_CIRCLE: 243 { 244 entity_pos_t nx = Clamp(x, x0, x1); 245 entity_pos_t nz = Clamp(z, z0, z1); 246 return (CFixedVector2D(nx, nz) - CFixedVector2D(x, z)).CompareLength(hw) > 0; 247 } 248 case INVERTED_SQUARE: 249 { 250 // Haven't bothered implementing these, since they're not needed by the 251 // current pathfinder design 252 debug_warn(L"PathGoal::RectContainsGoal doesn't support inverted shapes"); 253 return false; 254 } 255 default: 256 { 257 debug_warn(L"invalid type"); 258 return false; 259 } 260 } 261 } 262 263 fixed PathGoal::DistanceToPoint(CFixedVector2D pos) const 264 { 265 switch (type) 266 { 267 case PathGoal::POINT: 268 return (pos - CFixedVector2D(x, z)).Length(); 269 270 case PathGoal::CIRCLE: 271 case PathGoal::INVERTED_CIRCLE: 272 return ((pos - CFixedVector2D(x, z)).Length() - hw).Absolute(); 273 274 case PathGoal::SQUARE: 275 case PathGoal::INVERTED_SQUARE: 276 { 277 CFixedVector2D halfSize(hw, hh); 278 CFixedVector2D d(pos.X - x, pos.Y - z); 279 return Geometry::DistanceToSquare(d, u, v, halfSize); 280 } 281 282 default: 283 debug_warn(L"invalid type"); 284 return fixed::Zero(); 285 } 286 } -
new file source/simulation2/helpers/PathGoal.h
diff --git a/source/simulation2/helpers/PathGoal.h b/source/simulation2/helpers/PathGoal.h new file mode 100644 index 0000000..23bf1c0
- + 1 /* Copyright (C) 2012 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #ifndef INCLUDED_PATHGOAL 19 #define INCLUDED_PATHGOAL 20 21 #include "maths/FixedVector2D.h" 22 #include "simulation2/helpers/Position.h" 23 24 /** 25 * Pathfinder goal. 26 * The goal can be either a point, a circle, or a square (rectangle). 27 * For circles/squares, any point inside the shape is considered to be 28 * part of the goal. 29 * Also, it can be an 'inverted' circle/square, where any point outside 30 * the shape is part of the goal. 31 */ 32 class PathGoal 33 { 34 public: 35 enum Type { 36 POINT, // single point 37 CIRCLE, // the area inside a circle 38 INVERTED_CIRCLE, // the area outside a circle 39 SQUARE, // the area inside a square 40 INVERTED_SQUARE // the area outside a square 41 } type; 42 43 entity_pos_t x, z; // position of center 44 45 CFixedVector2D u, v; // if [INVERTED_]SQUARE, then orthogonal unit axes 46 47 entity_pos_t hw, hh; // if [INVERTED_]SQUARE, then half width & height; if [INVERTED_]CIRCLE, then hw is radius 48 49 /** 50 * Returns true if the given navcell contains a part of the goal area. 51 */ 52 bool NavcellContainsGoal(int i, int j) const; 53 54 /** 55 * Returns true if any navcell (i, j) where 56 * min(i0,i1) <= i <= max(i0,i1) 57 * min(j0,j1) <= j <= max(j0,j1), 58 * contains a part of the goal area. 59 * If so, arguments i and j (if not NULL) are set to the goal navcell nearest 60 * to (i0, j0), assuming the rect has either width or height = 1. 61 */ 62 bool NavcellRectContainsGoal(int i0, int j0, int i1, int j1, int* i, int* j) const; 63 64 /** 65 * Returns true if the rectangle defined by (x0,z0)-(x1,z1) (inclusive) 66 * contains a part of the goal area. 67 */ 68 bool RectContainsGoal(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) const; 69 70 /** 71 * Returns the minimum distance from the point with the given @p pos 72 * to any point on the outline of the goal shape. 73 */ 74 fixed DistanceToPoint(CFixedVector2D pos) const; 75 }; 76 77 #endif // INCLUDED_PATHGOAL -
source/simulation2/helpers/PriorityQueue.h
diff --git a/source/simulation2/helpers/PriorityQueue.h b/source/simulation2/helpers/PriorityQueue.h index 9e7c8e3..aecc754 100644
a b 32 32 template <typename Item, typename CMP> 33 33 struct QueueItemPriority 34 34 { 35 bool operator()(const Item& a, const Item& b) 35 bool operator()(const Item& a, const Item& b) const 36 36 { 37 37 if (CMP()(b.rank, a.rank)) // higher costs are lower priority 38 38 return true; 39 39 if (CMP()(a.rank, b.rank)) 40 40 return false; 41 41 // Need to tie-break to get a consistent ordering 42 // TODO: Should probably tie-break on g or h or something, but don't bother for now43 42 if (a.id < b.id) 44 43 return true; 45 44 if (b.id < a.id) … … public: 73 72 push_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority<Item, CMP>()); 74 73 } 75 74 76 Item* find(ID id) 77 { 78 for (size_t n = 0; n < m_Heap.size(); ++n) 79 { 80 if (m_Heap[n].id == id) 81 return &m_Heap[n]; 82 } 83 return NULL; 84 } 85 86 void promote(ID id, R newrank) 75 void promote(ID id, R UNUSED(oldrank), R newrank) 87 76 { 88 for (size_t n = 0; n < m_Heap.size(); ++n) 77 // Loop backwards since it seems a little faster in practice 78 for (ssize_t n = m_Heap.size() - 1; n >= 0; --n) 89 79 { 90 80 if (m_Heap[n].id == id) 91 81 { … … public: 104 94 #if PRIORITYQUEUE_DEBUG 105 95 ENSURE(m_Heap.size()); 106 96 #endif 107 Item r = m_Heap .front();97 Item r = m_Heap[0]; 108 98 pop_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority<Item, CMP>()); 109 99 m_Heap.pop_back(); 110 100 return r; … … public: 155 145 return NULL; 156 146 } 157 147 158 void promote(ID id, R newrank)148 void promote(ID id, R UNUSED(oldrank), R newrank) 159 149 { 160 150 find(id)->rank = newrank; 161 151 } -
new file source/simulation2/helpers/Rasterize.cpp
diff --git a/source/simulation2/helpers/Rasterize.cpp b/source/simulation2/helpers/Rasterize.cpp new file mode 100644 index 0000000..b493f2d
- + 1 /* Copyright (C) 2012 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #include "precompiled.h" 19 20 #include "Rasterize.h" 21 22 #include "simulation2/helpers/Geometry.h" 23 24 void SimRasterize::RasterizeRectWithClearance(Spans& spans, 25 const ICmpObstructionManager::ObstructionSquare& shape, 26 entity_pos_t clearance, entity_pos_t cellSize) 27 { 28 // Get the bounds of cells that might possibly be within the shape 29 // (We'll then test each of those cells more precisely) 30 CFixedVector2D halfSize(shape.hw + clearance, shape.hh + clearance); 31 CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize); 32 i16 i0 = ((shape.x - halfBound.X) / cellSize).ToInt_RoundToNegInfinity(); 33 i16 j0 = ((shape.z - halfBound.Y) / cellSize).ToInt_RoundToNegInfinity(); 34 i16 i1 = ((shape.x + halfBound.X) / cellSize).ToInt_RoundToInfinity(); 35 i16 j1 = ((shape.z + halfBound.Y) / cellSize).ToInt_RoundToInfinity(); 36 37 if (j1 <= j0) 38 return; // empty bounds - this shouldn't happen 39 40 spans.reserve(j1 - j0); 41 42 for (i16 j = j0; j < j1; ++j) 43 { 44 // Find the min/max range of cells that are strictly inside the square+clearance. 45 // (Since the square+clearance is a convex shape, we can just test each 46 // corner of each cell is inside the shape.) 47 // (TODO: This potentially does a lot of redundant work.) 48 i16 spanI0 = std::numeric_limits<i16>::max(); 49 i16 spanI1 = std::numeric_limits<i16>::min(); 50 for (i16 i = i0; i < i1; ++i) 51 { 52 if (Geometry::DistanceToSquare( 53 CFixedVector2D(cellSize*i, cellSize*j) - CFixedVector2D(shape.x, shape.z), 54 shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh), true) > clearance) 55 { 56 continue; 57 } 58 59 if (Geometry::DistanceToSquare( 60 CFixedVector2D(cellSize*(i+1), cellSize*j) - CFixedVector2D(shape.x, shape.z), 61 shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh), true) > clearance) 62 { 63 continue; 64 } 65 66 if (Geometry::DistanceToSquare( 67 CFixedVector2D(cellSize*i, cellSize*(j+1)) - CFixedVector2D(shape.x, shape.z), 68 shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh), true) > clearance) 69 { 70 continue; 71 } 72 73 if (Geometry::DistanceToSquare( 74 CFixedVector2D(cellSize*(i+1), cellSize*(j+1)) - CFixedVector2D(shape.x, shape.z), 75 shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh), true) > clearance) 76 { 77 continue; 78 } 79 80 spanI0 = std::min(spanI0, i); 81 spanI1 = std::max(spanI1, (i16)(i+1)); 82 } 83 84 // Add non-empty spans onto the list 85 if (spanI0 < spanI1) 86 { 87 Span span = { spanI0, spanI1, j }; 88 spans.push_back(span); 89 } 90 } 91 } -
new file source/simulation2/helpers/Rasterize.h
diff --git a/source/simulation2/helpers/Rasterize.h b/source/simulation2/helpers/Rasterize.h new file mode 100644 index 0000000..bfee13c
- + 1 /* Copyright (C) 2012 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #ifndef INCLUDED_HELPER_RASTERIZE 19 #define INCLUDED_HELPER_RASTERIZE 20 21 /** 22 * @file 23 * Helper functions related to rasterizing geometric shapes to grids. 24 */ 25 26 #include "simulation2/components/ICmpObstructionManager.h" 27 28 namespace SimRasterize 29 { 30 31 /** 32 * Represents the set of cells (i,j) where i0 <= i < i1 33 */ 34 struct Span 35 { 36 i16 i0; 37 i16 i1; 38 i16 j; 39 }; 40 41 typedef std::vector<Span> Spans; 42 43 /** 44 * Converts an ObstructionSquare @p shape (a rotated rectangle), 45 * expanded by the given @p clearance, 46 * into a list of spans of cells that are strictly inside the shape. 47 */ 48 void RasterizeRectWithClearance(Spans& spans, 49 const ICmpObstructionManager::ObstructionSquare& shape, 50 entity_pos_t clearance, entity_pos_t cellSize); 51 52 } 53 54 #endif // INCLUDED_HELPER_RASTERIZE -
source/simulation2/helpers/Render.cpp
diff --git a/source/simulation2/helpers/Render.cpp b/source/simulation2/helpers/Render.cpp index 2bee4d2..4531470 100644
a b void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vec 67 67 } 68 68 } 69 69 70 void SimRender::ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius, 71 SOverlayLine& overlay, bool floating, float heightOffset) 70 static void ConstructCircleOrClosedArc( 71 const CSimContext& context, float x, float z, float radius, 72 bool isCircle, 73 float start, float end, 74 SOverlayLine& overlay, bool floating, float heightOffset) 72 75 { 73 76 overlay.m_Coords.clear(); 74 77 … … void SimRender::ConstructCircleOnGround(const CSimContext& context, float x, flo 79 82 float water = 0.f; 80 83 if (floating) 81 84 { 82 CmpPtr<ICmpWaterManager> cmpWaterMan ager(context, SYSTEM_ENTITY);83 if (cmpWaterMan ager)84 water = cmpWaterMan ager->GetExactWaterLevel(x, z);85 CmpPtr<ICmpWaterManager> cmpWaterMan(context, SYSTEM_ENTITY); 86 if (cmpWaterMan) 87 water = cmpWaterMan->GetExactWaterLevel(x, z); 85 88 } 86 89 87 90 // Adapt the circle resolution to look reasonable for small and largeish radiuses 88 size_t numPoints = clamp((size_t)(radius* 4.0f), (size_t)12, (size_t)48);91 size_t numPoints = clamp((size_t)(radius*(end-start)), (size_t)12, (size_t)48); 89 92 90 overlay.m_Coords.reserve((numPoints + 1) * 3); 93 if (isCircle) 94 overlay.m_Coords.reserve((numPoints + 1 + 2) * 3); 95 else 96 overlay.m_Coords.reserve((numPoints + 1) * 3); 97 98 float cy; 99 if (!isCircle) 100 { 101 // Start at the center point 102 cy = std::max(water, cmpTerrain->GetExactGroundLevel(x, z)) + heightOffset; 103 overlay.m_Coords.push_back(x); 104 overlay.m_Coords.push_back(cy); 105 overlay.m_Coords.push_back(z); 106 } 91 107 92 108 for (size_t i = 0; i <= numPoints; ++i) // use '<=' so it's a closed loop 93 109 { 94 float a = (float)i * 2 * (float)M_PI/ (float)numPoints;95 float px = x + radius * sinf(a);96 float pz = z + radius * cosf(a);110 float a = start + (float)i * (end - start) / (float)numPoints; 111 float px = x + radius * cosf(a); 112 float pz = z + radius * sinf(a); 97 113 float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; 98 114 overlay.m_Coords.push_back(px); 99 115 overlay.m_Coords.push_back(py); 100 116 overlay.m_Coords.push_back(pz); 101 117 } 118 119 if (!isCircle) 120 { 121 // Return to the center point 122 overlay.m_Coords.push_back(x); 123 overlay.m_Coords.push_back(cy); 124 overlay.m_Coords.push_back(z); 125 } 126 } 127 128 void SimRender::ConstructCircleOnGround( 129 const CSimContext& context, float x, float z, float radius, 130 SOverlayLine& overlay, bool floating, float heightOffset) 131 { 132 ConstructCircleOrClosedArc(context, x, z, radius, true, 0.0f, 2.0f*(float)M_PI, overlay, floating, heightOffset); 133 } 134 135 void SimRender::ConstructClosedArcOnGround( 136 const CSimContext& context, float x, float z, float radius, 137 float start, float end, 138 SOverlayLine& overlay, bool floating, float heightOffset) 139 { 140 ConstructCircleOrClosedArc(context, x, z, radius, false, start, end, overlay, floating, heightOffset); 102 141 } 103 142 104 143 // This method splits up a straight line into a number of line segments each having a length ~= TERRAIN_TILE_SIZE -
source/simulation2/helpers/Render.h
diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h index 6ed8be2..fd4647d 100644
a b namespace SimRender 64 64 * @param[in] floating If true, the line conforms to water as well. 65 65 * @param[in] heightOffset Height above terrain to offset the line. 66 66 */ 67 void ConstructLineOnGround(const CSimContext& context, const std::vector<float>& xz, 67 void ConstructLineOnGround( 68 const CSimContext& context, const std::vector<float>& xz, 68 69 SOverlayLine& overlay, 69 70 bool floating, float heightOffset = 0.25f); 70 71 … … void ConstructLineOnGround(const CSimContext& context, const std::vector<float>& 78 79 * @param[in] heightOffset Height above terrain to offset the circle. 79 80 * @param heightOffset The vertical offset to apply to points, to raise the line off the terrain a bit. 80 81 */ 81 void ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius, 82 void ConstructCircleOnGround( 83 const CSimContext& context, float x, float z, float radius, 84 SOverlayLine& overlay, 85 bool floating, float heightOffset = 0.25f); 86 87 /** 88 * Constructs overlay line as an outlined circle sector (an arc with straight lines between the 89 * endpoints and the circle's center), conforming to terrain. 90 */ 91 void ConstructClosedArcOnGround( 92 const CSimContext& context, float x, float z, float radius, 93 float start, float end, 82 94 SOverlayLine& overlay, 83 95 bool floating, float heightOffset = 0.25f); 84 96 … … void ConstructCircleOnGround(const CSimContext& context, float x, float z, float 92 104 * @param[in] floating If true, the rectangle conforms to water as well. 93 105 * @param[in] heightOffset Height above terrain to offset the rectangle. 94 106 */ 95 void ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a, 107 void ConstructSquareOnGround( 108 const CSimContext& context, float x, float z, float w, float h, float a, 96 109 SOverlayLine& overlay, 97 110 bool floating, float heightOffset = 0.25f); 98 111 -
source/simulation2/serialization/SerializeTemplates.h
diff --git a/source/simulation2/serialization/SerializeTemplates.h b/source/simulation2/serialization/SerializeTemplates.h index 44fdf01..436f369 100644
a b struct SerializeWaypoint 191 191 struct SerializeGoal 192 192 { 193 193 template<typename S> 194 void operator()(S& serialize, const char* UNUSED(name), ICmpPathfinder::Goal& value)194 void operator()(S& serialize, const char* UNUSED(name), PathGoal& value) 195 195 { 196 SerializeU8_Enum< ICmpPathfinder::Goal::Type, ICmpPathfinder::Goal::SQUARE>()(serialize, "type", value.type);196 SerializeU8_Enum<PathGoal::Type, PathGoal::SQUARE>()(serialize, "type", value.type); 197 197 serialize.NumberFixed_Unbounded("goal x", value.x); 198 198 serialize.NumberFixed_Unbounded("goal z", value.z); 199 199 serialize.NumberFixed_Unbounded("goal u x", value.u.X); -
source/tools/atlas/GameInterface/View.cpp
diff --git a/source/tools/atlas/GameInterface/View.cpp b/source/tools/atlas/GameInterface/View.cpp index 9500038..85d5be4 100644
a b void AtlasViewGame::Render() 247 247 { 248 248 cmpPathfinder->SetDebugOverlay(true); 249 249 // Kind of a hack to make it update the terrain grid 250 ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, fixed::Zero(), fixed::Zero() };250 PathGoal goal = { PathGoal::POINT, fixed::Zero(), fixed::Zero() }; 251 251 ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(m_DisplayPassability); 252 ICmpPathfinder::cost_class_t costClass = cmpPathfinder->GetCostClass("default"); 253 cmpPathfinder->SetDebugPath(fixed::Zero(), fixed::Zero(), goal, passClass, costClass); 252 cmpPathfinder->SetDebugPath(fixed::Zero(), fixed::Zero(), goal, passClass); 254 253 } 255 254 } 256 255