LCOV - code coverage report
Current view: top level - source/graphics - ParticleEmitter.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 169 0.6 %
Date: 2023-01-19 00:18:29 Functions: 2 20 10.0 %

          Line data    Source code
       1             : /* Copyright (C) 2023 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 "ParticleEmitter.h"
      21             : 
      22             : #include "graphics/LightEnv.h"
      23             : #include "graphics/LOSTexture.h"
      24             : #include "graphics/ParticleEmitterType.h"
      25             : #include "graphics/ParticleManager.h"
      26             : #include "graphics/ShaderProgram.h"
      27             : #include "graphics/TextureManager.h"
      28             : #include "ps/CStrInternStatic.h"
      29             : #include "renderer/Renderer.h"
      30             : #include "renderer/SceneRenderer.h"
      31             : 
      32           0 : CParticleEmitter::CParticleEmitter(const CParticleEmitterTypePtr& type) :
      33             :     m_Type(type), m_Active(true), m_NextParticleIdx(0), m_EmissionRoundingError(0.f),
      34           0 :     m_LastUpdateTime(type->m_Manager.GetCurrentTime()),
      35             :     m_IndexArray(false),
      36             :     m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX, true),
      37           0 :     m_LastFrameNumber(-1)
      38             : {
      39             :     // If we should start with particles fully emitted, pretend that we
      40             :     // were created in the past so the first update will produce lots of
      41             :     // particles.
      42             :     // TODO: instead of this, maybe it would make more sense to do a full
      43             :     // lifetime-length update of all emitters when the game first starts
      44             :     // (so that e.g. buildings constructed later on won't have fully-started
      45             :     // emitters, but those at the start will)?
      46           0 :     if (m_Type->m_StartFull)
      47           0 :         m_LastUpdateTime -= m_Type->m_MaxLifetime;
      48             : 
      49           0 :     m_Particles.reserve(m_Type->m_MaxParticles);
      50             : 
      51           0 :     m_AttributePos.format = Renderer::Backend::Format::R32G32B32_SFLOAT;
      52           0 :     m_VertexArray.AddAttribute(&m_AttributePos);
      53             : 
      54           0 :     m_AttributeAxis.format = Renderer::Backend::Format::R32G32_SFLOAT;
      55           0 :     m_VertexArray.AddAttribute(&m_AttributeAxis);
      56             : 
      57           0 :     m_AttributeUV.format = Renderer::Backend::Format::R32G32_SFLOAT;
      58           0 :     m_VertexArray.AddAttribute(&m_AttributeUV);
      59             : 
      60           0 :     m_AttributeColor.format = Renderer::Backend::Format::R8G8B8A8_UNORM;
      61           0 :     m_VertexArray.AddAttribute(&m_AttributeColor);
      62             : 
      63           0 :     m_VertexArray.SetNumberOfVertices(m_Type->m_MaxParticles * 4);
      64           0 :     m_VertexArray.Layout();
      65             : 
      66           0 :     m_IndexArray.SetNumberOfVertices(m_Type->m_MaxParticles * 6);
      67           0 :     m_IndexArray.Layout();
      68           0 :     VertexArrayIterator<u16> index = m_IndexArray.GetIterator();
      69           0 :     for (u16 i = 0; i < m_Type->m_MaxParticles; ++i)
      70             :     {
      71           0 :         *index++ = i*4 + 0;
      72           0 :         *index++ = i*4 + 1;
      73           0 :         *index++ = i*4 + 2;
      74           0 :         *index++ = i*4 + 2;
      75           0 :         *index++ = i*4 + 3;
      76           0 :         *index++ = i*4 + 0;
      77             :     }
      78           0 :     m_IndexArray.Upload();
      79           0 :     m_IndexArray.FreeBackingStore();
      80             : 
      81           0 :     const uint32_t stride = m_VertexArray.GetStride();
      82           0 :     const std::array<Renderer::Backend::SVertexAttributeFormat, 4> attributes{{
      83             :         {Renderer::Backend::VertexAttributeStream::POSITION,
      84           0 :             m_AttributePos.format, m_AttributePos.offset, stride,
      85             :             Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
      86             :         {Renderer::Backend::VertexAttributeStream::COLOR,
      87           0 :             m_AttributeColor.format, m_AttributeColor.offset, stride,
      88             :             Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
      89             :         {Renderer::Backend::VertexAttributeStream::UV0,
      90           0 :             m_AttributeUV.format, m_AttributeUV.offset, stride,
      91             :             Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
      92             :         {Renderer::Backend::VertexAttributeStream::UV1,
      93           0 :             m_AttributeAxis.format, m_AttributeAxis.offset, stride,
      94             :             Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
      95           0 :     }};
      96           0 :     m_VertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
      97           0 : }
      98             : 
      99           0 : void CParticleEmitter::UpdateArrayData(int frameNumber)
     100             : {
     101           0 :     if (m_LastFrameNumber == frameNumber)
     102           0 :         return;
     103             : 
     104           0 :     m_LastFrameNumber = frameNumber;
     105             : 
     106             :     // Update m_Particles
     107           0 :     m_Type->UpdateEmitter(*this, m_Type->m_Manager.GetCurrentTime() - m_LastUpdateTime);
     108           0 :     m_LastUpdateTime = m_Type->m_Manager.GetCurrentTime();
     109             : 
     110             :     // Regenerate the vertex array data:
     111             : 
     112           0 :     VertexArrayIterator<CVector3D> attrPos = m_AttributePos.GetIterator<CVector3D>();
     113           0 :     VertexArrayIterator<float[2]> attrAxis = m_AttributeAxis.GetIterator<float[2]>();
     114           0 :     VertexArrayIterator<float[2]> attrUV = m_AttributeUV.GetIterator<float[2]>();
     115           0 :     VertexArrayIterator<SColor4ub> attrColor = m_AttributeColor.GetIterator<SColor4ub>();
     116             : 
     117           0 :     ENSURE(m_Particles.size() <= m_Type->m_MaxParticles);
     118             : 
     119           0 :     CBoundingBoxAligned bounds;
     120             : 
     121           0 :     for (size_t i = 0; i < m_Particles.size(); ++i)
     122             :     {
     123             :         // TODO: for more efficient rendering, maybe we should replace this with
     124             :         // a degenerate quad if alpha is 0
     125             : 
     126           0 :         bounds += m_Particles[i].pos;
     127             : 
     128           0 :         *attrPos++ = m_Particles[i].pos;
     129           0 :         *attrPos++ = m_Particles[i].pos;
     130           0 :         *attrPos++ = m_Particles[i].pos;
     131           0 :         *attrPos++ = m_Particles[i].pos;
     132             : 
     133             :         // Compute corner offsets, split into sin/cos components so the vertex
     134             :         // shader can multiply by the camera-right (or left?) and camera-up vectors
     135             :         // to get rotating billboards:
     136             : 
     137           0 :         float s = sin(m_Particles[i].angle) * m_Particles[i].size/2.f;
     138           0 :         float c = cos(m_Particles[i].angle) * m_Particles[i].size/2.f;
     139             : 
     140           0 :         (*attrAxis)[0] = c;
     141           0 :         (*attrAxis)[1] = s;
     142           0 :         ++attrAxis;
     143           0 :         (*attrAxis)[0] = s;
     144           0 :         (*attrAxis)[1] = -c;
     145           0 :         ++attrAxis;
     146           0 :         (*attrAxis)[0] = -c;
     147           0 :         (*attrAxis)[1] = -s;
     148           0 :         ++attrAxis;
     149           0 :         (*attrAxis)[0] = -s;
     150           0 :         (*attrAxis)[1] = c;
     151           0 :         ++attrAxis;
     152             : 
     153           0 :         (*attrUV)[0] = 1;
     154           0 :         (*attrUV)[1] = 0;
     155           0 :         ++attrUV;
     156           0 :         (*attrUV)[0] = 0;
     157           0 :         (*attrUV)[1] = 0;
     158           0 :         ++attrUV;
     159           0 :         (*attrUV)[0] = 0;
     160           0 :         (*attrUV)[1] = 1;
     161           0 :         ++attrUV;
     162           0 :         (*attrUV)[0] = 1;
     163           0 :         (*attrUV)[1] = 1;
     164           0 :         ++attrUV;
     165             : 
     166           0 :         SColor4ub color = m_Particles[i].color;
     167             : 
     168             :         // Special case: If the blending depends on the source color, not the source alpha,
     169             :         // then pre-multiply by the alpha. (This is kind of a hack.)
     170           0 :         if (m_Type->m_BlendMode == CParticleEmitterType::BlendMode::OVERLAY ||
     171           0 :             m_Type->m_BlendMode == CParticleEmitterType::BlendMode::MULTIPLY)
     172             :         {
     173           0 :             color.R = (color.R * color.A) / 255;
     174           0 :             color.G = (color.G * color.A) / 255;
     175           0 :             color.B = (color.B * color.A) / 255;
     176             :         }
     177             : 
     178           0 :         *attrColor++ = color;
     179           0 :         *attrColor++ = color;
     180           0 :         *attrColor++ = color;
     181           0 :         *attrColor++ = color;
     182             :     }
     183             : 
     184           0 :     m_ParticleBounds = bounds;
     185             : 
     186           0 :     m_VertexArray.Upload();
     187             : }
     188             : 
     189           0 : void CParticleEmitter::PrepareForRendering()
     190             : {
     191           0 :     m_VertexArray.PrepareForRendering();
     192           0 : }
     193             : 
     194           0 : void CParticleEmitter::UploadData(
     195             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
     196             : {
     197           0 :     m_VertexArray.UploadIfNeeded(deviceCommandContext);
     198           0 : }
     199             : 
     200           0 : void CParticleEmitter::Bind(
     201             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
     202             :     Renderer::Backend::IShaderProgram* shader)
     203             : {
     204           0 :     m_Type->m_Texture->UploadBackendTextureIfNeeded(deviceCommandContext);
     205             : 
     206           0 :     CLOSTexture& los = g_Renderer.GetSceneRenderer().GetScene().GetLOSTexture();
     207           0 :     deviceCommandContext->SetTexture(
     208           0 :         shader->GetBindingSlot(str_losTex), los.GetTextureSmooth());
     209           0 :     deviceCommandContext->SetUniform(
     210           0 :         shader->GetBindingSlot(str_losTransform),
     211           0 :         los.GetTextureMatrix()[0], los.GetTextureMatrix()[12]);
     212             : 
     213           0 :     const CLightEnv& lightEnv = g_Renderer.GetSceneRenderer().GetLightEnv();
     214             : 
     215           0 :     deviceCommandContext->SetUniform(
     216           0 :         shader->GetBindingSlot(str_sunColor), lightEnv.m_SunColor.AsFloatArray());
     217           0 :     deviceCommandContext->SetUniform(
     218           0 :         shader->GetBindingSlot(str_fogColor), lightEnv.m_FogColor.AsFloatArray());
     219           0 :     deviceCommandContext->SetUniform(
     220           0 :         shader->GetBindingSlot(str_fogParams), lightEnv.m_FogFactor, lightEnv.m_FogMax);
     221             : 
     222           0 :     deviceCommandContext->SetTexture(
     223           0 :         shader->GetBindingSlot(str_baseTex), m_Type->m_Texture->GetBackendTexture());
     224           0 : }
     225             : 
     226           0 : void CParticleEmitter::RenderArray(
     227             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
     228             : {
     229           0 :     if (m_Particles.empty())
     230           0 :         return;
     231             : 
     232           0 :     const uint32_t stride = m_VertexArray.GetStride();
     233           0 :     const uint32_t firstVertexOffset = m_VertexArray.GetOffset() * stride;
     234             : 
     235           0 :     deviceCommandContext->SetVertexInputLayout(m_VertexInputLayout);
     236             : 
     237           0 :     deviceCommandContext->SetVertexBuffer(
     238           0 :         0, m_VertexArray.GetBuffer(), firstVertexOffset);
     239           0 :     deviceCommandContext->SetIndexBuffer(m_IndexArray.GetBuffer());
     240             : 
     241           0 :     deviceCommandContext->DrawIndexed(m_IndexArray.GetOffset(), m_Particles.size() * 6, 0);
     242             : 
     243           0 :     g_Renderer.GetStats().m_DrawCalls++;
     244           0 :     g_Renderer.GetStats().m_Particles += m_Particles.size();
     245             : }
     246             : 
     247           0 : void CParticleEmitter::Unattach(const CParticleEmitterPtr& self)
     248             : {
     249           0 :     m_Active = false;
     250           0 :     m_Type->m_Manager.AddUnattachedEmitter(self);
     251           0 : }
     252             : 
     253           0 : void CParticleEmitter::AddParticle(const SParticle& particle)
     254             : {
     255           0 :     if (m_NextParticleIdx >= m_Particles.size())
     256           0 :         m_Particles.push_back(particle);
     257             :     else
     258           0 :         m_Particles[m_NextParticleIdx] = particle;
     259             : 
     260           0 :     m_NextParticleIdx = (m_NextParticleIdx + 1) % m_Type->m_MaxParticles;
     261           0 : }
     262             : 
     263           0 : void CParticleEmitter::SetEntityVariable(const std::string& name, float value)
     264             : {
     265           0 :     m_EntityVariables[name] = value;
     266           0 : }
     267             : 
     268           0 : CModelParticleEmitter::CModelParticleEmitter(const CParticleEmitterTypePtr& type) :
     269           0 :     m_Type(type)
     270             : {
     271           0 :     m_Emitter = CParticleEmitterPtr(new CParticleEmitter(m_Type));
     272           0 : }
     273             : 
     274           0 : CModelParticleEmitter::~CModelParticleEmitter()
     275             : {
     276           0 :     m_Emitter->Unattach(m_Emitter);
     277           0 : }
     278             : 
     279           0 : void CModelParticleEmitter::SetEntityVariable(const std::string& name, float value)
     280             : {
     281           0 :     m_Emitter->SetEntityVariable(name, value);
     282           0 : }
     283             : 
     284           0 : CModelAbstract* CModelParticleEmitter::Clone() const
     285             : {
     286           0 :     return new CModelParticleEmitter(m_Type);
     287             : }
     288             : 
     289           0 : void CModelParticleEmitter::CalcBounds()
     290             : {
     291             :     // TODO: we ought to compute sensible bounds here, probably based on the
     292             :     // current computed particle positions plus the emitter type's largest
     293             :     // potential bounding box at the current position
     294             : 
     295           0 :     m_WorldBounds = m_Type->CalculateBounds(m_Emitter->GetPosition(), m_Emitter->GetParticleBounds());
     296           0 : }
     297             : 
     298           0 : void CModelParticleEmitter::ValidatePosition()
     299             : {
     300             :     // TODO: do we need to do anything here?
     301             : 
     302             :     // This is a convenient (though possibly not particularly appropriate) place
     303             :     // to invalidate bounds so they'll be recomputed from the recent particle data
     304           0 :     InvalidateBounds();
     305           0 : }
     306             : 
     307           0 : void CModelParticleEmitter::InvalidatePosition()
     308             : {
     309           0 : }
     310             : 
     311           0 : void CModelParticleEmitter::SetTransform(const CMatrix3D& transform)
     312             : {
     313           0 :     if (m_Transform == transform)
     314           0 :         return;
     315             : 
     316           0 :     m_Emitter->SetPosition(transform.GetTranslation());
     317           0 :     m_Emitter->SetRotation(transform.GetRotation());
     318             : 
     319             :     // call base class to set transform on this object
     320           0 :     CRenderableObject::SetTransform(transform);
     321           3 : }

Generated by: LCOV version 1.13