Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CCmpRallyPointRenderer.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2012 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 #include "ICmpRallyPointRenderer.h"
20 
36 
37 #include "ps/CLogger.h"
38 #include "graphics/Overlay.h"
40 #include "renderer/Renderer.h"
41 
43 {
44  bool m_Visible;
45  size_t m_StartIndex;
46  size_t m_EndIndex; // inclusive
47 
48  SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex)
49  : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex)
50  {}
51 
52  bool operator==(const SVisibilitySegment& other) const
53  {
54  return (m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex);
55  }
56 
57  bool operator!=(const SVisibilitySegment& other) const
58  {
59  return !(*this == other);
60  }
61 
63  {
64  return (m_StartIndex == m_EndIndex);
65  }
66 };
67 
69 {
70  // import some types for less verbosity
76 
77 public:
78  static void ClassInit(CComponentManager& componentManager)
79  {
80  componentManager.SubscribeToMessageType(MT_RenderSubmit);
82  componentManager.SubscribeToMessageType(MT_TurnStart);
83  componentManager.SubscribeToMessageType(MT_Destroy);
85  }
86 
87  DEFAULT_COMPONENT_ALLOCATOR(RallyPointRenderer)
88 
89 protected:
90 
91  /// Display position of the rally points. Note that this are merely the display positions; they not necessarily the same as the
92  /// actual positions used in the simulation at any given time. In particular, we need this separate copy to support
93  /// instantaneously rendering the rally point markers/lines when the user sets one in-game (instead of waiting until the
94  /// network-synchronization code sets it on the RallyPoint component, which might take up to half a second).
96  /// Full path to the rally points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
97  std::vector<std::vector<CVector2D> > m_Path;
98  /// Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments.
99  std::deque<std::deque<SVisibilitySegment> > m_VisibilitySegments;
100 
101  bool m_Displayed; ///< Should we render the rally points and the path lines? (set from JS when e.g. the unit is selected/deselected)
102  bool m_SmoothPath; ///< Smooth the path before rendering?
103 
104  std::vector<entity_id_t> m_MarkerEntityIds; ///< Entity IDs of the rally point markers.
106  player_id_t m_LastOwner; ///< Last seen owner of this entity (used to keep track of ownership changes).
107  std::wstring m_MarkerTemplate; ///< Template name of the rally point markers.
108 
109  /// Marker connector line settings (loaded from XML)
115  std::wstring m_LineTexturePath;
116  std::wstring m_LineTextureMaskPath;
117  std::string m_LinePassabilityClass; ///< Pathfinder passability class to use for computing the (long-range) marker line path.
118  std::string m_LineCostClass; ///< Pathfinder cost class to use for computing the (long-range) marker line path.
119 
122 
123  /// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render
124  /// dashes for segments that are inside the SoD.
125  std::vector<std::vector<SOverlayTexturedLine> > m_TexturedOverlayLines;
126 
127  /// Draw little overlay circles to indicate where the exact path points are?
129  std::vector<std::vector<SOverlayLine> > m_DebugNodeOverlays;
130 
131 public:
132 
133  static std::string GetSchema()
134  {
135  return
136  "<a:help>Displays a rally point marker where created units will gather when spawned</a:help>"
137  "<a:example>"
138  "<MarkerTemplate>special/rallypoint</MarkerTemplate>"
139  "<LineThickness>0.75</LineThickness>"
140  "<LineStartCap>round</LineStartCap>"
141  "<LineEndCap>square</LineEndCap>"
142  "<LineColour r='20' g='128' b='240'></LineColour>"
143  "<LineDashColour r='158' g='11' b='15'></LineDashColour>"
144  "<LineCostClass>default</LineCostClass>"
145  "<LinePassabilityClass>default</LinePassabilityClass>"
146  "</a:example>"
147  "<element name='MarkerTemplate' a:help='Template name for the rally point marker entity (typically a waypoint flag actor)'>"
148  "<text/>"
149  "</element>"
150  "<element name='LineTexture' a:help='Texture file to use for the rally point line'>"
151  "<text />"
152  "</element>"
153  "<element name='LineTextureMask' a:help='Texture mask to indicate where overlay colors are to be applied (see LineColour and LineDashColour)'>"
154  "<text />"
155  "</element>"
156  "<element name='LineThickness' a:help='Thickness of the marker line connecting the entity to the rally point marker'>"
157  "<data type='decimal'/>"
158  "</element>"
159  "<element name='LineColour'>"
160  "<attribute name='r'>"
161  "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
162  "</attribute>"
163  "<attribute name='g'>"
164  "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
165  "</attribute>"
166  "<attribute name='b'>"
167  "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
168  "</attribute>"
169  "</element>"
170  "<element name='LineDashColour'>"
171  "<attribute name='r'>"
172  "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
173  "</attribute>"
174  "<attribute name='g'>"
175  "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
176  "</attribute>"
177  "<attribute name='b'>"
178  "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
179  "</attribute>"
180  "</element>"
181  "<element name='LineStartCap'>"
182  "<choice>"
183  "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>"
184  "<value a:help='Semi-circular line end cap'>round</value>"
185  "<value a:help='Sharp, pointy line end cap'>sharp</value>"
186  "<value a:help='Square line end cap'>square</value>"
187  "</choice>"
188  "</element>"
189  "<element name='LineEndCap'>"
190  "<choice>"
191  "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>"
192  "<value a:help='Semi-circular line end cap'>round</value>"
193  "<value a:help='Sharp, pointy line end cap'>sharp</value>"
194  "<value a:help='Square line end cap'>square</value>"
195  "</choice>"
196  "</element>"
197  "<element name='LinePassabilityClass' a:help='The pathfinder passability class to use for computing the rally point marker line path'>"
198  "<text />"
199  "</element>"
200  "<element name='LineCostClass' a:help='The pathfinder cost class to use for computing the rally point marker line path'>"
201  "<text />"
202  "</element>";
203  }
204 
205  virtual void Init(const CParamNode& paramNode);
206 
207  virtual void Deinit()
208  {
209  }
210 
211  virtual void Serialize(ISerializer& UNUSED(serialize))
212  {
213  // do NOT serialize anything; this is a rendering-only component, it does not and should not affect simulation state
214  }
215 
216  virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
217  {
218  Init(paramNode);
219  }
220 
221  virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
222  {
223  switch (msg.GetType())
224  {
225  case MT_RenderSubmit:
226  {
227  if (m_Displayed && IsSet())
228  {
229  const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
230  RenderSubmit(msgData.collector);
231  }
232  }
233  break;
234  case MT_OwnershipChanged:
235  {
236  UpdateMarkers(); // update marker variation to new player's civilization
237  }
238  break;
239  case MT_TurnStart:
240  {
241  UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly
242  }
243  break;
244  case MT_Destroy:
245  {
246  for (std::vector<entity_id_t>::iterator it = m_MarkerEntityIds.begin(); it < m_MarkerEntityIds.end(); ++it)
247  {
248  if (*it != INVALID_ENTITY)
249  {
251  *it = INVALID_ENTITY;
252  }
253  }
254  }
255  break;
256  case MT_PositionChanged:
257  {
258  // Unlikely to happen in-game, but can occur in atlas
259  // Just recompute the path from the entity to the first rally point
261  }
262  break;
263  }
264  }
265 
267  {
268  AddPosition(pos, false);
269  }
270 
271  virtual void SetPosition(CFixedVector2D pos)
272  {
273  if (!(m_RallyPoints.size() == 1 && m_RallyPoints.front() == pos))
274  {
275  m_RallyPoints.clear();
276  AddPosition(pos, true);
277  }
278  }
279 
280  virtual void SetDisplayed(bool displayed)
281  {
282  if (m_Displayed != displayed)
283  {
284  m_Displayed = displayed;
285 
286  // move the markers out of oblivion and back into the real world, or vice-versa
287  UpdateMarkers();
288 
289  // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method
290  // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred
291  // while this rally point was not being displayed.
293  }
294  }
295 
296  virtual void Reset()
297  {
298  m_RallyPoints.clear();
300  }
301 
302 private:
303 
304  /**
305  * Helper function for AddPosition_wrapper and SetPosition.
306  */
307  void AddPosition(CFixedVector2D pos, bool recompute)
308  {
309  m_RallyPoints.push_back(pos);
310  UpdateMarkers();
311 
312  if (recompute)
314  else
316  }
317 
318  /**
319  * Returns true iff at least one display rally point is set; i.e., if we have a point to render our marker/line at.
320  */
321  bool IsSet()
322  {
323  return !m_RallyPoints.empty();
324  }
325 
326  /**
327  * Repositions the rally point markers; moves them outside of the world (ie. hides them), or positions them at the currently
328  * set rally points. Also updates the actor's variation according to the entity's current owning player's civilization.
329  *
330  * Should be called whenever either the position of a rally point changes (including whether it is set or not), or the display
331  * flag changes, or the ownership of the entity changes.
332  */
333  void UpdateMarkers();
334 
335  /**
336  * Recomputes all the full paths from this entity to the rally point and from the rally point to the next, and does all the necessary
337  * post-processing to make them prettier.
338  *
339  * Should be called whenever all rally points' position changes.
340  */
342 
343  /**
344  * Recomputes the full path for m_Path[ @p index], and does all the necessary post-processing to make it prettier.
345  *
346  * Should be called whenever either the starting position or the rally point's position changes.
347  */
348  void RecomputeRallyPointPath_wrapper(size_t index);
349 
350  /**
351  * Recomputes the full path from this entity/the previous rally point to the next rally point, and does all the necessary
352  * post-processing to make it prettier. This doesn't check if we have a valid position or if a rally point is set.
353  *
354  * You shouldn't need to call this method directly.
355  */
356  void RecomputeRallyPointPath(size_t index, CmpPtr<ICmpPosition>& cmpPosition, CmpPtr<ICmpFootprint>& cmpFootprint, CmpPtr<ICmpPathfinder> cmpPathfinder);
357 
358  /**
359  * Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments and overlay lines to
360  * match if necessary. Does nothing if the rally point lines are not currently set to be displayed, or if no rally point is set.
361  */
362  void UpdateOverlayLines();
363 
364  /**
365  * Sets up all overlay lines for rendering according to the current full path and visibility segments. Splits the line into solid
366  * and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently set, this method
367  * does nothing.
368  */
370 
371  /**
372  * Sets up the overlay lines for rendering according to the full path and visibility segments at @p index. Splits the line into
373  * solid and dashed pieces (for the SoD). Should be called whenever the SoD of the path at @p index has changed.
374  */
375  void ConstructOverlayLines(size_t index);
376 
377  /**
378  * Removes points from @p coords that are obstructed by the originating building's footprint, and links up the last point
379  * nicely to the edge of the building's footprint. Only needed if the pathfinder can possibly return obstructed tile waypoints,
380  * i.e. when pathfinding is started from an obstructed tile.
381  */
382  void FixFootprintWaypoints(std::vector<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint);
383 
384  /**
385  * Returns a list of indices of waypoints in the current path (m_Path[index]) where the LOS visibility changes, ordered from
386  * building/previous rally point to rally point. Used to construct the overlay line segments and track changes to the SoD.
387  */
388  void GetVisibilitySegments(std::deque<SVisibilitySegment>& out, size_t index);
389 
390  /**
391  * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily
392  * intended to reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which
393  * will happily curve and bend to reduce costs. Visually, it doesn't make sense for a rally point path to curve and bend when it
394  * could just as well have gone in a straight line; that's why we have this, to make it look more natural.
395  *
396  * @p coords array of path coordinates to simplify
397  * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be joined into a
398  * single link. If this value is set to e.g. 1, then no reductions will be performed. A value of 3 means that
399  * at most 3 consecutive node links will be joined into a single link.
400  * @p floating whether to consider nodes who are under the water level as floating on top of the water
401  */
402  void ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks = 0, bool floating = true);
403 
404  /**
405  * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring
406  * segments. You should not have to call this method directly.
407  */
408  static void MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments);
409 
410  void RenderSubmit(SceneCollector& collector);
411 };
412 
413 REGISTER_COMPONENT_TYPE(RallyPointRenderer)
414 
415 void CCmpRallyPointRenderer::Init(const CParamNode& paramNode)
416 {
417  m_Displayed = false;
418  m_SmoothPath = true;
419  m_LastOwner = INVALID_PLAYER;
420  m_LastMarkerCount = 0;
421  m_EnableDebugNodeOverlay = false;
422 
423  // ---------------------------------------------------------------------------------------------
424  // load some XML configuration data (schema guarantees that all these nodes are valid)
425 
426  m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToString();
427 
428  const CParamNode& lineColor = paramNode.GetChild("LineColour");
429  m_LineColor = CColor(
430  lineColor.GetChild("@r").ToInt()/255.f,
431  lineColor.GetChild("@g").ToInt()/255.f,
432  lineColor.GetChild("@b").ToInt()/255.f,
433  1.f
434  );
435 
436  const CParamNode& lineDashColor = paramNode.GetChild("LineDashColour");
437  m_LineDashColor = CColor(
438  lineDashColor.GetChild("@r").ToInt()/255.f,
439  lineDashColor.GetChild("@g").ToInt()/255.f,
440  lineDashColor.GetChild("@b").ToInt()/255.f,
441  1.f
442  );
443 
444  m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat();
445  m_LineTexturePath = paramNode.GetChild("LineTexture").ToString();
446  m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString();
447  m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString());
448  m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString());
449  m_LineCostClass = paramNode.GetChild("LineCostClass").ToUTF8();
450  m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8();
451 
452  // ---------------------------------------------------------------------------------------------
453  // load some textures
454 
456  {
457  CTextureProperties texturePropsBase(m_LineTexturePath);
458  texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
459  texturePropsBase.SetMaxAnisotropy(4.f);
460  m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
461 
462  CTextureProperties texturePropsMask(m_LineTextureMaskPath);
463  texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
464  texturePropsMask.SetMaxAnisotropy(4.f);
465  m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
466  }
467 }
468 
470 {
471  player_id_t previousOwner = m_LastOwner;
472  for (size_t i = 0; i < m_RallyPoints.size(); ++i)
473  {
474  if (i >= m_MarkerEntityIds.size())
476 
478  {
479  // no marker exists yet, create one first
481 
482  // allocate a new entity for the marker
483  if (!m_MarkerTemplate.empty())
484  {
485  m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity();
488  }
489  }
490 
491  // the marker entity should be valid at this point, otherwise something went wrong trying to allocate it
493  LOGERROR(L"Failed to create rally point marker entity");
494 
495  CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntityIds[i]);
496  if (markerCmpPosition)
497  {
498  if (m_Displayed && IsSet())
499  {
500  markerCmpPosition->JumpTo(m_RallyPoints[i].X, m_RallyPoints[i].Y);
501  }
502  else
503  {
504  markerCmpPosition->MoveOutOfWorld(); // hide it
505  }
506  }
507 
508  // set rally point flag selection based on player civilization
509  CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
510  if (cmpOwnership)
511  {
512  player_id_t ownerId = cmpOwnership->GetOwner();
513  if (ownerId != INVALID_PLAYER && (ownerId != previousOwner || m_LastMarkerCount < i))
514  {
515  m_LastOwner = ownerId;
516  CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
517  // cmpPlayerManager should not be null as long as this method is called on-demand instead of at Init() time
518  // (we can't rely on component initialization order in Init())
519  if (cmpPlayerManager)
520  {
521  CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId));
522  if (cmpPlayer)
523  {
524  CmpPtr<ICmpVisual> cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]);
525  if (cmpVisualActor)
526  {
527  cmpVisualActor->SetUnitEntitySelection(CStrW(cmpPlayer->GetCiv()).ToUTF8());
528  }
529  }
530  }
531  }
532  }
533  }
534  m_LastMarkerCount = m_RallyPoints.size() - 1;
535 }
536 
538 {
539  m_Path.clear();
540  m_VisibilitySegments.clear();
541  m_TexturedOverlayLines.clear();
542 
543  //// <DEBUG> ///////////////////////////////////////////////
545  m_DebugNodeOverlays.clear();
546  //// </DEBUG> //////////////////////////////////////////////
547 
548  if (!IsSet())
549  return; // no use computing a path if the rally point isn't set
550 
551  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
552  if (!cmpPosition || !cmpPosition->IsInWorld())
553  return; // no point going on if this entity doesn't have a position or is outside of the world
554 
555  CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
556  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
557 
558  for (size_t i = 0; i < m_RallyPoints.size(); ++i)
559  {
560  RecomputeRallyPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder);
561  }
562 }
563 
565 {
566  if (!IsSet())
567  return; // no use computing a path if the rally point isn't set
568 
569  CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
570  if (!cmpPosition || !cmpPosition->IsInWorld())
571  return; // no point going on if this entity doesn't have a position or is outside of the world
572 
573  CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
574  CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
575 
576  RecomputeRallyPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder);
577 }
578 
580 {
581  while (index >= m_Path.size())
582  {
583  std::vector<CVector2D> tmp;
584  m_Path.push_back(tmp);
585  }
586  m_Path[index].clear();
587 
588  while (index >= m_VisibilitySegments.size())
589  {
590  std::deque<SVisibilitySegment> tmp;
591  m_VisibilitySegments.push_back(tmp);
592  }
593  m_VisibilitySegments[index].clear();
594 
595  entity_pos_t pathStartX;
596  entity_pos_t pathStartY;
597 
598  if (index == 0)
599  {
600  pathStartX = cmpPosition->GetPosition2D().X;
601  pathStartY = cmpPosition->GetPosition2D().Y;
602  }
603  else
604  {
605  pathStartX = m_RallyPoints[index-1].X;
606  pathStartY = m_RallyPoints[index-1].Y;
607  }
608 
609  // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a
610  // list of waypoints (i.e. a Path) from the building/previous rally point to the goal, where each
611  // waypoint is centered at a tile. We'll have to do some post-processing on the path to get it smooth.
612  Path path;
613  std::vector<Waypoint>& waypoints = path.m_Waypoints;
614 
615  Goal goal = { Goal::POINT, m_RallyPoints[index].X, m_RallyPoints[index].Y };
616  cmpPathfinder->ComputePath(
617  pathStartX,
618  pathStartY,
619  goal,
621  cmpPathfinder->GetCostClass(m_LineCostClass),
622  path
623  );
624 
625  // Check if we got a path back; if not we probably have two markers less than one tile apart.
626  if (path.m_Waypoints.size() < 2)
627  {
628  m_Path[index].push_back(CVector2D(goal.x.ToFloat(), goal.z.ToFloat()));
629  m_Path[index].push_back(CVector2D(pathStartX.ToFloat(), pathStartY.ToFloat()));
630  return;
631  }
632 
633  // From here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth
634  // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot.
635  // Since we'll only be further using these points for rendering purposes, using floats should be fine.
636 
637  // Make sure to add the actual goal point as the last point (the long pathfinder only finds paths to the tile closest to the
638  // goal, so we need to complete the last bit from the closest tile to the rally point itself)
639  // NOTE: the points are returned in reverse order (from the goal to the start point), so we actually need to insert it at the
640  // front of the coordinate list. Hence, we'll do this first before appending the rest of the fixed waypoints as CVector2Ds.
641 
642  Waypoint& lastWaypoint = waypoints.back();
643  if (lastWaypoint.x != goal.x || lastWaypoint.z != goal.z)
644  m_Path[index].push_back(CVector2D(goal.x.ToFloat(), goal.z.ToFloat()));
645 
646  // add the rest of the waypoints
647  for (size_t i = 0; i < waypoints.size(); ++i)
648  m_Path[index].push_back(CVector2D(waypoints[i].x.ToFloat(), waypoints[i].z.ToFloat()));
649 
650  // add the start position
651  m_Path[index].push_back(CVector2D(pathStartX.ToFloat(), pathStartY.ToFloat()));
652 
653  // -------------------------------------------------------------------------------------------
654  // post-processing
655 
656  // Linearize the path;
657  // Pass through the waypoints, averaging each waypoint with its next one except the last one. Because the path
658  // goes from the marker to this entity/the previous flag and we want to keep the point at the marker's exact position,
659  // loop backwards through the waypoints so that the marker waypoint is maintained.
660  // TODO: see if we can do this at the same time as the waypoint -> coord conversion above
661  for(size_t i = m_Path[index].size() - 2; i > 0; --i)
662  m_Path[index][i] = (m_Path[index][i] + m_Path[index][i-1]) / 2.0f;
663 
664  // if there's a footprint and this path starts from this entity, remove any points returned by the pathfinder that may be on obstructed footprint tiles
665  if (index == 0 && cmpFootprint)
666  FixFootprintWaypoints(m_Path[index], cmpPosition, cmpFootprint);
667 
668  // Eliminate some consecutive waypoints that are visible from eachother. Reduce across a maximum distance of approx. 6 tiles
669  // (prevents segments that are too long to properly stick to the terrain)
671 
672  //// <DEBUG> ///////////////////////////////////////////////
674  {
675  while (index >= m_DebugNodeOverlays.size())
676  {
677  std::vector<SOverlayLine> tmp;
678  m_DebugNodeOverlays.push_back(tmp);
679  }
680  m_DebugNodeOverlays[index].clear();
681  }
683  {
684  // Create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the
685  // terrain so we can still see them after the interpolated points are added)
686  for (size_t j = 0; j < m_Path[index].size(); ++j)
687  {
688  SOverlayLine overlayLine;
689  overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f);
690  overlayLine.m_Thickness = 2;
691  SimRender::ConstructSquareOnGround(GetSimContext(), m_Path[index][j].X, m_Path[index][j].Y, 0.2f, 0.2f, 1.0f, overlayLine, true);
692  m_DebugNodeOverlays[index].push_back(overlayLine);
693  }
694  }
695  //// </DEBUG> //////////////////////////////////////////////
696 
697  if (m_SmoothPath)
698  // The number of points to interpolate goes hand in hand with the maximum amount of node links allowed to be joined together
699  // by the visibility reduction. The more node links that can be joined together, the more interpolated points you need to
700  // generate to be able to deal with local terrain height changes.
701  SimRender::InterpolatePointsRNS(m_Path[index], false, 0, 8); // no offset, keep line at its exact path
702 
703  // -------------------------------------------------------------------------------------------
704 
705  // 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
707 
708  // build overlay lines for the new path
709  ConstructOverlayLines(index);
710 }
711 
713 {
714  m_TexturedOverlayLines.clear();
715 
716  for (size_t i = 0; i < m_Path.size(); ++i)
718 }
719 
721 {
722  // We need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the
723  // renderer, because it does some fancy vertex buffering thing and caches them internally instead of recomputing them on every
724  // pass (which is only sensible).
725  while (index >= m_TexturedOverlayLines.size())
726  {
727  std::vector<SOverlayTexturedLine> tmp;
728  m_TexturedOverlayLines.push_back(tmp);
729  }
730  m_TexturedOverlayLines[index].clear();
731 
732  if (m_Path[index].size() < 2)
733  return;
734 
735  CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
736  LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it)
737 
738  for (std::deque<SVisibilitySegment>::const_iterator it = m_VisibilitySegments[index].begin(); it != m_VisibilitySegments[index].end(); ++it)
739  {
740  const SVisibilitySegment& segment = (*it);
741 
742  if (segment.m_Visible)
743  {
744  // does this segment border on the building or rally point flag on either side?
745  bool bordersBuilding = (segment.m_EndIndex == m_Path[index].size() - 1);
746  bool bordersFlag = (segment.m_StartIndex == 0);
747 
748  // construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx
749  SOverlayTexturedLine overlayLine;
750  overlayLine.m_Thickness = m_LineThickness;
751  overlayLine.m_SimContext = &GetSimContext();
752  overlayLine.m_TextureBase = m_Texture;
753  overlayLine.m_TextureMask = m_TextureMask;
754  overlayLine.m_Color = m_LineColor;
755  overlayLine.m_Closed = false;
756  // we should take care to only use m_LineXCap for the actual end points at the building and the rally point; any intermediate
757  // end points (i.e., that border a dashed segment) should have the dashed cap
758  // the path line is actually in reverse order as well, so let's swap out the start and end caps
759  overlayLine.m_StartCapType = (bordersFlag ? m_LineEndCapType : dashesLineCapType);
760  overlayLine.m_EndCapType = (bordersBuilding ? m_LineStartCapType : dashesLineCapType);
761  overlayLine.m_AlwaysVisible = true;
762 
763  // push overlay line coordinates
764  ENSURE(segment.m_EndIndex > segment.m_StartIndex);
765  for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j) // end index is inclusive here
766  {
767  overlayLine.m_Coords.push_back(m_Path[index][j].X);
768  overlayLine.m_Coords.push_back(m_Path[index][j].Y);
769  }
770 
771  m_TexturedOverlayLines[index].push_back(overlayLine);
772  }
773  else
774  {
775  // construct dashed line from startPointIdx to endPointIdx; add textured overlay lines for it to the render list
776  std::vector<CVector2D> straightLine;
777  straightLine.push_back(m_Path[index][segment.m_StartIndex]);
778  straightLine.push_back(m_Path[index][segment.m_EndIndex]);
779 
780  // 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
781  // area is visually obvious. This requires some calculations to see what size we should make the dashes and clears for them
782  // to fit exactly.
783 
784  float maxDashSize = 3.f;
785  float maxClearSize = 3.f;
786 
787  float dashSize = maxDashSize;
788  float clearSize = maxClearSize;
789  float pairDashRatio = (dashSize / (dashSize + clearSize)); // ratio of the dash's length to a (dash + clear) pair's length
790 
791  float distance = (m_Path[index][segment.m_StartIndex] - m_Path[index][segment.m_EndIndex]).Length(); // straight-line distance between the points
792 
793  // See how many pairs (dash + clear) of unmodified size can fit into the distance. Then check the remaining distance; if it's not exactly
794  // a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is.
795  int numFitUnmodified = floor(distance/(dashSize + clearSize));
796  float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize));
797 
798  // Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly.
799  // We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make
800  // it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified
801  // (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's
802  // length and the clear's length.
803 
804  // we always want to have at least one dash/clear pair (i.e., "|===| |===|"); also, we need to avoid division by zero below.
805  numFitUnmodified = std::max(1, numFitUnmodified);
806 
807  float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified; // can be either positive or negative
808  dashSize += pairDashRatio * pairwiseLengthDifference;
809  clearSize += (1 - pairDashRatio) * pairwiseLengthDifference;
810 
811  // ------------------------------------------------------------------------------------------------
812 
813  SDashedLine dashedLine;
814  SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize);
815 
816  // build overlay lines for dashes
817  size_t numDashes = dashedLine.m_StartIndices.size();
818  for (size_t i=0; i < numDashes; i++)
819  {
820  SOverlayTexturedLine dashOverlay;
821 
822  dashOverlay.m_Thickness = m_LineThickness;
823  dashOverlay.m_SimContext = &GetSimContext();
824  dashOverlay.m_TextureBase = m_Texture;
825  dashOverlay.m_TextureMask = m_TextureMask;
826  dashOverlay.m_Color = m_LineDashColor;
827  dashOverlay.m_Closed = false;
828  dashOverlay.m_StartCapType = dashesLineCapType;
829  dashOverlay.m_EndCapType = dashesLineCapType;
830  dashOverlay.m_AlwaysVisible = true;
831  // TODO: maybe adjust the elevation of the dashes to be a little lower, so that it slides underneath the actual path
832 
833  size_t dashStartIndex = dashedLine.m_StartIndices[i];
834  size_t dashEndIndex = dashedLine.GetEndIndex(i);
835  ENSURE(dashEndIndex > dashStartIndex);
836 
837  for (size_t n = dashStartIndex; n < dashEndIndex; n++)
838  {
839  dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].X);
840  dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y);
841  }
842 
843  m_TexturedOverlayLines[index].push_back(dashOverlay);
844  }
845 
846  }
847  }
848 
849  //// <DEBUG> //////////////////////////////////////////////
851  {
852  while (index >= m_DebugNodeOverlays.size())
853  {
854  std::vector<SOverlayLine> tmp;
855  m_DebugNodeOverlays.push_back(tmp);
856  }
857  for (size_t j = 0; j < m_Path[index].size(); ++j)
858  {
859  SOverlayLine overlayLine;
860  overlayLine.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
861  overlayLine.m_Thickness = 1;
862  SimRender::ConstructCircleOnGround(GetSimContext(), m_Path[index][j].X, m_Path[index][j].Y, 0.075f, overlayLine, true);
863  m_DebugNodeOverlays[index].push_back(overlayLine);
864  }
865  }
866  //// </DEBUG> //////////////////////////////////////////////
867 }
868 
870 {
871  // We should only do this if the rally point is currently being displayed and set inside the world, otherwise it's a massive
872  // waste of time to calculate all this stuff (this method is called every turn)
873  if (!m_Displayed || !IsSet())
874  return;
875 
876  // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones
877  std::deque<std::deque<SVisibilitySegment> > newVisibilitySegments;
878  for (size_t i = 0; i < m_Path.size(); ++i)
879  {
880  std::deque<SVisibilitySegment> tmp;
881  newVisibilitySegments.push_back(tmp);
882  GetVisibilitySegments(newVisibilitySegments[i], i);
883  }
884 
885  // Check if the full path changed, then reconstruct all overlay lines, otherwise check if a segment changed and update that.
886  if (m_VisibilitySegments.size() != newVisibilitySegments.size())
887  {
888  m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time
890  }
891  else
892  {
893  for (size_t i = 0; i < m_VisibilitySegments.size(); ++i)
894  {
895  if (m_VisibilitySegments[i] != newVisibilitySegments[i])
896  {
897  // The visibility segments have changed, reconstruct the overlay lines to match. NOTE: The path itself doesn't
898  // change, only the overlay lines we construct from it.
899  m_VisibilitySegments[i] = newVisibilitySegments[i]; // save the new visibility segments to compare against next time
901  }
902  }
903  }
904 }
905 
906 void CCmpRallyPointRenderer::FixFootprintWaypoints(std::vector<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint)
907 {
908  ENSURE(cmpPosition);
909  ENSURE(cmpFootprint);
910 
911  // -----------------------------------------------------------------------------------------------------
912  // TODO: nasty fixed/float conversions everywhere
913 
914  // grab the shape and dimensions of the footprint
915  entity_pos_t footprintSize0, footprintSize1, footprintHeight;
916  ICmpFootprint::EShape footprintShape;
917  cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
918 
919  // grab the center of the footprint
920  CFixedVector2D center = cmpPosition->GetPosition2D();
921 
922  // -----------------------------------------------------------------------------------------------------
923 
924  switch (footprintShape)
925  {
927  {
928  // in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
929 
930  // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
931  // and the rotated unit vectors in the X/Z plane of the shape's footprint
932  // (the Footprint itself holds only the outline, the Position holds the orientation)
933 
934  fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
935  fixed a = cmpPosition->GetRotation().Y;
936  sincos_approx(a, s, c);
937  CFixedVector2D u(c, -s); // unit vector along the rotated X axis
938  CFixedVector2D v(s, c); // unit vector along the rotated Z axis
939  CFixedVector2D halfSize(footprintSize0/2, footprintSize1/2);
940 
941  // starting from the start position, check if any points are within the footprint of the building
942  // (this is possible if the pathfinder was started from a point located within the footprint)
943  for(int i = (int)(coords.size() - 1); i >= 0; i--)
944  {
945  const CVector2D& wp = coords[i];
946  if (Geometry::PointIsInSquare(CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center, u, v, halfSize))
947  {
948  coords.erase(coords.begin() + i);
949  }
950  else
951  {
952  break; // point no longer inside footprint, from this point on neither will any of the following be
953  }
954  }
955 
956  // add a point right on the edge of the footprint (nearest to the last waypoint) so that it links up nicely with the rest of the path
957  CFixedVector2D lastWaypoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y));
958  CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(lastWaypoint - center, u, v, halfSize); // relative to the shape origin (center)
959  CVector2D footprintEdge((center.X + footprintEdgePoint.X).ToFloat(), (center.Y + footprintEdgePoint.Y).ToFloat());
960  coords.push_back(footprintEdge);
961 
962  }
963  break;
965  {
966  // in this case, both footprintSize0 and 1 indicate the circle's radius
967 
968  for(int i = (int)(coords.size() - 1); i >= 0; i--)
969  {
970  const CVector2D& wp = coords[i];
971  fixed pointDistance = (CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center).Length();
972  if (pointDistance <= footprintSize0)
973  {
974  coords.erase(coords.begin() + i);
975  }
976  else
977  {
978  break; // point no longer inside footprint, from this point on neither will any of the following be
979  }
980  }
981 
982  // add a point right on the edge of the footprint so that it links up nicely with the rest of the path
983  CVector2D centerVec2D(center.X.ToFloat(), center.Y.ToFloat());
984  CVector2D centerToLast(coords.back() - centerVec2D);
985  coords.push_back(centerVec2D + (centerToLast.Normalized() * footprintSize0.ToFloat()));
986  }
987  break;
988  }
989 }
990 
991 void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks, bool floating)
992 {
993  CmpPtr<ICmpPathfinder> cmpPathFinder(GetSystemEntity());
994  CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
995  CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
996  ENSURE(cmpPathFinder && cmpTerrain && cmpWaterManager);
997 
998  if (coords.size() < 3)
999  return;
1000 
1001  // 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
1002  // 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
1003  // process from there on until the entire line is checked. The output is the array of base nodes.
1004 
1005  std::vector<CVector2D> newCoords;
1006  StationaryOnlyObstructionFilter obstructionFilter;
1008  ICmpPathfinder::pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass);
1009 
1010  newCoords.push_back(coords[0]); // save the first base node
1011 
1012  size_t baseNodeIdx = 0;
1013  size_t curNodeIdx = 1;
1014 
1015  float baseNodeY;
1016  entity_pos_t baseNodeX;
1017  entity_pos_t baseNodeZ;
1018 
1019  // set initial base node coords
1020  baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
1021  baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
1022  baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
1023  if (floating)
1024  baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
1025 
1026  while (curNodeIdx < coords.size())
1027  {
1028  ENSURE(curNodeIdx > baseNodeIdx); // this needs to be true at all times, otherwise we're checking visibility between a point and itself
1029 
1030  entity_pos_t curNodeX = fixed::FromFloat(coords[curNodeIdx].X);
1031  entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y);
1032  float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y);
1033  if (floating)
1034  curNodeY = std::max(curNodeY, cmpWaterManager->GetExactWaterLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y));
1035 
1036  // find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!)
1037  bool curNodeVisible = cmpPathFinder->CheckMovement(obstructionFilter, baseNodeX, baseNodeZ, curNodeX, curNodeZ, lineRadius, passabilityClass);
1038 
1039  // since height differences are ignored by CheckMovement, let's call two points visible from one another only if they're at
1040  // roughly the same terrain elevation
1041  curNodeVisible = curNodeVisible && (fabsf(curNodeY - baseNodeY) < 3.f); // TODO: this could probably use some tuning
1042  if (maxSegmentLinks > 0)
1043  // max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx)
1044  curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks);
1045 
1046  if (!curNodeVisible)
1047  {
1048  // current node is not visible from the base node, so the previous one was the last visible point from baseNode and should
1049  // hence become the new base node for further iterations.
1050 
1051  // if curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then
1052  // we should take care not to stay stuck at the current base node
1053  if (curNodeIdx > baseNodeIdx + 1)
1054  {
1055  baseNodeIdx = curNodeIdx - 1;
1056  }
1057  else
1058  {
1059  // curNodeIdx == baseNodeIdx + 1
1060  baseNodeIdx = curNodeIdx;
1061  curNodeIdx++; // move the next candidate node one forward so that we don't test a point against itself in the next iteration
1062  }
1063 
1064  newCoords.push_back(coords[baseNodeIdx]); // add new base node to output list
1065 
1066  // update base node coordinates
1067  baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
1068  baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
1069  baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
1070  if (floating)
1071  baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
1072  }
1073 
1074  curNodeIdx++;
1075  }
1076 
1077  // 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
1078  // base node, then the loop above just ends and no endpoint is ever added to the list.
1079  ENSURE(curNodeIdx == coords.size());
1080  newCoords.push_back(coords[coords.size() - 1]);
1081 
1082  coords.swap(newCoords);
1083 }
1084 
1085 void CCmpRallyPointRenderer::GetVisibilitySegments(std::deque<SVisibilitySegment>& out, size_t index)
1086 {
1087  out.clear();
1088 
1089  if (m_Path[index].size() < 2)
1090  return;
1091 
1093 
1095  CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
1096 
1097  // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
1098  // a new one at the next point.
1099 
1100  bool lastVisible = losQuerier.IsExplored(
1101  (fixed::FromFloat(m_Path[index][0].X) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest(),
1102  (fixed::FromFloat(m_Path[index][0].Y) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest()
1103  );
1104  size_t curSegmentStartIndex = 0; // starting node index of the current segment
1105 
1106  for (size_t k = 1; k < m_Path[index].size(); ++k)
1107  {
1108  // grab tile indices for this coord
1109  int i = (fixed::FromFloat(m_Path[index][k].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
1110  int j = (fixed::FromFloat(m_Path[index][k].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
1111 
1112  bool nodeVisible = losQuerier.IsExplored(i, j);
1113  if (nodeVisible != lastVisible)
1114  {
1115  // visibility changed; write out the segment that was just completed and get ready for the new one
1116  out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1));
1117 
1118  //curSegmentStartIndex = k; // new segment starts here
1119  curSegmentStartIndex = k - 1;
1120  lastVisible = nodeVisible;
1121  }
1122 
1123  }
1124 
1125  // terminate the last segment
1126  out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1));
1127 
1129 }
1130 
1131 void CCmpRallyPointRenderer::MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments)
1132 {
1133  // Scan for single-point segments; if they are inbetween two other segments, delete them and merge the surrounding segments.
1134  // If they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't
1135  // themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first).
1136 
1137  // first scan for absorptions of single-point surrounded segments (i.e. excluding edge segments)
1138  size_t numSegments = segments.size();
1139 
1140  // WARNING: FOR LOOP TRICKERY AHEAD!
1141  for (size_t i = 1; i < numSegments - 1;)
1142  {
1143  SVisibilitySegment& segment = segments[i];
1144  if (segment.IsSinglePoint())
1145  {
1146  // since the segments' visibility alternates, the surrounding ones should have the same visibility
1147  ENSURE(segments[i-1].m_Visible == segments[i+1].m_Visible);
1148 
1149  segments[i-1].m_EndIndex = segments[i+1].m_EndIndex; // make previous segment span all the way across to the next
1150  segments.erase(segments.begin() + i); // erase this segment ...
1151  segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i])
1152  numSegments -= 2; // we removed 2 segments, so update the loop condition
1153  // in the next iteration, i should still point to the segment right after the one that got expanded, which is now
1154  // at position i; so don't increment i here
1155  }
1156  else
1157  {
1158  ++i;
1159  }
1160  }
1161 
1162  ENSURE(numSegments == segments.size());
1163 
1164  // check to see if the first segment needs to be merged with its neighbour
1165  if (segments.size() >= 2 && segments[0].IsSinglePoint())
1166  {
1167  int firstSegmentStartIndex = segments.front().m_StartIndex;
1168  ENSURE(firstSegmentStartIndex == 0);
1169  ENSURE(!segments[1].IsSinglePoint()); // at this point, the second segment should never be a single-point segment
1170 
1171  segments.erase(segments.begin());
1172  segments.front().m_StartIndex = firstSegmentStartIndex;
1173 
1174  }
1175 
1176  // check to see if the last segment needs to be merged with its neighbour
1177  if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint())
1178  {
1179  int lastSegmentEndIndex = segments.back().m_EndIndex;
1180  ENSURE(!segments[segments.size()-2].IsSinglePoint()); // at this point, the second-to-last segment should never be a single-point segment
1181 
1182  segments.erase(segments.end());
1183  segments.back().m_EndIndex = lastSegmentEndIndex;
1184  }
1185 
1186  // --------------------------------------------------------------------------------------------------------
1187  // at this point, every segment should have at least 2 points
1188  for (size_t i = 0; i < segments.size(); ++i)
1189  {
1190  ENSURE(!segments[i].IsSinglePoint());
1191  ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex);
1192  }
1193 }
1194 
1196 {
1197  // we only get here if the rally point is set and should be displayed
1198  for (size_t i = 0; i < m_TexturedOverlayLines.size(); ++i)
1199  {
1200  for (size_t j = 0; j < m_TexturedOverlayLines[i].size(); ++j)
1201  {
1202  if (!m_TexturedOverlayLines[i][j].m_Coords.empty())
1203  collector.Submit(&m_TexturedOverlayLines[i][j]);
1204  }
1205  }
1206 
1208  {
1209  for (size_t i = 0; i < m_DebugNodeOverlays.size(); ++i)
1210  for (size_t j = 0; j < m_DebugNodeOverlays[i].size(); ++j)
1211  collector.Submit(&m_DebugNodeOverlays[i][j]);
1212  }
1213 }
An entity initialisation parameter node.
Definition: ParamNode.h:112
virtual pass_class_t GetPassabilityClass(const std::string &name)=0
Get the tag for a given passability class name.
void SubscribeToMessageType(MessageTypeId mtid)
Subscribe the current component type to the given message type.
A simple fixed-point number class.
Definition: Fixed.h:115
virtual void HandleMessage(const CMessage &msg, bool global)
Definition: Decompose.h:22
virtual void AddPosition_wrapper(CFixedVector2D pos)
Add another position at which a marker should be displayed, connected to the previous one...
#define REGISTER_COMPONENT_TYPE(cname)
Definition: Component.h:30
CTexturePtr m_TextureBase
Definition: Overlay.h:84
float m_LineThickness
Marker connector line settings (loaded from XML)
virtual void Init(const CParamNode &paramNode)
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
virtual CLosQuerier GetLosQuerier(player_id_t player)=0
Returns a CLosQuerier for checking whether vertex positions are visible to the given player (or other...
Line-based overlay, with world-space coordinates, rendered in the world potentially behind other obje...
Definition: Overlay.h:36
float X
Definition: Vector2D.h:157
SOverlayTexturedLine::LineCapType LineCapType
entity_id_t AddEntity(const std::wstring &templateName, entity_id_t ent)
Constructs an entity based on the given template, and adds it the world with entity ID ent...
const ssize_t TERRAIN_TILE_SIZE
metres [world space units] per tile in x and z
Definition: Terrain.h:40
Definition: Decompose.h:22
static LineCapType StrToLineCapType(const std::wstring &str)
Converts a string line cap type into its corresponding LineCap enum value, and returns the resulting ...
Definition: Overlay.cpp:24
#define LOGERROR
Definition: CLogger.h:35
SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex)
std::string m_LineCostClass
Pathfinder cost class to use for computing the (long-range) marker line path.
Definition: Overlay.h:34
Serialization interface; see serialization overview.
Definition: ISerializer.h:120
virtual float GetExactGroundLevel(float x, float z)=0
static Status Init()
Definition: h_mgr.cpp:744
static void MergeVisibilitySegments(std::deque< SVisibilitySegment > &segments)
Helper function to GetVisibilitySegments, factored out for testing.
Represents the filename and GL parameters of a texture, for passing to CTextureManager::CreateTexture...
Add renderable objects to the scene collector.
Definition: MessageTypes.h:145
void ConstructDashedLine(const std::vector< CVector2D > &linePoints, SDashedLine &dashedLineOut, const float dashLength, const float blankLength)
Creates a dashed line from the given line, dash length, and blank space between.
Definition: Render.cpp:438
static void out(const wchar_t *fmt,...)
Definition: wdbg_sym.cpp:419
void UpdateOverlayLines()
Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments...
ICmpRangeManager::CLosQuerier CLosQuerier
virtual CFixedVector3D GetRotation()=0
Returns the current rotation (relative to the upwards axis), as Euler angles with X=pitch...
Returned path.
void InterpolatePointsRNS(std::vector< CVector2D > &points, bool closed, float offset, int segmentSamples=4)
Updates the given points to include intermediate points interpolating between the original control po...
Definition: Render.cpp:347
virtual void SetPosition(CFixedVector2D pos)
Sets the position at which the rally point marker should be displayed.
std::vector< Waypoint > m_Waypoints
std::vector< CFixedVector2D > m_RallyPoints
Display position of the rally points.
bool m_AlwaysVisible
Should this line be rendered fully visible at all times, even under the SoD?
Definition: Overlay.h:96
virtual cost_class_t GetCostClass(const std::string &name)=0
Get the tag for a given movement cost class name.
int ToInt() const
Parses the content of this node as an integer.
Definition: ParamNode.cpp:213
bool operator!=(const SVisibilitySegment &other) const
LineCapType m_EndCapType
Definition: Overlay.h:99
std::wstring m_MarkerTemplate
Template name of the rally point markers.
virtual bool IsInWorld()=0
Returns true if the entity currently exists at a defined position in the world.
CFixedVector2D NearestPointOnSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize)
Find point closest to the given point on the edge of the given square or rectangle.
Definition: Geometry.cpp:119
Obstruction test filter that will test only against stationary (i.e.
int32_t player_id_t
valid player IDs are non-negative (see ICmpOwnership)
Definition: Player.h:24
bool m_Displayed
Should we render the rally points and the path lines? (set from JS when e.g. the unit is selected/des...
This interface accepts renderable objects.
Definition: Scene.h:82
#define g_Renderer
Definition: Renderer.h:61
size_t GetEndIndex(size_t i)
Returns the (exclusive) end point index (i.e. index within m_Points) of dash n.
Definition: Render.h:49
bool IsSet()
Returns true iff at least one display rally point is set; i.e., if we have a point to render our mark...
std::vector< entity_id_t > m_MarkerEntityIds
Entity IDs of the rally point markers.
CEntityHandle GetEntityHandle() const
Definition: IComponent.h:45
std::vector< std::vector< SOverlayTexturedLine > > m_TexturedOverlayLines
Textured overlay lines to be used for rendering the marker line.
virtual void MoveOutOfWorld()=0
Causes IsInWorld to return false.
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
void ConstructCircleOnGround(const CSimContext &context, float x, float z, float radius, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line as a circle with given center and radius, conforming to terrain.
Definition: Render.cpp:70
std::vector< std::vector< CVector2D > > m_Path
Full path to the rally points as returned by the pathfinder, with some post-processing applied to red...
void RecomputeAllRallyPointPaths()
Recomputes all the full paths from this entity to the rally point and from the rally point to the nex...
virtual int GetType() const =0
virtual void Reset()
Reset the positions of this rally point marker.
const CParamNode & GetChild(const char *name) const
Returns the (unique) child node with the given name, or a node with IsOk() == false if there is none...
Definition: ParamNode.cpp:185
virtual bool CheckMovement(const IObstructionTestFilter &filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass)=0
Check whether the given movement line is valid and doesn&#39;t hit any obstructions or impassable terrain...
Textured line overlay, with world-space coordinates, rendered in the world onto the terrain...
Definition: Overlay.h:62
bool m_Closed
Should this line be treated as a closed loop? If set, any end cap settings are ignored.
Definition: Overlay.h:94
virtual void Serialize(ISerializer &serialize)
bool m_EnableDebugNodeOverlay
Draw little overlay circles to indicate where the exact path points are?
std::deque< std::deque< SVisibilitySegment > > m_VisibilitySegments
Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments.
Semi-circular line ending.
Definition: Overlay.h:74
virtual entity_id_t GetPlayerByID(int32_t id)=0
float ToFloat() const
Convert to float. May be lossy - float can&#39;t represent all values.
Definition: Fixed.h:161
void ReduceSegmentsByVisibility(std::vector< CVector2D > &coords, unsigned maxSegmentLinks=0, bool floating=true)
Simplifies the path by removing waypoints that lie between two points that are visible from one anoth...
virtual CFixedVector2D GetPosition2D()=0
Returns the current x,z position (no interpolation).
virtual player_id_t GetOwner()=0
CColor m_Color
Color to apply to the line texture, where indicated by the mask.
Definition: Overlay.h:88
virtual float GetExactWaterLevel(float x, float z)=0
Get the current water level at the given point.
std::string m_LinePassabilityClass
Pathfinder passability class to use for computing the (long-range) marker line path.
std::vector< CVector2D > m_Points
Packed array of consecutive dashes&#39; points. Use m_StartIndices to navigate it.
Definition: Render.h:39
std::vector< float > m_Coords
(x, z) vertex coordinate pairs; y is computed automatically.
Definition: Overlay.h:90
std::vector< size_t > m_StartIndices
Start indices in m_Points of each dash.
Definition: Render.h:46
#define DEFAULT_COMPONENT_ALLOCATOR(cname)
Definition: Component.h:44
static void ClassInit(CComponentManager &componentManager)
const CSimContext & GetSimContext() const
Definition: IComponent.h:52
static bool IsInitialised()
Definition: Singleton.h:63
CTexturePtr m_TextureMask
Definition: Overlay.h:85
entity_id_t AllocateNewLocalEntity()
Returns a new local entity ID that has never been used before.
A simplified syntax for accessing entity components.
Definition: CmpPtr.h:55
float Y
Definition: Vector2D.h:157
bool operator==(const SVisibilitySegment &other) const
void FixFootprintWaypoints(std::vector< CVector2D > &coords, CmpPtr< ICmpPosition > cmpPosition, CmpPtr< ICmpFootprint > cmpFootprint)
Removes points from coords that are obstructed by the originating building&#39;s footprint, and links up the last point nicely to the edge of the building&#39;s footprint.
int GetCurrentDisplayedPlayer() const
Returns the player ID that the current display is being rendered for.
Definition: SimContext.cpp:68
std::vector< std::vector< SOverlayLine > > m_DebugNodeOverlays
CEntityHandle GetSystemEntity() const
Definition: IComponent.h:50
virtual void SetDisplayed(bool displayed)
Sets whether the rally point marker and line should be displayed.
Helper functions related to rendering.
bool IsExplored(ssize_t i, ssize_t j)
Returns whether the given vertex is explored (i.e.
void SetMaxAnisotropy(float aniso)
Set maximum anisotropy value.
player_id_t m_LastOwner
Last seen owner of this entity (used to keep track of ownership changes).
u8 m_Thickness
Definition: Overlay.h:42
virtual void SetUnitEntitySelection(const CStr &selection)=0
Sets the specified entity selection on the underlying unit.
SceneCollector & collector
Definition: MessageTypes.h:155
virtual void JumpTo(entity_pos_t x, entity_pos_t z)=0
Move immediately to the given location, with no interpolation.
Object providing efficient abstracted access to the LOS state.
virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal &goal, pass_class_t passClass, cost_class_t costClass, Path &ret)=0
Compute a tile-based path from the given point to the goal, and return the set of waypoints...
void RenderSubmit(SceneCollector &collector)
void ConstructSquareOnGround(const CSimContext &context, float x, float z, float w, float h, float a, SOverlayLine &overlay, bool floating, float heightOffset=0.25f)
Constructs overlay line as rectangle with given center and dimensions, conforming to terrain...
Definition: Render.cpp:121
void ConstructAllOverlayLines()
Sets up all overlay lines for rendering according to the current full path and visibility segments...
static CFixed FromFloat(float n)
Definition: Fixed.h:141
void UpdateMarkers()
Repositions the rally point markers; moves them outside of the world (ie.
void RecomputeRallyPointPath(size_t index, CmpPtr< ICmpPosition > &cmpPosition, CmpPtr< ICmpFootprint > &cmpFootprint, CmpPtr< ICmpPathfinder > cmpPathfinder)
Recomputes the full path from this entity/the previous rally point to the next rally point...
ICmpPathfinder::Waypoint Waypoint
void sincos_approx(CFixed_15_16 a, CFixed_15_16 &sin_out, CFixed_15_16 &cos_out)
Compute sin(a) and cos(a).
Definition: Fixed.cpp:187
virtual void GetShape(EShape &shape, entity_pos_t &size0, entity_pos_t &size1, entity_pos_t &height)=0
Return the shape of this footprint.
const CSimContext * m_SimContext
Simulation context applicable for this overlay line; used to obtain terrain information during automa...
Definition: Overlay.h:105
void DestroyComponentsSoon(entity_id_t ent)
Destroys all the components belonging to the specified entity when FlushDestroyedComponents is called...
void ConstructOverlayLines(size_t index)
Sets up the overlay lines for rendering according to the full path and visibility segments at index...
LineCapType m_StartCapType
Definition: Overlay.h:98
bool m_SmoothPath
Smooth the path before rendering?
bool PointIsInSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize)
Checks if a point is inside the given rotated square or rectangle.
Definition: Geometry.cpp:28
float m_Thickness
Half-width of the line, in world-space units.
Definition: Overlay.h:92
CColor m_Color
Definition: Overlay.h:40
static float Length(const SVec3 v)
Definition: mikktspace.cpp:112
const entity_id_t INVALID_ENTITY
Invalid entity ID.
Definition: Entity.h:36
void GetVisibilitySegments(std::deque< SVisibilitySegment > &out, size_t index)
Returns a list of indices of waypoints in the current path (m_Path[index]) where the LOS visibility c...
static std::string GetSchema()
void SetWrap(GLint wrap)
Set wrapping mode (typically GL_REPEAT, GL_CLAMP_TO_EDGE, etc).
u32 entity_id_t
Entity ID type.
Definition: Entity.h:24
virtual void Submit(CPatch *patch)=0
Submit a terrain patch that is part of the scene.
static const player_id_t INVALID_PLAYER
Definition: Player.h:26
void RecomputeRallyPointPath_wrapper(size_t index)
Recomputes the full path for m_Path[ index], and does all the necessary post-processing to make it pr...
Helper functions related to geometry algorithms.
CComponentManager & GetComponentManager() const
Definition: SimContext.cpp:35
void AddPosition(CFixedVector2D pos, bool recompute)
Helper function for AddPosition_wrapper and SetPosition.
Deserialization interface; see serialization overview.
Definition: IDeserializer.h:34
virtual void Deserialize(const CParamNode &paramNode, IDeserializer &deserialize)
shared_ptr< CTexture > CTexturePtr
Definition: Texture.h:22