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/BsParticleSystem.h"
4#include "Particles/BsParticleManager.h"
5#include "Particles/BsParticleEmitter.h"
6#include "Particles/BsParticleEvolver.h"
7#include "Private/Particles/BsParticleSet.h"
8#include "Private/RTTI/BsParticleSystemRTTI.h"
9#include "Allocators/BsPoolAlloc.h"
10#include "Material/BsMaterial.h"
11#include "Renderer/BsCamera.h"
12#include "Renderer/BsRenderer.h"
13#include "Physics/BsPhysics.h"
14#include "Particles/BsVectorField.h"
15#include "Mesh/BsMesh.h"
16#include "CoreThread/BsCoreObjectSync.h"
17#include "Scene/BsSceneManager.h"
18
19namespace bs
20{
21 static constexpr UINT32 INITIAL_PARTICLE_CAPACITY = 1000;
22
23 RTTITypeBase* ParticleSystemSettings::getRTTIStatic()
24 {
25 return ParticleSystemSettingsRTTI::instance();
26 }
27
28 RTTITypeBase* ParticleSystemSettings::getRTTI() const
29 {
30 return getRTTIStatic();
31 }
32
33 template <bool Core>
34 template <class P>
35 void TParticleSystemSettings<Core>::rttiEnumFields(P p)
36 {
37 p(gpuSimulation);
38 p(simulationSpace);
39 p(orientation);
40 p(orientationPlaneNormal);
41 p(orientationLockY);
42 p(duration);
43 p(isLooping);
44 p(sortMode);
45 p(material);
46 p(useAutomaticBounds);
47 p(customBounds);
48 p(renderMode);
49 p(mesh);
50 }
51
52 template<bool Core>
53 template<class P>
54 void TParticleVectorFieldSettings<Core>::rttiEnumFields(P p)
55 {
56 p(intensity);
57 p(tightness);
58 p(scale);
59 p(offset);
60 p(rotation);
61 p(rotationRate);
62 p(tilingX);
63 p(tilingY);
64 p(tilingZ);
65 p(vectorField);
66 }
67
68 RTTITypeBase* ParticleVectorFieldSettings::getRTTIStatic()
69 {
70 return ParticleVectorFieldSettingsRTTI::instance();
71 }
72
73 RTTITypeBase* ParticleVectorFieldSettings::getRTTI() const
74 {
75 return getRTTIStatic();
76 }
77
78 template<class P>
79 void ParticleDepthCollisionSettings::rttiEnumFields(P p)
80 {
81 p(enabled);
82 p(restitution);
83 p(dampening);
84 p(radiusScale);
85 }
86
87 RTTITypeBase* ParticleDepthCollisionSettings::getRTTIStatic()
88 {
89 return ParticleDepthCollisionSettingsRTTI::instance();
90 }
91
92 RTTITypeBase* ParticleDepthCollisionSettings::getRTTI() const
93 {
94 return getRTTIStatic();
95 }
96
97 template<bool Core>
98 template<class P>
99 void TParticleGpuSimulationSettings<Core>::rttiEnumFields(P p)
100 {
101 p(colorOverLifetime);
102 p(sizeScaleOverLifetime);
103 p(acceleration);
104 p(drag);
105 p(depthCollision);
106 p(vectorField);
107 };
108
109 RTTITypeBase* ParticleGpuSimulationSettings::getRTTIStatic()
110 {
111 return ParticleGpuSimulationSettingsRTTI::instance();
112 }
113
114 RTTITypeBase* ParticleGpuSimulationSettings::getRTTI() const
115 {
116 return getRTTIStatic();
117 }
118
119 ParticleSystem::ParticleSystem()
120 {
121 mId = ParticleManager::instance().registerParticleSystem(this);
122 mSeed = rand();
123
124 auto emitter = bs_shared_ptr_new<ParticleEmitter>();
125
126 PARTICLE_SPHERE_SHAPE_DESC desc;
127 desc.radius = 0.05f;
128
129 emitter->setShape(ParticleEmitterSphereShape::create(desc));
130
131 mEmitters = { emitter };
132 }
133
134 ParticleSystem::~ParticleSystem()
135 {
136 ParticleManager::instance().unregisterParticleSystem(this);
137
138 if(mParticleSet)
139 bs_delete(mParticleSet);
140 }
141
142 void ParticleSystem::setSettings(const ParticleSystemSettings& settings)
143 {
144 if(settings.useAutomaticSeed != mSettings.useAutomaticSeed)
145 {
146 if(settings.useAutomaticSeed)
147 mSeed = rand();
148 else
149 mSeed = settings.manualSeed;
150
151 mRandom.setSeed(mSeed);
152 }
153 else
154 {
155 if(!settings.useAutomaticSeed)
156 {
157 mSeed = settings.manualSeed;
158 mRandom.setSeed(mSeed);
159 }
160 }
161
162 if(settings.maxParticles < mSettings.maxParticles)
163 mParticleSet->clear(settings.maxParticles);
164
165 mSettings = settings;
166 _markCoreDirty();
167 markDependenciesDirty();
168 }
169
170 void ParticleSystem::setGpuSimulationSettings(const ParticleGpuSimulationSettings& settings)
171 {
172 mGpuSimulationSettings = settings;
173 _markCoreDirty();
174 }
175
176 void ParticleSystem::setLayer(UINT64 layer)
177 {
178 const bool isPow2 = layer && !((layer - 1) & layer);
179
180 if (!isPow2)
181 {
182 LOGWRN("Invalid layer provided. Only one layer bit may be set. Ignoring.");
183 return;
184 }
185
186 mLayer = layer;
187 _markCoreDirty();
188 }
189
190 void ParticleSystem::setEmitters(const Vector<SPtr<ParticleEmitter>>& emitters)
191 {
192 mEmitters = emitters;
193 _markCoreDirty();
194 }
195
196 void ParticleSystem::setEvolvers(const Vector<SPtr<ParticleEvolver>>& evolvers)
197 {
198 mEvolvers = evolvers;
199
200 std::sort(mEvolvers.begin(), mEvolvers.end(),
201 [](const SPtr<ParticleEvolver>& a, const SPtr<ParticleEvolver>& b)
202 {
203 INT32 priorityA = a ? a->getProperties().priority : 0;
204 INT32 priorityB = b ? b->getProperties().priority : 0;
205
206 if (priorityA == priorityB)
207 return a > b; // Use address, at this point it doesn't matter, but sorting requires us to differentiate
208 else
209 return priorityA > priorityB;
210 });
211
212 _markCoreDirty();
213 }
214
215 void ParticleSystem::play()
216 {
217 if(mState == State::Playing)
218 return;
219
220 if(mState == State::Uninitialized)
221 {
222 UINT32 particleCapacity = std::min(mSettings.maxParticles, INITIAL_PARTICLE_CAPACITY);
223 mParticleSet = bs_new<ParticleSet>(particleCapacity);
224 }
225
226 mState = State::Playing;
227 mTime = 0.0f;
228 mRandom.setSeed(mSeed);
229 }
230
231 void ParticleSystem::pause()
232 {
233 if(mState == State::Playing)
234 mState = State::Paused;
235 }
236
237 void ParticleSystem::stop()
238 {
239 if(mState != State::Playing && mState != State::Paused)
240 return;
241
242 mState = State::Stopped;
243 mParticleSet->clear();
244 }
245
246 void ParticleSystem::_simulate(float timeDelta, const EvaluatedAnimationData* animData)
247 {
248 if(mState != State::Playing)
249 return;
250
251 float timeStep;
252 const float newTime = _advanceTime(mTime, timeDelta, mSettings.duration, mSettings.isLooping, timeStep);
253
254 if(timeStep < 0.00001f)
255 return;
256
257 // Generate per-frame state
258 ParticleSystemState state;
259 state.timeStart = mTime;
260 state.timeEnd = newTime;
261 state.nrmTimeStart = state.timeStart / mSettings.duration;
262 state.nrmTimeEnd = state.timeEnd / mSettings.duration;
263 state.length = mSettings.duration;
264 state.timeStep = timeStep;
265 state.maxParticles = mSettings.maxParticles;
266 state.worldSpace = mSettings.simulationSpace == ParticleSimulationSpace::World;
267 state.gpuSimulated = mSettings.gpuSimulation;
268 state.localToWorld = mTransform.getMatrix();
269 state.worldToLocal = state.localToWorld.inverseAffine();
270 state.system = this;
271 state.scene = (mScene && mScene->isActive()) ? mScene.get() : gSceneManager().getMainScene().get();
272 state.animData = animData;
273
274 // For GPU simulation we only care about newly spawned particles, so clear old ones
275 if(mSettings.gpuSimulation)
276 mParticleSet->clear();
277
278 // Spawn new particles
279 for(auto& emitter : mEmitters)
280 {
281 if(emitter)
282 emitter->spawn(mRandom, state, *mParticleSet);
283 }
284
285 // Simulate if running on CPU, otherwise just pass the spawned particles off to the core thread
286 if(!mSettings.gpuSimulation)
287 {
288 const UINT32 numParticles = mParticleSet->getParticleCount();
289
290 preSimulate(state, 0, numParticles, false, 0.0f);
291 simulate(state, 0, numParticles, false, 0.0f);
292 postSimulate(state, 0, numParticles, false, 0.0f);
293 }
294
295 mTime = newTime;
296 }
297
298 void ParticleSystem::preSimulate(const ParticleSystemState& state, UINT32 startIdx, UINT32 count, bool spacing,
299 float spacingOffset)
300 {
301 const ParticleSetData& particles = mParticleSet->getParticles();
302 const float subFrameSpacing = (spacing && count > 0) ? 1.0f / count : 1.0f;
303 const UINT32 endIdx = startIdx + count;
304
305 // Decrement lifetime
306 for (UINT32 i = startIdx; i < endIdx; i++)
307 {
308 float timeStep = state.timeStep;
309 if(spacing)
310 {
311 // Note: We're calculating this in a few places during a single frame. Store it and re-use?
312 const UINT32 localIdx = i - startIdx;
313 const float subFrameOffset = ((float)localIdx + spacingOffset) * subFrameSpacing;
314 timeStep *= subFrameOffset;
315 }
316
317 particles.lifetime[i] -= timeStep;
318 }
319
320 // Kill expired particles
321 UINT32 numParticles = count;
322 for (UINT32 i = 0; i < numParticles;)
323 {
324 const UINT32 particleIdx = startIdx + i;
325 if (particles.lifetime[particleIdx] <= 0.0f)
326 {
327 mParticleSet->freeParticle(particleIdx);
328 numParticles--;
329 }
330 else
331 i++;
332 }
333
334 // Remember old positions
335 for (UINT32 i = startIdx; i < endIdx; i++)
336 particles.prevPosition[i] = particles.position[i];
337
338 // Evolve pre-simulation
339 for(auto& evolver : mEvolvers)
340 {
341 if(!evolver)
342 continue;
343
344 const ParticleEvolverProperties& props = evolver->getProperties();
345 if (props.priority < 0)
346 break;
347
348 evolver->evolve(mRandom, state, *mParticleSet, startIdx, count, spacing, spacingOffset);
349 }
350 }
351
352 void ParticleSystem::simulate(const ParticleSystemState& state, UINT32 startIdx, UINT32 count, bool spacing,
353 float spacingOffset)
354 {
355 const ParticleSetData& particles = mParticleSet->getParticles();
356 const float subFrameSpacing = (spacing && count > 0) ? 1.0f / count : 1.0f;
357 const UINT32 endIdx = startIdx + count;
358
359 for (UINT32 i = startIdx; i < endIdx; i++)
360 {
361 float timeStep = state.timeStep;
362 if(spacing)
363 {
364 const UINT32 localIdx = i - startIdx;
365 const float subFrameOffset = ((float)localIdx + spacingOffset) * subFrameSpacing;
366 timeStep *= subFrameOffset;
367 }
368
369 particles.position[i] += particles.velocity[i] * timeStep;
370 }
371 }
372
373 void ParticleSystem::postSimulate(const ParticleSystemState& state, UINT32 startIdx, UINT32 count, bool spacing,
374 float spacingOffset)
375 {
376 // Evolve post-simulation
377 for(auto& evolver : mEvolvers)
378 {
379 if(!evolver)
380 continue;
381
382 const ParticleEvolverProperties& props = evolver->getProperties();
383 if(props.priority >= 0)
384 continue;
385
386 evolver->evolve(mRandom, state, *mParticleSet, startIdx, count, spacing, spacingOffset);
387 }
388 }
389
390 AABox ParticleSystem::_calculateBounds() const
391 {
392 // TODO - If evolvers are deterministic (as well as their properties), calculate the maximinal bounds in an
393 // analytical way
394
395 const UINT32 particleCount = mParticleSet->getParticleCount();
396 if(particleCount == 0)
397 return AABox::BOX_EMPTY;
398
399 const ParticleSetData& particles = mParticleSet->getParticles();
400 AABox bounds(Vector3::INF, -Vector3::INF);
401 for(UINT32 i = 0; i < particleCount; i++)
402 bounds.merge(particles.position[i]);
403
404 return bounds;
405 }
406
407 float ParticleSystem::_advanceTime(float time, float timeDelta, float duration, bool loop, float& timeStep)
408 {
409 timeStep = timeDelta;
410 float newTime = time + timeStep;
411 if(newTime >= duration)
412 {
413 if(loop)
414 newTime = fmod(newTime, duration);
415 else
416 {
417 timeStep = time - duration;
418 newTime = duration;
419 }
420 }
421
422 return newTime;
423 }
424
425 SPtr<ct::ParticleSystem> ParticleSystem::getCore() const
426 {
427 return std::static_pointer_cast<ct::ParticleSystem>(mCoreSpecific);
428 }
429
430 SPtr<ct::CoreObject> ParticleSystem::createCore() const
431 {
432 ct::ParticleSystem* rawPtr = new (bs_alloc<ct::ParticleSystem>()) ct::ParticleSystem(mId);
433 SPtr<ct::ParticleSystem> ptr = bs_shared_ptr<ct::ParticleSystem>(rawPtr);
434 ptr->_setThisPtr(ptr);
435
436 return ptr;
437 }
438
439 void ParticleSystem::_markCoreDirty(ActorDirtyFlag flag)
440 {
441 markCoreDirty((UINT32)flag);
442 }
443
444 CoreSyncData ParticleSystem::syncToCore(FrameAlloc* allocator)
445 {
446 UINT32 size = rttiGetElemSize(getCoreDirtyFlags());
447 size += coreSyncGetElemSize((SceneActor&)*this);
448 size += coreSyncGetElemSize(mSettings);
449 size += coreSyncGetElemSize(mGpuSimulationSettings);
450 size += rttiGetElemSize(mLayer);
451
452 UINT8* data = allocator->alloc(size);
453 char* dataPtr = (char*)data;
454 dataPtr = rttiWriteElem(getCoreDirtyFlags(), dataPtr);
455 dataPtr = coreSyncWriteElem((SceneActor&)*this, dataPtr);
456 dataPtr = coreSyncWriteElem(mSettings, dataPtr);
457 dataPtr = coreSyncWriteElem(mGpuSimulationSettings, dataPtr);
458 dataPtr = rttiWriteElem(mLayer, dataPtr);
459
460 return CoreSyncData(data, size);
461 }
462
463 void ParticleSystem::getCoreDependencies(Vector<CoreObject*>& dependencies)
464 {
465 if (mSettings.mesh.isLoaded())
466 dependencies.push_back(mSettings.mesh.get());
467
468 if (mSettings.material.isLoaded())
469 dependencies.push_back(mSettings.material.get());
470 }
471
472 SPtr<ParticleSystem> ParticleSystem::create()
473 {
474 SPtr<ParticleSystem> ptr = createEmpty();
475 ptr->initialize();
476
477 return ptr;
478 }
479
480 SPtr<ParticleSystem> ParticleSystem::createEmpty()
481 {
482 ParticleSystem* rawPtr = new (bs_alloc<ParticleSystem>()) ParticleSystem();
483 SPtr<ParticleSystem> ptr = bs_core_ptr<ParticleSystem>(rawPtr);
484 ptr->_setThisPtr(ptr);
485
486 return ptr;
487 }
488
489 RTTITypeBase* ParticleSystem::getRTTIStatic()
490 {
491 return ParticleSystemRTTI::instance();
492 }
493
494 RTTITypeBase* ParticleSystem::getRTTI() const
495 {
496 return ParticleSystem::getRTTIStatic();
497 }
498
499 namespace ct
500 {
501 ParticleSystem::~ParticleSystem()
502 {
503 if(mActive)
504 gRenderer()->notifyParticleSystemRemoved(this);
505 }
506
507 void ParticleSystem::initialize()
508 {
509 gRenderer()->notifyParticleSystemAdded(this);
510 }
511
512 void ParticleSystem::setLayer(UINT64 layer)
513 {
514 const bool isPow2 = layer && !((layer - 1) & layer);
515
516 if (!isPow2)
517 {
518 LOGWRN("Invalid layer provided. Only one layer bit may be set. Ignoring.");
519 return;
520 }
521
522 mLayer = layer;
523 _markCoreDirty();
524 }
525
526 void ParticleSystem::syncToCore(const CoreSyncData& data)
527 {
528 char* dataPtr = (char*)data.getBuffer();
529
530 UINT32 dirtyFlags = 0;
531 const bool oldIsActive = mActive;
532
533 dataPtr = rttiReadElem(dirtyFlags, dataPtr);
534 dataPtr = coreSyncReadElem((SceneActor&)*this, dataPtr);
535 dataPtr = coreSyncReadElem(mSettings, dataPtr);
536 dataPtr = coreSyncReadElem(mGpuSimulationSettings, dataPtr);
537 dataPtr = rttiReadElem(mLayer, dataPtr);
538
539 constexpr UINT32 updateEverythingFlag = (UINT32)ActorDirtyFlag::Everything
540 | (UINT32)ActorDirtyFlag::Active
541 | (UINT32)ActorDirtyFlag::Dependency;
542
543 if ((dirtyFlags & updateEverythingFlag) != 0)
544 {
545 if (oldIsActive != mActive)
546 {
547 if (mActive)
548 gRenderer()->notifyParticleSystemAdded(this);
549 else
550 gRenderer()->notifyParticleSystemRemoved(this);
551 }
552 else
553 {
554 if(mActive)
555 gRenderer()->notifyParticleSystemUpdated(this, false);
556 }
557 }
558 else if ((dirtyFlags & ((UINT32)ActorDirtyFlag::Mobility | (UINT32)ActorDirtyFlag::Transform)) != 0)
559 gRenderer()->notifyParticleSystemUpdated(this, true);
560 }
561 }
562}
563