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 "simulation2/system/Component.h"
21 : #include "ICmpVisual.h"
22 :
23 : #include "simulation2/MessageTypes.h"
24 : #include "simulation2/serialization/SerializedTypes.h"
25 :
26 : #include "ICmpFootprint.h"
27 : #include "ICmpIdentity.h"
28 : #include "ICmpMirage.h"
29 : #include "ICmpOwnership.h"
30 : #include "ICmpPosition.h"
31 : #include "ICmpTemplateManager.h"
32 : #include "ICmpTerrain.h"
33 : #include "ICmpUnitMotion.h"
34 : #include "ICmpUnitRenderer.h"
35 : #include "ICmpValueModificationManager.h"
36 : #include "ICmpVisibility.h"
37 : #include "ICmpSound.h"
38 :
39 : #include "graphics/Decal.h"
40 : #include "graphics/Model.h"
41 : #include "graphics/ObjectBase.h"
42 : #include "graphics/ObjectEntry.h"
43 : #include "graphics/Unit.h"
44 : #include "graphics/UnitAnimation.h"
45 : #include "graphics/UnitManager.h"
46 : #include "maths/BoundingSphere.h"
47 : #include "maths/Frustum.h"
48 : #include "maths/Matrix3D.h"
49 : #include "maths/Vector3D.h"
50 : #include "ps/CLogger.h"
51 : #include "ps/GameSetup/Config.h"
52 : #include "renderer/Scene.h"
53 :
54 0 : class CCmpVisualActor final : public ICmpVisual
55 : {
56 : public:
57 116 : static void ClassInit(CComponentManager& componentManager)
58 : {
59 116 : componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged);
60 116 : componentManager.SubscribeToMessageType(MT_OwnershipChanged);
61 116 : componentManager.SubscribeToMessageType(MT_ValueModification);
62 116 : componentManager.SubscribeToMessageType(MT_Create);
63 116 : componentManager.SubscribeToMessageType(MT_Destroy);
64 116 : }
65 :
66 0 : DEFAULT_COMPONENT_ALLOCATOR(VisualActor)
67 :
68 : private:
69 : std::wstring m_BaseActorName, m_ActorName;
70 : bool m_IsFoundationActor;
71 :
72 : // Not initialized in non-visual mode
73 : CUnit* m_Unit;
74 : CModelAbstract::CustomSelectionShape* m_ShapeDescriptor = nullptr;
75 :
76 : fixed m_R, m_G, m_B; // shading color
77 :
78 : // Current animation state
79 : std::string m_AnimName;
80 : bool m_AnimOnce;
81 : fixed m_AnimSpeed;
82 : std::wstring m_SoundGroup;
83 : fixed m_AnimDesync;
84 : fixed m_AnimSyncRepeatTime; // 0.0 if not synced
85 : fixed m_AnimSyncOffsetTime;
86 :
87 : std::map<CStr, CStr> m_VariantSelections;
88 :
89 : u32 m_Seed; // seed used for random variations
90 :
91 : bool m_ConstructionPreview;
92 :
93 : bool m_VisibleInAtlasOnly;
94 : bool m_IsActorOnly; // an in-world entity should not have this or it might not be rendered.
95 :
96 : bool m_SilhouetteDisplay;
97 : bool m_SilhouetteOccluder;
98 : bool m_DisableShadows;
99 :
100 : ICmpUnitRenderer::tag_t m_ModelTag;
101 :
102 : public:
103 116 : static std::string GetSchema()
104 : {
105 : return
106 : "<a:help>Display the unit using the engine's actor system.</a:help>"
107 : "<a:example>"
108 : "<Actor>units/hellenes/infantry_spearman_b.xml</Actor>"
109 : "</a:example>"
110 : "<a:example>"
111 : "<Actor>structures/hellenes/barracks.xml</Actor>"
112 : "<FoundationActor>structures/fndn_4x4.xml</FoundationActor>"
113 : "</a:example>"
114 : "<element name='Actor' a:help='Filename of the actor to be used for this unit'>"
115 : "<text/>"
116 : "</element>"
117 : "<optional>"
118 : "<element name='FoundationActor' a:help='Filename of the actor to be used the foundation while this unit is being constructed'>"
119 : "<text/>"
120 : "</element>"
121 : "</optional>"
122 : "<optional>"
123 : "<element name='Foundation' a:help='Used internally; if present, the unit will be rendered as a foundation'>"
124 : "<empty/>"
125 : "</element>"
126 : "</optional>"
127 : "<optional>"
128 : "<element name='ConstructionPreview' a:help='If present, the unit should have a construction preview'>"
129 : "<empty/>"
130 : "</element>"
131 : "</optional>"
132 : "<optional>"
133 : "<element name='DisableShadows' a:help='Used internally; if present, shadows will be disabled'>"
134 : "<empty/>"
135 : "</element>"
136 : "</optional>"
137 : "<optional>"
138 : "<element name='ActorOnly' a:help='Used internally; if present, the unit will only be rendered if the user has high enough graphical settings.'>"
139 : "<empty/>"
140 : "</element>"
141 : "</optional>"
142 : "<element name='SilhouetteDisplay'>"
143 : "<data type='boolean'/>"
144 : "</element>"
145 : "<element name='SilhouetteOccluder'>"
146 : "<data type='boolean'/>"
147 : "</element>"
148 : "<optional>"
149 : "<element name='SelectionShape'>"
150 : "<choice>"
151 : "<element name='Bounds' a:help='Determines the selection box based on the model bounds'>"
152 : "<empty/>"
153 : "</element>"
154 : "<element name='Footprint' a:help='Determines the selection box based on the entity Footprint component'>"
155 : "<empty/>"
156 : "</element>"
157 : "<element name='Box' a:help='Sets the selection shape to a box of specified dimensions'>"
158 : "<attribute name='width'>"
159 : "<data type='decimal'>"
160 : "<param name='minExclusive'>0.0</param>"
161 : "</data>"
162 : "</attribute>"
163 : "<attribute name='height'>"
164 : "<data type='decimal'>"
165 : "<param name='minExclusive'>0.0</param>"
166 : "</data>"
167 : "</attribute>"
168 : "<attribute name='depth'>"
169 : "<data type='decimal'>"
170 : "<param name='minExclusive'>0.0</param>"
171 : "</data>"
172 : "</attribute>"
173 : "</element>"
174 : "<element name='Cylinder' a:help='Sets the selection shape to a cylinder of specified dimensions'>"
175 : "<attribute name='radius'>"
176 : "<data type='decimal'>"
177 : "<param name='minExclusive'>0.0</param>"
178 : "</data>"
179 : "</attribute>"
180 : "<attribute name='height'>"
181 : "<data type='decimal'>"
182 : "<param name='minExclusive'>0.0</param>"
183 : "</data>"
184 : "</attribute>"
185 : "</element>"
186 : "</choice>"
187 : "</element>"
188 : "</optional>"
189 : "<element name='VisibleInAtlasOnly'>"
190 : "<data type='boolean'/>"
191 116 : "</element>";
192 : }
193 :
194 0 : void Init(const CParamNode& paramNode) override
195 : {
196 0 : m_Unit = NULL;
197 0 : m_R = m_G = m_B = fixed::FromInt(1);
198 :
199 0 : m_ConstructionPreview = paramNode.GetChild("ConstructionPreview").IsOk();
200 :
201 0 : m_Seed = GetEntityId();
202 :
203 0 : m_IsFoundationActor = paramNode.GetChild("Foundation").IsOk() && paramNode.GetChild("FoundationActor").IsOk();
204 :
205 0 : m_BaseActorName = paramNode.GetChild(m_IsFoundationActor ? "FoundationActor" : "Actor").ToWString();
206 0 : ParseActorName(m_BaseActorName);
207 :
208 0 : m_VisibleInAtlasOnly = paramNode.GetChild("VisibleInAtlasOnly").ToBool();
209 0 : m_IsActorOnly = paramNode.GetChild("ActorOnly").IsOk();
210 :
211 0 : m_SilhouetteDisplay = paramNode.GetChild("SilhouetteDisplay").ToBool();
212 0 : m_SilhouetteOccluder = paramNode.GetChild("SilhouetteOccluder").ToBool();
213 0 : m_DisableShadows = paramNode.GetChild("DisableShadows").ToBool();
214 :
215 : // Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the
216 : // Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
217 : // shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
218 : // which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
219 : // initialize the selection shape descriptor on-demand.
220 0 : InitSelectionShapeDescriptor(paramNode);
221 0 : }
222 :
223 0 : void Deinit() override
224 : {
225 0 : if (m_Unit)
226 : {
227 0 : GetSimContext().GetUnitManager().DeleteUnit(m_Unit);
228 0 : m_Unit = NULL;
229 : }
230 0 : }
231 :
232 : template<typename S>
233 0 : void SerializeCommon(S& serialize)
234 : {
235 0 : serialize.NumberFixed_Unbounded("r", m_R);
236 0 : serialize.NumberFixed_Unbounded("g", m_G);
237 0 : serialize.NumberFixed_Unbounded("b", m_B);
238 :
239 0 : serialize.StringASCII("anim name", m_AnimName, 0, 256);
240 0 : serialize.Bool("anim once", m_AnimOnce);
241 0 : serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed);
242 0 : serialize.String("sound group", m_SoundGroup, 0, 256);
243 0 : serialize.NumberFixed_Unbounded("anim desync", m_AnimDesync);
244 0 : serialize.NumberFixed_Unbounded("anim sync repeat time", m_AnimSyncRepeatTime);
245 0 : serialize.NumberFixed_Unbounded("anim sync offset time", m_AnimSyncOffsetTime);
246 :
247 0 : Serializer(serialize, "variation", m_VariantSelections);
248 :
249 0 : serialize.NumberU32_Unbounded("seed", m_Seed);
250 0 : serialize.String("actor", m_ActorName, 0, 256);
251 :
252 : // TODO: store actor variables?
253 0 : }
254 :
255 0 : void Serialize(ISerializer& serialize) override
256 : {
257 : // TODO: store the actor name, if !debug and it differs from the template
258 :
259 0 : if (serialize.IsDebug())
260 : {
261 0 : serialize.String("base actor", m_BaseActorName, 0, 256);
262 : }
263 :
264 0 : SerializeCommon(serialize);
265 0 : }
266 :
267 0 : void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
268 : {
269 0 : Init(paramNode);
270 :
271 0 : u32 oldSeed = GetActorSeed();
272 :
273 0 : SerializeCommon(deserialize);
274 :
275 0 : InitModel();
276 :
277 : // If we serialized a different seed or different actor, reload actor
278 0 : if (oldSeed != GetActorSeed() || m_BaseActorName != m_ActorName)
279 0 : ReloadActor();
280 : else
281 0 : ReloadUnitAnimation();
282 :
283 0 : if (m_Unit)
284 : {
285 0 : CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
286 0 : if (cmpOwnership)
287 0 : m_Unit->GetModel().SetPlayerID(cmpOwnership->GetOwner());
288 : }
289 0 : }
290 :
291 0 : void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
292 : {
293 0 : switch (msg.GetType())
294 : {
295 0 : case MT_OwnershipChanged:
296 : {
297 0 : RecomputeActorName();
298 :
299 0 : if (!m_Unit)
300 0 : break;
301 :
302 0 : const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
303 0 : m_Unit->GetModel().SetPlayerID(msgData.to);
304 :
305 0 : break;
306 : }
307 0 : case MT_ValueModification:
308 : {
309 : // Mirages don't respond to technology modifications.
310 0 : CmpPtr<ICmpMirage> cmpMirage(GetEntityHandle());
311 0 : if (cmpMirage)
312 0 : return;
313 :
314 0 : const CMessageValueModification& msgData = static_cast<const CMessageValueModification&> (msg);
315 0 : if (msgData.component != L"VisualActor")
316 0 : break;
317 :
318 0 : RecomputeActorName();
319 :
320 0 : break;
321 : }
322 0 : case MT_InterpolatedPositionChanged:
323 : {
324 0 : const CMessageInterpolatedPositionChanged& msgData = static_cast<const CMessageInterpolatedPositionChanged&> (msg);
325 0 : if (m_ModelTag.valid())
326 : {
327 0 : CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
328 0 : cmpModelRenderer->UpdateUnitPos(m_ModelTag, msgData.inWorld, msgData.pos0, msgData.pos1);
329 : }
330 0 : break;
331 : }
332 0 : case MT_Create:
333 : {
334 0 : InitModel();
335 :
336 0 : SelectAnimation("idle");
337 0 : break;
338 : }
339 0 : case MT_Destroy:
340 : {
341 0 : if (m_ModelTag.valid())
342 : {
343 0 : CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
344 0 : cmpModelRenderer->RemoveUnit(m_ModelTag);
345 0 : m_ModelTag = ICmpUnitRenderer::tag_t();
346 : }
347 0 : break;
348 : }
349 : }
350 : }
351 :
352 0 : CBoundingBoxAligned GetBounds() const override
353 : {
354 0 : if (!m_Unit)
355 0 : return CBoundingBoxAligned::EMPTY;
356 0 : return m_Unit->GetModel().GetWorldBounds();
357 : }
358 :
359 0 : CUnit* GetUnit() override
360 : {
361 0 : return m_Unit;
362 : }
363 :
364 0 : CBoundingBoxOriented GetSelectionBox() const override
365 : {
366 0 : if (!m_Unit)
367 0 : return CBoundingBoxOriented::EMPTY;
368 0 : return m_Unit->GetModel().GetSelectionBox();
369 : }
370 :
371 0 : CVector3D GetPosition() const override
372 : {
373 0 : if (!m_Unit)
374 0 : return CVector3D(0, 0, 0);
375 0 : return m_Unit->GetModel().GetTransform().GetTranslation();
376 : }
377 :
378 0 : std::wstring GetProjectileActor() const override
379 : {
380 0 : if (!m_Unit)
381 0 : return L"";
382 0 : return m_Unit->GetObject().m_ProjectileModelName;
383 : }
384 :
385 0 : CFixedVector3D GetProjectileLaunchPoint() const override
386 : {
387 0 : if (!m_Unit)
388 0 : return CFixedVector3D();
389 :
390 0 : if (m_Unit->GetModel().ToCModel())
391 : {
392 : // Ensure the prop transforms are correct
393 0 : CmpPtr<ICmpUnitRenderer> cmpUnitRenderer(GetSystemEntity());
394 0 : CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
395 0 : if (cmpUnitRenderer && cmpPosition)
396 : {
397 0 : float frameOffset = cmpUnitRenderer->GetFrameOffset();
398 0 : CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset));
399 0 : m_Unit->GetModel().SetTransform(transform);
400 0 : m_Unit->GetModel().ValidatePosition();
401 : }
402 :
403 0 : CModelAbstract* ammo = m_Unit->GetModel().ToCModel()->FindFirstAmmoProp();
404 0 : if (ammo)
405 : {
406 0 : CVector3D vector = ammo->GetTransform().GetTranslation();
407 0 : return CFixedVector3D(fixed::FromFloat(vector.X), fixed::FromFloat(vector.Y), fixed::FromFloat(vector.Z));
408 : }
409 : }
410 :
411 0 : return CFixedVector3D();
412 : }
413 :
414 0 : void SetVariant(const CStr& key, const CStr& selection) override
415 : {
416 0 : if (m_VariantSelections[key] == selection)
417 0 : return;
418 :
419 0 : m_VariantSelections[key] = selection;
420 :
421 0 : if (m_Unit)
422 : {
423 0 : m_Unit->SetEntitySelection(key, selection);
424 0 : if (m_Unit->GetAnimation())
425 0 : m_Unit->GetAnimation()->ReloadAnimation();
426 : }
427 : }
428 :
429 0 : std::string GetAnimationName() const override
430 : {
431 0 : return m_AnimName;
432 : }
433 :
434 0 : void SelectAnimation(const std::string& name, bool once = false, fixed speed = fixed::FromInt(1)) override
435 : {
436 0 : m_AnimName = name;
437 0 : m_AnimOnce = once;
438 0 : m_AnimSpeed = speed;
439 0 : m_SoundGroup = L"";
440 0 : m_AnimDesync = fixed::FromInt(1)/20; // TODO: make this an argument
441 0 : m_AnimSyncRepeatTime = fixed::Zero();
442 0 : m_AnimSyncOffsetTime = fixed::Zero();
443 :
444 0 : SetVariant("animation", m_AnimName);
445 :
446 0 : CmpPtr<ICmpSound> cmpSound(GetEntityHandle());
447 0 : if (cmpSound)
448 0 : m_SoundGroup = cmpSound->GetSoundGroup(wstring_from_utf8(m_AnimName));
449 :
450 0 : if (!m_Unit || !m_Unit->GetAnimation() || !m_Unit->GetID())
451 0 : return;
452 :
453 0 : m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
454 : }
455 :
456 0 : void SelectMovementAnimation(const std::string& name, fixed speed) override
457 : {
458 0 : ENSURE(name == "idle" || name == "walk" || name == "run");
459 0 : if (m_AnimName != "idle" && m_AnimName != "walk" && m_AnimName != "run")
460 0 : return;
461 0 : if (m_AnimName == name && speed == m_AnimSpeed)
462 0 : return;
463 0 : SelectAnimation(name, false, speed);
464 : }
465 :
466 0 : void SetAnimationSyncRepeat(fixed repeattime) override
467 : {
468 0 : m_AnimSyncRepeatTime = repeattime;
469 :
470 0 : if (m_Unit && m_Unit->GetAnimation())
471 0 : m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat());
472 0 : }
473 :
474 0 : void SetAnimationSyncOffset(fixed actiontime) override
475 : {
476 0 : m_AnimSyncOffsetTime = actiontime;
477 :
478 0 : if (m_Unit && m_Unit->GetAnimation())
479 0 : m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat());
480 0 : }
481 :
482 0 : void SetShadingColor(fixed r, fixed g, fixed b, fixed a) override
483 : {
484 0 : m_R = r;
485 0 : m_G = g;
486 0 : m_B = b;
487 : UNUSED2(a); // TODO: why is this even an argument?
488 :
489 0 : if (m_Unit)
490 : {
491 0 : CModelAbstract& model = m_Unit->GetModel();
492 0 : model.SetShadingColor(CColor(m_R.ToFloat(), m_G.ToFloat(), m_B.ToFloat(), 1.0f));
493 : }
494 0 : }
495 :
496 0 : void SetVariable(const std::string& name, float value) override
497 : {
498 0 : if (m_Unit)
499 0 : m_Unit->GetModel().SetEntityVariable(name, value);
500 0 : }
501 :
502 0 : u32 GetActorSeed() const override
503 : {
504 0 : return m_Seed;
505 : }
506 :
507 0 : void SetActorSeed(u32 seed) override
508 : {
509 0 : if (seed == m_Seed)
510 0 : return;
511 :
512 0 : m_Seed = seed;
513 0 : ReloadActor();
514 : }
515 :
516 0 : void RecomputeActorName() override
517 : {
518 0 : CmpPtr<ICmpValueModificationManager> cmpValueModificationManager(GetSystemEntity());
519 0 : std::wstring newActorName;
520 0 : if (m_IsFoundationActor)
521 0 : newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/FoundationActor", m_BaseActorName, GetEntityId());
522 : else
523 0 : newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/Actor", m_BaseActorName, GetEntityId());
524 :
525 0 : if (newActorName != m_ActorName)
526 : {
527 0 : ParseActorName(newActorName);
528 0 : ReloadActor();
529 : }
530 0 : }
531 :
532 0 : bool HasConstructionPreview() const override
533 : {
534 0 : return m_ConstructionPreview;
535 : }
536 :
537 0 : void Hotload(const VfsPath& name) override
538 : {
539 0 : if (!m_Unit)
540 0 : return;
541 :
542 0 : if (!name.empty() && name != m_ActorName)
543 0 : return;
544 :
545 0 : ReloadActor();
546 : }
547 :
548 : private:
549 : // Replace {phenotype} with the correct value in m_ActorName
550 : void ParseActorName(std::wstring base);
551 :
552 : /// Helper function shared by component init and actor reloading
553 : void InitModel();
554 :
555 : /// Helper method; initializes the model selection shape descriptor from XML. Factored out for readability of @ref Init.
556 : void InitSelectionShapeDescriptor(const CParamNode& paramNode);
557 :
558 : // ReloadActor is used when the actor or seed changes.
559 : void ReloadActor();
560 : // ReloadUnitAnimation is used for a minimal reloading upon deserialization, when the actor and seed are identical.
561 : // It is also used by ReloadActor.
562 : void ReloadUnitAnimation();
563 : };
564 :
565 116 : REGISTER_COMPONENT_TYPE(VisualActor)
566 :
567 : // ------------------------------------------------------------------------------------------------------------------
568 :
569 0 : void CCmpVisualActor::ParseActorName(std::wstring base)
570 : {
571 0 : CmpPtr<ICmpIdentity> cmpIdentity(GetEntityHandle());
572 0 : const std::wstring pattern = L"{phenotype}";
573 0 : if (cmpIdentity)
574 : {
575 0 : size_t pos = base.find(pattern);
576 0 : while (pos != std::string::npos)
577 : {
578 0 : base.replace(pos, pattern.size(), cmpIdentity->GetPhenotype());
579 0 : pos = base.find(pattern, pos + pattern.size());
580 : }
581 : }
582 :
583 0 : m_ActorName = base;
584 0 : }
585 :
586 0 : void CCmpVisualActor::InitModel()
587 : {
588 0 : if (!GetSimContext().HasUnitManager())
589 0 : return;
590 :
591 0 : std::wstring actorName = m_ActorName;
592 0 : if (actorName.find(L".xml") == std::wstring::npos)
593 0 : actorName += L".xml";
594 0 : m_Unit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed());
595 0 : if (!m_Unit)
596 0 : return;
597 :
598 0 : CModelAbstract& model = m_Unit->GetModel();
599 0 : if (model.ToCModel())
600 : {
601 0 : u32 modelFlags = 0;
602 :
603 0 : if (m_SilhouetteDisplay)
604 0 : modelFlags |= MODELFLAG_SILHOUETTE_DISPLAY;
605 :
606 0 : if (m_SilhouetteOccluder)
607 0 : modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
608 :
609 0 : CmpPtr<ICmpVisibility> cmpVisibility(GetEntityHandle());
610 0 : if (cmpVisibility && cmpVisibility->GetAlwaysVisible())
611 0 : modelFlags |= MODELFLAG_IGNORE_LOS;
612 :
613 0 : model.ToCModel()->AddFlagsRec(modelFlags);
614 : }
615 :
616 0 : if (m_DisableShadows)
617 : {
618 0 : if (model.ToCModel())
619 0 : model.ToCModel()->RemoveShadowsRec();
620 0 : else if (model.ToCModelDecal())
621 0 : model.ToCModelDecal()->RemoveShadows();
622 : }
623 :
624 0 : m_Unit->SetID(GetEntityId());
625 :
626 0 : bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater;
627 0 : CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
628 0 : if (cmpPosition)
629 0 : cmpPosition->SetActorFloating(floating);
630 :
631 0 : if (!m_ModelTag.valid())
632 : {
633 0 : CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
634 0 : if (cmpModelRenderer)
635 : {
636 : // TODO: this should account for all possible props, animations, etc,
637 : // else we might accidentally cull the unit when it should be visible
638 0 : CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
639 0 : CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
640 :
641 0 : int flags = 0;
642 0 : if (m_IsActorOnly)
643 0 : flags |= ICmpUnitRenderer::ACTOR_ONLY;
644 0 : if (m_VisibleInAtlasOnly)
645 0 : flags |= ICmpUnitRenderer::VISIBLE_IN_ATLAS_ONLY;
646 :
647 0 : m_ModelTag = cmpModelRenderer->AddUnit(GetEntityHandle(), m_Unit, boundSphere, flags);
648 : }
649 : }
650 :
651 : // the model is now responsible for cleaning up the descriptor
652 0 : if (m_ShapeDescriptor != nullptr)
653 0 : m_Unit->GetModel().SetCustomSelectionShape(m_ShapeDescriptor);
654 : }
655 :
656 0 : void CCmpVisualActor::InitSelectionShapeDescriptor(const CParamNode& paramNode)
657 : {
658 : // by default, we don't need a custom selection shape and we can just keep the default behaviour
659 0 : m_ShapeDescriptor = nullptr;
660 :
661 0 : const CParamNode& shapeNode = paramNode.GetChild("SelectionShape");
662 0 : if (shapeNode.IsOk())
663 : {
664 0 : if (shapeNode.GetChild("Bounds").IsOk())
665 : {
666 : // default; no need to take action
667 : }
668 0 : else if (shapeNode.GetChild("Footprint").IsOk())
669 : {
670 0 : CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
671 0 : if (cmpFootprint)
672 : {
673 : ICmpFootprint::EShape fpShape; // fp stands for "footprint"
674 0 : entity_pos_t fpSize0, fpSize1, fpHeight; // fp stands for "footprint"
675 0 : cmpFootprint->GetShape(fpShape, fpSize0, fpSize1, fpHeight);
676 :
677 0 : float size0 = fpSize0.ToFloat();
678 0 : float size1 = fpSize1.ToFloat();
679 :
680 : // TODO: we should properly distinguish between CIRCLE and SQUARE footprint shapes here, but since cylinders
681 : // aren't implemented yet and are almost indistinguishable from boxes for small enough sizes anyway,
682 : // we'll just use boxes for either case. However, for circular footprints the size0 and size1 values both
683 : // represent the radius, so we do have to adjust them to match the size1 and size0's of square footprints
684 : // (which represent the full width and depth).
685 0 : if (fpShape == ICmpFootprint::CIRCLE)
686 : {
687 0 : size0 *= 2;
688 0 : size1 *= 2;
689 : }
690 :
691 0 : m_ShapeDescriptor = new CModelAbstract::CustomSelectionShape;
692 0 : m_ShapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
693 0 : m_ShapeDescriptor->m_Size0 = size0;
694 0 : m_ShapeDescriptor->m_Size1 = size1;
695 0 : m_ShapeDescriptor->m_Height = fpHeight.ToFloat();
696 : }
697 : else
698 : {
699 0 : LOGERROR("[VisualActor] Cannot apply footprint-based SelectionShape; Footprint component not initialized.");
700 : }
701 : }
702 0 : else if (shapeNode.GetChild("Box").IsOk())
703 : {
704 : // TODO: we might need to support the ability to specify a different box center in the future
705 0 : m_ShapeDescriptor = new CModelAbstract::CustomSelectionShape;
706 0 : m_ShapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
707 0 : m_ShapeDescriptor->m_Size0 = shapeNode.GetChild("Box").GetChild("@width").ToFixed().ToFloat();
708 0 : m_ShapeDescriptor->m_Size1 = shapeNode.GetChild("Box").GetChild("@depth").ToFixed().ToFloat();
709 0 : m_ShapeDescriptor->m_Height = shapeNode.GetChild("Box").GetChild("@height").ToFixed().ToFloat();
710 : }
711 0 : else if (shapeNode.GetChild("Cylinder").IsOk())
712 : {
713 0 : LOGWARNING("[VisualActor] TODO: Cylinder selection shapes are not yet implemented; defaulting to recursive bounding boxes");
714 : }
715 : else
716 : {
717 : // shouldn't happen by virtue of validation against schema
718 0 : LOGERROR("[VisualActor] No selection shape specified");
719 : }
720 : }
721 0 : }
722 :
723 0 : void CCmpVisualActor::ReloadActor()
724 : {
725 0 : if (!m_Unit)
726 0 : return;
727 :
728 : // Save some data from the old unit
729 0 : CColor shading = m_Unit->GetModel().GetShadingColor();
730 0 : player_id_t playerID = m_Unit->GetModel().GetPlayerID();
731 :
732 : // Replace with the new unit
733 0 : GetSimContext().GetUnitManager().DeleteUnit(m_Unit);
734 :
735 : // HACK: selection shape needs template data, but rather than storing all that data
736 : // in the component, we load the template here and pass it into a helper function
737 0 : CmpPtr<ICmpTemplateManager> cmpTemplateManager(GetSystemEntity());
738 0 : const CParamNode* node = cmpTemplateManager->LoadLatestTemplate(GetEntityId());
739 0 : ENSURE(node && node->GetChild("VisualActor").IsOk());
740 :
741 0 : InitSelectionShapeDescriptor(node->GetChild("VisualActor"));
742 :
743 0 : InitModel();
744 :
745 0 : if (!m_Unit)
746 : {
747 0 : if (m_ModelTag.valid())
748 : {
749 0 : CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
750 0 : if (cmpModelRenderer)
751 0 : cmpModelRenderer->RemoveUnit(m_ModelTag);
752 0 : m_ModelTag = ICmpUnitRenderer::tag_t{};
753 : }
754 0 : return;
755 : }
756 :
757 0 : ReloadUnitAnimation();
758 :
759 0 : m_Unit->GetModel().SetShadingColor(shading);
760 :
761 0 : m_Unit->GetModel().SetPlayerID(playerID);
762 :
763 0 : if (m_ModelTag.valid())
764 : {
765 0 : CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
766 0 : CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
767 0 : CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
768 0 : cmpModelRenderer->UpdateUnit(m_ModelTag, m_Unit, boundSphere);
769 : }
770 : }
771 :
772 0 : void CCmpVisualActor::ReloadUnitAnimation()
773 : {
774 0 : if (!m_Unit)
775 0 : return;
776 :
777 0 : m_Unit->SetEntitySelection(m_VariantSelections);
778 :
779 0 : if (!m_Unit->GetAnimation())
780 0 : return;
781 :
782 0 : m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
783 :
784 : // We'll lose the exact synchronisation but we should at least make sure it's going at the correct rate
785 0 : if (!m_AnimSyncRepeatTime.IsZero())
786 0 : m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat());
787 0 : if (!m_AnimSyncOffsetTime.IsZero())
788 0 : m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat());
789 3 : }
|