Moves model flags to ModelAbstract.
[0ad.git] / source / simulation2 / components / CCmpVisualActor.cpp
blob1f7878782a5b10bbd11b3a978e40b4ee7150399f
1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "simulation2/system/Component.h"
21 #include "ICmpVisual.h"
23 #include "simulation2/MessageTypes.h"
24 #include "simulation2/serialization/SerializedTypes.h"
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"
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"
54 class CCmpVisualActor final : public ICmpVisual
56 public:
57 static void ClassInit(CComponentManager& componentManager)
59 componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged);
60 componentManager.SubscribeToMessageType(MT_OwnershipChanged);
61 componentManager.SubscribeToMessageType(MT_ValueModification);
62 componentManager.SubscribeToMessageType(MT_Create);
63 componentManager.SubscribeToMessageType(MT_Destroy);
66 DEFAULT_COMPONENT_ALLOCATOR(VisualActor)
68 private:
69 std::wstring m_BaseActorName, m_ActorName;
70 bool m_IsFoundationActor;
72 // Not initialized in non-visual mode
73 CUnit* m_Unit;
74 CModelAbstract::CustomSelectionShape* m_ShapeDescriptor = nullptr;
76 fixed m_R, m_G, m_B; // shading color
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;
87 std::map<CStr, CStr> m_VariantSelections;
89 u32 m_Seed; // seed used for random variations
91 bool m_ConstructionPreview;
93 bool m_VisibleInAtlasOnly;
94 bool m_IsActorOnly; // an in-world entity should not have this or it might not be rendered.
96 bool m_SilhouetteDisplay;
97 bool m_SilhouetteOccluder;
98 bool m_DisableShadows;
100 ICmpUnitRenderer::tag_t m_ModelTag;
102 public:
103 static std::string GetSchema()
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 "</element>";
194 void Init(const CParamNode& paramNode) override
196 m_Unit = NULL;
197 m_R = m_G = m_B = fixed::FromInt(1);
199 m_ConstructionPreview = paramNode.GetChild("ConstructionPreview").IsOk();
201 m_Seed = GetEntityId();
203 m_IsFoundationActor = paramNode.GetChild("Foundation").IsOk() && paramNode.GetChild("FoundationActor").IsOk();
205 m_BaseActorName = paramNode.GetChild(m_IsFoundationActor ? "FoundationActor" : "Actor").ToWString();
206 ParseActorName(m_BaseActorName);
208 m_VisibleInAtlasOnly = paramNode.GetChild("VisibleInAtlasOnly").ToBool();
209 m_IsActorOnly = paramNode.GetChild("ActorOnly").IsOk();
211 m_SilhouetteDisplay = paramNode.GetChild("SilhouetteDisplay").ToBool();
212 m_SilhouetteOccluder = paramNode.GetChild("SilhouetteOccluder").ToBool();
213 m_DisableShadows = paramNode.GetChild("DisableShadows").ToBool();
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 InitSelectionShapeDescriptor(paramNode);
223 void Deinit() override
225 if (m_Unit)
227 GetSimContext().GetUnitManager().DeleteUnit(m_Unit);
228 m_Unit = NULL;
232 template<typename S>
233 void SerializeCommon(S& serialize)
235 serialize.NumberFixed_Unbounded("r", m_R);
236 serialize.NumberFixed_Unbounded("g", m_G);
237 serialize.NumberFixed_Unbounded("b", m_B);
239 serialize.StringASCII("anim name", m_AnimName, 0, 256);
240 serialize.Bool("anim once", m_AnimOnce);
241 serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed);
242 serialize.String("sound group", m_SoundGroup, 0, 256);
243 serialize.NumberFixed_Unbounded("anim desync", m_AnimDesync);
244 serialize.NumberFixed_Unbounded("anim sync repeat time", m_AnimSyncRepeatTime);
245 serialize.NumberFixed_Unbounded("anim sync offset time", m_AnimSyncOffsetTime);
247 Serializer(serialize, "variation", m_VariantSelections);
249 serialize.NumberU32_Unbounded("seed", m_Seed);
250 serialize.String("actor", m_ActorName, 0, 256);
252 // TODO: store actor variables?
255 void Serialize(ISerializer& serialize) override
257 // TODO: store the actor name, if !debug and it differs from the template
259 if (serialize.IsDebug())
261 serialize.String("base actor", m_BaseActorName, 0, 256);
264 SerializeCommon(serialize);
267 void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
269 Init(paramNode);
271 u32 oldSeed = GetActorSeed();
273 SerializeCommon(deserialize);
275 InitModel();
277 // If we serialized a different seed or different actor, reload actor
278 if (oldSeed != GetActorSeed() || m_BaseActorName != m_ActorName)
279 ReloadActor();
280 else
281 ReloadUnitAnimation();
283 if (m_Unit)
285 CmpPtr<ICmpOwnership> cmpOwnership(GetEntityHandle());
286 if (cmpOwnership)
287 m_Unit->GetModel().SetPlayerID(cmpOwnership->GetOwner());
291 void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
293 switch (msg.GetType())
295 case MT_OwnershipChanged:
297 RecomputeActorName();
299 if (!m_Unit)
300 break;
302 const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
303 m_Unit->GetModel().SetPlayerID(msgData.to);
305 break;
307 case MT_ValueModification:
309 // Mirages don't respond to technology modifications.
310 CmpPtr<ICmpMirage> cmpMirage(GetEntityHandle());
311 if (cmpMirage)
312 return;
314 const CMessageValueModification& msgData = static_cast<const CMessageValueModification&> (msg);
315 if (msgData.component != L"VisualActor")
316 break;
318 RecomputeActorName();
320 break;
322 case MT_InterpolatedPositionChanged:
324 const CMessageInterpolatedPositionChanged& msgData = static_cast<const CMessageInterpolatedPositionChanged&> (msg);
325 if (m_ModelTag.valid())
327 CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
328 cmpModelRenderer->UpdateUnitPos(m_ModelTag, msgData.inWorld, msgData.pos0, msgData.pos1);
330 break;
332 case MT_Create:
334 InitModel();
336 SelectAnimation("idle");
337 break;
339 case MT_Destroy:
341 if (m_ModelTag.valid())
343 CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
344 cmpModelRenderer->RemoveUnit(m_ModelTag);
345 m_ModelTag = ICmpUnitRenderer::tag_t();
347 break;
352 CBoundingBoxAligned GetBounds() const override
354 if (!m_Unit)
355 return CBoundingBoxAligned::EMPTY;
356 return m_Unit->GetModel().GetWorldBounds();
359 CUnit* GetUnit() override
361 return m_Unit;
364 CBoundingBoxOriented GetSelectionBox() const override
366 if (!m_Unit)
367 return CBoundingBoxOriented::EMPTY;
368 return m_Unit->GetModel().GetSelectionBox();
371 CVector3D GetPosition() const override
373 if (!m_Unit)
374 return CVector3D(0, 0, 0);
375 return m_Unit->GetModel().GetTransform().GetTranslation();
378 std::wstring GetProjectileActor() const override
380 if (!m_Unit)
381 return L"";
382 return m_Unit->GetObject().m_ProjectileModelName;
385 CFixedVector3D GetProjectileLaunchPoint() const override
387 if (!m_Unit)
388 return CFixedVector3D();
390 if (m_Unit->GetModel().ToCModel())
392 // Ensure the prop transforms are correct
393 CmpPtr<ICmpUnitRenderer> cmpUnitRenderer(GetSystemEntity());
394 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
395 if (cmpUnitRenderer && cmpPosition)
397 float frameOffset = cmpUnitRenderer->GetFrameOffset();
398 CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset));
399 m_Unit->GetModel().SetTransform(transform);
400 m_Unit->GetModel().ValidatePosition();
403 CModelAbstract* ammo = m_Unit->GetModel().ToCModel()->FindFirstAmmoProp();
404 if (ammo)
406 CVector3D vector = ammo->GetTransform().GetTranslation();
407 return CFixedVector3D(fixed::FromFloat(vector.X), fixed::FromFloat(vector.Y), fixed::FromFloat(vector.Z));
411 return CFixedVector3D();
414 void SetVariant(const CStr& key, const CStr& selection) override
416 if (m_VariantSelections[key] == selection)
417 return;
419 m_VariantSelections[key] = selection;
421 if (m_Unit)
423 m_Unit->SetEntitySelection(key, selection);
424 if (m_Unit->GetAnimation())
425 m_Unit->GetAnimation()->ReloadAnimation();
429 std::string GetAnimationName() const override
431 return m_AnimName;
434 void SelectAnimation(const std::string& name, bool once = false, fixed speed = fixed::FromInt(1)) override
436 m_AnimName = name;
437 m_AnimOnce = once;
438 m_AnimSpeed = speed;
439 m_SoundGroup = L"";
440 m_AnimDesync = fixed::FromInt(1)/20; // TODO: make this an argument
441 m_AnimSyncRepeatTime = fixed::Zero();
442 m_AnimSyncOffsetTime = fixed::Zero();
444 SetVariant("animation", m_AnimName);
446 CmpPtr<ICmpSound> cmpSound(GetEntityHandle());
447 if (cmpSound)
448 m_SoundGroup = cmpSound->GetSoundGroup(wstring_from_utf8(m_AnimName));
450 if (!m_Unit || !m_Unit->GetAnimation() || !m_Unit->GetID())
451 return;
453 m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
456 void SelectMovementAnimation(const std::string& name, fixed speed) override
458 ENSURE(name == "idle" || name == "walk" || name == "run");
459 if (m_AnimName != "idle" && m_AnimName != "walk" && m_AnimName != "run")
460 return;
461 if (m_AnimName == name && speed == m_AnimSpeed)
462 return;
463 SelectAnimation(name, false, speed);
466 void SetAnimationSyncRepeat(fixed repeattime) override
468 m_AnimSyncRepeatTime = repeattime;
470 if (m_Unit && m_Unit->GetAnimation())
471 m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat());
474 void SetAnimationSyncOffset(fixed actiontime) override
476 m_AnimSyncOffsetTime = actiontime;
478 if (m_Unit && m_Unit->GetAnimation())
479 m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat());
482 void SetShadingColor(fixed r, fixed g, fixed b, fixed a) override
484 m_R = r;
485 m_G = g;
486 m_B = b;
487 UNUSED2(a); // TODO: why is this even an argument?
489 if (m_Unit)
491 CModelAbstract& model = m_Unit->GetModel();
492 model.SetShadingColor(CColor(m_R.ToFloat(), m_G.ToFloat(), m_B.ToFloat(), 1.0f));
496 void SetVariable(const std::string& name, float value) override
498 if (m_Unit)
499 m_Unit->GetModel().SetEntityVariable(name, value);
502 u32 GetActorSeed() const override
504 return m_Seed;
507 void SetActorSeed(u32 seed) override
509 if (seed == m_Seed)
510 return;
512 m_Seed = seed;
513 ReloadActor();
516 void RecomputeActorName() override
518 CmpPtr<ICmpValueModificationManager> cmpValueModificationManager(GetSystemEntity());
519 std::wstring newActorName;
520 if (m_IsFoundationActor)
521 newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/FoundationActor", m_BaseActorName, GetEntityId());
522 else
523 newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/Actor", m_BaseActorName, GetEntityId());
525 if (newActorName != m_ActorName)
527 ParseActorName(newActorName);
528 ReloadActor();
532 bool HasConstructionPreview() const override
534 return m_ConstructionPreview;
537 void Hotload(const VfsPath& name) override
539 if (!m_Unit)
540 return;
542 if (!name.empty() && name != m_ActorName)
543 return;
545 ReloadActor();
548 private:
549 // Replace {phenotype} with the correct value in m_ActorName
550 void ParseActorName(std::wstring base);
552 /// Helper function shared by component init and actor reloading
553 void InitModel();
555 /// Helper method; initializes the model selection shape descriptor from XML. Factored out for readability of @ref Init.
556 void InitSelectionShapeDescriptor(const CParamNode& paramNode);
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();
565 REGISTER_COMPONENT_TYPE(VisualActor)
567 // ------------------------------------------------------------------------------------------------------------------
569 void CCmpVisualActor::ParseActorName(std::wstring base)
571 CmpPtr<ICmpIdentity> cmpIdentity(GetEntityHandle());
572 const std::wstring pattern = L"{phenotype}";
573 if (cmpIdentity)
575 size_t pos = base.find(pattern);
576 while (pos != std::string::npos)
578 base.replace(pos, pattern.size(), cmpIdentity->GetPhenotype());
579 pos = base.find(pattern, pos + pattern.size());
583 m_ActorName = base;
586 void CCmpVisualActor::InitModel()
588 if (!GetSimContext().HasUnitManager())
589 return;
591 std::wstring actorName = m_ActorName;
592 if (actorName.find(L".xml") == std::wstring::npos)
593 actorName += L".xml";
594 m_Unit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetEntityId(), GetActorSeed());
595 if (!m_Unit)
596 return;
598 CModelAbstract& model = m_Unit->GetModel();
599 if (model.ToCModel())
601 u32 modelFlags = 0;
603 if (m_SilhouetteDisplay)
604 modelFlags |= ModelFlag::SILHOUETTE_DISPLAY;
606 if (m_SilhouetteOccluder)
607 modelFlags |= ModelFlag::SILHOUETTE_OCCLUDER;
609 CmpPtr<ICmpVisibility> cmpVisibility(GetEntityHandle());
610 if (cmpVisibility && cmpVisibility->GetAlwaysVisible())
611 modelFlags |= ModelFlag::IGNORE_LOS;
613 model.ToCModel()->AddFlagsRec(modelFlags);
616 if (m_DisableShadows)
618 if (model.ToCModel())
619 model.ToCModel()->RemoveShadowsRec();
620 else if (model.ToCModelDecal())
621 model.ToCModelDecal()->RemoveShadows();
624 bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater;
625 CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
626 if (cmpPosition)
627 cmpPosition->SetActorFloating(floating);
629 if (!m_ModelTag.valid())
631 CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
632 if (cmpModelRenderer)
634 // TODO: this should account for all possible props, animations, etc,
635 // else we might accidentally cull the unit when it should be visible
636 CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
637 CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
639 int flags = 0;
640 if (m_IsActorOnly)
641 flags |= ICmpUnitRenderer::ACTOR_ONLY;
642 if (m_VisibleInAtlasOnly)
643 flags |= ICmpUnitRenderer::VISIBLE_IN_ATLAS_ONLY;
645 m_ModelTag = cmpModelRenderer->AddUnit(GetEntityHandle(), m_Unit, boundSphere, flags);
649 // the model is now responsible for cleaning up the descriptor
650 if (m_ShapeDescriptor != nullptr)
651 m_Unit->GetModel().SetCustomSelectionShape(m_ShapeDescriptor);
654 void CCmpVisualActor::InitSelectionShapeDescriptor(const CParamNode& paramNode)
656 // by default, we don't need a custom selection shape and we can just keep the default behaviour
657 m_ShapeDescriptor = nullptr;
659 const CParamNode& shapeNode = paramNode.GetChild("SelectionShape");
660 if (shapeNode.IsOk())
662 if (shapeNode.GetChild("Bounds").IsOk())
664 // default; no need to take action
666 else if (shapeNode.GetChild("Footprint").IsOk())
668 CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
669 if (cmpFootprint)
671 ICmpFootprint::EShape fpShape; // fp stands for "footprint"
672 entity_pos_t fpSize0, fpSize1, fpHeight; // fp stands for "footprint"
673 cmpFootprint->GetShape(fpShape, fpSize0, fpSize1, fpHeight);
675 float size0 = fpSize0.ToFloat();
676 float size1 = fpSize1.ToFloat();
678 // TODO: we should properly distinguish between CIRCLE and SQUARE footprint shapes here, but since cylinders
679 // aren't implemented yet and are almost indistinguishable from boxes for small enough sizes anyway,
680 // we'll just use boxes for either case. However, for circular footprints the size0 and size1 values both
681 // represent the radius, so we do have to adjust them to match the size1 and size0's of square footprints
682 // (which represent the full width and depth).
683 if (fpShape == ICmpFootprint::CIRCLE)
685 size0 *= 2;
686 size1 *= 2;
689 m_ShapeDescriptor = new CModelAbstract::CustomSelectionShape;
690 m_ShapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
691 m_ShapeDescriptor->m_Size0 = size0;
692 m_ShapeDescriptor->m_Size1 = size1;
693 m_ShapeDescriptor->m_Height = fpHeight.ToFloat();
695 else
697 LOGERROR("[VisualActor] Cannot apply footprint-based SelectionShape; Footprint component not initialized.");
700 else if (shapeNode.GetChild("Box").IsOk())
702 // TODO: we might need to support the ability to specify a different box center in the future
703 m_ShapeDescriptor = new CModelAbstract::CustomSelectionShape;
704 m_ShapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
705 m_ShapeDescriptor->m_Size0 = shapeNode.GetChild("Box").GetChild("@width").ToFixed().ToFloat();
706 m_ShapeDescriptor->m_Size1 = shapeNode.GetChild("Box").GetChild("@depth").ToFixed().ToFloat();
707 m_ShapeDescriptor->m_Height = shapeNode.GetChild("Box").GetChild("@height").ToFixed().ToFloat();
709 else if (shapeNode.GetChild("Cylinder").IsOk())
711 LOGWARNING("[VisualActor] TODO: Cylinder selection shapes are not yet implemented; defaulting to recursive bounding boxes");
713 else
715 // shouldn't happen by virtue of validation against schema
716 LOGERROR("[VisualActor] No selection shape specified");
721 void CCmpVisualActor::ReloadActor()
723 if (!m_Unit)
724 return;
726 // Save some data from the old unit
727 CColor shading = m_Unit->GetModel().GetShadingColor();
728 player_id_t playerID = m_Unit->GetModel().GetPlayerID();
730 // Replace with the new unit
731 GetSimContext().GetUnitManager().DeleteUnit(m_Unit);
733 // HACK: selection shape needs template data, but rather than storing all that data
734 // in the component, we load the template here and pass it into a helper function
735 CmpPtr<ICmpTemplateManager> cmpTemplateManager(GetSystemEntity());
736 const CParamNode* node = cmpTemplateManager->LoadLatestTemplate(GetEntityId());
737 ENSURE(node && node->GetChild("VisualActor").IsOk());
739 InitSelectionShapeDescriptor(node->GetChild("VisualActor"));
741 InitModel();
743 if (!m_Unit)
745 if (m_ModelTag.valid())
747 CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
748 if (cmpModelRenderer)
749 cmpModelRenderer->RemoveUnit(m_ModelTag);
750 m_ModelTag = ICmpUnitRenderer::tag_t{};
752 return;
755 ReloadUnitAnimation();
757 m_Unit->GetModel().SetShadingColor(shading);
759 m_Unit->GetModel().SetPlayerID(playerID);
761 if (m_ModelTag.valid())
763 CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
764 CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
765 CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
766 cmpModelRenderer->UpdateUnit(m_ModelTag, m_Unit, boundSphere);
770 void CCmpVisualActor::ReloadUnitAnimation()
772 if (!m_Unit)
773 return;
775 m_Unit->SetEntitySelection(m_VariantSelections);
777 if (!m_Unit->GetAnimation())
778 return;
780 m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
782 // We'll lose the exact synchronisation but we should at least make sure it's going at the correct rate
783 if (!m_AnimSyncRepeatTime.IsZero())
784 m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat());
785 if (!m_AnimSyncOffsetTime.IsZero())
786 m_Unit->GetAnimation()->SetAnimationSyncOffset(m_AnimSyncOffsetTime.ToFloat());