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