1 /* Copyright (C) 2023 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "ParticleEmitter.h"
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"
32 CParticleEmitter::CParticleEmitter(const CParticleEmitterTypePtr
& type
) :
33 m_Type(type
), m_Active(true), m_NextParticleIdx(0), m_EmissionRoundingError(0.f
),
34 m_LastUpdateTime(type
->m_Manager
.GetCurrentTime()),
36 m_VertexArray(Renderer::Backend::IBuffer::Type::VERTEX
, true),
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
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 if (m_Type
->m_StartFull
)
47 m_LastUpdateTime
-= m_Type
->m_MaxLifetime
;
49 m_Particles
.reserve(m_Type
->m_MaxParticles
);
51 m_AttributePos
.format
= Renderer::Backend::Format::R32G32B32_SFLOAT
;
52 m_VertexArray
.AddAttribute(&m_AttributePos
);
54 m_AttributeAxis
.format
= Renderer::Backend::Format::R32G32_SFLOAT
;
55 m_VertexArray
.AddAttribute(&m_AttributeAxis
);
57 m_AttributeUV
.format
= Renderer::Backend::Format::R32G32_SFLOAT
;
58 m_VertexArray
.AddAttribute(&m_AttributeUV
);
60 m_AttributeColor
.format
= Renderer::Backend::Format::R8G8B8A8_UNORM
;
61 m_VertexArray
.AddAttribute(&m_AttributeColor
);
63 m_VertexArray
.SetNumberOfVertices(m_Type
->m_MaxParticles
* 4);
64 m_VertexArray
.Layout();
66 m_IndexArray
.SetNumberOfVertices(m_Type
->m_MaxParticles
* 6);
67 m_IndexArray
.Layout();
68 VertexArrayIterator
<u16
> index
= m_IndexArray
.GetIterator();
69 for (u16 i
= 0; i
< m_Type
->m_MaxParticles
; ++i
)
78 m_IndexArray
.Upload();
79 m_IndexArray
.FreeBackingStore();
81 const uint32_t stride
= m_VertexArray
.GetStride();
82 const std::array
<Renderer::Backend::SVertexAttributeFormat
, 4> attributes
{{
83 {Renderer::Backend::VertexAttributeStream::POSITION
,
84 m_AttributePos
.format
, m_AttributePos
.offset
, stride
,
85 Renderer::Backend::VertexAttributeRate::PER_VERTEX
, 0},
86 {Renderer::Backend::VertexAttributeStream::COLOR
,
87 m_AttributeColor
.format
, m_AttributeColor
.offset
, stride
,
88 Renderer::Backend::VertexAttributeRate::PER_VERTEX
, 0},
89 {Renderer::Backend::VertexAttributeStream::UV0
,
90 m_AttributeUV
.format
, m_AttributeUV
.offset
, stride
,
91 Renderer::Backend::VertexAttributeRate::PER_VERTEX
, 0},
92 {Renderer::Backend::VertexAttributeStream::UV1
,
93 m_AttributeAxis
.format
, m_AttributeAxis
.offset
, stride
,
94 Renderer::Backend::VertexAttributeRate::PER_VERTEX
, 0},
96 m_VertexInputLayout
= g_Renderer
.GetVertexInputLayout(attributes
);
99 void CParticleEmitter::UpdateArrayData(int frameNumber
)
101 if (m_LastFrameNumber
== frameNumber
)
104 m_LastFrameNumber
= frameNumber
;
106 // Update m_Particles
107 m_Type
->UpdateEmitter(*this, m_Type
->m_Manager
.GetCurrentTime() - m_LastUpdateTime
);
108 m_LastUpdateTime
= m_Type
->m_Manager
.GetCurrentTime();
110 // Regenerate the vertex array data:
112 VertexArrayIterator
<CVector3D
> attrPos
= m_AttributePos
.GetIterator
<CVector3D
>();
113 VertexArrayIterator
<float[2]> attrAxis
= m_AttributeAxis
.GetIterator
<float[2]>();
114 VertexArrayIterator
<float[2]> attrUV
= m_AttributeUV
.GetIterator
<float[2]>();
115 VertexArrayIterator
<SColor4ub
> attrColor
= m_AttributeColor
.GetIterator
<SColor4ub
>();
117 ENSURE(m_Particles
.size() <= m_Type
->m_MaxParticles
);
119 CBoundingBoxAligned bounds
;
121 for (size_t i
= 0; i
< m_Particles
.size(); ++i
)
123 // TODO: for more efficient rendering, maybe we should replace this with
124 // a degenerate quad if alpha is 0
126 bounds
+= m_Particles
[i
].pos
;
128 *attrPos
++ = m_Particles
[i
].pos
;
129 *attrPos
++ = m_Particles
[i
].pos
;
130 *attrPos
++ = m_Particles
[i
].pos
;
131 *attrPos
++ = m_Particles
[i
].pos
;
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:
137 float s
= sin(m_Particles
[i
].angle
) * m_Particles
[i
].size
/2.f
;
138 float c
= cos(m_Particles
[i
].angle
) * m_Particles
[i
].size
/2.f
;
166 SColor4ub color
= m_Particles
[i
].color
;
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 if (m_Type
->m_BlendMode
== CParticleEmitterType::BlendMode::OVERLAY
||
171 m_Type
->m_BlendMode
== CParticleEmitterType::BlendMode::MULTIPLY
)
173 color
.R
= (color
.R
* color
.A
) / 255;
174 color
.G
= (color
.G
* color
.A
) / 255;
175 color
.B
= (color
.B
* color
.A
) / 255;
178 *attrColor
++ = color
;
179 *attrColor
++ = color
;
180 *attrColor
++ = color
;
181 *attrColor
++ = color
;
184 m_ParticleBounds
= bounds
;
186 m_VertexArray
.Upload();
189 void CParticleEmitter::PrepareForRendering()
191 m_VertexArray
.PrepareForRendering();
194 void CParticleEmitter::UploadData(
195 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
)
197 m_VertexArray
.UploadIfNeeded(deviceCommandContext
);
200 void CParticleEmitter::Bind(
201 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
,
202 Renderer::Backend::IShaderProgram
* shader
)
204 m_Type
->m_Texture
->UploadBackendTextureIfNeeded(deviceCommandContext
);
206 CLOSTexture
& los
= g_Renderer
.GetSceneRenderer().GetScene().GetLOSTexture();
207 deviceCommandContext
->SetTexture(
208 shader
->GetBindingSlot(str_losTex
), los
.GetTextureSmooth());
209 deviceCommandContext
->SetUniform(
210 shader
->GetBindingSlot(str_losTransform
),
211 los
.GetTextureMatrix()[0], los
.GetTextureMatrix()[12]);
213 const CLightEnv
& lightEnv
= g_Renderer
.GetSceneRenderer().GetLightEnv();
215 deviceCommandContext
->SetUniform(
216 shader
->GetBindingSlot(str_sunColor
), lightEnv
.m_SunColor
.AsFloatArray());
217 deviceCommandContext
->SetUniform(
218 shader
->GetBindingSlot(str_fogColor
), lightEnv
.m_FogColor
.AsFloatArray());
219 deviceCommandContext
->SetUniform(
220 shader
->GetBindingSlot(str_fogParams
), lightEnv
.m_FogFactor
, lightEnv
.m_FogMax
);
222 deviceCommandContext
->SetTexture(
223 shader
->GetBindingSlot(str_baseTex
), m_Type
->m_Texture
->GetBackendTexture());
226 void CParticleEmitter::RenderArray(
227 Renderer::Backend::IDeviceCommandContext
* deviceCommandContext
)
229 if (m_Particles
.empty())
232 const uint32_t stride
= m_VertexArray
.GetStride();
233 const uint32_t firstVertexOffset
= m_VertexArray
.GetOffset() * stride
;
235 deviceCommandContext
->SetVertexInputLayout(m_VertexInputLayout
);
237 deviceCommandContext
->SetVertexBuffer(
238 0, m_VertexArray
.GetBuffer(), firstVertexOffset
);
239 deviceCommandContext
->SetIndexBuffer(m_IndexArray
.GetBuffer());
241 deviceCommandContext
->DrawIndexed(m_IndexArray
.GetOffset(), m_Particles
.size() * 6, 0);
243 g_Renderer
.GetStats().m_DrawCalls
++;
244 g_Renderer
.GetStats().m_Particles
+= m_Particles
.size();
247 void CParticleEmitter::Unattach(const CParticleEmitterPtr
& self
)
250 m_Type
->m_Manager
.AddUnattachedEmitter(self
);
253 void CParticleEmitter::AddParticle(const SParticle
& particle
)
255 if (m_NextParticleIdx
>= m_Particles
.size())
256 m_Particles
.push_back(particle
);
258 m_Particles
[m_NextParticleIdx
] = particle
;
260 m_NextParticleIdx
= (m_NextParticleIdx
+ 1) % m_Type
->m_MaxParticles
;
263 void CParticleEmitter::SetEntityVariable(const std::string
& name
, float value
)
265 m_EntityVariables
[name
] = value
;
268 CModelParticleEmitter::CModelParticleEmitter(const CParticleEmitterTypePtr
& type
) :
271 m_Emitter
= CParticleEmitterPtr(new CParticleEmitter(m_Type
));
274 CModelParticleEmitter::~CModelParticleEmitter()
276 m_Emitter
->Unattach(m_Emitter
);
279 void CModelParticleEmitter::SetEntityVariable(const std::string
& name
, float value
)
281 m_Emitter
->SetEntityVariable(name
, value
);
284 std::unique_ptr
<CModelAbstract
> CModelParticleEmitter::Clone() const
286 return std::make_unique
<CModelParticleEmitter
>(m_Type
);
289 void CModelParticleEmitter::CalcBounds()
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
295 m_WorldBounds
= m_Type
->CalculateBounds(m_Emitter
->GetPosition(), m_Emitter
->GetParticleBounds());
298 void CModelParticleEmitter::ValidatePosition()
300 // TODO: do we need to do anything here?
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
307 void CModelParticleEmitter::InvalidatePosition()
311 void CModelParticleEmitter::SetTransform(const CMatrix3D
& transform
)
313 if (m_Transform
== transform
)
316 m_Emitter
->SetPosition(transform
.GetTranslation());
317 m_Emitter
->SetRotation(transform
.GetRotation());
319 // call base class to set transform on this object
320 CRenderableObject::SetTransform(transform
);