2021-04-11 02:23:10 -07:00
/* Copyright (C) 2021 Wildfire Games.
2010-01-09 11:20:14 -08:00
* This file is part of 0 A . D .
*
* 0 A . D . is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 2 of the License , or
* ( at your option ) any later version .
*
* 0 A . D . is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with 0 A . D . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "precompiled.h"
# include "ICmpSelectable.h"
# include "graphics/Overlay.h"
2012-04-21 21:04:02 -07:00
# include "graphics/Terrain.h"
# include "graphics/TextureManager.h"
# include "maths/Ease.h"
2010-01-09 11:20:14 -08:00
# include "maths/MathUtil.h"
# include "maths/Matrix3D.h"
# include "maths/Vector3D.h"
2012-04-21 21:04:02 -07:00
# include "maths/Vector2D.h"
2020-11-21 03:20:29 -08:00
# include "ps/CLogger.h"
2016-06-21 03:33:11 -07:00
# include "ps/Profile.h"
2010-01-09 11:20:14 -08:00
# include "renderer/Scene.h"
2012-04-21 21:04:02 -07:00
# include "renderer/Renderer.h"
# include "simulation2/MessageTypes.h"
# include "simulation2/components/ICmpPosition.h"
# include "simulation2/components/ICmpFootprint.h"
# include "simulation2/components/ICmpVisual.h"
# include "simulation2/components/ICmpTerrain.h"
# include "simulation2/components/ICmpOwnership.h"
# include "simulation2/components/ICmpPlayer.h"
# include "simulation2/components/ICmpPlayerManager.h"
# include "simulation2/components/ICmpWaterManager.h"
# include "simulation2/helpers/Render.h"
2012-06-06 12:37:03 -07:00
# include "simulation2/system/Component.h"
2010-01-09 11:20:14 -08:00
2012-07-18 21:45:01 -07:00
// Minimum alpha value for always visible overlays [0 fully transparent, 1 fully opaque]
static const float MIN_ALPHA_ALWAYS_VISIBLE = 0.65f ;
// Minimum alpha value for other overlays
static const float MIN_ALPHA_UNSELECTED = 0.0f ;
// Desaturation value for unselected, always visible overlays (0.33 = 33% desaturated or 66% of original saturation)
static const float RGB_DESATURATION = 0.333333f ;
2010-01-09 11:20:14 -08:00
class CCmpSelectable : public ICmpSelectable
{
public :
2020-07-26 11:26:20 -07:00
enum EShape
{
FOOTPRINT ,
CIRCLE ,
SQUARE
} ;
2010-01-09 11:20:14 -08:00
static void ClassInit ( CComponentManager & componentManager )
{
2012-04-21 21:04:02 -07:00
componentManager . SubscribeToMessageType ( MT_OwnershipChanged ) ;
2017-08-09 04:19:14 -07:00
componentManager . SubscribeToMessageType ( MT_PlayerColorChanged ) ;
2012-04-21 21:04:02 -07:00
componentManager . SubscribeToMessageType ( MT_PositionChanged ) ;
2014-05-26 06:45:49 -07:00
componentManager . SubscribeToMessageType ( MT_TerrainChanged ) ;
componentManager . SubscribeToMessageType ( MT_WaterChanged ) ;
2010-01-09 11:20:14 -08:00
}
DEFAULT_COMPONENT_ALLOCATOR ( Selectable )
CCmpSelectable ( )
2016-11-23 05:02:58 -08:00
: m_DebugBoundingBoxOverlay ( NULL ) , m_DebugSelectionBoxOverlay ( NULL ) ,
2017-12-09 18:41:08 -08:00
m_BuildingOverlay ( NULL ) , m_UnitOverlay ( NULL ) ,
2012-07-18 21:45:01 -07:00
m_FadeBaselineAlpha ( 0.f ) , m_FadeDeltaAlpha ( 0.f ) , m_FadeProgress ( 0.f ) ,
2012-07-30 14:06:54 -07:00
m_Selected ( false ) , m_Cached ( false ) , m_Visible ( false )
2010-01-09 11:20:14 -08:00
{
2012-04-21 21:04:02 -07:00
m_Color = CColor ( 0 , 0 , 0 , m_FadeBaselineAlpha ) ;
2010-01-09 11:20:14 -08:00
}
2012-04-21 21:04:02 -07:00
~ CCmpSelectable ( )
{
2011-11-24 22:36:13 -08:00
delete m_DebugBoundingBoxOverlay ;
delete m_DebugSelectionBoxOverlay ;
2012-04-21 21:04:02 -07:00
delete m_BuildingOverlay ;
delete m_UnitOverlay ;
2011-11-24 22:36:13 -08:00
}
2010-04-23 09:09:03 -07:00
static std : : string GetSchema ( )
{
return
" <a:help>Allows this entity to be selected by the player.</a:help> "
" <a:example/> "
2012-02-26 21:32:35 -08:00
" <optional> "
" <element name='EditorOnly' a:help='If this element is present, the entity is only selectable in Atlas'> "
" <empty/> "
" </element> "
2012-04-21 21:04:02 -07:00
" </optional> "
2020-07-26 11:26:20 -07:00
" <element name='Overlay' a:help='Specifies the type of overlay to be displayed when this entity is selected.'> "
" <interleave> "
" <optional> "
" <element name='Shape' a:help='Specifies shape of overlay. If not specified, footprint shape will be used.'> "
" <choice> "
" <element name='Square' a:help='Set the overlay to a square of the given size'> "
" <attribute name='width' a:help='Size of the overlay along the left/right direction (in metres)'> "
" <data type='decimal'> "
" <param name='minExclusive'>0.0</param> "
" </data> "
" </attribute> "
" <attribute name='depth' a:help='Size of the overlay along the front/back direction (in metres)'> "
" <data type='decimal'> "
" <param name='minExclusive'>0.0</param> "
" </data> "
" </attribute> "
" </element> "
" <element name='Circle' a:help='Set the overlay to a circle of the given size'> "
" <attribute name='radius' a:help='Radius of the overlay (in metres)'> "
" <data type='decimal'> "
" <param name='minExclusive'>0.0</param> "
" </data> "
" </attribute> "
" </element> "
" </choice> "
" </element> "
" </optional> "
" <optional> "
" <element name='AlwaysVisible' a:help='If this element is present, the selection overlay will always be visible (with transparency and desaturation)'> "
" <empty/> "
" </element> "
" </optional> "
" <choice> "
" <element name='Texture' a:help='Displays a texture underneath the entity.'> "
" <element name='MainTexture' a:help='Texture to display underneath the entity. Filepath relative to art/textures/selection/.'><text/></element> "
" <element name='MainTextureMask' a:help='Mask texture that controls where to apply player color. Filepath relative to art/textures/selection/.'><text/></element> "
" </element> "
" <element name='Outline' a:help='Traces the outline of the entity with a line texture.'> "
" <element name='LineTexture' a:help='Texture to apply to the line. Filepath relative to art/textures/selection/.'><text/></element> "
" <element name='LineTextureMask' a:help='Texture that controls where to apply player color. Filepath relative to art/textures/selection/.'><text/></element> "
" <element name='LineThickness' a:help='Thickness of the line, in world units.'><ref name='positiveDecimal'/></element> "
" </element> "
" </choice> "
" </interleave> "
2012-04-21 21:04:02 -07:00
" </element> " ;
2010-04-23 09:09:03 -07:00
}
2020-07-26 11:26:20 -07:00
EShape m_Shape ;
entity_pos_t m_Width ; // width/radius
entity_pos_t m_Height ; // height/radius
2012-02-26 21:32:35 -08:00
virtual void Init ( const CParamNode & paramNode )
2010-01-09 11:20:14 -08:00
{
2012-02-26 21:32:35 -08:00
m_EditorOnly = paramNode . GetChild ( " EditorOnly " ) . IsOk ( ) ;
2010-01-09 11:20:14 -08:00
2012-07-18 21:45:01 -07:00
// Certain special units always have their selection overlay shown
m_AlwaysVisible = paramNode . GetChild ( " Overlay " ) . GetChild ( " AlwaysVisible " ) . IsOk ( ) ;
if ( m_AlwaysVisible )
{
m_AlphaMin = MIN_ALPHA_ALWAYS_VISIBLE ;
m_Color . a = m_AlphaMin ;
}
else
m_AlphaMin = MIN_ALPHA_UNSELECTED ;
2012-04-21 21:04:02 -07:00
const CParamNode & textureNode = paramNode . GetChild ( " Overlay " ) . GetChild ( " Texture " ) ;
const CParamNode & outlineNode = paramNode . GetChild ( " Overlay " ) . GetChild ( " Outline " ) ;
// Save some memory by using interned file paths in these descriptors (almost all actors and
// entities have this component, and many use the same textures).
if ( textureNode . IsOk ( ) )
{
// textured quad mode (dynamic, for units)
2017-12-09 11:49:56 -08:00
m_OverlayDescriptor . m_Type = DYNAMIC_QUAD ;
2021-04-11 02:23:10 -07:00
m_OverlayDescriptor . m_QuadTexture = CStrIntern ( TEXTUREBASEPATH + textureNode . GetChild ( " MainTexture " ) . ToString ( ) ) ;
m_OverlayDescriptor . m_QuadTextureMask = CStrIntern ( TEXTUREBASEPATH + textureNode . GetChild ( " MainTextureMask " ) . ToString ( ) ) ;
2012-04-21 21:04:02 -07:00
}
else if ( outlineNode . IsOk ( ) )
{
// textured outline mode (static, for buildings)
2017-12-09 11:49:56 -08:00
m_OverlayDescriptor . m_Type = STATIC_OUTLINE ;
2021-04-11 02:23:10 -07:00
m_OverlayDescriptor . m_LineTexture = CStrIntern ( TEXTUREBASEPATH + outlineNode . GetChild ( " LineTexture " ) . ToString ( ) ) ;
m_OverlayDescriptor . m_LineTextureMask = CStrIntern ( TEXTUREBASEPATH + outlineNode . GetChild ( " LineTextureMask " ) . ToString ( ) ) ;
2012-04-21 21:04:02 -07:00
m_OverlayDescriptor . m_LineThickness = outlineNode . GetChild ( " LineThickness " ) . ToFloat ( ) ;
}
2014-06-19 16:20:12 -07:00
2020-07-26 11:26:20 -07:00
const CParamNode & shapeNode = paramNode . GetChild ( " Overlay " ) . GetChild ( " Shape " ) ;
if ( shapeNode . IsOk ( ) )
{
if ( shapeNode . GetChild ( " Square " ) . IsOk ( ) )
{
m_Shape = SQUARE ;
m_Width = shapeNode . GetChild ( " Square " ) . GetChild ( " @width " ) . ToFixed ( ) ;
m_Height = shapeNode . GetChild ( " Square " ) . GetChild ( " @depth " ) . ToFixed ( ) ;
}
else if ( shapeNode . GetChild ( " Circle " ) . IsOk ( ) )
{
m_Shape = CIRCLE ;
m_Width = m_Height = shapeNode . GetChild ( " Circle " ) . GetChild ( " @radius " ) . ToFixed ( ) ;
}
else
{
// Should not happen
m_Shape = FOOTPRINT ;
LOGWARNING ( " [Selectable] Selected overlay shape is not implemented. " ) ;
}
}
else
{
m_Shape = FOOTPRINT ;
}
2014-06-19 16:20:12 -07:00
m_EnabledInterpolate = false ;
m_EnabledRenderSubmit = false ;
UpdateMessageSubscriptions ( ) ;
2010-01-09 11:20:14 -08:00
}
2012-04-21 21:04:02 -07:00
virtual void Deinit ( ) { }
2010-01-09 11:20:14 -08:00
virtual void Serialize ( ISerializer & UNUSED ( serialize ) )
{
// Nothing to do here (the overlay object is not worth saving, it'll get
// reconstructed by the GUI soon enough, I think)
}
2012-02-28 14:12:30 -08:00
virtual void Deserialize ( const CParamNode & paramNode , IDeserializer & UNUSED ( deserialize ) )
2010-01-09 11:20:14 -08:00
{
2012-02-28 14:12:30 -08:00
// Need to call Init to reload the template properties
Init ( paramNode ) ;
2010-01-09 11:20:14 -08:00
}
2012-04-21 21:04:02 -07:00
virtual void HandleMessage ( const CMessage & msg , bool UNUSED ( global ) ) ;
2016-01-23 07:17:56 -08:00
virtual void SetSelectionHighlight ( const CColor & color , bool selected )
2010-01-09 11:20:14 -08:00
{
2012-07-18 21:45:01 -07:00
m_Selected = selected ;
2012-04-21 21:04:02 -07:00
m_Color . r = color . r ;
m_Color . g = color . g ;
m_Color . b = color . b ;
2012-07-18 21:45:01 -07:00
// Always-visible overlays will be desaturated if their parent unit is deselected.
if ( m_AlwaysVisible & & ! selected )
{
float max ;
// Reduce saturation by one-third, the quick-and-dirty way.
if ( m_Color . r > m_Color . b )
max = ( m_Color . r > m_Color . g ) ? m_Color . r : m_Color . g ;
else
max = ( m_Color . b > m_Color . g ) ? m_Color . b : m_Color . g ;
m_Color . r + = ( max - m_Color . r ) * RGB_DESATURATION ;
m_Color . g + = ( max - m_Color . g ) * RGB_DESATURATION ;
m_Color . b + = ( max - m_Color . b ) * RGB_DESATURATION ;
}
2012-04-22 10:49:43 -07:00
SetSelectionHighlightAlpha ( color . a ) ;
}
2012-04-21 21:04:02 -07:00
2012-04-22 10:49:43 -07:00
virtual void SetSelectionHighlightAlpha ( float alpha )
{
2012-07-18 21:45:01 -07:00
alpha = std : : max ( m_AlphaMin , alpha ) ;
2012-04-21 21:04:02 -07:00
// set up fading from the current value (as the baseline) to the target value
m_FadeBaselineAlpha = m_Color . a ;
2012-04-22 10:49:43 -07:00
m_FadeDeltaAlpha = alpha - m_FadeBaselineAlpha ;
2012-04-21 21:04:02 -07:00
m_FadeProgress = 0.f ;
2014-06-19 16:20:12 -07:00
UpdateMessageSubscriptions ( ) ;
2012-04-21 21:04:02 -07:00
}
2012-07-30 14:06:54 -07:00
virtual void SetVisibility ( bool visible )
{
m_Visible = visible ;
2014-06-19 16:20:12 -07:00
UpdateMessageSubscriptions ( ) ;
2012-07-30 14:06:54 -07:00
}
2017-01-19 18:25:19 -08:00
virtual bool IsEditorOnly ( ) const
2012-04-21 21:04:02 -07:00
{
return m_EditorOnly ;
}
2019-12-10 15:13:37 -08:00
void RenderSubmit ( SceneCollector & collector , const CFrustum & frustum , bool culling ) ;
2012-04-21 21:04:02 -07:00
/**
2020-07-26 11:26:20 -07:00
* Draw a textured line overlay .
2012-04-21 21:04:02 -07:00
*/
2017-12-09 18:41:08 -08:00
void UpdateTexturedLineOverlay ( const SOverlayDescriptor * overlayDescriptor , SOverlayTexturedLine & overlay , float frameOffset ) ;
2012-04-21 21:04:02 -07:00
/**
* Called from the interpolation handler ; responsible for ensuring the dynamic overlay ( provided we ' re
* using one ) is up - to - date and ready to be submitted to the next rendering run .
*/
void UpdateDynamicOverlay ( float frameOffset ) ;
/// Explicitly invalidates the static overlay.
void InvalidateStaticOverlay ( ) ;
2014-06-19 16:20:12 -07:00
/**
* Subscribe / unsubscribe to MT_Interpolate , MT_RenderSubmit , depending on
* whether we will do any actual work when receiving them . ( This is to avoid
* the performance cost of receiving messages in the typical case when the
* entity is not selected . )
*
* Must be called after changing m_Visible , m_FadeDeltaAlpha , m_Color . a
*/
void UpdateMessageSubscriptions ( ) ;
2017-08-09 04:19:14 -07:00
/**
* Set the color of the current owner .
*/
2018-02-03 06:17:31 -08:00
virtual void UpdateColor ( ) ;
2017-08-09 04:19:14 -07:00
2012-04-21 21:04:02 -07:00
private :
SOverlayDescriptor m_OverlayDescriptor ;
SOverlayTexturedLine * m_BuildingOverlay ;
SOverlayQuad * m_UnitOverlay ;
2019-12-10 15:13:37 -08:00
CBoundingBoxAligned m_UnitOverlayBoundingBox ;
2012-04-21 21:04:02 -07:00
SOverlayLine * m_DebugBoundingBoxOverlay ;
SOverlayLine * m_DebugSelectionBoxOverlay ;
2014-06-19 16:20:12 -07:00
bool m_EnabledInterpolate ;
bool m_EnabledRenderSubmit ;
2012-07-30 14:06:54 -07:00
// Whether the selectable will be rendered.
bool m_Visible ;
2012-07-18 21:45:01 -07:00
// Whether the entity is only selectable in Atlas editor
2012-04-21 21:04:02 -07:00
bool m_EditorOnly ;
2012-07-18 21:45:01 -07:00
// Whether the selection overlay is always visible
bool m_AlwaysVisible ;
/// Whether the parent entity is selected (caches GUI's selection state).
bool m_Selected ;
2012-04-21 21:04:02 -07:00
/// Current selection overlay color. Alpha component is subject to fading.
CColor m_Color ;
2015-03-15 16:59:48 -07:00
/// Whether the selectable's player color has been cached for rendering.
2012-07-18 21:45:01 -07:00
bool m_Cached ;
/// Minimum value for current selection overlay alpha.
float m_AlphaMin ;
2012-04-21 21:04:02 -07:00
/// Baseline alpha value to start fading from. Constant during a single fade.
float m_FadeBaselineAlpha ;
/// Delta between target and baseline alpha. Constant during a single fade. Can be positive or negative.
float m_FadeDeltaAlpha ;
/// Linear time progress of the fade, between 0 and m_FadeDuration.
float m_FadeProgress ;
2012-06-06 12:37:03 -07:00
2012-04-21 21:04:02 -07:00
/// Total duration of a single fade, in seconds. Assumed constant for now; feel free to change this into
/// a member variable if you need to adjust it per component.
2020-11-26 14:28:50 -08:00
static const float FADE_DURATION ;
2017-05-05 17:47:21 -07:00
static const char * TEXTUREBASEPATH ;
2012-04-21 21:04:02 -07:00
} ;
2020-11-26 14:28:50 -08:00
const float CCmpSelectable : : FADE_DURATION = 0.3f ;
2017-05-05 17:47:21 -07:00
const char * CCmpSelectable : : TEXTUREBASEPATH = " art/textures/selection/ " ;
2012-04-21 21:04:02 -07:00
void CCmpSelectable : : HandleMessage ( const CMessage & msg , bool UNUSED ( global ) )
{
switch ( msg . GetType ( ) )
{
case MT_Interpolate :
2015-02-07 07:38:22 -08:00
{
2016-06-25 06:12:35 -07:00
PROFILE ( " Selectable::Interpolate " ) ;
2014-06-19 16:20:12 -07:00
2015-02-07 07:38:22 -08:00
const CMessageInterpolate & msgData = static_cast < const CMessageInterpolate & > ( msg ) ;
2012-04-21 21:04:02 -07:00
2015-02-07 07:38:22 -08:00
if ( m_FadeDeltaAlpha ! = 0.f )
{
m_FadeProgress + = msgData . deltaRealTime ;
if ( m_FadeProgress > = FADE_DURATION )
2010-01-09 11:20:14 -08:00
{
2015-02-07 07:38:22 -08:00
const float targetAlpha = m_FadeBaselineAlpha + m_FadeDeltaAlpha ;
// stop the fade
m_Color . a = targetAlpha ;
m_FadeBaselineAlpha = targetAlpha ;
m_FadeDeltaAlpha = 0.f ;
m_FadeProgress = FADE_DURATION ; // will need to be reset to start the next fade again
2010-01-09 11:20:14 -08:00
}
2015-02-07 07:38:22 -08:00
else
{
m_Color . a = Ease : : QuartOut ( m_FadeProgress , m_FadeBaselineAlpha , m_FadeDeltaAlpha , FADE_DURATION ) ;
}
}
2012-04-21 21:04:02 -07:00
2015-02-07 07:38:22 -08:00
// update dynamic overlay only when visible
if ( m_Color . a > 0 )
UpdateDynamicOverlay ( msgData . offset ) ;
2012-04-21 21:04:02 -07:00
2015-02-07 07:38:22 -08:00
UpdateMessageSubscriptions ( ) ;
2014-06-19 16:20:12 -07:00
2015-02-07 07:38:22 -08:00
break ;
}
2016-11-23 05:02:58 -08:00
case MT_OwnershipChanged :
2015-02-07 07:38:22 -08:00
{
const CMessageOwnershipChanged & msgData = static_cast < const CMessageOwnershipChanged & > ( msg ) ;
2012-04-21 21:04:02 -07:00
2017-08-11 13:05:03 -07:00
// Ignore newly constructed entities, as they receive their color upon first selection
// Ignore deleted entities because they won't be rendered
if ( msgData . from = = INVALID_PLAYER | | msgData . to = = INVALID_PLAYER )
2015-02-07 07:38:22 -08:00
break ;
2012-04-21 21:04:02 -07:00
2018-02-03 06:17:31 -08:00
UpdateColor ( ) ;
2017-08-09 04:19:14 -07:00
InvalidateStaticOverlay ( ) ;
break ;
}
case MT_PlayerColorChanged :
{
const CMessagePlayerColorChanged & msgData = static_cast < const CMessagePlayerColorChanged & > ( msg ) ;
2012-04-21 21:04:02 -07:00
2017-08-09 04:19:14 -07:00
CmpPtr < ICmpOwnership > cmpOwnership ( GetEntityHandle ( ) ) ;
if ( ! cmpOwnership | | msgData . player ! = cmpOwnership - > GetOwner ( ) )
2015-02-07 07:38:22 -08:00
break ;
2012-04-21 21:04:02 -07:00
2018-02-03 06:17:31 -08:00
UpdateColor ( ) ;
2015-02-07 07:38:22 -08:00
break ;
}
2012-04-21 21:04:02 -07:00
case MT_PositionChanged :
2014-05-26 06:45:49 -07:00
case MT_TerrainChanged :
case MT_WaterChanged :
2015-02-07 07:38:22 -08:00
InvalidateStaticOverlay ( ) ;
break ;
2012-04-21 21:04:02 -07:00
case MT_RenderSubmit :
2015-02-07 07:38:22 -08:00
{
2016-06-25 06:12:35 -07:00
PROFILE ( " Selectable::RenderSubmit " ) ;
2014-06-19 16:20:12 -07:00
2015-02-07 07:38:22 -08:00
const CMessageRenderSubmit & msgData = static_cast < const CMessageRenderSubmit & > ( msg ) ;
2019-12-10 15:13:37 -08:00
RenderSubmit ( msgData . collector , msgData . frustum , msgData . culling ) ;
2012-04-21 21:04:02 -07:00
2015-02-07 07:38:22 -08:00
break ;
}
2010-01-09 11:20:14 -08:00
}
2012-04-21 21:04:02 -07:00
}
2010-01-09 11:20:14 -08:00
2018-02-03 06:17:31 -08:00
void CCmpSelectable : : UpdateColor ( )
2017-08-09 04:19:14 -07:00
{
CmpPtr < ICmpOwnership > cmpOwnership ( GetEntityHandle ( ) ) ;
CmpPtr < ICmpPlayerManager > cmpPlayerManager ( GetSystemEntity ( ) ) ;
if ( ! cmpPlayerManager )
return ;
// Default to white if there's no owner (e.g. decorative, editor-only actors)
CColor color ( 1.0 , 1.0 , 1.0 , 1.0 ) ;
if ( cmpOwnership )
{
CmpPtr < ICmpPlayer > cmpPlayer ( GetSimContext ( ) , cmpPlayerManager - > GetPlayerByID ( cmpOwnership - > GetOwner ( ) ) ) ;
if ( cmpPlayer )
2018-02-03 06:17:31 -08:00
color = cmpPlayer - > GetDisplayedColor ( ) ;
2017-08-09 04:19:14 -07:00
}
// Update the highlight color, while keeping the current alpha target value intact
// (i.e. baseline + delta), so that any ongoing fades simply continue with the new color.
color . a = m_FadeBaselineAlpha + m_FadeDeltaAlpha ;
SetSelectionHighlight ( color , m_Selected ) ;
}
2014-06-19 16:20:12 -07:00
void CCmpSelectable : : UpdateMessageSubscriptions ( )
{
bool needInterpolate = false ;
bool needRenderSubmit = false ;
if ( m_FadeDeltaAlpha ! = 0.f | | m_Color . a > 0 )
needInterpolate = true ;
if ( m_Visible & & m_Color . a > 0 )
needRenderSubmit = true ;
if ( needInterpolate ! = m_EnabledInterpolate )
{
GetSimContext ( ) . GetComponentManager ( ) . DynamicSubscriptionNonsync ( MT_Interpolate , this , needInterpolate ) ;
m_EnabledInterpolate = needInterpolate ;
}
if ( needRenderSubmit ! = m_EnabledRenderSubmit )
{
GetSimContext ( ) . GetComponentManager ( ) . DynamicSubscriptionNonsync ( MT_RenderSubmit , this , needRenderSubmit ) ;
m_EnabledRenderSubmit = needRenderSubmit ;
}
}
2012-04-21 21:04:02 -07:00
void CCmpSelectable : : InvalidateStaticOverlay ( )
{
SAFE_DELETE ( m_BuildingOverlay ) ;
}
2012-02-26 21:32:35 -08:00
2017-12-09 18:41:08 -08:00
void CCmpSelectable : : UpdateTexturedLineOverlay ( const SOverlayDescriptor * overlayDescriptor , SOverlayTexturedLine & overlay , float frameOffset )
2012-04-21 21:04:02 -07:00
{
if ( ! CRenderer : : IsInitialised ( ) )
return ;
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
2020-07-26 11:26:20 -07:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2012-04-21 21:04:02 -07:00
return ;
2020-07-26 11:26:20 -07:00
ICmpFootprint : : EShape fpShape = ICmpFootprint : : CIRCLE ;
if ( m_Shape = = FOOTPRINT )
{
CmpPtr < ICmpFootprint > cmpFootprint ( GetEntityHandle ( ) ) ;
if ( ! cmpFootprint )
return ;
entity_pos_t h ;
cmpFootprint - > GetShape ( fpShape , m_Width , m_Height , h ) ;
}
2017-05-05 17:47:21 -07:00
float rotY ;
CVector2D origin ;
cmpPosition - > GetInterpolatedPosition2D ( frameOffset , origin . X , origin . Y , rotY ) ;
overlay . m_SimContext = & GetSimContext ( ) ;
overlay . m_Color = m_Color ;
2017-12-09 16:19:51 -08:00
overlay . CreateOverlayTexture ( overlayDescriptor ) ;
2012-04-21 21:04:02 -07:00
2020-07-26 11:26:20 -07:00
if ( m_Shape = = SQUARE | | ( m_Shape = = FOOTPRINT & & fpShape = = ICmpFootprint : : SQUARE ) )
SimRender : : ConstructTexturedLineBox ( overlay , origin , cmpPosition - > GetRotation ( ) , m_Width . ToFloat ( ) , m_Height . ToFloat ( ) ) ;
2017-05-05 17:47:21 -07:00
else
2020-07-26 11:26:20 -07:00
SimRender : : ConstructTexturedLineCircle ( overlay , origin , m_Width . ToFloat ( ) ) ;
2012-04-21 21:04:02 -07:00
}
void CCmpSelectable : : UpdateDynamicOverlay ( float frameOffset )
{
// Dynamic overlay lines are allocated once and never deleted. Since they are expected to change frequently,
// they are assumed dirty on every call to this function, and we should therefore use this function more
// thoughtfully than calling it right before every frame render.
2016-11-23 06:09:58 -08:00
2012-04-21 21:04:02 -07:00
if ( m_OverlayDescriptor . m_Type ! = DYNAMIC_QUAD )
return ;
if ( ! CRenderer : : IsInitialised ( ) )
return ;
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpPosition > cmpPosition ( GetEntityHandle ( ) ) ;
2020-07-26 11:26:20 -07:00
if ( ! cmpPosition | | ! cmpPosition - > IsInWorld ( ) )
2012-04-21 21:04:02 -07:00
return ;
2020-07-26 11:26:20 -07:00
ICmpFootprint : : EShape fpShape = ICmpFootprint : : CIRCLE ;
if ( m_Shape = = FOOTPRINT )
{
CmpPtr < ICmpFootprint > cmpFootprint ( GetEntityHandle ( ) ) ;
if ( ! cmpFootprint )
return ;
entity_pos_t h ;
cmpFootprint - > GetShape ( fpShape , m_Width , m_Height , h ) ;
}
2012-04-21 21:04:02 -07:00
float rotY ;
CVector2D position ;
cmpPosition - > GetInterpolatedPosition2D ( frameOffset , position . X , position . Y , rotY ) ;
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpWaterManager > cmpWaterManager ( GetSystemEntity ( ) ) ;
CmpPtr < ICmpTerrain > cmpTerrain ( GetSystemEntity ( ) ) ;
2012-04-21 21:04:02 -07:00
ENSURE ( cmpWaterManager & & cmpTerrain ) ;
CTerrain * terrain = cmpTerrain - > GetCTerrain ( ) ;
ENSURE ( terrain ) ;
// ---------------------------------------------------------------------------------
if ( ! m_UnitOverlay )
{
m_UnitOverlay = new SOverlayQuad ;
// Assuming we don't need the capability of swapping textures on-demand.
2016-11-23 05:02:58 -08:00
CTextureProperties texturePropsBase ( m_OverlayDescriptor . m_QuadTexture . c_str ( ) ) ;
texturePropsBase . SetWrap ( GL_CLAMP_TO_BORDER , GL_CLAMP_TO_EDGE ) ;
2012-04-21 21:04:02 -07:00
texturePropsBase . SetMaxAnisotropy ( 4.f ) ;
CTextureProperties texturePropsMask ( m_OverlayDescriptor . m_QuadTextureMask . c_str ( ) ) ;
2016-11-23 05:02:58 -08:00
texturePropsMask . SetWrap ( GL_CLAMP_TO_BORDER , GL_CLAMP_TO_EDGE ) ;
2012-04-21 21:04:02 -07:00
texturePropsMask . SetMaxAnisotropy ( 4.f ) ;
m_UnitOverlay - > m_Texture = g_Renderer . GetTextureManager ( ) . CreateTexture ( texturePropsBase ) ;
m_UnitOverlay - > m_TextureMask = g_Renderer . GetTextureManager ( ) . CreateTexture ( texturePropsMask ) ;
2010-01-09 11:20:14 -08:00
}
2012-04-21 21:04:02 -07:00
m_UnitOverlay - > m_Color = m_Color ;
2016-11-23 05:02:58 -08:00
// TODO: some code duplication here :< would be nice to factor out getting the corner points of an
2012-04-21 21:04:02 -07:00
// entity based on its footprint sizes (and regardless of whether it's a circle or a square)
float s = sinf ( - rotY ) ;
float c = cosf ( - rotY ) ;
CVector2D unitX ( c , s ) ;
CVector2D unitZ ( - s , c ) ;
2020-07-26 11:26:20 -07:00
float halfSizeX = m_Width . ToFloat ( ) ;
float halfSizeZ = m_Height . ToFloat ( ) ;
if ( m_Shape = = SQUARE | | ( m_Shape = = FOOTPRINT & & fpShape = = ICmpFootprint : : SQUARE ) )
2010-01-09 11:20:14 -08:00
{
2012-04-21 21:04:02 -07:00
halfSizeX / = 2.0f ;
halfSizeZ / = 2.0f ;
}
2010-01-09 11:20:14 -08:00
2012-04-21 21:04:02 -07:00
std : : vector < CVector2D > points ;
points . push_back ( CVector2D ( position + unitX * ( - halfSizeX ) + unitZ * halfSizeZ ) ) ; // top left
points . push_back ( CVector2D ( position + unitX * ( - halfSizeX ) + unitZ * ( - halfSizeZ ) ) ) ; // bottom left
points . push_back ( CVector2D ( position + unitX * halfSizeX + unitZ * ( - halfSizeZ ) ) ) ; // bottom right
points . push_back ( CVector2D ( position + unitX * halfSizeX + unitZ * halfSizeZ ) ) ; // top right
2010-01-09 11:20:14 -08:00
2019-12-10 15:13:37 -08:00
m_UnitOverlayBoundingBox = CBoundingBoxAligned ( ) ;
for ( size_t i = 0 ; i < 4 ; + + i )
2012-04-21 21:04:02 -07:00
{
float quadY = std : : max (
terrain - > GetExactGroundLevel ( points [ i ] . X , points [ i ] . Y ) ,
cmpWaterManager - > GetExactWaterLevel ( points [ i ] . X , points [ i ] . Y )
) ;
2010-01-09 11:20:14 -08:00
2012-04-21 21:04:02 -07:00
m_UnitOverlay - > m_Corners [ i ] = CVector3D ( points [ i ] . X , quadY , points [ i ] . Y ) ;
2019-12-10 15:13:37 -08:00
m_UnitOverlayBoundingBox + = m_UnitOverlay - > m_Corners [ i ] ;
2012-04-21 21:04:02 -07:00
}
}
2019-12-10 15:13:37 -08:00
void CCmpSelectable : : RenderSubmit ( SceneCollector & collector , const CFrustum & frustum , bool culling )
2012-04-21 21:04:02 -07:00
{
// don't render selection overlay if it's not gonna be visible
2016-01-03 04:41:04 -08:00
if ( ! ICmpSelectable : : m_OverrideVisible )
return ;
2012-07-30 14:06:54 -07:00
if ( m_Visible & & m_Color . a > 0 )
2012-04-21 21:04:02 -07:00
{
2012-07-18 21:45:01 -07:00
if ( ! m_Cached )
{
2018-02-03 06:17:31 -08:00
UpdateColor ( ) ;
2012-07-18 21:45:01 -07:00
m_Cached = true ;
}
2012-04-21 21:04:02 -07:00
switch ( m_OverlayDescriptor . m_Type )
2010-04-23 09:57:18 -07:00
{
2012-04-21 21:04:02 -07:00
case STATIC_OUTLINE :
{
2017-05-05 17:47:21 -07:00
if ( ! m_BuildingOverlay )
{
// Static overlays are allocated once and not updated until they are explicitly deleted again
// (see InvalidateStaticOverlay). Since they are expected to change rarely (if ever) during
// normal gameplay, this saves us doing all the work below on each frame.
m_BuildingOverlay = new SOverlayTexturedLine ;
2017-12-09 18:41:08 -08:00
UpdateTexturedLineOverlay ( & m_OverlayDescriptor , * m_BuildingOverlay , 0 ) ;
2017-05-05 17:47:21 -07:00
}
2012-04-21 21:04:02 -07:00
m_BuildingOverlay - > m_Color = m_Color ; // done separately so alpha changes don't require a full update call
2019-12-10 15:13:37 -08:00
if ( culling & & ! m_BuildingOverlay - > IsVisibleInFrustum ( frustum ) )
break ;
2012-04-21 21:04:02 -07:00
collector . Submit ( m_BuildingOverlay ) ;
}
break ;
case DYNAMIC_QUAD :
{
2019-12-10 15:13:37 -08:00
if ( culling & & ! frustum . IsBoxVisible ( m_UnitOverlayBoundingBox ) )
break ;
2012-04-21 21:04:02 -07:00
if ( m_UnitOverlay )
collector . Submit ( m_UnitOverlay ) ;
}
break ;
default :
break ;
2010-04-23 09:57:18 -07:00
}
2010-01-09 11:20:14 -08:00
}
2012-04-21 21:04:02 -07:00
// Render bounding box debug overlays if we have a positive target alpha value. This ensures
// that the debug overlays respond immediately to deselection without delay from fading out.
if ( m_FadeBaselineAlpha + m_FadeDeltaAlpha > 0 )
2010-01-09 11:20:14 -08:00
{
2011-11-24 22:36:13 -08:00
if ( ICmpSelectable : : ms_EnableDebugOverlays )
{
// allocate debug overlays on-demand
if ( ! m_DebugBoundingBoxOverlay ) m_DebugBoundingBoxOverlay = new SOverlayLine ;
if ( ! m_DebugSelectionBoxOverlay ) m_DebugSelectionBoxOverlay = new SOverlayLine ;
2013-09-11 13:41:53 -07:00
CmpPtr < ICmpVisual > cmpVisual ( GetEntityHandle ( ) ) ;
2016-11-23 05:02:58 -08:00
if ( cmpVisual )
2011-11-24 22:36:13 -08:00
{
SimRender : : ConstructBoxOutline ( cmpVisual - > GetBounds ( ) , * m_DebugBoundingBoxOverlay ) ;
2021-04-25 13:48:44 -07:00
m_DebugBoundingBoxOverlay - > m_Thickness = 0.1f ;
2011-11-24 22:36:13 -08:00
m_DebugBoundingBoxOverlay - > m_Color = CColor ( 1.f , 0.f , 0.f , 1.f ) ;
SimRender : : ConstructBoxOutline ( cmpVisual - > GetSelectionBox ( ) , * m_DebugSelectionBoxOverlay ) ;
2021-04-25 13:48:44 -07:00
m_DebugSelectionBoxOverlay - > m_Thickness = 0.1f ;
2011-11-24 22:36:13 -08:00
m_DebugSelectionBoxOverlay - > m_Color = CColor ( 0.f , 1.f , 0.f , 1.f ) ;
collector . Submit ( m_DebugBoundingBoxOverlay ) ;
collector . Submit ( m_DebugSelectionBoxOverlay ) ;
}
}
else
{
// reclaim debug overlay line memory when no longer debugging (and make sure to set to zero after deletion)
if ( m_DebugBoundingBoxOverlay ) SAFE_DELETE ( m_DebugBoundingBoxOverlay ) ;
if ( m_DebugSelectionBoxOverlay ) SAFE_DELETE ( m_DebugSelectionBoxOverlay ) ;
}
2010-01-09 11:20:14 -08:00
}
2012-04-21 21:04:02 -07:00
}
2010-01-09 11:20:14 -08:00
REGISTER_COMPONENT_TYPE ( Selectable )