LCOV - code coverage report
Current view: top level - source/simulation2/components - CCmpRallyPointRenderer.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 10 488 2.0 %
Date: 2023-01-19 00:18:29 Functions: 4 31 12.9 %

          Line data    Source code
       1             : /* Copyright (C) 2022 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             : #include "CCmpRallyPointRenderer.h"
      20             : 
      21             : #include "ps/Profile.h"
      22             : #include "simulation2/components/ICmpRangeManager.h"
      23             : #include "simulation2/helpers/Los.h"
      24             : 
      25         116 : std::string CCmpRallyPointRenderer::GetSchema()
      26             : {
      27             :     return
      28             :         "<a:help>Displays a rally point marker where created units will gather when spawned</a:help>"
      29             :         "<a:example>"
      30             :             "<MarkerTemplate>special/rallypoint</MarkerTemplate>"
      31             :             "<LineThickness>0.75</LineThickness>"
      32             :             "<LineStartCap>round</LineStartCap>"
      33             :             "<LineEndCap>square</LineEndCap>"
      34             :             "<LineDashColor r='158' g='11' b='15'></LineDashColor>"
      35             :             "<LinePassabilityClass>default</LinePassabilityClass>"
      36             :         "</a:example>"
      37             :         "<element name='MarkerTemplate' a:help='Template name for the rally point marker entity (typically a waypoint flag actor)'>"
      38             :             "<text/>"
      39             :         "</element>"
      40             :         "<element name='LineTexture' a:help='Texture file to use for the rally point line'>"
      41             :             "<text />"
      42             :         "</element>"
      43             :         "<element name='LineTextureMask' a:help='Texture mask to indicate where overlay colors are to be applied (see LineColor and LineDashColor)'>"
      44             :             "<text />"
      45             :         "</element>"
      46             :         "<element name='LineThickness' a:help='Thickness of the marker line connecting the entity to the rally point marker'>"
      47             :             "<data type='decimal'/>"
      48             :         "</element>"
      49             :         "<element name='LineDashColor'>"
      50             :             "<attribute name='r'>"
      51             :                 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
      52             :             "</attribute>"
      53             :             "<attribute name='g'>"
      54             :                 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
      55             :             "</attribute>"
      56             :             "<attribute name='b'>"
      57             :                 "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
      58             :             "</attribute>"
      59             :         "</element>"
      60             :         "<element name='LineStartCap'>"
      61             :             "<choice>"
      62             :                 "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>"
      63             :                 "<value a:help='Semi-circular line end cap'>round</value>"
      64             :                 "<value a:help='Sharp, pointy line end cap'>sharp</value>"
      65             :                 "<value a:help='Square line end cap'>square</value>"
      66             :             "</choice>"
      67             :         "</element>"
      68             :         "<element name='LineEndCap'>"
      69             :             "<choice>"
      70             :                 "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>"
      71             :                 "<value a:help='Semi-circular line end cap'>round</value>"
      72             :                 "<value a:help='Sharp, pointy line end cap'>sharp</value>"
      73             :                 "<value a:help='Square line end cap'>square</value>"
      74             :             "</choice>"
      75             :         "</element>"
      76             :         "<element name='LinePassabilityClass' a:help='The pathfinder passability class to use for computing the rally point marker line path'>"
      77             :             "<text />"
      78         116 :         "</element>";
      79             : }
      80             : 
      81           0 : void CCmpRallyPointRenderer::Init(const CParamNode& paramNode)
      82             : {
      83           0 :     m_Displayed = false;
      84           0 :     m_SmoothPath = true;
      85           0 :     m_LastOwner = INVALID_PLAYER;
      86           0 :     m_LastMarkerCount = 0;
      87           0 :     m_EnableDebugNodeOverlay = false;
      88             : 
      89           0 :     UpdateLineColor();
      90             :     // ---------------------------------------------------------------------------------------------
      91             :     // Load some XML configuration data (schema guarantees that all these nodes are valid)
      92             : 
      93           0 :     m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToWString();
      94           0 :     const CParamNode& lineDashColor = paramNode.GetChild("LineDashColor");
      95           0 :     m_LineDashColor = CColor(
      96           0 :         lineDashColor.GetChild("@r").ToInt()/255.f,
      97           0 :         lineDashColor.GetChild("@g").ToInt()/255.f,
      98           0 :         lineDashColor.GetChild("@b").ToInt()/255.f,
      99             :         1.f
     100             :     );
     101             : 
     102           0 :     m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat();
     103           0 :     m_LineTexturePath = paramNode.GetChild("LineTexture").ToWString();
     104           0 :     m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToWString();
     105           0 :     m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToWString());
     106           0 :     m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToWString());
     107           0 :     m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToString();
     108             : 
     109             :     // ---------------------------------------------------------------------------------------------
     110             :     // Load some textures
     111             : 
     112           0 :     if (CRenderer::IsInitialised())
     113             :     {
     114           0 :         CTextureProperties texturePropsBase(m_LineTexturePath);
     115           0 :         texturePropsBase.SetAddressMode(
     116             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
     117             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     118           0 :         texturePropsBase.SetAnisotropicFilter(true);
     119           0 :         m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
     120             : 
     121           0 :         CTextureProperties texturePropsMask(m_LineTextureMaskPath);
     122           0 :         texturePropsMask.SetAddressMode(
     123             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
     124             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     125           0 :         texturePropsMask.SetAnisotropicFilter(true);
     126           0 :         m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
     127             :     }
     128           0 : }
     129             : 
     130         116 : void CCmpRallyPointRenderer::ClassInit(CComponentManager& componentManager)
     131             : {
     132         116 :     componentManager.SubscribeGloballyToMessageType(MT_PlayerColorChanged);
     133         116 :     componentManager.SubscribeToMessageType(MT_OwnershipChanged);
     134         116 :     componentManager.SubscribeToMessageType(MT_TurnStart);
     135         116 :     componentManager.SubscribeToMessageType(MT_Destroy);
     136         116 :     componentManager.SubscribeToMessageType(MT_PositionChanged);
     137         116 : }
     138             : 
     139           0 : void CCmpRallyPointRenderer::Deinit()
     140             : {
     141           0 : }
     142             : 
     143           0 : void CCmpRallyPointRenderer::Serialize(ISerializer& UNUSED(serialize))
     144             : {
     145             :     // Do NOT serialize anything; this is a rendering-only component, it does not and should not affect simulation state
     146           0 : }
     147             : 
     148           0 : void CCmpRallyPointRenderer::Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
     149             : {
     150           0 :     Init(paramNode);
     151             :     // The dependent components have not been deserialized, so the color is loaded on first SetDisplayed
     152           0 : }
     153             : 
     154           0 : void CCmpRallyPointRenderer::HandleMessage(const CMessage& msg, bool UNUSED(global))
     155             : {
     156           0 :     switch (msg.GetType())
     157             :     {
     158           0 :     case MT_PlayerColorChanged:
     159             :     {
     160           0 :         const CMessagePlayerColorChanged& msgData = static_cast<const CMessagePlayerColorChanged&> (msg);
     161             : 
     162           0 :         CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
     163           0 :         if (!cmpOwnership || msgData.player != cmpOwnership->GetOwner())
     164           0 :             break;
     165             : 
     166           0 :         UpdateLineColor();
     167           0 :         ConstructAllOverlayLines();
     168             :     }
     169           0 :     break;
     170           0 :     case MT_RenderSubmit:
     171             :     {
     172           0 :         PROFILE("RallyPoint::RenderSubmit");
     173           0 :         if (m_Displayed && IsSet())
     174             :         {
     175           0 :             const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
     176           0 :             RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
     177           0 :         }
     178             :     }
     179           0 :     break;
     180           0 :     case MT_OwnershipChanged:
     181             :     {
     182           0 :         const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
     183             : 
     184             :         // Ignore destroyed entities
     185           0 :         if (msgData.to == INVALID_PLAYER)
     186           0 :             break;
     187           0 :         Reset();
     188             :         // Required for both the initial and capturing players color
     189           0 :         UpdateLineColor();
     190             : 
     191             :         // Support capturing, even though RallyPoint is typically deleted then
     192           0 :         UpdateMarkers();
     193           0 :         ConstructAllOverlayLines();
     194             :     }
     195           0 :     break;
     196           0 :     case MT_TurnStart:
     197             :     {
     198           0 :         UpdateOverlayLines(); // Check for changes to the SoD and update the overlay lines accordingly
     199             :     }
     200           0 :     break;
     201           0 :     case MT_Destroy:
     202             :     {
     203           0 :         Reset();
     204             :     }
     205           0 :     break;
     206           0 :     case MT_PositionChanged:
     207             :     {
     208             :         // Unlikely to happen in-game, but can occur in atlas
     209             :         // Just recompute the path from the entity to the first rally point
     210           0 :         RecomputeRallyPointPath_wrapper(0);
     211             :     }
     212           0 :     break;
     213             :     }
     214           0 : }
     215             : 
     216           0 : void CCmpRallyPointRenderer::UpdateMessageSubscriptions()
     217             : {
     218           0 :     GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, m_Displayed && IsSet());
     219           0 : }
     220             : 
     221           0 : void CCmpRallyPointRenderer::UpdateMarkers()
     222             : {
     223           0 :     player_id_t previousOwner = m_LastOwner;
     224           0 :     for (size_t i = 0; i < m_RallyPoints.size(); ++i)
     225             :     {
     226           0 :         if (i >= m_MarkerEntityIds.size())
     227           0 :             m_MarkerEntityIds.push_back(INVALID_ENTITY);
     228             : 
     229           0 :         if (m_MarkerEntityIds[i] == INVALID_ENTITY)
     230             :         {
     231             :             // No marker exists yet, create one first
     232           0 :             CComponentManager& componentMgr = GetSimContext().GetComponentManager();
     233             : 
     234             :             // Allocate a new entity for the marker
     235           0 :             if (!m_MarkerTemplate.empty())
     236             :             {
     237           0 :                 m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity();
     238           0 :                 if (m_MarkerEntityIds[i] != INVALID_ENTITY)
     239           0 :                     m_MarkerEntityIds[i] = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityIds[i]);
     240             :             }
     241             :         }
     242             : 
     243             :         // The marker entity should be valid at this point, otherwise something went wrong trying to allocate it
     244           0 :         if (m_MarkerEntityIds[i] == INVALID_ENTITY)
     245           0 :             LOGERROR("Failed to create rally point marker entity");
     246             : 
     247           0 :         CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntityIds[i]);
     248           0 :         if (markerCmpPosition)
     249             :         {
     250           0 :             if (m_Displayed && IsSet())
     251             :             {
     252           0 :                 markerCmpPosition->MoveTo(m_RallyPoints[i].X, m_RallyPoints[i].Y);
     253             :             }
     254             :             else
     255             :             {
     256           0 :                 markerCmpPosition->MoveOutOfWorld();
     257             :             }
     258             :         }
     259             : 
     260             :         // Set rally point flag selection based on player civilization
     261           0 :         CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
     262           0 :         if (!cmpOwnership)
     263           0 :             continue;
     264             : 
     265           0 :         player_id_t ownerId = cmpOwnership->GetOwner();
     266           0 :         if (ownerId == INVALID_PLAYER || (ownerId == previousOwner && m_LastMarkerCount >= i))
     267           0 :             continue;
     268             : 
     269           0 :         m_LastOwner = ownerId;
     270           0 :         CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
     271             :         // cmpPlayerManager should not be null as long as this method is called on-demand instead of at Init() time
     272             :         // (we can't rely on component initialization order in Init())
     273           0 :         if (!cmpPlayerManager)
     274           0 :             continue;
     275             : 
     276           0 :         CmpPtr<ICmpIdentity> cmpIdentity(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId));
     277           0 :         if (!cmpIdentity)
     278           0 :             continue;
     279             : 
     280           0 :         CmpPtr<ICmpVisual> cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]);
     281           0 :         if (cmpVisualActor)
     282           0 :             cmpVisualActor->SetVariant("civ", CStrW(cmpIdentity->GetCiv()).ToUTF8());
     283             :     }
     284           0 :     m_LastMarkerCount = m_RallyPoints.size() - 1;
     285           0 : }
     286             : 
     287           0 : void CCmpRallyPointRenderer::UpdateLineColor()
     288             : {
     289           0 :     CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
     290           0 :     if (!cmpOwnership)
     291           0 :         return;
     292             : 
     293           0 :     player_id_t owner = cmpOwnership->GetOwner();
     294           0 :     if (owner == INVALID_PLAYER)
     295           0 :         return;
     296             : 
     297           0 :     CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
     298           0 :     if (!cmpPlayerManager)
     299           0 :         return;
     300             : 
     301           0 :     CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(owner));
     302           0 :     if (!cmpPlayer)
     303           0 :         return;
     304             : 
     305           0 :     m_LineColor = cmpPlayer->GetDisplayedColor();
     306             : }
     307             : 
     308           0 : void CCmpRallyPointRenderer::RecomputeAllRallyPointPaths()
     309             : {
     310           0 :     m_Path.clear();
     311           0 :     m_VisibilitySegments.clear();
     312           0 :     m_TexturedOverlayLines.clear();
     313             : 
     314           0 :     if (m_EnableDebugNodeOverlay)
     315           0 :         m_DebugNodeOverlays.clear();
     316             : 
     317             :     // No use computing a path if the rally point isn't set
     318           0 :     if (!IsSet())
     319           0 :         return;
     320             : 
     321           0 :     CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     322             :     // No point going on if this entity doesn't have a position or is outside of the world
     323           0 :     if (!cmpPosition || !cmpPosition->IsInWorld())
     324           0 :         return;
     325             : 
     326           0 :     CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
     327           0 :     CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
     328             : 
     329           0 :     for (size_t i = 0; i < m_RallyPoints.size(); ++i)
     330             :     {
     331           0 :         RecomputeRallyPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder);
     332             :     }
     333             : }
     334             : 
     335           0 : void CCmpRallyPointRenderer::RecomputeRallyPointPath_wrapper(size_t index)
     336             : {
     337             :     // No use computing a path if the rally point isn't set
     338           0 :     if (!IsSet())
     339           0 :         return;
     340             : 
     341             :     // No point going on if this entity doesn't have a position or is outside of the world
     342           0 :     CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     343           0 :     if (!cmpPosition || !cmpPosition->IsInWorld())
     344           0 :         return;
     345             : 
     346           0 :     CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
     347           0 :     CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
     348             : 
     349           0 :     RecomputeRallyPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder);
     350             : }
     351             : 
     352           0 : void CCmpRallyPointRenderer::RecomputeRallyPointPath(size_t index, CmpPtr<ICmpPosition>& cmpPosition, CmpPtr<ICmpFootprint>& cmpFootprint, CmpPtr<ICmpPathfinder> cmpPathfinder)
     353             : {
     354           0 :     while (index >= m_Path.size())
     355             :     {
     356           0 :         std::vector<CVector2D> tmp;
     357           0 :         m_Path.push_back(tmp);
     358             :     }
     359           0 :     m_Path[index].clear();
     360             : 
     361           0 :     while (index >= m_VisibilitySegments.size())
     362             :     {
     363           0 :         std::vector<SVisibilitySegment> tmp;
     364           0 :         m_VisibilitySegments.push_back(tmp);
     365             :     }
     366           0 :     m_VisibilitySegments[index].clear();
     367             : 
     368             :     // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a
     369             :     // list of waypoints (i.e. a Path) from the goal to the foundation/previous rally point, where each
     370             :     // waypoint is centered at a tile. We'll have to do some post-processing on the path to get it smooth.
     371           0 :     WaypointPath path;
     372           0 :     std::vector<Waypoint>& waypoints = path.m_Waypoints;
     373             : 
     374           0 :     CFixedVector2D start(cmpPosition->GetPosition2D());
     375           0 :     PathGoal goal = { PathGoal::POINT, m_RallyPoints[index].X, m_RallyPoints[index].Y };
     376             : 
     377           0 :     if (index == 0)
     378           0 :         GetClosestsEdgePointFrom(start,m_RallyPoints[index], cmpPosition, cmpFootprint);
     379             :     else
     380             :     {
     381           0 :         start.X = m_RallyPoints[index-1].X;
     382           0 :         start.Y = m_RallyPoints[index-1].Y;
     383             :     }
     384           0 :     cmpPathfinder->ComputePathImmediate(start.X, start.Y, goal, cmpPathfinder->GetPassabilityClass(m_LinePassabilityClass), path);
     385             : 
     386             :     // Check if we got a path back; if not we probably have two markers less than one tile apart.
     387           0 :     if (path.m_Waypoints.size() < 2)
     388             :     {
     389           0 :         m_Path[index].emplace_back(start.X.ToFloat(), start.Y.ToFloat());
     390           0 :         m_Path[index].emplace_back(m_RallyPoints[index].X.ToFloat(), m_RallyPoints[index].Y.ToFloat());
     391           0 :         return;
     392             :     }
     393           0 :     else if (index == 0)
     394             :     {
     395             :         // Sometimes this ends up not being optimal if you asked for a long path, so improve.
     396           0 :         CFixedVector2D newend(waypoints[waypoints.size()-2].x,waypoints[waypoints.size()-2].z);
     397           0 :         GetClosestsEdgePointFrom(newend,newend, cmpPosition, cmpFootprint);
     398           0 :         waypoints.back().x = newend.X;
     399           0 :         waypoints.back().z = newend.Y;
     400             :     }
     401             :     else
     402             :     {
     403             :         // Make sure we actually start at the rallypoint because the pathfinder moves us to a usable tile.
     404           0 :         waypoints.back().x = m_RallyPoints[index-1].X;
     405           0 :         waypoints.back().z = m_RallyPoints[index-1].Y;
     406             :     }
     407             : 
     408             :     // Pathfinder makes us go to the nearest passable cell which isn't always what we want
     409           0 :     waypoints[0].x = m_RallyPoints[index].X;
     410           0 :     waypoints[0].z = m_RallyPoints[index].Y;
     411             : 
     412             :     // From here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth
     413             :     // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot.
     414             :     // Since we'll only be further using these points for rendering purposes, using floats should be fine.
     415             : 
     416           0 :     for (Waypoint& waypoint : waypoints)
     417           0 :         m_Path[index].emplace_back(waypoint.x.ToFloat(), waypoint.z.ToFloat());
     418             : 
     419             :     // Post-processing
     420             : 
     421             :     // Linearize the path;
     422             :     // Pass through the waypoints, averaging each waypoint with its next one except the last one. Because the path
     423             :     // goes from the marker to this entity/the previous flag and we want to keep the point at the marker's exact position,
     424             :     // loop backwards through the waypoints so that the marker waypoint is maintained.
     425             :     // TODO: see if we can do this at the same time as the waypoint -> coord conversion above
     426           0 :     for(size_t i = m_Path[index].size() - 2; i > 0; --i)
     427           0 :         m_Path[index][i] = (m_Path[index][i] + m_Path[index][i-1]) / 2.0f;
     428             : 
     429             :     // Eliminate some consecutive waypoints that are visible from eachother. Reduce across a maximum distance of approx. 6 tiles
     430             :     // (prevents segments that are too long to properly stick to the terrain)
     431           0 :     ReduceSegmentsByVisibility(m_Path[index], 6);
     432             : 
     433             :     // Debug overlays
     434           0 :     if (m_EnableDebugNodeOverlay)
     435             :     {
     436           0 :         while (index >= m_DebugNodeOverlays.size())
     437           0 :             m_DebugNodeOverlays.emplace_back();
     438             : 
     439           0 :         m_DebugNodeOverlays[index].clear();
     440             :     }
     441           0 :     if (m_EnableDebugNodeOverlay && m_SmoothPath)
     442             :     {
     443             :         // Create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the
     444             :         // terrain so we can still see them after the interpolated points are added)
     445           0 :         for (const CVector2D& point : m_Path[index])
     446             :         {
     447           0 :             SOverlayLine overlayLine;
     448           0 :             overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f);
     449           0 :             overlayLine.m_Thickness = 0.1f;
     450           0 :             SimRender::ConstructSquareOnGround(GetSimContext(), point.X, point.Y, 0.2f, 0.2f, 1.0f, overlayLine, true);
     451           0 :             m_DebugNodeOverlays[index].push_back(overlayLine);
     452             :         }
     453             :     }
     454             : 
     455           0 :     if (m_SmoothPath)
     456             :         // The number of points to interpolate goes hand in hand with the maximum amount of node links allowed to be joined together
     457             :         // by the visibility reduction. The more node links that can be joined together, the more interpolated points you need to
     458             :         // generate to be able to deal with local terrain height changes.
     459             :         // no offset, keep line at its exact path
     460           0 :         SimRender::InterpolatePointsRNS(m_Path[index], false, 0, 4);
     461             : 
     462             :     // Find which point is the last visible point before going into the SoD, so we have a point to compare to on the next turn
     463           0 :     GetVisibilitySegments(m_VisibilitySegments[index], index);
     464             : 
     465             :     // Build overlay lines for the new path
     466           0 :     ConstructOverlayLines(index);
     467             : }
     468             : 
     469           0 : void CCmpRallyPointRenderer::ConstructAllOverlayLines()
     470             : {
     471           0 :     m_TexturedOverlayLines.clear();
     472             : 
     473           0 :     for (size_t i = 0; i < m_Path.size(); ++i)
     474           0 :         ConstructOverlayLines(i);
     475           0 : }
     476             : 
     477           0 : void CCmpRallyPointRenderer::ConstructOverlayLines(size_t index)
     478             : {
     479             :     // We need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the
     480             :     // renderer, because it does some fancy vertex buffering thing and caches them internally instead of recomputing them on every
     481             :     // pass (which is only sensible).
     482           0 :     while (index >= m_TexturedOverlayLines.size())
     483             :     {
     484           0 :         std::vector<SOverlayTexturedLine> tmp;
     485           0 :         m_TexturedOverlayLines.push_back(tmp);
     486             :     }
     487           0 :     m_TexturedOverlayLines[index].clear();
     488             : 
     489           0 :     if (m_Path[index].size() < 2)
     490           0 :         return;
     491             : 
     492           0 :     SOverlayTexturedLine::LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it)
     493             : 
     494             : 
     495           0 :     for(const SVisibilitySegment& segment : m_VisibilitySegments[index])
     496             :     {
     497           0 :         if (segment.m_Visible)
     498             :         {
     499             :             // Does this segment border on the building or rally point flag on either side?
     500           0 :             bool bordersBuilding = (segment.m_EndIndex == m_Path[index].size() - 1);
     501           0 :             bool bordersFlag = (segment.m_StartIndex == 0);
     502             : 
     503             :             // Construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx
     504           0 :             SOverlayTexturedLine overlayLine;
     505           0 :             overlayLine.m_Thickness = m_LineThickness;
     506           0 :             overlayLine.m_SimContext = &GetSimContext();
     507           0 :             overlayLine.m_TextureBase = m_Texture;
     508           0 :             overlayLine.m_TextureMask = m_TextureMask;
     509           0 :             overlayLine.m_Color = m_LineColor;
     510           0 :             overlayLine.m_Closed = false;
     511             :             // We should take care to only use m_LineXCap for the actual end points at the building and the rally point; any intermediate
     512             :             // end points (i.e., that border a dashed segment) should have the dashed cap
     513             :             // the path line is actually in reverse order as well, so let's swap out the start and end caps
     514           0 :             overlayLine.m_StartCapType = (bordersFlag ? m_LineEndCapType : dashesLineCapType);
     515           0 :             overlayLine.m_EndCapType = (bordersBuilding ? m_LineStartCapType : dashesLineCapType);
     516           0 :             overlayLine.m_AlwaysVisible = true;
     517             : 
     518             :             // Push overlay line coordinates
     519           0 :             ENSURE(segment.m_EndIndex > segment.m_StartIndex);
     520             :             // End index is inclusive here
     521           0 :             for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j)
     522           0 :                 overlayLine.m_Coords.push_back(m_Path[index][j]);
     523             : 
     524           0 :             m_TexturedOverlayLines[index].push_back(overlayLine);
     525             :         }
     526             :         else
     527             :         {
     528             :             // Construct dashed line from startPointIdx to endPointIdx; add textured overlay lines for it to the render list
     529           0 :             std::vector<CVector2D> straightLine;
     530           0 :             straightLine.push_back(m_Path[index][segment.m_StartIndex]);
     531           0 :             straightLine.push_back(m_Path[index][segment.m_EndIndex]);
     532             : 
     533             :             // We always want to the dashed line to end at either point with a full dash (i.e. not a cleared space), so that the dashed
     534             :             // area is visually obvious. This requires some calculations to see what size we should make the dashes and clears for them
     535             :             // to fit exactly.
     536             : 
     537           0 :             float maxDashSize = 3.f;
     538           0 :             float maxClearSize = 3.f;
     539             : 
     540           0 :             float dashSize = maxDashSize;
     541           0 :             float clearSize = maxClearSize;
     542             :             // Ratio of the dash's length to a (dash + clear) pair's length
     543           0 :             float pairDashRatio = dashSize / (dashSize + clearSize);
     544             : 
     545             :             // Straight-line distance between the points
     546           0 :             float distance = (m_Path[index][segment.m_StartIndex] - m_Path[index][segment.m_EndIndex]).Length();
     547             : 
     548             :             // See how many pairs (dash + clear) of unmodified size can fit into the distance. Then check the remaining distance; if it's not exactly
     549             :             // a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is.
     550           0 :             int numFitUnmodified = floor(distance/(dashSize + clearSize));
     551           0 :             float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize));
     552             : 
     553             :             // Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly.
     554             :             // We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make
     555             :             // it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified
     556             :             // (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's
     557             :             // length and the clear's length.
     558             : 
     559             :             // We always want to have at least one dash/clear pair (i.e., "|===|   |===|"); also, we need to avoid division by zero below.
     560           0 :             numFitUnmodified = std::max(1, numFitUnmodified);
     561             : 
     562             :             // Can be either positive or negative
     563           0 :             float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified;
     564           0 :             dashSize += pairDashRatio * pairwiseLengthDifference;
     565           0 :             clearSize += (1 - pairDashRatio) * pairwiseLengthDifference;
     566             : 
     567             :             // ------------------------------------------------------------------------------------------------
     568             : 
     569           0 :             SDashedLine dashedLine;
     570           0 :             SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize);
     571             : 
     572             :             // Build overlay lines for dashes
     573           0 :             size_t numDashes = dashedLine.m_StartIndices.size();
     574           0 :             for (size_t i=0; i < numDashes; i++)
     575             :             {
     576           0 :                 SOverlayTexturedLine dashOverlay;
     577             : 
     578           0 :                 dashOverlay.m_Thickness = m_LineThickness;
     579           0 :                 dashOverlay.m_SimContext = &GetSimContext();
     580           0 :                 dashOverlay.m_TextureBase = m_Texture;
     581           0 :                 dashOverlay.m_TextureMask = m_TextureMask;
     582           0 :                 dashOverlay.m_Color = m_LineDashColor;
     583           0 :                 dashOverlay.m_Closed = false;
     584           0 :                 dashOverlay.m_StartCapType = dashesLineCapType;
     585           0 :                 dashOverlay.m_EndCapType = dashesLineCapType;
     586           0 :                 dashOverlay.m_AlwaysVisible = true;
     587             :                 // TODO: maybe adjust the elevation of the dashes to be a little lower, so that it slides underneath the actual path
     588             : 
     589           0 :                 size_t dashStartIndex = dashedLine.m_StartIndices[i];
     590           0 :                 size_t dashEndIndex = dashedLine.GetEndIndex(i);
     591           0 :                 ENSURE(dashEndIndex > dashStartIndex);
     592             : 
     593           0 :                 for (size_t j = dashStartIndex; j < dashEndIndex; ++j)
     594           0 :                     dashOverlay.m_Coords.push_back(dashedLine.m_Points[j]);
     595             : 
     596           0 :                 m_TexturedOverlayLines[index].push_back(dashOverlay);
     597             :             }
     598             : 
     599             :         }
     600             :     }
     601             : 
     602             :     //// <DEBUG> //////////////////////////////////////////////
     603           0 :     if (m_EnableDebugNodeOverlay)
     604             :     {
     605           0 :         while (index >= m_DebugNodeOverlays.size())
     606             :         {
     607           0 :             std::vector<SOverlayLine> tmp;
     608           0 :             m_DebugNodeOverlays.push_back(tmp);
     609             :         }
     610           0 :         for (size_t j = 0; j < m_Path[index].size(); ++j)
     611             :         {
     612           0 :             SOverlayLine overlayLine;
     613           0 :             overlayLine.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     614           0 :             overlayLine.m_Thickness = 1;
     615           0 :             SimRender::ConstructCircleOnGround(GetSimContext(), m_Path[index][j].X, m_Path[index][j].Y, 0.075f, overlayLine, true);
     616           0 :             m_DebugNodeOverlays[index].push_back(overlayLine);
     617             :         }
     618             :     }
     619             :     //// </DEBUG> //////////////////////////////////////////////
     620             : }
     621             : 
     622           0 : void CCmpRallyPointRenderer::UpdateOverlayLines()
     623             : {
     624             :     // We should only do this if the rally point is currently being displayed and set inside the world, otherwise it's a massive
     625             :     // waste of time to calculate all this stuff (this method is called every turn)
     626           0 :     if (!m_Displayed || !IsSet())
     627           0 :         return;
     628             : 
     629             :     // See if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones
     630           0 :     std::vector<std::vector<SVisibilitySegment> > newVisibilitySegments;
     631           0 :     for (size_t i = 0; i < m_Path.size(); ++i)
     632             :     {
     633           0 :         std::vector<SVisibilitySegment> tmp;
     634           0 :         newVisibilitySegments.push_back(tmp);
     635           0 :         GetVisibilitySegments(newVisibilitySegments[i], i);
     636             :     }
     637             : 
     638             :     // Check if the full path changed, then reconstruct all overlay lines, otherwise check if a segment changed and update that.
     639           0 :     if (m_VisibilitySegments.size() != newVisibilitySegments.size())
     640             :     {
     641             :         // Save the new visibility segments to compare against next time
     642           0 :         m_VisibilitySegments = newVisibilitySegments;
     643           0 :         ConstructAllOverlayLines();
     644             :     }
     645             :     else
     646             :     {
     647           0 :         for (size_t i = 0; i < m_VisibilitySegments.size(); ++i)
     648             :         {
     649           0 :             if (m_VisibilitySegments[i] != newVisibilitySegments[i])
     650             :             {
     651             :                 // The visibility segments have changed, reconstruct the overlay lines to match. NOTE: The path itself doesn't
     652             :                 // change, only the overlay lines we construct from it.
     653             :                 // Save the new visibility segments to compare against next time
     654           0 :                 m_VisibilitySegments[i] = newVisibilitySegments[i];
     655           0 :                 ConstructOverlayLines(i);
     656             :             }
     657             :         }
     658             :     }
     659             : }
     660             : 
     661           0 : void CCmpRallyPointRenderer::GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint) const
     662             : {
     663           0 :     ENSURE(cmpPosition);
     664           0 :     ENSURE(cmpFootprint);
     665             : 
     666             :     // Grab the shape and dimensions of the footprint
     667           0 :     entity_pos_t footprintSize0, footprintSize1, footprintHeight;
     668             :     ICmpFootprint::EShape footprintShape;
     669           0 :     cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
     670             : 
     671             :     // Grab the center of the footprint
     672           0 :     CFixedVector2D center = cmpPosition->GetPosition2D();
     673             : 
     674           0 :     switch (footprintShape)
     675             :     {
     676           0 :         case ICmpFootprint::SQUARE:
     677             :         {
     678             :             // In this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
     679             :             // The building's footprint could be rotated any which way, so let's get the rotation around the Y axis
     680             :             // and the rotated unit vectors in the X/Z plane of the shape's footprint
     681             :             // (the Footprint itself holds only the outline, the Position holds the orientation)
     682             : 
     683             :             // Sinus and cosinus of the Y axis rotation angle (aka the yaw)
     684           0 :             fixed s, c;
     685           0 :             fixed a = cmpPosition->GetRotation().Y;
     686           0 :             sincos_approx(a, s, c);
     687             :             // Unit vector along the rotated X axis
     688           0 :             CFixedVector2D u(c, -s);
     689             :             // Unit vector along the rotated Z axis
     690           0 :             CFixedVector2D v(s, c);
     691           0 :             CFixedVector2D halfSize(footprintSize0 / 2, footprintSize1 / 2);
     692             : 
     693           0 :             CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(start - center, u, v, halfSize);
     694           0 :             result = center + footprintEdgePoint;
     695           0 :             break;
     696             :         }
     697           0 :         case ICmpFootprint::CIRCLE:
     698             :         {
     699             :             // In this case, both footprintSize0 and 1 indicate the circle's radius
     700             :             // Transform target to the point nearest on the edge.
     701           0 :             CFixedVector2D centerVec2D(center.X, center.Y);
     702           0 :             CFixedVector2D centerToLast(start - centerVec2D);
     703           0 :             centerToLast.Normalize();
     704           0 :             result = centerVec2D + (centerToLast.Multiply(footprintSize0));
     705           0 :             break;
     706             :         }
     707             :     }
     708           0 : }
     709             : 
     710           0 : void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks, bool floating) const
     711             : {
     712           0 :     CmpPtr<ICmpPathfinder> cmpPathFinder(GetSystemEntity());
     713           0 :     CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     714           0 :     CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
     715           0 :     ENSURE(cmpPathFinder && cmpTerrain && cmpWaterManager);
     716             : 
     717           0 :     if (coords.size() < 3)
     718           0 :         return;
     719             : 
     720             :     // The basic idea is this: starting from a base node, keep checking each individual point along the path to see if there's a visible
     721             :     // line between it and the base point. If so, keep going, otherwise, make the last visible point the new base node and start the same
     722             :     // process from there on until the entire line is checked. The output is the array of base nodes.
     723             : 
     724           0 :     std::vector<CVector2D> newCoords;
     725           0 :     StationaryOnlyObstructionFilter obstructionFilter;
     726           0 :     entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness);
     727           0 :     pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass);
     728             : 
     729             :     // Save the first base node
     730           0 :     newCoords.push_back(coords[0]);
     731             : 
     732           0 :     size_t baseNodeIdx = 0;
     733           0 :     size_t curNodeIdx = 1;
     734             : 
     735             :     float baseNodeY;
     736           0 :     entity_pos_t baseNodeX;
     737           0 :     entity_pos_t baseNodeZ;
     738             : 
     739             :     // Set initial base node coords
     740           0 :     baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
     741           0 :     baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
     742           0 :     baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
     743           0 :     if (floating)
     744           0 :         baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
     745             : 
     746           0 :     while (curNodeIdx < coords.size())
     747             :     {
     748             :         // This needs to be true at all times, otherwise we're checking visibility between a point and itself.
     749           0 :         ENSURE(curNodeIdx > baseNodeIdx);
     750             : 
     751           0 :         entity_pos_t curNodeX = fixed::FromFloat(coords[curNodeIdx].X);
     752           0 :         entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y);
     753           0 :         float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y);
     754           0 :         if (floating)
     755           0 :             curNodeY = std::max(curNodeY, cmpWaterManager->GetExactWaterLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y));
     756             : 
     757             :         // Find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!)
     758           0 :         bool curNodeVisible = cmpPathFinder->CheckMovement(obstructionFilter, baseNodeX, baseNodeZ, curNodeX, curNodeZ, lineRadius, passabilityClass);
     759             : 
     760             :         // Since height differences are ignored by CheckMovement, let's call two points visible from one another only if they're at
     761             :         // roughly the same terrain elevation
     762             :         // TODO: this could probably use some tuning
     763           0 :         curNodeVisible = curNodeVisible && (fabsf(curNodeY - baseNodeY) < 3.f);
     764           0 :         if (maxSegmentLinks > 0)
     765             :             // Max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx)
     766           0 :             curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks);
     767             : 
     768           0 :         if (!curNodeVisible)
     769             :         {
     770             :             // Current node is not visible from the base node, so the previous one was the last visible point from baseNode and should
     771             :             // hence become the new base node for further iterations.
     772             : 
     773             :             // If curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then
     774             :             // we should take care not to stay stuck at the current base node
     775           0 :             if (curNodeIdx > baseNodeIdx + 1)
     776             :             {
     777           0 :                 baseNodeIdx = curNodeIdx - 1;
     778             :             }
     779             :             else
     780             :             {
     781             :                 // curNodeIdx == baseNodeIdx + 1
     782           0 :                 baseNodeIdx = curNodeIdx;
     783             :                 // Move the next candidate node one forward so that we don't test a point against itself in the next iteration
     784           0 :                 ++curNodeIdx;
     785             :             }
     786             : 
     787             :             // Add new base node to output list
     788           0 :             newCoords.push_back(coords[baseNodeIdx]);
     789             : 
     790             :             // Update base node coordinates
     791           0 :             baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
     792           0 :             baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
     793           0 :             baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
     794           0 :             if (floating)
     795           0 :                 baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
     796             :         }
     797             : 
     798           0 :         ++curNodeIdx;
     799             :     }
     800             : 
     801             :     // We always need to add the last point back to the array; if e.g. all the points up to the last one are all visible from the current
     802             :     // base node, then the loop above just ends and no endpoint is ever added to the list.
     803           0 :     ENSURE(curNodeIdx == coords.size());
     804           0 :     newCoords.push_back(coords[coords.size() - 1]);
     805             : 
     806           0 :     coords.swap(newCoords);
     807             : }
     808             : 
     809           0 : void CCmpRallyPointRenderer::GetVisibilitySegments(std::vector<SVisibilitySegment>& out, size_t index) const
     810             : {
     811           0 :     out.clear();
     812             : 
     813           0 :     if (m_Path[index].size() < 2)
     814           0 :         return;
     815             : 
     816           0 :     CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSystemEntity());
     817             : 
     818           0 :     player_id_t currentPlayer = static_cast<player_id_t>(GetSimContext().GetCurrentDisplayedPlayer());
     819           0 :     CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
     820             : 
     821             :     // Go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
     822             :     // a new one at the next point.
     823             : 
     824           0 :     const float cellSize = static_cast<float>(LOS_TILE_SIZE);
     825           0 :     bool lastVisible = losQuerier.IsExplored(
     826           0 :         (fixed::FromFloat(m_Path[index][0].X / cellSize)).ToInt_RoundToNearest(),
     827           0 :         (fixed::FromFloat(m_Path[index][0].Y / cellSize)).ToInt_RoundToNearest()
     828           0 :     );
     829             :     // Starting node index of the current segment
     830           0 :     size_t curSegmentStartIndex = 0;
     831             : 
     832           0 :     for (size_t k = 1; k < m_Path[index].size(); ++k)
     833             :     {
     834             :         // Grab tile indices for this coord
     835           0 :         int i = (fixed::FromFloat(m_Path[index][k].X / cellSize)).ToInt_RoundToNearest();
     836           0 :         int j = (fixed::FromFloat(m_Path[index][k].Y / cellSize)).ToInt_RoundToNearest();
     837             : 
     838           0 :         bool nodeVisible = losQuerier.IsExplored(i, j);
     839           0 :         if (nodeVisible != lastVisible)
     840             :         {
     841             :             // Visibility changed; write out the segment that was just completed and get ready for the new one
     842           0 :             out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1));
     843             : 
     844           0 :             curSegmentStartIndex = k - 1;
     845           0 :             lastVisible = nodeVisible;
     846             :         }
     847             : 
     848             :     }
     849             : 
     850             :     // Terminate the last segment
     851           0 :     out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1));
     852             : 
     853           0 :     MergeVisibilitySegments(out);
     854             : }
     855             : 
     856           0 : void CCmpRallyPointRenderer::MergeVisibilitySegments(std::vector<SVisibilitySegment>& segments)
     857             : {
     858             :     // Scan for single-point segments; if they are inbetween two other segments, delete them and merge the surrounding segments.
     859             :     // If they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't
     860             :     // themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first).
     861             : 
     862             :     // First scan for absorptions of single-point surrounded segments (i.e. excluding edge segments)
     863           0 :     size_t numSegments = segments.size();
     864             : 
     865             :     // WARNING: FOR LOOP TRICKERY AHEAD!
     866           0 :     for (size_t i = 1; i < numSegments - 1;)
     867             :     {
     868           0 :         SVisibilitySegment& segment = segments[i];
     869           0 :         if (segment.IsSinglePoint())
     870             :         {
     871             :             // Since the segments' visibility alternates, the surrounding ones should have the same visibility
     872           0 :             ENSURE(segments[i-1].m_Visible == segments[i+1].m_Visible);
     873             : 
     874             :             // Make previous segment span all the way across to the next
     875           0 :             segments[i-1].m_EndIndex = segments[i+1].m_EndIndex;
     876             :             // Erase this segment
     877           0 :             segments.erase(segments.begin() + i);
     878             :             // And the next (we removed [i], so [i+1] is now at position [i])
     879           0 :             segments.erase(segments.begin() + i);
     880             :             // We removed 2 segments, so update the loop condition
     881           0 :             numSegments -= 2;
     882             :             // In the next iteration, i should still point to the segment right after the one that got expanded, which is now
     883             :             // at position i; so don't increment i here
     884             :         }
     885             :         else
     886             :         {
     887           0 :             ++i;
     888             :         }
     889             :     }
     890             : 
     891           0 :     ENSURE(numSegments == segments.size());
     892             : 
     893             :     // Check to see if the first segment needs to be merged with its neighbour
     894           0 :     if (segments.size() >= 2 && segments[0].IsSinglePoint())
     895             :     {
     896           0 :         int firstSegmentStartIndex = segments.front().m_StartIndex;
     897           0 :         ENSURE(firstSegmentStartIndex == 0);
     898             :         // At this point, the second segment should never be a single-point segment
     899           0 :         ENSURE(!segments[1].IsSinglePoint());
     900             : 
     901           0 :         segments.erase(segments.begin());
     902           0 :         segments.front().m_StartIndex = firstSegmentStartIndex;
     903             :     }
     904             : 
     905             :     // check to see if the last segment needs to be merged with its neighbour
     906           0 :     if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint())
     907             :     {
     908           0 :         int lastSegmentEndIndex = segments.back().m_EndIndex;
     909             :         // At this point, the second-to-last segment should never be a single-point segment
     910           0 :         ENSURE(!segments[segments.size()-2].IsSinglePoint());
     911             : 
     912           0 :         segments.pop_back();
     913           0 :         segments.back().m_EndIndex = lastSegmentEndIndex;
     914             :     }
     915             : 
     916             :     // --------------------------------------------------------------------------------------------------------
     917             :     // At this point, every segment should have at least 2 points
     918           0 :     for (size_t i = 0; i < segments.size(); ++i)
     919             :     {
     920           0 :         ENSURE(!segments[i].IsSinglePoint());
     921           0 :         ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex);
     922             :     }
     923           0 : }
     924             : 
     925           0 : void CCmpRallyPointRenderer::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
     926             : {
     927             :     // We only get here if the rally point is set and should be displayed
     928           0 :     for(std::vector<SOverlayTexturedLine>& row : m_TexturedOverlayLines)
     929           0 :         for (SOverlayTexturedLine& col : row) {
     930           0 :             if (col.m_Coords.empty())
     931           0 :                 continue;
     932           0 :             if (culling && !col.IsVisibleInFrustum(frustum))
     933           0 :                 continue;
     934           0 :             collector.Submit(&col);
     935             :         }
     936             : 
     937           0 :     if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty())
     938             :     {
     939           0 :         for (std::vector<SOverlayLine>& row : m_DebugNodeOverlays)
     940           0 :             for (SOverlayLine& col : row)
     941           0 :                 if (!col.m_Coords.empty())
     942           0 :                     collector.Submit(&col);
     943             :     }
     944           0 : }
     945             : 
     946           0 : void CCmpRallyPointRenderer::AddPosition_wrapper(const CFixedVector2D& pos)
     947             : {
     948           0 :     AddPosition(pos, false);
     949           0 : }
     950             : 
     951           0 : void CCmpRallyPointRenderer::SetPosition(const CFixedVector2D& pos)
     952             : {
     953           0 :     if (!(m_RallyPoints.size() == 1 && m_RallyPoints.front() == pos))
     954             :     {
     955           0 :         m_RallyPoints.clear();
     956           0 :         AddPosition(pos, true);
     957             :         // Don't need to UpdateMessageSubscriptions here since AddPosition already calls it
     958             :     }
     959           0 : }
     960             : 
     961           0 : void CCmpRallyPointRenderer::UpdatePosition(u32 rallyPointId, const CFixedVector2D& pos)
     962             : {
     963           0 :     if (rallyPointId >= m_RallyPoints.size())
     964           0 :         return;
     965             : 
     966           0 :     m_RallyPoints[rallyPointId] = pos;
     967             : 
     968           0 :     UpdateMarkers();
     969             : 
     970             :     // Compute a new path for the current, and if existing the next rally point
     971           0 :     RecomputeRallyPointPath_wrapper(rallyPointId);
     972           0 :     if (rallyPointId + 1 < m_RallyPoints.size())
     973           0 :         RecomputeRallyPointPath_wrapper(rallyPointId + 1);
     974             : }
     975             : 
     976           0 : void CCmpRallyPointRenderer::SetDisplayed(bool displayed)
     977             : {
     978           0 :     if (m_Displayed != displayed)
     979             :     {
     980           0 :         m_Displayed = displayed;
     981             : 
     982             :         // Set color after all dependent components are deserialized
     983           0 :         if (displayed && m_LineColor.r < 0)
     984             :         {
     985           0 :             UpdateLineColor();
     986           0 :             ConstructAllOverlayLines();
     987             :         }
     988             : 
     989             :         // Move the markers out of oblivion and back into the real world, or vice-versa
     990           0 :         UpdateMarkers();
     991             : 
     992             :         // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method
     993             :         // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred
     994             :         // while this rally point was not being displayed.
     995           0 :         UpdateOverlayLines();
     996             : 
     997           0 :         UpdateMessageSubscriptions();
     998             :     }
     999           0 : }
    1000             : 
    1001           0 : void CCmpRallyPointRenderer::Reset()
    1002             : {
    1003           0 :     for (entity_id_t& componentId : m_MarkerEntityIds)
    1004             :     {
    1005           0 :         if (componentId != INVALID_ENTITY)
    1006             :         {
    1007           0 :             GetSimContext().GetComponentManager().DestroyComponentsSoon(componentId);
    1008           0 :             componentId = INVALID_ENTITY;
    1009             :         }
    1010             :     }
    1011           0 :     m_RallyPoints.clear();
    1012           0 :     m_MarkerEntityIds.clear();
    1013           0 :     m_LastOwner = INVALID_PLAYER;
    1014           0 :     m_LastMarkerCount = 0;
    1015           0 :     RecomputeAllRallyPointPaths();
    1016           0 :     UpdateMessageSubscriptions();
    1017           0 : }
    1018             : 
    1019           0 : void CCmpRallyPointRenderer::UpdateColor()
    1020             : {
    1021           0 :     UpdateLineColor();
    1022           0 :     ConstructAllOverlayLines();
    1023           0 : }
    1024             : 
    1025           0 : void CCmpRallyPointRenderer::AddPosition(CFixedVector2D pos, bool recompute)
    1026             : {
    1027           0 :     m_RallyPoints.push_back(pos);
    1028           0 :     UpdateMarkers();
    1029             : 
    1030           0 :     if (recompute)
    1031           0 :         RecomputeAllRallyPointPaths();
    1032             :     else
    1033           0 :         RecomputeRallyPointPath_wrapper(m_RallyPoints.size() - 1);
    1034             : 
    1035           0 :     UpdateMessageSubscriptions();
    1036           0 : }
    1037             : 
    1038           0 : bool CCmpRallyPointRenderer::IsSet() const
    1039             : {
    1040           0 :     return !m_RallyPoints.empty();
    1041           3 : }

Generated by: LCOV version 1.13