summaryrefslogtreecommitdiff
path: root/Runtime/Graphics/ParticleSystem/ParticleSystem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Graphics/ParticleSystem/ParticleSystem.cpp')
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystem.cpp2110
1 files changed, 2110 insertions, 0 deletions
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystem.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystem.cpp
new file mode 100644
index 0000000..47e952f
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystem.cpp
@@ -0,0 +1,2110 @@
+#include "UnityPrefix.h"
+#include "ParticleSystem.h"
+#include "ParticleSystemRenderer.h"
+#include "ParticleSystemCurves.h"
+#include "ParticleSystemUtils.h"
+#include "Modules/ParticleSystemModule.h"
+#include "Modules/InitialModule.h"
+#include "Modules/ShapeModule.h"
+#include "Modules/EmissionModule.h"
+#include "Modules/SizeModule.h"
+#include "Modules/RotationModule.h"
+#include "Modules/ColorModule.h"
+#include "Modules/UVModule.h"
+#include "Modules/VelocityModule.h"
+#include "Modules/ForceModule.h"
+#include "Modules/ExternalForcesModule.h"
+#include "Modules/ClampVelocityModule.h"
+#include "Modules/SizeByVelocityModule.h"
+#include "Modules/RotationByVelocityModule.h"
+#include "Modules/ColorByVelocityModule.h"
+#include "Modules/CollisionModule.h"
+#include "Modules/SubModule.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Math/FloatConversion.h"
+#include "Runtime/Math/Random/rand.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/Math/AnimationCurve.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Threads/JobScheduler.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Serialize/TransferFunctions/TransferNameConversions.h"
+#include "Runtime/Shaders/GraphicsCaps.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Threads/Thread.h"
+#include "Runtime/Misc/GameObjectUtility.h"
+#include "Runtime/Misc/ResourceManager.h"
+#include "Runtime/Shaders/Material.h"
+#include "Runtime/Misc/QualitySettings.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Core/Callbacks/GlobalCallbacks.h"
+
+#if UNITY_EDITOR
+#include "Editor/Src/ParticleSystem/ParticleSystemEditor.h"
+#include "Runtime/Mono/MonoManager.h"
+#endif
+
+// P1:
+// . Calling ps.Play() in Start(). Does it actually stsrt playing when game starts? No.
+
+// P2:
+// Automatic Culling:
+// . Improve procedural AABB
+// . Gravity: make it work with transforming into local space the same way velocity and force modules work
+// . Get tighter bounds by forces counteracting eachother instead of always expanding
+// . For procedural systems, no gain in calculating AABB every frame. Just do it when something changes.
+// . Solving for roots analytically
+// . http://en.wikipedia.org/wiki/Cubic_function
+// . http://en.wikipedia.org/wiki/Quartic_function
+// . http://en.wikipedia.org/wiki/Quintic_function
+
+// External Forces:
+// . Currently not using turbulence. Start using that?
+
+// Other:
+// . Batching: Make it work with meshes as well
+// . !!! Add runtime tests for playback code. Playing, stopping, simulating etc. Make sure it's 100%.
+
+struct ParticleSystemManager
+{
+ ParticleSystemManager()
+ :needSync(false)
+ {
+ activeEmitters.reserve(32);
+ }
+
+ dynamic_array<ParticleSystem*> activeEmitters;
+
+#if ENABLE_MULTITHREADED_PARTICLES
+ JobScheduler::JobGroupID worldCollisionJobGroup; // group for particle systems performing world collisions, PhysX is currently not threadsafe so deleting objects in LateUpdate could cause crashes as that could overlap with collision testing
+ JobScheduler::JobGroupID jobGroup; // for any other collisions
+#endif
+ bool needSync;
+};
+
+Material* GetDefaultParticleMaterial ()
+{
+ #if WEBPLUG
+ if (!IS_CONTENT_NEWER_OR_SAME(kUnityVersion_OldWebResourcesAdded))
+ return GetBuiltinOldWebResource<Material> ("Default-Particle.mat");
+ #endif
+
+ #if UNITY_EDITOR
+ return GetBuiltinExtraResource<Material> ("Default-Particle.mat");
+ #endif
+
+ // If someone happens to create a new particle system component at runtime,
+ // just don't assign any material. The default one might not even be included
+ // into the build.
+ return NULL;
+}
+
+ParticleSystemManager gParticleSystemManager;
+
+PROFILER_INFORMATION(gParticleSystemProfile, "ParticleSystem.Update", kProfilerParticles)
+PROFILER_INFORMATION(gParticleSystemPrewarm, "ParticleSystem.Prewarm", kProfilerParticles)
+PROFILER_INFORMATION(gParticleSystemWait, "ParticleSystem.WaitForUpdateThreads", kProfilerParticles)
+PROFILER_INFORMATION(gParticleSystemJobProfile, "ParticleSystem.UpdateJob", kProfilerParticles)
+
+PROFILER_INFORMATION(gParticleSystemUpdateCollisions, "ParticleSystem.UpdateCollisions", kProfilerParticles)
+
+
+#define MAX_TIME_STEP (0.03f)
+static float GetTimeStep(float dt, bool fixedTimeStep)
+{
+ if(fixedTimeStep)
+ return GetTimeManager().GetFixedDeltaTime();
+ else if(dt > MAX_TIME_STEP)
+ return dt / Ceilf(dt / MAX_TIME_STEP);
+ else
+ return dt;
+}
+
+static void ApplyStartDelay (float& delayT, float& accumulatedDt)
+{
+ if(delayT > 0.0f)
+ {
+ delayT -= accumulatedDt;
+ accumulatedDt = max(-delayT, 0.0f);
+ delayT = max(delayT, 0.0f);
+ }
+ DebugAssert(delayT >= 0.0f);
+}
+
+static inline void CheckParticleConsistency(ParticleSystemState& state, ParticleSystemParticle& particle)
+{
+ particle.lifetime = min(particle.lifetime, particle.startLifetime);
+ state.maxSize = max(state.maxSize, particle.size);
+}
+
+ParticleSystem::ParticleSystem (MemLabelId label, ObjectCreationMode mode)
+: Super(label, mode)
+, m_RayBudget(0)
+, m_EmittersIndex (-1)
+#if UNITY_EDITOR
+, m_EditorListNode ( this )
+#endif
+{
+ m_State = new ParticleSystemState ();
+ m_ReadOnlyState = new ParticleSystemReadOnlyState ();
+
+ m_SizeModule = new SizeModule ();
+ m_RotationModule = new RotationModule ();
+ m_ColorModule = new ColorModule ();
+ m_UVModule = new UVModule ();
+ m_VelocityModule = new VelocityModule ();
+ m_ForceModule = new ForceModule ();
+ m_ExternalForcesModule = new ExternalForcesModule ();
+ m_ClampVelocityModule = new ClampVelocityModule ();
+ m_SizeBySpeedModule = new SizeBySpeedModule ();
+ m_RotationBySpeedModule = new RotationBySpeedModule ();
+ m_ColorBySpeedModule = new ColorBySpeedModule ();
+ m_CollisionModule = new CollisionModule ();
+ m_SubModule = new SubModule ();
+
+#if UNITY_EDITOR
+ ParticleSystemEditor::SetupDefaultParticleSystem(*this);
+ m_EditorRandomSeedIndex = 0;
+#endif
+}
+
+ParticleSystem::~ParticleSystem ()
+{
+ delete m_State;
+ delete m_ReadOnlyState;
+
+ delete m_SizeModule;
+ delete m_RotationModule;
+ delete m_ColorModule;
+ delete m_UVModule;
+ delete m_VelocityModule;
+ delete m_ForceModule;
+ delete m_ExternalForcesModule;
+ delete m_ClampVelocityModule;
+ delete m_SizeBySpeedModule;
+ delete m_RotationBySpeedModule;
+ delete m_ColorBySpeedModule;
+ delete m_CollisionModule;
+ delete m_SubModule;
+}
+
+void ParticleSystem::InitializeClass ()
+{
+#if UNITY_EDITOR
+ // This is only necessary to avoid pain during development (Pre 3.5) - can be removed later...
+ RegisterAllowTypeNameConversion ("MinMaxColorCurve", "MinMaxColor");
+ // Only needed due to 3.5 beta content
+ RegisterAllowNameConversion ("MinMaxCurve", "maxScalar", "scalar");
+ RegisterAllowNameConversion ("InitialModule", "lifetime", "startLifetime");
+ RegisterAllowNameConversion ("InitialModule", "speed", "startSpeed");
+ RegisterAllowNameConversion ("InitialModule", "color", "startColor");
+ RegisterAllowNameConversion ("InitialModule", "size", "startSize");
+ RegisterAllowNameConversion ("InitialModule", "rotation", "startRotation");
+ RegisterAllowNameConversion ("ShapeModule", "m_Radius", "radius");
+ RegisterAllowNameConversion ("ShapeModule", "m_Angle", "angle");
+ RegisterAllowNameConversion ("ShapeModule", "m_BoxX", "boxX");
+ RegisterAllowNameConversion ("ShapeModule", "m_BoxY", "boxY");
+ RegisterAllowNameConversion ("ShapeModule", "m_BoxZ", "boxZ");
+
+ REGISTER_MESSAGE_VOID (ParticleSystem, kTransformChanged, TransformChanged);
+#endif
+
+ REGISTER_MESSAGE_VOID(ParticleSystem, kDidDeleteMesh, DidDeleteMesh);
+ REGISTER_MESSAGE_VOID(ParticleSystem, kDidModifyMesh, DidModifyMesh);
+}
+
+void ParticleSystem::SetRayBudget (int rayBudget)
+{
+ m_RayBudget = rayBudget;
+};
+
+int ParticleSystem::GetRayBudget() const
+{
+ return m_RayBudget;
+};
+
+void ParticleSystem::DidModifyMesh ()
+{
+ m_ShapeModule.DidModifyMeshData();
+}
+
+void ParticleSystem::DidDeleteMesh ()
+{
+ m_ShapeModule.DidDeleteMesh(this);
+}
+
+void ParticleSystem::Cull()
+{
+ if(!IsWorldPlaying())
+ return;
+
+ m_State->culled = true;
+
+ Clear(false);
+ m_State->cullTime = GetCurTime ();
+ RemoveFromManager();
+}
+
+void ParticleSystem::RendererBecameVisible()
+{
+ if(m_State->culled)
+ {
+ m_State->culled = false;
+ if(!m_State->stopEmitting && IsPlaying() && IsWorldPlaying() && CheckSupportsProcedural (*this))
+ {
+ double timeDiff = GetCurTime() - m_State->cullTime;
+ Simulate(m_State->numLoops * m_ReadOnlyState->lengthInSec + m_State->t + timeDiff, true);
+ Play();
+ }
+ }
+}
+
+void ParticleSystem::RendererBecameInvisible()
+{
+ if(!m_State->culled && CheckSupportsProcedural(*this))
+ Cull();
+}
+
+void ParticleSystem::AddToManager()
+{
+ if(m_EmittersIndex >= 0)
+ return;
+ size_t index = gParticleSystemManager.activeEmitters.size();
+ gParticleSystemManager.activeEmitters.push_back(this);
+ m_EmittersIndex = index;
+}
+
+void ParticleSystem::RemoveFromManager()
+{
+ if(m_EmittersIndex < 0)
+ return;
+ const int index = m_EmittersIndex;
+ gParticleSystemManager.activeEmitters[index]->m_EmittersIndex = -1;
+ gParticleSystemManager.activeEmitters[index] = gParticleSystemManager.activeEmitters.back();
+ if(gParticleSystemManager.activeEmitters[index] != this) // corner case
+ gParticleSystemManager.activeEmitters[index]->m_EmittersIndex = index;
+ gParticleSystemManager.activeEmitters.resize_uninitialized(gParticleSystemManager.activeEmitters.size() - 1);
+}
+
+void ParticleSystem::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad (awakeMode);
+ m_InitialModule.AwakeFromLoad(this, *m_ReadOnlyState);
+ m_ShapeModule.AwakeFromLoad(this, *m_ReadOnlyState);
+
+ if (!IsActive () || (kDefaultAwakeFromLoad == awakeMode))
+ return;
+
+ m_State->localToWorld = GetComponent (Transform).GetLocalToWorldMatrixNoScale();
+ Matrix4x4f::Invert_General3D(m_State->localToWorld, m_State->worldToLocal);
+ m_State->maxSize = 0.0f;
+ m_State->invalidateProcedural = false;
+
+ if (IsWorldPlaying () && m_ReadOnlyState->playOnAwake)
+ Play ();
+
+ // Does this even happen?
+ if(GetParticleCount() || IsPlaying())
+ AddToManager();
+}
+
+void ParticleSystem::Deactivate (DeactivateOperation operation)
+{
+ Super::Deactivate(operation);
+ SyncJobs();
+ RemoveFromManager();
+}
+
+void ParticleSystem::SyncJobs()
+{
+ if(gParticleSystemManager.needSync)
+ {
+#if ENABLE_MULTITHREADED_PARTICLES
+ PROFILER_BEGIN(gParticleSystemWait, NULL);
+ JobScheduler& scheduler = GetJobScheduler();
+ scheduler.WaitForGroup (gParticleSystemManager.jobGroup);
+ PROFILER_END;
+
+#endif
+ const float deltaTimeEpsilon = 0.0001f;
+ float deltaTime = GetDeltaTime();
+ if(deltaTime < deltaTimeEpsilon)
+ return;
+
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size(); ++i)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ ParticleSystemState& state = *system.m_State;
+ system.Update2 (system, *system.m_ReadOnlyState, state, false);
+ }
+
+ gParticleSystemManager.needSync = false;
+ }
+}
+
+#if ENABLE_MULTITHREADED_PARTICLES
+void* ParticleSystem::UpdateFunction (void* rawData)
+{
+ Assert (rawData);
+ ParticleSystem* system = reinterpret_cast<ParticleSystem*>(rawData);
+ ParticleSystem::Update1 (*system, system->GetParticles((int)ParticleSystem::kParticleBuffer0), system->GetThreadScratchPad().deltaTime, false, false, system->m_RayBudget);
+ return NULL;
+}
+#endif
+
+#define MAX_NUM_SUB_EMIT_CMDS (1024)
+void ParticleSystem::Update(ParticleSystem& system, float deltaTime, bool fixedTimeStep, bool useProcedural, int rayBudget)
+{
+ system.m_State->recordSubEmits = false;
+ Update0 (system, *system.m_ReadOnlyState, *system.m_State, deltaTime, fixedTimeStep);
+ Update1 (system, system.GetParticles((int)ParticleSystem::kParticleBuffer0), deltaTime, fixedTimeStep, useProcedural, rayBudget);
+ Update2 (system, *system.m_ReadOnlyState, *system.m_State, fixedTimeStep);
+#if UNITY_EDITOR
+ ParticleSystemEditor::PerformInterpolationStep(&system);
+#endif
+}
+
+size_t ParticleSystem::EmitFromModules (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemEmissionState& emissionState, size_t& numContinuous, const Vector3f velocity, float fromT, float toT, float dt)
+{
+ if(system.m_EmissionModule.GetEnabled())
+ return EmitFromData(emissionState, numContinuous, system.m_EmissionModule.GetEmissionDataRef(), velocity, fromT, toT, dt, roState.lengthInSec);
+ return 0;
+}
+
+size_t ParticleSystem::EmitFromData (ParticleSystemEmissionState& emissionState, size_t& numContinuous, const ParticleSystemEmissionData& emissionData, const Vector3f velocity, float fromT, float toT, float dt, float length)
+{
+ size_t amountOfParticlesToEmit = 0;
+ EmissionModule::Emit(emissionState, amountOfParticlesToEmit, numContinuous, emissionData, velocity, fromT, toT, dt, length);
+ return amountOfParticlesToEmit;
+}
+
+size_t ParticleSystem::LimitParticleCount(size_t requestSize) const
+{
+ const size_t maxNumParticles = m_InitialModule.GetMaxNumParticles();
+ return min<size_t>(requestSize, maxNumParticles);
+}
+
+size_t ParticleSystem::AddNewParticles(ParticleSystemParticles& particles, size_t newParticles) const
+{
+ const size_t fromIndex = particles.array_size();
+ const size_t newSize = LimitParticleCount(fromIndex + newParticles);
+ particles.array_resize(newSize);
+ return min(fromIndex, newSize);
+}
+
+void ParticleSystem::SimulateParticles (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, float dt)
+{
+ size_t particleCount = ps.array_size();
+ for (size_t q = fromIndex; q < particleCount; )
+ {
+ ps.lifetime[q] -= dt;
+
+#if UNITY_EDITOR
+ if(ParticleSystemEditor::GetIsExtraInterpolationStep())
+ {
+ ps.lifetime[q] = max(ps.lifetime[q], 0.0f);
+ }
+ else
+#endif
+
+ if (ps.lifetime[q] < 0)
+ {
+ KillParticle(roState, state, ps, q, particleCount);
+ continue;
+ }
+ ++q;
+ }
+ ps.array_resize(particleCount);
+
+ for (size_t q = fromIndex; q < particleCount; ++q)
+ ps.position[q] += (ps.velocity[q] + ps.animatedVelocity[q]) * dt;
+
+ if(ps.usesRotationalSpeed)
+ for (size_t q = fromIndex; q < particleCount; ++q)
+ ps.rotation[q] += ps.rotationalSpeed[q] * dt;
+}
+
+// Copy staging particles into real particle buffer
+void ParticleSystem::AddStagingBuffer(ParticleSystem& system)
+{
+ if(0 == system.m_ParticlesStaging.array_size())
+ return;
+
+ bool needsAxisOfRotation = system.m_Particles[kParticleBuffer0].usesAxisOfRotation;
+ bool needsEmitAccumulator = system.m_Particles[kParticleBuffer0].numEmitAccumulators > 0;
+
+ ASSERT_RUNNING_ON_MAIN_THREAD;
+ const int numParticles = system.m_Particles[kParticleBuffer0].array_size();
+ const int numStaging = system.m_ParticlesStaging.array_size();
+ system.m_Particles->array_resize(numParticles + numStaging);
+ system.m_Particles->array_merge_preallocated(system.m_ParticlesStaging, numParticles, needsAxisOfRotation, needsEmitAccumulator);
+ system.m_ParticlesStaging.array_resize(0);
+}
+
+void ParticleSystem::Emit(ParticleSystem& system, const SubEmitterEmitCommand& command, ParticleSystemEmitMode emitMode)
+{
+ int amountOfParticlesToEmit = command.particlesToEmit;
+ if (amountOfParticlesToEmit > 0)
+ {
+ ParticleSystemState& state = *system.m_State;
+ const ParticleSystemReadOnlyState& roState = *system.m_ReadOnlyState;
+
+ const int numContinuous = command.particlesToEmitContinuous;
+ const float deltaTime = command.deltaTime;
+ float parentT = command.parentT;
+ Vector3f position = command.position;
+ Vector3f initialVelocity = command.velocity;
+
+ Matrix3x3f rotMat;
+ Vector3f normalizedVelocity = NormalizeSafe(initialVelocity);
+ float angle = Abs(Dot(normalizedVelocity, Vector3f::zAxis));
+ Vector3f up = Lerp(Vector3f::zAxis, Vector3f::yAxis, angle);
+ if (!LookRotationToMatrix(normalizedVelocity, up, &rotMat))
+ rotMat.SetIdentity();
+
+ Matrix4x4f parentParticleMatrix = rotMat;
+ parentParticleMatrix.SetPosition(position);
+
+ // Transform into local space of sub emitter
+ Matrix4x4f concatMatrix;
+ if(roState.useLocalSpace)
+ MultiplyMatrices3x4(state.worldToLocal, parentParticleMatrix, concatMatrix);
+ else
+ concatMatrix = parentParticleMatrix;
+
+ if(roState.useLocalSpace)
+ initialVelocity = state.worldToLocal.MultiplyVector3(initialVelocity);
+
+ DebugAssert(state.GetIsSubEmitter());
+ DebugAssert(state.stopEmitting);
+
+ const float commandAliveTime = command.timeAlive;
+ const float timeStep = GetTimeStep(commandAliveTime, true);
+
+ // @TODO: Perform culling: if max lifetime < timeAlive, then just skip emit
+
+ // Perform sub emitter loop
+ if(roState.looping)
+ parentT = fmodf(parentT, roState.lengthInSec);
+
+ ParticleSystemParticles& particles = (kParticleSystemEMDirect == emitMode) ? system.m_Particles[kParticleBuffer0] : system.m_ParticlesStaging;
+
+ size_t fromIndex = system.AddNewParticles(particles, amountOfParticlesToEmit);
+ StartModules (system, roState, state, command.emissionState, initialVelocity, concatMatrix, particles, fromIndex, deltaTime, parentT, numContinuous, 0.0f);
+
+ // Make sure particles get updated
+ if(0 == fromIndex)
+ system.KeepUpdating();
+
+ // Update incremental
+ if(timeStep > 0.0001f)
+ {
+ float accumulatedDt = commandAliveTime;
+ while (accumulatedDt >= timeStep)
+ {
+ accumulatedDt -= timeStep;
+ UpdateModulesIncremental(system, roState, state, particles, fromIndex, timeStep);
+ }
+ }
+ }
+}
+
+
+void ParticleSystem::PlaybackSubEmitterCommandBuffer(const ParticleSystem& parent, ParticleSystemState& state, bool fixedTimeStep)
+{
+ ParticleSystem* subEmittersBirth[kParticleSystemMaxSubBirth];
+ ParticleSystem* subEmittersCollision[kParticleSystemMaxSubCollision];
+ ParticleSystem* subEmittersDeath[kParticleSystemMaxSubDeath];
+ int numBirth = parent.m_SubModule->GetSubEmitterPtrsBirth(&subEmittersBirth[0]);
+ int numCollision = parent.m_SubModule->GetSubEmitterPtrsCollision(&subEmittersCollision[0]);
+ int numDeath = parent.m_SubModule->GetSubEmitterPtrsDeath(&subEmittersDeath[0]);
+
+ const int numCommands = state.subEmitterCommandBuffer.commandCount;
+ const SubEmitterEmitCommand* commands = state.subEmitterCommandBuffer.commands;
+ for(int i = 0; i < numCommands; i++)
+ {
+ const SubEmitterEmitCommand& command = commands[i];
+ ParticleSystem* shuriken = NULL;
+ if(command.subEmitterType == kParticleSystemSubTypeBirth)
+ shuriken = (command.subEmitterIndex < numBirth) ? subEmittersBirth[command.subEmitterIndex] : NULL;
+ else if(command.subEmitterType == kParticleSystemSubTypeCollision)
+ shuriken = (command.subEmitterIndex < numCollision) ? subEmittersCollision[command.subEmitterIndex] : NULL;
+ else if(command.subEmitterType == kParticleSystemSubTypeDeath)
+ shuriken = (command.subEmitterIndex < numDeath) ? subEmittersDeath[command.subEmitterIndex] : NULL;
+ else
+ Assert(!"PlaybackSubEmitterCommandBuffer: Sub emitter type not implemented");
+
+ DebugAssert(shuriken && "Y U NO HERE ANYMORE?");
+ if(!shuriken)
+ continue;
+
+ ParticleSystem::Emit(*shuriken, command, kParticleSystemEMDirect);
+ }
+
+ state.subEmitterCommandBuffer.commandCount = 0;
+}
+
+void ParticleSystem::UpdateBounds(const ParticleSystem& system, const ParticleSystemParticles& ps, ParticleSystemState& state)
+{
+ const size_t particleCount = ps.array_size();
+ if (particleCount == 0)
+ {
+ state.minMaxAABB = MinMaxAABB (Vector3f::zero, Vector3f::zero);
+ return;
+ }
+
+ state.minMaxAABB.Init();
+
+ if(CheckSupportsProcedural(system))
+ {
+ const Transform& transform = system.GetComponent (Transform);
+ Matrix4x4f localToWorld = transform.GetLocalToWorldMatrixNoScale ();
+
+ // Lifetime and max speed
+ const Vector2f minMaxLifeTime = system.m_InitialModule.GetLifeTimeCurve().FindMinMax();
+
+ const float maxLifeTime = minMaxLifeTime.y;
+ const Vector2f minMaxStartSpeed = system.m_InitialModule.GetSpeedCurve().FindMinMax() * maxLifeTime;
+
+ state.minMaxAABB = MinMaxAABB(Vector3f::zero, Vector3f::zero);
+ state.minMaxAABB.Encapsulate(Vector3f::zAxis * minMaxStartSpeed.x);
+ state.minMaxAABB.Encapsulate(Vector3f::zAxis * minMaxStartSpeed.y);
+ if(system.m_ShapeModule.GetEnabled())
+ system.m_ShapeModule.CalculateProceduralBounds(state.minMaxAABB, state.emitterScale, minMaxStartSpeed);
+
+ // Gravity
+ // @TODO: Do what we do for force and velocity here, i.e. transform bounds properly
+ const Vector3f gravityBounds = system.m_InitialModule.GetGravity(*system.m_ReadOnlyState, *system.m_State) * maxLifeTime * maxLifeTime * 0.5f;
+
+ state.minMaxAABB.m_Max += max(gravityBounds, Vector3f::zero);
+ state.minMaxAABB.m_Min += min(gravityBounds, Vector3f::zero);
+
+ MinMaxAABB velocityBounds (Vector3f::zero, Vector3f::zero);
+ if(system.m_VelocityModule->GetEnabled())
+ system.m_VelocityModule->CalculateProceduralBounds(velocityBounds, localToWorld, maxLifeTime);
+ state.minMaxAABB.m_Max += velocityBounds.m_Max;
+ state.minMaxAABB.m_Min += velocityBounds.m_Min;
+
+ MinMaxAABB forceBounds (Vector3f::zero, Vector3f::zero);
+ if(system.m_ForceModule->GetEnabled())
+ system.m_ForceModule->CalculateProceduralBounds(forceBounds, localToWorld, maxLifeTime);
+ state.minMaxAABB.m_Max += forceBounds.m_Max;
+ state.minMaxAABB.m_Min += forceBounds.m_Min;
+
+ // Modules that are not supported by procedural
+ DebugAssert(!system.m_RotationBySpeedModule->GetEnabled()); // find out if possible to support it
+ DebugAssert(!system.m_ClampVelocityModule->GetEnabled()); // unsupported: (Need to compute velocity by deriving position curves...), possible to support?
+ DebugAssert(!system.m_CollisionModule->GetEnabled());
+ DebugAssert(!system.m_SubModule->GetEnabled()); // find out if possible to support it
+ DebugAssert(!system.m_ExternalForcesModule->GetEnabled());
+ }
+ else
+ {
+ for (size_t q = 0; q < particleCount; ++q)
+ state.minMaxAABB.Encapsulate (ps.position[q]);
+
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(renderer && (renderer->GetRenderMode() == kSRMStretch3D))
+ {
+ const float velocityScale = renderer->GetVelocityScale();
+ const float lengthScale = renderer->GetLengthScale();
+ for(size_t q = 0; q < particleCount; ++q )
+ {
+ float sqrVelocity = SqrMagnitude (ps.velocity[q]+ps.animatedVelocity[q]);
+ if (sqrVelocity > Vector3f::epsilon)
+ {
+ float scale = velocityScale + FastInvSqrt (sqrVelocity) * lengthScale * ps.size[q];
+ state.minMaxAABB.Encapsulate(ps.position[q] - ps.velocity[q] * scale);
+ }
+ }
+ }
+ }
+
+ // Expand with maximum particle size * sqrt(2) (the length of a diagonal of a particle, if it should happen to be rotated)
+ const float kSqrtOf2 = 1.42f;
+ float maxSize = kSqrtOf2 * 0.5f * system.m_InitialModule.GetSizeCurve().FindMinMax().y;
+ if(system.m_SizeModule->GetEnabled())
+ maxSize *= system.m_SizeModule->GetCurve().FindMinMax().y;
+ if(system.m_SizeBySpeedModule->GetEnabled())
+ maxSize *= system.m_SizeBySpeedModule->GetCurve().FindMinMax().y;
+ state.minMaxAABB.Expand (max(maxSize, state.maxSize));
+
+ Assert (state.minMaxAABB.IsValid ());
+}
+
+IMPLEMENT_CLASS_HAS_INIT (ParticleSystem)
+IMPLEMENT_OBJECT_SERIALIZE (ParticleSystem)
+
+
+template<class TransferFunction>
+void ParticleSystem::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+
+ m_ReadOnlyState->Transfer (transfer); m_ReadOnlyState->CheckConsistency();
+ m_State->Transfer (transfer);
+ transfer.Transfer(m_InitialModule, m_InitialModule.GetName ()); m_InitialModule.CheckConsistency ();
+ transfer.Transfer(m_ShapeModule, m_ShapeModule.GetName ()); m_ShapeModule.CheckConsistency ();
+ transfer.Transfer(m_EmissionModule, m_EmissionModule.GetName ()); m_EmissionModule.CheckConsistency ();
+ transfer.Transfer(*m_SizeModule, m_SizeModule->GetName ()); m_SizeModule->CheckConsistency ();
+ transfer.Transfer(*m_RotationModule, m_RotationModule->GetName ()); m_RotationModule->CheckConsistency ();
+ transfer.Transfer(*m_ColorModule, m_ColorModule->GetName ()); m_ColorModule->CheckConsistency ();
+ transfer.Transfer(*m_UVModule, m_UVModule->GetName ()); m_UVModule->CheckConsistency ();
+ transfer.Transfer(*m_VelocityModule, m_VelocityModule->GetName ()); m_VelocityModule->CheckConsistency ();
+ transfer.Transfer(*m_ForceModule, m_ForceModule->GetName ()); m_ForceModule->CheckConsistency ();
+ transfer.Transfer(*m_ExternalForcesModule, m_ExternalForcesModule->GetName ()); m_ExternalForcesModule->CheckConsistency ();
+ transfer.Transfer(*m_ClampVelocityModule, m_ClampVelocityModule->GetName ()); m_ClampVelocityModule->CheckConsistency ();
+ transfer.Transfer(*m_SizeBySpeedModule, m_SizeBySpeedModule->GetName ()); m_SizeBySpeedModule->CheckConsistency ();
+ transfer.Transfer(*m_RotationBySpeedModule, m_RotationBySpeedModule->GetName ()); m_RotationBySpeedModule->CheckConsistency ();
+ transfer.Transfer(*m_ColorBySpeedModule, m_ColorBySpeedModule->GetName ()); m_ColorBySpeedModule->CheckConsistency ();
+ transfer.Transfer(*m_CollisionModule, m_CollisionModule->GetName ()); m_CollisionModule->CheckConsistency ();
+ transfer.Transfer(*m_SubModule, m_SubModule->GetName ()); m_SubModule->CheckConsistency ();
+ if(transfer.IsReading())
+ {
+ m_State->supportsProcedural = DetermineSupportsProcedural(*this);
+ m_State->invalidateProcedural = true; // Stuff might have changed which we can't support (example: start speed has become smaller)
+ }
+}
+
+bool ParticleSystem::CheckSupportsProcedural(const ParticleSystem& system)
+{
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(renderer && (renderer->GetRenderMode() == kSRMStretch3D))
+ return false;
+ return system.m_State->supportsProcedural && !system.m_State->invalidateProcedural;
+}
+
+// TODO: Needs to match UpdateCullingSupportedString in all xxModule.cs files
+bool ParticleSystem::DetermineSupportsProcedural(const ParticleSystem& system)
+{
+ bool supportsProcedural = true;
+ supportsProcedural = supportsProcedural && system.m_ReadOnlyState->useLocalSpace;
+
+ // Can't be random as we recreate it every frame. TODO: Would be great if we can support this in procedural mode.
+ const short lifeTimeMinMaxState = system.m_InitialModule.GetLifeTimeCurve().minMaxState;
+ supportsProcedural = supportsProcedural && (lifeTimeMinMaxState == kMMCScalar || lifeTimeMinMaxState == kMMCCurve);
+
+ supportsProcedural = supportsProcedural && (EmissionModule::kEmissionTypeDistance != system.m_EmissionModule.GetEmissionDataRef().type);
+
+ supportsProcedural = supportsProcedural && !system.m_ExternalForcesModule->GetEnabled();
+ supportsProcedural = supportsProcedural && !system.m_ClampVelocityModule->GetEnabled();
+ supportsProcedural = supportsProcedural && !system.m_RotationBySpeedModule->GetEnabled();
+ supportsProcedural = supportsProcedural && !system.m_CollisionModule->GetEnabled();
+ supportsProcedural = supportsProcedural && !system.m_SubModule->GetEnabled();
+
+ if(system.m_RotationModule->GetEnabled())
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_RotationModule->GetCurve().editorCurves, system.m_RotationModule->GetCurve().minMaxState);
+
+ if(system.m_VelocityModule->GetEnabled())
+ {
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_VelocityModule->GetXCurve().editorCurves, system.m_VelocityModule->GetXCurve().minMaxState);
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_VelocityModule->GetYCurve().editorCurves, system.m_VelocityModule->GetYCurve().minMaxState);
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_VelocityModule->GetZCurve().editorCurves, system.m_VelocityModule->GetZCurve().minMaxState);
+ }
+
+ if(system.m_ForceModule->GetEnabled())
+ {
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_ForceModule->GetXCurve().editorCurves, system.m_ForceModule->GetXCurve().minMaxState);
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_ForceModule->GetYCurve().editorCurves, system.m_ForceModule->GetYCurve().minMaxState);
+ supportsProcedural = supportsProcedural && CurvesSupportProcedural (system.m_ForceModule->GetZCurve().editorCurves, system.m_ForceModule->GetZCurve().minMaxState);
+ supportsProcedural = supportsProcedural && !system.m_ForceModule->GetRandomizePerFrame();
+ }
+
+ return supportsProcedural;
+}
+
+bool ParticleSystem::ComputePrewarmStartParameters(float& prewarmTime, float t)
+{
+ const float fixedDelta = GetTimeManager().GetFixedDeltaTime();
+ const float maxLifetime = m_InitialModule.GetLifeTimeCurve().FindMinMax().y;
+ const float length = m_ReadOnlyState->lengthInSec;
+ if(!m_ReadOnlyState->looping && ((maxLifetime + length) < t))
+ return false;
+
+ prewarmTime = m_SubModule->GetEnabled() ? CalculateSubEmitterMaximumLifeTime(maxLifetime) : 0.0f;
+ prewarmTime = max(prewarmTime, maxLifetime);
+
+ float frac = fmodf(t, fixedDelta);
+ float startT = t - prewarmTime - frac;
+ prewarmTime += frac;
+
+ // Clamp to start
+ if(!m_ReadOnlyState->prewarm)
+ {
+ startT = max(startT, 0.0f);
+ prewarmTime = min(t, prewarmTime);
+ }
+
+ // This is needed to figure out if emitacc should be inverted or not
+ const float signStartT = Sign(startT);
+ const float absStartT = Abs(startT);
+
+ while(startT < 0.0f)
+ startT += length;
+ float endT = startT + absStartT;
+ m_State->t = fmodf(startT, length);
+
+ ParticleSystemEmissionState emissionState;
+ const Vector3f emitVelocity = Vector3f::zero;
+ const float epsilon = 0.0001f;
+ int seedIndex = 0;
+
+ const float prevStartT = startT;
+ const float nextStartT = startT + fixedDelta;
+ const float prevEndT = endT;
+ const float nextEndT = endT + fixedDelta;
+ if (!(nextStartT > prevStartT && nextEndT > prevEndT))
+ {
+ ErrorStringObject ("Precision issue while prewarming particle system - 'Duration' or 'Start Lifetime' is likely a too large value.", this);
+ return false;
+ }
+
+ while((startT + epsilon) < endT)
+ {
+ const float toT = fmodf(startT + fixedDelta, length);
+ const float fromT = fmodf(startT, length);
+
+ size_t numContinuous;
+ int numParticles = ParticleSystem::EmitFromModules(*this, *m_ReadOnlyState, emissionState, numContinuous, emitVelocity, fromT, toT, fixedDelta);
+ if(numParticles > 0)
+ seedIndex++;
+ startT += fixedDelta;
+ }
+ m_State->emissionState.m_ToEmitAccumulator = ((signStartT > 0.0f ? emissionState.m_ToEmitAccumulator : 1.0f - emissionState.m_ToEmitAccumulator)) + epsilon;
+#if UNITY_EDITOR
+ m_EditorRandomSeedIndex = signStartT > 0.0f ? seedIndex : -seedIndex-1;
+#endif
+
+ return true;
+}
+
+void ParticleSystem::Simulate (float t, bool restart)
+{
+ PROFILER_AUTO(gParticleSystemPrewarm, NULL)
+
+ if(restart)
+ {
+ m_InitialModule.ResetSeed(*m_ReadOnlyState);
+ m_ShapeModule.ResetSeed(*m_ReadOnlyState);
+ Stop();
+ Clear();
+ Play (false);
+
+ ApplyStartDelay (m_State->delayT, t);
+ float prewarmTime;
+ if(ComputePrewarmStartParameters(prewarmTime, t))
+ {
+ Update(*this, prewarmTime, true, CheckSupportsProcedural(*this));
+ Pause ();
+ }
+ else
+ {
+ Stop();
+ Clear();
+ }
+ }
+ else
+ {
+ m_State->playing = true;
+ Update(*this, t, true, false);
+ Pause ();
+ }
+}
+
+void ParticleSystem::Play (bool autoPrewarm)
+{
+ Assert (m_State);
+ if(!IsActive () || m_State->GetIsSubEmitter())
+ return;
+
+ m_State->stopEmitting = false;
+ m_State->playing = true;
+ if (m_State->needRestart)
+ {
+ if(m_ReadOnlyState->prewarm)
+ {
+ if (autoPrewarm)
+ AutoPrewarm();
+ }
+ else
+ {
+ m_State->delayT = m_ReadOnlyState->startDelay;
+ }
+
+ m_State->playing = true;
+ m_State->t = 0.0f;
+ m_State->numLoops = 0;
+ m_State->invalidateProcedural = false;
+ m_State->accumulatedDt = 0.0f;
+#if UNITY_EDITOR
+ m_EditorRandomSeedIndex = 0;
+#endif
+ m_State->emissionState.Clear();
+ }
+
+ if(m_State->culled && CheckSupportsProcedural(*this))
+ Cull();
+ else
+ AddToManager();
+}
+
+void ParticleSystem::AutoPrewarm()
+{
+ if(m_ReadOnlyState->prewarm && m_ReadOnlyState->looping)
+ {
+ DebugAssert(!m_State->GetIsSubEmitter());
+ DebugAssert(m_State->playing); // Only use together with play
+ Simulate (0.0f, true);
+ //DebugAssert(CompareApproximately(m_State->emissionState.m_ToEmitAccumulator, 1.0f, 0.01f) && "Particle emission gaps may occur");
+ }
+}
+
+void ParticleSystem::Stop ()
+{
+ Assert (m_State);
+ m_State->needRestart = true;
+ m_State->stopEmitting = true;
+}
+
+void ParticleSystem::Pause ()
+{
+ Assert (m_State);
+ m_State->playing = false;
+ m_State->needRestart = false;
+ RemoveFromManager();
+}
+
+bool ParticleSystem::IsPaused () const
+{
+ Assert (m_State);
+ return !m_State->playing && !m_State->needRestart;
+}
+
+bool ParticleSystem::IsStopped () const
+{
+ Assert (m_State);
+ return !m_State->playing && m_State->needRestart;
+}
+
+void ParticleSystem::KeepUpdating()
+{
+ if (IsActive())
+ {
+ // Ensure added particles will update, but stop emission
+ m_State->playing = true;
+ m_State->stopEmitting = true;
+ AddToManager();
+ }
+}
+
+void ParticleSystem::Emit (int count)
+{
+ // StartParticles() takes size_t so check for negative value here (case 495098)
+ if (count <= 0)
+ return;
+
+ KeepUpdating();
+ Assert (m_State);
+
+ const Transform& transform = GetComponent (Transform);
+ Matrix4x4f oldLocalToWorld = m_State->localToWorld;
+ Matrix4x4f oldWorldToLocal = m_State->worldToLocal;
+ Vector3f oldEmitterScale = m_State->emitterScale;
+ m_State->localToWorld = transform.GetLocalToWorldMatrixNoScale ();
+ Matrix4x4f::Invert_General3D(m_State->localToWorld, m_State->worldToLocal);
+ if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_2_a1))
+ m_State->emitterScale = transform.GetWorldScaleLossy();
+ else
+ m_State->emitterScale = transform.GetLocalScale();
+ StartParticles(*this, m_Particles[kParticleBuffer0], 0.0f, m_State->t, 0.0f, 0, count, 0.0f);
+ m_State->localToWorld = oldLocalToWorld;
+ m_State->worldToLocal = oldWorldToLocal;
+ m_State->emitterScale = oldEmitterScale;
+}
+
+void ParticleSystem::EmitParticleExternal(ParticleSystemParticle* particle)
+{
+#if UNITY_EDITOR
+ if(!IsWorldPlaying() && IsStopped ())
+ return;
+#endif
+
+ m_State->invalidateProcedural = true;
+
+ CheckParticleConsistency(*m_State, *particle);
+ if(particle->lifetime <= 0.0f)
+ return;
+
+ KeepUpdating();
+
+ m_Particles[kParticleBuffer0].AddParticle(particle);
+
+#if UNITY_EDITOR
+ if(!IsWorldPlaying())
+ m_Particles[kParticleBuffer1].AddParticle(particle);
+#endif
+
+ if(!IsPlaying())
+ UpdateBounds(*this, m_Particles[kParticleBuffer0], *m_State);
+}
+
+void ParticleSystem::Clear (bool updateBounds)
+{
+ for(int i = 0; i < kNumParticleBuffers; i++)
+ m_Particles[i].array_resize(0);
+ m_ParticlesStaging.array_resize(0);
+ m_State->emitReplay.resize_uninitialized(0);
+
+ if (m_State->stopEmitting)
+ {
+ // This triggers sometimes, why? (case 491684)
+ DebugAssert (m_State->needRestart);
+ m_State->playing = false;
+ RemoveFromManager();
+ }
+
+ if(updateBounds)
+ {
+ UpdateBounds(*this, m_Particles[kParticleBuffer0], *m_State);
+ Update2(*this, *m_ReadOnlyState, *m_State, false);
+ }
+}
+
+float ParticleSystem::GetStartDelay() const
+{
+ Assert(m_State);
+ return m_ReadOnlyState->startDelay;
+}
+
+void ParticleSystem::SetStartDelay(float value)
+{
+ Assert(m_State);
+ m_ReadOnlyState->startDelay = value;
+ SetDirty ();
+}
+
+bool ParticleSystem::IsAlive () const
+{
+ Assert(m_State);
+ return (!IsStopped() || (GetParticleCount() > 0));
+}
+
+bool ParticleSystem::IsPlaying () const
+{
+ Assert (m_State);
+ return m_State->playing;
+}
+
+bool ParticleSystem::GetLoop () const
+{
+ Assert (m_State);
+ return m_ReadOnlyState->looping;
+}
+
+void ParticleSystem::SetLoop (bool loop)
+{
+ Assert (m_State);
+ m_ReadOnlyState->looping = loop;
+ SetDirty ();
+}
+
+bool ParticleSystem::GetPlayOnAwake () const
+{
+ Assert (m_State);
+ return m_ReadOnlyState->playOnAwake;
+}
+
+void ParticleSystem::SetPlayOnAwake (bool playOnAwake)
+{
+ Assert (m_State);
+ m_ReadOnlyState->playOnAwake = playOnAwake;
+ SetDirty ();
+}
+
+ParticleSystemSimulationSpace ParticleSystem::GetSimulationSpace () const
+{
+ Assert (m_State);
+ return (m_ReadOnlyState->useLocalSpace ? kSimLocal : kSimWorld);
+}
+
+void ParticleSystem::SetSimulationSpace (ParticleSystemSimulationSpace simulationSpace)
+{
+ Assert (m_State);
+ m_ReadOnlyState->useLocalSpace = (simulationSpace == kSimLocal);
+ SetDirty ();
+}
+
+float ParticleSystem::GetSecPosition () const
+{
+ Assert (m_State);
+ return m_State->t;
+}
+
+void ParticleSystem::SetSecPosition (float pos)
+{
+ Assert (m_State);
+ m_State->t = pos;
+ SetDirty ();
+}
+
+float ParticleSystem::GetPlaybackSpeed () const
+{
+ Assert (m_State);
+ return m_ReadOnlyState->speed;
+}
+
+void ParticleSystem::SetPlaybackSpeed (float speed)
+{
+ Assert (m_State);
+ m_ReadOnlyState->speed = speed;
+ SetDirty ();
+}
+
+float ParticleSystem::GetLengthInSec () const
+{
+ Assert (m_State);
+ return m_ReadOnlyState->lengthInSec;
+}
+
+void ParticleSystem::GetNumTiles(int& uvTilesX, int& uvTilesY) const
+{
+ uvTilesX = uvTilesY = 1;
+ if(m_UVModule->GetEnabled())
+ m_UVModule->GetNumTiles(uvTilesX, uvTilesY);
+}
+
+Matrix4x4f ParticleSystem::GetLocalToWorldMatrix() const
+{
+ return m_State->localToWorld;
+}
+
+bool ParticleSystem::GetEnableEmission() const
+{
+ return m_EmissionModule.GetEnabled();
+}
+
+void ParticleSystem::SetEnableEmission(bool value)
+{
+ m_State->invalidateProcedural = true;
+ m_EmissionModule.SetEnabled(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetEmissionRate() const
+{
+ return m_EmissionModule.GetEmissionDataRef().rate.GetScalar();
+}
+
+void ParticleSystem::SetEmissionRate(float value)
+{
+ m_State->invalidateProcedural = true;
+ m_EmissionModule.GetEmissionData().rate.SetScalar(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetStartSpeed() const
+{
+ return m_InitialModule.GetSpeedCurve().GetScalar();
+}
+
+void ParticleSystem::SetStartSpeed(float value)
+{
+ m_State->invalidateProcedural = true;
+ m_InitialModule.GetSpeedCurve().SetScalar(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetStartSize() const
+{
+ return m_InitialModule.GetSizeCurve().GetScalar();
+}
+
+void ParticleSystem::SetStartSize(float value)
+{
+ m_InitialModule.GetSizeCurve().SetScalar(value);
+ SetDirty();
+}
+
+ColorRGBAf ParticleSystem::GetStartColor() const
+{
+ return m_InitialModule.GetColor().maxColor;
+}
+
+void ParticleSystem::SetStartColor(ColorRGBAf value)
+{
+ m_InitialModule.GetColor().maxColor = value;
+ SetDirty();
+}
+
+float ParticleSystem::GetStartRotation() const
+{
+ return m_InitialModule.GetRotationCurve().GetScalar();
+}
+
+void ParticleSystem::SetStartRotation(float value)
+{
+ m_InitialModule.GetRotationCurve().SetScalar(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetStartLifeTime() const
+{
+ return m_InitialModule.GetLifeTimeCurve().GetScalar();
+}
+
+void ParticleSystem::SetStartLifeTime(float value)
+{
+ m_State->invalidateProcedural = true;
+ m_InitialModule.GetLifeTimeCurve().SetScalar(value);
+ SetDirty();
+}
+
+float ParticleSystem::GetGravityModifier() const
+{
+ return m_InitialModule.GetGravityModifier();
+}
+
+void ParticleSystem::SetGravityModifier(float value)
+{
+ m_State->invalidateProcedural = true;
+ m_InitialModule.SetGravityModifier(value);
+ SetDirty();
+}
+
+UInt32 ParticleSystem::GetRandomSeed() const
+{
+ return m_ReadOnlyState->randomSeed;
+}
+
+void ParticleSystem::SetRandomSeed(UInt32 value)
+{
+ m_ReadOnlyState->randomSeed = value;
+ SetDirty();
+}
+
+int ParticleSystem::GetMaxNumParticles () const
+{
+ return m_InitialModule.GetMaxNumParticles ();
+}
+
+void ParticleSystem::SetMaxNumParticles (int value)
+{
+ m_InitialModule.SetMaxNumParticles (value);
+ SetDirty();
+}
+
+void ParticleSystem::AllocateAllStructuresOfArrays()
+{
+ // Make sure all particle buffers have all elements
+ for(int i = 0; i < kNumParticleBuffers; i++)
+ {
+ if(!m_Particles[i].usesAxisOfRotation)
+ m_Particles[i].SetUsesAxisOfRotation();
+ m_Particles[i].SetUsesEmitAccumulator(kParticleSystemMaxNumEmitAccumulators);
+ }
+}
+
+void ParticleSystem::SetParticlesExternal (ParticleSystemParticle* particles, int size)
+{
+#if UNITY_EDITOR
+ if(!IsWorldPlaying() && IsStopped ())
+ return;
+#endif
+
+ m_State->invalidateProcedural = true;
+
+ // Make sure particles are in the correct ranges etc.
+ for (size_t q = 0; q < size; q++)
+ CheckParticleConsistency(*m_State, particles[q]);
+
+ AllocateAllStructuresOfArrays();
+ ParticleSystemParticles& ps = m_Particles[kParticleBuffer0];
+ ps.array_resize(size);
+ ps.CopyFromArrayAOS(particles, size);
+ size_t particleCount = size;
+ for (size_t q = 0; q < particleCount; )
+ {
+ if (ps.lifetime[q] < 0)
+ {
+ KillParticle(*m_ReadOnlyState, *m_State, ps, q, particleCount);
+ continue;
+ }
+ ++q;
+ }
+ ps.array_resize(particleCount);
+
+ #if UNITY_EDITOR
+ if(!IsWorldPlaying())
+ m_Particles[kParticleBuffer1].array_assign(ps);
+ #endif
+
+ if(!IsPlaying())
+ UpdateBounds(*this, ps, *m_State);
+}
+
+void ParticleSystem::GetParticlesExternal (ParticleSystemParticle* particles, int size)
+{
+ AllocateAllStructuresOfArrays();
+ ParticleSystemParticles& ps = m_Particles[kParticleBuffer0];
+ ps.CopyToArrayAOS(particles, size);
+}
+
+int ParticleSystem::GetSafeCollisionEventSize () const
+{
+ const ParticleSystemParticles& ps = m_Particles[kParticleBuffer0];
+ return ps.collisionEvents.GetCollisionEventCount ();
+}
+
+int ParticleSystem::GetCollisionEventsExternal (int instanceID, MonoParticleCollisionEvent* collisionEvents, int size) const
+{
+ const ParticleSystemParticles& ps = m_Particles[kParticleBuffer0];
+ return ps.collisionEvents.GetCollisionEvents (instanceID, collisionEvents, size);
+}
+
+ParticleSystemParticles& ParticleSystem::GetParticles (int index)
+{
+#if UNITY_EDITOR
+ if(index == -1)
+ if(ParticleSystemEditor::UseInterpolation(*this))
+ index = (int)kParticleBuffer1;
+ else
+ index = (int)kParticleBuffer0;
+ return m_Particles[index];
+#else
+ return m_Particles[kParticleBuffer0];
+#endif
+}
+
+const ParticleSystemParticles& ParticleSystem::GetParticles (int index) const
+{
+#if UNITY_EDITOR
+ if(index == -1)
+ if(ParticleSystemEditor::UseInterpolation(*this))
+ index = (int)kParticleBuffer1;
+ else
+ index = (int)kParticleBuffer0;
+ return m_Particles[index];
+#else
+ return m_Particles[kParticleBuffer0];
+#endif
+}
+
+size_t ParticleSystem::GetParticleCount () const
+{
+ return m_Particles[kParticleBuffer0].array_size ();
+}
+
+int ParticleSystem::SetupSubEmitters(ParticleSystem& shuriken, ParticleSystemState& state)
+{
+ Assert(!state.cachedSubDataBirth && !state.numCachedSubDataBirth);
+ Assert(!state.cachedSubDataCollision && !state.numCachedSubDataCollision);
+ Assert(!state.cachedSubDataDeath && !state.numCachedSubDataDeath);
+ int subEmitterCount = 0;
+
+ if(shuriken.m_SubModule->GetEnabled())
+ {
+ ParticleSystem* subEmittersBirth[kParticleSystemMaxSubBirth];
+ state.numCachedSubDataBirth = shuriken.m_SubModule->GetSubEmitterPtrsBirth(&subEmittersBirth[0]);
+ state.cachedSubDataBirth = ALLOC_TEMP_MANUAL(ParticleSystemSubEmitterData, state.numCachedSubDataBirth);
+ std::uninitialized_fill (state.cachedSubDataBirth, state.cachedSubDataBirth + state.numCachedSubDataBirth, ParticleSystemSubEmitterData());
+ for(int i = 0; i < state.numCachedSubDataBirth; i++)
+ {
+ ParticleSystem* subEmitter = subEmittersBirth[i];
+ ParticleSystemSubEmitterData& subData = state.cachedSubDataBirth[i];
+ subData.startDelayInSec = subEmitter->m_ReadOnlyState->startDelay;
+ subData.lengthInSec = subEmitter->GetLoop() ? numeric_limits<float>::max() : subEmitter->GetLengthInSec();
+ subData.maxLifetime = subEmitter->m_InitialModule.GetLifeTimeCurve().GetScalar();
+ subData.emitter = subEmitter;
+ subEmitter->m_EmissionModule.GetEmissionDataCopy(&subData.emissionData);
+ subEmitter->m_State->SetIsSubEmitter(true);
+ subEmitterCount++;
+ }
+ ParticleSystem* subEmittersCollision[kParticleSystemMaxSubCollision];
+ state.numCachedSubDataCollision = shuriken.m_SubModule->GetSubEmitterPtrsCollision(&subEmittersCollision[0]);
+ state.cachedSubDataCollision = ALLOC_TEMP_MANUAL(ParticleSystemSubEmitterData, state.numCachedSubDataCollision);
+ std::uninitialized_fill (state.cachedSubDataCollision, state.cachedSubDataCollision + state.numCachedSubDataCollision, ParticleSystemSubEmitterData());
+ for(int i = 0; i < state.numCachedSubDataCollision; i++)
+ {
+ ParticleSystem* subEmitter = subEmittersCollision[i];
+ ParticleSystemSubEmitterData& subData = state.cachedSubDataCollision[i];
+ subData.emitter = subEmitter;
+ subEmitter->m_EmissionModule.GetEmissionDataCopy(&subData.emissionData);
+ subEmitter->m_State->SetIsSubEmitter(true);
+ subEmitterCount++;
+ }
+ ParticleSystem* subEmittersDeath[kParticleSystemMaxSubDeath];
+ state.numCachedSubDataDeath = shuriken.m_SubModule->GetSubEmitterPtrsDeath(&subEmittersDeath[0]);
+ state.cachedSubDataDeath = ALLOC_TEMP_MANUAL(ParticleSystemSubEmitterData, state.numCachedSubDataDeath);
+ std::uninitialized_fill (state.cachedSubDataDeath, state.cachedSubDataDeath + state.numCachedSubDataDeath, ParticleSystemSubEmitterData());
+ for(int i = 0; i < state.numCachedSubDataDeath; i++)
+ {
+ ParticleSystem* subEmitter = subEmittersDeath[i];
+ ParticleSystemSubEmitterData& subData = state.cachedSubDataDeath[i];
+ subData.emitter = subEmitter;
+ subEmitter->m_EmissionModule.GetEmissionDataCopy(&subData.emissionData);
+ subEmitter->m_State->SetIsSubEmitter(true);
+ subEmitterCount++;
+ }
+ }
+ return subEmitterCount;
+}
+
+int ParticleSystem::CalculateMaximumSubEmitterEmitCount(ParticleSystem& shuriken, ParticleSystemState& state, float deltaTime, bool fixedTimeStep)
+{
+ // Save emission state
+ const ParticleSystemEmissionState orgEmissionState = state.emissionState;
+ ParticleSystemEmissionState emissionState;
+
+ // Simulate emission
+ float timeStep = GetTimeStep(deltaTime, fixedTimeStep);
+ DebugAssert(timeStep > 0.0001f);
+
+ // @TODO: Maybe we can go back to just picking the conservative solution, since no longer use this for scrubbing/prewarm we shouldn't allocate that much.
+
+
+ int existingParticleCount = shuriken.GetParticleCount();
+ int totalPossibleEmits = 0;
+ float acc = deltaTime;
+ float fromT = state.t;
+ while(acc >= timeStep)
+ {
+ acc -= timeStep;
+ float toT = fromT + (deltaTime - acc);
+ float dt;
+ const float length = shuriken.m_ReadOnlyState->lengthInSec;
+ if(shuriken.m_ReadOnlyState->looping)
+ {
+ toT = fmodf (toT, length);
+ dt = timeStep;
+ }
+ else
+ {
+ toT = min(toT, length);
+ dt = toT - fromT;
+ }
+
+ size_t numContinuous;
+ const Vector3f emitVelocity = state.emitterVelocity;
+ existingParticleCount += EmitFromModules(shuriken, *shuriken.m_ReadOnlyState, emissionState, numContinuous, emitVelocity, fromT, toT, dt);
+ totalPossibleEmits += existingParticleCount;
+ fromT = toT;
+ }
+
+ totalPossibleEmits += existingParticleCount;
+
+ // Restore emission state
+ state.emissionState = orgEmissionState;
+
+ const int kExtra = 5; // Give a few extra to avoid denying due to rounding etc.
+ return totalPossibleEmits + kExtra;
+}
+
+// @TODO: what about chained effects?
+float ParticleSystem::CalculateSubEmitterMaximumLifeTime(float parentLifeTime) const
+{
+ ParticleSystem* subEmitters[kParticleSystemMaxSubTotal];
+ m_SubModule->GetSubEmitterPtrs(subEmitters);
+
+ float maxLifeTime = 0.0f;
+ for(int i = 0; i < kParticleSystemMaxSubTotal; i++)
+ {
+ if (subEmitters[i] && (subEmitters[i] != this))
+ {
+ const float emitterMaximumLifetime = subEmitters[i]->m_InitialModule.GetLifeTimeCurve().FindMinMax().y;
+ maxLifeTime = max(maxLifeTime, parentLifeTime + emitterMaximumLifetime);
+ maxLifeTime = std::max(maxLifeTime, subEmitters[i]->CalculateSubEmitterMaximumLifeTime(parentLifeTime + emitterMaximumLifetime));
+ }
+ }
+ return maxLifeTime;
+}
+
+void ParticleSystem::SmartReset ()
+{
+ Super::SmartReset();
+ AddParticleSystemRenderer ();
+}
+
+void ParticleSystem::AddParticleSystemRenderer ()
+{
+ if (GameObject* go = GetGameObjectPtr ())
+ {
+ ParticleSystemRenderer* renderer = go->QueryComponent (ParticleSystemRenderer);
+ if (renderer == NULL)
+ {
+ string error;
+ AddComponent (*go, ClassID(ParticleSystemRenderer), NULL, &error);
+ if (error.empty ())
+ go->GetComponent (ParticleSystemRenderer).SetMaterial (GetDefaultParticleMaterial(), 0);
+ else
+ LogString (Format("%s", error.c_str()));
+ }
+ }
+}
+
+
+void ParticleSystem::SetUsesRotationalSpeed()
+{
+ ParticleSystemParticles& ps0 = m_Particles[kParticleBuffer0];
+ if(!ps0.usesRotationalSpeed)
+ ps0.SetUsesRotationalSpeed ();
+#if UNITY_EDITOR
+ ParticleSystemParticles& ps1 = m_Particles[kParticleBuffer1];
+ if(!ps1.usesRotationalSpeed)
+ ps1.SetUsesRotationalSpeed ();
+#endif
+ ParticleSystemParticles& pss = m_ParticlesStaging;
+ if(!pss.usesRotationalSpeed)
+ pss.SetUsesRotationalSpeed ();
+}
+
+void ParticleSystem::SetUsesAxisOfRotation()
+{
+ ParticleSystemParticles& ps0 = m_Particles[kParticleBuffer0];
+ if(!ps0.usesAxisOfRotation)
+ ps0.SetUsesAxisOfRotation ();
+#if UNITY_EDITOR
+ ParticleSystemParticles& ps1 = m_Particles[kParticleBuffer1];
+ if(!ps1.usesAxisOfRotation)
+ ps1.SetUsesAxisOfRotation ();
+#endif
+ ParticleSystemParticles& pss = m_ParticlesStaging;
+ if(!pss.usesAxisOfRotation)
+ pss.SetUsesAxisOfRotation ();
+}
+
+void ParticleSystem::SetUsesEmitAccumulator(int numAccumulators)
+{
+ m_Particles[kParticleBuffer0].SetUsesEmitAccumulator (numAccumulators);
+#if UNITY_EDITOR
+ m_Particles[kParticleBuffer1].SetUsesEmitAccumulator (numAccumulators);
+#endif
+ m_ParticlesStaging.SetUsesEmitAccumulator (numAccumulators);
+}
+
+bool ParticleSystem::GetIsDistanceEmitter() const
+{
+ return (EmissionModule::kEmissionTypeDistance == m_EmissionModule.GetEmissionDataRef().type);
+}
+
+// check if the system would like to use any raycasting in this frame
+bool ParticleSystem::SystemWannaRayCast(const ParticleSystem& system)
+{
+ return system.IsActive() && system.m_CollisionModule && system.m_CollisionModule->GetEnabled() && system.m_CollisionModule->IsWorldCollision() && system.m_RayBudgetState.ReceiveRays();
+}
+
+// check if the system will actually use any raycasting in this frame
+bool ParticleSystem::SystemWillRayCast(const ParticleSystem& system)
+{
+ return system.IsActive() && system.m_CollisionModule && system.m_CollisionModule->GetEnabled() && system.m_CollisionModule->IsWorldCollision() && (system.GetRayBudget() > 0);
+}
+
+// dole out ray budgets to each system that will do raycasting
+void ParticleSystem::AssignRayBudgets()
+{
+ int activeCount = gParticleSystemManager.activeEmitters.size();
+
+ // count the jobs and update quality setting
+ int numApproximateWorldCollisionJobs = 0;
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ system.m_RayBudgetState.SetQuality( system.m_CollisionModule->GetQuality() );
+ system.SetRayBudget( 0 );
+ if ( SystemWannaRayCast( system ) )
+ {
+ if ( system.m_CollisionModule->IsApproximate() )
+ {
+ numApproximateWorldCollisionJobs++;
+ }
+ else
+ {
+ // high quality always get to trace all rays!
+ system.SetRayBudget( (int)system.GetParticleCount() );
+ }
+ }
+ }
+ if (numApproximateWorldCollisionJobs<1) return;
+
+ // assign ray budget to particle systems
+ int totalBudget = GetQualitySettings().GetCurrent().particleRaycastBudget;
+ int raysPerSystem = std::max( 0, totalBudget / numApproximateWorldCollisionJobs );
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ if ( SystemWannaRayCast( system ) && system.m_CollisionModule->IsApproximate() )
+ {
+ const int rays = std::min((int)system.GetParticleCount(),raysPerSystem);
+ system.SetRayBudget( rays );
+ totalBudget = std::max( totalBudget-rays, 0);
+ }
+ }
+
+ // assign any remaining rays
+ // TODO: possibly better to sort and go through the list incrementally updating the per system budget, than doing this hacky two pass thing
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ if ( SystemWannaRayCast( system ) && system.m_CollisionModule->IsApproximate() )
+ {
+ const int rays = std::min(totalBudget,(int)system.GetParticleCount()-system.GetRayBudget());
+ system.SetRayBudget( system.GetRayBudget()+rays );
+ totalBudget -= rays;
+ }
+ system.m_RayBudgetState.Update();
+ }
+}
+
+void ParticleSystem::BeginUpdateAll ()
+{
+ const float deltaTimeEpsilon = 0.0001f;
+ float deltaTime = GetDeltaTime();
+ if(deltaTime < deltaTimeEpsilon)
+ return;
+
+ PROFILER_AUTO(gParticleSystemProfile, NULL)
+
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size(); i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+
+ if (!system.IsActive ())
+ {
+ AssertStringObject( "UpdateParticle system should not happen on disabled vGO", &system);
+ system.RemoveFromManager();
+ continue;
+ }
+
+#if ENABLE_MULTITHREADED_PARTICLES
+ system.m_State->recordSubEmits = true;
+#else
+ system.m_State->recordSubEmits = false;
+#endif
+ Update0 (system, *system.m_ReadOnlyState, *system.m_State, deltaTime, false);
+ }
+
+ gParticleSystemManager.needSync = true;
+
+ // make sure ray budgets are assigned for the frame
+ ParticleSystem::AssignRayBudgets();
+
+#if ENABLE_MULTITHREADED_PARTICLES
+ JobScheduler& scheduler = GetJobScheduler();
+ int activeCount = gParticleSystemManager.activeEmitters.size();
+
+ // count the jobs
+ int numActiveWorldCollisionJobs = 0;
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ if ( system.GetRayBudget() > 0 )
+ numActiveWorldCollisionJobs++;
+ }
+
+ // add collision jobs
+ gParticleSystemManager.worldCollisionJobGroup = scheduler.BeginGroup(numActiveWorldCollisionJobs);
+ gParticleSystemManager.jobGroup = scheduler.BeginGroup(activeCount-numActiveWorldCollisionJobs);
+ for(int i = 0; i < activeCount; i++)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ system.GetThreadScratchPad().deltaTime = deltaTime;
+ if ( system.GetRayBudget() > 0 )
+ scheduler.SubmitJob (gParticleSystemManager.worldCollisionJobGroup, ParticleSystem::UpdateFunction, &system, NULL);
+ else
+ scheduler.SubmitJob (gParticleSystemManager.jobGroup, ParticleSystem::UpdateFunction, &system, NULL);
+ }
+ scheduler.WaitForGroup(gParticleSystemManager.worldCollisionJobGroup);
+#else
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size(); i++)
+ {
+ //printf_console("BeginUpdateAll [%d]:\n",i);
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ system.Update1 (system, system.GetParticles((int)ParticleSystem::kParticleBuffer0), deltaTime, false, false);
+ }
+#endif
+
+}
+
+void ParticleSystem::EndUpdateAll ()
+{
+ SyncJobs();
+
+ // messages
+ for (int i = 0; i < gParticleSystemManager.activeEmitters.size(); ++i)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ if (!system.IsActive ())
+ continue;
+ if (!system.m_CollisionModule->GetUsesCollisionMessages ())
+ continue;
+ ParticleSystemParticles& ps = system.GetParticles((int)ParticleSystem::kParticleBuffer0);
+ ps.collisionEvents.SwapCollisionEventArrays ();
+ ps.collisionEvents.SendCollisionEvents (system);
+ }
+
+ // Remove emitters that are finished (no longer emitting)
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size();)
+ {
+ ParticleSystem& system = *gParticleSystemManager.activeEmitters[i];
+ ParticleSystemState& state = *system.m_State;
+ const size_t particleCount = system.GetParticleCount();
+ if ((particleCount == 0) && state.playing && state.stopEmitting)
+ {
+ // collision subemitters may not have needRestart==true when being restarted
+ // from a paused state
+ //Assert (state.needRestart);
+ state.playing = false;
+ system.RemoveFromManager();
+ continue;
+ }
+
+ i++;
+ }
+
+ // Interpolate in the editor for very low preview speeds
+#if UNITY_EDITOR
+ for(int i = 0; i < gParticleSystemManager.activeEmitters.size(); i++)
+ ParticleSystemEditor::PerformInterpolationStep(gParticleSystemManager.activeEmitters[i]);
+#endif
+}
+
+void ParticleSystem::StartParticles(ParticleSystem& system, ParticleSystemParticles& ps, const float prevT, const float t, const float dt, const size_t numContinuous, size_t amountOfParticlesToEmit, float frameOffset)
+{
+ if (amountOfParticlesToEmit <= 0)
+ return;
+
+ const ParticleSystemReadOnlyState& roState = *system.m_ReadOnlyState;
+ ParticleSystemState& state = *system.m_State;
+ size_t fromIndex = system.AddNewParticles(ps, amountOfParticlesToEmit);
+ const Matrix4x4f localToWorld = !roState.useLocalSpace ? state.localToWorld : Matrix4x4f::identity;
+ StartModules (system, roState, state, state.emissionState, state.emitterVelocity, localToWorld, ps, fromIndex, dt, t, numContinuous, frameOffset);
+}
+
+void ParticleSystem::StartParticlesProcedural(ParticleSystem& system, ParticleSystemParticles& ps, const float prevT, const float t, const float dt, const size_t numContinuous, size_t amountOfParticlesToEmit, float frameOffset)
+{
+ DebugAssert(CheckSupportsProcedural(system));
+
+ ParticleSystemState& state = *system.m_State;
+
+ int numParticlesRecorded = 0;
+ for (int i=0;i<state.emitReplay.size();i++)
+ numParticlesRecorded += state.emitReplay[i].particlesToEmit;
+
+ float emissionOffset = state.emissionState.m_ToEmitAccumulator;
+ float emissionGap = state.emissionState.m_ParticleSpacing * dt;
+ amountOfParticlesToEmit = system.LimitParticleCount(numParticlesRecorded + amountOfParticlesToEmit) - numParticlesRecorded;
+
+ if (amountOfParticlesToEmit > 0)
+ {
+ UInt32 randomSeed = 0;
+#if UNITY_EDITOR
+ ParticleSystemEditor::UpdateRandomSeed(system);
+ randomSeed = ParticleSystemEditor::GetRandomSeed(system);
+#endif
+ state.emitReplay.push_back(ParticleSystemEmitReplay(t, amountOfParticlesToEmit, emissionOffset, emissionGap, numContinuous, randomSeed));
+ }
+}
+
+void ParticleSystem::StartModules (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, const ParticleSystemEmissionState& emissionState, Vector3f initialVelocity, const Matrix4x4f& matrix, ParticleSystemParticles& ps, size_t fromIndex, float dt, float t, size_t numContinuous, float frameOffset)
+{
+#if UNITY_EDITOR
+ ParticleSystemEditor::UpdateRandomSeed(system);
+ ParticleSystemEditor::ApplyRandomSeed(system, ParticleSystemEditor::GetRandomSeed(system));
+#endif
+
+ system.m_InitialModule.Start (roState, state, ps, matrix, fromIndex, t);
+ if(system.m_ShapeModule.GetEnabled())
+ system.m_ShapeModule.Start (roState, state, ps, matrix, fromIndex, t);
+
+ DebugAssert(roState.lengthInSec > 0.0001f);
+ const float normalizedT = t / roState.lengthInSec;
+ DebugAssert (normalizedT >= 0.0f);
+ DebugAssert (normalizedT <= 1.0f);
+
+ size_t count = ps.array_size();
+ const Vector3f velocityOffset = system.m_InitialModule.GetInheritVelocity() * initialVelocity;
+ for(size_t q = fromIndex; q < count; q++)
+ {
+ const float randomValue = GenerateRandom(ps.randomSeed[q] + kParticleSystemStartSpeedCurveId);
+ ps.velocity[q] *= Evaluate (system.m_InitialModule.GetSpeedCurve(), normalizedT, randomValue);
+ ps.velocity[q] += velocityOffset;
+ }
+
+ for(size_t q = fromIndex; q < count; ) // array size changes
+ {
+ // subFrameOffset allows particles to be spawned at increasing times, thus spacing particles within a single frame.
+ // For example if you spawn particles with high velocity you will get a continous streaming instead of a clump of particles.
+ const int particleIndex = q - fromIndex;
+ float subFrameOffset = (particleIndex < numContinuous) ? (float(particleIndex) + emissionState.m_ToEmitAccumulator) * emissionState.m_ParticleSpacing : 0.0f;
+ DebugAssert(subFrameOffset >= -0.01f);
+ DebugAssert(subFrameOffset <= 1.5f); // Not 1 due to possibly really bad precision
+ subFrameOffset = clamp01(subFrameOffset);
+
+ // Update from curves and apply forces etc.
+ UpdateModulesPreSimulationIncremental (system, roState, state, ps, q, q+1, subFrameOffset * dt);
+
+ // Position change due to where the emitter was at time of emission
+ ps.position[q] -= initialVelocity * (frameOffset + subFrameOffset) * dt;
+
+ // Position, rotation and energy change due to how much the particle has travelled since time of emission
+ // @TODO: Call Simulate instead?
+ ps.lifetime[q] -= subFrameOffset * dt;
+ if((ps.lifetime[q] < 0.0f) && (count > 0))
+ {
+ KillParticle(roState, state, ps, q, count);
+ continue;
+ }
+
+ ps.position[q] += (ps.velocity[q] + ps.animatedVelocity[q]) * subFrameOffset * dt;
+
+ if(ps.usesRotationalSpeed)
+ ps.rotation[q] += ps.rotationalSpeed[q] * subFrameOffset * dt;
+
+ if(system.m_SubModule->GetEnabled())
+ system.m_SubModule->Update (roState, state, ps, q, q+1, subFrameOffset * dt);
+
+ ++q;
+ }
+ ps.array_resize(count);
+}
+
+void ParticleSystem::UpdateModulesPreSimulationIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& particles, const size_t fromIndex, const size_t toIndex, float dt)
+{
+ const size_t count = particles.array_size();
+ system.m_InitialModule.Update (roState, state, particles, fromIndex, toIndex, dt);
+ if(system.m_RotationModule->GetEnabled())
+ system.m_RotationModule->Update (roState, state, particles, fromIndex, toIndex);
+ if(system.m_VelocityModule->GetEnabled())
+ system.m_VelocityModule->Update (roState, state, particles, fromIndex, toIndex);
+ if(system.m_ForceModule->GetEnabled())
+ system.m_ForceModule->Update (roState, state, particles, fromIndex, toIndex, dt);
+ if(system.m_ExternalForcesModule->GetEnabled())
+ system.m_ExternalForcesModule->Update (roState, state, particles, fromIndex, toIndex, dt);
+ if(system.m_ClampVelocityModule->GetEnabled())
+ system.m_ClampVelocityModule->Update (roState, state, particles, fromIndex, toIndex);
+ if(system.m_RotationBySpeedModule->GetEnabled())
+ system.m_RotationBySpeedModule->Update (roState, state, particles, fromIndex, toIndex);
+
+ Assert(count >= toIndex);
+ Assert(particles.array_size() == count);
+}
+
+void ParticleSystem::UpdateModulesPostSimulationIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& particles, const size_t fromIndex, float dt)
+{
+ const size_t count = particles.array_size();
+ if(system.m_SubModule->GetEnabled())
+ system.m_SubModule->Update (roState, state, particles, fromIndex, particles.array_size(), dt);
+ Assert(count == particles.array_size());
+
+ if(system.m_CollisionModule->GetEnabled())
+ {
+#if !ENABLE_MULTITHREADED_PARTICLES
+ PROFILER_AUTO(gParticleSystemUpdateCollisions, NULL)
+#endif
+ system.m_CollisionModule->Update (roState, state, particles, fromIndex, dt);
+ }
+}
+
+void ParticleSystem::UpdateModulesNonIncremental (const ParticleSystem& system, const ParticleSystemParticles& particles, ParticleSystemParticlesTempData& psTemp, size_t fromIndex, size_t toIndex)
+{
+ Assert(particles.array_size() == psTemp.particleCount);
+
+ for(int i = fromIndex; i < toIndex; i++)
+ psTemp.color[i] = particles.color[i];
+ for(int i = fromIndex; i < toIndex; i++)
+ psTemp.size[i] = particles.size[i];
+
+ if(system.m_ColorModule->GetEnabled())
+ system.m_ColorModule->Update (particles, psTemp.color, fromIndex, toIndex);
+ if(system.m_ColorBySpeedModule->GetEnabled())
+ system.m_ColorBySpeedModule->Update (particles, psTemp.color, fromIndex, toIndex);
+ if(system.m_SizeModule->GetEnabled())
+ system.m_SizeModule->Update (particles, psTemp.size, fromIndex, toIndex);
+ if(system.m_SizeBySpeedModule->GetEnabled())
+ system.m_SizeBySpeedModule->Update (particles, psTemp.size, fromIndex, toIndex);
+
+ if (gGraphicsCaps.needsToSwizzleVertexColors)
+ std::transform(&psTemp.color[fromIndex], &psTemp.color[toIndex], &psTemp.color[fromIndex], SwizzleColorForPlatform);
+
+ if(system.m_UVModule->GetEnabled())
+ {
+ // No other systems used sheet index yet, allocate!
+ if(!psTemp.sheetIndex)
+ {
+ psTemp.sheetIndex = ALLOC_TEMP_MANUAL(float, psTemp.particleCount);
+ for(int i = 0; i < fromIndex; i++)
+ psTemp.sheetIndex[i] = 0.0f;
+ }
+ system.m_UVModule->Update (particles, psTemp.sheetIndex, fromIndex, toIndex);
+ }
+ else if(psTemp.sheetIndex) // if this is present with disabled module, that means we have a combined buffer with one system not using UV module, just initislize to 0.0f
+ for(int i = fromIndex; i < toIndex; i++)
+ psTemp.sheetIndex[i] = 0.0f;
+}
+
+void ParticleSystem::UpdateModulesIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& particles, size_t fromIndex, float dt)
+{
+ UpdateModulesPreSimulationIncremental (system, roState, state, particles, fromIndex, particles.array_size(), dt);
+ SimulateParticles(roState, state, particles, fromIndex, dt);
+ UpdateModulesPostSimulationIncremental (system, roState, state, particles, fromIndex, dt);
+}
+
+void ParticleSystem::Update0 (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, float dt, bool fixedTimeStep)
+{
+ const Transform& transform = system.GetComponent (Transform);
+ Vector3f oldPosition = state.localToWorld.GetPosition();
+ state.localToWorld = transform.GetLocalToWorldMatrixNoScale ();
+ Matrix4x4f::Invert_General3D(state.localToWorld, state.worldToLocal);
+
+ if (IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_1_a1))
+ state.emitterScale = transform.GetWorldScaleLossy();
+ else
+ state.emitterScale = transform.GetLocalScale();
+
+ if (state.playing && (dt > 0.0001f))
+ {
+ const Vector3f position = transform.GetPosition();
+
+ if (roState.useLocalSpace)
+ state.emitterVelocity = Vector3f::zero;
+ else
+ state.emitterVelocity = (position - oldPosition) / dt;
+ }
+
+ AddStagingBuffer(system);
+
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(renderer && !renderer->GetScreenSpaceRotation())
+ ParticleSystemRenderer::SetUsesAxisOfRotationRec(system, true);
+
+ if(system.m_RotationModule->GetEnabled() || system.m_RotationBySpeedModule->GetEnabled())
+ system.SetUsesRotationalSpeed();
+
+ int subEmitterBirthTypeCount = system.m_SubModule->GetSubEmitterTypeCount(kParticleSystemSubTypeBirth);
+ if(system.m_SubModule->GetEnabled() && subEmitterBirthTypeCount)
+ system.SetUsesEmitAccumulator (subEmitterBirthTypeCount);
+
+ int subEmitterCount = SetupSubEmitters(system, *system.m_State);
+
+#if ENABLE_MULTITHREADED_PARTICLES
+ if(state.recordSubEmits)
+ {
+ const int numCommands = min(ParticleSystem::CalculateMaximumSubEmitterEmitCount(system, *system.m_State, dt, fixedTimeStep) * subEmitterCount, MAX_NUM_SUB_EMIT_CMDS);
+ ParticleSystemSubEmitCmdBuffer& buffer = system.m_State->subEmitterCommandBuffer;
+ Assert(NULL == buffer.commands);
+ buffer.commands = ALLOC_TEMP_MANUAL(SubEmitterEmitCommand, numCommands);
+ buffer.commandCount = 0;
+ buffer.maxCommandCount = numCommands;
+ }
+#endif
+
+ if(system.m_CollisionModule->GetEnabled())
+ system.m_CollisionModule->AllocateAndCache(roState, state);
+ if(system.m_ExternalForcesModule->GetEnabled())
+ system.m_ExternalForcesModule->AllocateAndCache(roState, state);
+}
+
+void ParticleSystem::Update1 (ParticleSystem& system, ParticleSystemParticles& ps, float dt, bool fixedTimeStep, bool useProcedural, int rayBudget)
+{
+ PROFILER_AUTO(gParticleSystemJobProfile, NULL)
+
+ const ParticleSystemReadOnlyState& roState = *system.m_ReadOnlyState;
+ ParticleSystemState& state = *system.m_State;
+ state.rayBudget = rayBudget;
+
+ // Exposed through script
+ dt *= std::max<float> (roState.speed, 0.0f);
+
+ float timeStep = GetTimeStep(dt, fixedTimeStep);
+ if(timeStep < 0.00001f)
+ return;
+
+ if (state.playing)
+ {
+ state.accumulatedDt += dt;
+
+ if(system.GetIsDistanceEmitter())
+ {
+ float t = state.t + state.accumulatedDt;
+ const float length = roState.lengthInSec;
+ t = roState.looping ? fmodf(t, length) : min(t, length);
+ size_t numContinuous = 0;
+ size_t amountOfParticlesToEmit = system.EmitFromModules (system, roState, state.emissionState, numContinuous, state.emitterVelocity, state.t, t, dt);
+ StartParticles(system, ps, state.t, t, dt, numContinuous, amountOfParticlesToEmit, 0.0f);
+ }
+
+ Update1Incremental(system, roState, state, ps, 0, timeStep, useProcedural);
+
+ if (useProcedural)
+ UpdateProcedural(system, roState, state, ps);
+ }
+
+ UpdateBounds(system, ps, state);
+}
+
+void ParticleSystem::Update2 (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, bool fixedTimeStep)
+{
+ if(state.subEmitterCommandBuffer.commandCount > 0)
+ PlaybackSubEmitterCommandBuffer(system, state, fixedTimeStep);
+ state.ClearSubEmitterCommandBuffer();
+
+ CollisionModule::FreeCache(state);
+ ExternalForcesModule::FreeCache(state);
+
+ AddStagingBuffer(system);
+
+ //
+ // Update renderer
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if (renderer)
+ {
+ MinMaxAABB result;
+ ParticleSystemRenderer::CombineBoundsRec(system, result, true);
+ renderer->Update (result);
+ }
+}
+
+// Returns true if update loop is executed at least once
+void ParticleSystem::Update1Incremental(ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt, bool useProcedural)
+{
+ ApplyStartDelay (state.delayT, state.accumulatedDt);
+
+ int numTimeSteps = 0;
+ const int numTimeStepsTotal = int(state.accumulatedDt / dt);
+
+ while (state.accumulatedDt >= dt)
+ {
+ const float prevT = state.t;
+ state.Tick (roState, dt);
+ const float t = state.t;
+ const bool timePassedDuration = t >= (roState.lengthInSec);
+ const float frameOffset = float(numTimeStepsTotal - 1 - numTimeSteps);
+
+ if(!roState.looping && timePassedDuration)
+ system.Stop();
+
+ // Update simulation
+ if (!useProcedural)
+ UpdateModulesIncremental(system, roState, state, ps, fromIndex, dt);
+ else
+ for (int i=0;i<state.emitReplay.size();i++)
+ state.emitReplay[i].aliveTime += dt;
+
+ // Emission
+ bool emit = !system.GetIsDistanceEmitter() && !state.stopEmitting;
+ if(emit)
+ {
+ size_t numContinuous = 0;
+ size_t amountOfParticlesToEmit = system.EmitFromModules (system, roState, state.emissionState, numContinuous, state.emitterVelocity, prevT, t, dt);
+ if(useProcedural)
+ StartParticlesProcedural(system, ps, prevT, t, dt, numContinuous, amountOfParticlesToEmit, frameOffset);
+ else
+ StartParticles(system, ps, prevT, t, dt, numContinuous, amountOfParticlesToEmit, frameOffset);
+ }
+
+ state.accumulatedDt -= dt;
+
+ AddStagingBuffer(system);
+
+ // Workaround for external forces being dependent on AABB (need to update it before the next time step)
+ if(!useProcedural && (state.accumulatedDt >= dt) && system.m_ExternalForcesModule->GetEnabled())
+ UpdateBounds(system, ps, state);
+
+ numTimeSteps++;
+ }
+}
+
+void ParticleSystem::UpdateProcedural (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps)
+{
+ DebugAssert(CheckSupportsProcedural(system));
+
+ // Clear all particles
+ ps.array_resize(0);
+
+ const Matrix4x4f localToWorld = !roState.useLocalSpace ? state.localToWorld : Matrix4x4f::identity;
+
+ // Emit all particles
+ for (int i=0; i<state.emitReplay.size(); i++)
+ {
+ const ParticleSystemEmitReplay& emit = state.emitReplay[i];
+
+#if UNITY_EDITOR
+ ParticleSystemEditor::ApplyRandomSeed(system, emit.randomSeed);
+#endif
+ //@TODO: remove passing m_State since that is very dangerous when making things procedural compatible
+ size_t previousParticleCount = ps.array_size();
+ system.m_InitialModule.GenerateProcedural (roState, state, ps, emit);
+
+ //@TODO: This can be moved out of the emit all particles loop...
+ if (system.m_ShapeModule.GetEnabled())
+ system.m_ShapeModule.Start (roState, state, ps, localToWorld, previousParticleCount, emit.t);
+
+ // Apply gravity & integrated velocity after shape module so that it picks up any changes done in shapemodule (for example rotating the velocity)
+ Vector3f gravity = system.m_InitialModule.GetGravity(roState, state);
+ float particleIndex = 0.0f;
+ const size_t particleCount = ps.array_size();
+ for (int q = previousParticleCount; q < particleCount; q++)
+ {
+ const float normalizedT = emit.t / roState.lengthInSec;
+ ps.velocity[q] *= Evaluate (system.m_InitialModule.GetSpeedCurve(), normalizedT, GenerateRandom(ps.randomSeed[q] + kParticleSystemStartSpeedCurveId));
+ Vector3f velocity = ps.velocity[q];
+ float frameOffset = (particleIndex + emit.emissionOffset) * emit.emissionGap * float(particleIndex < emit.numContinuous);
+ float aliveTime = emit.aliveTime + frameOffset;
+
+ ps.position[q] += velocity * aliveTime + gravity * aliveTime * aliveTime * 0.5F;
+ ps.velocity[q] += gravity * aliveTime;
+
+ particleIndex += 1.0f;
+ }
+
+ // If no particles were emitted we can get rid of the emit replay state...
+ if (previousParticleCount == ps.array_size())
+ {
+ state.emitReplay[i] = state.emitReplay.back();
+ state.emitReplay.pop_back();
+ i--;
+ }
+ }
+
+ if (system.m_RotationModule->GetEnabled())
+ system.m_RotationModule->UpdateProcedural (state, ps);
+
+ if (system.m_VelocityModule->GetEnabled())
+ system.m_VelocityModule->UpdateProcedural (roState, state, ps);
+
+ if (system.m_ForceModule->GetEnabled())
+ system.m_ForceModule->UpdateProcedural (roState, state, ps);
+
+ // Modules that are not supported by procedural
+ DebugAssert(!system.m_RotationBySpeedModule->GetEnabled()); // find out if possible to support it
+ DebugAssert(!system.m_ClampVelocityModule->GetEnabled()); // unsupported: (Need to compute velocity by deriving position curves...), possible to support?
+ DebugAssert(!system.m_CollisionModule->GetEnabled());
+ DebugAssert(!system.m_SubModule->GetEnabled()); // find out if possible to support it
+ DebugAssert(!system.m_ExternalForcesModule->GetEnabled());
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// // Editor only
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if UNITY_EDITOR
+void ParticleSystem::TransformChanged()
+{
+ if(!IsWorldPlaying() && ParticleSystemEditor::GetResimulation())
+ {
+ const Transform& transform = GetComponent (Transform);
+ Matrix4x4f newMatrix = transform.GetLocalToWorldMatrixNoScale ();
+ newMatrix.SetPosition(Vector3f::zero);
+ Matrix4x4f oldMatrix = m_State->localToWorld;
+ oldMatrix.SetPosition(Vector3f::zero);
+ bool rotationChanged = !CompareApproximately(oldMatrix, newMatrix);
+ if(m_ReadOnlyState->useLocalSpace || rotationChanged)
+ ParticleSystemEditor::PerformCompleteResimulation(this);
+ }
+}
+#endif
+