LCOV - code coverage report
Current view: top level - source/simulation2/components - CCmpProjectileManager.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 14 163 8.6 %
Date: 2022-06-14 00:41:00 Functions: 5 17 29.4 %

          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 "ICmpProjectileManager.h"
      22             : 
      23             : #include "ICmpObstruction.h"
      24             : #include "ICmpObstructionManager.h"
      25             : #include "ICmpPosition.h"
      26             : #include "ICmpRangeManager.h"
      27             : #include "ICmpTerrain.h"
      28             : #include "simulation2/helpers/Los.h"
      29             : #include "simulation2/MessageTypes.h"
      30             : 
      31             : #include "graphics/Model.h"
      32             : #include "graphics/Unit.h"
      33             : #include "graphics/UnitManager.h"
      34             : #include "maths/Frustum.h"
      35             : #include "maths/Matrix3D.h"
      36             : #include "maths/Quaternion.h"
      37             : #include "maths/Vector3D.h"
      38             : #include "ps/CLogger.h"
      39             : #include "renderer/Scene.h"
      40             : 
      41             : // Time (in seconds) before projectiles that stuck in the ground are destroyed
      42             : const static float PROJECTILE_DECAY_TIME = 30.f;
      43             : 
      44           3 : class CCmpProjectileManager final : public ICmpProjectileManager
      45             : {
      46             : public:
      47             :     static void ClassInit(CComponentManager& componentManager)
      48             :     {
      49         115 :         componentManager.SubscribeToMessageType(MT_Interpolate);
      50         115 :         componentManager.SubscribeToMessageType(MT_RenderSubmit);
      51             :     }
      52             : 
      53           6 :     DEFAULT_COMPONENT_ALLOCATOR(ProjectileManager)
      54             : 
      55             :     static std::string GetSchema()
      56             :     {
      57         115 :         return "<a:component type='system'/><empty/>";
      58             :     }
      59             : 
      60           3 :     void Init(const CParamNode& UNUSED(paramNode)) override
      61             :     {
      62           3 :         m_ActorSeed = 0;
      63           3 :         m_NextId = 1;
      64           3 :     }
      65             : 
      66           3 :     void Deinit() override
      67             :     {
      68           3 :         for (size_t i = 0; i < m_Projectiles.size(); ++i)
      69           0 :             GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles[i].unit);
      70           3 :         m_Projectiles.clear();
      71           3 :     }
      72             : 
      73           0 :     void Serialize(ISerializer& serialize) override
      74             :     {
      75             :         // Because this is just graphical effects, and because it's all non-deterministic floating point,
      76             :         // we don't do much serialization here.
      77             :         // (That means projectiles will vanish if you save/load - is that okay?)
      78             : 
      79             :         // The attack code stores the id so that the projectile gets deleted when it hits the target
      80           0 :         serialize.NumberU32_Unbounded("next id", m_NextId);
      81           0 :     }
      82             : 
      83           0 :     void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) override
      84             :     {
      85           0 :         Init(paramNode);
      86             : 
      87             :         // The attack code stores the id so that the projectile gets deleted when it hits the target
      88           0 :         deserialize.NumberU32_Unbounded("next id", m_NextId);
      89           0 :     }
      90             : 
      91           0 :     void HandleMessage(const CMessage& msg, bool UNUSED(global)) override
      92             :     {
      93           0 :         switch (msg.GetType())
      94             :         {
      95           0 :         case MT_Interpolate:
      96           0 :         {
      97           0 :             const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
      98           0 :             Interpolate(msgData.deltaSimTime);
      99           0 :             break;
     100             :         }
     101           0 :         case MT_RenderSubmit:
     102           0 :         {
     103           0 :             const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
     104           0 :             RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
     105           0 :             break;
     106             :         }
     107             :         }
     108           0 :     }
     109             : 
     110           0 :     uint32_t LaunchProjectileAtPoint(const CFixedVector3D& launchPoint, const CFixedVector3D& target, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime) override
     111             :     {
     112           0 :         return LaunchProjectile(launchPoint, target, speed, gravity, actorName, impactActorName, impactAnimationLifetime);
     113             :     }
     114             : 
     115             :     void RemoveProjectile(uint32_t) override;
     116             : 
     117             :     void RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector, const CFrustum& frustum, bool culling,
     118             :         const CLosQuerier& los, bool losRevealAll) const;
     119             : 
     120             : private:
     121           0 :     struct Projectile
     122             :     {
     123             :         CUnit* unit;
     124             :         CVector3D origin;
     125             :         CVector3D pos;
     126             :         CVector3D v;
     127             :         float time;
     128             :         float timeHit;
     129             :         float gravity;
     130             :         float impactAnimationLifetime;
     131             :         uint32_t id;
     132             :         std::wstring impactActorName;
     133             :         bool isImpactAnimationCreated;
     134             :         bool stopped;
     135             : 
     136           0 :         CVector3D position(float t)
     137             :         {
     138           0 :             float t2 = t;
     139           0 :             if (t2 > timeHit)
     140           0 :                 t2 = timeHit + logf(1.f + t2 - timeHit);
     141             : 
     142           0 :             CVector3D ret(origin);
     143           0 :             ret.X += v.X * t2;
     144           0 :             ret.Z += v.Z * t2;
     145           0 :             ret.Y += v.Y * t2 - 0.5f * gravity * t * t;
     146           0 :             return ret;
     147             :         }
     148             :     };
     149             : 
     150           0 :     struct ProjectileImpactAnimation
     151             :     {
     152             :         CUnit* unit;
     153             :         CVector3D pos;
     154             :         float time;
     155             :     };
     156             : 
     157             :     std::vector<Projectile> m_Projectiles;
     158             : 
     159             :     std::vector<ProjectileImpactAnimation> m_ProjectileImpactAnimations;
     160             : 
     161             :     uint32_t m_ActorSeed;
     162             : 
     163             :     uint32_t m_NextId;
     164             : 
     165             :     uint32_t LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity,
     166             :         const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime);
     167             : 
     168             :     void AdvanceProjectile(Projectile& projectile, float dt) const;
     169             : 
     170             :     void Interpolate(float frameTime);
     171             : 
     172             :     void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) const;
     173             : };
     174             : 
     175         345 : REGISTER_COMPONENT_TYPE(ProjectileManager)
     176             : 
     177           0 : uint32_t CCmpProjectileManager::LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime)
     178             : {
     179             :     // This is network synced so don't use GUI checks before incrementing or it breaks any non GUI simulations
     180           0 :     uint32_t currentId = m_NextId++;
     181             : 
     182           0 :     if (!GetSimContext().HasUnitManager() || actorName.empty())
     183             :         return currentId; // do nothing if graphics are disabled
     184             : 
     185           0 :     Projectile projectile;
     186           0 :     projectile.id = currentId;
     187           0 :     projectile.time = 0.f;
     188           0 :     projectile.stopped = false;
     189           0 :     projectile.gravity = gravity.ToFloat();
     190           0 :     projectile.isImpactAnimationCreated = false;
     191             : 
     192           0 :     if (!impactActorName.empty())
     193             :     {
     194           0 :         projectile.impactActorName = impactActorName;
     195           0 :         projectile.impactAnimationLifetime = impactAnimationLifetime.ToFloat();
     196             :     }
     197             :     else
     198             :     {
     199           0 :         projectile.impactActorName = L"";
     200           0 :         projectile.impactAnimationLifetime = 0.0f;
     201             :     }
     202             : 
     203           0 :     projectile.origin = launchPoint;
     204             : 
     205           0 :     projectile.unit = GetSimContext().GetUnitManager().CreateUnit(actorName, m_ActorSeed++);
     206           0 :     if (!projectile.unit) // The error will have already been logged
     207             :         return currentId;
     208             : 
     209           0 :     projectile.pos = projectile.origin;
     210           0 :     CVector3D offset(targetPoint);
     211           0 :     offset -= projectile.pos;
     212           0 :     float horizDistance = sqrtf(offset.X*offset.X + offset.Z*offset.Z);
     213           0 :     projectile.timeHit = horizDistance / speed.ToFloat();
     214           0 :     projectile.v = offset * (1.f / projectile.timeHit);
     215             : 
     216           0 :     projectile.v.Y = offset.Y / projectile.timeHit  + 0.5f * projectile.gravity * projectile.timeHit;
     217             : 
     218           0 :     m_Projectiles.push_back(projectile);
     219             : 
     220           0 :     return projectile.id;
     221             : }
     222             : 
     223           0 : void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt) const
     224             : {
     225           0 :     projectile.time += dt;
     226           0 :     if (projectile.stopped)
     227           0 :         return;
     228             : 
     229           0 :     CVector3D delta;
     230           0 :     if (dt < 0.1f)
     231           0 :         delta = projectile.pos;
     232             :     else // For big dt delta is unprecise
     233           0 :         delta = projectile.position(projectile.time - 0.1f);
     234             : 
     235           0 :     projectile.pos = projectile.position(projectile.time);
     236             : 
     237           0 :     delta = projectile.pos - delta;
     238             : 
     239             :     // If we've passed the target position and haven't stopped yet,
     240             :     // carry on until we reach solid land
     241           0 :     if (projectile.time >= projectile.timeHit)
     242             :     {
     243           0 :         CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     244           0 :         if (cmpTerrain)
     245             :         {
     246           0 :             float h = cmpTerrain->GetExactGroundLevel(projectile.pos.X, projectile.pos.Z);
     247           0 :             if (projectile.pos.Y < h)
     248             :             {
     249           0 :                 projectile.pos.Y = h; // stick precisely to the terrain
     250           0 :                 projectile.stopped = true;
     251             :             }
     252             :         }
     253             :     }
     254             : 
     255             :     // Construct a rotation matrix so that (0,1,0) is in the direction of 'delta'
     256             : 
     257           0 :     CVector3D up(0, 1, 0);
     258             : 
     259           0 :     delta.Normalize();
     260           0 :     CVector3D axis = up.Cross(delta);
     261           0 :     if (axis.LengthSquared() < 0.0001f)
     262           0 :         axis = CVector3D(1, 0, 0); // if up & delta are almost collinear, rotate around some other arbitrary axis
     263             :     else
     264           0 :         axis.Normalize();
     265             : 
     266           0 :     float angle = acosf(up.Dot(delta));
     267             : 
     268           0 :     CMatrix3D transform;
     269           0 :     CQuaternion quat;
     270           0 :     quat.FromAxisAngle(axis, angle);
     271           0 :     quat.ToMatrix(transform);
     272             : 
     273             :     // Then apply the translation
     274           0 :     transform.Translate(projectile.pos);
     275             : 
     276             :     // Move the model
     277           0 :     projectile.unit->GetModel().SetTransform(transform);
     278             : }
     279             : 
     280           0 : void CCmpProjectileManager::Interpolate(float frameTime)
     281             : {
     282           0 :     for (size_t i = 0; i < m_Projectiles.size(); ++i)
     283             :     {
     284           0 :         AdvanceProjectile(m_Projectiles[i], frameTime);
     285             :     }
     286             : 
     287             :     // Remove the ones that have reached their target
     288           0 :     for (size_t i = 0; i < m_Projectiles.size(); )
     289             :     {
     290           0 :         if (!m_Projectiles[i].stopped)
     291             :         {
     292           0 :             ++i;
     293           0 :             continue;
     294             :         }
     295             : 
     296           0 :         if (!m_Projectiles[i].impactActorName.empty() && !m_Projectiles[i].isImpactAnimationCreated)
     297             :         {
     298           0 :             m_Projectiles[i].isImpactAnimationCreated = true;
     299           0 :             CMatrix3D transform;
     300           0 :             CQuaternion quat;
     301           0 :             quat.ToMatrix(transform);
     302           0 :             transform.Translate(m_Projectiles[i].pos);
     303             : 
     304           0 :             CUnit* unit = GetSimContext().GetUnitManager().CreateUnit(m_Projectiles[i].impactActorName, m_ActorSeed++);
     305           0 :             unit->GetModel().SetTransform(transform);
     306             : 
     307           0 :             ProjectileImpactAnimation projectileImpactAnimation;
     308           0 :             projectileImpactAnimation.unit = unit;
     309           0 :             projectileImpactAnimation.time = m_Projectiles[i].impactAnimationLifetime;
     310           0 :             projectileImpactAnimation.pos = m_Projectiles[i].pos;
     311           0 :             m_ProjectileImpactAnimations.push_back(projectileImpactAnimation);
     312             :         }
     313             : 
     314             :         // Projectiles hitting targets get removed immediately.
     315             :         // Those hitting the ground stay for a while, because it looks pretty.
     316           0 :         if (m_Projectiles[i].time - m_Projectiles[i].timeHit > PROJECTILE_DECAY_TIME)
     317             :         {
     318             :             // Delete in-place by swapping with the last in the list
     319           0 :             std::swap(m_Projectiles[i], m_Projectiles.back());
     320           0 :             GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
     321           0 :             m_Projectiles.pop_back();
     322           0 :             continue;
     323             :         }
     324           0 :         ++i;
     325             :     }
     326             : 
     327           0 :     for (size_t i = 0; i < m_ProjectileImpactAnimations.size();)
     328             :     {
     329           0 :         if (m_ProjectileImpactAnimations[i].time > 0)
     330             :         {
     331           0 :             m_ProjectileImpactAnimations[i].time -= frameTime;
     332           0 :             ++i;
     333             :         }
     334             :         else
     335             :         {
     336           0 :             std::swap(m_ProjectileImpactAnimations[i], m_ProjectileImpactAnimations.back());
     337           0 :             GetSimContext().GetUnitManager().DeleteUnit(m_ProjectileImpactAnimations.back().unit);
     338           0 :             m_ProjectileImpactAnimations.pop_back();
     339             :         }
     340             :     }
     341           0 : }
     342             : 
     343           0 : void CCmpProjectileManager::RemoveProjectile(uint32_t id)
     344             : {
     345             :     // Scan through the projectile list looking for one with the correct id to remove
     346           0 :     for (size_t i = 0; i < m_Projectiles.size(); i++)
     347             :     {
     348           0 :         if (m_Projectiles[i].id == id)
     349             :         {
     350             :             // Delete in-place by swapping with the last in the list
     351           0 :             std::swap(m_Projectiles[i], m_Projectiles.back());
     352           0 :             GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
     353           0 :             m_Projectiles.pop_back();
     354           0 :             return;
     355             :         }
     356             :     }
     357             : }
     358             : 
     359           0 : void CCmpProjectileManager::RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector,
     360             :     const CFrustum& frustum, bool culling, const CLosQuerier& los, bool losRevealAll) const
     361             : {
     362             :     // Don't display objects outside the visible area
     363           0 :     ssize_t posi = (ssize_t)(0.5f + position.X / LOS_TILE_SIZE);
     364           0 :     ssize_t posj = (ssize_t)(0.5f + position.Z / LOS_TILE_SIZE);
     365           0 :     if (!losRevealAll && !los.IsVisible(posi, posj))
     366             :         return;
     367             : 
     368           0 :     model.ValidatePosition();
     369             : 
     370           0 :     if (culling && !frustum.IsBoxVisible(model.GetWorldBoundsRec()))
     371           0 :         return;
     372             : 
     373             :     // TODO: do something about LOS (copy from CCmpVisualActor)
     374             : 
     375           0 :     collector.SubmitRecursive(&model);
     376             : }
     377             : 
     378           0 : void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) const
     379             : {
     380           0 :     CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
     381           0 :     int player = GetSimContext().GetCurrentDisplayedPlayer();
     382           0 :     CLosQuerier los(cmpRangeManager->GetLosQuerier(player));
     383           0 :     bool losRevealAll = cmpRangeManager->GetLosRevealAll(player);
     384             : 
     385           0 :     for (const Projectile& projectile : m_Projectiles)
     386             :     {
     387           0 :         RenderModel(projectile.unit->GetModel(), projectile.pos, collector, frustum, culling, los, losRevealAll);
     388             :     }
     389             : 
     390           0 :     for (const ProjectileImpactAnimation& projectileImpactAnimation : m_ProjectileImpactAnimations)
     391             :     {
     392           0 :         RenderModel(projectileImpactAnimation.unit->GetModel(), projectileImpactAnimation.pos,
     393             :             collector, frustum, culling, los, losRevealAll);
     394             :     }
     395           0 : }

Generated by: LCOV version 1.13