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