1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "BsParticleEmitter.h"
4#include "Mesh/BsMeshData.h"
5#include "Mesh/BsMeshUtility.h"
6#include "RenderAPI/BsVertexDataDesc.h"
7#include "Math/BsRandom.h"
8#include "Components/BsCRenderable.h"
9#include "Private/Particles/BsParticleSet.h"
10#include "Private/RTTI/BsParticleSystemRTTI.h"
11#include "Animation/BsAnimation.h"
12#include "Animation/BsAnimationManager.h"
13#include "Mesh/BsMesh.h"
14
15namespace bs
16{
17 MeshWeightedTriangles::MeshWeightedTriangles(const MeshData& meshData)
18 {
19 calculate(meshData);
20 }
21
22 void MeshWeightedTriangles::calculate(const MeshData& meshData)
23 {
24 const UINT32 numIndices = meshData.getNumIndices();
25 assert(numIndices % 3 == 0);
26
27 const UINT32 numTriangles = numIndices / 3;
28 mWeights.resize(numTriangles);
29
30 UINT8* vertices = meshData.getElementData(VES_POSITION);
31
32 const SPtr<VertexDataDesc>& vertexDesc = meshData.getVertexDesc();
33 const UINT32 stride = vertexDesc->getVertexStride();
34
35 float totalArea = 0.0f;
36 if(meshData.getIndexType() == IT_32BIT)
37 {
38 UINT32* indices = meshData.getIndices32();
39
40 for(UINT32 i = 0; i < numTriangles; i++)
41 {
42 TriangleWeight& weight = mWeights[i];
43
44 weight.indices[0] = indices[i * 3 + 0];
45 weight.indices[1] = indices[i * 3 + 1];
46 weight.indices[2] = indices[i * 3 + 2];
47 }
48 }
49 else
50 {
51 UINT16* indices = meshData.getIndices16();
52
53 for(UINT32 i = 0; i < numTriangles; i++)
54 {
55 TriangleWeight& weight = mWeights[i];
56
57 weight.indices[0] = indices[i * 3 + 0];
58 weight.indices[1] = indices[i * 3 + 1];
59 weight.indices[2] = indices[i * 3 + 2];
60 }
61 }
62
63 for (UINT32 i = 0; i < numTriangles; i++)
64 {
65 TriangleWeight& weight = mWeights[i];
66 const Vector3& a = *(Vector3*)(vertices + weight.indices[0] * stride);
67 const Vector3& b = *(Vector3*)(vertices + weight.indices[1] * stride);
68 const Vector3& c = *(Vector3*)(vertices + weight.indices[2] * stride);
69
70 // Note: Using squared length here would be faster, but the weights can be small and squaring them just
71 // makes them smaller, causing precision issues
72 weight.cumulativeWeight = Vector3::cross(b - a, c - a).length();
73 totalArea += weight.cumulativeWeight;
74 }
75
76 const float invTotalArea = 1.0f / totalArea;
77 for (UINT32 i = 0; i < numTriangles; i++)
78 mWeights[i].cumulativeWeight *= invTotalArea;
79
80 for (UINT32 i = 1; i < numTriangles; i++)
81 mWeights[i].cumulativeWeight += mWeights[i - 1].cumulativeWeight;
82
83 mWeights[numTriangles - 1].cumulativeWeight = 1.0f;
84 }
85
86 void MeshWeightedTriangles::getTriangle(const Random& random, std::array<UINT32, 3>& indices) const
87 {
88 struct Comp
89 {
90 bool operator()(float a, const TriangleWeight& b) const
91 {
92 return a < b.cumulativeWeight;
93 }
94
95 bool operator()(const TriangleWeight& a, float b) const
96 {
97 return a.cumulativeWeight < b;
98 }
99 };
100
101 const float val = random.getUNorm();
102
103 const auto findIter = std::lower_bound(mWeights.begin(), mWeights.end(), val, Comp());
104 if(findIter != mWeights.end())
105 memcpy(indices.data(), findIter->indices, sizeof(indices));
106 else
107 bs_zero_out(indices);
108 }
109
110 template <class Pr>
111 UINT32 spawnMultiple(ParticleSet& particles, UINT32 count, Pr predicate)
112 {
113 const UINT32 index = particles.allocParticles(count);
114 ParticleSetData& particleData = particles.getParticles();
115
116 const UINT32 end = index + count;
117 for (UINT32 i = index; i < end; i++)
118 predicate(i - index, particleData.position[i], particleData.velocity[i]);
119
120 return index;
121 }
122
123 template <class T>
124 UINT32 spawnMultipleRandom(T* spawner, const Random& random, ParticleSet& particles, UINT32 count)
125 {
126 const UINT32 index = particles.allocParticles(count);
127 ParticleSetData& particleData = particles.getParticles();
128
129 const UINT32 end = index + count;
130 for (UINT32 i = index; i < end; i++)
131 spawner->_spawn(random, particleData.position[i], particleData.velocity[i]);
132
133 return index;
134 }
135
136 template <class T>
137 UINT32 spawnMultipleSpread(T* spawner, float length, float interval, ParticleSet& particles, UINT32 count)
138 {
139 const UINT32 index = particles.allocParticles(count);
140 ParticleSetData& particleData = particles.getParticles();
141
142 const float dt = length / (float)count;
143
144 float accum = 0.0f;
145 for (UINT32 i = 0; i < count; i++)
146 {
147 float t = accum;
148 if(interval > 0)
149 t = Math::roundToMultiple(accum, interval);
150
151 const UINT32 particleIdx = index + i;
152 spawner->_spawn(t, particleData.position[particleIdx], particleData.velocity[particleIdx]);
153
154 accum += dt;
155 }
156
157 return index;
158 }
159
160 template <class T>
161 UINT32 spawnMultipleLoop(T* spawner, float length, float speed, float interval, ParticleSet& particles,
162 UINT32 count, const ParticleSystemState& state)
163 {
164 const UINT32 index = particles.allocParticles(count);
165 ParticleSetData& particleData = particles.getParticles();
166
167 const float dt = state.timeStep / (float)count;
168
169 for (UINT32 i = 0; i < count; i++)
170 {
171 float t = (state.timeStart + dt * i) * speed;
172 t = fmod(t, length);
173
174 if(interval > 0.0f)
175 t = Math::roundToMultiple(t, interval);
176
177 const UINT32 particleIdx = index + i;
178 spawner->_spawn(t, particleData.position[particleIdx], particleData.velocity[particleIdx]);
179 }
180
181 return index;
182 }
183
184 template <class T>
185 UINT32 spawnMultiplePingPong(T* spawner, float length, float speed, float interval, ParticleSet& particles,
186 UINT32 count, const ParticleSystemState& state)
187 {
188 const UINT32 index = particles.allocParticles(count);
189 ParticleSetData& particleData = particles.getParticles();
190
191 const float dt = state.timeStep / (float)count;
192
193 for (UINT32 i = 0; i < count; i++)
194 {
195 float t = (state.timeStart + dt * i) * speed;
196
197 const auto loop = (UINT32)(t / length);
198 if (loop % 2 == 1)
199 t = length - fmod(t, length);
200 else
201 t = fmod(t, length);
202
203 if(interval > 0.0f)
204 t = Math::roundToMultiple(t, interval);
205
206 const UINT32 particleIdx = index + i;
207 spawner->_spawn(t, particleData.position[particleIdx], particleData.velocity[particleIdx]);
208 }
209
210 return index;
211 }
212
213 template <class T>
214 UINT32 spawnMultipleMode(T* spawner, ParticleEmissionModeType type, float length, float speed, float interval,
215 const Random& random, ParticleSet& particles, UINT32 count, const ParticleSystemState& state)
216 {
217 if(count > 0)
218 {
219 switch (type)
220 {
221 case ParticleEmissionModeType::Random:
222 return spawnMultipleRandom(spawner, random, particles, count);
223 case ParticleEmissionModeType::Loop:
224 return spawnMultipleLoop(spawner, length, speed, interval, particles,
225 count, state);
226 case ParticleEmissionModeType::PingPong:
227 return spawnMultiplePingPong(spawner, length, speed, interval, particles,
228 count, state);
229 case ParticleEmissionModeType::Spread:
230 return spawnMultipleSpread(spawner, length, interval, particles, count);
231 default:
232 break;
233 }
234 }
235
236 return particles.getParticleCount();
237 }
238
239 ParticleEmitterConeShape::ParticleEmitterConeShape(const PARTICLE_CONE_SHAPE_DESC& desc)
240 :mInfo(desc)
241 { }
242
243 UINT32 ParticleEmitterConeShape::_spawn(const Random& random, ParticleSet& particles, UINT32 count,
244 const ParticleSystemState& state) const
245 {
246 return spawnMultipleMode(this, mInfo.mode.type, mInfo.arc.valueRadians(), mInfo.mode.speed * Math::DEG2RAD,
247 mInfo.mode.interval * Math::DEG2RAD, random, particles, count, state);
248 }
249
250 void ParticleEmitterConeShape::_spawn(const Random& random, Vector3& position, Vector3& normal) const
251 {
252 Vector2 pos2D;
253 if (Math::approxEquals(mInfo.arc.valueDegrees(), 360.0f))
254 pos2D = random.getPointInCircleShell(mInfo.thickness);
255 else
256 pos2D = random.getPointInArcShell(mInfo.arc, mInfo.thickness);
257
258 getPointInCone(pos2D, random.getUNorm() * mInfo.length, position, normal);
259 }
260
261 void ParticleEmitterConeShape::_spawn(float t, Vector3& position, Vector3& normal) const
262 {
263 const Vector2 pos2D(Math::cos(t), Math::sin(t));
264
265 getPointInCone(pos2D, 0.0f, position, normal);
266 }
267
268 void ParticleEmitterConeShape::getPointInCone(const Vector2& pos2D, float distance, Vector3& position,
269 Vector3& normal) const
270 {
271 const float angleSin = Math::sin(mInfo.angle);
272 normal = Vector3(pos2D.x * angleSin, pos2D.y * angleSin, Math::cos(mInfo.angle));
273 normal.normalize();
274
275 position = Vector3(pos2D.x * mInfo.radius, pos2D.y * mInfo.radius, 0.0f);
276
277 if(mInfo.type == ParticleEmitterConeType::Volume)
278 position += normal * distance;
279 }
280
281 SPtr<ParticleEmitterConeShape> ParticleEmitterConeShape::create(const PARTICLE_CONE_SHAPE_DESC& desc)
282 {
283 return bs_shared_ptr_new<ParticleEmitterConeShape>(desc);
284 }
285
286 SPtr<ParticleEmitterConeShape> ParticleEmitterConeShape::create()
287 {
288 return bs_shared_ptr_new<ParticleEmitterConeShape>();
289 }
290
291 void ParticleEmitterConeShape::calcBounds(AABox& shape, AABox& velocity) const
292 {
293 const float sinAngle = Math::sin(mInfo.angle);
294 const float cosAngle = Math::cos(mInfo.angle);
295
296 if(mInfo.type == ParticleEmitterConeType::Base)
297 {
298 shape.setMin(Vector3(-mInfo.radius, -mInfo.radius, 0.0f));
299 shape.setMax(Vector3(mInfo.radius, mInfo.radius, 0.0f));
300 }
301 else
302 {
303 const float topRadius = mInfo.radius + mInfo.length * sinAngle;
304 const float length = mInfo.length * cosAngle;
305
306 shape.setMin(Vector3(-topRadius, -topRadius, 0.0f));
307 shape.setMax(Vector3(topRadius, topRadius, length));
308 }
309
310 velocity.setMin(Vector3(-sinAngle, -sinAngle, 0.0f));
311 velocity.setMax(Vector3(sinAngle, sinAngle, 1.0f));
312 }
313
314 RTTITypeBase* ParticleEmitterConeShape::getRTTIStatic()
315 {
316 return ParticleEmitterConeShapeRTTI::instance();
317 }
318
319 RTTITypeBase* ParticleEmitterConeShape::getRTTI() const
320 {
321 return getRTTIStatic();
322 }
323
324 ParticleEmitterSphereShape::ParticleEmitterSphereShape(const PARTICLE_SPHERE_SHAPE_DESC& desc)
325 :mInfo(desc)
326 { }
327
328 UINT32 ParticleEmitterSphereShape::_spawn(const Random& random, ParticleSet& particles, UINT32 count,
329 const ParticleSystemState& state) const
330 {
331 return spawnMultipleRandom(this, random, particles, count);
332 }
333
334 void ParticleEmitterSphereShape::_spawn(const Random& random, Vector3& position, Vector3& normal) const
335 {
336 position = random.getPointInSphereShell(mInfo.thickness);
337 normal = Vector3::normalize(position);
338
339 position *= mInfo.radius;
340 }
341
342 void ParticleEmitterSphereShape::calcBounds(AABox& shape, AABox& velocity) const
343 {
344 shape.setMin(Vector3::ONE * -mInfo.radius);
345 shape.setMax(Vector3::ONE * mInfo.radius);
346
347 velocity.setMin(-Vector3::ONE);
348 velocity.setMax(Vector3::ONE);
349 }
350
351 SPtr<ParticleEmitterSphereShape> ParticleEmitterSphereShape::create(const PARTICLE_SPHERE_SHAPE_DESC& desc)
352 {
353 return bs_shared_ptr_new<ParticleEmitterSphereShape>(desc);
354 }
355
356 SPtr<ParticleEmitterSphereShape> ParticleEmitterSphereShape::create()
357 {
358 return bs_shared_ptr_new<ParticleEmitterSphereShape>();
359 }
360
361 RTTITypeBase* ParticleEmitterSphereShape::getRTTIStatic()
362 {
363 return ParticleEmitterSphereShapeRTTI::instance();
364 }
365
366 RTTITypeBase* ParticleEmitterSphereShape::getRTTI() const
367 {
368 return getRTTIStatic();
369 }
370
371 ParticleEmitterHemisphereShape::ParticleEmitterHemisphereShape(const PARTICLE_HEMISPHERE_SHAPE_DESC& desc)
372 :mInfo(desc)
373 { }
374
375 UINT32 ParticleEmitterHemisphereShape::_spawn(const Random& random, ParticleSet& particles, UINT32 count,
376 const ParticleSystemState& state) const
377 {
378 return spawnMultipleRandom(this, random, particles, count);
379 }
380
381 void ParticleEmitterHemisphereShape::_spawn(const Random& random, Vector3& position, Vector3& normal) const
382 {
383 position = random.getPointInSphereShell(mInfo.thickness);
384 if (position.z > 0.0f)
385 position.z *= -1.0f;
386
387 normal = Vector3::normalize(position);
388 position *= mInfo.radius;
389 }
390
391 void ParticleEmitterHemisphereShape::calcBounds(AABox& shape, AABox& velocity) const
392 {
393 shape.setMin(Vector3(-mInfo.radius, -mInfo.radius, 0.0f));
394 shape.setMax(Vector3::ONE * mInfo.radius);
395
396 velocity.setMin(Vector3(-1.0f, -1.0f, 0.0f));
397 velocity.setMax(Vector3::ONE);
398 }
399
400 SPtr<ParticleEmitterHemisphereShape> ParticleEmitterHemisphereShape::create(const PARTICLE_HEMISPHERE_SHAPE_DESC& desc)
401 {
402 return bs_shared_ptr_new<ParticleEmitterHemisphereShape>(desc);
403 }
404
405 SPtr<ParticleEmitterHemisphereShape> ParticleEmitterHemisphereShape::create()
406 {
407 return bs_shared_ptr_new<ParticleEmitterHemisphereShape>();
408 }
409
410 RTTITypeBase* ParticleEmitterHemisphereShape::getRTTIStatic()
411 {
412 return ParticleEmitterHemisphereShapeRTTI::instance();
413 }
414
415 RTTITypeBase* ParticleEmitterHemisphereShape::getRTTI() const
416 {
417 return getRTTIStatic();
418 }
419
420 ParticleEmitterBoxShape::ParticleEmitterBoxShape(const PARTICLE_BOX_SHAPE_DESC& desc)
421 :mInfo(desc)
422 {
423 switch(mInfo.type)
424 {
425 case ParticleEmitterBoxType::Surface:
426 {
427 float totalSurfaceArea = 0.0f;
428 for(UINT32 i = 0; i < 3; i++)
429 {
430 mSurfaceArea[i] = Math::sqr(desc.extents[i]);
431 totalSurfaceArea += mSurfaceArea[i];
432 }
433
434 if(totalSurfaceArea > 0.0f)
435 {
436 const float invTotalSurfaceArea = 1.0f / totalSurfaceArea;
437 for(UINT32 i = 0; i < 3; i++)
438 mSurfaceArea[i] *= invTotalSurfaceArea;
439
440 mSurfaceArea[1] += mSurfaceArea[0];
441 mSurfaceArea[2] = 1.0f;
442 }
443 }
444 break;
445 case ParticleEmitterBoxType::Edge:
446 {
447 float totalEdgeLength = 0.0f;
448 for(UINT32 i = 0; i < 3; i++)
449 {
450 mEdgeLengths[i] = desc.extents[i];
451 totalEdgeLength += mEdgeLengths[i];
452 }
453
454 if(totalEdgeLength > 0.0f)
455 {
456 const float invTotalEdgeLength = 1.0f / totalEdgeLength;
457 for(UINT32 i = 0; i < 3; i++)
458 mEdgeLengths[i] *= invTotalEdgeLength;
459
460 mEdgeLengths[1] += mEdgeLengths[0];
461 mEdgeLengths[2] = 1.0f;
462 }
463 }
464 break;
465 default:
466 case ParticleEmitterBoxType::Volume: break;
467 }
468 }
469
470 UINT32 ParticleEmitterBoxShape::_spawn(const Random& random, ParticleSet& particles, UINT32 count,
471 const ParticleSystemState& state) const
472 {
473 return spawnMultipleRandom(this, random, particles, count);
474 }
475
476 void ParticleEmitterBoxShape::_spawn(const Random& random, Vector3& position, Vector3& normal) const
477 {
478 switch(mInfo.type)
479 {
480 default:
481 case ParticleEmitterBoxType::Volume:
482 position.x = mInfo.extents.x * random.getSNorm();
483 position.y = mInfo.extents.y * random.getSNorm();
484 position.z = mInfo.extents.z * random.getSNorm();
485 normal = Vector3::UNIT_Z;
486 break;
487 case ParticleEmitterBoxType::Surface:
488 {
489 const float u = random.getSNorm();
490 const float v = random.getSNorm();
491
492 // Determine an axis (based on their size, larger being more likely)
493 const float axisRnd = random.getUNorm();
494 UINT32 axis = 0;
495 for (; axis < 3; axis++)
496 {
497 if(axisRnd <= mSurfaceArea[axis])
498 break;
499 }
500
501 switch(axis)
502 {
503 case 0:
504 position.x = mInfo.extents.x * u;
505 position.y = mInfo.extents.y * v;
506 position.z = random.getUNorm() > 0.5f ? mInfo.extents.z : -mInfo.extents.z;
507 break;
508 case 1:
509 position.x = mInfo.extents.x * u;
510 position.y = random.getUNorm() > 0.5f ? mInfo.extents.y : -mInfo.extents.y;
511 position.z = mInfo.extents.z * v;
512 break;
513 case 2:
514 position.x = random.getUNorm() > 0.5f ? mInfo.extents.x : -mInfo.extents.x;
515 position.y = mInfo.extents.y * v;
516 position.z = mInfo.extents.z * u;
517 break;
518 default:
519 break;
520 }
521
522 normal = Vector3::UNIT_Z;
523 }
524 break;
525 case ParticleEmitterBoxType::Edge:
526 {
527 const float u = random.getSNorm();
528
529 // Determine an axis (based on their length, longer being more likely)
530 const float axisRnd = random.getUNorm();
531 UINT32 axis = 0;
532 for (; axis < 3; axis++)
533 {
534 if(axisRnd <= mEdgeLengths[axis])
535 break;
536 }
537
538 switch(axis)
539 {
540 case 0:
541 position.x = mInfo.extents.x * u;
542 position.y = random.getUNorm() > 0.5f ? mInfo.extents.y : -mInfo.extents.y;
543 position.z = random.getUNorm() > 0.5f ? mInfo.extents.z : -mInfo.extents.z;
544 break;
545 case 1:
546 position.x = random.getUNorm() > 0.5f ? mInfo.extents.x : -mInfo.extents.x;
547 position.y = mInfo.extents.y * u;
548 position.z = random.getUNorm() > 0.5f ? mInfo.extents.z : -mInfo.extents.z;
549 break;
550 case 2:
551 position.x = random.getUNorm() > 0.5f ? mInfo.extents.x : -mInfo.extents.x;
552 position.y = random.getUNorm() > 0.5f ? mInfo.extents.y : -mInfo.extents.y;
553 position.z = mInfo.extents.z * u;
554 break;
555 default:
556 break;
557 }
558
559 normal = Vector3::UNIT_Z;
560 }
561 break;
562 }
563 }
564
565 void ParticleEmitterBoxShape::calcBounds(AABox& shape, AABox& velocity) const
566 {
567 shape.setMin(-mInfo.extents);
568 shape.setMax(mInfo.extents);
569
570 velocity.setMin(Vector3::ZERO);
571 velocity.setMax(Vector3::UNIT_Z);
572 }
573
574 SPtr<ParticleEmitterBoxShape> ParticleEmitterBoxShape::create(const PARTICLE_BOX_SHAPE_DESC& desc)
575 {
576 return bs_shared_ptr_new<ParticleEmitterBoxShape>(desc);
577 }
578
579 SPtr<ParticleEmitterBoxShape> ParticleEmitterBoxShape::create()
580 {
581 return bs_shared_ptr_new<ParticleEmitterBoxShape>();
582 }
583
584 RTTITypeBase* ParticleEmitterBoxShape::getRTTIStatic()
585 {
586 return ParticleEmitterBoxShapeRTTI::instance();
587 }
588
589 RTTITypeBase* ParticleEmitterBoxShape::getRTTI() const
590 {
591 return getRTTIStatic();
592 }
593
594 ParticleEmitterLineShape::ParticleEmitterLineShape(const PARTICLE_LINE_SHAPE_DESC& desc)
595 :mInfo(desc)
596 { }
597
598 UINT32 ParticleEmitterLineShape::_spawn(const Random& random, ParticleSet& particles, UINT32 count,
599 const ParticleSystemState& state) const
600 {
601 return spawnMultipleMode(this, mInfo.mode.type, mInfo.length, mInfo.mode.speed,
602 mInfo.mode.interval, random, particles, count, state);
603 }
604
605 void ParticleEmitterLineShape::_spawn(const Random& random, Vector3& position, Vector3& normal) const
606 {
607 position = Vector3(random.getSNorm() * mInfo.length * 0.5f, 0.0f, 0.0f);
608 normal = Vector3::UNIT_Z;
609 }
610
611 void ParticleEmitterLineShape::_spawn(float t, Vector3& position, Vector3& normal) const
612 {
613 position = Vector3(t * mInfo.length - mInfo.length * 0.5f, 0.0f, 0.0f);
614 normal = Vector3::UNIT_Z;
615 }
616
617 void ParticleEmitterLineShape::calcBounds(AABox& shape, AABox& velocity) const
618 {
619 shape.setMin(Vector3(-mInfo.length * 0.5f, 0.0f, 0.0f));
620 shape.setMax(Vector3(mInfo.length * 0.5f, 0.0f, 0.0f));
621
622 velocity.setMin(Vector3::ZERO);
623 velocity.setMax(Vector3::UNIT_Z);
624 }
625
626 SPtr<ParticleEmitterLineShape> ParticleEmitterLineShape::create(const PARTICLE_LINE_SHAPE_DESC& desc)
627 {
628 return bs_shared_ptr_new<ParticleEmitterLineShape>(desc);
629 }
630
631 SPtr<ParticleEmitterLineShape> ParticleEmitterLineShape::create()
632 {
633 return bs_shared_ptr_new<ParticleEmitterLineShape>();
634 }
635
636 RTTITypeBase* ParticleEmitterLineShape::getRTTIStatic()
637 {
638 return ParticleEmitterLineShapeRTTI::instance();
639 }
640
641 RTTITypeBase* ParticleEmitterLineShape::getRTTI() const
642 {
643 return getRTTIStatic();
644 }
645
646 ParticleEmitterCircleShape::ParticleEmitterCircleShape(const PARTICLE_CIRCLE_SHAPE_DESC& desc)
647 :mInfo(desc)
648 { }
649
650 UINT32 ParticleEmitterCircleShape::_spawn(const Random& random, ParticleSet& particles, UINT32 count,
651 const ParticleSystemState& state) const
652 {
653 return spawnMultipleMode(this, mInfo.mode.type, mInfo.arc.valueRadians(), mInfo.mode.speed * Math::DEG2RAD,
654 mInfo.mode.interval * Math::DEG2RAD, random, particles, count, state);
655 }
656
657 void ParticleEmitterCircleShape::_spawn(const Random& random, Vector3& position, Vector3& normal) const
658 {
659 Vector2 pos2D;
660 if (Math::approxEquals(mInfo.arc.valueDegrees(), 360.0f))
661 pos2D = random.getPointInCircleShell(mInfo.thickness);
662 else
663 pos2D = random.getPointInArcShell(mInfo.arc, mInfo.thickness);
664
665 position = Vector3(pos2D.x * mInfo.radius, pos2D.y * mInfo.radius, 0.0f);
666 normal = Vector3::UNIT_Z;
667 }
668
669 void ParticleEmitterCircleShape::_spawn(float t, Vector3& position, Vector3& normal) const
670 {
671 const Vector2 pos2D(Math::cos(t), Math::sin(t));
672
673 position = Vector3(pos2D.x * mInfo.radius, pos2D.y * mInfo.radius, 0.0f);
674 normal = Vector3::UNIT_Z;
675 }
676
677 void ParticleEmitterCircleShape::calcBounds(AABox& shape, AABox& velocity) const
678 {
679 shape.setMin(Vector3(-mInfo.radius, -mInfo.radius, 0.0f));
680 shape.setMax(Vector3(mInfo.radius, mInfo.radius, 0.0f));
681
682 velocity.setMin(Vector3::ZERO);
683 velocity.setMax(Vector3::UNIT_Z);
684 }
685
686 SPtr<ParticleEmitterCircleShape> ParticleEmitterCircleShape::create(const PARTICLE_CIRCLE_SHAPE_DESC& desc)
687 {
688 return bs_shared_ptr_new<ParticleEmitterCircleShape>(desc);
689 }
690
691 SPtr<ParticleEmitterCircleShape> ParticleEmitterCircleShape::create()
692 {
693 return bs_shared_ptr_new<ParticleEmitterCircleShape>();
694 }
695
696 RTTITypeBase* ParticleEmitterCircleShape::getRTTIStatic()
697 {
698 return ParticleEmitterCircleShapeRTTI::instance();
699 }
700
701 RTTITypeBase* ParticleEmitterCircleShape::getRTTI() const
702 {
703 return getRTTIStatic();
704 }
705
706 ParticleEmitterRectShape::ParticleEmitterRectShape(const PARTICLE_RECT_SHAPE_DESC& desc)
707 :mInfo(desc)
708 { }
709
710 UINT32 ParticleEmitterRectShape::_spawn(const Random& random, ParticleSet& particles, UINT32 count,
711 const ParticleSystemState& state) const
712 {
713 return spawnMultipleRandom(this, random, particles, count);
714 }
715
716 void ParticleEmitterRectShape::_spawn(const Random& random, Vector3& position, Vector3& normal) const
717 {
718 position.x = random.getSNorm() * mInfo.extents.x;
719 position.y = random.getSNorm() * mInfo.extents.y;
720 position.z = 0.0f;
721
722 normal = Vector3::UNIT_Z;
723 }
724
725 void ParticleEmitterRectShape::calcBounds(AABox& shape, AABox& velocity) const
726 {
727 shape.setMin(Vector3(-mInfo.extents.x, -mInfo.extents.y, 0.0f));
728 shape.setMax(Vector3(mInfo.extents.x, mInfo.extents.y, 0.0f));
729
730 velocity.setMin(Vector3::ZERO);
731 velocity.setMax(Vector3::UNIT_Z);
732 }
733
734 SPtr<ParticleEmitterRectShape> ParticleEmitterRectShape::create(const PARTICLE_RECT_SHAPE_DESC& desc)
735 {
736 return bs_shared_ptr_new<ParticleEmitterRectShape>(desc);
737 }
738
739 SPtr<ParticleEmitterRectShape> ParticleEmitterRectShape::create()
740 {
741 return bs_shared_ptr_new<ParticleEmitterRectShape>();
742 }
743
744 RTTITypeBase* ParticleEmitterRectShape::getRTTIStatic()
745 {
746 return ParticleEmitterRectShapeRTTI::instance();
747 }
748
749 RTTITypeBase* ParticleEmitterRectShape::getRTTI() const
750 {
751 return getRTTIStatic();
752 }
753
754 bool MeshEmissionHelper::initialize(const HMesh& mesh, bool perVertex, bool skinning)
755 {
756 // Validate
757 if(mesh)
758 {
759 mMeshData = mesh->getCachedData();
760
761 if(!mMeshData)
762 {
763 LOGWRN_VERBOSE("Particle emitter mesh not created with CPU caching, performing an expensive GPU read.");
764
765 mMeshData = mesh->allocBuffer();
766 mesh->readData(mMeshData);
767
768 gCoreThread().submit(true);
769 }
770 }
771
772 if(!mMeshData)
773 {
774 // No warning as user could want to add mesh data later
775 return false;
776 }
777
778 const SPtr<VertexDataDesc>& vertexDesc = mMeshData->getVertexDesc();
779 const VertexElement* positionElement = vertexDesc->getElement(VES_POSITION);
780 if(positionElement == nullptr)
781 {
782 LOGERR("Mesh particle emitter requires position vertex data to be present in the provided mesh data.");
783 return false;
784 }
785
786 if(positionElement->getType() != VET_FLOAT3)
787 {
788 LOGERR("Mesh particle emitter requires position vertex data to use 3D vectors for individual elements.");
789 return false;
790 }
791
792 if(!perVertex && (mMeshData->getNumIndices() % 3 != 0))
793 {
794 LOGERR("Unless using the per-vertex emission mode, mesh particle emitter requires the number of indices to be \
795 divisible by three, using a triangle list layout.");
796 return false;
797 }
798
799 if(skinning)
800 {
801 const VertexElement* blendIdxElement = vertexDesc->getElement(VES_BLEND_INDICES);
802 const VertexElement* blendWeightElement = vertexDesc->getElement(VES_BLEND_WEIGHTS);
803
804 if (blendIdxElement == nullptr || blendWeightElement == nullptr)
805 {
806 LOGERR("Skinned mesh particle emitter requires blend indices and blend weight data to be present in the \
807 provided mesh data.");
808 return false;
809 }
810
811 if (blendIdxElement->getType() != VET_UBYTE4)
812 {
813 LOGERR("Skinned mesh particle emitter requires blend indices to be a 4-byte encoded format.");
814 return false;
815 }
816
817 if (blendWeightElement->getType() != VET_FLOAT4)
818 {
819 LOGERR("Skinned mesh particle emitter requires blend weights to be a 4D vector format.");
820 return false;
821 }
822 }
823
824 // Initialize
825 mVertices = mMeshData->getElementData(VES_POSITION);
826 mNumVertices = mMeshData->getNumVertices();
827 mVertexStride = vertexDesc->getVertexStride();
828
829 const VertexElement* normalElement = vertexDesc->getElement(VES_NORMAL);
830
831 mNormals = nullptr;
832 if(normalElement)
833 {
834 if(normalElement->getType() == VET_UBYTE4_NORM)
835 {
836 mNormals = mMeshData->getElementData(VES_NORMAL);
837 m32BitNormals = true;
838 }
839 else if(normalElement->getType() == VET_FLOAT3)
840 {
841 mNormals = mMeshData->getElementData(VES_NORMAL);
842 m32BitNormals = false;
843 }
844 }
845
846 if(skinning)
847 {
848 mBoneIndices = mMeshData->getElementData(VES_BLEND_INDICES);
849 mBoneWeights = mMeshData->getElementData(VES_BLEND_WEIGHTS);
850 }
851
852 if(!perVertex)
853 mWeightedTriangles.calculate(*mMeshData);
854
855 return true;
856 }
857
858 void MeshEmissionHelper::getSequentialVertex(class Vector3& position, class Vector3& normal, UINT32& idx) const
859 {
860 idx = mNextSequentialIdx;
861 position = *(Vector3*)(mVertices + mVertexStride * idx);
862
863 if (mNormals)
864 {
865 if (m32BitNormals)
866 normal = MeshUtility::unpackNormal(mNormals + mVertexStride * idx);
867 else
868 normal = *(Vector3*)(mNormals + mVertexStride * idx);
869 }
870 else
871 normal = Vector3::UNIT_Z;
872
873 mNextSequentialIdx = (mNextSequentialIdx + 1) % mNumVertices;
874 }
875
876 void MeshEmissionHelper::getRandomVertex(const Random& random, Vector3& position, Vector3& normal,
877 UINT32& idx) const
878 {
879 idx = random.get() % mNumVertices;
880 position = *(Vector3*)(mVertices + mVertexStride * idx);
881
882 if (mNormals)
883 {
884 if (m32BitNormals)
885 normal = MeshUtility::unpackNormal(mNormals + mVertexStride * idx);
886 else
887 normal = *(Vector3*)(mNormals + mVertexStride * idx);
888 }
889 else
890 normal = Vector3::UNIT_Z;
891 }
892
893 void MeshEmissionHelper::getRandomEdge(const Random& random, std::array<Vector3, 2>& position,
894 std::array<Vector3, 2>& normal, std::array<UINT32, 2>& idx) const
895 {
896 std::array<UINT32, 3> triIndices;
897 mWeightedTriangles.getTriangle(random, triIndices);
898
899 // Pick edge
900 // Note: Longer edges should be given higher chance, but we're assuming they are all equal length for performance
901 const int32_t edge = random.getRange(0, 2);
902 switch (edge)
903 {
904 default:
905 case 0:
906 idx[0] = triIndices[0];
907 idx[1] = triIndices[1];
908 break;
909 case 1:
910 idx[0] = triIndices[1];
911 idx[1] = triIndices[2];
912 break;
913 case 2:
914 idx[0] = triIndices[2];
915 idx[1] = triIndices[0];
916 break;
917 }
918
919 position[0] = *(Vector3*)(mVertices + mVertexStride * idx[0]);
920 position[1] = *(Vector3*)(mVertices + mVertexStride * idx[1]);
921
922 if (mNormals)
923 {
924 if (m32BitNormals)
925 {
926 normal[0] = MeshUtility::unpackNormal(mNormals + mVertexStride * idx[0]);
927 normal[1] = MeshUtility::unpackNormal(mNormals + mVertexStride * idx[1]);
928 }
929 else
930 {
931 normal[0] = *(Vector3*)(mNormals + mVertexStride * idx[0]);
932 normal[1] = *(Vector3*)(mNormals + mVertexStride * idx[1]);
933 }
934 }
935 else
936 {
937 normal[0] = Vector3::UNIT_Z;
938 normal[1] = Vector3::UNIT_Z;
939 }
940 }
941
942 void MeshEmissionHelper::getRandomTriangle(const Random& random, std::array<Vector3, 3>& position,
943 std::array<Vector3, 3>& normal, std::array<UINT32, 3>& idx) const
944 {
945 mWeightedTriangles.getTriangle(random, idx);
946
947 for (uint32_t i = 0; i < 3; i++)
948 {
949 position[i] = *(Vector3*)(mVertices + mVertexStride * idx[i]);
950
951 if (mNormals)
952 {
953 if (m32BitNormals)
954 normal[i] = MeshUtility::unpackNormal(mNormals + mVertexStride * idx[i]);
955 else
956 normal[i] = *(Vector3*)(mNormals + mVertexStride * idx[i]);
957 }
958 else
959 normal[i] = Vector3::UNIT_Z;
960 }
961 }
962
963 Matrix4 MeshEmissionHelper::getBlendMatrix(const Matrix4* bones, UINT32 vertexIdx) const
964 {
965 if(bones)
966 {
967 const UINT32 boneIndices = *(UINT32*)(mBoneIndices + vertexIdx * mVertexStride);
968 const Vector4& boneWeights = *(Vector4*)(mBoneWeights + vertexIdx * mVertexStride);
969
970 return
971 bones[boneIndices & 0xFF] * boneWeights[0] +
972 bones[(boneIndices >> 8) & 0xFF] * boneWeights[1] +
973 bones[(boneIndices >> 16) & 0xFF] * boneWeights[2] +
974 bones[(boneIndices >> 24) & 0xFF] * boneWeights[3];
975 }
976 else
977 return Matrix4::IDENTITY;
978 }
979
980 ParticleEmitterStaticMeshShape::ParticleEmitterStaticMeshShape(const PARTICLE_STATIC_MESH_SHAPE_DESC& desc)
981 :mInfo(desc)
982 {
983 mIsValid = mMeshEmissionHelper.initialize(desc.mesh, desc.type == ParticleEmitterMeshType::Vertex, false);
984 }
985
986 ParticleEmitterStaticMeshShape::ParticleEmitterStaticMeshShape()
987 {
988 mIsValid = false;
989 }
990
991 void ParticleEmitterStaticMeshShape::setOptions(const PARTICLE_STATIC_MESH_SHAPE_DESC& options)
992 {
993 mInfo = options;
994 mIsValid = mMeshEmissionHelper.initialize(options.mesh, options.type == ParticleEmitterMeshType::Vertex, false);
995 }
996
997 UINT32 ParticleEmitterStaticMeshShape::_spawn(const Random& random, ParticleSet& particles, UINT32 count,
998 const ParticleSystemState& state) const
999 {
1000 if(count == 0)
1001 return particles.getParticleCount();
1002
1003 switch(mInfo.type)
1004 {
1005 case ParticleEmitterMeshType::Vertex:
1006 if(mInfo.sequential)
1007 {
1008 return spawnMultiple(particles, count, [this](UINT32 idx, Vector3& position, Vector3& normal)
1009 {
1010 UINT32 vertexIdx;
1011 mMeshEmissionHelper.getSequentialVertex(position, normal, vertexIdx);
1012 });
1013 }
1014 else
1015 {
1016 return spawnMultiple(particles, count, [this, &random](UINT32 idx, Vector3& position, Vector3& normal)
1017 {
1018 UINT32 vertexIdx;
1019 mMeshEmissionHelper.getRandomVertex(random, position, normal, vertexIdx);
1020 });
1021 }
1022 case ParticleEmitterMeshType::Edge:
1023 return spawnMultiple(particles, count, [this, &random](UINT32 idx, Vector3& position, Vector3& normal)
1024 {
1025 std::array<Vector3, 2> edgePositions, edgeNormals;
1026 std::array<UINT32, 2> edgeIndices;
1027
1028 mMeshEmissionHelper.getRandomEdge(random, edgePositions, edgeNormals, edgeIndices);
1029
1030 const float rnd = random.getUNorm();
1031 position = Math::lerp(rnd, edgePositions[0], edgePositions[1]);
1032 normal = Math::lerp(rnd, edgeNormals[0], edgeNormals[1]);
1033 });
1034 default:
1035 case ParticleEmitterMeshType::Triangle:
1036 return spawnMultiple(particles, count, [this, &random](UINT32 idx, Vector3& position, Vector3& normal)
1037 {
1038 std::array<Vector3, 3> triPositions, triNormals;
1039 std::array<UINT32, 3> triIndices;
1040
1041 mMeshEmissionHelper.getRandomTriangle(random, triPositions, triNormals, triIndices);
1042
1043 position = Vector3::ZERO;
1044 normal = Vector3::ZERO;
1045 Vector3 barycenter = random.getBarycentric();
1046
1047 for (uint32_t i = 0; i < 3; i++)
1048 {
1049 position += triPositions[i] * barycenter[i];
1050 normal += triNormals[i] * barycenter[i];
1051 }
1052 });
1053 }
1054 }
1055
1056 void ParticleEmitterStaticMeshShape::calcBounds(AABox& shape, AABox& velocity) const
1057 {
1058 if(mInfo.mesh.isLoaded(false))
1059 shape = mInfo.mesh->getProperties().getBounds().getBox();
1060 else
1061 shape = AABox::BOX_EMPTY;
1062
1063 velocity.setMin(-Vector3::ONE);
1064 velocity.setMax(Vector3::ONE);
1065 }
1066
1067 SPtr<ParticleEmitterStaticMeshShape> ParticleEmitterStaticMeshShape::create(const PARTICLE_STATIC_MESH_SHAPE_DESC& desc)
1068 {
1069 return bs_shared_ptr_new<ParticleEmitterStaticMeshShape>(desc);
1070 }
1071
1072 SPtr<ParticleEmitterStaticMeshShape> ParticleEmitterStaticMeshShape::create()
1073 {
1074 return bs_shared_ptr_new<ParticleEmitterStaticMeshShape>();
1075 }
1076
1077 RTTITypeBase* ParticleEmitterStaticMeshShape::getRTTIStatic()
1078 {
1079 return ParticleEmitterStaticMeshShapeRTTI::instance();
1080 }
1081
1082 RTTITypeBase* ParticleEmitterStaticMeshShape::getRTTI() const
1083 {
1084 return getRTTIStatic();
1085 }
1086
1087 ParticleEmitterSkinnedMeshShape::ParticleEmitterSkinnedMeshShape()
1088 {
1089 mIsValid = false;
1090 }
1091
1092 ParticleEmitterSkinnedMeshShape::ParticleEmitterSkinnedMeshShape(const PARTICLE_SKINNED_MESH_SHAPE_DESC& desc)
1093 :mInfo(desc)
1094 {
1095 HMesh mesh;
1096 if(!desc.renderable.empty())
1097 mesh = desc.renderable.getActor()->getMesh();
1098
1099 mIsValid = mMeshEmissionHelper.initialize(mesh, desc.type == ParticleEmitterMeshType::Vertex, false);
1100 }
1101
1102 void ParticleEmitterSkinnedMeshShape::setOptions(const PARTICLE_SKINNED_MESH_SHAPE_DESC& options)
1103 {
1104 mInfo = options;
1105
1106 HMesh mesh;
1107 if(!options.renderable.empty())
1108 mesh = options.renderable.getActor()->getMesh();
1109
1110 mIsValid = mMeshEmissionHelper.initialize(mesh, options.type == ParticleEmitterMeshType::Vertex, false);
1111 }
1112
1113 UINT32 ParticleEmitterSkinnedMeshShape::_spawn(const Random& random, ParticleSet& particles, UINT32 count,
1114 const ParticleSystemState& state) const
1115 {
1116 if(count == 0)
1117 return particles.getParticleCount();
1118
1119 const Matrix4* bones = nullptr;
1120
1121 if(!mInfo.renderable.empty())
1122 {
1123 const SPtr<Renderable>& renderable = mInfo.renderable.getActor();
1124 const SPtr<Animation>& animation = renderable->getAnimation();;
1125 if(animation)
1126 {
1127 const UINT64 animId = animation->_getId();
1128
1129 if(state.animData)
1130 {
1131 const auto iterFind = state.animData->infos.find(animId);
1132 if(iterFind != state.animData->infos.end())
1133 bones = &state.animData->transforms[iterFind->second.poseInfo.startIdx];
1134 }
1135 }
1136 }
1137
1138 switch(mInfo.type)
1139 {
1140 case ParticleEmitterMeshType::Vertex:
1141 if(mInfo.sequential)
1142 {
1143 return spawnMultiple(particles, count, [this, bones]
1144 (UINT32 idx, Vector3& position, Vector3& normal)
1145 {
1146 UINT32 vertexIdx;
1147 mMeshEmissionHelper.getSequentialVertex(position, normal, vertexIdx);
1148
1149 Matrix4 blendMatrix = mMeshEmissionHelper.getBlendMatrix(bones, vertexIdx);
1150 position = blendMatrix.multiplyAffine(position);
1151 normal = blendMatrix.multiplyDirection(normal);
1152 });
1153 }
1154 else
1155 {
1156 return spawnMultiple(particles, count, [this, &random, bones]
1157 (UINT32 idx, Vector3& position, Vector3& normal)
1158 {
1159 UINT32 vertexIdx;
1160 mMeshEmissionHelper.getRandomVertex(random, position, normal, vertexIdx);
1161
1162 Matrix4 blendMatrix = mMeshEmissionHelper.getBlendMatrix(bones, vertexIdx);
1163 position = blendMatrix.multiplyAffine(position);
1164 normal = blendMatrix.multiplyDirection(normal);
1165 });
1166 }
1167 case ParticleEmitterMeshType::Edge:
1168 return spawnMultiple(particles, count, [this, &random, bones]
1169 (UINT32 idx, Vector3& position, Vector3& normal)
1170 {
1171 std::array<Vector3, 2> edgePositions, edgeNormals;
1172 std::array<UINT32, 2> edgeIndices;
1173
1174 mMeshEmissionHelper.getRandomEdge(random, edgePositions, edgeNormals, edgeIndices);
1175
1176 for(uint32_t i = 0; i < 2; i++)
1177 {
1178 Matrix4 blendMatrix = mMeshEmissionHelper.getBlendMatrix(bones, edgeIndices[i]);
1179 edgePositions[i] = blendMatrix.multiplyAffine(edgePositions[i]);
1180 edgeNormals[i] = blendMatrix.multiplyAffine(edgeNormals[i]);
1181 }
1182
1183 const float rnd = random.getUNorm();
1184 position = Math::lerp(rnd, edgePositions[0], edgePositions[1]);
1185 normal = Math::lerp(rnd, edgeNormals[0], edgeNormals[1]);
1186 });
1187 default:
1188 case ParticleEmitterMeshType::Triangle:
1189 return spawnMultiple(particles, count, [this, &random, bones]
1190 (UINT32 idx, Vector3& position, Vector3& normal)
1191 {
1192 std::array<Vector3, 3> triPositions, triNormals;
1193 std::array<UINT32, 3> triIndices;
1194
1195 mMeshEmissionHelper.getRandomTriangle(random, triPositions, triNormals, triIndices);
1196
1197 position = Vector3::ZERO;
1198 normal = Vector3::ZERO;
1199 Vector3 barycenter = random.getBarycentric();
1200
1201 for(uint32_t i = 0; i < 3; i++)
1202 {
1203 Matrix4 blendMatrix = mMeshEmissionHelper.getBlendMatrix(bones, triIndices[i]);
1204 triPositions[i] = blendMatrix.multiplyAffine(triPositions[i]);
1205 triNormals[i] = blendMatrix.multiplyAffine(triNormals[i]);
1206 }
1207
1208 for (uint32_t i = 0; i < 3; i++)
1209 {
1210 position += triPositions[i] * barycenter[i];
1211 normal += triNormals[i] * barycenter[i];
1212 }
1213 });
1214 };
1215 }
1216
1217 void ParticleEmitterSkinnedMeshShape::calcBounds(AABox& shape, AABox& velocity) const
1218 {
1219 if(!mInfo.renderable.empty())
1220 {
1221 const SPtr<Renderable>& renderable = mInfo.renderable.getActor();
1222 const SPtr<Animation>& anim = renderable->getAnimation();
1223 if(anim)
1224 {
1225 // No culling, make the box infinite
1226 if(!anim->getCulling())
1227 shape = AABox::INF_BOX;
1228 else
1229 shape = anim->getBounds();
1230 }
1231 else
1232 {
1233 const HMesh& mesh = renderable->getMesh();
1234 if (mesh.isLoaded(false))
1235 shape = mesh->getProperties().getBounds().getBox();
1236 else
1237 shape = AABox::BOX_EMPTY;
1238 }
1239 }
1240 else
1241 shape = AABox::BOX_EMPTY;
1242
1243 velocity.setMin(-Vector3::ONE);
1244 velocity.setMax(Vector3::ONE);
1245 }
1246
1247 SPtr<ParticleEmitterSkinnedMeshShape> ParticleEmitterSkinnedMeshShape::create(const PARTICLE_SKINNED_MESH_SHAPE_DESC& desc)
1248 {
1249 return bs_shared_ptr_new<ParticleEmitterSkinnedMeshShape>(desc);
1250 }
1251
1252 SPtr<ParticleEmitterSkinnedMeshShape> ParticleEmitterSkinnedMeshShape::create()
1253 {
1254 return bs_shared_ptr_new<ParticleEmitterSkinnedMeshShape>();
1255 }
1256
1257 RTTITypeBase* ParticleEmitterSkinnedMeshShape::getRTTIStatic()
1258 {
1259 return ParticleEmitterSkinnedMeshShapeRTTI::instance();
1260 }
1261
1262 RTTITypeBase* ParticleEmitterSkinnedMeshShape::getRTTI() const
1263 {
1264 return getRTTIStatic();
1265 }
1266
1267 void ParticleEmitter::setEmissionBursts(Vector<ParticleBurst> bursts)
1268 {
1269 mBursts = std::move(bursts);
1270 mBurstAccumulator.resize(mBursts.size());
1271
1272 for(auto& entry : mBurstAccumulator)
1273 entry = 0.0f;
1274 }
1275
1276 void ParticleEmitter::spawn(Random& random, const ParticleSystemState& state, ParticleSet& set) const
1277 {
1278 if(!mShape || !mShape->isValid())
1279 return;
1280
1281 const float emitterT = state.nrmTimeEnd;
1282
1283 // Continous emission rate
1284 const float rate = mEmissionRate.evaluate(emitterT, random);
1285
1286 mEmitAccumulator += rate * state.timeStep;
1287 auto numContinous = (UINT32)mEmitAccumulator;
1288 mEmitAccumulator -= (float)numContinous;
1289
1290 // Bursts
1291 UINT32 numBurst = 0;
1292 const auto emitBursts = [this, &emitterT, &random](float start, float end)
1293 {
1294 constexpr float MIN_BURST_INTERVAL = 0.01f;
1295
1296 UINT32 numBurst = 0;
1297 for (UINT32 i = 0; i < (UINT32)mBursts.size(); i++)
1298 {
1299 const ParticleBurst& burst = mBursts[i];
1300
1301 const float relT0 = std::max(0.0f, start - burst.time);
1302 const float relT1 = end - burst.time;
1303 if (relT1 <= 0.0f)
1304 continue;
1305
1306 // Handle initial burst cycle
1307 if (relT0 == 0.0f)
1308 numBurst += (UINT32)burst.count.evaluate(emitterT, random);
1309
1310 // Handle remaining cycles
1311 const float dt = relT1 - relT0;
1312 const float interval = std::max(burst.interval, MIN_BURST_INTERVAL);
1313
1314 const float emitDuration = dt + mBurstAccumulator[i];
1315 const UINT32 emitCycles = Math::floorToPosInt(emitDuration / interval);
1316 mBurstAccumulator[i] = emitDuration - emitCycles * interval;
1317
1318 for (UINT32 j = 0; j < emitCycles; j++)
1319 numBurst += (UINT32)burst.count.evaluate(emitterT, random);
1320 }
1321
1322 return numBurst;
1323 };
1324
1325 // Handle loop
1326 if(state.timeEnd < state.timeStart)
1327 {
1328 numBurst += emitBursts(state.timeStart, state.length);
1329
1330 // Reset accumulator
1331 for(auto& entry : mBurstAccumulator)
1332 entry = 0.0f;
1333
1334 numBurst += emitBursts(0.0f, state.timeEnd);
1335 }
1336 else
1337 numBurst += emitBursts(state.timeStart, state.timeEnd);
1338
1339 const UINT32 startIdx = set.getParticleCount();
1340 numContinous = spawn(numContinous, random, state, set, true);
1341
1342 state.system->preSimulate(state, startIdx, numContinous, true, mEmitAccumulator);
1343 state.system->simulate(state, startIdx, numContinous, true, mEmitAccumulator);
1344
1345 spawn(numBurst, random, state, set, false);
1346 }
1347
1348 UINT32 ParticleEmitter::spawn(UINT32 count, Random& random, const ParticleSystemState& state, ParticleSet& set,
1349 bool spacing) const
1350 {
1351 const float subFrameSpacing = count > 0 ? 1.0f / count : 1.0f;
1352
1353 const UINT32 numPartices = set.getParticleCount() + count;
1354 if(!state.gpuSimulated)
1355 {
1356 if (numPartices > state.maxParticles)
1357 count = state.maxParticles - set.getParticleCount();
1358 }
1359
1360 const UINT32 firstIdx = mShape->_spawn(random, set, count, state);
1361 const UINT32 endIdx = firstIdx + count;
1362
1363 ParticleSetData& particles = set.getParticles();
1364 float* emitterT = bs_stack_alloc<float>(sizeof(float) * count);
1365
1366 if(spacing)
1367 {
1368 for (UINT32 i = 0; i < count; i++)
1369 {
1370 const float subFrameOffset = (i + mEmitAccumulator) * subFrameSpacing;
1371 emitterT[i] = state.nrmTimeStart + state.timeStep * subFrameOffset;
1372 }
1373 }
1374 else
1375 {
1376 for (UINT32 i = 0; i < count; i++)
1377 emitterT[i] = state.nrmTimeEnd;
1378 }
1379
1380 for(UINT32 i = firstIdx; i < endIdx; i++)
1381 {
1382 const float lifetime = mInitialLifetime.evaluate(emitterT[i - firstIdx], random);
1383
1384 particles.initialLifetime[i] = lifetime;
1385 particles.lifetime[i] = lifetime;
1386 }
1387
1388 for(UINT32 i = firstIdx; i < endIdx; i++)
1389 particles.velocity[i] *= mInitialSpeed.evaluate(emitterT[i - firstIdx], random);
1390
1391 if(!mUse3DSize)
1392 {
1393 for (UINT32 i = firstIdx; i < endIdx; i++)
1394 {
1395 const float size = mInitialSize.evaluate(emitterT[i - firstIdx], random);
1396
1397 // Encode UV flip in size XY as sign
1398 const float flipU = random.getUNorm() < mFlipU ? -1.0f : 1.0f;
1399 const float flipV = random.getUNorm() < mFlipV ? -1.0f : 1.0f;
1400
1401 particles.size[i] = Vector3(size * flipU, size * flipV, size);
1402 }
1403 }
1404 else
1405 {
1406 for (UINT32 i = firstIdx; i < endIdx; i++)
1407 {
1408 Vector3 size = mInitialSize3D.evaluate(emitterT[i - firstIdx], random);
1409
1410 // Encode UV flip in size XY as sign
1411 size.x *= random.getUNorm() < mFlipU ? -1.0f : 1.0f;
1412 size.y *= random.getUNorm() < mFlipV ? -1.0f : 1.0f;
1413
1414 particles.size[i] = size;
1415 }
1416 }
1417
1418 if(mRandomOffset > 0.0f)
1419 {
1420 for (UINT32 i = firstIdx; i < endIdx; i++)
1421 particles.position[i] += Vector3(random.getSNorm(), random.getSNorm(), random.getSNorm()) * mRandomOffset;
1422 }
1423
1424 if(!mUse3DRotation)
1425 {
1426 for (UINT32 i = firstIdx; i < endIdx; i++)
1427 {
1428 const float rotation = mInitialRotation.evaluate(emitterT[i - firstIdx], random);
1429 particles.rotation[i] = Vector3(rotation, 0.0f, 0.0f);
1430 }
1431 }
1432 else
1433 {
1434 for (UINT32 i = firstIdx; i < endIdx; i++)
1435 {
1436 const Vector3 rotation = mInitialRotation3D.evaluate(emitterT[i - firstIdx], random);
1437 particles.rotation[i] = rotation;
1438 }
1439 }
1440
1441 for(UINT32 i = firstIdx; i < endIdx; i++)
1442 particles.color[i] = mInitialColor.evaluate(emitterT[i - firstIdx], random);
1443
1444 for(UINT32 i = firstIdx; i < endIdx; i++)
1445 particles.seed[i] = random.get();
1446
1447 for(UINT32 i = firstIdx; i < endIdx; i++)
1448 particles.frame[i] = 0.0f;
1449
1450 // If in world-space we apply the transform here, otherwise we apply it in the rendering code
1451 if(state.worldSpace)
1452 {
1453 for (UINT32 i = firstIdx; i < endIdx; i++)
1454 particles.position[i] = state.localToWorld.multiplyAffine(particles.position[i]);
1455
1456 for (UINT32 i = firstIdx; i < endIdx; i++)
1457 particles.velocity[i] = state.localToWorld.multiplyDirection(particles.velocity[i]);
1458 }
1459
1460 bs_stack_free(emitterT);
1461
1462 return count;
1463 }
1464
1465 SPtr<ParticleEmitter> ParticleEmitter::create()
1466 {
1467 return bs_shared_ptr_new<ParticleEmitter>();
1468 }
1469
1470 RTTITypeBase* ParticleEmitter::getRTTIStatic()
1471 {
1472 return ParticleEmitterRTTI::instance();
1473 }
1474
1475 RTTITypeBase* ParticleEmitter::getRTTI() const
1476 {
1477 return getRTTIStatic();
1478 }
1479}
1480