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