virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_PositionChanged: { if (!m_Active) break; const CMessagePositionChanged& data = static_cast<const CMessagePositionChanged&> (msg); if (!data.inWorld && !m_Tag.valid()) break; // nothing needs to change CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpObstructionManager) break; // error if (data.inWorld && m_Tag.valid()) { cmpObstructionManager->MoveShape(m_Tag, data.x, data.z, data.a); } else if (data.inWorld && !m_Tag.valid()) { // Need to create a new pathfinder shape: if (m_Type == STATIC) m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), data.x, data.z, data.a, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2); else m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), data.x, data.z, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); } else if (!data.inWorld && m_Tag.valid()) { cmpObstructionManager->RemoveShape(m_Tag); m_Tag = tag_t(); } break; } case MT_Destroy: { if (m_Tag.valid()) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpObstructionManager) break; // error cmpObstructionManager->RemoveShape(m_Tag); m_Tag = tag_t(); } break; } } }
virtual void SetActive(bool active) { if (active && !m_Active) { m_Active = true; // Construct the obstruction shape CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return; // error CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); if (!cmpPosition) return; // error if (!cmpPosition->IsInWorld()) return; // don't need an obstruction // TODO: code duplication from message handlers CFixedVector2D pos = cmpPosition->GetPosition2D(); if (m_Type == STATIC) m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2); else if (m_Type == UNIT) m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), pos.X, pos.Y, m_Size0, m_Clearance, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); else AddClusterShapes(pos.X, pos.Y, cmpPosition->GetRotation().Y); } else if (!active && m_Active) { m_Active = false; // Delete the obstruction shape // TODO: code duplication from message handlers if (m_Tag.valid()) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return; // error cmpObstructionManager->RemoveShape(m_Tag); m_Tag = tag_t(); if (m_Type == CLUSTER) RemoveClusterShapes(); } } // else we didn't change the active status }
virtual void ReloadTerrain() { // TODO: should refactor this code to be nicer u16 tiles = GetTilesPerSide(); u16 vertices = GetVerticesPerSide(); CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (cmpObstructionManager) { cmpObstructionManager->SetBounds(entity_pos_t::Zero(), entity_pos_t::Zero(), entity_pos_t::FromInt(tiles*(int)TERRAIN_TILE_SIZE), entity_pos_t::FromInt(tiles*(int)TERRAIN_TILE_SIZE)); } CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity()); if (cmpRangeManager) { cmpRangeManager->SetBounds(entity_pos_t::Zero(), entity_pos_t::Zero(), entity_pos_t::FromInt(tiles*(int)TERRAIN_TILE_SIZE), entity_pos_t::FromInt(tiles*(int)TERRAIN_TILE_SIZE), vertices); } if (CRenderer::IsInitialised()) g_Renderer.GetWaterManager()->SetMapSize(vertices); MakeDirty(0, 0, tiles+1, tiles+1); }
virtual bool CheckDuplicateFoundation() { CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); if (!cmpPosition) return false; // error if (!cmpPosition->IsInWorld()) return false; // no obstruction CFixedVector2D pos = cmpPosition->GetPosition2D(); CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return false; // error // required precondition to use SkipControlGroupsRequireFlagObstructionFilter if (m_ControlGroup == INVALID_ENTITY) { LOGERROR("[CmpObstruction] Cannot test for foundation obstructions; primary control group must be valid"); return false; } // Ignore collisions with entities unless they block foundations and match both control groups. SkipTagRequireControlGroupsAndFlagObstructionFilter filter(m_Tag, m_ControlGroup, m_ControlGroup2, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION); if (m_Type == UNIT) return !cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, NULL); else return !cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, NULL ); }
bool CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass) { // Check unit obstruction CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager.null()) return false; if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL)) return false; // Test against terrain: UpdateGrid(); u16 i0, j0, i1, j1; NearestTile(x - r, z - r, i0, j0); NearestTile(x + r, z + r, i1, j1); for (u16 j = j0; j <= j1; ++j) { for (u16 i = i0; i <= i1; ++i) { if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass)) { return false; } } } return true; }
virtual std::vector<entity_id_t> GetConstructionCollisions() { std::vector<entity_id_t> ret; CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); if (cmpPosition.null()) return ret; // error if (!cmpPosition->IsInWorld()) return ret; // no obstruction CFixedVector2D pos = cmpPosition->GetPosition2D(); CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager.null()) return ret; // error // Ignore collisions with self, or with non-construction-blocking obstructions SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); if (m_Type == STATIC) cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret); else cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, &ret); return ret; }
void AtlasViewGame::Render() { SViewPort vp = { 0, 0, g_xres, g_yres }; CCamera& camera = GetCamera(); camera.SetViewPort(vp); camera.SetProjection(g_Game->GetView()->GetNear(), g_Game->GetView()->GetFar(), g_Game->GetView()->GetFOV()); camera.UpdateFrustum(); // Update the pathfinder display if necessary if (!m_DisplayPassability.empty()) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(*GetSimulation2(), SYSTEM_ENTITY); if (cmpObstructionManager) { cmpObstructionManager->SetDebugOverlay(true); } CmpPtr<ICmpPathfinder> cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY); if (cmpPathfinder) { cmpPathfinder->SetDebugOverlay(true); // Kind of a hack to make it update the terrain grid PathGoal goal = { PathGoal::POINT, fixed::Zero(), fixed::Zero() }; pass_class_t passClass = cmpPathfinder->GetPassabilityClass(m_DisplayPassability); cmpPathfinder->SetDebugPath(fixed::Zero(), fixed::Zero(), goal, passClass); } } ::Render(); Atlas_GLSwapBuffers((void*)g_AtlasGameLoop->glCanvas); }
void UpdateControlGroups() { if (m_Tag.valid()) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (cmpObstructionManager) { if (m_Type == UNIT) { cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup); } else if (m_Type == STATIC) { cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2); } else { cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2); for (size_t i = 0; i < m_ClusterTags.size(); ++i) { cmpObstructionManager->SetStaticControlGroup(m_ClusterTags[i], m_ControlGroup, m_ControlGroup2); } } } } }
virtual void SetActive(bool active) { if (active && !m_Active) { m_Active = true; // Construct the obstruction shape CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager.null()) return; // error CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); if (cmpPosition.null()) return; // error if (!cmpPosition->IsInWorld()) return; // don't need an obstruction CFixedVector2D pos = cmpPosition->GetPosition2D(); if (m_Type == STATIC) m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, m_Flags); else m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), pos.X, pos.Y, m_Size0, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); } else if (!active && m_Active) { m_Active = false; // Delete the obstruction shape if (m_Tag.valid()) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager.null()) return; // error cmpObstructionManager->RemoveShape(m_Tag); m_Tag = tag_t(); } } // else we didn't change the active status }
ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass, bool onlyCenterPoint) { // Check unit obstruction CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpObstructionManager) return ICmpObstruction::FOUNDATION_CHECK_FAIL_ERROR; if (cmpObstructionManager->TestStaticShape(filter, x, z, a, w, h, NULL)) return ICmpObstruction::FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION; // Test against terrain: UpdateGrid(); ICmpObstructionManager::ObstructionSquare square; CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id); if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square)) return ICmpObstruction::FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION; if (onlyCenterPoint) { u16 i, j; NearestTile(x, z, i, j); if (IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass)) return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; } // Expand bounds by 1/sqrt(2) tile (multiply by TERRAIN_TILE_SIZE since we want world coordinates) entity_pos_t expand = entity_pos_t::FromInt(2).Sqrt().Multiply(entity_pos_t::FromInt(TERRAIN_TILE_SIZE / 2)); CFixedVector2D halfSize(square.hw + expand, square.hh + expand); CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize); u16 i0, j0, i1, j1; NearestTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0); NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1); for (u16 j = j0; j <= j1; ++j) { for (u16 i = i0; i <= i1; ++i) { entity_pos_t x, z; TileCenter(i, j, x, z); if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize) && !IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass)) { return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; } } } return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
virtual void SetMovingFlag(bool enabled) { m_Moving = enabled; if (m_Tag.valid() && m_Type == UNIT) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager) cmpObstructionManager->SetUnitMovingFlag(m_Tag, m_Moving); } }
virtual void SetControlGroup(entity_id_t group) { m_ControlGroup = group; if (m_Tag.valid() && m_Type == UNIT) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpObstructionManager.null()) cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup); } }
virtual void SetMovingFlag(bool enabled) { m_Moving = enabled; if (m_Tag.valid() && m_Type == UNIT) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (cmpObstructionManager) cmpObstructionManager->SetUnitMovingFlag(m_Tag, m_Moving); } }
bool CCmpPathfinder::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) { // Test against obstructions first. filter may discard pathfinding-blocking obstructions. CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager || cmpObstructionManager->TestLine(filter, x0, z0, x1, z1, r)) return false; // Then test against the passability grid. return Pathfinding::CheckLineMovement(x0, z0, x1, z1, passClass, *m_Grid); }
bool CCmpPathfinder::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) { // Test against obstructions first CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return false; if (cmpObstructionManager->TestLine(filter, x0, z0, x1, z1, r)) return false; // Then test against the terrain return Pathfinding::CheckLineMovement(x0, z0, x1, z1, passClass, *m_TerrainOnlyGrid); }
virtual std::vector<entity_id_t> GetEntityCollisions(bool checkStructures, bool checkUnits) { // TODO: That function actually only checks for overlapping units when a new buidling is started. // The name of the function should be changed and useless code removed. std::vector<entity_id_t> ret; CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return ret; // error flags_t flags = 0; bool invertMatch = false; // There are four 'block' flags: construction, foundation, movement, // and pathfinding. Structures have all of these flags, while units // block only movement and construction. // The 'block construction' flag is common to both units and structures. if (checkStructures && checkUnits) flags = ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION; // The 'block foundation' flag is exclusive to structures. else if (checkStructures) flags = ICmpObstructionManager::FLAG_BLOCK_FOUNDATION; else if (checkUnits) { // As structures block a superset of what units do, matching units // but not structures means excluding entities that contain any of // the structure-specific flags (foundation and pathfinding). invertMatch = true; flags = ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_PATHFINDING; } // Ignore collisions within the same control group, or with other shapes that don't match the filter. // Note that, since the control group for each entity defaults to the entity's ID, this is typically // equivalent to only ignoring the entity's own shape and other shapes that don't match the filter. SkipControlGroupsRequireFlagObstructionFilter filter(invertMatch, m_ControlGroup, m_ControlGroup2, flags); ICmpObstructionManager::ObstructionSquare square; if (!GetObstructionSquare(square)) return ret; // error cmpObstructionManager->GetUnitsOnObstruction(square, ret, filter); return ret; }
void UpdateControlGroups() { if (m_Tag.valid()) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager) { if (m_Type == UNIT) { cmpObstructionManager->SetUnitControlGroup(m_Tag, m_ControlGroup); } else if (m_Type == STATIC) { cmpObstructionManager->SetStaticControlGroup(m_Tag, m_ControlGroup, m_ControlGroup2); } } } }
ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass, bool UNUSED(onlyCenterPoint)) { // Check unit obstruction CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return ICmpObstruction::FOUNDATION_CHECK_FAIL_ERROR; if (cmpObstructionManager->TestStaticShape(filter, x, z, a, w, h, NULL)) return ICmpObstruction::FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION; // Test against terrain: ICmpObstructionManager::ObstructionSquare square; CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id); if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square)) return ICmpObstruction::FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION; entity_pos_t expand; const PathfinderPassability* passability = GetPassabilityFromMask(passClass); if (passability) expand = passability->m_Clearance; SimRasterize::Spans spans; SimRasterize::RasterizeRectWithClearance(spans, square, expand, Pathfinding::NAVCELL_SIZE); for (SimRasterize::Span& span : spans) { i16 i0 = span.i0; i16 i1 = span.i1; i16 j = span.j; // Fail if any span extends outside the grid if (i0 < 0 || i1 > m_TerrainOnlyGrid->m_W || j < 0 || j > m_TerrainOnlyGrid->m_H) return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; // Fail if any span includes an impassable tile for (i16 i = i0; i < i1; ++i) if (!IS_PASSABLE(m_TerrainOnlyGrid->get(i, j), passClass)) return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; } return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
void AtlasViewGame::SetParam(const std::wstring& name, const std::wstring& value) { if (name == L"passability") { m_DisplayPassability = CStrW(value).ToUTF8(); CmpPtr<ICmpObstructionManager> cmpObstructionManager(*GetSimulation2(), SYSTEM_ENTITY); if (cmpObstructionManager) cmpObstructionManager->SetDebugOverlay(!value.empty()); CmpPtr<ICmpPathfinder> cmpPathfinder(*GetSimulation2(), SYSTEM_ENTITY); if (cmpPathfinder) cmpPathfinder->SetDebugOverlay(!value.empty()); } else if (name == L"renderpath") { g_Renderer.SetRenderPath(g_Renderer.GetRenderPathByName(CStrW(value).ToUTF8())); } }
bool CCmpPathfinder::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) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (cmpObstructionManager.null()) return false; if (cmpObstructionManager->TestLine(filter, x0, z0, x1, z1, r)) return false; // Test against terrain: // (TODO: this could probably be a tiny bit faster by not reusing all the vertex computation code) UpdateGrid(); std::vector<Edge> edgesAA; std::vector<Vertex> vertexes; u16 i0, j0, i1, j1; NearestTile(std::min(x0, x1) - r, std::min(z0, z1) - r, i0, j0); NearestTile(std::max(x0, x1) + r, std::max(z0, z1) + r, i1, j1); AddTerrainEdges(edgesAA, vertexes, i0, j0, i1, j1, r, passClass, *m_Grid); CFixedVector2D a(x0, z0); CFixedVector2D b(x1, z1); std::vector<EdgeAA> edgesLeft; std::vector<EdgeAA> edgesRight; std::vector<EdgeAA> edgesBottom; std::vector<EdgeAA> edgesTop; SplitAAEdges(a, edgesAA, edgesLeft, edgesRight, edgesBottom, edgesTop); bool visible = CheckVisibilityLeft(a, b, edgesLeft) && CheckVisibilityRight(a, b, edgesRight) && CheckVisibilityBottom(a, b, edgesBottom) && CheckVisibilityTop(a, b, edgesTop); return visible; }
virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) { CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); if (!cmpPosition) return false; // error CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpObstructionManager) return false; // error if (!cmpPosition->IsInWorld()) return false; // no obstruction square CFixedVector2D pos = cmpPosition->GetPosition2D(); if (m_Type == STATIC) out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1); else out = cmpObstructionManager->GetUnitShapeObstruction(pos.X, pos.Y, m_Size0); return true; }
ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass, bool onlyCenterPoint) { // Check unit obstruction CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpObstructionManager) return ICmpObstruction::FOUNDATION_CHECK_FAIL_ERROR; if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL)) return ICmpObstruction::FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION; // Test against terrain: UpdateGrid(); if (onlyCenterPoint) { u16 i, j; NearestTile(x , z, i, j); if (IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass)) return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; } u16 i0, j0, i1, j1; NearestTile(x - r, z - r, i0, j0); NearestTile(x + r, z + r, i1, j1); for (u16 j = j0; j <= j1; ++j) { for (u16 i = i0; i <= i1; ++i) { if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass)) { return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; } } } return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
virtual void ReloadTerrain() { // TODO: should refactor this code to be nicer CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpObstructionManager.null()) { cmpObstructionManager->SetBounds(entity_pos_t::Zero(), entity_pos_t::Zero(), entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE), entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE)); } CmpPtr<ICmpRangeManager> cmpRangeManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpRangeManager.null()) { cmpRangeManager->SetBounds(entity_pos_t::Zero(), entity_pos_t::Zero(), entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE), entity_pos_t::FromInt(m_Terrain->GetTilesPerSide()*CELL_SIZE), m_Terrain->GetVerticesPerSide()); } MakeDirty(0, 0, m_Terrain->GetTilesPerSide()+1, m_Terrain->GetTilesPerSide()+1); }
virtual std::vector<entity_id_t> GetConstructionCollisions() { std::vector<entity_id_t> ret; CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); if (!cmpPosition) return ret; // error if (!cmpPosition->IsInWorld()) return ret; // no obstruction CFixedVector2D pos = cmpPosition->GetPosition2D(); CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpObstructionManager) return ret; // error // required precondition to use SkipControlGroupsRequireFlagObstructionFilter if (m_ControlGroup == INVALID_ENTITY) { LOGERROR(L"[CmpObstruction] Cannot test for construction obstructions; primary control group must be valid"); return ret; } // Ignore collisions within the same control group, or with other non-construction-blocking shapes. // Note that, since the control group for each entity defaults to the entity's ID, this is typically // equivalent to only ignoring the entity's own shape and other non-construction-blocking shapes. SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2, ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION); if (m_Type == STATIC) cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret); else cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, &ret); return ret; }
ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass, bool UNUSED(onlyCenterPoint)) { // Check unit obstruction CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return ICmpObstruction::FOUNDATION_CHECK_FAIL_ERROR; if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL)) return ICmpObstruction::FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION; // Test against terrain and static obstructions: u16 i, j; Pathfinding::NearestNavcell(x, z, i, j, m_MapSize*Pathfinding::NAVCELLS_PER_TILE, m_MapSize*Pathfinding::NAVCELLS_PER_TILE); if (!IS_PASSABLE(m_Grid->get(i, j), passClass)) return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; // (Static obstructions will be redundantly tested against in both the // obstruction-shape test and navcell-passability test, which is slightly // inefficient but shouldn't affect behaviour) return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; }
virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out, bool previousPosition) { CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); if (!cmpPosition) return false; // error CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return false; // error if (!cmpPosition->IsInWorld()) return false; // no obstruction square CFixedVector2D pos; if (previousPosition) pos = cmpPosition->GetPreviousPosition2D(); else pos = cmpPosition->GetPosition2D(); if (m_Type == UNIT) out = cmpObstructionManager->GetUnitShapeObstruction(pos.X, pos.Y, m_Size0); else out = cmpObstructionManager->GetStaticShapeObstruction(pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1); return true; }
void CCmpPathfinder::TerrainUpdateHelper(bool expandPassability) { PROFILE3("TerrainUpdateHelper"); CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); CTerrain& terrain = GetSimContext().GetTerrain(); if (!cmpTerrain || !cmpObstructionManager) return; u16 terrainSize = cmpTerrain->GetTilesPerSide(); if (terrainSize == 0) return; if (!m_TerrainOnlyGrid || m_MapSize != terrainSize) { m_MapSize = terrainSize; SAFE_DELETE(m_TerrainOnlyGrid); m_TerrainOnlyGrid = new Grid<NavcellData>(m_MapSize * Pathfinding::NAVCELLS_PER_TILE, m_MapSize * Pathfinding::NAVCELLS_PER_TILE); } Grid<u16> shoreGrid = ComputeShoreGrid(); // Compute initial terrain-dependent passability for (int j = 0; j < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++j) { for (int i = 0; i < m_MapSize * Pathfinding::NAVCELLS_PER_TILE; ++i) { // World-space coordinates for this navcell fixed x, z; Pathfinding::NavcellCenter(i, j, x, z); // Terrain-tile coordinates for this navcell int itile = i / Pathfinding::NAVCELLS_PER_TILE; int jtile = j / Pathfinding::NAVCELLS_PER_TILE; // Gather all the data potentially needed to determine passability: fixed height = terrain.GetExactGroundLevelFixed(x, z); fixed water; if (cmpWaterManager) water = cmpWaterManager->GetWaterLevel(x, z); fixed depth = water - height; // Exact slopes give kind of weird output, so just use rough tile-based slopes fixed slope = terrain.GetSlopeFixed(itile, jtile); // Get world-space coordinates from shoreGrid (which uses terrain tiles) fixed shoredist = fixed::FromInt(shoreGrid.get(itile, jtile)).MultiplyClamp(TERRAIN_TILE_SIZE); // Compute the passability for every class for this cell NavcellData t = 0; for (PathfinderPassability& passability : m_PassClasses) if (!passability.IsPassable(depth, slope, shoredist)) t |= passability.m_Mask; m_TerrainOnlyGrid->set(i, j, t); } } // Compute off-world passability // WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this const int edgeSize = 3 * Pathfinding::NAVCELLS_PER_TILE; // number of tiles around the edge that will be off-world NavcellData edgeMask = 0; for (PathfinderPassability& passability : m_PassClasses) edgeMask |= passability.m_Mask; int w = m_TerrainOnlyGrid->m_W; int h = m_TerrainOnlyGrid->m_H; if (cmpObstructionManager->GetPassabilityCircular()) { for (int j = 0; j < h; ++j) { for (int i = 0; i < w; ++i) { // Based on CCmpRangeManager::LosIsOffWorld // but tweaked since it's tile-based instead. // (We double all the values so we can handle half-tile coordinates.) // This needs to be slightly tighter than the LOS circle, // else units might get themselves lost in the SoD around the edge. int dist2 = (i*2 + 1 - w)*(i*2 + 1 - w) + (j*2 + 1 - h)*(j*2 + 1 - h); if (dist2 >= (w - 2*edgeSize) * (h - 2*edgeSize)) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); } } } else { for (u16 j = 0; j < h; ++j) for (u16 i = 0; i < edgeSize; ++i) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); for (u16 j = 0; j < h; ++j) for (u16 i = w-edgeSize+1; i < w; ++i) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); for (u16 j = 0; j < edgeSize; ++j) for (u16 i = edgeSize; i < w-edgeSize+1; ++i) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); for (u16 j = h-edgeSize+1; j < h; ++j) for (u16 i = edgeSize; i < w-edgeSize+1; ++i) m_TerrainOnlyGrid->set(i, j, m_TerrainOnlyGrid->get(i, j) | edgeMask); } if (!expandPassability) return; // Expand the impassability grid, for any class with non-zero clearance, // so that we can stop units getting too close to impassable navcells. // Note: It's not possible to perform this expansion once for all passabilities // with the same clearance, because the impassable cells are not necessarily the // same for all these passabilities. for (PathfinderPassability& passability : m_PassClasses) { if (passability.m_Clearance == fixed::Zero()) continue; int clearance = (passability.m_Clearance / Pathfinding::NAVCELL_SIZE).ToInt_RoundToInfinity(); ExpandImpassableCells(*m_TerrainOnlyGrid, clearance, passability.m_Mask); } }
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_PositionChanged: { if (!m_Active) break; const CMessagePositionChanged& data = static_cast<const CMessagePositionChanged&> (msg); if (!data.inWorld && !m_Tag.valid()) break; // nothing needs to change CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) break; // error if (data.inWorld && m_Tag.valid()) { cmpObstructionManager->MoveShape(m_Tag, data.x, data.z, data.a); if(m_Type == CLUSTER) { for (size_t i = 0; i < m_Shapes.size(); ++i) { Shape& b = m_Shapes[i]; fixed s, c; sincos_approx(data.a, s, c); cmpObstructionManager->MoveShape(m_ClusterTags[i], data.x + b.dx.Multiply(c) + b.dz.Multiply(s), data.z + b.dz.Multiply(c) - b.dx.Multiply(s), data.a + b.da); } } } else if (data.inWorld && !m_Tag.valid()) { // Need to create a new pathfinder shape: if (m_Type == STATIC) m_Tag = cmpObstructionManager->AddStaticShape(GetEntityId(), data.x, data.z, data.a, m_Size0, m_Size1, m_Flags, m_ControlGroup, m_ControlGroup2); else if (m_Type == UNIT) m_Tag = cmpObstructionManager->AddUnitShape(GetEntityId(), data.x, data.z, m_Size0, m_Clearance, (flags_t)(m_Flags | (m_Moving ? ICmpObstructionManager::FLAG_MOVING : 0)), m_ControlGroup); else AddClusterShapes(data.x, data.x, data.a); } else if (!data.inWorld && m_Tag.valid()) { cmpObstructionManager->RemoveShape(m_Tag); m_Tag = tag_t(); if(m_Type == CLUSTER) RemoveClusterShapes(); } break; } case MT_Destroy: { if (m_Tag.valid()) { CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) break; // error cmpObstructionManager->RemoveShape(m_Tag); m_Tag = tag_t(); if(m_Type == CLUSTER) RemoveClusterShapes(); } break; } } }
void CCmpPathfinder::UpdateGrid() { CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY); if (!cmpTerrain) return; // error // If the terrain was resized then delete the old grid data if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide()) { SAFE_DELETE(m_Grid); SAFE_DELETE(m_ObstructionGrid); m_TerrainDirty = true; } // Initialise the terrain data when first needed if (!m_Grid) { m_MapSize = cmpTerrain->GetTilesPerSide(); m_Grid = new Grid<TerrainTile>(m_MapSize, m_MapSize); m_ObstructionGrid = new Grid<u8>(m_MapSize, m_MapSize); } CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); bool obstructionsDirty = cmpObstructionManager->Rasterise(*m_ObstructionGrid); if (obstructionsDirty && !m_TerrainDirty) { PROFILE("UpdateGrid obstructions"); // Obstructions changed - we need to recompute passability // Since terrain hasn't changed we only need to update the obstruction bits // and can skip the rest of the data // TODO: if ObstructionManager::SetPassabilityCircular was called at runtime // (which should probably never happen, but that's not guaranteed), // then TILE_OUTOFBOUNDS will change and we can't use this fast path, but // currently it'll just set obstructionsDirty and we won't notice for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { TerrainTile& t = m_Grid->get(i, j); u8 obstruct = m_ObstructionGrid->get(i, j); if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING) t |= 1; else t &= (TerrainTile)~1; if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION) t |= 2; else t &= (TerrainTile)~2; } } ++m_Grid->m_DirtyID; } else if (obstructionsDirty || m_TerrainDirty) { PROFILE("UpdateGrid full"); // Obstructions or terrain changed - we need to recompute passability // TODO: only bother recomputing the region that has actually changed CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); // TOOD: these bits should come from ICmpTerrain CTerrain& terrain = GetSimContext().GetTerrain(); // avoid integer overflow in intermediate calculation const u16 shoreMax = 32767; // First pass - find underwater tiles Grid<bool> waterGrid(m_MapSize, m_MapSize); for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { fixed x, z; TileCenter(i, j, x, z); bool underWater = cmpWaterManager && (cmpWaterManager->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z)); waterGrid.set(i, j, underWater); } } // Second pass - find shore tiles Grid<u16> shoreGrid(m_MapSize, m_MapSize); for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { // Find a land tile if (!waterGrid.get(i, j)) { 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)) || (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)) || (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1)) ) { // If it's bordered by water, it's a shore tile shoreGrid.set(i, j, 0); } else { shoreGrid.set(i, j, shoreMax); } } } } // Expand influences on land to find shore distance for (u16 y = 0; y < m_MapSize; ++y) { u16 min = shoreMax; for (u16 x = 0; x < m_MapSize; ++x) { if (!waterGrid.get(x, y)) { u16 g = shoreGrid.get(x, y); if (g > min) shoreGrid.set(x, y, min); else if (g < min) min = g; ++min; } } for (u16 x = m_MapSize; x > 0; --x) { if (!waterGrid.get(x-1, y)) { u16 g = shoreGrid.get(x-1, y); if (g > min) shoreGrid.set(x-1, y, min); else if (g < min) min = g; ++min; } } } for (u16 x = 0; x < m_MapSize; ++x) { u16 min = shoreMax; for (u16 y = 0; y < m_MapSize; ++y) { if (!waterGrid.get(x, y)) { u16 g = shoreGrid.get(x, y); if (g > min) shoreGrid.set(x, y, min); else if (g < min) min = g; ++min; } } for (u16 y = m_MapSize; y > 0; --y) { if (!waterGrid.get(x, y-1)) { u16 g = shoreGrid.get(x, y-1); if (g > min) shoreGrid.set(x, y-1, min); else if (g < min) min = g; ++min; } } } // Apply passability classes to terrain for (u16 j = 0; j < m_MapSize; ++j) { for (u16 i = 0; i < m_MapSize; ++i) { fixed x, z; TileCenter(i, j, x, z); TerrainTile t = 0; u8 obstruct = m_ObstructionGrid->get(i, j); fixed height = terrain.GetExactGroundLevelFixed(x, z); fixed water; if (cmpWaterManager) water = cmpWaterManager->GetWaterLevel(x, z); fixed depth = water - height; fixed slope = terrain.GetSlopeFixed(i, j); fixed shoredist = fixed::FromInt(shoreGrid.get(i, j)); if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING) t |= 1; if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION) t |= 2; if (obstruct & ICmpObstructionManager::TILE_OUTOFBOUNDS) { // If out of bounds, nobody is allowed to pass for (size_t n = 0; n < m_PassClasses.size(); ++n) t |= m_PassClasses[n].m_Mask; } else { for (size_t n = 0; n < m_PassClasses.size(); ++n) { if (!m_PassClasses[n].IsPassable(depth, slope, shoredist)) t |= m_PassClasses[n].m_Mask; } } std::string moveClass = terrain.GetMovementClass(i, j); if (m_TerrainCostClassTags.find(moveClass) != m_TerrainCostClassTags.end()) t |= COST_CLASS_MASK(m_TerrainCostClassTags[moveClass]); m_Grid->set(i, j, t); } } m_TerrainDirty = false; ++m_Grid->m_DirtyID; } }
void ResolveFoundationCollisions() { if (m_Type == UNIT) return; CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return; CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); if (!cmpPosition) return; // error if (!cmpPosition->IsInWorld()) return; // no obstruction CFixedVector2D pos = cmpPosition->GetPosition2D(); // Ignore collisions within the same control group, or with other non-foundation-blocking shapes. // Note that, since the control group for each entity defaults to the entity's ID, this is typically // equivalent to only ignoring the entity's own shape and other non-foundation-blocking shapes. SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION); std::vector<entity_id_t> collisions; if (cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &collisions)) { std::vector<entity_id_t> persistentEnts, normalEnts; if (m_ControlPersist) persistentEnts.push_back(m_ControlGroup); else normalEnts.push_back(GetEntityId()); for (std::vector<entity_id_t>::iterator it = collisions.begin(); it != collisions.end(); ++it) { entity_id_t ent = *it; if (ent == INVALID_ENTITY) continue; CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), ent); if (!cmpObstruction->IsControlPersistent()) normalEnts.push_back(ent); else persistentEnts.push_back(cmpObstruction->GetControlGroup()); } // The collision can't be resolved without usable persistent control groups. if (persistentEnts.empty()) return; // Attempt to replace colliding entities' control groups with a persistent one. for (std::vector<entity_id_t>::iterator it = normalEnts.begin(); it != normalEnts.end(); ++it) { entity_id_t ent = *it; CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), ent); for (std::vector<entity_id_t>::iterator it = persistentEnts.begin(); it != persistentEnts.end(); ++it) { entity_id_t persistent = *it; entity_id_t group = cmpObstruction->GetControlGroup(); // Only clobber 'default' control groups. if (group == ent) cmpObstruction->SetControlGroup(persistent); else if (cmpObstruction->GetControlGroup2() == INVALID_ENTITY && group != persistent) cmpObstruction->SetControlGroup2(persistent); } } } }