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)
|