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 : }
|