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