Changeset View
Standalone View
source/simulation2/components/CCmpRallyPointRenderer.cpp
/* Copyright (C) 2019 Wildfire Games. | /* Copyright (C) 2019 Wildfire Games. | ||||
vladislavbelov: Bump year. | |||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 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 | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
* GNU General Public License for more details. | * GNU General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
#include "precompiled.h" | #include "precompiled.h" | ||||
#include "ICmpRallyPointRenderer.h" | #include "CCmpRallyPointRenderer.h" | ||||
#include "simulation2/MessageTypes.h" | std::string CCmpRallyPointRenderer::GetSchema() | ||||
#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<CFixedVector2D> m_RallyPoints; | |||||
/// Full path to the rally points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging. | |||||
std::vector<std::vector<CVector2D> > m_Path; | |||||
/// Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments. | |||||
std::deque<std::deque<SVisibilitySegment> > 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<entity_id_t> 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<std::vector<SOverlayTexturedLine> > m_TexturedOverlayLines; | |||||
/// Draw little overlay circles to indicate where the exact path points are? | |||||
bool m_EnableDebugNodeOverlay; | |||||
std::vector<std::vector<SOverlayLine> > m_DebugNodeOverlays; | |||||
public: | |||||
static std::string GetSchema() | |||||
{ | { | ||||
return | return | ||||
"<a:help>Displays a rally point marker where created units will gather when spawned</a:help>" | "<a:help>Displays a rally point marker where created units will gather when spawned</a:help>" | ||||
"<a:example>" | "<a:example>" | ||||
"<MarkerTemplate>special/rallypoint</MarkerTemplate>" | "<MarkerTemplate>special/rallypoint</MarkerTemplate>" | ||||
"<LineThickness>0.75</LineThickness>" | "<LineThickness>0.75</LineThickness>" | ||||
"<LineStartCap>round</LineStartCap>" | "<LineStartCap>round</LineStartCap>" | ||||
"<LineEndCap>square</LineEndCap>" | "<LineEndCap>square</LineEndCap>" | ||||
"<LineDashColor r='158' g='11' b='15'></LineDashColor>" | "<LineDashColor r='158' g='11' b='15'></LineDashColor>" | ||||
"<LinePassabilityClass>default</LinePassabilityClass>" | "<LinePassabilityClass>default</LinePassabilityClass>" | ||||
"</a:example>" | "</a:example>" | ||||
"<element name='MarkerTemplate' a:help='Template name for the rally point marker entity (typically a waypoint flag actor)'>" | "<element name='MarkerTemplate' a:help='Template name for the rally point marker entity (typically a waypoint flag actor)'>" | ||||
"<text/>" | "<text/>" | ||||
"</element>" | "</element>" | ||||
"<element name='LineTexture' a:help='Texture file to use for the rally point line'>" | "<element name='LineTexture' a:help='Texture file to use for the rally point line'>" | ||||
"<text />" | "<text />" | ||||
"</element>" | "</element>" | ||||
"<element name='LineTextureMask' a:help='Texture mask to indicate where overlay colors are to be applied (see LineColor and LineDashColor)'>" | "<element name='LineTextureMask' a:help='Texture mask to indicate where overlay colors are to be applied (see LineColor and LineDashColor)'>" | ||||
"<text />" | "<text />" | ||||
"</element>" | "</element>" | ||||
"<element name='LineThickness' a:help='Thickness of the marker line connecting the entity to the rally point marker'>" | "<element name='LineThickness' a:help='Thickness of the marker line connecting the entity to the rally point marker'>" | ||||
"<data type='decimal'/>" | "<data type='decimal'/>" | ||||
"</element>" | "</element>" | ||||
"<element name='LineDashColor'>" | "<element name='LineDashColor'>" | ||||
"<attribute name='r'>" | "<attribute name='r'>" | ||||
"<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" | "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" | ||||
"</attribute>" | "</attribute>" | ||||
"<attribute name='g'>" | "<attribute name='g'>" | ||||
"<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" | "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" | ||||
"</attribute>" | "</attribute>" | ||||
"<attribute name='b'>" | "<attribute name='b'>" | ||||
"<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" | "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>" | ||||
"</attribute>" | "</attribute>" | ||||
"</element>" | "</element>" | ||||
"<element name='LineStartCap'>" | "<element name='LineStartCap'>" | ||||
"<choice>" | "<choice>" | ||||
"<value a:help='Abrupt line ending; line endings are not closed'>flat</value>" | "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>" | ||||
"<value a:help='Semi-circular line end cap'>round</value>" | "<value a:help='Semi-circular line end cap'>round</value>" | ||||
"<value a:help='Sharp, pointy line end cap'>sharp</value>" | "<value a:help='Sharp, pointy line end cap'>sharp</value>" | ||||
"<value a:help='Square line end cap'>square</value>" | "<value a:help='Square line end cap'>square</value>" | ||||
"</choice>" | "</choice>" | ||||
"</element>" | "</element>" | ||||
"<element name='LineEndCap'>" | "<element name='LineEndCap'>" | ||||
"<choice>" | "<choice>" | ||||
"<value a:help='Abrupt line ending; line endings are not closed'>flat</value>" | "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>" | ||||
"<value a:help='Semi-circular line end cap'>round</value>" | "<value a:help='Semi-circular line end cap'>round</value>" | ||||
"<value a:help='Sharp, pointy line end cap'>sharp</value>" | "<value a:help='Sharp, pointy line end cap'>sharp</value>" | ||||
"<value a:help='Square line end cap'>square</value>" | "<value a:help='Square line end cap'>square</value>" | ||||
"</choice>" | "</choice>" | ||||
"</element>" | "</element>" | ||||
"<element name='LinePassabilityClass' a:help='The pathfinder passability class to use for computing the rally point marker line path'>" | "<element name='LinePassabilityClass' a:help='The pathfinder passability class to use for computing the rally point marker line path'>" | ||||
"<text />" | "<text />" | ||||
"</element>"; | "</element>"; | ||||
} | } | ||||
virtual void Init(const CParamNode& paramNode); | void CCmpRallyPointRenderer::Init(const CParamNode& paramNode) | ||||
{ | |||||
m_Displayed = false; | |||||
m_SmoothPath = true; | |||||
m_LastOwner = INVALID_PLAYER; | |||||
m_LastMarkerCount = 0; | |||||
m_EnableDebugNodeOverlay = false; | |||||
UpdateLineColor(); | |||||
// --------------------------------------------------------------------------------------------- | |||||
// 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"); | |||||
m_LineDashColor = CColor( | |||||
lineDashColor.GetChild("@r").ToInt()/255.f, | |||||
lineDashColor.GetChild("@g").ToInt()/255.f, | |||||
lineDashColor.GetChild("@b").ToInt()/255.f, | |||||
1.f | |||||
); | |||||
m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat(); | |||||
m_LineTexturePath = paramNode.GetChild("LineTexture").ToString(); | |||||
m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString(); | |||||
m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString()); | |||||
m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString()); | |||||
m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8(); | |||||
// --------------------------------------------------------------------------------------------- | |||||
// Load some textures | |||||
if (CRenderer::IsInitialised()) | |||||
{ | |||||
CTextureProperties texturePropsBase(m_LineTexturePath); | |||||
texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); | |||||
texturePropsBase.SetMaxAnisotropy(4.f); | |||||
m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); | |||||
CTextureProperties texturePropsMask(m_LineTextureMaskPath); | |||||
texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); | |||||
texturePropsMask.SetMaxAnisotropy(4.f); | |||||
m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); | |||||
} | |||||
} | |||||
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); | |||||
} | |||||
virtual void Deinit() | void CCmpRallyPointRenderer::Deinit() | ||||
{ | { | ||||
} | } | ||||
virtual void Serialize(ISerializer& UNUSED(serialize)) | 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 | // 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)) | void CCmpRallyPointRenderer::Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) | ||||
{ | { | ||||
Init(paramNode); | Init(paramNode); | ||||
// The dependent components have not been deserialized, so the color is loaded on first SetDisplayed | // The dependent components have not been deserialized, so the color is loaded on first SetDisplayed | ||||
} | } | ||||
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) | void CCmpRallyPointRenderer::HandleMessage(const CMessage& msg, bool UNUSED(global)) | ||||
{ | { | ||||
switch (msg.GetType()) | switch (msg.GetType()) | ||||
{ | { | ||||
case MT_PlayerColorChanged: | case MT_PlayerColorChanged: | ||||
{ | { | ||||
const CMessagePlayerColorChanged& msgData = static_cast<const CMessagePlayerColorChanged&> (msg); | const CMessagePlayerColorChanged& msgData = static_cast<const CMessagePlayerColorChanged&> (msg); | ||||
CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle()); | CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle()); | ||||
if (!cmpOwnership || msgData.player != cmpOwnership->GetOwner()) | if (!cmpOwnership || msgData.player != cmpOwnership->GetOwner()) | ||||
break; | break; | ||||
UpdateLineColor(); | UpdateLineColor(); | ||||
ConstructAllOverlayLines(); | ConstructAllOverlayLines(); | ||||
} | } | ||||
break; | break; | ||||
case MT_RenderSubmit: | case MT_RenderSubmit: | ||||
{ | { | ||||
PROFILE("RallyPoint::RenderSubmit"); | PROFILE("RallyPoint::RenderSubmit"); | ||||
if (m_Displayed && IsSet()) | if (m_Displayed && IsSet()) | ||||
{ | { | ||||
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | ||||
RenderSubmit(msgData.collector); | RenderSubmit(msgData.collector); | ||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case MT_OwnershipChanged: | case MT_OwnershipChanged: | ||||
{ | { | ||||
const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg); | const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg); | ||||
// Ignore destroyed entities | // Ignore destroyed entities | ||||
if (msgData.to == INVALID_PLAYER) | if (msgData.to == INVALID_PLAYER) | ||||
break; | break; | ||||
Reset(); | |||||
// Required for both the initial and capturing players color | // Required for both the initial and capturing players color | ||||
UpdateLineColor(); | UpdateLineColor(); | ||||
// Support capturing, even though RallyPoint is typically deleted then | // Support capturing, even though RallyPoint is typically deleted then | ||||
UpdateMarkers(); | UpdateMarkers(); | ||||
ConstructAllOverlayLines(); | ConstructAllOverlayLines(); | ||||
} | } | ||||
break; | break; | ||||
case MT_TurnStart: | case MT_TurnStart: | ||||
{ | { | ||||
UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly | UpdateOverlayLines(); // Check for changes to the SoD and update the overlay lines accordingly | ||||
} | } | ||||
break; | break; | ||||
case MT_Destroy: | case MT_Destroy: | ||||
{ | { | ||||
Reset(); | Reset(); | ||||
} | } | ||||
break; | break; | ||||
case MT_PositionChanged: | case MT_PositionChanged: | ||||
{ | { | ||||
// Unlikely to happen in-game, but can occur in atlas | // Unlikely to happen in-game, but can occur in atlas | ||||
// Just recompute the path from the entity to the first rally point | // Just recompute the path from the entity to the first rally point | ||||
RecomputeRallyPointPath_wrapper(0); | RecomputeRallyPointPath_wrapper(0); | ||||
} | } | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
/* | void CCmpRallyPointRenderer::UpdateMessageSubscriptions() | ||||
* 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, 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<ICmpPosition>& cmpPosition, CmpPtr<ICmpFootprint>& cmpFootprint, CmpPtr<ICmpPathfinder> 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<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint) const; | |||||
/** | |||||
* Get the point on the footprint edge that's as close from "start" as possible. | |||||
*/ | |||||
void GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> 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<SVisibilitySegment>& 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<CVector2D>& 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<SVisibilitySegment>& segments); | |||||
void RenderSubmit(SceneCollector& collector); | |||||
}; | |||||
REGISTER_COMPONENT_TYPE(RallyPointRenderer) | |||||
void CCmpRallyPointRenderer::Init(const CParamNode& paramNode) | |||||
{ | |||||
m_Displayed = false; | |||||
m_SmoothPath = true; | |||||
m_LastOwner = INVALID_PLAYER; | |||||
m_LastMarkerCount = 0; | |||||
m_EnableDebugNodeOverlay = false; | |||||
UpdateLineColor(); | |||||
// --------------------------------------------------------------------------------------------- | |||||
// 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"); | |||||
m_LineDashColor = CColor( | |||||
lineDashColor.GetChild("@r").ToInt()/255.f, | |||||
lineDashColor.GetChild("@g").ToInt()/255.f, | |||||
lineDashColor.GetChild("@b").ToInt()/255.f, | |||||
1.f | |||||
); | |||||
m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat(); | |||||
m_LineTexturePath = paramNode.GetChild("LineTexture").ToString(); | |||||
m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString(); | |||||
m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString()); | |||||
m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString()); | |||||
m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8(); | |||||
// --------------------------------------------------------------------------------------------- | |||||
// load some textures | |||||
if (CRenderer::IsInitialised()) | |||||
{ | |||||
CTextureProperties texturePropsBase(m_LineTexturePath); | |||||
texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); | |||||
texturePropsBase.SetMaxAnisotropy(4.f); | |||||
m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); | |||||
CTextureProperties texturePropsMask(m_LineTextureMaskPath); | |||||
texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); | |||||
texturePropsMask.SetMaxAnisotropy(4.f); | |||||
m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); | |||||
} | |||||
} | } | ||||
void CCmpRallyPointRenderer::UpdateMarkers() | void CCmpRallyPointRenderer::UpdateMarkers() | ||||
{ | { | ||||
player_id_t previousOwner = m_LastOwner; | player_id_t previousOwner = m_LastOwner; | ||||
for (size_t i = 0; i < m_RallyPoints.size(); ++i) | for (size_t i = 0; i < m_RallyPoints.size(); ++i) | ||||
{ | { | ||||
if (i >= m_MarkerEntityIds.size()) | if (i >= m_MarkerEntityIds.size()) | ||||
m_MarkerEntityIds.push_back(INVALID_ENTITY); | m_MarkerEntityIds.push_back(INVALID_ENTITY); | ||||
if (m_MarkerEntityIds[i] == INVALID_ENTITY) | if (m_MarkerEntityIds[i] == INVALID_ENTITY) | ||||
{ | { | ||||
// no marker exists yet, create one first | // No marker exists yet, create one first | ||||
CComponentManager& componentMgr = GetSimContext().GetComponentManager(); | CComponentManager& componentMgr = GetSimContext().GetComponentManager(); | ||||
// allocate a new entity for the marker | // Allocate a new entity for the marker | ||||
if (!m_MarkerTemplate.empty()) | if (!m_MarkerTemplate.empty()) | ||||
{ | { | ||||
m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity(); | m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity(); | ||||
if (m_MarkerEntityIds[i] != INVALID_ENTITY) | if (m_MarkerEntityIds[i] != INVALID_ENTITY) | ||||
m_MarkerEntityIds[i] = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityIds[i]); | m_MarkerEntityIds[i] = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityIds[i]); | ||||
} | } | ||||
} | } | ||||
// 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) | if (m_MarkerEntityIds[i] == INVALID_ENTITY) | ||||
LOGERROR("Failed to create rally point marker entity"); | LOGERROR("Failed to create rally point marker entity"); | ||||
CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntityIds[i]); | CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntityIds[i]); | ||||
if (markerCmpPosition) | if (markerCmpPosition) | ||||
{ | { | ||||
if (m_Displayed && IsSet()) | if (m_Displayed && IsSet()) | ||||
{ | { | ||||
markerCmpPosition->MoveTo(m_RallyPoints[i].X, m_RallyPoints[i].Y); | markerCmpPosition->MoveTo(m_RallyPoints[i].X, m_RallyPoints[i].Y); | ||||
} | } | ||||
else | 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<ICmpOwnership> cmpOwnership(GetEntityHandle()); | CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle()); | ||||
if (!cmpOwnership) | if (!cmpOwnership) | ||||
continue; | continue; | ||||
player_id_t ownerId = cmpOwnership->GetOwner(); | player_id_t ownerId = cmpOwnership->GetOwner(); | ||||
if (ownerId == INVALID_PLAYER || (ownerId == previousOwner && m_LastMarkerCount >= i)) | if (ownerId == INVALID_PLAYER || (ownerId == previousOwner && m_LastMarkerCount >= i)) | ||||
continue; | continue; | ||||
Show All 10 Lines | for (size_t i = 0; i < m_RallyPoints.size(); ++i) | ||||
CmpPtr<ICmpVisual> cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]); | CmpPtr<ICmpVisual> cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]); | ||||
if (cmpVisualActor) | if (cmpVisualActor) | ||||
cmpVisualActor->SetVariant("civ", CStrW(cmpPlayer->GetCiv()).ToUTF8()); | cmpVisualActor->SetVariant("civ", CStrW(cmpPlayer->GetCiv()).ToUTF8()); | ||||
} | } | ||||
m_LastMarkerCount = m_RallyPoints.size() - 1; | m_LastMarkerCount = m_RallyPoints.size() - 1; | ||||
} | } | ||||
void CCmpRallyPointRenderer::UpdateLineColor() | void CCmpRallyPointRenderer::UpdateLineColor() | ||||
{ | { | ||||
CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle()); | CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle()); | ||||
if (!cmpOwnership) | if (!cmpOwnership) | ||||
return; | return; | ||||
player_id_t owner = cmpOwnership->GetOwner(); | player_id_t owner = cmpOwnership->GetOwner(); | ||||
if (owner == INVALID_PLAYER) | if (owner == INVALID_PLAYER) | ||||
Show All 11 Lines | |||||
} | } | ||||
void CCmpRallyPointRenderer::RecomputeAllRallyPointPaths() | void CCmpRallyPointRenderer::RecomputeAllRallyPointPaths() | ||||
{ | { | ||||
m_Path.clear(); | m_Path.clear(); | ||||
m_VisibilitySegments.clear(); | m_VisibilitySegments.clear(); | ||||
m_TexturedOverlayLines.clear(); | m_TexturedOverlayLines.clear(); | ||||
//// <DEBUG> /////////////////////////////////////////////// | |||||
if (m_EnableDebugNodeOverlay) | if (m_EnableDebugNodeOverlay) | ||||
m_DebugNodeOverlays.clear(); | m_DebugNodeOverlays.clear(); | ||||
//// </DEBUG> ////////////////////////////////////////////// | |||||
// No use computing a path if the rally point isn't set | |||||
if (!IsSet()) | if (!IsSet()) | ||||
return; // no use computing a path if the rally point isn't set | return; | ||||
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
// No point going on if this entity doesn't have a position or is outside of the world | |||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | 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<ICmpFootprint> cmpFootprint(GetEntityHandle()); | CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle()); | ||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||
for (size_t i = 0; i < m_RallyPoints.size(); ++i) | for (size_t i = 0; i < m_RallyPoints.size(); ++i) | ||||
{ | { | ||||
RecomputeRallyPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder); | RecomputeRallyPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder); | ||||
} | } | ||||
} | } | ||||
void CCmpRallyPointRenderer::RecomputeRallyPointPath_wrapper(size_t index) | void CCmpRallyPointRenderer::RecomputeRallyPointPath_wrapper(size_t index) | ||||
{ | { | ||||
// No use computing a path if the rally point isn't set | |||||
if (!IsSet()) | 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<ICmpPosition> cmpPosition(GetEntityHandle()); | CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle()); | ||||
if (!cmpPosition || !cmpPosition->IsInWorld()) | 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<ICmpFootprint> cmpFootprint(GetEntityHandle()); | CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle()); | ||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity()); | ||||
RecomputeRallyPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder); | RecomputeRallyPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder); | ||||
} | } | ||||
void CCmpRallyPointRenderer::RecomputeRallyPointPath(size_t index, CmpPtr<ICmpPosition>& cmpPosition, CmpPtr<ICmpFootprint>& cmpFootprint, CmpPtr<ICmpPathfinder> cmpPathfinder) | void CCmpRallyPointRenderer::RecomputeRallyPointPath(size_t index, CmpPtr<ICmpPosition>& cmpPosition, CmpPtr<ICmpFootprint>& cmpFootprint, CmpPtr<ICmpPathfinder> cmpPathfinder) | ||||
{ | { | ||||
while (index >= m_Path.size()) | while (index >= m_Path.size()) | ||||
{ | { | ||||
std::vector<CVector2D> tmp; | std::vector<CVector2D> tmp; | ||||
m_Path.push_back(tmp); | m_Path.push_back(tmp); | ||||
} | } | ||||
m_Path[index].clear(); | m_Path[index].clear(); | ||||
while (index >= m_VisibilitySegments.size()) | while (index >= m_VisibilitySegments.size()) | ||||
{ | { | ||||
std::deque<SVisibilitySegment> tmp; | std::vector<SVisibilitySegment> tmp; | ||||
m_VisibilitySegments.push_back(tmp); | m_VisibilitySegments.push_back(tmp); | ||||
} | } | ||||
m_VisibilitySegments[index].clear(); | m_VisibilitySegments[index].clear(); | ||||
// Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a | // 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 | // 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. | // 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<Waypoint>& waypoints = path.m_Waypoints; | std::vector<Waypoint>& waypoints = path.m_Waypoints; | ||||
CFixedVector2D start(cmpPosition->GetPosition2D()); | 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) | if (index == 0) | ||||
GetClosestsEdgePointFrom(start,m_RallyPoints[index], cmpPosition, cmpFootprint); | GetClosestsEdgePointFrom(start,m_RallyPoints[index], cmpPosition, cmpFootprint); | ||||
else | else | ||||
{ | { | ||||
start.X = m_RallyPoints[index-1].X; | start.X = m_RallyPoints[index-1].X; | ||||
start.Y = m_RallyPoints[index-1].Y; | start.Y = m_RallyPoints[index-1].Y; | ||||
} | } | ||||
cmpPathfinder->ComputePath(start.X, start.Y, goal, cmpPathfinder->GetPassabilityClass(m_LinePassabilityClass), path); | cmpPathfinder->ComputePath(start.X, start.Y, goal, cmpPathfinder->GetPassabilityClass(m_LinePassabilityClass), path); | ||||
// Check if we got a path back; if not we probably have two markers less than one tile apart. | // Check if we got a path back; if not we probably have two markers less than one tile apart. | ||||
if (path.m_Waypoints.size() < 2) | if (path.m_Waypoints.size() < 2) | ||||
{ | { | ||||
m_Path[index].emplace_back(start.X.ToFloat(), start.Y.ToFloat()); | m_Path[index].emplace_back(start.X.ToFloat(), start.Y.ToFloat()); | ||||
m_Path[index].emplace_back(m_RallyPoints[index].X.ToFloat(), m_RallyPoints[index].Y.ToFloat()); | m_Path[index].emplace_back(m_RallyPoints[index].X.ToFloat(), m_RallyPoints[index].Y.ToFloat()); | ||||
return; | return; | ||||
} | } | ||||
else if (index == 0) | 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); | CFixedVector2D newend(waypoints[waypoints.size()-2].x,waypoints[waypoints.size()-2].z); | ||||
GetClosestsEdgePointFrom(newend,newend, cmpPosition, cmpFootprint); | GetClosestsEdgePointFrom(newend,newend, cmpPosition, cmpFootprint); | ||||
waypoints.back().x = newend.X; | waypoints.back().x = newend.X; | ||||
waypoints.back().z = newend.Y; | waypoints.back().z = newend.Y; | ||||
} | } | ||||
else | 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().x = m_RallyPoints[index-1].X; | ||||
waypoints.back().z = m_RallyPoints[index-1].Y; | 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].x = m_RallyPoints[index].X; | ||||
waypoints[0].z = m_RallyPoints[index].Y; | waypoints[0].z = m_RallyPoints[index].Y; | ||||
// From here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth | // From here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth | ||||
// between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot. | // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot. | ||||
// Since we'll only be further using these points for rendering purposes, using floats should be fine. | // Since we'll only be further using these points for rendering purposes, using floats should be fine. | ||||
for (Waypoint& waypoint : waypoints) | for (Waypoint& waypoint : waypoints) | ||||
m_Path[index].emplace_back(waypoint.x.ToFloat(), waypoint.z.ToFloat()); | 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 | // Post-processing | ||||
// Linearize the path; | // Linearize the path; | ||||
// Pass through the waypoints, averaging each waypoint with its next one except the last one. Because the path | // Pass through the waypoints, averaging each waypoint with its next one except the last one. Because the path | ||||
// goes from the marker to this entity/the previous flag and we want to keep the point at the marker's exact position, | // goes from the marker to this entity/the previous flag and we want to keep the point at the marker's exact position, | ||||
// loop backwards through the waypoints so that the marker waypoint is maintained. | // loop backwards through the waypoints so that the marker waypoint is maintained. | ||||
// TODO: see if we can do this at the same time as the waypoint -> coord conversion above | // TODO: see if we can do this at the same time as the waypoint -> coord conversion above | ||||
for(size_t i = m_Path[index].size() - 2; i > 0; --i) | 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; | 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); | |||||
Done Inline ActionsIt seems it was the only one usage of the FixFootprintWaypoints, do we need it? Or why the code was removed? Is it useless? vladislavbelov: It seems it was the only one usage of the `FixFootprintWaypoints`, do we need it? Or why the… | |||||
Done Inline ActionsStan: The code was removed because of the new Pathfinder in rP16751 maybe @Itms know more about it. | |||||
Done Inline ActionsWith the I am not sure what this footprint-related condition is about. I guess the code was maybe commented out instead of deleted because of that uncertainty. We should delete it now. Itms: With the ~~new~~ modern pathfinder, the paths are already free of obstructed waypoints. So this… | |||||
// Eliminate some consecutive waypoints that are visible from eachother. Reduce across a maximum distance of approx. 6 tiles | // 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) | // (prevents segments that are too long to properly stick to the terrain) | ||||
ReduceSegmentsByVisibility(m_Path[index], 6); | ReduceSegmentsByVisibility(m_Path[index], 6); | ||||
// Debug overlays | // Debug overlays | ||||
if (m_EnableDebugNodeOverlay) | if (m_EnableDebugNodeOverlay) | ||||
{ | { | ||||
while (index >= m_DebugNodeOverlays.size()) | while (index >= m_DebugNodeOverlays.size()) | ||||
m_DebugNodeOverlays.emplace_back(); | m_DebugNodeOverlays.emplace_back(); | ||||
m_DebugNodeOverlays[index].clear(); | m_DebugNodeOverlays[index].clear(); | ||||
} | } | ||||
if (m_EnableDebugNodeOverlay && m_SmoothPath) | if (m_EnableDebugNodeOverlay && m_SmoothPath) | ||||
{ | { | ||||
// Create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the | // 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) | // 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]) | ||||
Done Inline Actionsconst CVector2D& point? vladislavbelov: `const CVector2D& point`? | |||||
{ | { | ||||
SOverlayLine overlayLine; | SOverlayLine overlayLine; | ||||
overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f); | overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f); | ||||
overlayLine.m_Thickness = 2; | overlayLine.m_Thickness = 2; | ||||
SimRender::ConstructSquareOnGround(GetSimContext(), point.X, point.Y, 0.2f, 0.2f, 1.0f, overlayLine, true); | SimRender::ConstructSquareOnGround(GetSimContext(), point.X, point.Y, 0.2f, 0.2f, 1.0f, overlayLine, true); | ||||
m_DebugNodeOverlays[index].push_back(overlayLine); | m_DebugNodeOverlays[index].push_back(overlayLine); | ||||
} | } | ||||
} | } | ||||
if (m_SmoothPath) | if (m_SmoothPath) | ||||
// The number of points to interpolate goes hand in hand with the maximum amount of node links allowed to be joined together | // 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 | // 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. | // 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); | GetVisibilitySegments(m_VisibilitySegments[index], index); | ||||
// build overlay lines for the new path | // Build overlay lines for the new path | ||||
ConstructOverlayLines(index); | ConstructOverlayLines(index); | ||||
} | } | ||||
void CCmpRallyPointRenderer::ConstructAllOverlayLines() | void CCmpRallyPointRenderer::ConstructAllOverlayLines() | ||||
{ | { | ||||
m_TexturedOverlayLines.clear(); | m_TexturedOverlayLines.clear(); | ||||
for (size_t i = 0; i < m_Path.size(); ++i) | for (size_t i = 0; i < m_Path.size(); ++i) | ||||
Show All 10 Lines | while (index >= m_TexturedOverlayLines.size()) | ||||
std::vector<SOverlayTexturedLine> tmp; | std::vector<SOverlayTexturedLine> tmp; | ||||
m_TexturedOverlayLines.push_back(tmp); | m_TexturedOverlayLines.push_back(tmp); | ||||
} | } | ||||
m_TexturedOverlayLines[index].clear(); | m_TexturedOverlayLines[index].clear(); | ||||
if (m_Path[index].size() < 2) | if (m_Path[index].size() < 2) | ||||
return; | 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<SVisibilitySegment>::const_iterator it = m_VisibilitySegments[index].begin(); it != m_VisibilitySegments[index].end(); ++it) | |||||
{ | |||||
const SVisibilitySegment& segment = (*it); | |||||
Done Inline ActionsI suppose we can replace it with range based loop. vladislavbelov: I suppose we can replace it with range based loop. | |||||
for(const SVisibilitySegment& segment : m_VisibilitySegments[index]) | |||||
{ | |||||
if (segment.m_Visible) | 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 bordersBuilding = (segment.m_EndIndex == m_Path[index].size() - 1); | ||||
bool bordersFlag = (segment.m_StartIndex == 0); | 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; | SOverlayTexturedLine overlayLine; | ||||
overlayLine.m_Thickness = m_LineThickness; | overlayLine.m_Thickness = m_LineThickness; | ||||
overlayLine.m_SimContext = &GetSimContext(); | overlayLine.m_SimContext = &GetSimContext(); | ||||
overlayLine.m_TextureBase = m_Texture; | overlayLine.m_TextureBase = m_Texture; | ||||
overlayLine.m_TextureMask = m_TextureMask; | overlayLine.m_TextureMask = m_TextureMask; | ||||
overlayLine.m_Color = m_LineColor; | overlayLine.m_Color = m_LineColor; | ||||
overlayLine.m_Closed = false; | 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 | // 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 | // 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_StartCapType = (bordersFlag ? m_LineEndCapType : dashesLineCapType); | ||||
overlayLine.m_EndCapType = (bordersBuilding ? m_LineStartCapType : dashesLineCapType); | overlayLine.m_EndCapType = (bordersBuilding ? m_LineStartCapType : dashesLineCapType); | ||||
overlayLine.m_AlwaysVisible = true; | overlayLine.m_AlwaysVisible = true; | ||||
// push overlay line coordinates | // Push overlay line coordinates | ||||
ENSURE(segment.m_EndIndex > segment.m_StartIndex); | 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].X); | ||||
overlayLine.m_Coords.push_back(m_Path[index][j].Y); | overlayLine.m_Coords.push_back(m_Path[index][j].Y); | ||||
} | } | ||||
m_TexturedOverlayLines[index].push_back(overlayLine); | m_TexturedOverlayLines[index].push_back(overlayLine); | ||||
} | } | ||||
else | 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<CVector2D> straightLine; | std::vector<CVector2D> straightLine; | ||||
straightLine.push_back(m_Path[index][segment.m_StartIndex]); | straightLine.push_back(m_Path[index][segment.m_StartIndex]); | ||||
straightLine.push_back(m_Path[index][segment.m_EndIndex]); | straightLine.push_back(m_Path[index][segment.m_EndIndex]); | ||||
// 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 | // 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 | ||||
// area is visually obvious. This requires some calculations to see what size we should make the dashes and clears for them | // area is visually obvious. This requires some calculations to see what size we should make the dashes and clears for them | ||||
// to fit exactly. | // to fit exactly. | ||||
float maxDashSize = 3.f; | float maxDashSize = 3.f; | ||||
float maxClearSize = 3.f; | float maxClearSize = 3.f; | ||||
float dashSize = maxDashSize; | float dashSize = maxDashSize; | ||||
float clearSize = maxClearSize; | 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); | |||||
Done Inline ActionsUnnecessary parenthesis. vladislavbelov: Unnecessary parenthesis. | |||||
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 | // 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. | // a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is. | ||||
int numFitUnmodified = floor(distance/(dashSize + clearSize)); | int numFitUnmodified = floor(distance/(dashSize + clearSize)); | ||||
float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize)); | float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize)); | ||||
// Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly. | // Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly. | ||||
// We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make | // We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make | ||||
// it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified | // it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified | ||||
// (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's | // (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. | // 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); | 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; | dashSize += pairDashRatio * pairwiseLengthDifference; | ||||
clearSize += (1 - pairDashRatio) * pairwiseLengthDifference; | clearSize += (1 - pairDashRatio) * pairwiseLengthDifference; | ||||
// ------------------------------------------------------------------------------------------------ | // ------------------------------------------------------------------------------------------------ | ||||
SDashedLine dashedLine; | SDashedLine dashedLine; | ||||
SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize); | SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize); | ||||
// build overlay lines for dashes | // Build overlay lines for dashes | ||||
size_t numDashes = dashedLine.m_StartIndices.size(); | size_t numDashes = dashedLine.m_StartIndices.size(); | ||||
for (size_t i=0; i < numDashes; i++) | for (size_t i=0; i < numDashes; i++) | ||||
{ | { | ||||
SOverlayTexturedLine dashOverlay; | SOverlayTexturedLine dashOverlay; | ||||
dashOverlay.m_Thickness = m_LineThickness; | dashOverlay.m_Thickness = m_LineThickness; | ||||
dashOverlay.m_SimContext = &GetSimContext(); | dashOverlay.m_SimContext = &GetSimContext(); | ||||
dashOverlay.m_TextureBase = m_Texture; | dashOverlay.m_TextureBase = m_Texture; | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | |||||
void CCmpRallyPointRenderer::UpdateOverlayLines() | void CCmpRallyPointRenderer::UpdateOverlayLines() | ||||
{ | { | ||||
// We should only do this if the rally point is currently being displayed and set inside the world, otherwise it's a massive | // We should only do this if the rally point is currently being displayed and set inside the world, otherwise it's a massive | ||||
// waste of time to calculate all this stuff (this method is called every turn) | // waste of time to calculate all this stuff (this method is called every turn) | ||||
if (!m_Displayed || !IsSet()) | if (!m_Displayed || !IsSet()) | ||||
return; | return; | ||||
// see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones | // 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<std::deque<SVisibilitySegment> > newVisibilitySegments; | std::vector<std::vector<SVisibilitySegment> > newVisibilitySegments; | ||||
Done Inline ActionsThere is an erasing of elements from this container, and std::deque is faster by constant for such operation. So I think it'd be better to use std::deque here, some performance tests may help to decide. vladislavbelov: There is an erasing of elements from this container, and `std::deque` is faster by constant for… | |||||
Done Inline ActionsDefinitely do performance tests, as it generally turns out std::vector is faster anyways. wraitii: Definitely do performance tests, as it generally turns out std::vector is faster anyways. | |||||
Done Inline ActionsStan: https://baptiste-wicht.com/posts/2012/12/cpp-benchmark-vector-list-deque.html | |||||
for (size_t i = 0; i < m_Path.size(); ++i) | for (size_t i = 0; i < m_Path.size(); ++i) | ||||
{ | { | ||||
std::deque<SVisibilitySegment> tmp; | std::vector<SVisibilitySegment> tmp; | ||||
newVisibilitySegments.push_back(tmp); | newVisibilitySegments.push_back(tmp); | ||||
GetVisibilitySegments(newVisibilitySegments[i], i); | GetVisibilitySegments(newVisibilitySegments[i], i); | ||||
} | } | ||||
// Check if the full path changed, then reconstruct all overlay lines, otherwise check if a segment changed and update that. | // 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()) | 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(); | ConstructAllOverlayLines(); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
for (size_t i = 0; i < m_VisibilitySegments.size(); ++i) | for (size_t i = 0; i < m_VisibilitySegments.size(); ++i) | ||||
{ | { | ||||
if (m_VisibilitySegments[i] != newVisibilitySegments[i]) | if (m_VisibilitySegments[i] != newVisibilitySegments[i]) | ||||
{ | { | ||||
// The visibility segments have changed, reconstruct the overlay lines to match. NOTE: The path itself doesn't | // 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. | // 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); | ConstructOverlayLines(i); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
void CCmpRallyPointRenderer::GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint) const | void CCmpRallyPointRenderer::GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint) const | ||||
{ | { | ||||
ENSURE(cmpPosition); | ENSURE(cmpPosition); | ||||
ENSURE(cmpFootprint); | ENSURE(cmpFootprint); | ||||
// grab the shape and dimensions of the footprint | // Grab the shape and dimensions of the footprint | ||||
entity_pos_t footprintSize0, footprintSize1, footprintHeight; | entity_pos_t footprintSize0, footprintSize1, footprintHeight; | ||||
ICmpFootprint::EShape footprintShape; | ICmpFootprint::EShape footprintShape; | ||||
cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight); | cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight); | ||||
// grab the center of the footprint | // Grab the center of the footprint | ||||
CFixedVector2D center = cmpPosition->GetPosition2D(); | CFixedVector2D center = cmpPosition->GetPosition2D(); | ||||
switch (footprintShape) | switch (footprintShape) | ||||
{ | { | ||||
case ICmpFootprint::SQUARE: | case ICmpFootprint::SQUARE: | ||||
{ | { | ||||
// in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively. | // 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 | |||||
// 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 | // 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) | // (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; | fixed a = cmpPosition->GetRotation().Y; | ||||
sincos_approx(a, s, c); | sincos_approx(a, s, c); | ||||
CFixedVector2D u(c, -s); // unit vector along the rotated X axis | // Unit vector along the rotated X axis | ||||
CFixedVector2D v(s, c); // unit vector along the rotated Z axis | CFixedVector2D u(c, -s); | ||||
// Unit vector along the rotated Z axis | |||||
CFixedVector2D v(s, c); | |||||
CFixedVector2D halfSize(footprintSize0/2, footprintSize1/2); | CFixedVector2D halfSize(footprintSize0 / 2, footprintSize1 / 2); | ||||
CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(start - center, u, v, halfSize); | CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(start - center, u, v, halfSize); | ||||
result = center + footprintEdgePoint; | result = center + footprintEdgePoint; | ||||
break; | break; | ||||
} | } | ||||
case ICmpFootprint::CIRCLE: | 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. | // Transform target to the point nearest on the edge. | ||||
CFixedVector2D centerVec2D(center.X, center.Y); | CFixedVector2D centerVec2D(center.X, center.Y); | ||||
CFixedVector2D centerToLast(start - centerVec2D); | CFixedVector2D centerToLast(start - centerVec2D); | ||||
centerToLast.Normalize(); | centerToLast.Normalize(); | ||||
result = centerVec2D + (centerToLast.Multiply(footprintSize0)); | result = centerVec2D + (centerToLast.Multiply(footprintSize0)); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
void CCmpRallyPointRenderer::FixFootprintWaypoints(std::vector<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> 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<CVector2D>& coords, unsigned maxSegmentLinks, bool floating) const | void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks, bool floating) const | ||||
Done Inline ActionsThe for looks weird. I would prefer following: for (size_t i = 0; i < coords.size(); ++i) { // ... coords.erase(coords.rbegin() + i); } vladislavbelov: The `for` looks weird. I would prefer following:
```lang=cpp
for (size_t i = 0; i < coords.size… | |||||
Done Inline ActionsOr as the article suggests: segments.pop_back();. vladislavbelov: Or as the article suggests: `segments.pop_back();`. | |||||
Done Inline ActionsThis comment was to the UB line (I don't know how the comment points out to this line). vladislavbelov: This comment was to the UB line (I don't know how the comment points out to this line). | |||||
Done Inline ActionsYou changed the logic here. vladislavbelov: You changed the logic here. | |||||
Done Inline ActionsHere too. vladislavbelov: Here too. | |||||
Done Inline ActionsWe shouldn't use size_t for such kind of loop conditions: for (size_t i = coords.size() - 1; i >= 0; i--). Because subtraction from size_t that equals to 0 isn't good. You'll get underflow: in coords.size() - 1 if coords.size() == 0 and in i-- if i == 0. The last one means an infinity loop. vladislavbelov: We shouldn't use `size_t` for such kind of loop conditions: `for (size_t i = coords.size() - 1… | |||||
Done Inline ActionsIs there a better way to write this loop than for(int i = static_cast<int>)(coords.size() - 1); i >= 0; i--) Stan: Is there a better way to write this loop than for(int i = static_cast<int>)(coords.size() - 1)… | |||||
Done Inline ActionsJust loop from coords.size() to a strict >0 and access coords[i-1]. Itms: Just loop from `coords.size()` to a strict `>0` and access `coords[i-1]`. | |||||
Done Inline ActionsI'd prefer to not have the erasing with alive indices. // It'd probably good to add a function for explicit Vector2D > CFixedVector2D conversion. while (!coords.empty() && Geometry::PointIsInSquare(CFixedVector2D(coords.back()) - center, u, v, halfSize)) { coords.pop_back(); } vladislavbelov: I'd prefer to not have the erasing with alive indices.
```lang=cpp
// It'd probably good to add… | |||||
Done Inline ActionsAh, sure, I hadn't taken a look at the contents of the loop. It should be changed indeed. But in case Stan encounters another loop with a size_t and descending order, this is the way to avoid the underflow. Itms: Ah, sure, I hadn't taken a look at the contents of the loop. It should be changed indeed. But… | |||||
Done Inline ActionsThanks for the tips guys ! Since the function is gone now it's all good. Stan: Thanks for the tips guys ! Since the function is gone now it's all good. | |||||
{ | { | ||||
CmpPtr<ICmpPathfinder> cmpPathFinder(GetSystemEntity()); | CmpPtr<ICmpPathfinder> cmpPathFinder(GetSystemEntity()); | ||||
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); | CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity()); | ||||
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity()); | CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity()); | ||||
ENSURE(cmpPathFinder && cmpTerrain && cmpWaterManager); | ENSURE(cmpPathFinder && cmpTerrain && cmpWaterManager); | ||||
if (coords.size() < 3) | if (coords.size() < 3) | ||||
return; | return; | ||||
// 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 | // 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 | ||||
// 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 | // 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 | ||||
// process from there on until the entire line is checked. The output is the array of base nodes. | // process from there on until the entire line is checked. The output is the array of base nodes. | ||||
std::vector<CVector2D> newCoords; | std::vector<CVector2D> newCoords; | ||||
StationaryOnlyObstructionFilter obstructionFilter; | StationaryOnlyObstructionFilter obstructionFilter; | ||||
entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness); | entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness); | ||||
pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass); | 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 baseNodeIdx = 0; | ||||
size_t curNodeIdx = 1; | size_t curNodeIdx = 1; | ||||
float baseNodeY; | float baseNodeY; | ||||
entity_pos_t baseNodeX; | entity_pos_t baseNodeX; | ||||
entity_pos_t baseNodeZ; | entity_pos_t baseNodeZ; | ||||
// set initial base node coords | // Set initial base node coords | ||||
baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X); | baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X); | ||||
baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y); | baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y); | ||||
baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y); | baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y); | ||||
if (floating) | if (floating) | ||||
baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y)); | baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y)); | ||||
while (curNodeIdx < coords.size()) | 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 curNodeX = fixed::FromFloat(coords[curNodeIdx].X); | ||||
entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y); | entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y); | ||||
float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y); | float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y); | ||||
if (floating) | if (floating) | ||||
curNodeY = std::max(curNodeY, cmpWaterManager->GetExactWaterLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y)); | 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); | 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 | // 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) | 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); | curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks); | ||||
if (!curNodeVisible) | 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. | // 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 | // we should take care not to stay stuck at the current base node | ||||
if (curNodeIdx > baseNodeIdx + 1) | if (curNodeIdx > baseNodeIdx + 1) | ||||
{ | { | ||||
baseNodeIdx = curNodeIdx - 1; | baseNodeIdx = curNodeIdx - 1; | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
// curNodeIdx == baseNodeIdx + 1 | // curNodeIdx == baseNodeIdx + 1 | ||||
baseNodeIdx = curNodeIdx; | 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); | baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X); | ||||
baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y); | baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y); | ||||
baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y); | baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y); | ||||
if (floating) | if (floating) | ||||
baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y)); | 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. | // base node, then the loop above just ends and no endpoint is ever added to the list. | ||||
ENSURE(curNodeIdx == coords.size()); | ENSURE(curNodeIdx == coords.size()); | ||||
newCoords.push_back(coords[coords.size() - 1]); | newCoords.push_back(coords[coords.size() - 1]); | ||||
coords.swap(newCoords); | coords.swap(newCoords); | ||||
} | } | ||||
void CCmpRallyPointRenderer::GetVisibilitySegments(std::deque<SVisibilitySegment>& out, size_t index) const | void CCmpRallyPointRenderer::GetVisibilitySegments(std::vector<SVisibilitySegment>& out, size_t index) const | ||||
{ | { | ||||
out.clear(); | out.clear(); | ||||
if (m_Path[index].size() < 2) | if (m_Path[index].size() < 2) | ||||
return; | return; | ||||
CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSystemEntity()); | CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSystemEntity()); | ||||
player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer(); | player_id_t currentPlayer = static_cast<player_id_t>(GetSimContext().GetCurrentDisplayedPlayer()); | ||||
CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer)); | 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. | // a new one at the next point. | ||||
const float terrainSize = static_cast<float>(TERRAIN_TILE_SIZE); | |||||
Done Inline Actionsconst or constexpr. vladislavbelov: `const` or `constexpr`. | |||||
bool lastVisible = losQuerier.IsExplored( | bool lastVisible = losQuerier.IsExplored( | ||||
(fixed::FromFloat(m_Path[index][0].X) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest(), | (fixed::FromFloat(m_Path[index][0].X / terrainSize)).ToInt_RoundToNearest(), | ||||
(fixed::FromFloat(m_Path[index][0].Y) / (int) TERRAIN_TILE_SIZE).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) | for (size_t k = 1; k < m_Path[index].size(); ++k) | ||||
{ | { | ||||
// grab tile indices for this coord | // Grab tile indices for this coord | ||||
int i = (fixed::FromFloat(m_Path[index][k].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(); | int i = (fixed::FromFloat(m_Path[index][k].X / terrainSize)).ToInt_RoundToNearest(); | ||||
int j = (fixed::FromFloat(m_Path[index][k].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(); | int j = (fixed::FromFloat(m_Path[index][k].Y / terrainSize)).ToInt_RoundToNearest(); | ||||
bool nodeVisible = losQuerier.IsExplored(i, j); | bool nodeVisible = losQuerier.IsExplored(i, j); | ||||
if (nodeVisible != lastVisible) | 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)); | out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1)); | ||||
//curSegmentStartIndex = k; // new segment starts here | |||||
curSegmentStartIndex = k - 1; | curSegmentStartIndex = k - 1; | ||||
lastVisible = nodeVisible; | lastVisible = nodeVisible; | ||||
} | } | ||||
} | } | ||||
// terminate the last segment | // Terminate the last segment | ||||
out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1)); | out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1)); | ||||
MergeVisibilitySegments(out); | MergeVisibilitySegments(out); | ||||
} | } | ||||
void CCmpRallyPointRenderer::MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments) | void CCmpRallyPointRenderer::MergeVisibilitySegments(std::vector<SVisibilitySegment>& segments) | ||||
{ | { | ||||
// Scan for single-point segments; if they are inbetween two other segments, delete them and merge the surrounding 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 | // 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). | // 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(); | size_t numSegments = segments.size(); | ||||
// WARNING: FOR LOOP TRICKERY AHEAD! | // WARNING: FOR LOOP TRICKERY AHEAD! | ||||
for (size_t i = 1; i < numSegments - 1;) | for (size_t i = 1; i < numSegments - 1;) | ||||
{ | { | ||||
SVisibilitySegment& segment = segments[i]; | SVisibilitySegment& segment = segments[i]; | ||||
if (segment.IsSinglePoint()) | 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); | 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 | // Make previous segment span all the way across to the next | ||||
segments.erase(segments.begin() + i); // erase this segment ... | segments[i-1].m_EndIndex = segments[i+1].m_EndIndex; | ||||
segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i]) | // Erase this segment | ||||
numSegments -= 2; // we removed 2 segments, so update the loop condition | segments.erase(segments.begin() + i); | ||||
// in the next iteration, i should still point to the segment right after the one that got expanded, which is now | // 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 | // at position i; so don't increment i here | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
++i; | ++i; | ||||
} | } | ||||
} | } | ||||
ENSURE(numSegments == segments.size()); | 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()) | if (segments.size() >= 2 && segments[0].IsSinglePoint()) | ||||
{ | { | ||||
int firstSegmentStartIndex = segments.front().m_StartIndex; | int firstSegmentStartIndex = segments.front().m_StartIndex; | ||||
ENSURE(firstSegmentStartIndex == 0); | 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.erase(segments.begin()); | ||||
segments.front().m_StartIndex = firstSegmentStartIndex; | segments.front().m_StartIndex = firstSegmentStartIndex; | ||||
} | } | ||||
// check to see if the last segment needs to be merged with its neighbour | // check to see if the last segment needs to be merged with its neighbour | ||||
if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint()) | if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint()) | ||||
{ | { | ||||
int lastSegmentEndIndex = segments.back().m_EndIndex; | 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(); | ||||
Done Inline ActionsPlease remove UB here (#5288). It should be segments.erase(std::prev(segments.end())); or segments.erase(segments.rbegin()); I suppose. vladislavbelov: Please remove UB here (#5288). It should be `segments.erase(std::prev(segments.end()));` or… | |||||
Done Inline Actionssegments.pop_back() was for this line. vladislavbelov: `segments.pop_back()` was for this line. | |||||
segments.back().m_EndIndex = lastSegmentEndIndex; | 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) | for (size_t i = 0; i < segments.size(); ++i) | ||||
{ | { | ||||
ENSURE(!segments[i].IsSinglePoint()); | ENSURE(!segments[i].IsSinglePoint()); | ||||
ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex); | ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex); | ||||
} | } | ||||
} | } | ||||
void CCmpRallyPointRenderer::RenderSubmit(SceneCollector& collector) | void CCmpRallyPointRenderer::RenderSubmit(SceneCollector& collector) | ||||
{ | { | ||||
// we only get here if the rally point is set and should be displayed | // We only get here if the rally point is set and should be displayed | ||||
for (size_t i = 0; i < m_TexturedOverlayLines.size(); ++i) | for(std::vector<SOverlayTexturedLine>& row : m_TexturedOverlayLines) | ||||
for (SOverlayTexturedLine& col : row) | |||||
Done Inline ActionsRange based loop here and below. vladislavbelov: Range based loop here and below. | |||||
if (!col.m_Coords.empty()) | |||||
collector.Submit(&col); | |||||
if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty()) | |||||
{ | |||||
for (std::vector<SOverlayLine>& row : m_DebugNodeOverlays) | |||||
for (SOverlayLine& col : row) | |||||
if (!col.m_Coords.empty()) | |||||
collector.Submit(&col); | |||||
} | |||||
} | |||||
void CCmpRallyPointRenderer::AddPosition_wrapper(const CFixedVector2D& pos) | |||||
Done Inline ActionsUsually, classinit, serialize, deserialize, message handling, and message subscriptions are up there just after Init. Easier to find them. Itms: Usually, classinit, serialize, deserialize, message handling, and message subscriptions are up… | |||||
{ | { | ||||
for (size_t j = 0; j < m_TexturedOverlayLines[i].size(); ++j) | AddPosition(pos, false); | ||||
} | |||||
void CCmpRallyPointRenderer::SetPosition(const CFixedVector2D& pos) | |||||
{ | { | ||||
if (!m_TexturedOverlayLines[i][j].m_Coords.empty()) | if (!(m_RallyPoints.size() == 1 && m_RallyPoints.front() == pos)) | ||||
collector.Submit(&m_TexturedOverlayLines[i][j]); | { | ||||
m_RallyPoints.clear(); | |||||
AddPosition(pos, true); | |||||
// Don't need to UpdateMessageSubscriptions here since AddPosition already calls it | |||||
} | } | ||||
} | } | ||||
if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty()) | void CCmpRallyPointRenderer::UpdatePosition(u32 rallyPointId, const CFixedVector2D& pos) | ||||
{ | { | ||||
for (size_t i = 0; i < m_DebugNodeOverlays.size(); ++i) | if (rallyPointId >= m_RallyPoints.size()) | ||||
for (size_t j = 0; j < m_DebugNodeOverlays[i].size(); ++j) | return; | ||||
collector.Submit(&m_DebugNodeOverlays[i][j]); | |||||
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) | |||||
{ | |||||
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(); | |||||
} | |||||
} | |||||
void CCmpRallyPointRenderer::Reset() | |||||
{ | |||||
for (entity_id_t& componentId : m_MarkerEntityIds) | |||||
{ | |||||
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(); | |||||
} | } | ||||
Done Inline ActionsThe schema should also be in the top region of the file. Itms: The schema should also be in the top region of the file. |
Bump year.