LCOV - code coverage report
Current view: top level - source/graphics - ParticleEmitterType.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 272 0.4 %
Date: 2023-01-19 00:18:29 Functions: 2 45 4.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 "ParticleEmitterType.h"
      21             : 
      22             : #include "graphics/Color.h"
      23             : #include "graphics/ParticleEmitter.h"
      24             : #include "graphics/ParticleManager.h"
      25             : #include "graphics/TextureManager.h"
      26             : #include "maths/MathUtil.h"
      27             : #include "ps/CLogger.h"
      28             : #include "ps/Filesystem.h"
      29             : #include "ps/XML/Xeromyces.h"
      30             : #include "renderer/Renderer.h"
      31             : 
      32             : #include <boost/random/uniform_real_distribution.hpp>
      33             : 
      34             : 
      35             : /**
      36             :  * Interface for particle state variables, which get evaluated for each newly
      37             :  * constructed particle.
      38             :  */
      39             : class IParticleVar
      40             : {
      41             : public:
      42           0 :     IParticleVar() : m_LastValue(0) { }
      43           0 :     virtual ~IParticleVar() {}
      44             : 
      45             :     /// Computes and returns a new value.
      46           0 :     float Evaluate(CParticleEmitter& emitter)
      47             :     {
      48           0 :         m_LastValue = Compute(*emitter.m_Type, emitter);
      49           0 :         return m_LastValue;
      50             :     }
      51             : 
      52             :     /**
      53             :      * Returns the last value that Evaluate returned.
      54             :      * This is used for variables that depend on other variables (which is kind
      55             :      * of fragile since it's very order-dependent), so they don't get re-randomised
      56             :      * and don't have a danger of infinite recursion.
      57             :      */
      58           0 :     float LastValue() { return m_LastValue; }
      59             : 
      60             :     /**
      61             :      * Returns the minimum value that Evaluate might ever return,
      62             :      * for computing bounds.
      63             :      */
      64             :     virtual float Min(CParticleEmitterType& type) = 0;
      65             : 
      66             :     /**
      67             :      * Returns the maximum value that Evaluate might ever return,
      68             :      * for computing bounds.
      69             :      */
      70             :     virtual float Max(CParticleEmitterType& type) = 0;
      71             : 
      72             : protected:
      73             :     virtual float Compute(CParticleEmitterType& type, CParticleEmitter& emitter) = 0;
      74             : 
      75             : private:
      76             :     float m_LastValue;
      77             : };
      78             : 
      79             : /**
      80             :  * Particle variable that returns a constant value.
      81             :  */
      82           0 : class CParticleVarConstant : public IParticleVar
      83             : {
      84             : public:
      85           0 :     CParticleVarConstant(float val) :
      86           0 :         m_Value(val)
      87             :     {
      88           0 :     }
      89             : 
      90           0 :     virtual float Compute(CParticleEmitterType& UNUSED(type), CParticleEmitter& UNUSED(emitter))
      91             :     {
      92           0 :         return m_Value;
      93             :     }
      94             : 
      95           0 :     virtual float Min(CParticleEmitterType& UNUSED(type))
      96             :     {
      97           0 :         return m_Value;
      98             :     }
      99             : 
     100           0 :     virtual float Max(CParticleEmitterType& UNUSED(type))
     101             :     {
     102           0 :         return m_Value;
     103             :     }
     104             : 
     105             : private:
     106             :     float m_Value;
     107             : };
     108             : 
     109             : /**
     110             :  * Particle variable that returns a uniformly-distributed random value.
     111             :  */
     112           0 : class CParticleVarUniform : public IParticleVar
     113             : {
     114             : public:
     115           0 :     CParticleVarUniform(float min, float max) :
     116           0 :         m_Min(min), m_Max(max)
     117             :     {
     118           0 :     }
     119             : 
     120           0 :     virtual float Compute(CParticleEmitterType& type, CParticleEmitter& UNUSED(emitter))
     121             :     {
     122           0 :         return boost::random::uniform_real_distribution<float>(m_Min, m_Max)(type.m_Manager.m_RNG);
     123             :     }
     124             : 
     125           0 :     virtual float Min(CParticleEmitterType& UNUSED(type))
     126             :     {
     127           0 :         return m_Min;
     128             :     }
     129             : 
     130           0 :     virtual float Max(CParticleEmitterType& UNUSED(type))
     131             :     {
     132           0 :         return m_Max;
     133             :     }
     134             : 
     135             : private:
     136             :     float m_Min;
     137             :     float m_Max;
     138             : };
     139             : 
     140             : /**
     141             :  * Particle variable that returns the same value as some other variable
     142             :  * (assuming that variable was evaluated before this one).
     143             :  */
     144           0 : class CParticleVarCopy : public IParticleVar
     145             : {
     146             : public:
     147           0 :     CParticleVarCopy(int from) :
     148           0 :         m_From(from)
     149             :     {
     150           0 :     }
     151             : 
     152           0 :     virtual float Compute(CParticleEmitterType& type, CParticleEmitter& UNUSED(emitter))
     153             :     {
     154           0 :         return type.m_Variables[m_From]->LastValue();
     155             :     }
     156             : 
     157           0 :     virtual float Min(CParticleEmitterType& type)
     158             :     {
     159           0 :         return type.m_Variables[m_From]->Min(type);
     160             :     }
     161             : 
     162           0 :     virtual float Max(CParticleEmitterType& type)
     163             :     {
     164           0 :         return type.m_Variables[m_From]->Max(type);
     165             :     }
     166             : 
     167             : private:
     168             :     int m_From;
     169             : };
     170             : 
     171             : /**
     172             :  * A terrible ad-hoc attempt at handling some particular variable calculation,
     173             :  * which really needs to be cleaned up and generalised.
     174             :  */
     175           0 : class CParticleVarExpr : public IParticleVar
     176             : {
     177             : public:
     178           0 :     CParticleVarExpr(const CStr& from, float mul, float max) :
     179           0 :         m_From(from), m_Mul(mul), m_Max(max)
     180             :     {
     181           0 :     }
     182             : 
     183           0 :     virtual float Compute(CParticleEmitterType& UNUSED(type), CParticleEmitter& emitter)
     184             :     {
     185           0 :         return std::min(m_Max, emitter.m_EntityVariables[m_From] * m_Mul);
     186             :     }
     187             : 
     188           0 :     virtual float Min(CParticleEmitterType& UNUSED(type))
     189             :     {
     190           0 :         return 0.f;
     191             :     }
     192             : 
     193           0 :     virtual float Max(CParticleEmitterType& UNUSED(type))
     194             :     {
     195           0 :         return m_Max;
     196             :     }
     197             : 
     198             : private:
     199             :     CStr m_From;
     200             :     float m_Mul;
     201             :     float m_Max;
     202             : };
     203             : 
     204             : 
     205             : 
     206             : /**
     207             :  * Interface for particle effectors, which get evaluated every frame to
     208             :  * update particles.
     209             :  */
     210             : class IParticleEffector
     211             : {
     212             : public:
     213           0 :     IParticleEffector() { }
     214           0 :     virtual ~IParticleEffector() {}
     215             : 
     216             :     /// Updates all particles.
     217             :     virtual void Evaluate(std::vector<SParticle>& particles, float dt) = 0;
     218             : 
     219             :     /// Returns maximum acceleration caused by this effector.
     220             :     virtual CVector3D Max() = 0;
     221             : 
     222             : };
     223             : 
     224             : /**
     225             :  * Particle effector that applies a constant acceleration.
     226             :  */
     227           0 : class CParticleEffectorForce : public IParticleEffector
     228             : {
     229             : public:
     230           0 :     CParticleEffectorForce(float x, float y, float z) :
     231           0 :         m_Accel(x, y, z)
     232             :     {
     233           0 :     }
     234             : 
     235           0 :     virtual void Evaluate(std::vector<SParticle>& particles, float dt)
     236             :     {
     237           0 :         CVector3D dv = m_Accel * dt;
     238             : 
     239           0 :         for (size_t i = 0; i < particles.size(); ++i)
     240           0 :             particles[i].velocity += dv;
     241           0 :     }
     242             : 
     243           0 :     virtual CVector3D Max()
     244             :     {
     245           0 :         return m_Accel;
     246             :     }
     247             : 
     248             : private:
     249             :     CVector3D m_Accel;
     250             : };
     251             : 
     252             : 
     253             : 
     254             : 
     255           0 : CParticleEmitterType::CParticleEmitterType(const VfsPath& path, CParticleManager& manager) :
     256           0 :     m_Manager(manager)
     257             : {
     258           0 :     LoadXML(path);
     259             :     // TODO: handle load failure
     260             : 
     261             :     // Upper bound on number of particles depends on maximum rate and lifetime
     262           0 :     m_MaxLifetime = m_Variables[VAR_LIFETIME]->Max(*this);
     263           0 :     m_MaxParticles = ceil(m_Variables[VAR_EMISSIONRATE]->Max(*this) * m_MaxLifetime);
     264             : 
     265             : 
     266             :     // Compute the worst-case bounds of all possible particles,
     267             :     // based on the min/max values of positions and velocities and accelerations
     268             :     // and sizes. (This isn't a guaranteed bound but it should be sufficient for
     269             :     // culling.)
     270             : 
     271             :     // Assuming constant acceleration,
     272             :     //        p = p0 + v0*t + 1/2 a*t^2
     273             :     // => dp/dt = v0 + a*t
     274             :     //          = 0 at t = -v0/a
     275             :     // max(p) is at t=0, or t=tmax, or t=-v0/a if that's between 0 and tmax
     276             :     // => max(p) = max(p0, p0 + v0*tmax + 1/2 a*tmax, p0 - 1/2 v0^2/a)
     277             : 
     278             :     // Compute combined acceleration (assume constant)
     279           0 :     CVector3D accel;
     280           0 :     for (size_t i = 0; i < m_Effectors.size(); ++i)
     281           0 :         accel += m_Effectors[i]->Max();
     282             : 
     283           0 :     CVector3D vmin(m_Variables[VAR_VELOCITY_X]->Min(*this), m_Variables[VAR_VELOCITY_Y]->Min(*this), m_Variables[VAR_VELOCITY_Z]->Min(*this));
     284           0 :     CVector3D vmax(m_Variables[VAR_VELOCITY_X]->Max(*this), m_Variables[VAR_VELOCITY_Y]->Max(*this), m_Variables[VAR_VELOCITY_Z]->Max(*this));
     285             : 
     286             :     // Start by assuming p0 = 0; compute each XYZ component individually
     287           0 :     m_MaxBounds.SetEmpty();
     288             :     // Lower/upper bounds at t=0, t=tmax
     289           0 :     m_MaxBounds[0].X = std::min(0.f, vmin.X*m_MaxLifetime + 0.5f*accel.X*m_MaxLifetime*m_MaxLifetime);
     290           0 :     m_MaxBounds[0].Y = std::min(0.f, vmin.Y*m_MaxLifetime + 0.5f*accel.Y*m_MaxLifetime*m_MaxLifetime);
     291           0 :     m_MaxBounds[0].Z = std::min(0.f, vmin.Z*m_MaxLifetime + 0.5f*accel.Z*m_MaxLifetime*m_MaxLifetime);
     292           0 :     m_MaxBounds[1].X = std::max(0.f, vmax.X*m_MaxLifetime + 0.5f*accel.X*m_MaxLifetime*m_MaxLifetime);
     293           0 :     m_MaxBounds[1].Y = std::max(0.f, vmax.Y*m_MaxLifetime + 0.5f*accel.Y*m_MaxLifetime*m_MaxLifetime);
     294           0 :     m_MaxBounds[1].Z = std::max(0.f, vmax.Z*m_MaxLifetime + 0.5f*accel.Z*m_MaxLifetime*m_MaxLifetime);
     295             :     // Extend bounds to include position at t where dp/dt=0, if 0 < t < tmax
     296           0 :     if (accel.X && 0 < -vmin.X/accel.X && -vmin.X/accel.X < m_MaxLifetime)
     297           0 :         m_MaxBounds[0].X = std::min(m_MaxBounds[0].X, -0.5f*vmin.X*vmin.X / accel.X);
     298           0 :     if (accel.Y && 0 < -vmin.Y/accel.Y && -vmin.Y/accel.Y < m_MaxLifetime)
     299           0 :         m_MaxBounds[0].Y = std::min(m_MaxBounds[0].Y, -0.5f*vmin.Y*vmin.Y / accel.Y);
     300           0 :     if (accel.Z && 0 < -vmin.Z/accel.Z && -vmin.Z/accel.Z < m_MaxLifetime)
     301           0 :         m_MaxBounds[0].Z = std::min(m_MaxBounds[0].Z, -0.5f*vmin.Z*vmin.Z / accel.Z);
     302           0 :     if (accel.X && 0 < -vmax.X/accel.X && -vmax.X/accel.X < m_MaxLifetime)
     303           0 :         m_MaxBounds[1].X = std::max(m_MaxBounds[1].X, -0.5f*vmax.X*vmax.X / accel.X);
     304           0 :     if (accel.Y && 0 < -vmax.Y/accel.Y && -vmax.Y/accel.Y < m_MaxLifetime)
     305           0 :         m_MaxBounds[1].Y = std::max(m_MaxBounds[1].Y, -0.5f*vmax.Y*vmax.Y / accel.Y);
     306           0 :     if (accel.Z && 0 < -vmax.Z/accel.Z && -vmax.Z/accel.Z < m_MaxLifetime)
     307           0 :         m_MaxBounds[1].Z = std::max(m_MaxBounds[1].Z, -0.5f*vmax.Z*vmax.Z / accel.Z);
     308             : 
     309             :     // Offset by the initial positions
     310           0 :     m_MaxBounds[0] += CVector3D(m_Variables[VAR_POSITION_X]->Min(*this), m_Variables[VAR_POSITION_Y]->Min(*this), m_Variables[VAR_POSITION_Z]->Min(*this));
     311           0 :     m_MaxBounds[1] += CVector3D(m_Variables[VAR_POSITION_X]->Max(*this), m_Variables[VAR_POSITION_Y]->Max(*this), m_Variables[VAR_POSITION_Z]->Max(*this));
     312           0 : }
     313             : 
     314           0 : int CParticleEmitterType::GetVariableID(const std::string& name)
     315             : {
     316           0 :     if (name == "emissionrate")       return VAR_EMISSIONRATE;
     317           0 :     if (name == "lifetime")           return VAR_LIFETIME;
     318           0 :     if (name == "position.x")     return VAR_POSITION_X;
     319           0 :     if (name == "position.y")     return VAR_POSITION_Y;
     320           0 :     if (name == "position.z")     return VAR_POSITION_Z;
     321           0 :     if (name == "angle")          return VAR_ANGLE;
     322           0 :     if (name == "velocity.x")     return VAR_VELOCITY_X;
     323           0 :     if (name == "velocity.y")     return VAR_VELOCITY_Y;
     324           0 :     if (name == "velocity.z")     return VAR_VELOCITY_Z;
     325           0 :     if (name == "velocity.angle") return VAR_VELOCITY_ANGLE;
     326           0 :     if (name == "size")               return VAR_SIZE;
     327           0 :     if (name == "size.growthRate")    return VAR_SIZE_GROWTHRATE;
     328           0 :     if (name == "color.r")            return VAR_COLOR_R;
     329           0 :     if (name == "color.g")            return VAR_COLOR_G;
     330           0 :     if (name == "color.b")            return VAR_COLOR_B;
     331           0 :     LOGWARNING("Particle sets unknown variable '%s'", name.c_str());
     332           0 :     return -1;
     333             : }
     334             : 
     335           0 : bool CParticleEmitterType::LoadXML(const VfsPath& path)
     336             : {
     337             :     // Initialise with sane defaults
     338           0 :     m_Variables.clear();
     339           0 :     m_Variables.resize(VAR__MAX);
     340           0 :     m_Variables[VAR_EMISSIONRATE] = IParticleVarPtr(new CParticleVarConstant(10.f));
     341           0 :     m_Variables[VAR_LIFETIME] = IParticleVarPtr(new CParticleVarConstant(3.f));
     342           0 :     m_Variables[VAR_POSITION_X] = IParticleVarPtr(new CParticleVarConstant(0.f));
     343           0 :     m_Variables[VAR_POSITION_Y] = IParticleVarPtr(new CParticleVarConstant(0.f));
     344           0 :     m_Variables[VAR_POSITION_Z] = IParticleVarPtr(new CParticleVarConstant(0.f));
     345           0 :     m_Variables[VAR_ANGLE] = IParticleVarPtr(new CParticleVarConstant(0.f));
     346           0 :     m_Variables[VAR_VELOCITY_X] = IParticleVarPtr(new CParticleVarConstant(0.f));
     347           0 :     m_Variables[VAR_VELOCITY_Y] = IParticleVarPtr(new CParticleVarConstant(1.f));
     348           0 :     m_Variables[VAR_VELOCITY_Z] = IParticleVarPtr(new CParticleVarConstant(0.f));
     349           0 :     m_Variables[VAR_VELOCITY_ANGLE] = IParticleVarPtr(new CParticleVarConstant(0.f));
     350           0 :     m_Variables[VAR_SIZE] = IParticleVarPtr(new CParticleVarConstant(1.f));
     351           0 :     m_Variables[VAR_SIZE_GROWTHRATE] = IParticleVarPtr(new CParticleVarConstant(0.f));
     352           0 :     m_Variables[VAR_COLOR_R] = IParticleVarPtr(new CParticleVarConstant(1.f));
     353           0 :     m_Variables[VAR_COLOR_G] = IParticleVarPtr(new CParticleVarConstant(1.f));
     354           0 :     m_Variables[VAR_COLOR_B] = IParticleVarPtr(new CParticleVarConstant(1.f));
     355           0 :     m_BlendMode = BlendMode::ADD;
     356           0 :     m_StartFull = false;
     357           0 :     m_UseRelativeVelocity = false;
     358           0 :     m_Texture = g_Renderer.GetTextureManager().GetErrorTexture();
     359             : 
     360           0 :     CXeromyces XeroFile;
     361           0 :     PSRETURN ret = XeroFile.Load(g_VFS, path, "particle");
     362           0 :     if (ret != PSRETURN_OK)
     363           0 :         return false;
     364             : 
     365             :     // Define all the elements and attributes used in the XML file
     366             : #define EL(x) int el_##x = XeroFile.GetElementID(#x)
     367             : #define AT(x) int at_##x = XeroFile.GetAttributeID(#x)
     368           0 :     EL(texture);
     369           0 :     EL(blend);
     370           0 :     EL(start_full);
     371           0 :     EL(use_relative_velocity);
     372           0 :     EL(constant);
     373           0 :     EL(uniform);
     374           0 :     EL(copy);
     375           0 :     EL(expr);
     376           0 :     EL(force);
     377           0 :     AT(mode);
     378           0 :     AT(name);
     379           0 :     AT(value);
     380           0 :     AT(min);
     381           0 :     AT(max);
     382           0 :     AT(mul);
     383           0 :     AT(from);
     384           0 :     AT(x);
     385           0 :     AT(y);
     386           0 :     AT(z);
     387             : #undef AT
     388             : #undef EL
     389             : 
     390           0 :     XMBElement Root = XeroFile.GetRoot();
     391             : 
     392           0 :     XERO_ITER_EL(Root, Child)
     393             :     {
     394           0 :         if (Child.GetNodeName() == el_texture)
     395             :         {
     396           0 :             CTextureProperties textureProps(Child.GetText().FromUTF8());
     397           0 :             textureProps.SetAddressMode(
     398             :                 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     399           0 :             m_Texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
     400             :         }
     401           0 :         else if (Child.GetNodeName() == el_blend)
     402             :         {
     403           0 :             const CStr mode = Child.GetAttributes().GetNamedItem(at_mode);
     404           0 :             if (mode == "add")
     405           0 :                 m_BlendMode = BlendMode::ADD;
     406           0 :             else if (mode == "subtract")
     407           0 :                 m_BlendMode = BlendMode::SUBTRACT;
     408           0 :             else if (mode == "over")
     409           0 :                 m_BlendMode = BlendMode::OVERLAY;
     410           0 :             else if (mode == "multiply")
     411           0 :                 m_BlendMode = BlendMode::MULTIPLY;
     412             :         }
     413           0 :         else if (Child.GetNodeName() == el_start_full)
     414             :         {
     415           0 :             m_StartFull = true;
     416             :         }
     417           0 :         else if (Child.GetNodeName() == el_use_relative_velocity)
     418             :         {
     419           0 :             m_UseRelativeVelocity = true;
     420             :         }
     421           0 :         else if (Child.GetNodeName() == el_constant)
     422             :         {
     423           0 :             int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
     424           0 :             if (id != -1)
     425             :             {
     426           0 :                 m_Variables[id] = IParticleVarPtr(new CParticleVarConstant(
     427           0 :                     Child.GetAttributes().GetNamedItem(at_value).ToFloat()
     428           0 :                 ));
     429             :             }
     430             :         }
     431           0 :         else if (Child.GetNodeName() == el_uniform)
     432             :         {
     433           0 :             int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
     434           0 :             if (id != -1)
     435             :             {
     436           0 :                 float min = Child.GetAttributes().GetNamedItem(at_min).ToFloat();
     437           0 :                 float max = Child.GetAttributes().GetNamedItem(at_max).ToFloat();
     438             :                 // To avoid hangs in the RNG, only use it if [min, max) is non-empty
     439           0 :                 if (min < max)
     440           0 :                     m_Variables[id] = IParticleVarPtr(new CParticleVarUniform(min, max));
     441             :                 else
     442           0 :                     m_Variables[id] = IParticleVarPtr(new CParticleVarConstant(min));
     443             :             }
     444             :         }
     445           0 :         else if (Child.GetNodeName() == el_copy)
     446             :         {
     447           0 :             int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
     448           0 :             int from = GetVariableID(Child.GetAttributes().GetNamedItem(at_from));
     449           0 :             if (id != -1 && from != -1)
     450           0 :                 m_Variables[id] = IParticleVarPtr(new CParticleVarCopy(from));
     451             :         }
     452           0 :         else if (Child.GetNodeName() == el_expr)
     453             :         {
     454           0 :             int id = GetVariableID(Child.GetAttributes().GetNamedItem(at_name));
     455           0 :             CStr from = Child.GetAttributes().GetNamedItem(at_from);
     456           0 :             float mul = Child.GetAttributes().GetNamedItem(at_mul).ToFloat();
     457           0 :             float max = Child.GetAttributes().GetNamedItem(at_max).ToFloat();
     458           0 :             if (id != -1)
     459           0 :                 m_Variables[id] = IParticleVarPtr(new CParticleVarExpr(from, mul, max));
     460             :         }
     461           0 :         else if (Child.GetNodeName() == el_force)
     462             :         {
     463           0 :             float x = Child.GetAttributes().GetNamedItem(at_x).ToFloat();
     464           0 :             float y = Child.GetAttributes().GetNamedItem(at_y).ToFloat();
     465           0 :             float z = Child.GetAttributes().GetNamedItem(at_z).ToFloat();
     466           0 :             m_Effectors.push_back(IParticleEffectorPtr(new CParticleEffectorForce(x, y, z)));
     467             :         }
     468             :     }
     469             : 
     470           0 :     return true;
     471             : }
     472             : 
     473           0 : void CParticleEmitterType::UpdateEmitter(CParticleEmitter& emitter, float dt)
     474             : {
     475             :     // If dt is very large, we should do the update in multiple small
     476             :     // steps to prevent all the particles getting clumped together at
     477             :     // low framerates
     478             : 
     479           0 :     const float maxStepLength = 0.2f;
     480             : 
     481             :     // Avoid wasting time by computing periods longer than the lifetime
     482             :     // period of the particles
     483           0 :     dt = std::min(dt, m_MaxLifetime);
     484             : 
     485           0 :     while (dt > maxStepLength)
     486             :     {
     487           0 :         UpdateEmitterStep(emitter, maxStepLength);
     488           0 :         dt -= maxStepLength;
     489             :     }
     490             : 
     491           0 :     UpdateEmitterStep(emitter, dt);
     492           0 : }
     493             : 
     494           0 : void CParticleEmitterType::UpdateEmitterStep(CParticleEmitter& emitter, float dt)
     495             : {
     496           0 :     ENSURE(emitter.m_Type.get() == this);
     497             : 
     498           0 :     if (emitter.m_Active)
     499             :     {
     500           0 :         float emissionRate = m_Variables[VAR_EMISSIONRATE]->Evaluate(emitter);
     501             : 
     502             :         // Find how many new particles to spawn, and accumulate any rounding errors
     503             :         // (to maintain a constant emission rate even if dt is very small)
     504           0 :         int newParticles = floor(emitter.m_EmissionRoundingError + dt*emissionRate);
     505           0 :         emitter.m_EmissionRoundingError += dt*emissionRate - newParticles;
     506             : 
     507             :         // If dt was very large, there's no point spawning new particles that
     508             :         // we'll immediately overwrite, so clamp it
     509           0 :         newParticles = std::min(newParticles, (int)m_MaxParticles);
     510             : 
     511           0 :         for (int i = 0; i < newParticles; ++i)
     512             :         {
     513             :             // Compute new particle state based on variables
     514           0 :             SParticle particle;
     515             : 
     516           0 :             particle.pos.X = m_Variables[VAR_POSITION_X]->Evaluate(emitter);
     517           0 :             particle.pos.Y = m_Variables[VAR_POSITION_Y]->Evaluate(emitter);
     518           0 :             particle.pos.Z = m_Variables[VAR_POSITION_Z]->Evaluate(emitter);
     519           0 :             particle.pos += emitter.m_Pos;
     520             : 
     521           0 :             if (m_UseRelativeVelocity)
     522             :             {
     523           0 :                 float xVel = m_Variables[VAR_VELOCITY_X]->Evaluate(emitter);
     524           0 :                 float yVel = m_Variables[VAR_VELOCITY_Y]->Evaluate(emitter);
     525           0 :                 float zVel = m_Variables[VAR_VELOCITY_Z]->Evaluate(emitter);
     526           0 :                 CVector3D EmitterAngle = emitter.GetRotation().ToMatrix().Transform(CVector3D(xVel,yVel,zVel));
     527           0 :                 particle.velocity.X = EmitterAngle.X;
     528           0 :                 particle.velocity.Y = EmitterAngle.Y;
     529           0 :                 particle.velocity.Z = EmitterAngle.Z;
     530             :             } else {
     531           0 :                 particle.velocity.X = m_Variables[VAR_VELOCITY_X]->Evaluate(emitter);
     532           0 :                 particle.velocity.Y = m_Variables[VAR_VELOCITY_Y]->Evaluate(emitter);
     533           0 :                 particle.velocity.Z = m_Variables[VAR_VELOCITY_Z]->Evaluate(emitter);
     534             :             }
     535           0 :             particle.angle = m_Variables[VAR_ANGLE]->Evaluate(emitter);
     536           0 :             particle.angleSpeed = m_Variables[VAR_VELOCITY_ANGLE]->Evaluate(emitter);
     537             : 
     538           0 :             particle.size = m_Variables[VAR_SIZE]->Evaluate(emitter);
     539           0 :             particle.sizeGrowthRate = m_Variables[VAR_SIZE_GROWTHRATE]->Evaluate(emitter);
     540             : 
     541           0 :             RGBColor color;
     542           0 :             color.X = m_Variables[VAR_COLOR_R]->Evaluate(emitter);
     543           0 :             color.Y = m_Variables[VAR_COLOR_G]->Evaluate(emitter);
     544           0 :             color.Z = m_Variables[VAR_COLOR_B]->Evaluate(emitter);
     545           0 :             particle.color = ConvertRGBColorTo4ub(color);
     546             : 
     547           0 :             particle.age = 0.f;
     548           0 :             particle.maxAge = m_Variables[VAR_LIFETIME]->Evaluate(emitter);
     549             : 
     550           0 :             emitter.AddParticle(particle);
     551             :         }
     552             :     }
     553             : 
     554             :     // Update particle states
     555           0 :     for (size_t i = 0; i < emitter.m_Particles.size(); ++i)
     556             :     {
     557           0 :         SParticle& p = emitter.m_Particles[i];
     558             : 
     559             :         // Don't bother updating particles already at the end of their life
     560           0 :         if (p.age > p.maxAge)
     561           0 :             continue;
     562             : 
     563           0 :         p.pos += p.velocity * dt;
     564           0 :         p.angle += p.angleSpeed * dt;
     565           0 :         p.age += dt;
     566           0 :         p.size += p.sizeGrowthRate * dt;
     567             : 
     568             :         // Make alpha fade in/out nicely
     569             :         // TODO: this should probably be done as a variable or something,
     570             :         // instead of hardcoding
     571           0 :         float ageFrac = p.age / p.maxAge;
     572           0 :         float a = std::min(1.f - ageFrac, 5.f * ageFrac);
     573           0 :         p.color.A = Clamp(static_cast<int>(a * 255.f), 0, 255);
     574             :     }
     575             : 
     576           0 :     for (size_t i = 0; i < m_Effectors.size(); ++i)
     577             :     {
     578           0 :         m_Effectors[i]->Evaluate(emitter.m_Particles, dt);
     579             :     }
     580           0 : }
     581             : 
     582           0 : CBoundingBoxAligned CParticleEmitterType::CalculateBounds(CVector3D emitterPos, CBoundingBoxAligned emittedBounds)
     583             : {
     584           0 :     CBoundingBoxAligned bounds = m_MaxBounds;
     585           0 :     bounds[0] += emitterPos;
     586           0 :     bounds[1] += emitterPos;
     587             : 
     588           0 :     bounds += emittedBounds;
     589             : 
     590             :     // The current bounds is for the particles' centers, so expand by
     591             :     // sqrt(2) * max_size/2 to ensure any rotated billboards fit in
     592           0 :     bounds.Expand(m_Variables[VAR_SIZE]->Max(*this)/2.f * sqrt(2.f));
     593             : 
     594           0 :     return bounds;
     595           3 : }

Generated by: LCOV version 1.13