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 287 3.5 %
Date: 2022-06-14 00:41:00 Functions: 2 23 8.7 %

          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         115 :     static void ClassInit(CComponentManager& componentManager)
      64             :     {
      65         115 :         componentManager.SubscribeToMessageType(MT_OwnershipChanged);
      66         115 :         componentManager.SubscribeToMessageType(MT_PlayerColorChanged);
      67         115 :         componentManager.SubscribeToMessageType(MT_PositionChanged);
      68         115 :         componentManager.SubscribeToMessageType(MT_TerrainChanged);
      69         115 :         componentManager.SubscribeToMessageType(MT_WaterChanged);
      70         115 :     }
      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           0 : 
      91             :     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           0 :             "<optional>"
      97           0 :                 "<element name='EditorOnly' a:help='If this element is present, the entity is only selectable in Atlas'>"
      98           0 :                     "<empty/>"
      99           0 :                 "</element>"
     100           0 :             "</optional>"
     101           0 :             "<element name='Overlay' a:help='Specifies the type of overlay to be displayed when this entity is selected.'>"
     102           0 :                 "<interleave>"
     103           0 :                     "<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         115 :                                     "<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             :             "</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             :     void Init(const CParamNode& paramNode) override
     153             :     {
     154             :         m_EditorOnly = paramNode.GetChild("EditorOnly").IsOk();
     155             : 
     156             :         // Certain special units always have their selection overlay shown
     157             :         m_AlwaysVisible = paramNode.GetChild("Overlay").GetChild("AlwaysVisible").IsOk();
     158             :         if (m_AlwaysVisible)
     159         115 :         {
     160             :             m_AlphaMin = MIN_ALPHA_ALWAYS_VISIBLE;
     161             :             m_Color.a = m_AlphaMin;
     162             :         }
     163             :         else
     164             :             m_AlphaMin = MIN_ALPHA_UNSELECTED;
     165             : 
     166           0 :         const CParamNode& textureNode = paramNode.GetChild("Overlay").GetChild("Texture");
     167             :         const CParamNode& outlineNode = paramNode.GetChild("Overlay").GetChild("Outline");
     168           0 : 
     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           0 :         {
     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             :             m_OverlayDescriptor.m_QuadTextureMask = CStrIntern(TEXTUREBASEPATH + textureNode.GetChild("MainTextureMask").ToString());
     177             :         }
     178           0 :         else if (outlineNode.IsOk())
     179             :         {
     180           0 :             // textured outline mode (static, for buildings)
     181           0 :             m_OverlayDescriptor.m_Type = STATIC_OUTLINE;
     182             :             m_OverlayDescriptor.m_LineTexture = CStrIntern(TEXTUREBASEPATH + outlineNode.GetChild("LineTexture").ToString());
     183             :             m_OverlayDescriptor.m_LineTextureMask = CStrIntern(TEXTUREBASEPATH + outlineNode.GetChild("LineTextureMask").ToString());
     184             :             m_OverlayDescriptor.m_LineThickness = outlineNode.GetChild("LineThickness").ToFloat();
     185           0 :         }
     186             : 
     187             :         const CParamNode& shapeNode = paramNode.GetChild("Overlay").GetChild("Shape");
     188           0 :         if (shapeNode.IsOk())
     189           0 :         {
     190           0 :             if (shapeNode.GetChild("Square").IsOk())
     191             :             {
     192           0 :                 m_Shape = SQUARE;
     193             :                 m_Width = shapeNode.GetChild("Square").GetChild("@width").ToFixed();
     194             :                 m_Height = shapeNode.GetChild("Square").GetChild("@depth").ToFixed();
     195           0 :             }
     196           0 :             else if (shapeNode.GetChild("Circle").IsOk())
     197           0 :             {
     198           0 :                 m_Shape = CIRCLE;
     199             :                 m_Width = m_Height = shapeNode.GetChild("Circle").GetChild("@radius").ToFixed();
     200             :             }
     201           0 :             else
     202           0 :             {
     203             :                 // Should not happen
     204           0 :                 m_Shape = FOOTPRINT;
     205             :                 LOGWARNING("[Selectable] Selected overlay shape is not implemented.");
     206           0 :             }
     207           0 :         }
     208           0 :         else
     209             :         {
     210           0 :             m_Shape = FOOTPRINT;
     211             :         }
     212           0 : 
     213           0 :         m_EnabledInterpolate = false;
     214             :         m_EnabledRenderSubmit = false;
     215             :         UpdateMessageSubscriptions();
     216             :     }
     217             : 
     218           0 :     void Deinit() override { }
     219           0 : 
     220             :     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             :     void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) override
     227           0 :     {
     228           0 :         // Need to call Init to reload the template properties
     229           0 :         Init(paramNode);
     230           0 :     }
     231             : 
     232           0 :     void HandleMessage(const CMessage& msg, bool UNUSED(global)) override;
     233             : 
     234           0 :     void SetSelectionHighlight(const CColor& color, bool selected) override
     235             :     {
     236             :         m_Selected = selected;
     237             :         m_Color.r = color.r;
     238           0 :         m_Color.g = color.g;
     239             :         m_Color.b = color.b;
     240           0 : 
     241             :         // Always-visible overlays will be desaturated if their parent unit is deselected.
     242             :         if (m_AlwaysVisible && !selected)
     243           0 :         {
     244           0 :             float max;
     245             : 
     246             :             // Reduce saturation by one-third, the quick-and-dirty way.
     247             :             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           0 : 
     252           0 :             m_Color.r += (max - m_Color.r) * RGB_DESATURATION;
     253           0 :             m_Color.g += (max - m_Color.g) * RGB_DESATURATION;
     254             :             m_Color.b += (max - m_Color.b) * RGB_DESATURATION;
     255             :         }
     256           0 : 
     257             :         SetSelectionHighlightAlpha(color.a);
     258           0 :     }
     259             : 
     260             :     void SetSelectionHighlightAlpha(float alpha) override
     261           0 :     {
     262           0 :         alpha = std::max(m_AlphaMin, alpha);
     263             : 
     264           0 :         // set up fading from the current value (as the baseline) to the target value
     265             :         m_FadeBaselineAlpha = m_Color.a;
     266           0 :         m_FadeDeltaAlpha = alpha - m_FadeBaselineAlpha;
     267           0 :         m_FadeProgress = 0.f;
     268           0 : 
     269             :         UpdateMessageSubscriptions();
     270             :     }
     271           0 : 
     272           0 :     void SetVisibility(bool visible) override
     273             :     {
     274           0 :         m_Visible = visible;
     275             :         UpdateMessageSubscriptions();
     276           0 :     }
     277             : 
     278             :     bool IsEditorOnly() const override
     279           0 :     {
     280           0 :         return m_EditorOnly;
     281           0 :     }
     282             : 
     283           0 :     void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
     284           0 : 
     285             :     /**
     286           0 :      * Draw a textured line overlay.
     287             :      */
     288           0 :     void UpdateTexturedLineOverlay(const SOverlayDescriptor* overlayDescriptor, SOverlayTexturedLine& overlay, float frameOffset);
     289           0 : 
     290           0 :     /**
     291             :      * Called from the interpolation handler; responsible for ensuring the dynamic overlay (provided we're
     292           0 :      * using one) is up-to-date and ready to be submitted to the next rendering run.
     293             :      */
     294           0 :     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             : void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global))
     358             : {
     359             :     switch (msg.GetType())
     360             :     {
     361             :     case MT_Interpolate:
     362             :     {
     363             :         PROFILE("Selectable::Interpolate");
     364             : 
     365             :         const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
     366             : 
     367             :         if (m_FadeDeltaAlpha != 0.f)
     368             :         {
     369             :             m_FadeProgress += msgData.deltaRealTime;
     370             :             if (m_FadeProgress >= FADE_DURATION)
     371           0 :             {
     372             :                 const float targetAlpha = m_FadeBaselineAlpha + m_FadeDeltaAlpha;
     373           0 : 
     374             :                 // stop the fade
     375           0 :                 m_Color.a = targetAlpha;
     376           0 :                 m_FadeBaselineAlpha = targetAlpha;
     377           0 :                 m_FadeDeltaAlpha = 0.f;
     378             :                 m_FadeProgress = FADE_DURATION; // will need to be reset to start the next fade again
     379           0 :             }
     380             :             else
     381           0 :             {
     382             :                 m_Color.a = Ease::QuartOut(m_FadeProgress, m_FadeBaselineAlpha, m_FadeDeltaAlpha, FADE_DURATION);
     383           0 :             }
     384           0 :         }
     385             : 
     386           0 :         // update dynamic overlay only when visible
     387             :         if (m_Color.a > 0)
     388             :             UpdateDynamicOverlay(msgData.offset);
     389           0 : 
     390           0 :         UpdateMessageSubscriptions();
     391           0 : 
     392           0 :         break;
     393             :     }
     394             :     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             :         if (msgData.from == INVALID_PLAYER || msgData.to == INVALID_PLAYER)
     401           0 :             break;
     402           0 : 
     403             :         UpdateColor();
     404           0 :         InvalidateStaticOverlay();
     405             :         break;
     406           0 :     }
     407             :     case MT_PlayerColorChanged:
     408           0 :     {
     409           0 :         const CMessagePlayerColorChanged& msgData = static_cast<const CMessagePlayerColorChanged&> (msg);
     410           0 : 
     411             :         CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
     412             :         if (!cmpOwnership || msgData.player != cmpOwnership->GetOwner())
     413             :             break;
     414           0 : 
     415             :         UpdateColor();
     416             :         break;
     417           0 :     }
     418           0 :     case MT_PositionChanged:
     419           0 :     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           0 : 
     427             :         const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
     428             :         RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
     429           0 : 
     430             :         break;
     431             :     }
     432           0 :     }
     433           0 : }
     434           0 : 
     435           0 : void CCmpSelectable::UpdateColor()
     436           0 : {
     437           0 :     CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
     438           0 : 
     439           0 :     CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSystemEntity());
     440             :     if (!cmpPlayerManager)
     441           0 :         return;
     442           0 : 
     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             :     if (cmpOwnership)
     447           0 :     {
     448             :         CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(cmpOwnership->GetOwner()));
     449           0 :         if (cmpPlayer)
     450             :             color = cmpPlayer->GetDisplayedColor();
     451           0 :     }
     452             : 
     453           0 :     // Update the highlight color, while keeping the current alpha target value intact
     454           0 :     // (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             :     SetSelectionHighlight(color, m_Selected);
     458           0 : }
     459             : 
     460           0 : void CCmpSelectable::UpdateMessageSubscriptions()
     461             : {
     462           0 :     bool needInterpolate = false;
     463           0 :     bool needRenderSubmit = false;
     464           0 : 
     465             :     if (m_FadeDeltaAlpha != 0.f || m_Color.a > 0)
     466             :         needInterpolate = true;
     467             : 
     468             :     if (m_Visible && m_Color.a > 0)
     469           0 :         needRenderSubmit = true;
     470             : 
     471           0 :     if (needInterpolate != m_EnabledInterpolate)
     472             :     {
     473             :         GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, needInterpolate);
     474           0 :         m_EnabledInterpolate = needInterpolate;
     475             :     }
     476           0 : 
     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           0 : 
     484             : void CCmpSelectable::InvalidateStaticOverlay()
     485           0 : {
     486             :     SAFE_DELETE(m_BuildingOverlay);
     487           0 : }
     488           0 : 
     489             : void CCmpSelectable::UpdateTexturedLineOverlay(const SOverlayDescriptor* overlayDescriptor, SOverlayTexturedLine& overlay, float frameOffset)
     490             : {
     491           0 :     if (!CRenderer::IsInitialised())
     492             :         return;
     493           0 : 
     494           0 :     CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     495             :     if (!cmpPosition || !cmpPosition->IsInWorld())
     496           0 :         return;
     497             : 
     498           0 :     ICmpFootprint::EShape fpShape = ICmpFootprint::CIRCLE;
     499             :     if (m_Shape == FOOTPRINT)
     500           0 :     {
     501           0 :         CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
     502             :         if (!cmpFootprint)
     503           0 :             return;
     504             :         entity_pos_t h;
     505           0 :         cmpFootprint->GetShape(fpShape, m_Width, m_Height, h);
     506           0 :     }
     507             :     float rotY;
     508           0 :     CVector2D origin;
     509           0 :     cmpPosition->GetInterpolatedPosition2D(frameOffset, origin.X, origin.Y, rotY);
     510           0 : 
     511             :     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           0 :     else
     518           0 :         SimRender::ConstructTexturedLineCircle(overlay, origin, m_Width.ToFloat());
     519           0 : }
     520             : 
     521           0 : void CCmpSelectable::UpdateDynamicOverlay(float frameOffset)
     522           0 : {
     523           0 :     // 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           0 :     // thoughtfully than calling it right before every frame render.
     526           0 : 
     527           0 :     if (m_OverlayDescriptor.m_Type != DYNAMIC_QUAD)
     528             :         return;
     529           0 : 
     530           0 :     if (!CRenderer::IsInitialised())
     531             :         return;
     532           0 : 
     533             :     CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     534             :     if (!cmpPosition || !cmpPosition->IsInWorld())
     535           0 :         return;
     536             : 
     537             :     ICmpFootprint::EShape fpShape = ICmpFootprint::CIRCLE;
     538             :     if (m_Shape == FOOTPRINT)
     539             :     {
     540             :         CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
     541           0 :         if (!cmpFootprint)
     542           0 :             return;
     543             :         entity_pos_t h;
     544           0 :         cmpFootprint->GetShape(fpShape, m_Width, m_Height, h);
     545             :     }
     546             : 
     547           0 :     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             :     ENSURE(cmpWaterManager && cmpTerrain);
     554           0 : 
     555           0 :     CTerrain* terrain = cmpTerrain->GetCTerrain();
     556           0 :     ENSURE(terrain);
     557           0 : 
     558           0 :     // ---------------------------------------------------------------------------------
     559             : 
     560             :     if (!m_UnitOverlay)
     561           0 :     {
     562           0 :         m_UnitOverlay = new SOverlayQuad;
     563           0 : 
     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           0 :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
     568             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     569           0 :         texturePropsBase.SetAnisotropicFilter(true);
     570           0 : 
     571             :         CTextureProperties texturePropsMask(m_OverlayDescriptor.m_QuadTextureMask.c_str());
     572             :         texturePropsMask.SetAddressMode(
     573             :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_BORDER,
     574           0 :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     575             :         texturePropsMask.SetAnisotropicFilter(true);
     576           0 : 
     577             :         m_UnitOverlay->m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
     578             :         m_UnitOverlay->m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
     579           0 :     }
     580           0 : 
     581             :     m_UnitOverlay->m_Color = m_Color;
     582             : 
     583           0 :     // 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           0 : 
     586           0 :     float s = sinf(-rotY);
     587             :     float c = cosf(-rotY);
     588             :     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             :     if (m_Shape == SQUARE || (m_Shape == FOOTPRINT && fpShape == ICmpFootprint::SQUARE))
     594             :     {
     595           0 :         halfSizeX /= 2.0f;
     596             :         halfSizeZ /= 2.0f;
     597             :     }
     598             : 
     599             :     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           0 :     {
     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             :         );
     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           0 :     }
     616           0 : }
     617           0 : 
     618             : void CCmpSelectable::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
     619           0 : {
     620           0 :     // don't render selection overlay if it's not gonna be visible
     621             :     if (!ICmpSelectable::m_OverrideVisible)
     622           0 :         return;
     623           0 : 
     624           0 :     if (m_Visible && m_Color.a > 0)
     625           0 :     {
     626             :         if (!m_Cached)
     627           0 :         {
     628           0 :             UpdateColor();
     629             :             m_Cached = true;
     630             :         }
     631             : 
     632           0 :         switch (m_OverlayDescriptor.m_Type)
     633             :         {
     634             :             case STATIC_OUTLINE:
     635           0 :                 {
     636             :                     if (!m_BuildingOverlay)
     637             :                     {
     638           0 :                         // 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           0 :                         // normal gameplay, this saves us doing all the work below on each frame.
     641             :                         m_BuildingOverlay = new SOverlayTexturedLine;
     642           0 :                         UpdateTexturedLineOverlay(&m_OverlayDescriptor, *m_BuildingOverlay, 0);
     643           0 :                     }
     644             :                     m_BuildingOverlay->m_Color = m_Color; // done separately so alpha changes don't require a full update call
     645             :                     if (culling && !m_BuildingOverlay->IsVisibleInFrustum(frustum))
     646           0 :                         break;
     647             :                     collector.Submit(m_BuildingOverlay);
     648           0 :                 }
     649           0 :                 break;
     650           0 :             case DYNAMIC_QUAD:
     651             :                 {
     652             :                     if (culling && !frustum.IsBoxVisible(m_UnitOverlayBoundingBox))
     653             :                         break;
     654             :                     if (m_UnitOverlay)
     655           0 :                         collector.Submit(m_UnitOverlay);
     656           0 :                 }
     657             :                 break;
     658           0 :             default:
     659           0 :                 break;
     660             :         }
     661           0 :     }
     662             : 
     663           0 :     // Render bounding box debug overlays if we have a positive target alpha value. This ensures
     664           0 :     // that the debug overlays respond immediately to deselection without delay from fading out.
     665           0 :     if (m_FadeBaselineAlpha + m_FadeDeltaAlpha > 0)
     666           0 :     {
     667             :         if (ICmpSelectable::ms_EnableDebugOverlays)
     668           0 :         {
     669           0 :             // allocate debug overlays on-demand
     670             :             if (!m_DebugBoundingBoxOverlay) m_DebugBoundingBoxOverlay = new SOverlayLine;
     671             :             if (!m_DebugSelectionBoxOverlay) m_DebugSelectionBoxOverlay = new SOverlayLine;
     672             : 
     673             :             CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle());
     674             :             if (cmpVisual)
     675             :             {
     676             :                 SimRender::ConstructBoxOutline(cmpVisual->GetBounds(), *m_DebugBoundingBoxOverlay);
     677             :                 m_DebugBoundingBoxOverlay->m_Thickness = 0.1f;
     678             :                 m_DebugBoundingBoxOverlay->m_Color = CColor(1.f, 0.f, 0.f, 1.f);
     679           0 : 
     680             :                 SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), *m_DebugSelectionBoxOverlay);
     681           0 :                 m_DebugSelectionBoxOverlay->m_Thickness = 0.1f;
     682             :                 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           0 :         }
     688           0 :         else
     689             :         {
     690           0 :             // 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           0 :     }
     695           0 : }
     696           0 : 
     697             : REGISTER_COMPONENT_TYPE(Selectable)

Generated by: LCOV version 1.13