Index: ps/trunk/source/simulation2/components/CCmpRallyPointRenderer.h
===================================================================
--- ps/trunk/source/simulation2/components/CCmpRallyPointRenderer.h
+++ ps/trunk/source/simulation2/components/CCmpRallyPointRenderer.h
@@ -0,0 +1,276 @@
+/* Copyright (C) 2019 Wildfire Games.
+* This file is part of 0 A.D.
+*
+* 0 A.D. is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 of the License, or
+* (at your option) any later version.
+*
+* 0 A.D. is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with 0 A.D. If not, see .
+*/
+
+#ifndef INCLUDED_CCMPRALLYPOINTRENDERER
+#define INCLUDED_CCMPRALLYPOINTRENDERER
+
+#include "ICmpRallyPointRenderer.h"
+#include "graphics/Overlay.h"
+#include "graphics/TextureManager.h"
+#include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpFootprint.h"
+#include "simulation2/components/ICmpObstructionManager.h"
+#include "simulation2/components/ICmpOwnership.h"
+#include "simulation2/components/ICmpPathfinder.h"
+#include "simulation2/components/ICmpPlayer.h"
+#include "simulation2/components/ICmpPlayerManager.h"
+#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpRangeManager.h"
+#include "simulation2/components/ICmpTerrain.h"
+#include "simulation2/components/ICmpVisual.h"
+#include "simulation2/components/ICmpWaterManager.h"
+#include "simulation2/helpers/Render.h"
+#include "simulation2/helpers/Geometry.h"
+#include "simulation2/system/Component.h"
+
+#include "ps/CLogger.h"
+#include "ps/Profile.h"
+#include "renderer/Renderer.h"
+
+struct SVisibilitySegment
+{
+ bool m_Visible;
+ size_t m_StartIndex;
+ size_t m_EndIndex; // Inclusive
+
+ SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex)
+ : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex)
+ {}
+
+ bool operator==(const SVisibilitySegment& other) const
+ {
+ return m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex;
+ }
+
+ bool operator!=(const SVisibilitySegment& other) const
+ {
+ return !(*this == other);
+ }
+
+ bool IsSinglePoint() const
+ {
+ return m_StartIndex == m_EndIndex;
+ }
+};
+
+class CCmpRallyPointRenderer : public ICmpRallyPointRenderer
+{
+public:
+ static std::string GetSchema();
+ static void ClassInit(CComponentManager& componentManager);
+
+ virtual void Init(const CParamNode& paramNode);
+ virtual void Deinit();
+
+ virtual void Serialize(ISerializer& UNUSED(serialize));
+ virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize));
+
+ virtual void HandleMessage(const CMessage& msg, bool UNUSED(global));
+
+ /*
+ * Must be called whenever m_Displayed or the size of m_RallyPoints change,
+ * to determine whether we need to respond to render messages.
+ */
+ virtual void UpdateMessageSubscriptions();
+
+ virtual void AddPosition_wrapper(const CFixedVector2D& pos);
+
+ virtual void SetPosition(const CFixedVector2D& pos);
+
+ virtual void UpdatePosition(u32 rallyPointId, const CFixedVector2D& pos);
+
+ virtual void SetDisplayed(bool displayed);
+
+ virtual void Reset();
+
+ virtual void UpdateColor();
+
+ /**
+ * Returns true if at least one display rally point is set; i.e., if we have a point to render our marker/line at.
+ */
+ virtual bool IsSet() const;
+
+ DEFAULT_COMPONENT_ALLOCATOR(RallyPointRenderer)
+
+protected:
+ /**
+ * Display position of the rally points. Note that this are merely the display positions; they not necessarily the same as the
+ * actual positions used in the simulation at any given time. In particular, we need this separate copy to support
+ * instantaneously rendering the rally point markers/lines when the user sets one in-game (instead of waiting until the
+ * network-synchronization code sets it on the RallyPoint component, which might take up to half a second).
+ */
+ std::vector m_RallyPoints;
+ /**
+ * Full path to the rally points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
+ */
+ std::vector > m_Path;
+ /**
+ * Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments.
+ */
+ std::vector > m_VisibilitySegments;
+ /**
+ * Should we render the rally points and the path lines? (set from JS when e.g. the unit is selected/deselected)
+ */
+ bool m_Displayed;
+ /**
+ * Smooth the path before rendering?
+ */
+ bool m_SmoothPath;
+ /**
+ * Entity IDs of the rally point markers.
+ */
+ std::vector m_MarkerEntityIds;
+
+ size_t m_LastMarkerCount;
+ /**
+ * Last seen owner of this entity (used to keep track of ownership changes).
+ */
+ player_id_t m_LastOwner;
+ /**
+ * Template name of the rally point markers.
+ */
+ std::wstring m_MarkerTemplate;
+
+ /**
+ * Marker connector line settings (loaded from XML)
+ */
+ float m_LineThickness;
+ CColor m_LineColor;
+ CColor m_LineDashColor;
+ SOverlayTexturedLine::LineCapType m_LineStartCapType;
+ SOverlayTexturedLine::LineCapType m_LineEndCapType;
+ std::wstring m_LineTexturePath;
+ std::wstring m_LineTextureMaskPath;
+ /**
+ * Pathfinder passability class to use for computing the (long-range) marker line path.
+ */
+ std::string m_LinePassabilityClass;
+
+ CTexturePtr m_Texture;
+ CTexturePtr m_TextureMask;
+
+ /**
+ * Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render
+ * dashes for segments that are inside the SoD.
+ */
+ std::vector > m_TexturedOverlayLines;
+
+ /**
+ * Draw little overlay circles to indicate where the exact path points are.
+ */
+ bool m_EnableDebugNodeOverlay;
+ std::vector > m_DebugNodeOverlays;
+
+private:
+ /**
+ * Helper function for AddPosition_wrapper and SetPosition.
+ */
+ void AddPosition(CFixedVector2D pos, bool recompute);
+
+ /**
+ * Helper function to set the line color to its owner's color.
+ */
+ void UpdateLineColor();
+
+ /**
+ * Repositions the rally point markers; moves them outside of the world (ie. hides them), or positions them at the currently
+ * set rally points. Also updates the actor's variation according to the entity's current owning player's civilization.
+ *
+ * Should be called whenever either the position of a rally point changes (including whether it is set or not), or the display
+ * flag changes, or the ownership of the entity changes.
+ */
+ void UpdateMarkers();
+
+ /**
+ * 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
+ * post-processing to make them prettier.
+ *
+ * Should be called whenever all rally points' position changes.
+ */
+ void RecomputeAllRallyPointPaths();
+
+ /**
+ * Recomputes the full path for m_Path[ @p index], and does all the necessary post-processing to make it prettier.
+ *
+ * Should be called whenever either the starting position or the rally point's position changes.
+ */
+ void RecomputeRallyPointPath_wrapper(size_t index);
+
+ /**
+ * Recomputes the full path from this entity/the previous rally point to the next rally point, and does all the necessary
+ * post-processing to make it prettier. This doesn't check if we have a valid position or if a rally point is set.
+ *
+ * You shouldn't need to call this method directly.
+ */
+ void RecomputeRallyPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder);
+
+ /**
+ * Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments and overlay lines to
+ * match if necessary. Does nothing if the rally point lines are not currently set to be displayed, or if no rally point is set.
+ */
+ void UpdateOverlayLines();
+
+ /**
+ * Sets up all overlay lines for rendering according to the current full path and visibility segments. Splits the line into solid
+ * and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently set, this method
+ * does nothing.
+ */
+ void ConstructAllOverlayLines();
+
+ /**
+ * Sets up the overlay lines for rendering according to the full path and visibility segments at @p index. Splits the line into
+ * solid and dashed pieces (for the SoD). Should be called whenever the SoD of the path at @p index has changed.
+ */
+ void ConstructOverlayLines(size_t index);
+
+ /**
+ * Get the point on the footprint edge that's as close from "start" as possible.
+ */
+ void GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr cmpPosition, CmpPtr cmpFootprint) const;
+
+ /**
+ * Returns a list of indices of waypoints in the current path (m_Path[index]) where the LOS visibility changes, ordered from
+ * building/previous rally point to rally point. Used to construct the overlay line segments and track changes to the SoD.
+ */
+ void GetVisibilitySegments(std::vector& out, size_t index) const;
+
+ /**
+ * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily
+ * intended to reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which
+ * 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
+ * could just as well have gone in a straight line; that's why we have this, to make it look more natural.
+ *
+ * @p coords array of path coordinates to simplify
+ * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be joined into a
+ * single link. If this value is set to e.g. 1, then no reductions will be performed. A value of 3 means that
+ * at most 3 consecutive node links will be joined into a single link.
+ * @p floating whether to consider nodes who are under the water level as floating on top of the water
+ */
+ void ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks = 0, bool floating = true) const;
+
+ /**
+ * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring
+ * segments. You should not have to call this method directly.
+ */
+ static void MergeVisibilitySegments(std::vector& segments);
+
+ void RenderSubmit(SceneCollector& collector);
+};
+
+REGISTER_COMPONENT_TYPE(RallyPointRenderer)
+
+#endif // INCLUDED_CCMPRALLYPOINTRENDERER
Index: ps/trunk/source/simulation2/components/CCmpRallyPointRenderer.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpRallyPointRenderer.cpp
+++ ps/trunk/source/simulation2/components/CCmpRallyPointRenderer.cpp
@@ -16,468 +16,63 @@
*/
#include "precompiled.h"
-#include "ICmpRallyPointRenderer.h"
+#include "CCmpRallyPointRenderer.h"
-#include "simulation2/MessageTypes.h"
-#include "simulation2/components/ICmpFootprint.h"
-#include "simulation2/components/ICmpObstructionManager.h"
-#include "simulation2/components/ICmpOwnership.h"
-#include "simulation2/components/ICmpPathfinder.h"
-#include "simulation2/components/ICmpPlayer.h"
-#include "simulation2/components/ICmpPlayerManager.h"
-#include "simulation2/components/ICmpPosition.h"
-#include "simulation2/components/ICmpRangeManager.h"
-#include "simulation2/components/ICmpTerrain.h"
-#include "simulation2/components/ICmpVisual.h"
-#include "simulation2/components/ICmpWaterManager.h"
-#include "simulation2/helpers/Render.h"
-#include "simulation2/helpers/Geometry.h"
-#include "simulation2/system/Component.h"
-
-#include "graphics/Overlay.h"
-#include "graphics/TextureManager.h"
-#include "ps/CLogger.h"
-#include "ps/Profile.h"
-#include "renderer/Renderer.h"
-
-struct SVisibilitySegment
-{
- bool m_Visible;
- size_t m_StartIndex;
- size_t m_EndIndex; // inclusive
-
- SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex)
- : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex)
- {}
-
- bool operator==(const SVisibilitySegment& other) const
- {
- return (m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex);
- }
-
- bool operator!=(const SVisibilitySegment& other) const
- {
- return !(*this == other);
- }
-
- bool IsSinglePoint() const
- {
- return (m_StartIndex == m_EndIndex);
- }
-};
-
-class CCmpRallyPointRenderer : public ICmpRallyPointRenderer
-{
- // import some types for less verbosity
- typedef WaypointPath Path;
- typedef PathGoal Goal;
- typedef ICmpRangeManager::CLosQuerier CLosQuerier;
- typedef SOverlayTexturedLine::LineCapType LineCapType;
-
-public:
- static void ClassInit(CComponentManager& componentManager)
- {
- componentManager.SubscribeGloballyToMessageType(MT_PlayerColorChanged);
- componentManager.SubscribeToMessageType(MT_OwnershipChanged);
- componentManager.SubscribeToMessageType(MT_TurnStart);
- componentManager.SubscribeToMessageType(MT_Destroy);
- componentManager.SubscribeToMessageType(MT_PositionChanged);
- }
-
- DEFAULT_COMPONENT_ALLOCATOR(RallyPointRenderer)
-
-protected:
-
- /// Display position of the rally points. Note that this are merely the display positions; they not necessarily the same as the
- /// actual positions used in the simulation at any given time. In particular, we need this separate copy to support
- /// instantaneously rendering the rally point markers/lines when the user sets one in-game (instead of waiting until the
- /// network-synchronization code sets it on the RallyPoint component, which might take up to half a second).
- std::vector m_RallyPoints;
- /// Full path to the rally points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
- std::vector > m_Path;
- /// Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments.
- std::deque > m_VisibilitySegments;
-
- bool m_Displayed; ///< Should we render the rally points and the path lines? (set from JS when e.g. the unit is selected/deselected)
- bool m_SmoothPath; ///< Smooth the path before rendering?
-
- std::vector m_MarkerEntityIds; ///< Entity IDs of the rally point markers.
- size_t m_LastMarkerCount;
- player_id_t m_LastOwner; ///< Last seen owner of this entity (used to keep track of ownership changes).
- std::wstring m_MarkerTemplate; ///< Template name of the rally point markers.
-
- /// Marker connector line settings (loaded from XML)
- float m_LineThickness;
- CColor m_LineColor;
- CColor m_LineDashColor;
- LineCapType m_LineStartCapType;
- LineCapType m_LineEndCapType;
- std::wstring m_LineTexturePath;
- std::wstring m_LineTextureMaskPath;
- std::string m_LinePassabilityClass; ///< Pathfinder passability class to use for computing the (long-range) marker line path.
-
- CTexturePtr m_Texture;
- CTexturePtr m_TextureMask;
-
- /// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render
- /// dashes for segments that are inside the SoD.
- std::vector > m_TexturedOverlayLines;
-
- /// Draw little overlay circles to indicate where the exact path points are?
- bool m_EnableDebugNodeOverlay;
- std::vector > m_DebugNodeOverlays;
-
-public:
-
- static std::string GetSchema()
- {
- return
- "Displays a rally point marker where created units will gather when spawned"
- ""
- "special/rallypoint"
- "0.75"
- "round"
- "square"
- ""
- "default"
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- "0255"
- ""
- ""
- "0255"
- ""
- ""
- "0255"
- ""
- ""
- ""
- ""
- "flat"
- "round"
- "sharp"
- "square"
- ""
- ""
- ""
- ""
- "flat"
- "round"
- "sharp"
- "square"
- ""
- ""
- ""
- ""
- "";
- }
-
- virtual void Init(const CParamNode& paramNode);
-
- virtual void Deinit()
- {
- }
-
- virtual void Serialize(ISerializer& UNUSED(serialize))
- {
- // do NOT serialize anything; this is a rendering-only component, it does not and should not affect simulation state
- }
-
- virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
- {
- Init(paramNode);
- // The dependent components have not been deserialized, so the color is loaded on first SetDisplayed
- }
-
- virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
- {
- switch (msg.GetType())
- {
- case MT_PlayerColorChanged:
- {
- const CMessagePlayerColorChanged& msgData = static_cast (msg);
-
- CmpPtr cmpOwnership(GetEntityHandle());
- if (!cmpOwnership || msgData.player != cmpOwnership->GetOwner())
- break;
-
- UpdateLineColor();
- ConstructAllOverlayLines();
- }
- break;
- case MT_RenderSubmit:
- {
- PROFILE("RallyPoint::RenderSubmit");
- if (m_Displayed && IsSet())
- {
- const CMessageRenderSubmit& msgData = static_cast (msg);
- RenderSubmit(msgData.collector);
- }
- }
- break;
- case MT_OwnershipChanged:
- {
- const CMessageOwnershipChanged& msgData = static_cast (msg);
-
- // Ignore destroyed entities
- if (msgData.to == INVALID_PLAYER)
- break;
-
- // Required for both the initial and capturing players color
- UpdateLineColor();
-
- // Support capturing, even though RallyPoint is typically deleted then
- UpdateMarkers();
- ConstructAllOverlayLines();
- }
- break;
- case MT_TurnStart:
- {
- UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly
- }
- break;
- case MT_Destroy:
- {
- Reset();
- }
- break;
- case MT_PositionChanged:
- {
- // Unlikely to happen in-game, but can occur in atlas
- // Just recompute the path from the entity to the first rally point
- RecomputeRallyPointPath_wrapper(0);
- }
- break;
- }
- }
-
- /*
- * Must be called whenever m_Displayed or the size of m_RallyPoints change,
- * to determine whether we need to respond to render messages.
- */
- void UpdateMessageSubscriptions()
- {
- bool needRender = m_Displayed && IsSet();
- GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender);
- }
-
- virtual void AddPosition_wrapper(const CFixedVector2D& pos)
- {
- AddPosition(pos, false);
- }
-
- virtual void SetPosition(const CFixedVector2D& pos)
- {
- if (!(m_RallyPoints.size() == 1 && m_RallyPoints.front() == pos))
- {
- m_RallyPoints.clear();
- AddPosition(pos, true);
- // Don't need to UpdateMessageSubscriptions here since AddPosition already calls it
- }
- }
-
- virtual void UpdatePosition(u32 rallyPointId, const CFixedVector2D& pos)
- {
- if (rallyPointId >= m_RallyPoints.size())
- return;
-
- m_RallyPoints[rallyPointId] = pos;
-
- UpdateMarkers();
-
- // Compute a new path for the current, and if existing the next rally point
- RecomputeRallyPointPath_wrapper(rallyPointId);
- if (rallyPointId+1 < m_RallyPoints.size())
- RecomputeRallyPointPath_wrapper(rallyPointId+1);
- }
-
- virtual void SetDisplayed(bool displayed)
- {
- if (m_Displayed != displayed)
- {
- m_Displayed = displayed;
-
- // Set color after all dependent components are deserialized
- if (displayed && m_LineColor.r < 0)
- {
- UpdateLineColor();
- ConstructAllOverlayLines();
- }
-
- // move the markers out of oblivion and back into the real world, or vice-versa
- UpdateMarkers();
-
- // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method
- // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred
- // while this rally point was not being displayed.
- UpdateOverlayLines();
-
- UpdateMessageSubscriptions();
- }
- }
-
- virtual void Reset()
- {
- for (entity_id_t& componentId : m_MarkerEntityIds)
- {
- if (componentId != INVALID_ENTITY)
- {
- GetSimContext().GetComponentManager().DestroyComponentsSoon(componentId);
- componentId = INVALID_ENTITY;
- }
- }
-
- m_MarkerEntityIds.clear();
- m_LastOwner = INVALID_PLAYER;
- m_LastMarkerCount = 0;
- m_RallyPoints.clear();
- RecomputeAllRallyPointPaths();
- UpdateMessageSubscriptions();
- }
-
- /**
- * Returns true if at least one display rally point is set; i.e., if we have a point to render our marker/line at.
- */
- bool IsSet() const
- {
- return !m_RallyPoints.empty();
- }
-
- void UpdateColor()
- {
- UpdateLineColor();
- ConstructAllOverlayLines();
- }
-
-private:
-
- /**
- * Helper function for AddPosition_wrapper and SetPosition.
- */
- void AddPosition(CFixedVector2D pos, bool recompute)
- {
- m_RallyPoints.push_back(pos);
- UpdateMarkers();
-
- if (recompute)
- RecomputeAllRallyPointPaths();
- else
- RecomputeRallyPointPath_wrapper(m_RallyPoints.size()-1);
-
- UpdateMessageSubscriptions();
- }
-
- /**
- * Helper function to set the line color to its owner's color.
- */
- void UpdateLineColor();
-
- /**
- * Repositions the rally point markers; moves them outside of the world (ie. hides them), or positions them at the currently
- * set rally points. Also updates the actor's variation according to the entity's current owning player's civilization.
- *
- * Should be called whenever either the position of a rally point changes (including whether it is set or not), or the display
- * flag changes, or the ownership of the entity changes.
- */
- void UpdateMarkers();
-
- /**
- * 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
- * post-processing to make them prettier.
- *
- * Should be called whenever all rally points' position changes.
- */
- void RecomputeAllRallyPointPaths();
-
- /**
- * Recomputes the full path for m_Path[ @p index], and does all the necessary post-processing to make it prettier.
- *
- * Should be called whenever either the starting position or the rally point's position changes.
- */
- void RecomputeRallyPointPath_wrapper(size_t index);
-
- /**
- * Recomputes the full path from this entity/the previous rally point to the next rally point, and does all the necessary
- * post-processing to make it prettier. This doesn't check if we have a valid position or if a rally point is set.
- *
- * You shouldn't need to call this method directly.
- */
- void RecomputeRallyPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder);
-
- /**
- * Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments and overlay lines to
- * match if necessary. Does nothing if the rally point lines are not currently set to be displayed, or if no rally point is set.
- */
- void UpdateOverlayLines();
-
- /**
- * Sets up all overlay lines for rendering according to the current full path and visibility segments. Splits the line into solid
- * and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently set, this method
- * does nothing.
- */
- void ConstructAllOverlayLines();
-
- /**
- * Sets up the overlay lines for rendering according to the full path and visibility segments at @p index. Splits the line into
- * solid and dashed pieces (for the SoD). Should be called whenever the SoD of the path at @p index has changed.
- */
- void ConstructOverlayLines(size_t index);
-
- /**
- * Removes points from @p coords that are obstructed by the originating building's footprint, and links up the last point
- * nicely to the edge of the building's footprint. Only needed if the pathfinder can possibly return obstructed tile waypoints,
- * i.e. when pathfinding is started from an obstructed tile.
- */
- void FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint) const;
-
- /**
- * Get the point on the footprint edge that's as close from "start" as possible.
- */
- void GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr cmpPosition, CmpPtr cmpFootprint) const;
-
- /**
- * Returns a list of indices of waypoints in the current path (m_Path[index]) where the LOS visibility changes, ordered from
- * building/previous rally point to rally point. Used to construct the overlay line segments and track changes to the SoD.
- */
- void GetVisibilitySegments(std::deque& out, size_t index) const;
-
- /**
- * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily
- * intended to reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which
- * 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
- * could just as well have gone in a straight line; that's why we have this, to make it look more natural.
- *
- * @p coords array of path coordinates to simplify
- * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be joined into a
- * single link. If this value is set to e.g. 1, then no reductions will be performed. A value of 3 means that
- * at most 3 consecutive node links will be joined into a single link.
- * @p floating whether to consider nodes who are under the water level as floating on top of the water
- */
- void ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks = 0, bool floating = true) const;
-
- /**
- * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring
- * segments. You should not have to call this method directly.
- */
- static void MergeVisibilitySegments(std::deque& segments);
-
- void RenderSubmit(SceneCollector& collector);
-};
-
-REGISTER_COMPONENT_TYPE(RallyPointRenderer)
+std::string CCmpRallyPointRenderer::GetSchema()
+{
+ return
+ "Displays a rally point marker where created units will gather when spawned"
+ ""
+ "special/rallypoint"
+ "0.75"
+ "round"
+ "square"
+ ""
+ "default"
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ ""
+ ""
+ "flat"
+ "round"
+ "sharp"
+ "square"
+ ""
+ ""
+ ""
+ ""
+ "flat"
+ "round"
+ "sharp"
+ "square"
+ ""
+ ""
+ ""
+ ""
+ "";
+}
void CCmpRallyPointRenderer::Init(const CParamNode& paramNode)
{
@@ -489,7 +84,7 @@
UpdateLineColor();
// ---------------------------------------------------------------------------------------------
- // load some XML configuration data (schema guarantees that all these nodes are valid)
+ // Load some XML configuration data (schema guarantees that all these nodes are valid)
m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToString();
const CParamNode& lineDashColor = paramNode.GetChild("LineDashColor");
@@ -508,7 +103,7 @@
m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8();
// ---------------------------------------------------------------------------------------------
- // load some textures
+ // Load some textures
if (CRenderer::IsInitialised())
{
@@ -524,6 +119,97 @@
}
}
+void CCmpRallyPointRenderer::ClassInit(CComponentManager& componentManager)
+{
+ componentManager.SubscribeGloballyToMessageType(MT_PlayerColorChanged);
+ componentManager.SubscribeToMessageType(MT_OwnershipChanged);
+ componentManager.SubscribeToMessageType(MT_TurnStart);
+ componentManager.SubscribeToMessageType(MT_Destroy);
+ componentManager.SubscribeToMessageType(MT_PositionChanged);
+}
+
+void CCmpRallyPointRenderer::Deinit()
+{
+}
+
+void CCmpRallyPointRenderer::Serialize(ISerializer& UNUSED(serialize))
+{
+ // Do NOT serialize anything; this is a rendering-only component, it does not and should not affect simulation state
+}
+
+void CCmpRallyPointRenderer::Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
+{
+ Init(paramNode);
+ // The dependent components have not been deserialized, so the color is loaded on first SetDisplayed
+}
+
+void CCmpRallyPointRenderer::HandleMessage(const CMessage& msg, bool UNUSED(global))
+{
+ switch (msg.GetType())
+ {
+ case MT_PlayerColorChanged:
+ {
+ const CMessagePlayerColorChanged& msgData = static_cast (msg);
+
+ CmpPtr cmpOwnership(GetEntityHandle());
+ if (!cmpOwnership || msgData.player != cmpOwnership->GetOwner())
+ break;
+
+ UpdateLineColor();
+ ConstructAllOverlayLines();
+ }
+ break;
+ case MT_RenderSubmit:
+ {
+ PROFILE("RallyPoint::RenderSubmit");
+ if (m_Displayed && IsSet())
+ {
+ const CMessageRenderSubmit& msgData = static_cast (msg);
+ RenderSubmit(msgData.collector);
+ }
+ }
+ break;
+ case MT_OwnershipChanged:
+ {
+ const CMessageOwnershipChanged& msgData = static_cast (msg);
+
+ // Ignore destroyed entities
+ if (msgData.to == INVALID_PLAYER)
+ break;
+ Reset();
+ // Required for both the initial and capturing players color
+ UpdateLineColor();
+
+ // Support capturing, even though RallyPoint is typically deleted then
+ UpdateMarkers();
+ ConstructAllOverlayLines();
+ }
+ break;
+ case MT_TurnStart:
+ {
+ UpdateOverlayLines(); // Check for changes to the SoD and update the overlay lines accordingly
+ }
+ break;
+ case MT_Destroy:
+ {
+ Reset();
+ }
+ break;
+ case MT_PositionChanged:
+ {
+ // Unlikely to happen in-game, but can occur in atlas
+ // Just recompute the path from the entity to the first rally point
+ RecomputeRallyPointPath_wrapper(0);
+ }
+ break;
+ }
+}
+
+void CCmpRallyPointRenderer::UpdateMessageSubscriptions()
+{
+ GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, m_Displayed && IsSet());
+}
+
void CCmpRallyPointRenderer::UpdateMarkers()
{
player_id_t previousOwner = m_LastOwner;
@@ -534,10 +220,10 @@
if (m_MarkerEntityIds[i] == INVALID_ENTITY)
{
- // no marker exists yet, create one first
+ // No marker exists yet, create one first
CComponentManager& componentMgr = GetSimContext().GetComponentManager();
- // allocate a new entity for the marker
+ // Allocate a new entity for the marker
if (!m_MarkerTemplate.empty())
{
m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity();
@@ -546,7 +232,7 @@
}
}
- // the marker entity should be valid at this point, otherwise something went wrong trying to allocate it
+ // The marker entity should be valid at this point, otherwise something went wrong trying to allocate it
if (m_MarkerEntityIds[i] == INVALID_ENTITY)
LOGERROR("Failed to create rally point marker entity");
@@ -559,11 +245,11 @@
}
else
{
- markerCmpPosition->MoveOutOfWorld(); // hide it
+ markerCmpPosition->MoveOutOfWorld();
}
}
- // set rally point flag selection based on player civilization
+ // Set rally point flag selection based on player civilization
CmpPtr cmpOwnership(GetEntityHandle());
if (!cmpOwnership)
continue;
@@ -590,7 +276,6 @@
m_LastMarkerCount = m_RallyPoints.size() - 1;
}
-
void CCmpRallyPointRenderer::UpdateLineColor()
{
CmpPtr cmpOwnership(GetEntityHandle());
@@ -618,17 +303,17 @@
m_VisibilitySegments.clear();
m_TexturedOverlayLines.clear();
- //// ///////////////////////////////////////////////
if (m_EnableDebugNodeOverlay)
m_DebugNodeOverlays.clear();
- //// //////////////////////////////////////////////
+ // No use computing a path if the rally point isn't set
if (!IsSet())
- return; // no use computing a path if the rally point isn't set
+ return;
CmpPtr cmpPosition(GetEntityHandle());
+ // No point going on if this entity doesn't have a position or is outside of the world
if (!cmpPosition || !cmpPosition->IsInWorld())
- return; // no point going on if this entity doesn't have a position or is outside of the world
+ return;
CmpPtr cmpFootprint(GetEntityHandle());
CmpPtr cmpPathfinder(GetSystemEntity());
@@ -641,12 +326,14 @@
void CCmpRallyPointRenderer::RecomputeRallyPointPath_wrapper(size_t index)
{
+ // No use computing a path if the rally point isn't set
if (!IsSet())
- return; // no use computing a path if the rally point isn't set
+ return;
+ // No point going on if this entity doesn't have a position or is outside of the world
CmpPtr cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
- return; // no point going on if this entity doesn't have a position or is outside of the world
+ return;
CmpPtr cmpFootprint(GetEntityHandle());
CmpPtr cmpPathfinder(GetSystemEntity());
@@ -665,7 +352,7 @@
while (index >= m_VisibilitySegments.size())
{
- std::deque tmp;
+ std::vector tmp;
m_VisibilitySegments.push_back(tmp);
}
m_VisibilitySegments[index].clear();
@@ -673,11 +360,11 @@
// Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a
// list of waypoints (i.e. a Path) from the goal to the foundation/previous rally point, where each
// waypoint is centered at a tile. We'll have to do some post-processing on the path to get it smooth.
- Path path;
+ WaypointPath path;
std::vector& waypoints = path.m_Waypoints;
CFixedVector2D start(cmpPosition->GetPosition2D());
- Goal goal = { Goal::POINT, m_RallyPoints[index].X, m_RallyPoints[index].Y };
+ PathGoal goal = { PathGoal::POINT, m_RallyPoints[index].X, m_RallyPoints[index].Y };
if (index == 0)
GetClosestsEdgePointFrom(start,m_RallyPoints[index], cmpPosition, cmpFootprint);
@@ -697,7 +384,7 @@
}
else if (index == 0)
{
- // sometimes this ends up not being optimal if you asked for a long path, so improve.
+ // Sometimes this ends up not being optimal if you asked for a long path, so improve.
CFixedVector2D newend(waypoints[waypoints.size()-2].x,waypoints[waypoints.size()-2].z);
GetClosestsEdgePointFrom(newend,newend, cmpPosition, cmpFootprint);
waypoints.back().x = newend.X;
@@ -705,12 +392,12 @@
}
else
{
- // make sure we actually start at the rallypoint because the pathfinder moves us to a usable tile.
+ // Make sure we actually start at the rallypoint because the pathfinder moves us to a usable tile.
waypoints.back().x = m_RallyPoints[index-1].X;
waypoints.back().z = m_RallyPoints[index-1].Y;
}
- // pathfinder makes us go to the nearest passable cell which isn't always what we want
+ // Pathfinder makes us go to the nearest passable cell which isn't always what we want
waypoints[0].x = m_RallyPoints[index].X;
waypoints[0].z = m_RallyPoints[index].Y;
@@ -721,9 +408,6 @@
for (Waypoint& waypoint : waypoints)
m_Path[index].emplace_back(waypoint.x.ToFloat(), waypoint.z.ToFloat());
- // add the start position
- // m_Path[index].emplace_back(m_RallyPoints[index].X.ToFloat(), m_RallyPoints[index].Y.ToFloat());
-
// Post-processing
// Linearize the path;
@@ -734,10 +418,6 @@
for(size_t i = m_Path[index].size() - 2; i > 0; --i)
m_Path[index][i] = (m_Path[index][i] + m_Path[index][i-1]) / 2.0f;
- // 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
- //if (index == 0 && cmpFootprint)
- // FixFootprintWaypoints(m_Path[index], cmpPosition, cmpFootprint);
-
// Eliminate some consecutive waypoints that are visible from eachother. Reduce across a maximum distance of approx. 6 tiles
// (prevents segments that are too long to properly stick to the terrain)
ReduceSegmentsByVisibility(m_Path[index], 6);
@@ -754,7 +434,7 @@
{
// Create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the
// terrain so we can still see them after the interpolated points are added)
- for (CVector2D& point : m_Path[index])
+ for (const CVector2D& point : m_Path[index])
{
SOverlayLine overlayLine;
overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f);
@@ -768,12 +448,13 @@
// The number of points to interpolate goes hand in hand with the maximum amount of node links allowed to be joined together
// by the visibility reduction. The more node links that can be joined together, the more interpolated points you need to
// generate to be able to deal with local terrain height changes.
- SimRender::InterpolatePointsRNS(m_Path[index], false, 0, 4); // no offset, keep line at its exact path
+ // no offset, keep line at its exact path
+ SimRender::InterpolatePointsRNS(m_Path[index], false, 0, 4);
- // 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
+ // 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
GetVisibilitySegments(m_VisibilitySegments[index], index);
- // build overlay lines for the new path
+ // Build overlay lines for the new path
ConstructOverlayLines(index);
}
@@ -800,19 +481,18 @@
if (m_Path[index].size() < 2)
return;
- LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it)
+ SOverlayTexturedLine::LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it)
- for (std::deque::const_iterator it = m_VisibilitySegments[index].begin(); it != m_VisibilitySegments[index].end(); ++it)
- {
- const SVisibilitySegment& segment = (*it);
+ for(const SVisibilitySegment& segment : m_VisibilitySegments[index])
+ {
if (segment.m_Visible)
{
- // does this segment border on the building or rally point flag on either side?
+ // Does this segment border on the building or rally point flag on either side?
bool bordersBuilding = (segment.m_EndIndex == m_Path[index].size() - 1);
bool bordersFlag = (segment.m_StartIndex == 0);
- // construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx
+ // Construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx
SOverlayTexturedLine overlayLine;
overlayLine.m_Thickness = m_LineThickness;
overlayLine.m_SimContext = &GetSimContext();
@@ -820,16 +500,17 @@
overlayLine.m_TextureMask = m_TextureMask;
overlayLine.m_Color = m_LineColor;
overlayLine.m_Closed = false;
- // we should take care to only use m_LineXCap for the actual end points at the building and the rally point; any intermediate
+ // We should take care to only use m_LineXCap for the actual end points at the building and the rally point; any intermediate
// end points (i.e., that border a dashed segment) should have the dashed cap
// the path line is actually in reverse order as well, so let's swap out the start and end caps
overlayLine.m_StartCapType = (bordersFlag ? m_LineEndCapType : dashesLineCapType);
overlayLine.m_EndCapType = (bordersBuilding ? m_LineStartCapType : dashesLineCapType);
overlayLine.m_AlwaysVisible = true;
- // push overlay line coordinates
+ // Push overlay line coordinates
ENSURE(segment.m_EndIndex > segment.m_StartIndex);
- for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j) // end index is inclusive here
+ // End index is inclusive here
+ for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j)
{
overlayLine.m_Coords.push_back(m_Path[index][j].X);
overlayLine.m_Coords.push_back(m_Path[index][j].Y);
@@ -839,7 +520,7 @@
}
else
{
- // construct dashed line from startPointIdx to endPointIdx; add textured overlay lines for it to the render list
+ // Construct dashed line from startPointIdx to endPointIdx; add textured overlay lines for it to the render list
std::vector straightLine;
straightLine.push_back(m_Path[index][segment.m_StartIndex]);
straightLine.push_back(m_Path[index][segment.m_EndIndex]);
@@ -853,9 +534,11 @@
float dashSize = maxDashSize;
float clearSize = maxClearSize;
- float pairDashRatio = (dashSize / (dashSize + clearSize)); // ratio of the dash's length to a (dash + clear) pair's length
+ // Ratio of the dash's length to a (dash + clear) pair's length
+ float pairDashRatio = dashSize / (dashSize + clearSize);
- float distance = (m_Path[index][segment.m_StartIndex] - m_Path[index][segment.m_EndIndex]).Length(); // straight-line distance between the points
+ // Straight-line distance between the points
+ float distance = (m_Path[index][segment.m_StartIndex] - m_Path[index][segment.m_EndIndex]).Length();
// See how many pairs (dash + clear) of unmodified size can fit into the distance. Then check the remaining distance; if it's not exactly
// a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is.
@@ -868,10 +551,11 @@
// (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's
// length and the clear's length.
- // we always want to have at least one dash/clear pair (i.e., "|===| |===|"); also, we need to avoid division by zero below.
+ // We always want to have at least one dash/clear pair (i.e., "|===| |===|"); also, we need to avoid division by zero below.
numFitUnmodified = std::max(1, numFitUnmodified);
- float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified; // can be either positive or negative
+ // Can be either positive or negative
+ float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified;
dashSize += pairDashRatio * pairwiseLengthDifference;
clearSize += (1 - pairDashRatio) * pairwiseLengthDifference;
@@ -880,7 +564,7 @@
SDashedLine dashedLine;
SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize);
- // build overlay lines for dashes
+ // Build overlay lines for dashes
size_t numDashes = dashedLine.m_StartIndices.size();
for (size_t i=0; i < numDashes; i++)
{
@@ -940,11 +624,11 @@
if (!m_Displayed || !IsSet())
return;
- // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones
- std::deque > newVisibilitySegments;
+ // See if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones
+ std::vector > newVisibilitySegments;
for (size_t i = 0; i < m_Path.size(); ++i)
{
- std::deque tmp;
+ std::vector tmp;
newVisibilitySegments.push_back(tmp);
GetVisibilitySegments(newVisibilitySegments[i], i);
}
@@ -952,7 +636,8 @@
// Check if the full path changed, then reconstruct all overlay lines, otherwise check if a segment changed and update that.
if (m_VisibilitySegments.size() != newVisibilitySegments.size())
{
- m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time
+ // Save the new visibility segments to compare against next time
+ m_VisibilitySegments = newVisibilitySegments;
ConstructAllOverlayLines();
}
else
@@ -963,7 +648,8 @@
{
// The visibility segments have changed, reconstruct the overlay lines to match. NOTE: The path itself doesn't
// change, only the overlay lines we construct from it.
- m_VisibilitySegments[i] = newVisibilitySegments[i]; // save the new visibility segments to compare against next time
+ // Save the new visibility segments to compare against next time
+ m_VisibilitySegments[i] = newVisibilitySegments[i];
ConstructOverlayLines(i);
}
}
@@ -975,30 +661,32 @@
ENSURE(cmpPosition);
ENSURE(cmpFootprint);
- // grab the shape and dimensions of the footprint
+ // Grab the shape and dimensions of the footprint
entity_pos_t footprintSize0, footprintSize1, footprintHeight;
ICmpFootprint::EShape footprintShape;
cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
- // grab the center of the footprint
+ // Grab the center of the footprint
CFixedVector2D center = cmpPosition->GetPosition2D();
switch (footprintShape)
{
case ICmpFootprint::SQUARE:
{
- // in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
-
- // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
+ // In this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
+ // The building's footprint could be rotated any which way, so let's get the rotation around the Y axis
// and the rotated unit vectors in the X/Z plane of the shape's footprint
// (the Footprint itself holds only the outline, the Position holds the orientation)
- fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
+ // Sinus and cosinus of the Y axis rotation angle (aka the yaw)
+ fixed s, c;
fixed a = cmpPosition->GetRotation().Y;
sincos_approx(a, s, c);
- CFixedVector2D u(c, -s); // unit vector along the rotated X axis
- CFixedVector2D v(s, c); // unit vector along the rotated Z axis
- CFixedVector2D halfSize(footprintSize0/2, footprintSize1/2);
+ // Unit vector along the rotated X axis
+ CFixedVector2D u(c, -s);
+ // Unit vector along the rotated Z axis
+ CFixedVector2D v(s, c);
+ CFixedVector2D halfSize(footprintSize0 / 2, footprintSize1 / 2);
CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(start - center, u, v, halfSize);
result = center + footprintEdgePoint;
@@ -1006,7 +694,7 @@
}
case ICmpFootprint::CIRCLE:
{
- // in this case, both footprintSize0 and 1 indicate the circle's radius
+ // In this case, both footprintSize0 and 1 indicate the circle's radius
// Transform target to the point nearest on the edge.
CFixedVector2D centerVec2D(center.X, center.Y);
CFixedVector2D centerToLast(start - centerVec2D);
@@ -1017,91 +705,6 @@
}
}
-void CCmpRallyPointRenderer::FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint) const
-{
- ENSURE(cmpPosition);
- ENSURE(cmpFootprint);
-
- // -----------------------------------------------------------------------------------------------------
- // TODO: nasty fixed/float conversions everywhere
-
- // grab the shape and dimensions of the footprint
- entity_pos_t footprintSize0, footprintSize1, footprintHeight;
- ICmpFootprint::EShape footprintShape;
- cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
-
- // grab the center of the footprint
- CFixedVector2D center = cmpPosition->GetPosition2D();
-
- // -----------------------------------------------------------------------------------------------------
-
- switch (footprintShape)
- {
- case ICmpFootprint::SQUARE:
- {
- // in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
-
- // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
- // and the rotated unit vectors in the X/Z plane of the shape's footprint
- // (the Footprint itself holds only the outline, the Position holds the orientation)
-
- fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
- fixed a = cmpPosition->GetRotation().Y;
- sincos_approx(a, s, c);
- CFixedVector2D u(c, -s); // unit vector along the rotated X axis
- CFixedVector2D v(s, c); // unit vector along the rotated Z axis
- CFixedVector2D halfSize(footprintSize0/2, footprintSize1/2);
-
- // starting from the start position, check if any points are within the footprint of the building
- // (this is possible if the pathfinder was started from a point located within the footprint)
- for(int i = (int)(coords.size() - 1); i >= 0; i--)
- {
- const CVector2D& wp = coords[i];
- if (Geometry::PointIsInSquare(CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center, u, v, halfSize))
- {
- coords.erase(coords.begin() + i);
- }
- else
- {
- break; // point no longer inside footprint, from this point on neither will any of the following be
- }
- }
-
- // 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
- CFixedVector2D lastWaypoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y));
- CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(lastWaypoint - center, u, v, halfSize); // relative to the shape origin (center)
- CVector2D footprintEdge((center.X + footprintEdgePoint.X).ToFloat(), (center.Y + footprintEdgePoint.Y).ToFloat());
- coords.push_back(footprintEdge);
-
- }
- break;
- case ICmpFootprint::CIRCLE:
- {
- // in this case, both footprintSize0 and 1 indicate the circle's radius
-
- for(int i = (int)(coords.size() - 1); i >= 0; i--)
- {
- const CVector2D& wp = coords[i];
- fixed pointDistance = (CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center).Length();
- if (pointDistance <= footprintSize0)
- {
- coords.erase(coords.begin() + i);
- }
- else
- {
- break; // point no longer inside footprint, from this point on neither will any of the following be
- }
- }
-
- // add a point right on the edge of the footprint so that it links up nicely with the rest of the path
- CVector2D centerVec2D(center.X.ToFloat(), center.Y.ToFloat());
- CVector2D centerToLast(coords.back() - centerVec2D);
- coords.push_back(centerVec2D + (centerToLast.Normalized() * footprintSize0.ToFloat()));
- }
- break;
- }
-}
-
void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks, bool floating) const
{
CmpPtr cmpPathFinder(GetSystemEntity());
@@ -1121,7 +724,8 @@
entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness);
pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass);
- newCoords.push_back(coords[0]); // save the first base node
+ // Save the first base node
+ newCoords.push_back(coords[0]);
size_t baseNodeIdx = 0;
size_t curNodeIdx = 1;
@@ -1130,7 +734,7 @@
entity_pos_t baseNodeX;
entity_pos_t baseNodeZ;
- // set initial base node coords
+ // Set initial base node coords
baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
@@ -1139,7 +743,8 @@
while (curNodeIdx < coords.size())
{
- ENSURE(curNodeIdx > baseNodeIdx); // this needs to be true at all times, otherwise we're checking visibility between a point and itself
+ // This needs to be true at all times, otherwise we're checking visibility between a point and itself.
+ ENSURE(curNodeIdx > baseNodeIdx);
entity_pos_t curNodeX = fixed::FromFloat(coords[curNodeIdx].X);
entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y);
@@ -1147,22 +752,23 @@
if (floating)
curNodeY = std::max(curNodeY, cmpWaterManager->GetExactWaterLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y));
- // find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!)
+ // Find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!)
bool curNodeVisible = cmpPathFinder->CheckMovement(obstructionFilter, baseNodeX, baseNodeZ, curNodeX, curNodeZ, lineRadius, passabilityClass);
- // since height differences are ignored by CheckMovement, let's call two points visible from one another only if they're at
+ // Since height differences are ignored by CheckMovement, let's call two points visible from one another only if they're at
// roughly the same terrain elevation
- curNodeVisible = curNodeVisible && (fabsf(curNodeY - baseNodeY) < 3.f); // TODO: this could probably use some tuning
+ // TODO: this could probably use some tuning
+ curNodeVisible = curNodeVisible && (fabsf(curNodeY - baseNodeY) < 3.f);
if (maxSegmentLinks > 0)
- // max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx)
+ // Max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx)
curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks);
if (!curNodeVisible)
{
- // current node is not visible from the base node, so the previous one was the last visible point from baseNode and should
+ // Current node is not visible from the base node, so the previous one was the last visible point from baseNode and should
// hence become the new base node for further iterations.
- // if curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then
+ // If curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then
// we should take care not to stay stuck at the current base node
if (curNodeIdx > baseNodeIdx + 1)
{
@@ -1172,12 +778,14 @@
{
// curNodeIdx == baseNodeIdx + 1
baseNodeIdx = curNodeIdx;
- curNodeIdx++; // move the next candidate node one forward so that we don't test a point against itself in the next iteration
+ // Move the next candidate node one forward so that we don't test a point against itself in the next iteration
+ ++curNodeIdx;
}
- newCoords.push_back(coords[baseNodeIdx]); // add new base node to output list
+ // Add new base node to output list
+ newCoords.push_back(coords[baseNodeIdx]);
- // update base node coordinates
+ // Update base node coordinates
baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
@@ -1185,10 +793,10 @@
baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
}
- curNodeIdx++;
+ ++curNodeIdx;
}
- // 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
+ // 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
// base node, then the loop above just ends and no endpoint is ever added to the list.
ENSURE(curNodeIdx == coords.size());
newCoords.push_back(coords[coords.size() - 1]);
@@ -1196,7 +804,7 @@
coords.swap(newCoords);
}
-void CCmpRallyPointRenderer::GetVisibilitySegments(std::deque& out, size_t index) const
+void CCmpRallyPointRenderer::GetVisibilitySegments(std::vector& out, size_t index) const
{
out.clear();
@@ -1205,50 +813,51 @@
CmpPtr cmpRangeMgr(GetSystemEntity());
- player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
- CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
+ player_id_t currentPlayer = static_cast(GetSimContext().GetCurrentDisplayedPlayer());
+ ICmpRangeManager::CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
- // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
+ // Go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
// a new one at the next point.
+ const float terrainSize = static_cast(TERRAIN_TILE_SIZE);
bool lastVisible = losQuerier.IsExplored(
- (fixed::FromFloat(m_Path[index][0].X) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest(),
- (fixed::FromFloat(m_Path[index][0].Y) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest()
+ (fixed::FromFloat(m_Path[index][0].X / terrainSize)).ToInt_RoundToNearest(),
+ (fixed::FromFloat(m_Path[index][0].Y / terrainSize)).ToInt_RoundToNearest()
);
- size_t curSegmentStartIndex = 0; // starting node index of the current segment
+ // Starting node index of the current segment
+ size_t curSegmentStartIndex = 0;
for (size_t k = 1; k < m_Path[index].size(); ++k)
{
- // grab tile indices for this coord
- int i = (fixed::FromFloat(m_Path[index][k].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
- int j = (fixed::FromFloat(m_Path[index][k].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
+ // Grab tile indices for this coord
+ int i = (fixed::FromFloat(m_Path[index][k].X / terrainSize)).ToInt_RoundToNearest();
+ int j = (fixed::FromFloat(m_Path[index][k].Y / terrainSize)).ToInt_RoundToNearest();
bool nodeVisible = losQuerier.IsExplored(i, j);
if (nodeVisible != lastVisible)
{
- // visibility changed; write out the segment that was just completed and get ready for the new one
+ // Visibility changed; write out the segment that was just completed and get ready for the new one
out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1));
- //curSegmentStartIndex = k; // new segment starts here
curSegmentStartIndex = k - 1;
lastVisible = nodeVisible;
}
}
- // terminate the last segment
+ // Terminate the last segment
out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1));
MergeVisibilitySegments(out);
}
-void CCmpRallyPointRenderer::MergeVisibilitySegments(std::deque& segments)
+void CCmpRallyPointRenderer::MergeVisibilitySegments(std::vector& segments)
{
// Scan for single-point segments; if they are inbetween two other segments, delete them and merge the surrounding segments.
// If they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't
// themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first).
- // first scan for absorptions of single-point surrounded segments (i.e. excluding edge segments)
+ // First scan for absorptions of single-point surrounded segments (i.e. excluding edge segments)
size_t numSegments = segments.size();
// WARNING: FOR LOOP TRICKERY AHEAD!
@@ -1257,14 +866,18 @@
SVisibilitySegment& segment = segments[i];
if (segment.IsSinglePoint())
{
- // since the segments' visibility alternates, the surrounding ones should have the same visibility
+ // Since the segments' visibility alternates, the surrounding ones should have the same visibility
ENSURE(segments[i-1].m_Visible == segments[i+1].m_Visible);
- segments[i-1].m_EndIndex = segments[i+1].m_EndIndex; // make previous segment span all the way across to the next
- segments.erase(segments.begin() + i); // erase this segment ...
- segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i])
- numSegments -= 2; // we removed 2 segments, so update the loop condition
- // in the next iteration, i should still point to the segment right after the one that got expanded, which is now
+ // Make previous segment span all the way across to the next
+ segments[i-1].m_EndIndex = segments[i+1].m_EndIndex;
+ // Erase this segment
+ segments.erase(segments.begin() + i);
+ // And the next (we removed [i], so [i+1] is now at position [i])
+ segments.erase(segments.begin() + i);
+ // We removed 2 segments, so update the loop condition
+ numSegments -= 2;
+ // In the next iteration, i should still point to the segment right after the one that got expanded, which is now
// at position i; so don't increment i here
}
else
@@ -1275,12 +888,13 @@
ENSURE(numSegments == segments.size());
- // check to see if the first segment needs to be merged with its neighbour
+ // Check to see if the first segment needs to be merged with its neighbour
if (segments.size() >= 2 && segments[0].IsSinglePoint())
{
int firstSegmentStartIndex = segments.front().m_StartIndex;
ENSURE(firstSegmentStartIndex == 0);
- ENSURE(!segments[1].IsSinglePoint()); // at this point, the second segment should never be a single-point segment
+ // At this point, the second segment should never be a single-point segment
+ ENSURE(!segments[1].IsSinglePoint());
segments.erase(segments.begin());
segments.front().m_StartIndex = firstSegmentStartIndex;
@@ -1290,14 +904,15 @@
if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint())
{
int lastSegmentEndIndex = segments.back().m_EndIndex;
- ENSURE(!segments[segments.size()-2].IsSinglePoint()); // at this point, the second-to-last segment should never be a single-point segment
+ // At this point, the second-to-last segment should never be a single-point segment
+ ENSURE(!segments[segments.size()-2].IsSinglePoint());
- segments.erase(segments.end());
+ segments.pop_back();
segments.back().m_EndIndex = lastSegmentEndIndex;
}
// --------------------------------------------------------------------------------------------------------
- // at this point, every segment should have at least 2 points
+ // At this point, every segment should have at least 2 points
for (size_t i = 0; i < segments.size(); ++i)
{
ENSURE(!segments[i].IsSinglePoint());
@@ -1307,20 +922,114 @@
void CCmpRallyPointRenderer::RenderSubmit(SceneCollector& collector)
{
- // we only get here if the rally point is set and should be displayed
- for (size_t i = 0; i < m_TexturedOverlayLines.size(); ++i)
+ // We only get here if the rally point is set and should be displayed
+ for(std::vector& row : m_TexturedOverlayLines)
+ for (SOverlayTexturedLine& col : row)
+ if (!col.m_Coords.empty())
+ collector.Submit(&col);
+
+ if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty())
+ {
+ for (std::vector& row : m_DebugNodeOverlays)
+ for (SOverlayLine& col : row)
+ if (!col.m_Coords.empty())
+ collector.Submit(&col);
+ }
+}
+
+void CCmpRallyPointRenderer::AddPosition_wrapper(const CFixedVector2D& pos)
+{
+ AddPosition(pos, false);
+}
+
+void CCmpRallyPointRenderer::SetPosition(const CFixedVector2D& pos)
+{
+ if (!(m_RallyPoints.size() == 1 && m_RallyPoints.front() == pos))
+ {
+ m_RallyPoints.clear();
+ AddPosition(pos, true);
+ // Don't need to UpdateMessageSubscriptions here since AddPosition already calls it
+ }
+}
+
+void CCmpRallyPointRenderer::UpdatePosition(u32 rallyPointId, const CFixedVector2D& pos)
+{
+ if (rallyPointId >= m_RallyPoints.size())
+ return;
+
+ m_RallyPoints[rallyPointId] = pos;
+
+ UpdateMarkers();
+
+ // Compute a new path for the current, and if existing the next rally point
+ RecomputeRallyPointPath_wrapper(rallyPointId);
+ if (rallyPointId + 1 < m_RallyPoints.size())
+ RecomputeRallyPointPath_wrapper(rallyPointId + 1);
+}
+
+void CCmpRallyPointRenderer::SetDisplayed(bool displayed)
+{
+ if (m_Displayed != displayed)
{
- for (size_t j = 0; j < m_TexturedOverlayLines[i].size(); ++j)
+ m_Displayed = displayed;
+
+ // Set color after all dependent components are deserialized
+ if (displayed && m_LineColor.r < 0)
{
- if (!m_TexturedOverlayLines[i][j].m_Coords.empty())
- collector.Submit(&m_TexturedOverlayLines[i][j]);
+ UpdateLineColor();
+ ConstructAllOverlayLines();
}
+
+ // Move the markers out of oblivion and back into the real world, or vice-versa
+ UpdateMarkers();
+
+ // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method
+ // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred
+ // while this rally point was not being displayed.
+ UpdateOverlayLines();
+
+ UpdateMessageSubscriptions();
}
+}
- if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty())
+void CCmpRallyPointRenderer::Reset()
+{
+ for (entity_id_t& componentId : m_MarkerEntityIds)
{
- for (size_t i = 0; i < m_DebugNodeOverlays.size(); ++i)
- for (size_t j = 0; j < m_DebugNodeOverlays[i].size(); ++j)
- collector.Submit(&m_DebugNodeOverlays[i][j]);
+ if (componentId != INVALID_ENTITY)
+ {
+ GetSimContext().GetComponentManager().DestroyComponentsSoon(componentId);
+ componentId = INVALID_ENTITY;
+ }
}
+ m_RallyPoints.clear();
+ m_MarkerEntityIds.clear();
+ m_LastOwner = INVALID_PLAYER;
+ m_LastMarkerCount = 0;
+ RecomputeAllRallyPointPaths();
+ UpdateMessageSubscriptions();
+}
+
+void CCmpRallyPointRenderer::UpdateColor()
+{
+ UpdateLineColor();
+ ConstructAllOverlayLines();
+}
+
+void CCmpRallyPointRenderer::AddPosition(CFixedVector2D pos, bool recompute)
+{
+ m_RallyPoints.push_back(pos);
+ UpdateMarkers();
+
+ if (recompute)
+ RecomputeAllRallyPointPaths();
+ else
+ RecomputeRallyPointPath_wrapper(m_RallyPoints.size() - 1);
+
+ UpdateMessageSubscriptions();
+}
+
+bool CCmpRallyPointRenderer::IsSet() const
+{
+ return !m_RallyPoints.empty();
}