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 "Particles/BsParticleEvolver.h" |
4 | #include "Private/Particles/BsParticleSet.h" |
5 | #include "Private/RTTI/BsParticleSystemRTTI.h" |
6 | #include "Particles/BsVectorField.h" |
7 | #include "Image/BsSpriteTexture.h" |
8 | #include "BsParticleSystem.h" |
9 | #include "Material/BsMaterial.h" |
10 | #include "Math/BsRay.h" |
11 | #include "Physics/BsPhysics.h" |
12 | #include "Physics/BsCollider.h" |
13 | #include "Math/BsLineSegment3.h" |
14 | #include "Material/BsShader.h" |
15 | #include "Scene/BsSceneObject.h" |
16 | #include "Scene/BsSceneManager.h" |
17 | |
18 | namespace bs |
19 | { |
20 | // Arbitrary random numbers to add variation to different random particle properties, since we use just a single |
21 | // seed value per particle |
22 | static constexpr UINT32 PARTICLE_ROW_VARIATION = 0x1e8b2f4a; |
23 | static constexpr UINT32 PARTICLE_ORBIT_VELOCITY = 0x24c00a5b; |
24 | static constexpr UINT32 PARTICLE_ORBIT_RADIAL = 0x35978d21; |
25 | static constexpr UINT32 PARTICLE_LINEAR_VELOCITY = 0x0a299430; |
26 | static constexpr UINT32 PARTICLE_FORCE = 0x1b618144; |
27 | static constexpr UINT32 PARTICLE_COLOR = 0x378578b2; |
28 | static constexpr UINT32 PARTICLE_SIZE = 0x91088409; |
29 | static constexpr UINT32 PARTICLE_ROTATION = 0x4680eaa4; |
30 | |
31 | /** Helper method that applies a transform to either a point or a direction. */ |
32 | template<bool dir> |
33 | Vector3 applyTransform(const Matrix4& tfrm, const Vector3& input) |
34 | { |
35 | return tfrm.multiplyAffine(input); |
36 | } |
37 | |
38 | template<> |
39 | Vector3 applyTransform<true>(const Matrix4& tfrm, const Vector3& input) |
40 | { |
41 | return tfrm.multiplyDirection(input); |
42 | } |
43 | |
44 | /** |
45 | * Evaluates a 3D vector distribution and transforms the output into the same space as the particle system. |
46 | * @p inWorldSpace parameter controls whether the values in the distribution are assumed to be in world or local space. |
47 | * |
48 | * @tparam dir If true the evaluated vector is assumed to be a direction, otherwise a point. |
49 | */ |
50 | template<bool dir = false> |
51 | Vector3 evaluateTransformed(const Vector3Distribution& distribution, const ParticleSystemState& state, float t, |
52 | const Random& factor, bool inWorldSpace) |
53 | { |
54 | const Vector3 output = distribution.evaluate(t, factor); |
55 | |
56 | if(state.worldSpace == inWorldSpace) |
57 | return output; |
58 | |
59 | if(state.worldSpace) |
60 | return applyTransform<dir>(state.localToWorld, output); |
61 | else |
62 | return applyTransform<dir>(state.worldToLocal, output); |
63 | } |
64 | |
65 | ParticleTextureAnimation::ParticleTextureAnimation(const PARTICLE_TEXTURE_ANIMATION_DESC& desc) |
66 | :mDesc(desc) |
67 | { } |
68 | |
69 | void ParticleTextureAnimation::evolve(Random& random, const ParticleSystemState& state, ParticleSet& set, |
70 | UINT32 startIdx, UINT32 count, bool spacing, float spacingOffset) const |
71 | { |
72 | const UINT32 endIdx = startIdx + count; |
73 | ParticleSetData& particles = set.getParticles(); |
74 | |
75 | SpriteTexture* texture = nullptr; |
76 | const HMaterial& material = state.system->getSettings().material; |
77 | if (material.isLoaded(false)) |
78 | { |
79 | const HShader& shader = material->getShader(); |
80 | if(shader->hasTextureParam("gTexture" )) |
81 | { |
82 | const HSpriteTexture& spriteTex = material->getSpriteTexture("gTexture" ); |
83 | if (spriteTex.isLoaded(true)) |
84 | texture = spriteTex.get(); |
85 | } |
86 | |
87 | if(shader->hasTextureParam("gAlbedoTex" )) |
88 | { |
89 | const HSpriteTexture& spriteTex = material->getSpriteTexture("gAlbedoTex" ); |
90 | if (spriteTex.isLoaded(true)) |
91 | texture = spriteTex.get(); |
92 | } |
93 | } |
94 | |
95 | bool hasValidAnimation = texture != nullptr; |
96 | if(hasValidAnimation) |
97 | { |
98 | const SpriteSheetGridAnimation& gridAnim = texture->getAnimation(); |
99 | hasValidAnimation = gridAnim.numRows > 0 && gridAnim.numColumns > 0 && gridAnim.count > 0; |
100 | } |
101 | |
102 | if(!hasValidAnimation) |
103 | { |
104 | for (UINT32 i = startIdx; i < endIdx; i++) |
105 | particles.frame[i] = 0.0f; |
106 | |
107 | return; |
108 | } |
109 | |
110 | const SpriteSheetGridAnimation& gridAnim = texture->getAnimation(); |
111 | |
112 | for (UINT32 i = startIdx; i < endIdx; i++) |
113 | { |
114 | UINT32 frameOffset; |
115 | UINT32 numFrames; |
116 | if (mDesc.randomizeRow) |
117 | { |
118 | const UINT32 rowSeed = particles.seed[i] + PARTICLE_ROW_VARIATION; |
119 | const UINT32 row = Random(rowSeed).getRange(0, gridAnim.numRows); |
120 | |
121 | frameOffset = row * gridAnim.numColumns; |
122 | numFrames = gridAnim.numColumns; |
123 | } |
124 | else |
125 | { |
126 | frameOffset = 0; |
127 | numFrames = gridAnim.count; |
128 | } |
129 | |
130 | float particleT = (particles.initialLifetime[i] - particles.lifetime[i]) / particles.initialLifetime[i]; |
131 | particleT = Math::repeat(mDesc.numCycles * particleT, 1.0f); |
132 | |
133 | const float frame = particleT * (numFrames - 1); |
134 | particles.frame[i] = frameOffset + Math::clamp(frame, 0.0f, (float)(numFrames - 1)); |
135 | } |
136 | } |
137 | |
138 | SPtr<ParticleTextureAnimation> ParticleTextureAnimation::create(const PARTICLE_TEXTURE_ANIMATION_DESC& desc) |
139 | { |
140 | return bs_shared_ptr_new<ParticleTextureAnimation>(desc); |
141 | } |
142 | |
143 | SPtr<ParticleTextureAnimation> ParticleTextureAnimation::create() |
144 | { |
145 | return bs_shared_ptr_new<ParticleTextureAnimation>(); |
146 | } |
147 | |
148 | RTTITypeBase* ParticleTextureAnimation::getRTTIStatic() |
149 | { |
150 | return ParticleTextureAnimationRTTI::instance(); |
151 | } |
152 | |
153 | RTTITypeBase* ParticleTextureAnimation::getRTTI() const |
154 | { |
155 | return getRTTIStatic(); |
156 | } |
157 | |
158 | ParticleOrbit::ParticleOrbit(const PARTICLE_ORBIT_DESC& desc) |
159 | :mDesc(desc) |
160 | { } |
161 | |
162 | void ParticleOrbit::evolve(Random& random, const ParticleSystemState& state, ParticleSet& set, |
163 | UINT32 startIdx, UINT32 count, bool spacing, float spacingOffset) const |
164 | { |
165 | const UINT32 endIdx = startIdx + count; |
166 | ParticleSetData& particles = set.getParticles(); |
167 | |
168 | const Vector3 center = evaluateTransformed(mDesc.center, state, state.nrmTimeEnd, random, mDesc.worldSpace); |
169 | const float subFrameSpacing = (spacing && count > 0) ? 1.0f / count : 1.0f; |
170 | |
171 | for (UINT32 i = startIdx; i < endIdx; i++) |
172 | { |
173 | const float particleT = (particles.initialLifetime[i] - particles.lifetime[i]) / particles.initialLifetime[i]; |
174 | |
175 | float timeStep = state.timeStep; |
176 | if(spacing) |
177 | { |
178 | const UINT32 localIdx = i - startIdx; |
179 | const float subFrameOffset = ((float)localIdx + spacingOffset) * subFrameSpacing; |
180 | timeStep *= subFrameOffset; |
181 | } |
182 | |
183 | const UINT32 velocitySeed = particles.seed[i] + PARTICLE_ORBIT_VELOCITY; |
184 | Vector3 orbitVelocity = evaluateTransformed<true>(mDesc.velocity, state, particleT, Random(velocitySeed), |
185 | mDesc.worldSpace); |
186 | orbitVelocity *= Math::TWO_PI; |
187 | |
188 | |
189 | orbitVelocity *= timeStep; |
190 | |
191 | const Matrix3 rotation(Radian(orbitVelocity.x), Radian(orbitVelocity.y), Radian(orbitVelocity.z)); |
192 | |
193 | const Vector3 point = particles.position[i] - center; |
194 | const Vector3 newPoint = rotation.multiply(point); |
195 | |
196 | Vector3 velocity = newPoint - point; |
197 | |
198 | const UINT32 radialSeed = particles.seed[i] + PARTICLE_ORBIT_RADIAL; |
199 | const float radial = mDesc.radial.evaluate(particleT, Random(radialSeed).getUNorm()); |
200 | if(radial != 0.0f) |
201 | velocity += Vector3::normalize(point) * radial * timeStep; |
202 | |
203 | particles.position[i] += velocity; |
204 | } |
205 | } |
206 | |
207 | SPtr<ParticleOrbit> ParticleOrbit::create(const PARTICLE_ORBIT_DESC& desc) |
208 | { |
209 | return bs_shared_ptr_new<ParticleOrbit>(desc); |
210 | } |
211 | |
212 | SPtr<ParticleOrbit> ParticleOrbit::create() |
213 | { |
214 | return bs_shared_ptr_new<ParticleOrbit>(); |
215 | } |
216 | |
217 | RTTITypeBase* ParticleOrbit::getRTTIStatic() |
218 | { |
219 | return ParticleOrbitRTTI::instance(); |
220 | } |
221 | |
222 | RTTITypeBase* ParticleOrbit::getRTTI() const |
223 | { |
224 | return getRTTIStatic(); |
225 | } |
226 | |
227 | ParticleVelocity::ParticleVelocity(const PARTICLE_VELOCITY_DESC& desc) |
228 | :mDesc(desc) |
229 | { } |
230 | |
231 | void ParticleVelocity::evolve(Random& random, const ParticleSystemState& state, ParticleSet& set, |
232 | UINT32 startIdx, UINT32 count, bool spacing, float spacingOffset) const |
233 | { |
234 | const UINT32 endIdx = startIdx + count; |
235 | ParticleSetData& particles = set.getParticles(); |
236 | |
237 | const float subFrameSpacing = (spacing && count > 0) ? 1.0f / count : 1.0f; |
238 | for (UINT32 i = startIdx; i < endIdx; i++) |
239 | { |
240 | const float particleT = (particles.initialLifetime[i] - particles.lifetime[i]) / particles.initialLifetime[i]; |
241 | |
242 | float timeStep = state.timeStep; |
243 | if(spacing) |
244 | { |
245 | const UINT32 localIdx = i - startIdx; |
246 | const float subFrameOffset = ((float)localIdx + spacingOffset) * subFrameSpacing; |
247 | timeStep *= subFrameOffset; |
248 | } |
249 | |
250 | const UINT32 velocitySeed = particles.seed[i] + PARTICLE_LINEAR_VELOCITY; |
251 | const Vector3 velocity = evaluateTransformed<true>(mDesc.velocity, state, particleT, Random(velocitySeed), |
252 | mDesc.worldSpace) * timeStep; |
253 | |
254 | particles.position[i] += velocity; |
255 | } |
256 | } |
257 | |
258 | SPtr<ParticleVelocity> ParticleVelocity::create(const PARTICLE_VELOCITY_DESC& desc) |
259 | { |
260 | return bs_shared_ptr_new<ParticleVelocity>(desc); |
261 | } |
262 | |
263 | SPtr<ParticleVelocity> ParticleVelocity::create() |
264 | { |
265 | return bs_shared_ptr_new<ParticleVelocity>(); |
266 | } |
267 | |
268 | RTTITypeBase* ParticleVelocity::getRTTIStatic() |
269 | { |
270 | return ParticleVelocityRTTI::instance(); |
271 | } |
272 | |
273 | RTTITypeBase* ParticleVelocity::getRTTI() const |
274 | { |
275 | return getRTTIStatic(); |
276 | } |
277 | |
278 | ParticleForce::ParticleForce(const PARTICLE_FORCE_DESC& desc) |
279 | :mDesc(desc) |
280 | { } |
281 | |
282 | void ParticleForce::evolve(Random& random, const ParticleSystemState& state, ParticleSet& set, |
283 | UINT32 startIdx, UINT32 count, bool spacing, float spacingOffset) const |
284 | { |
285 | const UINT32 endIdx = startIdx + count; |
286 | ParticleSetData& particles = set.getParticles(); |
287 | |
288 | const float subFrameSpacing = (spacing && count > 0) ? 1.0f / count : 1.0f; |
289 | for (UINT32 i = startIdx; i < endIdx; i++) |
290 | { |
291 | const float particleT = (particles.initialLifetime[i] - particles.lifetime[i]) / particles.initialLifetime[i]; |
292 | |
293 | float timeStep = state.timeStep; |
294 | if(spacing) |
295 | { |
296 | const UINT32 localIdx = i - startIdx; |
297 | const float subFrameOffset = ((float)localIdx + spacingOffset) * subFrameSpacing; |
298 | timeStep *= subFrameOffset; |
299 | } |
300 | |
301 | const UINT32 forceSeed = particles.seed[i] + PARTICLE_FORCE; |
302 | const Vector3 force = evaluateTransformed<true>(mDesc.force, state, particleT, Random(forceSeed), |
303 | mDesc.worldSpace) * timeStep; |
304 | |
305 | particles.velocity[i] += force * timeStep; |
306 | } |
307 | } |
308 | |
309 | SPtr<ParticleForce> ParticleForce::create(const PARTICLE_FORCE_DESC& desc) |
310 | { |
311 | return bs_shared_ptr_new<ParticleForce>(desc); |
312 | } |
313 | |
314 | SPtr<ParticleForce> ParticleForce::create() |
315 | { |
316 | return bs_shared_ptr_new<ParticleForce>(); |
317 | } |
318 | |
319 | RTTITypeBase* ParticleForce::getRTTIStatic() |
320 | { |
321 | return ParticleForceRTTI::instance(); |
322 | } |
323 | |
324 | RTTITypeBase* ParticleForce::getRTTI() const |
325 | { |
326 | return getRTTIStatic(); |
327 | } |
328 | |
329 | ParticleGravity::ParticleGravity(const PARTICLE_GRAVITY_DESC& desc) |
330 | :mDesc(desc) |
331 | { } |
332 | |
333 | void ParticleGravity::evolve(Random& random, const ParticleSystemState& state, ParticleSet& set, |
334 | UINT32 startIdx, UINT32 count, bool spacing, float spacingOffset) const |
335 | { |
336 | Vector3 gravity = state.scene->getPhysicsScene()->getGravity() * mDesc.scale; |
337 | |
338 | if (!state.worldSpace) |
339 | gravity = state.worldToLocal.multiplyDirection(gravity); |
340 | |
341 | const UINT32 endIdx = startIdx + count; |
342 | ParticleSetData& particles = set.getParticles(); |
343 | |
344 | const float subFrameSpacing = (spacing && count > 0) ? 1.0f / count : 1.0f; |
345 | for (UINT32 i = startIdx; i < endIdx; i++) |
346 | { |
347 | float timeStep = state.timeStep; |
348 | if(spacing) |
349 | { |
350 | const UINT32 localIdx = i - startIdx; |
351 | const float subFrameOffset = ((float)localIdx + spacingOffset) * subFrameSpacing; |
352 | timeStep *= subFrameOffset; |
353 | } |
354 | |
355 | particles.velocity[i] += gravity * timeStep; |
356 | } |
357 | } |
358 | |
359 | SPtr<ParticleGravity> ParticleGravity::create(const PARTICLE_GRAVITY_DESC& desc) |
360 | { |
361 | return bs_shared_ptr_new<ParticleGravity>(desc); |
362 | } |
363 | |
364 | SPtr<ParticleGravity> ParticleGravity::create() |
365 | { |
366 | return bs_shared_ptr_new<ParticleGravity>(); |
367 | } |
368 | |
369 | RTTITypeBase* ParticleGravity::getRTTIStatic() |
370 | { |
371 | return ParticleGravityRTTI::instance(); |
372 | } |
373 | |
374 | RTTITypeBase* ParticleGravity::getRTTI() const |
375 | { |
376 | return getRTTIStatic(); |
377 | } |
378 | |
379 | ParticleColor::ParticleColor(const PARTICLE_COLOR_DESC& desc) |
380 | :mDesc(desc) |
381 | { } |
382 | |
383 | void ParticleColor::evolve(Random& random, const ParticleSystemState& state, ParticleSet& set, |
384 | UINT32 startIdx, UINT32 count, bool spacing, float spacingOffset) const |
385 | { |
386 | const UINT32 endIdx = startIdx + count; |
387 | ParticleSetData& particles = set.getParticles(); |
388 | |
389 | for (UINT32 i = startIdx; i < endIdx; i++) |
390 | { |
391 | const UINT32 colorSeed = particles.seed[i] + PARTICLE_COLOR; |
392 | const float particleT = (particles.initialLifetime[i] - particles.lifetime[i]) / particles.initialLifetime[i]; |
393 | |
394 | particles.color[i] = mDesc.color.evaluate(particleT, Random(colorSeed)); |
395 | } |
396 | } |
397 | |
398 | SPtr<ParticleColor> ParticleColor::create(const PARTICLE_COLOR_DESC& desc) |
399 | { |
400 | return bs_shared_ptr_new<ParticleColor>(desc); |
401 | } |
402 | |
403 | SPtr<ParticleColor> ParticleColor::create() |
404 | { |
405 | return bs_shared_ptr_new<ParticleColor>(); |
406 | } |
407 | |
408 | RTTITypeBase* ParticleColor::getRTTIStatic() |
409 | { |
410 | return ParticleColorRTTI::instance(); |
411 | } |
412 | |
413 | RTTITypeBase* ParticleColor::getRTTI() const |
414 | { |
415 | return getRTTIStatic(); |
416 | } |
417 | |
418 | ParticleSize::ParticleSize(const PARTICLE_SIZE_DESC& desc) |
419 | :mDesc(desc) |
420 | { } |
421 | |
422 | void ParticleSize::evolve(Random& random, const ParticleSystemState& state, ParticleSet& set, |
423 | UINT32 startIdx, UINT32 count, bool spacing, float spacingOffset) const |
424 | { |
425 | const UINT32 endIdx = startIdx + count; |
426 | ParticleSetData& particles = set.getParticles(); |
427 | |
428 | if(!mDesc.use3DSize) |
429 | { |
430 | for (UINT32 i = startIdx; i < endIdx; i++) |
431 | { |
432 | const UINT32 sizeSeed = particles.seed[i] + PARTICLE_SIZE; |
433 | const float particleT = (particles.initialLifetime[i] - particles.lifetime[i]) / particles.initialLifetime[i]; |
434 | |
435 | const float size = mDesc.size.evaluate(particleT, Random(sizeSeed)); |
436 | particles.size[i] = Vector3(size, size, size); |
437 | } |
438 | } |
439 | else |
440 | { |
441 | for (UINT32 i = startIdx; i < endIdx; i++) |
442 | { |
443 | const UINT32 sizeSeed = particles.seed[i] + PARTICLE_SIZE; |
444 | const float particleT = (particles.initialLifetime[i] - particles.lifetime[i]) / particles.initialLifetime[i]; |
445 | |
446 | particles.size[i] = mDesc.size3D.evaluate(particleT, Random(sizeSeed)); |
447 | } |
448 | } |
449 | } |
450 | |
451 | SPtr<ParticleSize> ParticleSize::create(const PARTICLE_SIZE_DESC& desc) |
452 | { |
453 | return bs_shared_ptr_new<ParticleSize>(desc); |
454 | } |
455 | |
456 | SPtr<ParticleSize> ParticleSize::create() |
457 | { |
458 | return bs_shared_ptr_new<ParticleSize>(); |
459 | } |
460 | |
461 | RTTITypeBase* ParticleSize::getRTTIStatic() |
462 | { |
463 | return ParticleSizeRTTI::instance(); |
464 | } |
465 | |
466 | RTTITypeBase* ParticleSize::getRTTI() const |
467 | { |
468 | return getRTTIStatic(); |
469 | } |
470 | |
471 | ParticleRotation::ParticleRotation(const PARTICLE_ROTATION_DESC& desc) |
472 | :mDesc(desc) |
473 | { } |
474 | |
475 | void ParticleRotation::evolve(Random& random, const ParticleSystemState& state, ParticleSet& set, |
476 | UINT32 startIdx, UINT32 count, bool spacing, float spacingOffset) const |
477 | { |
478 | const UINT32 endIdx = startIdx + count; |
479 | ParticleSetData& particles = set.getParticles(); |
480 | |
481 | if(!mDesc.use3DRotation) |
482 | { |
483 | for (UINT32 i = startIdx; i < endIdx; i++) |
484 | { |
485 | const UINT32 rotationSeed = particles.seed[i] + PARTICLE_ROTATION; |
486 | const float particleT = (particles.initialLifetime[i] - particles.lifetime[i]) / particles.initialLifetime[i]; |
487 | |
488 | const float rotation = mDesc.rotation.evaluate(particleT, Random(rotationSeed)); |
489 | particles.rotation[i] = Vector3(rotation, 0.0f, 0.0f); |
490 | } |
491 | } |
492 | else |
493 | { |
494 | for (UINT32 i = startIdx; i < endIdx; i++) |
495 | { |
496 | const UINT32 rotationSeed = particles.seed[i] + PARTICLE_ROTATION; |
497 | const float particleT = (particles.initialLifetime[i] - particles.lifetime[i]) / particles.initialLifetime[i]; |
498 | |
499 | particles.rotation[i] = mDesc.rotation3D.evaluate(particleT, Random(rotationSeed)); |
500 | } |
501 | } |
502 | } |
503 | |
504 | SPtr<ParticleRotation> ParticleRotation::create(const PARTICLE_ROTATION_DESC& desc) |
505 | { |
506 | return bs_shared_ptr_new<ParticleRotation>(desc); |
507 | } |
508 | |
509 | SPtr<ParticleRotation> ParticleRotation::create() |
510 | { |
511 | return bs_shared_ptr_new<ParticleRotation>(); |
512 | } |
513 | |
514 | RTTITypeBase* ParticleRotation::getRTTIStatic() |
515 | { |
516 | return ParticleRotationRTTI::instance(); |
517 | } |
518 | |
519 | RTTITypeBase* ParticleRotation::getRTTI() const |
520 | { |
521 | return getRTTIStatic(); |
522 | } |
523 | |
524 | /** Information about a particle collision. */ |
525 | struct ParticleHitInfo |
526 | { |
527 | Vector3 position; |
528 | Vector3 normal; |
529 | UINT32 idx; |
530 | }; |
531 | |
532 | /** Calculates the new position and velocity after a particle was detected to be colliding. */ |
533 | void calcCollisionResponse(Vector3& position, Vector3& velocity, const ParticleHitInfo& hitInfo, |
534 | const PARTICLE_COLLISIONS_DESC& desc) |
535 | { |
536 | Vector3 diff = position - hitInfo.position; |
537 | |
538 | // Reflect & dampen |
539 | const float dampenFactor = 1.0f - desc.dampening; |
540 | |
541 | Vector3 reflectedPos = diff.reflect(hitInfo.normal) * dampenFactor; |
542 | Vector3 reflectedVel = velocity.reflect(hitInfo.normal) * dampenFactor; |
543 | |
544 | // Bounce |
545 | const float restitutionFactor = 1.0f - desc.restitution; |
546 | |
547 | reflectedPos -= hitInfo.normal * reflectedPos.dot(hitInfo.normal) * restitutionFactor; |
548 | reflectedVel -= hitInfo.normal * reflectedVel.dot(hitInfo.normal) * restitutionFactor; |
549 | |
550 | position = hitInfo.position + reflectedPos; |
551 | velocity = reflectedVel; |
552 | } |
553 | |
554 | UINT32 groupRaycast(const PhysicsScene& physicsScene, LineSegment3* segments, ParticleHitInfo* hits, UINT32 numRays, |
555 | UINT64 layer) |
556 | { |
557 | if(numRays == 0) |
558 | return 0; |
559 | |
560 | // Calculate bounds of all rays |
561 | AABox groupBounds = AABox::INF_BOX; |
562 | for(UINT32 i = 0; i < numRays; i++) |
563 | { |
564 | groupBounds.merge(segments[i].start); |
565 | groupBounds.merge(segments[i].end); |
566 | } |
567 | |
568 | Vector<Collider*> hitColliders = physicsScene._boxOverlap(groupBounds, Quaternion::IDENTITY, layer); |
569 | if(hitColliders.empty()) |
570 | return 0; |
571 | |
572 | UINT32 numHits = 0; |
573 | for(UINT32 i = 0; i < numRays; i++) |
574 | { |
575 | float nearestHit = std::numeric_limits<float>::max(); |
576 | ParticleHitInfo hitInfo; |
577 | hitInfo.idx = i; |
578 | |
579 | Vector3 diff = segments[i].end - segments[i].start; |
580 | const float length = diff.length(); |
581 | |
582 | if(Math::approxEquals(length, 0.0f)) |
583 | continue; |
584 | |
585 | Ray ray; |
586 | ray.setOrigin(segments[i].start); |
587 | ray.setDirection(diff / length); |
588 | |
589 | for(auto& collider : hitColliders) |
590 | { |
591 | PhysicsQueryHit queryHit; |
592 | if(collider->rayCast(ray, queryHit, length)) |
593 | { |
594 | if(queryHit.distance < nearestHit) |
595 | { |
596 | nearestHit = queryHit.distance; |
597 | |
598 | hitInfo.position = queryHit.point; |
599 | hitInfo.normal = queryHit.normal; |
600 | } |
601 | } |
602 | } |
603 | |
604 | if(nearestHit != std::numeric_limits<float>::max()) |
605 | hits[numHits++] = hitInfo; |
606 | } |
607 | |
608 | return numHits; |
609 | } |
610 | |
611 | ParticleCollisions::ParticleCollisions(const PARTICLE_COLLISIONS_DESC& desc) |
612 | :mDesc(desc) |
613 | { |
614 | mDesc.restitution = std::max(mDesc.restitution, 0.0f); |
615 | mDesc.dampening = Math::clamp01(mDesc.dampening); |
616 | mDesc.lifetimeLoss = Math::clamp01(mDesc.lifetimeLoss); |
617 | mDesc.radius = std::max(mDesc.radius, 0.0f); |
618 | } |
619 | |
620 | void ParticleCollisions::evolve(Random& random, const ParticleSystemState& state, ParticleSet& set, |
621 | UINT32 startIdx, UINT32 count, bool spacing, float spacingOffset) const |
622 | { |
623 | const UINT32 endIdx = startIdx + count; |
624 | ParticleSetData& particles = set.getParticles(); |
625 | |
626 | if(mDesc.mode == ParticleCollisionMode::Plane) |
627 | { |
628 | UINT32 numPlanes[2] = { 0, 0 }; |
629 | Plane* planes[2]; |
630 | |
631 | // Extract planes from scene objects |
632 | Plane* objPlanes = nullptr; |
633 | |
634 | if(!mCollisionPlaneObjects.empty()) |
635 | { |
636 | objPlanes = bs_stack_alloc<Plane>((UINT32)mCollisionPlaneObjects.size()); |
637 | for (auto& entry : mCollisionPlaneObjects) |
638 | { |
639 | if(entry.isDestroyed()) |
640 | continue; |
641 | |
642 | const Transform& tfrm = entry->getTransform(); |
643 | Plane plane = Plane(tfrm.getForward(), tfrm.getPosition()); |
644 | |
645 | if(!state.worldSpace) |
646 | plane = state.worldToLocal.multiplyAffine(plane); |
647 | |
648 | objPlanes[numPlanes[0]++] = plane; |
649 | } |
650 | } |
651 | |
652 | planes[0] = objPlanes; |
653 | |
654 | // If particles are in world space, we can just use collision planes as is |
655 | Plane* localPlanes = nullptr; |
656 | if (state.worldSpace) |
657 | planes[1] = (Plane*)mCollisionPlanes.data(); |
658 | else |
659 | { |
660 | const Matrix4& worldToLocal = state.worldToLocal; |
661 | localPlanes = bs_stack_alloc<Plane>((UINT32)mCollisionPlanes.size()); |
662 | |
663 | for (UINT32 i = 0; i < (UINT32)mCollisionPlanes.size(); i++) |
664 | localPlanes[i] = worldToLocal.multiplyAffine(mCollisionPlanes[i]); |
665 | |
666 | planes[1] = localPlanes; |
667 | } |
668 | |
669 | numPlanes[1] = (UINT32)mCollisionPlanes.size(); |
670 | |
671 | for(UINT32 i = startIdx; i < endIdx; i++) |
672 | { |
673 | Vector3& position = particles.position[i]; |
674 | Vector3& velocity = particles.velocity[i]; |
675 | |
676 | for(UINT32 j = 0; j < bs_size(planes); j++) |
677 | { |
678 | for (UINT32 k = 0; k < numPlanes[j]; k++) |
679 | { |
680 | const Plane& plane = planes[j][k]; |
681 | |
682 | const float dist = plane.getDistance(position); |
683 | if (dist > mDesc.radius) |
684 | continue; |
685 | |
686 | const float distToTravelAlongNormal = plane.normal.dot(velocity); |
687 | |
688 | // Ignore movement parallel to the plane |
689 | if (Math::approxEquals(distToTravelAlongNormal, 0.0f)) |
690 | continue; |
691 | |
692 | const float distFromBoundary = mDesc.radius - dist; |
693 | const float rayT = distFromBoundary / distToTravelAlongNormal; |
694 | |
695 | ParticleHitInfo hitInfo; |
696 | hitInfo.normal = plane.normal; |
697 | hitInfo.position = position + velocity * rayT; |
698 | hitInfo.idx = i; |
699 | |
700 | calcCollisionResponse(position, velocity, hitInfo, mDesc); |
701 | particles.lifetime[i] -= mDesc.lifetimeLoss * particles.initialLifetime[i]; |
702 | |
703 | break; |
704 | } |
705 | } |
706 | } |
707 | |
708 | if(objPlanes) |
709 | bs_stack_free(objPlanes); |
710 | |
711 | if(localPlanes) |
712 | bs_stack_free(localPlanes); |
713 | } |
714 | else |
715 | { |
716 | const UINT32 rayStart = startIdx; |
717 | const UINT32 rayEnd = endIdx; |
718 | const UINT32 numRays = rayEnd - rayStart; |
719 | |
720 | const auto segments = bs_stack_alloc<LineSegment3>(numRays); |
721 | const auto hits = bs_stack_alloc<ParticleHitInfo>(numRays); |
722 | |
723 | for(UINT32 i = 0; i < numRays; i++) |
724 | { |
725 | const Vector3& prevPosition = particles.prevPosition[rayStart + i]; |
726 | const Vector3& position = particles.position[rayStart + i]; |
727 | |
728 | segments[i] = LineSegment3(prevPosition, position); |
729 | } |
730 | |
731 | if(!state.worldSpace) |
732 | { |
733 | for (UINT32 i = 0; i < numRays; i++) |
734 | { |
735 | segments[i].start = state.localToWorld.multiplyAffine(segments[i].start); |
736 | segments[i].end = state.localToWorld.multiplyAffine(segments[i].end); |
737 | } |
738 | } |
739 | |
740 | const PhysicsScene& physicsScene = *state.scene->getPhysicsScene(); |
741 | const UINT32 numHits = groupRaycast(physicsScene, segments, hits, numRays, mDesc.layer); |
742 | |
743 | if(!state.worldSpace) |
744 | { |
745 | for (UINT32 i = 0; i < numHits; i++) |
746 | { |
747 | hits[i].position = state.worldToLocal.multiplyAffine(hits[i].position); |
748 | hits[i].normal = state.worldToLocal.multiplyDirection(hits[i].normal); |
749 | } |
750 | } |
751 | |
752 | for(UINT32 i = 0; i < numHits; i++) |
753 | { |
754 | ParticleHitInfo& hitInfo = hits[i]; |
755 | const UINT32 particleIdx = rayStart + hitInfo.idx; |
756 | |
757 | Vector3& position = particles.position[particleIdx]; |
758 | Vector3& velocity = particles.velocity[particleIdx]; |
759 | |
760 | calcCollisionResponse(position, velocity, hitInfo, mDesc); |
761 | |
762 | particles.lifetime[particleIdx] -= mDesc.lifetimeLoss * particles.initialLifetime[particleIdx]; |
763 | } |
764 | |
765 | bs_stack_free(hits); |
766 | bs_stack_free(segments); |
767 | } |
768 | } |
769 | |
770 | SPtr<ParticleCollisions> ParticleCollisions::create(const PARTICLE_COLLISIONS_DESC& desc) |
771 | { |
772 | return bs_shared_ptr_new<ParticleCollisions>(desc); |
773 | } |
774 | |
775 | SPtr<ParticleCollisions> ParticleCollisions::create() |
776 | { |
777 | return bs_shared_ptr_new<ParticleCollisions>(); |
778 | } |
779 | |
780 | RTTITypeBase* ParticleCollisions::getRTTIStatic() |
781 | { |
782 | return ParticleCollisionsRTTI::instance(); |
783 | } |
784 | |
785 | RTTITypeBase* ParticleCollisions::getRTTI() const |
786 | { |
787 | return getRTTIStatic(); |
788 | } |
789 | } |
790 | |