LCOV - code coverage report
Current view: top level - source/simulation2/components - CCmpVisualActor.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 11 334 3.3 %
Date: 2023-01-19 00:18:29 Functions: 5 42 11.9 %

          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 : }

Generated by: LCOV version 1.13