LCOV - code coverage report
Current view: top level - source/simulation2/components - CCmpSelectable.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 10 275 3.6 %
Date: 2023-01-19 00:18:29 Functions: 5 26 19.2 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "ICmpSelectable.h"
      21             : 
      22             : #include "graphics/Overlay.h"
      23             : #include "graphics/Terrain.h"
      24             : #include "graphics/TextureManager.h"
      25             : #include "maths/Ease.h"
      26             : #include "maths/MathUtil.h"
      27             : #include "maths/Matrix3D.h"
      28             : #include "maths/Vector3D.h"
      29             : #include "maths/Vector2D.h"
      30             : #include "ps/CLogger.h"
      31             : #include "ps/Profile.h"
      32             : #include "renderer/Scene.h"
      33             : #include "renderer/Renderer.h"
      34             : #include "simulation2/MessageTypes.h"
      35             : #include "simulation2/components/ICmpPosition.h"
      36             : #include "simulation2/components/ICmpFootprint.h"
      37             : #include "simulation2/components/ICmpVisual.h"
      38             : #include "simulation2/components/ICmpTerrain.h"
      39             : #include "simulation2/components/ICmpOwnership.h"
      40             : #include "simulation2/components/ICmpPlayer.h"
      41             : #include "simulation2/components/ICmpPlayerManager.h"
      42             : #include "simulation2/components/ICmpWaterManager.h"
      43             : #include "simulation2/helpers/Render.h"
      44             : #include "simulation2/system/Component.h"
      45             : 
      46             : // Minimum alpha value for always visible overlays [0 fully transparent, 1 fully opaque]
      47             : static const float MIN_ALPHA_ALWAYS_VISIBLE = 0.65f;
      48             : // Minimum alpha value for other overlays
      49             : static const float MIN_ALPHA_UNSELECTED = 0.0f;
      50             : // Desaturation value for unselected, always visible overlays (0.33 = 33% desaturated or 66% of original saturation)
      51             : static const float RGB_DESATURATION = 0.333333f;
      52             : 
      53             : class CCmpSelectable final : public ICmpSelectable
      54             : {
      55             : public:
      56             :     enum EShape
      57             :     {
      58             :         FOOTPRINT,
      59             :         CIRCLE,
      60             :         SQUARE
      61             :     };
      62             : 
      63         116 :     static void ClassInit(CComponentManager& componentManager)
      64             :     {
      65         116 :         componentManager.SubscribeToMessageType(MT_OwnershipChanged);
      66         116 :         componentManager.SubscribeToMessageType(MT_PlayerColorChanged);
      67         116 :         componentManager.SubscribeToMessageType(MT_PositionChanged);
      68         116 :         componentManager.SubscribeToMessageType(MT_TerrainChanged);
      69         116 :         componentManager.SubscribeToMessageType(MT_WaterChanged);
      70         116 :     }
      71             : 
      72           0 :     DEFAULT_COMPONENT_ALLOCATOR(Selectable)
      73             : 
      74           0 :     CCmpSelectable()
      75           0 :         : m_DebugBoundingBoxOverlay(NULL), m_DebugSelectionBoxOverlay(NULL),
      76             :           m_BuildingOverlay(NULL), m_UnitOverlay(NULL),
      77             :           m_FadeBaselineAlpha(0.f), m_FadeDeltaAlpha(0.f), m_FadeProgress(0.f),
      78           0 :           m_Selected(false), m_Cached(false), m_Visible(false)
      79             :     {
      80           0 :         m_Color = CColor(0, 0, 0, m_FadeBaselineAlpha);
      81           0 :     }
      82             : 
      83           0 :     ~CCmpSelectable()
      84           0 :     {
      85           0 :         delete m_DebugBoundingBoxOverlay;
      86           0 :         delete m_DebugSelectionBoxOverlay;
      87           0 :         delete m_BuildingOverlay;
      88           0 :         delete m_UnitOverlay;
      89           0 :     }
      90             : 
      91         116 :     static std::string GetSchema()
      92             :     {
      93             :         return
      94             :             "<a:help>Allows this entity to be selected by the player.</a:help>"
      95             :             "<a:example/>"
      96             :             "<optional>"
      97             :                 "<element name='EditorOnly' a:help='If this element is present, the entity is only selectable in Atlas'>"
      98             :                     "<empty/>"
      99             :                 "</element>"
     100             :             "</optional>"
     101             :             "<element name='Overlay' a:help='Specifies the type of overlay to be displayed when this entity is selected.'>"
     102             :                 "<interleave>"
     103             :                     "<optional>"
     104             :                         "<element name='Shape' a:help='Specifies shape of overlay. If not specified, footprint shape will be used.'>"
     105             :                             "<choice>"
     106             :                                 "<element name='Square' a:help='Set the overlay to a square of the given size'>"
     107             :                                     "<attribute name='width' a:help='Size of the overlay along the left/right direction (in metres)'>"
     108             :                                         "<data type='decimal'>"
     109             :                                             "<param name='minExclusive'>0.0</param>"
     110             :                                         "</data>"
     111             :                                     "</attribute>"
     112             :                                     "<attribute name='depth' a:help='Size of the overlay along the front/back direction (in metres)'>"
     113             :                                         "<data type='decimal'>"
     114             :                                             "<param name='minExclusive'>0.0</param>"
     115             :                                         "</data>"
     116             :                                     "</attribute>"
     117             :                                 "</element>"
     118             :                                 "<element name='Circle' a:help='Set the overlay to a circle of the given size'>"
     119             :                                     "<attribute name='radius' a:help='Radius of the overlay (in metres)'>"
     120             :                                         "<data type='decimal'>"
     121             :                                             "<param name='minExclusive'>0.0</param>"
     122             :                                         "</data>"
     123             :                                     "</attribute>"
     124             :                                 "</element>"
     125             :                             "</choice>"
     126             :                         "</element>"
     127             :                     "</optional>"
     128             :                     "<optional>"
     129             :                         "<element name='AlwaysVisible' a:help='If this element is present, the selection overlay will always be visible (with transparency and desaturation)'>"
     130             :                             "<empty/>"
     131             :                         "</element>"
     132             :                     "</optional>"
     133             :                     "<choice>"
     134             :                         "<element name='Texture' a:help='Displays a texture underneath the entity.'>"
     135             :                             "<element name='MainTexture' a:help='Texture to display underneath the entity. Filepath relative to art/textures/selection/.'><text/></element>"
     136             :                             "<element name='MainTextureMask' a:help='Mask texture that controls where to apply player color. Filepath relative to art/textures/selection/.'><text/></element>"
     137             :                         "</element>"
     138             :                         "<element name='Outline' a:help='Traces the outline of the entity with a line texture.'>"
     139             :                             "<element name='LineTexture' a:help='Texture to apply to the line. Filepath relative to art/textures/selection/.'><text/></element>"
     140             :                             "<element name='LineTextureMask' a:help='Texture that controls where to apply player color. Filepath relative to art/textures/selection/.'><text/></element>"
     141             :                             "<element name='LineThickness' a:help='Thickness of the line, in world units.'><ref name='positiveDecimal'/></element>"
     142             :                         "</element>"
     143             :                     "</choice>"
     144             :                 "</interleave>"
     145         116 :             "</element>";
     146             :     }
     147             : 
     148             :     EShape m_Shape;
     149             :     entity_pos_t m_Width; // width/radius
     150             :     entity_pos_t m_Height; // height/radius
     151             : 
     152           0 :     void Init(const CParamNode& paramNode) override
     153             :     {
     154           0 :         m_EditorOnly = paramNode.GetChild("EditorOnly").IsOk();
     155             : 
     156             :         // Certain special units always have their selection overlay shown
     157           0 :         m_AlwaysVisible = paramNode.GetChild("Overlay").GetChild("AlwaysVisible").IsOk();
     158           0 :         if (m_AlwaysVisible)
     159             :         {
     160           0 :             m_AlphaMin = MIN_ALPHA_ALWAYS_VISIBLE;
     161           0 :             m_Color.a = m_AlphaMin;
     162             :         }
     163             :         else
     164           0 :             m_AlphaMin = MIN_ALPHA_UNSELECTED;
     165             : 
     166           0 :         const CParamNode& textureNode = paramNode.GetChild("Overlay").GetChild("Texture");
     167           0 :         const CParamNode& outlineNode = paramNode.GetChild("Overlay").GetChild("Outline");
     168             : 
     169             :         // Save some memory by using interned file paths in these descriptors (almost all actors and
     170             :         // entities have this component, and many use the same textures).
     171           0 :         if (textureNode.IsOk())
     172             :         {
     173             :             // textured quad mode (dynamic, for units)
     174           0 :             m_OverlayDescriptor.m_Type = DYNAMIC_QUAD;
     175           0 :             m_OverlayDescriptor.m_QuadTexture = CStrIntern(TEXTUREBASEPATH + textureNode.GetChild("MainTexture").ToString());
     176           0 :             m_OverlayDescriptor.m_QuadTextureMask = CStrIntern(TEXTUREBASEPATH + textureNode.GetChild("MainTextureMask").ToString());
     177             :         }
     178           0 :         else if (outlineNode.IsOk())
     179             :         {
     180             :             // textured outline mode (static, for buildings)
     181           0 :             m_OverlayDescriptor.m_Type = STATIC_OUTLINE;
     182           0 :             m_OverlayDescriptor.m_LineTexture = CStrIntern(TEXTUREBASEPATH + outlineNode.GetChild("LineTexture").ToString());
     183           0 :             m_OverlayDescriptor.m_LineTextureMask = CStrIntern(TEXTUREBASEPATH + outlineNode.GetChild("LineTextureMask").ToString());
     184           0 :             m_OverlayDescriptor.m_LineThickness = outlineNode.GetChild("LineThickness").ToFloat();
     185             :         }
     186             : 
     187           0 :         const CParamNode& shapeNode = paramNode.GetChild("Overlay").GetChild("Shape");
     188           0 :         if (shapeNode.IsOk())
     189             :         {
     190           0 :             if (shapeNode.GetChild("Square").IsOk())
     191             :             {
     192           0 :                 m_Shape = SQUARE;
     193           0 :                 m_Width = shapeNode.GetChild("Square").GetChild("@width").ToFixed();
     194           0 :                 m_Height = shapeNode.GetChild("Square").GetChild("@depth").ToFixed();
     195             :             }
     196           0 :             else if (shapeNode.GetChild("Circle").IsOk())
     197             :             {
     198           0 :                 m_Shape = CIRCLE;
     199           0 :                 m_Width = m_Height = shapeNode.GetChild("Circle").GetChild("@radius").ToFixed();
     200             :             }
     201             :             else
     202             :             {
     203             :                 // Should not happen
     204           0 :                 m_Shape = FOOTPRINT;
     205           0 :                 LOGWARNING("[Selectable] Selected overlay shape is not implemented.");
     206             :             }
     207             :         }
     208             :         else
     209             :         {
     210           0 :             m_Shape = FOOTPRINT;
     211             :         }
     212             : 
     213           0 :         m_EnabledInterpolate = false;
     214           0 :         m_EnabledRenderSubmit = false;
     215           0 :         UpdateMessageSubscriptions();
     216           0 :     }
     217             : 
     218           0 :     void Deinit() override { }
     219             : 
     220           0 :     void Serialize(ISerializer& UNUSED(serialize)) override
     221             :     {
     222             :         // Nothing to do here (the overlay object is not worth saving, it'll get
     223             :         // reconstructed by the GUI soon enough, I think)
     224           0 :     }
     225             : 
     226           0 :     void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) override
     227             :     {
     228             :         // Need to call Init to reload the template properties
     229           0 :         Init(paramNode);
     230           0 :     }
     231             : 
     232             :     void HandleMessage(const CMessage& msg, bool UNUSED(global)) override;
     233             : 
     234           0 :     void SetSelectionHighlight(const CColor& color, bool selected) override
     235             :     {
     236           0 :         m_Selected = selected;
     237           0 :         m_Color.r = color.r;
     238           0 :         m_Color.g = color.g;
     239           0 :         m_Color.b = color.b;
     240             : 
     241             :         // Always-visible overlays will be desaturated if their parent unit is deselected.
     242           0 :         if (m_AlwaysVisible && !selected)
     243             :         {
     244             :             float max;
     245             : 
     246             :             // Reduce saturation by one-third, the quick-and-dirty way.
     247           0 :             if (m_Color.r > m_Color.b)
     248           0 :                 max = (m_Color.r > m_Color.g) ? m_Color.r : m_Color.g;
     249             :             else
     250           0 :                 max = (m_Color.b > m_Color.g) ? m_Color.b : m_Color.g;
     251             : 
     252           0 :             m_Color.r += (max - m_Color.r) * RGB_DESATURATION;
     253           0 :             m_Color.g += (max - m_Color.g) * RGB_DESATURATION;
     254           0 :             m_Color.b += (max - m_Color.b) * RGB_DESATURATION;
     255             :         }
     256             : 
     257           0 :         SetSelectionHighlightAlpha(color.a);
     258           0 :     }
     259             : 
     260           0 :     void SetSelectionHighlightAlpha(float alpha) override
     261             :     {
     262           0 :         alpha = std::max(m_AlphaMin, alpha);
     263             : 
     264             :         // set up fading from the current value (as the baseline) to the target value
     265           0 :         m_FadeBaselineAlpha = m_Color.a;
     266           0 :         m_FadeDeltaAlpha = alpha - m_FadeBaselineAlpha;
     267           0 :         m_FadeProgress = 0.f;
     268             : 
     269           0 :         UpdateMessageSubscriptions();
     270           0 :     }
     271             : 
     272           0 :     void SetVisibility(bool visible) override
     273             :     {
     274           0 :         m_Visible = visible;
     275           0 :         UpdateMessageSubscriptions();
     276           0 :     }
     277             : 
     278           0 :     bool IsEditorOnly() const override
     279             :     {
     280           0 :         return m_EditorOnly;
     281             :     }
     282             : 
     283             :     void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
     284             : 
     285             :     /**
     286             :      * Draw a textured line overlay.
     287             :      */
     288             :     void UpdateTexturedLineOverlay(const SOverlayDescriptor* overlayDescriptor, SOverlayTexturedLine& overlay, float frameOffset);
     289             : 
     290             :     /**
     291             :      * Called from the interpolation handler; responsible for ensuring the dynamic overlay (provided we're
     292             :      * using one) is up-to-date and ready to be submitted to the next rendering run.
     293             :      */
     294             :     void UpdateDynamicOverlay(float frameOffset);
     295             : 
     296             :     /// Explicitly invalidates the static overlay.
     297             :     void InvalidateStaticOverlay();
     298             : 
     299             :     /**
     300             :      * Subscribe/unsubscribe to MT_Interpolate, MT_RenderSubmit, depending on
     301             :      * whether we will do any actual work when receiving them. (This is to avoid
     302             :      * the performance cost of receiving messages in the typical case when the
     303             :      * entity is not selected.)
     304             :      *
     305             :      * Must be called after changing m_Visible, m_FadeDeltaAlpha, m_Color.a
     306             :      */
     307             :     void UpdateMessageSubscriptions();
     308             : 
     309             :     /**
     310             :      * Set the color of the current owner.
     311             :      */
     312             :     void UpdateColor() override;
     313             : 
     314             : private:
     315             :     SOverlayDescriptor m_OverlayDescriptor;
     316             :     SOverlayTexturedLine* m_BuildingOverlay;
     317             :     SOverlayQuad* m_UnitOverlay;
     318             : 
     319             :     CBoundingBoxAligned m_UnitOverlayBoundingBox;
     320             : 
     321             :     SOverlayLine* m_DebugBoundingBoxOverlay;
     322             :     SOverlayLine* m_DebugSelectionBoxOverlay;
     323             : 
     324             :     bool m_EnabledInterpolate;
     325             :     bool m_EnabledRenderSubmit;
     326             : 
     327             :     // Whether the selectable will be rendered.
     328             :     bool m_Visible;
     329             :     // Whether the entity is only selectable in Atlas editor
     330             :     bool m_EditorOnly;
     331             :     // Whether the selection overlay is always visible
     332             :     bool m_AlwaysVisible;
     333             :     /// Whether the parent entity is selected (caches GUI's selection state).
     334             :     bool m_Selected;
     335             :     /// Current selection overlay color. Alpha component is subject to fading.
     336             :     CColor m_Color;
     337             :     /// Whether the selectable's player color has been cached for rendering.
     338             :     bool m_Cached;
     339             :     /// Minimum value for current selection overlay alpha.
     340             :     float m_AlphaMin;
     341             :     /// Baseline alpha value to start fading from. Constant during a single fade.
     342             :     float m_FadeBaselineAlpha;
     343             :     /// Delta between target and baseline alpha. Constant during a single fade. Can be positive or negative.
     344             :     float m_FadeDeltaAlpha;
     345             :     /// Linear time progress of the fade, between 0 and m_FadeDuration.
     346             :     float m_FadeProgress;
     347             : 
     348             :     /// Total duration of a single fade, in seconds. Assumed constant for now; feel free to change this into
     349             :     /// a member variable if you need to adjust it per component.
     350             :     static const float FADE_DURATION;
     351             :     static const char* TEXTUREBASEPATH;
     352             : };
     353             : 
     354             : const float CCmpSelectable::FADE_DURATION = 0.3f;
     355             : const char* CCmpSelectable::TEXTUREBASEPATH = "art/textures/selection/";
     356             : 
     357           0 : void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global))
     358             : {
     359           0 :     switch (msg.GetType())
     360             :     {
     361           0 :     case MT_Interpolate:
     362             :     {
     363           0 :         PROFILE("Selectable::Interpolate");
     364             : 
     365           0 :         const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
     366             : 
     367           0 :         if (m_FadeDeltaAlpha != 0.f)
     368             :         {
     369           0 :             m_FadeProgress += msgData.deltaRealTime;
     370           0 :             if (m_FadeProgress >= FADE_DURATION)
     371             :             {
     372           0 :                 const float targetAlpha = m_FadeBaselineAlpha + m_FadeDeltaAlpha;
     373             : 
     374             :                 // stop the fade
     375           0 :                 m_Color.a = targetAlpha;
     376           0 :                 m_FadeBaselineAlpha = targetAlpha;
     377           0 :                 m_FadeDeltaAlpha = 0.f;
     378           0 :                 m_FadeProgress = FADE_DURATION; // will need to be reset to start the next fade again
     379             :             }
     380             :             else
     381             :             {
     382           0 :                 m_Color.a = Ease::QuartOut(m_FadeProgress, m_FadeBaselineAlpha, m_FadeDeltaAlpha, FADE_DURATION);
     383             :             }
     384             :         }
     385             : 
     386             :         // update dynamic overlay only when visible
     387           0 :         if (m_Color.a > 0)
     388           0 :             UpdateDynamicOverlay(msgData.offset);
     389             : 
     390           0 :         UpdateMessageSubscriptions();
     391             : 
     392           0 :         break;
     393             :     }
     394           0 :     case MT_OwnershipChanged:
     395             :     {
     396           0 :         const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
     397             : 
     398             :         // Ignore newly constructed entities, as they receive their color upon first selection
     399             :         // Ignore deleted entities because they won't be rendered
     400           0 :         if (msgData.from == INVALID_PLAYER || msgData.to == INVALID_PLAYER)
     401             :             break;
     402             : 
     403           0 :         UpdateColor();
     404           0 :         InvalidateStaticOverlay();
     405           0 :         break;
     406             :     }
     407           0 :     case MT_PlayerColorChanged:
     408             :     {
     409           0 :         const CMessagePlayerColorChanged& msgData = static_cast<const CMessagePlayerColorChanged&> (msg);
     410             : 
     411           0 :         CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
     412           0 :         if (!cmpOwnership || msgData.player != cmpOwnership->GetOwner())
     413           0 :             break;
     414             : 
     415           0 :         UpdateColor();
     416           0 :         break;
     417             :     }
     418           0 :     case MT_PositionChanged:
     419             :     case MT_TerrainChanged:
     420             :     case MT_WaterChanged:
     421           0 :         InvalidateStaticOverlay();
     422           0 :         break;
     423           0 :     case MT_RenderSubmit:
     424             :     {
     425           0 :         PROFILE("Selectable::RenderSubmit");
     426             : 
     427           0 :         const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
     428           0 :         RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
     429             : 
     430           0 :         break;
     431             :     }
     432             :     }
     433           0 : }
     434             : 
     435           0 : void CCmpSelectable::UpdateColor()
     436             : {
     437           0 :     CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
     438             : 
     439           0 :     CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
     440           0 :     if (!cmpPlayerManager)
     441           0 :         return;
     442             : 
     443             :     // Default to white if there's no owner (e.g. decorative, editor-only actors)
     444           0 :     CColor color(1.0, 1.0, 1.0, 1.0);
     445             : 
     446           0 :     if (cmpOwnership)
     447             :     {
     448           0 :         CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(cmpOwnership->GetOwner()));
     449           0 :         if (cmpPlayer)
     450           0 :             color = cmpPlayer->GetDisplayedColor();
     451             :     }
     452             : 
     453             :     // Update the highlight color, while keeping the current alpha target value intact
     454             :     // (i.e. baseline + delta), so that any ongoing fades simply continue with the new color.
     455           0 :     color.a = m_FadeBaselineAlpha + m_FadeDeltaAlpha;
     456             : 
     457           0 :     SetSelectionHighlight(color, m_Selected);
     458             : }
     459             : 
     460           0 : void CCmpSelectable::UpdateMessageSubscriptions()
     461             : {
     462           0 :     bool needInterpolate = false;
     463           0 :     bool needRenderSubmit = false;
     464             : 
     465           0 :     if (m_FadeDeltaAlpha != 0.f || m_Color.a > 0)
     466           0 :         needInterpolate = true;
     467             : 
     468           0 :     if (m_Visible && m_Color.a > 0)
     469           0 :         needRenderSubmit = true;
     470             : 
     471           0 :     if (needInterpolate != m_EnabledInterpolate)
     472             :     {
     473           0 :         GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, needInterpolate);
     474           0 :         m_EnabledInterpolate = needInterpolate;
     475             :     }
     476             : 
     477           0 :     if (needRenderSubmit != m_EnabledRenderSubmit)
     478             :     {
     479           0 :         GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRenderSubmit);
     480           0 :         m_EnabledRenderSubmit = needRenderSubmit;
     481             :     }
     482           0 : }
     483             : 
     484           0 : void CCmpSelectable::InvalidateStaticOverlay()
     485             : {
     486           0 :     SAFE_DELETE(m_BuildingOverlay);
     487           0 : }
     488             : 
     489           0 : void CCmpSelectable::UpdateTexturedLineOverlay(const SOverlayDescriptor* overlayDescriptor, SOverlayTexturedLine& overlay, float frameOffset)
     490             : {
     491           0 :     if (!CRenderer::IsInitialised())
     492           0 :         return;
     493             : 
     494           0 :     CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     495           0 :     if (!cmpPosition || !cmpPosition->IsInWorld())
     496           0 :         return;
     497             : 
     498           0 :     ICmpFootprint::EShape fpShape = ICmpFootprint::CIRCLE;
     499           0 :     if (m_Shape == FOOTPRINT)
     500             :     {
     501           0 :         CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
     502           0 :         if (!cmpFootprint)
     503           0 :             return;
     504           0 :         entity_pos_t h;
     505           0 :         cmpFootprint->GetShape(fpShape, m_Width, m_Height, h);
     506             :     }
     507             :     float rotY;
     508           0 :     CVector2D origin;
     509           0 :     cmpPosition->GetInterpolatedPosition2D(frameOffset, origin.X, origin.Y, rotY);
     510             : 
     511           0 :     overlay.m_SimContext = &GetSimContext();
     512           0 :     overlay.m_Color = m_Color;
     513           0 :     overlay.CreateOverlayTexture(overlayDescriptor);
     514             : 
     515           0 :     if (m_Shape == SQUARE || (m_Shape == FOOTPRINT && fpShape == ICmpFootprint::SQUARE))
     516           0 :         SimRender::ConstructTexturedLineBox(overlay, origin, cmpPosition->GetRotation(), m_Width.ToFloat(), m_Height.ToFloat());
     517             :     else
     518           0 :         SimRender::ConstructTexturedLineCircle(overlay, origin, m_Width.ToFloat());
     519             : }
     520             : 
     521           0 : void CCmpSelectable::UpdateDynamicOverlay(float frameOffset)
     522             : {
     523             :     // Dynamic overlay lines are allocated once and never deleted. Since they are expected to change frequently,
     524             :     // they are assumed dirty on every call to this function, and we should therefore use this function more
     525             :     // thoughtfully than calling it right before every frame render.
     526             : 
     527           0 :     if (m_OverlayDescriptor.m_Type != DYNAMIC_QUAD)
     528           0 :         return;
     529             : 
     530           0 :     if (!CRenderer::IsInitialised())
     531           0 :         return;
     532             : 
     533           0 :     CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     534           0 :     if (!cmpPosition || !cmpPosition->IsInWorld())
     535           0 :         return;
     536             : 
     537           0 :     ICmpFootprint::EShape fpShape = ICmpFootprint::CIRCLE;
     538           0 :     if (m_Shape == FOOTPRINT)
     539             :     {
     540           0 :         CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
     541           0 :         if (!cmpFootprint)
     542           0 :             return;
     543           0 :         entity_pos_t h;
     544           0 :         cmpFootprint->GetShape(fpShape, m_Width, m_Height, h);
     545             :     }
     546             : 
     547             :     float rotY;
     548           0 :     CVector2D position;
     549           0 :     cmpPosition->GetInterpolatedPosition2D(frameOffset, position.X, position.Y, rotY);
     550             : 
     551           0 :     CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
     552           0 :     CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     553           0 :     ENSURE(cmpWaterManager && cmpTerrain);
     554             : 
     555           0 :     CTerrain* terrain = cmpTerrain->GetCTerrain();
     556           0 :     ENSURE(terrain);
     557             : 
     558             :     // ---------------------------------------------------------------------------------
     559             : 
     560           0 :     if (!m_UnitOverlay)
     561             :     {
     562           0 :         m_UnitOverlay = new SOverlayQuad;
     563             : 
     564             :         // Assuming we don't need the capability of swapping textures on-demand.
     565           0 :         CTextureProperties texturePropsBase(m_OverlayDescriptor.m_QuadTexture.c_str());
     566           0 :         texturePropsBase.SetAddressMode(
     567             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
     568             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     569           0 :         texturePropsBase.SetAnisotropicFilter(true);
     570             : 
     571           0 :         CTextureProperties texturePropsMask(m_OverlayDescriptor.m_QuadTextureMask.c_str());
     572           0 :         texturePropsMask.SetAddressMode(
     573             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
     574             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     575           0 :         texturePropsMask.SetAnisotropicFilter(true);
     576             : 
     577           0 :         m_UnitOverlay->m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
     578           0 :         m_UnitOverlay->m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
     579             :     }
     580             : 
     581           0 :     m_UnitOverlay->m_Color = m_Color;
     582             : 
     583             :     // TODO: some code duplication here :< would be nice to factor out getting the corner points of an
     584             :     // entity based on its footprint sizes (and regardless of whether it's a circle or a square)
     585             : 
     586           0 :     float s = sinf(-rotY);
     587           0 :     float c = cosf(-rotY);
     588           0 :     CVector2D unitX(c, s);
     589           0 :     CVector2D unitZ(-s, c);
     590             : 
     591           0 :     float halfSizeX = m_Width.ToFloat();
     592           0 :     float halfSizeZ = m_Height.ToFloat();
     593           0 :     if (m_Shape == SQUARE || (m_Shape == FOOTPRINT && fpShape == ICmpFootprint::SQUARE))
     594             :     {
     595           0 :         halfSizeX /= 2.0f;
     596           0 :         halfSizeZ /= 2.0f;
     597             :     }
     598             : 
     599           0 :     std::vector<CVector2D> points;
     600           0 :     points.push_back(CVector2D(position + unitX *(-halfSizeX)   + unitZ *  halfSizeZ));  // top left
     601           0 :     points.push_back(CVector2D(position + unitX *(-halfSizeX)   + unitZ *(-halfSizeZ))); // bottom left
     602           0 :     points.push_back(CVector2D(position + unitX *  halfSizeX    + unitZ *(-halfSizeZ))); // bottom right
     603           0 :     points.push_back(CVector2D(position + unitX *  halfSizeX    + unitZ *  halfSizeZ));  // top right
     604             : 
     605           0 :     m_UnitOverlayBoundingBox = CBoundingBoxAligned();
     606           0 :     for (size_t i = 0; i < 4; ++i)
     607             :     {
     608             :         float quadY = std::max(
     609           0 :             terrain->GetExactGroundLevel(points[i].X, points[i].Y),
     610           0 :             cmpWaterManager->GetExactWaterLevel(points[i].X, points[i].Y)
     611           0 :         );
     612             : 
     613           0 :         m_UnitOverlay->m_Corners[i] = CVector3D(points[i].X, quadY, points[i].Y);
     614           0 :         m_UnitOverlayBoundingBox += m_UnitOverlay->m_Corners[i];
     615             :     }
     616             : }
     617             : 
     618           0 : void CCmpSelectable::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
     619             : {
     620             :     // don't render selection overlay if it's not gonna be visible
     621           0 :     if (!ICmpSelectable::m_OverrideVisible)
     622           0 :         return;
     623             : 
     624           0 :     if (m_Visible && m_Color.a > 0)
     625             :     {
     626           0 :         if (!m_Cached)
     627             :         {
     628           0 :             UpdateColor();
     629           0 :             m_Cached = true;
     630             :         }
     631             : 
     632           0 :         switch (m_OverlayDescriptor.m_Type)
     633             :         {
     634           0 :             case STATIC_OUTLINE:
     635             :                 {
     636           0 :                     if (!m_BuildingOverlay)
     637             :                     {
     638             :                         // Static overlays are allocated once and not updated until they are explicitly deleted again
     639             :                         // (see InvalidateStaticOverlay). Since they are expected to change rarely (if ever) during
     640             :                         // normal gameplay, this saves us doing all the work below on each frame.
     641           0 :                         m_BuildingOverlay = new SOverlayTexturedLine;
     642           0 :                         UpdateTexturedLineOverlay(&m_OverlayDescriptor, *m_BuildingOverlay, 0);
     643             :                     }
     644           0 :                     m_BuildingOverlay->m_Color = m_Color; // done separately so alpha changes don't require a full update call
     645           0 :                     if (culling && !m_BuildingOverlay->IsVisibleInFrustum(frustum))
     646           0 :                         break;
     647           0 :                     collector.Submit(m_BuildingOverlay);
     648             :                 }
     649           0 :                 break;
     650           0 :             case DYNAMIC_QUAD:
     651             :                 {
     652           0 :                     if (culling && !frustum.IsBoxVisible(m_UnitOverlayBoundingBox))
     653           0 :                         break;
     654           0 :                     if (m_UnitOverlay)
     655           0 :                         collector.Submit(m_UnitOverlay);
     656             :                 }
     657           0 :                 break;
     658           0 :             default:
     659           0 :                 break;
     660             :         }
     661             :     }
     662             : 
     663             :     // Render bounding box debug overlays if we have a positive target alpha value. This ensures
     664             :     // that the debug overlays respond immediately to deselection without delay from fading out.
     665           0 :     if (m_FadeBaselineAlpha + m_FadeDeltaAlpha > 0)
     666             :     {
     667           0 :         if (ICmpSelectable::ms_EnableDebugOverlays)
     668             :         {
     669             :             // allocate debug overlays on-demand
     670           0 :             if (!m_DebugBoundingBoxOverlay) m_DebugBoundingBoxOverlay = new SOverlayLine;
     671           0 :             if (!m_DebugSelectionBoxOverlay) m_DebugSelectionBoxOverlay = new SOverlayLine;
     672             : 
     673           0 :             CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle());
     674           0 :             if (cmpVisual)
     675             :             {
     676           0 :                 SimRender::ConstructBoxOutline(cmpVisual->GetBounds(), *m_DebugBoundingBoxOverlay);
     677           0 :                 m_DebugBoundingBoxOverlay->m_Thickness = 0.1f;
     678           0 :                 m_DebugBoundingBoxOverlay->m_Color = CColor(1.f, 0.f, 0.f, 1.f);
     679             : 
     680           0 :                 SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), *m_DebugSelectionBoxOverlay);
     681           0 :                 m_DebugSelectionBoxOverlay->m_Thickness = 0.1f;
     682           0 :                 m_DebugSelectionBoxOverlay->m_Color = CColor(0.f, 1.f, 0.f, 1.f);
     683             : 
     684           0 :                 collector.Submit(m_DebugBoundingBoxOverlay);
     685           0 :                 collector.Submit(m_DebugSelectionBoxOverlay);
     686             :             }
     687             :         }
     688             :         else
     689             :         {
     690             :             // reclaim debug overlay line memory when no longer debugging (and make sure to set to zero after deletion)
     691           0 :             if (m_DebugBoundingBoxOverlay) SAFE_DELETE(m_DebugBoundingBoxOverlay);
     692           0 :             if (m_DebugSelectionBoxOverlay) SAFE_DELETE(m_DebugSelectionBoxOverlay);
     693             :         }
     694             :     }
     695             : }
     696             : 
     697         119 : REGISTER_COMPONENT_TYPE(Selectable)

Generated by: LCOV version 1.13