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 | |
15 | namespace 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 | |