summaryrefslogtreecommitdiff
path: root/Runtime/Graphics/ParticleSystem
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Graphics/ParticleSystem')
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.cpp102
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.h29
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/CollisionModule.cpp603
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/CollisionModule.h62
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.cpp60
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ColorModule.cpp51
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ColorModule.h25
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/EmissionModule.cpp124
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/EmissionModule.h34
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.cpp118
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.h25
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ForceModule.cpp164
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ForceModule.h36
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/InitialModule.cpp193
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/InitialModule.h66
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.cpp141
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.h238
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.cpp51
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/RotationModule.cpp83
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/RotationModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp650
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/ShapeModule.h80
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.cpp50
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SizeModule.cpp41
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SizeModule.h27
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SubModule.cpp148
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/SubModule.h38
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/UVModule.cpp105
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/UVModule.h36
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/VelocityModule.cpp127
-rw-r--r--Runtime/Graphics/ParticleSystem/Modules/VelocityModule.h36
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.cpp122
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h42
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystem.cpp2110
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystem.h298
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemCommon.h48
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemCurves.cpp196
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h187
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemGradients.cpp27
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h87
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemParticle.cpp323
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h116
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.cpp1241
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h134
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemRendererTests.cpp29
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemUtils.cpp113
-rw-r--r--Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h48
-rw-r--r--Runtime/Graphics/ParticleSystem/PolynomialCurve.cpp405
-rw-r--r--Runtime/Graphics/ParticleSystem/PolynomialCurve.h229
-rw-r--r--Runtime/Graphics/ParticleSystem/PolynomialCurveTests.cpp259
53 files changed, 9665 insertions, 0 deletions
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.cpp
new file mode 100644
index 0000000..362fbff
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.cpp
@@ -0,0 +1,102 @@
+#include "UnityPrefix.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "ClampVelocityModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Misc/BuildSettings.h"
+
+inline float DampenOutsideLimit (float v, float limit, float dampen)
+{
+ float sgn = Sign (v);
+ float abs = Abs (v);
+ if (abs > limit)
+ abs = Lerp(abs, limit, dampen);
+ return abs * sgn;
+}
+
+ClampVelocityModule::ClampVelocityModule () : ParticleSystemModule(false)
+, m_SeparateAxis (false)
+, m_InWorldSpace (false)
+, m_Dampen (1.0f)
+{
+}
+
+template<ParticleSystemCurveEvalMode mode>
+void MagnitudeUpdateTpl(const MinMaxCurve& magnitude, ParticleSystemParticles& ps, size_t fromIndex, size_t toIndex, float dampen)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ float limit = Evaluate<mode> (magnitude, NormalizedTime(ps, q), GenerateRandom(ps.randomSeed[q] + kParticleSystemClampVelocityCurveId));
+ Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ vel = NormalizeSafe (vel) * DampenOutsideLimit (Magnitude (vel), limit, dampen);
+ ps.velocity[q] = vel - ps.animatedVelocity[q];
+ }
+}
+
+void ClampVelocityModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ if (m_SeparateAxis)
+ {
+ Matrix4x4f matrix;
+ Matrix4x4f invMatrix;
+ bool transform;
+ if(IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_0_a1))
+ transform = GetTransformationMatrices(matrix, invMatrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+ else
+ transform = false; // Revert to old broken behavior
+
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemClampVelocityCurveId);
+ const float time = NormalizedTime(ps, q);
+
+ Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ if(transform)
+ vel = matrix.MultiplyVector3 (vel);
+ const Vector3f limit (Evaluate (m_X, time, random.x), Evaluate (m_Y, time, random.y), Evaluate (m_Z, time, random.z));
+ vel.x = DampenOutsideLimit (vel.x, limit.x, m_Dampen);
+ vel.y = DampenOutsideLimit (vel.y, limit.y, m_Dampen);
+ vel.z = DampenOutsideLimit (vel.z, limit.z, m_Dampen);
+ vel = vel - ps.animatedVelocity[q];
+ if(transform)
+ vel = invMatrix.MultiplyVector3 (vel);
+ ps.velocity[q] = vel;
+ }
+ }
+ else
+ {
+ if(m_Magnitude.minMaxState == kMMCScalar)
+ MagnitudeUpdateTpl<kEMScalar> (m_Magnitude, ps, fromIndex, toIndex, m_Dampen);
+ else if(m_Magnitude.IsOptimized() && m_Magnitude.UsesMinMax())
+ MagnitudeUpdateTpl<kEMOptimizedMinMax> (m_Magnitude, ps, fromIndex, toIndex, m_Dampen);
+ else if(m_Magnitude.IsOptimized())
+ MagnitudeUpdateTpl<kEMOptimized> (m_Magnitude, ps, fromIndex, toIndex, m_Dampen);
+ else
+ MagnitudeUpdateTpl<kEMSlow> (m_Magnitude, ps, fromIndex, toIndex, m_Dampen);
+ }
+}
+
+void ClampVelocityModule::CheckConsistency ()
+{
+ m_Dampen = clamp<float> (m_Dampen, 0.0f, 1.0f);
+ m_X.SetScalar(std::max<float> (0.0f, m_X.GetScalar()));
+ m_Y.SetScalar(std::max<float> (0.0f, m_Y.GetScalar()));
+ m_Z.SetScalar(std::max<float> (0.0f, m_Z.GetScalar()));
+ m_Magnitude.SetScalar(std::max<float> (0.0f, m_Magnitude.GetScalar()));
+}
+
+
+template<class TransferFunction>
+void ClampVelocityModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_X, "x");
+ transfer.Transfer (m_Y, "y");
+ transfer.Transfer (m_Z, "z");
+ transfer.Transfer (m_Magnitude, "magnitude");
+ transfer.Transfer (m_SeparateAxis, "separateAxis");
+ transfer.Transfer (m_InWorldSpace, "inWorldSpace"); transfer.Align ();
+ transfer.Transfer (m_Dampen, "dampen");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ClampVelocityModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.h
new file mode 100644
index 0000000..e315d61
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ClampVelocityModule.h
@@ -0,0 +1,29 @@
+#ifndef SHURIKENMODULECLAMPVELOCITY_H
+#define SHURIKENMODULECLAMPVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+
+class ClampVelocityModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ClampVelocityModule)
+ ClampVelocityModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex);
+ void CheckConsistency ();
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_X;
+ MinMaxCurve m_Y;
+ MinMaxCurve m_Z;
+ MinMaxCurve m_Magnitude;
+ bool m_InWorldSpace;
+ bool m_SeparateAxis;
+ float m_Dampen;
+};
+
+#endif // SHURIKENMODULECLAMPVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.cpp
new file mode 100644
index 0000000..9e16bde
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.cpp
@@ -0,0 +1,603 @@
+#include "UnityPrefix.h"
+#include <float.h>
+#include "CollisionModule.h"
+
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Input/TimeManager.h"
+#include "Runtime/Interfaces/IRaycast.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+#include "Editor/Src/Utility/DebugPrimitives.h"
+
+struct ParticleSystemCollisionParameters
+{
+ float bounceFactor;
+ float energyLossOnCollision;
+ float minKillSpeedSqr;
+ float particleRadius;
+ float dampen;
+
+ PlaneColliderCache* planeColliderCache;
+ IRaycast* raycaster;
+ size_t rayBudget;
+ size_t nextParticleToTrace;
+ float voxelSize;
+};
+
+struct ColliderInfo
+{
+ Plane m_CollisionPlane;
+ bool m_Traced;
+ int m_ColliderInstanceID;
+ int m_RigidBodyOrColliderInstanceID;
+};
+
+struct CollisionInfo
+{
+ CollisionInfo():m_NumWorldCollisions(0),m_NumCachedCollisions(0),m_NumPlaneCollisions(0),m_Colliders(NULL) {}
+
+ size_t m_NumWorldCollisions;
+ size_t m_NumCachedCollisions;
+ size_t m_NumPlaneCollisions;
+ ColliderInfo* m_Colliders;
+
+ size_t AllCollisions() const { return m_NumWorldCollisions+m_NumCachedCollisions+m_NumPlaneCollisions; }
+};
+
+/// @TODO: Why does Vector3f has a constructor? WTF.
+inline void CalculateCollisionResponse(const ParticleSystemReadOnlyState& roState,
+ ParticleSystemState& state,
+ ParticleSystemParticles& ps,
+ const size_t q,
+ const ParticleSystemCollisionParameters& params,
+ const Vector3f& position,
+ const Vector3f& velocity,
+ const HitInfo& hitInfo)
+{
+ // Reflect + dampen
+ Vector3f positionOffset = ReflectVector (position - hitInfo.intersection, hitInfo.normal) * params.dampen;
+ Vector3f newVelocity = ReflectVector(velocity, hitInfo.normal) * params.dampen;
+
+ // Apply bounce
+ positionOffset -= hitInfo.normal * (Dot(positionOffset, hitInfo.normal)) * params.bounceFactor;
+ newVelocity -= hitInfo.normal * Dot(newVelocity, hitInfo.normal) * params.bounceFactor;
+
+ ps.position[q] = hitInfo.intersection + positionOffset;
+ ps.velocity[q] = newVelocity - ps.animatedVelocity[q];
+
+ for(int s = 0; s < state.numCachedSubDataCollision; s++)
+ {
+ ParticleSystemEmissionState emissionState;
+ RecordEmit(emissionState, state.cachedSubDataCollision[s], roState, state, ps, kParticleSystemSubTypeCollision, s, q, 0.0f, 0.0001f, 1.0f);
+ }
+ ps.lifetime[q] -= params.energyLossOnCollision * ps.startLifetime[q];
+
+ if (ps.GetUsesCollisionEvents () && !(hitInfo.colliderInstanceID == 0))
+ {
+ Vector3f wcIntersection = hitInfo.intersection;
+ Vector3f wcNormal = hitInfo.normal;
+ Vector3f wcVelocity = velocity;
+ if ( roState.useLocalSpace )
+ {
+ wcIntersection = state.localToWorld.MultiplyPoint3 (wcIntersection);
+ wcNormal = state.localToWorld.MultiplyVector3 (wcNormal);
+ wcVelocity = state.localToWorld.MultiplyVector3 (wcVelocity);
+ }
+ ps.collisionEvents.AddEvent (ParticleCollisionEvent (wcIntersection, wcNormal, wcVelocity, hitInfo.colliderInstanceID, hitInfo.rigidBodyOrColliderInstanceID));
+ //printf_console (Format ("Intersected '%s' -> id: %d -> go id: %d.\n", hitInfo.collider->GetName (), hitInfo.collider->GetInstanceID (), hitInfo.collider->GetGameObject ().GetInstanceID ()).c_str ());
+}
+}
+
+CollisionModule::CollisionModule () : ParticleSystemModule(false)
+, m_Type(0)
+, m_Dampen(0.0f)
+, m_Bounce(1.0f)
+, m_EnergyLossOnCollision(0.0f)
+, m_MinKillSpeed(0.0f)
+, m_ParticleRadius(0.01f)
+, m_Quality(0)
+, m_VoxelSize(0.5f)
+, m_CollisionMessages(false)
+{
+ m_CollidesWith.m_Bits = 0xFFFFFFFF;
+}
+
+void CollisionModule::AllocateAndCache(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state)
+{
+ Assert(!state.cachedCollisionPlanes);
+
+ Matrix4x4f matrix = Matrix4x4f::identity;
+ if(roState.useLocalSpace)
+ matrix = state.worldToLocal;
+
+ state.numCachedCollisionPlanes = 0;
+ for (unsigned i=0; i<kMaxNumPrimitives; ++i)
+ {
+ if (m_Primitives[i].IsNull ())
+ continue;
+ state.numCachedCollisionPlanes++;
+ }
+
+ state.cachedCollisionPlanes = ALLOC_TEMP_MANUAL(Plane, state.numCachedCollisionPlanes);
+
+ int planeCount = 0;
+
+ for (unsigned i=0; i<kMaxNumPrimitives; ++i)
+ {
+ if (m_Primitives[i].IsNull ())
+ continue;
+
+ const Transform* transform = m_Primitives[i]->QueryComponent(Transform);
+ const Vector3f position = matrix.MultiplyPoint3(transform->GetPosition());
+
+ Assert(planeCount <= state.numCachedCollisionPlanes);
+ const Vector3f normal = matrix.MultiplyVector3(RotateVectorByQuat (transform->GetRotation (), Vector3f::yAxis));
+ state.cachedCollisionPlanes[planeCount].SetNormalAndPosition (normal, position);
+ state.cachedCollisionPlanes[planeCount].NormalizeRobust();
+ planeCount++;
+ }
+}
+
+void CollisionModule::FreeCache(ParticleSystemState& state)
+{
+ if(state.cachedCollisionPlanes)
+ {
+ FREE_TEMP_MANUAL(state.cachedCollisionPlanes);
+ state.cachedCollisionPlanes = 0;
+ state.numCachedCollisionPlanes = 0;
+ }
+}
+
+// read the plane cache for a given range
+size_t ReadCache(const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, const ParticleSystemParticles& ps, const ParticleSystemCollisionParameters& params, CollisionInfo& collision, const size_t fromIndex, const size_t& toIndex, const float dt)
+{
+ size_t numIntersections = 0;
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ // initialise plane to value that guarantees no intersection
+ collision.m_Colliders[q].m_CollisionPlane.distance = FLT_MAX;
+ collision.m_Colliders[q].m_Traced = false;
+ collision.m_Colliders[q].m_ColliderInstanceID = -1;
+ collision.m_Colliders[q].m_RigidBodyOrColliderInstanceID = -1;
+
+ // build start/end points
+ Vector3f to = ps.position[q];
+ const Vector3f v = ps.velocity[q] + ps.animatedVelocity[q];
+ const Vector3f d = v * dt;
+ Vector3f from = to - d;
+ // convert to WC
+ if ( roState.useLocalSpace )
+ {
+ from = state.localToWorld.MultiplyPoint3(from);
+ to = state.localToWorld.MultiplyPoint3(to);
+ }
+
+ // lookup the cache
+ Plane plane;
+ int colliderInstanceID;
+ int rigidBodyOrColliderInstanceID;
+ if ( params.planeColliderCache->Find (from, to-from, plane, colliderInstanceID, rigidBodyOrColliderInstanceID, params.voxelSize) )
+ {
+ collision.m_Colliders[q].m_CollisionPlane = plane;
+ collision.m_Colliders[q].m_ColliderInstanceID = colliderInstanceID;
+ collision.m_Colliders[q].m_RigidBodyOrColliderInstanceID = rigidBodyOrColliderInstanceID;
+ numIntersections++;
+ }
+ }
+ return numIntersections;
+}
+
+// Perform ray casts. Ray casts are done in WC and results are transformed back into sim space if necessary.
+// There are three sets of indices:
+// [fromIndex ; params.nextParticleToTrace[ for these we do caching if the cache is available
+// [params.nextParticleToTrace ; params.nextParticleToTrace + params.rayBudget[ for these we trace fresh rays
+// [params.nextParticleToTrace + params.rayBudget ; toIndex[ for these we do caching if the cache is available
+CollisionInfo WorldCollision(const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, const ParticleSystemParticles& ps, const ParticleSystemCollisionParameters& params, const size_t fromIndex, const int filter, const float dt)
+{
+ CollisionInfo collisionCounter;
+ const bool approximate = ( params.planeColliderCache ? true : false );
+ size_t numIntersections = 0;
+
+ const size_t toIndex = ps.array_size();
+
+ // pre range that is cached
+ const size_t traceRangeFrom = approximate ? params.nextParticleToTrace : fromIndex;
+ const size_t traceRangeTo = approximate ? std::min(params.nextParticleToTrace+params.rayBudget, toIndex) : toIndex;
+
+ const size_t fromIndex0 = fromIndex;
+ const size_t toIndex0 = traceRangeFrom;
+ // range to actually ray trace
+ const size_t fromIndex1 = traceRangeFrom;
+ const size_t toIndex1 = traceRangeTo;
+ // post range that is cached
+ const size_t fromIndex2 = traceRangeTo;
+ const size_t toIndex2 = toIndex;
+
+ collisionCounter.m_Colliders = ALLOC_TEMP_MANUAL(ColliderInfo, ps.array_size());
+ collisionCounter.m_NumCachedCollisions = (toIndex0-fromIndex0)+(toIndex2-fromIndex2);
+ collisionCounter.m_NumWorldCollisions = toIndex1-fromIndex1;
+
+ if ( toIndex1-fromIndex1 > 0 )
+ {
+ // batch trace selected range
+ dynamic_array< BatchedRaycast > rayBatch(toIndex1-fromIndex1,kMemTempAlloc);
+ dynamic_array< BatchedRaycastResult > rayResults(toIndex1-fromIndex1,kMemTempAlloc);
+ // build request array
+ size_t i = 0;
+ for (size_t q = fromIndex1; q < toIndex1; ++q, ++i)
+ {
+ // build start/end points
+ const Vector3f to = ps.position[q];
+ const Vector3f v = ps.velocity[q] + ps.animatedVelocity[q];
+ const Vector3f d = v * dt;
+ const Vector3f from = to - d;
+
+ if ( approximate )
+ {
+ // extend ray to trace across the entire voxel (and then some)
+ const float voxSize = std::max(params.voxelSize,params.voxelSize*kVoxelHeightMultiplier);
+ const Vector3f displacement = to-from;
+
+ ///@TODO: handle magnitude 0. Should we skip the raycast???
+ const Vector3f direction = NormalizeFast(displacement);
+ const Vector3f extendedTo = from + direction * voxSize;
+
+ // insert into batch
+ rayBatch[i] = BatchedRaycast(q,from,extendedTo);
+ }
+ else
+ rayBatch[i] = BatchedRaycast(q,from,to);
+
+ // initialise plane to value that guarantees no intersection
+ collisionCounter.m_Colliders[q].m_CollisionPlane.distance = FLT_MAX;
+ collisionCounter.m_Colliders[q].m_Traced = (!approximate);
+ collisionCounter.m_Colliders[q].m_ColliderInstanceID = -1;
+ collisionCounter.m_Colliders[q].m_RigidBodyOrColliderInstanceID = -1;
+ }
+ // convert to WC
+ if ( roState.useLocalSpace )
+ {
+ const Matrix4x4f m = state.localToWorld;
+ for (size_t i = 0; i < rayBatch.size(); ++i)
+ {
+ rayBatch[i].from = m.MultiplyPoint3(rayBatch[i].from);
+ rayBatch[i].to = m.MultiplyPoint3(rayBatch[i].to);
+ }
+ }
+ // trace the rays
+ const size_t numIx = params.raycaster->BatchIntersect( rayBatch, rayResults, filter, approximate );
+
+ // convert back to local space
+ if ( roState.useLocalSpace )
+ {
+ const Matrix4x4f m = state.worldToLocal;
+ for (size_t i = 0; i < numIx; ++i)
+ {
+ // the plane intersection was computed in WC, transform intersection to local space.
+ rayResults[i].hitInfo.intersection = m.MultiplyPoint3( rayResults[i].hitInfo.intersection );
+ rayResults[i].hitInfo.normal = m.MultiplyVector3( rayResults[i].hitInfo.normal );
+ }
+ }
+
+ // store planes in the particles that intersected something
+ for (size_t i = 0; i < numIx; ++i)
+ {
+ const size_t q = rayBatch[rayResults[i].index].index;
+ collisionCounter.m_Colliders[q].m_CollisionPlane.normal = rayResults[i].hitInfo.normal;
+ collisionCounter.m_Colliders[q].m_CollisionPlane.distance = -Dot(rayResults[i].hitInfo.intersection, rayResults[i].hitInfo.normal);
+ collisionCounter.m_Colliders[q].m_ColliderInstanceID = rayResults[i].hitInfo.colliderInstanceID;
+ collisionCounter.m_Colliders[q].m_RigidBodyOrColliderInstanceID = rayResults[i].hitInfo.rigidBodyOrColliderInstanceID;
+ }
+
+ // store intersections in cache
+ if (approximate)
+ {
+ for (size_t i = 0; i < numIx; ++i)
+ {
+ const size_t r = rayResults[i].index;
+ const size_t q = rayBatch[rayResults[i].index].index;
+ params.planeColliderCache->Replace (rayBatch[r].from, rayBatch[r].to-rayBatch[r].from, Plane(collisionCounter.m_Colliders[q].m_CollisionPlane), collisionCounter.m_Colliders[q].m_ColliderInstanceID, collisionCounter.m_Colliders[q].m_RigidBodyOrColliderInstanceID, params.voxelSize);
+ }
+ }
+ numIntersections += numIx;
+ }
+
+ // process pre cache range
+ if ( toIndex0-fromIndex0 > 0 )
+ {
+ numIntersections += ReadCache(roState, state, ps, params, collisionCounter, fromIndex0, toIndex0, dt);
+ }
+ // process post cache range
+ if ( toIndex2-fromIndex2 > 0 )
+ {
+ numIntersections += ReadCache(roState, state, ps, params, collisionCounter, fromIndex2, toIndex2, dt);
+ }
+
+ return collisionCounter;
+}
+
+
+// Plane collide all particles in simulation space (remember the cached planes are defined in sim space).
+CollisionInfo PlaneCollision(const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, const ParticleSystemParticles& ps, const ParticleSystemCollisionParameters& params, const int fromIndex, const float dt)
+{
+ CollisionInfo collisionInfo;
+ collisionInfo.m_Colliders = ALLOC_TEMP_MANUAL(ColliderInfo, ps.array_size());
+
+ const bool newBehaviour = IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_0_a1);
+
+ size_t toIndex = ps.array_size();
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ // initialise to value that guarantees no intersection
+ collisionInfo.m_Colliders[q].m_CollisionPlane.distance = FLT_MAX;
+
+ const Vector3f position = ps.position[q];
+ const Vector3f velocity = ps.velocity[q] + ps.animatedVelocity[q];
+ const Vector3f displacement = velocity * dt;
+ const Vector3f origin = position - displacement;
+
+ // walk the planes
+ for (unsigned i=0; i<state.numCachedCollisionPlanes; ++i)
+ {
+ const Plane plane = state.cachedCollisionPlanes[i];
+
+ if ( newBehaviour )
+ {
+ // new behaviour:
+ // plane collisions are single sided only, all particles 'behind' the plane will be forced onto the plane
+ const float dist = plane.GetDistanceToPoint(position);
+ if (dist > params.particleRadius)
+ continue;
+ }
+ else
+ {
+ // old (but fixed) behaviour, particles can collide with both front and back plane
+ const float d0 = plane.GetDistanceToPoint(origin);
+ const float d1 = plane.GetDistanceToPoint(position);
+ const bool sameSide = ( (d0 > 0.0f && d1 > 0.0f) || (d0 <= 0.0f && d1 <= 0.0f) );
+ const float aD0 = Abs(d0);
+ const float aD1 = Abs(d1);
+ if ( sameSide && aD0 > params.particleRadius && aD1 > params.particleRadius )
+ continue;
+ }
+
+ collisionInfo.m_Colliders[q].m_CollisionPlane = plane;
+ collisionInfo.m_Colliders[q].m_ColliderInstanceID = 0;
+ collisionInfo.m_Colliders[q].m_RigidBodyOrColliderInstanceID = 0;
+ collisionInfo.m_NumPlaneCollisions++;
+ break;
+ }
+ }
+
+ return collisionInfo;
+}
+
+// Compute the collision plane to use for each particle
+CollisionInfo UpdateCollisionPlanes(bool worldCollision, UInt32 collisionFilter, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, const ParticleSystemCollisionParameters& params, ParticleSystemParticles& ps, size_t fromIndex, float dt)
+{
+ if( worldCollision )
+ {
+ // Check if we support raycasting
+ if (params.raycaster == NULL)
+ return CollisionInfo();
+
+ ////@TODO: dtPerParticle
+ return WorldCollision(roState, state, ps, params, fromIndex, collisionFilter, dt);
+ }
+ else
+ {
+ ////@TODO: dtPerParticle
+ return PlaneCollision(roState, state, ps, params, fromIndex, dt);
+ }
+}
+
+// Collide the particles against their selected planes and update collision response - this is done purely in simulation space
+static const float kEpsilon = 0.000001f;
+static const float kRayEpsilon = 0.00001f;
+void PerformPlaneCollisions(bool worldCollision, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, const ParticleSystemCollisionParameters& params, const CollisionInfo& collisionInfo, const int fromIndex, const float dt)
+{
+ // for world collisions we use and epsilon but for plane intersections we use the particle radius
+ const float radius = worldCollision ? kRayEpsilon : params.particleRadius;
+ const bool newBehaviour = IS_CONTENT_NEWER_OR_SAME(kUnityVersion4_0_a1);
+ //const bool approximate = ( params.planeColliderCache ? true : false );
+
+ // collide with plane
+ size_t toIndex = ps.array_size();
+
+ for (size_t q = fromIndex; q < toIndex; q++)
+ {
+ // check if the actual ray was traced in which case we want to disable the ray testing. Note: does not apply to approximate mode as we trace longer rays to improve cache quality
+ const bool tracedParticle = collisionInfo.m_Colliders[q].m_Traced;
+ const Plane plane = collisionInfo.m_Colliders[q].m_CollisionPlane;
+ if ( plane.distance != FLT_MAX )
+ {
+ const Vector3f position = ps.position[q];
+ const Vector3f velocity = ps.velocity[q] + ps.animatedVelocity[q];
+ const Vector3f displacement = velocity * dt;
+ const Vector3f origin = position - displacement;
+
+ HitInfo hit;
+ hit.normal = plane.GetNormal();
+ hit.colliderInstanceID = collisionInfo.m_Colliders[q].m_ColliderInstanceID;
+ hit.rigidBodyOrColliderInstanceID = collisionInfo.m_Colliders[q].m_RigidBodyOrColliderInstanceID;
+
+ if ( worldCollision )
+ {
+ // plane ray dot product
+ const float VdN = Dot( plane.GetNormal(), displacement );
+ if ( !tracedParticle && VdN >= 0 )
+ continue; // only pick up front face intersections
+
+ // Recreate hit point.
+ const float t = -( Dot( origin, plane.GetNormal() ) + plane.distance ) / VdN;
+ if ( !tracedParticle && (t < 0 || t > 1) )
+ continue;
+
+ // build intersection description from t value and ray
+ hit.intersection = origin + displacement * t;
+
+ // Adjust intersection along normal to make sure the particle doesn't fall through geometry when it comes to rest.
+ // This is also an issue when dampen and bounce is zero in which case CalculateCollisionResponse will set the particle
+ // position to *exactly* the intersection point, which will then have issues next time the intersection is executed
+ // where it will try and compare two floating point numbers which are equal and the intersection test will come out
+ // either way depending on fp accuracy
+ //
+ // for world collisions we use and epsilon but for plane intersections we use the particle radius
+ hit.intersection += radius * hit.normal;
+ }
+ else
+ {
+ if (newBehaviour)
+ {
+ const float dist = plane.GetDistanceToPoint(position);
+
+ if (dist > radius)
+ continue;
+
+ const float VdN = Dot( plane.GetNormal(), velocity );
+ if (VdN == 0.0F || VdN == -0.0F)
+ continue;
+
+ const float t = -( Dot( position, plane.GetNormal() ) + plane.distance - radius ) / VdN;
+
+ hit.intersection = position + velocity * t;
+ }
+ else
+ {
+ const float OriginDotPlane = Dot (plane.GetNormal(), origin);
+ const float signedDistanceToOrigin = OriginDotPlane + plane.distance;
+ const float PositionDotPlane = Dot (plane.GetNormal(), position);
+ const float signedDistanceToPosition = PositionDotPlane + plane.distance;
+ const bool originInside = Abs(signedDistanceToOrigin)<radius;
+ const bool positionInside = Abs(signedDistanceToPosition)<=radius;
+ const bool oppositeSide = !( (signedDistanceToOrigin > 0.0f && signedDistanceToPosition > 0.0f) || (signedDistanceToOrigin <= 0.0f && signedDistanceToPosition <= 0.0f) );
+
+ // if the points are both inside or outside the radius we can bail if they're not on opposite sides outside the radius
+ if ( originInside==positionInside && !(oppositeSide && !originInside && !positionInside ) )
+ continue;
+
+ // compute the side of the face we are on - this determines if we are trying to intersect with the front or back face of the plane
+ const float signOrigin = signedDistanceToOrigin < 0.0 ? -1.f : 1.f;
+ const float signedRadius = radius*signOrigin;
+
+ // check the direction of the ray is opposite to the plane normal (the sign flips the normal as appropriate)
+ const float VdN = Dot( plane.GetNormal(), velocity );
+ if (VdN*signOrigin >= 0)
+ continue;
+
+ // calculate intersection point
+ const float t = -( signedDistanceToPosition - signedRadius ) / VdN;
+ hit.intersection = position + velocity * t;
+ }
+ }
+
+ // compute the bounce
+ CalculateCollisionResponse(roState, state, ps, q, params, ps.position[q], ps.velocity[q] + ps.animatedVelocity[q], hit);
+
+ // Kill particle?
+ const float speedSqr = SqrMagnitude(ps.velocity[q] + ps.animatedVelocity[q]);
+ if (ps.lifetime[q] < 0.0f || speedSqr < params.minKillSpeedSqr)
+ {
+ // when killing a particle the last element from the array is pulled into the position of the killed particle and toIndex is updated accordingly
+ // we do a q-- in order to make sure the new particle at q is also collided
+ collisionInfo.m_Colliders[q] = collisionInfo.m_Colliders[toIndex-1];
+ KillParticle(roState, state, ps, q, toIndex);
+ q--;
+ }
+ }
+ }
+
+ // resize array to account for killed particles
+ ps.array_resize(toIndex);
+}
+
+// Update collision for the particle system.
+void CollisionModule::Update (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt )
+{
+ // any work todo?
+ if (fromIndex == ps.array_size())
+ return;
+
+ ps.SetUsesCollisionEvents (m_CollisionMessages);
+
+ // setup params
+ ParticleSystemCollisionParameters params;
+ params.bounceFactor = 1.0f - m_Bounce;
+ params.energyLossOnCollision = m_EnergyLossOnCollision;
+ params.minKillSpeedSqr = m_MinKillSpeed * m_MinKillSpeed;
+ params.particleRadius = m_ParticleRadius;
+ params.dampen = 1.0f - m_Dampen;
+ params.planeColliderCache = ( IsApproximate() ? &m_ColliderCache : NULL );
+ params.raycaster = GetRaycastInterface();
+ params.rayBudget = state.rayBudget;
+ params.voxelSize = m_VoxelSize;
+ params.nextParticleToTrace = ( state.nextParticleToTrace >= ps.array_size() ? fromIndex : std::max(state.nextParticleToTrace,fromIndex) );
+
+ // update particle collision planes
+ const CollisionInfo collisionCounter = UpdateCollisionPlanes(m_Type != kPlaneCollision, m_CollidesWith.m_Bits, roState, state, params, ps, fromIndex, dt);
+
+ // update collider index
+ state.nextParticleToTrace = params.nextParticleToTrace + params.rayBudget;
+
+ // decrement ray budget
+ state.rayBudget = (state.rayBudget>collisionCounter.m_NumWorldCollisions ? state.rayBudget-collisionCounter.m_NumWorldCollisions : 0);
+
+ // early out if there were no collisions at all
+ if ( collisionCounter.AllCollisions() <= 0 )
+ {
+ FREE_TEMP_MANUAL(collisionCounter.m_Colliders);
+ return;
+ }
+
+ // perform plane collisions
+ PerformPlaneCollisions(m_Type != kPlaneCollision, roState, state, ps, params, collisionCounter, fromIndex, dt);
+
+ FREE_TEMP_MANUAL(collisionCounter.m_Colliders);
+
+ if (ps.GetUsesCollisionEvents ())
+ {
+ ps.collisionEvents.SortCollisionEventThreadArray ();
+ }
+}
+
+void CollisionModule::CheckConsistency ()
+{
+ m_Dampen = clamp<float> (m_Dampen, 0.0f, 1.0f);
+ m_Bounce = clamp<float> (m_Bounce, 0.0f, 2.0f);
+ m_EnergyLossOnCollision = clamp<float> (m_EnergyLossOnCollision, 0.0f, 1.0f);
+ m_ParticleRadius = max<float>(m_ParticleRadius, 0.01f);
+}
+
+template<class TransferFunction>
+void CollisionModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Type, "type");
+
+ const char* kPrimitiveNames [kMaxNumPrimitives] = { "plane0", "plane1", "plane2", "plane3", "plane4", "plane5"};
+ for(int i = 0; i < kMaxNumPrimitives; i++)
+ transfer.Transfer (m_Primitives[i], kPrimitiveNames[i]);
+
+ transfer.Transfer (m_Dampen, "dampen");
+ transfer.Transfer (m_Bounce, "bounce");
+ transfer.Transfer (m_EnergyLossOnCollision, "energyLossOnCollision");
+ transfer.Transfer (m_MinKillSpeed, "minKillSpeed");
+ transfer.Transfer (m_ParticleRadius, "particleRadius");
+ transfer.Align();
+ transfer.Transfer (m_CollidesWith, "collidesWith");
+ transfer.Transfer (m_Quality, "quality");
+ transfer.Align();
+ transfer.Transfer (m_VoxelSize, "voxelSize");
+ transfer.Transfer (m_CollisionMessages, "collisionMessages");
+}
+
+INSTANTIATE_TEMPLATE_TRANSFER(CollisionModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.h b/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.h
new file mode 100644
index 0000000..77b5541
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/CollisionModule.h
@@ -0,0 +1,62 @@
+#pragma once
+#include "ParticleSystemModule.h"
+#include "Runtime/BaseClasses/BitField.h"
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Geometry/Plane.h"
+#include "Runtime/Utilities/SpatialHash.h"
+
+struct ParticleSystemParticles;
+class Transform;
+
+class CollisionModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (CollisionModule)
+ CollisionModule ();
+
+ void AllocateAndCache(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state);
+ static void FreeCache(ParticleSystemState& state);
+
+#if UNITY_EDITOR
+ float GetEnergyLoss() const { return m_EnergyLossOnCollision; }
+ void SetEnergyLoss(float value) { m_EnergyLossOnCollision = value; };
+ float GetMinKillSpeed() const { return m_MinKillSpeed; }
+ void SetMinKillSpeed(float value) { m_MinKillSpeed = value; };
+#endif
+
+ bool GetUsesCollisionMessages () const {return m_CollisionMessages;}
+ bool IsWorldCollision() const {return m_Type==kWorldCollision;}
+ bool IsApproximate() const {return m_Quality>0;}
+ int GetQuality() const {return m_Quality;}
+
+ void Update (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt);
+
+ void CheckConsistency ();
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ void ClearPrimitives();
+
+ enum { kPlaneCollision, kWorldCollision };
+ enum { kMaxNumPrimitives = 6 };
+
+ PlaneColliderCache m_ColliderCache;
+
+ // Serialized
+ int m_Type;
+ float m_Dampen;
+ float m_Bounce;
+ float m_EnergyLossOnCollision;
+ float m_MinKillSpeed;
+ float m_ParticleRadius;
+ /// Collides the particles with every collider whose layerMask & m_CollidesWith != 0
+ BitField m_CollidesWith;
+ /// Perform approximate world particle collisions
+ int m_Quality; // selected quality, 0 is high (no approximations), 1 is medium (approximate), 2 is low (approximate)
+ float m_VoxelSize;
+ bool m_CollisionMessages;
+ PPtr<Transform> m_Primitives [kMaxNumPrimitives];
+};
+
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.cpp
new file mode 100644
index 0000000..0437322
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.cpp
@@ -0,0 +1,60 @@
+#include "UnityPrefix.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "ColorByVelocityModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+template<MinMaxGradientEvalMode mode>
+void UpdateTpl(const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, const MinMaxGradient& gradient, const OptimizedMinMaxGradient& optGradient, const Vector2f offsetScale, size_t fromIndex, size_t toIndex)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ const float time = InverseLerpFast01 (offsetScale, Magnitude(vel));
+ const int random = GenerateRandomByte(ps.randomSeed[q] + kParticleSystemColorByVelocityGradientId);
+
+ ColorRGBA32 value;
+ if(mode == kGEMGradient)
+ value = EvaluateGradient (optGradient, time);
+ else if(mode == kGEMGradientMinMax)
+ value = EvaluateRandomGradient (optGradient, time, random);
+ else
+ value = Evaluate (gradient, time, random);
+
+ colorTemp[q] *= value;
+ }
+}
+
+ColorBySpeedModule::ColorBySpeedModule () : ParticleSystemModule(false)
+, m_Range (0.0f, 1.0f)
+{}
+
+void ColorBySpeedModule::Update (const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, size_t fromIndex, size_t toIndex)
+{
+ DebugAssert(toIndex <= ps.array_size());
+
+ Vector2f offsetScale = CalculateInverseLerpOffsetScale (m_Range);
+ OptimizedMinMaxGradient gradient;
+ m_Gradient.InitializeOptimized(gradient);
+ if(m_Gradient.minMaxState == kMMGGradient)
+ UpdateTpl<kGEMGradient>(ps, colorTemp, m_Gradient, gradient, offsetScale, fromIndex, toIndex);
+ else if(m_Gradient.minMaxState == kMMGRandomBetweenTwoGradients)
+ UpdateTpl<kGEMGradientMinMax>(ps, colorTemp, m_Gradient, gradient, offsetScale, fromIndex, toIndex);
+ else
+ UpdateTpl<kGEMSlow>(ps, colorTemp, m_Gradient, gradient, offsetScale, fromIndex, toIndex);
+}
+
+void ColorBySpeedModule::CheckConsistency ()
+{
+ const float MyEpsilon = 0.001f;
+ m_Range.x = std::min (m_Range.x, m_Range.y - MyEpsilon);
+}
+
+template<class TransferFunction>
+void ColorBySpeedModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Gradient, "gradient");
+ transfer.Transfer (m_Range, "range");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ColorBySpeedModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.h
new file mode 100644
index 0000000..f140e68
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ColorByVelocityModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULECOLORBYVELOCITY_H
+#define SHURIKENMODULECOLORBYVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h"
+#include "Runtime/Math/Vector2.h"
+
+class ColorBySpeedModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ColorBySpeedModule)
+ ColorBySpeedModule ();
+
+ void Update (const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, size_t fromIndex, size_t toIndex);
+ void CheckConsistency ();
+
+ inline MinMaxGradient& GetGradient() { return m_Gradient; };
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxGradient m_Gradient;
+ Vector2f m_Range;
+};
+
+#endif // SHURIKENMODULECOLORBYVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ColorModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ColorModule.cpp
new file mode 100644
index 0000000..c9e78c0
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ColorModule.cpp
@@ -0,0 +1,51 @@
+#include "UnityPrefix.h"
+#include "ColorModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+template<MinMaxGradientEvalMode mode>
+void UpdateTpl(const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, const MinMaxGradient& gradient, const OptimizedMinMaxGradient& optGradient, size_t fromIndex, size_t toIndex)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float time = NormalizedTime(ps, q);
+ const int random = GenerateRandomByte(ps.randomSeed[q] + kParticleSystemColorGradientId);
+
+ ColorRGBA32 value;
+ if(mode == kGEMGradient)
+ value = EvaluateGradient (optGradient, time);
+ else if(mode == kGEMGradientMinMax)
+ value = EvaluateRandomGradient (optGradient, time, random);
+ else
+ value = Evaluate (gradient, time, random);
+
+ colorTemp[q] *= value;
+ }
+}
+
+ColorModule::ColorModule () : ParticleSystemModule(false)
+{}
+
+void ColorModule::Update (const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, size_t fromIndex, size_t toIndex)
+{
+ DebugAssert(toIndex <= ps.array_size());
+
+ OptimizedMinMaxGradient gradient;
+ m_Gradient.InitializeOptimized(gradient);
+ if(m_Gradient.minMaxState == kMMGGradient)
+ UpdateTpl<kGEMGradient>(ps, colorTemp, m_Gradient, gradient, fromIndex, toIndex);
+ else if(m_Gradient.minMaxState == kMMGRandomBetweenTwoGradients)
+ UpdateTpl<kGEMGradientMinMax>(ps, colorTemp, m_Gradient, gradient, fromIndex, toIndex);
+ else
+ UpdateTpl<kGEMSlow>(ps, colorTemp, m_Gradient, gradient, fromIndex, toIndex);
+}
+
+template<class TransferFunction>
+void ColorModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Gradient, "gradient");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ColorModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ColorModule.h b/Runtime/Graphics/ParticleSystem/Modules/ColorModule.h
new file mode 100644
index 0000000..1fe7250
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ColorModule.h
@@ -0,0 +1,25 @@
+#ifndef SHURIKENMODULECOLOR_H
+#define SHURIKENMODULECOLOR_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h"
+
+class ColorModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ColorModule)
+ ColorModule ();
+
+ void Update (const ParticleSystemParticles& ps, ColorRGBA32* colorTemp, size_t fromIndex, size_t toIndex);
+ void CheckConsistency() {};
+
+ inline MinMaxGradient& GetGradient() { return m_Gradient; };
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxGradient m_Gradient;
+};
+
+#endif // SHURIKENMODULECOLOR_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.cpp
new file mode 100644
index 0000000..111b06f
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.cpp
@@ -0,0 +1,124 @@
+#include "UnityPrefix.h"
+#include "EmissionModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Math/Vector2.h"
+
+static int AccumulateBursts (const ParticleSystemEmissionData& emissionData, float t0, float t1)
+{
+ int burstParticles = 0;
+ const size_t count = emissionData.burstCount;
+ for (size_t q = 0; q < count; ++q)
+ {
+ if (emissionData.burstTime[q] >= t0 && emissionData.burstTime[q] < t1)
+ burstParticles += emissionData.burstParticleCount[q];
+ }
+ return burstParticles;
+}
+
+static float AccumulateContinuous(const ParticleSystemEmissionData& emissionData, const float length, const float toT, const float dt)
+{
+ DebugAssert (length > 0.0001f);
+ DebugAssert (toT >= 0.0f);
+ DebugAssert (toT <= length);
+ float normalizedT = toT / length;
+ return std::max<float> (0.0f, Evaluate (emissionData.rate, normalizedT)) * dt;
+};
+
+EmissionModule::EmissionModule () : ParticleSystemModule(true)
+{
+ m_EmissionData.burstCount = 0;
+ m_EmissionData.type = kEmissionTypeTime;
+ for(int i = 0; i < ParticleSystemEmissionData::kMaxNumBursts; i++)
+ {
+ m_EmissionData.burstParticleCount[i] = 30;
+ m_EmissionData.burstTime[i] = 0.0f;
+ }
+}
+
+void EmissionModule::Emit (ParticleSystemEmissionState& emissionState, size_t& amountOfParticlesToEmit, size_t& numContinuous, const ParticleSystemEmissionData& emissionData, const Vector3f velocity, float fromT, float toT, float dt, float length)
+{
+ const float epsilon = 0.0001f;
+ if(kEmissionTypeTime == emissionData.type)
+ {
+ float rate = 0.0f;
+ float t0 = std::max<float> (0.0f, fromT);
+ float t1 = std::max<float> (0.0f, toT);
+ if (t1 < t0) // handle loop
+ {
+ rate += AccumulateContinuous (emissionData, length, t1, t1); // from start to current time
+ t1 = length; // from last time to end
+ }
+ rate += AccumulateContinuous (emissionData, length, t1, t1 - t0); // from start to current time
+
+ const float newParticles = rate;
+ if(newParticles >= epsilon)
+ emissionState.m_ParticleSpacing = 1.0f / newParticles;
+ else
+ emissionState.m_ParticleSpacing = 1.0f;
+
+ emissionState.m_ToEmitAccumulator += newParticles;
+ amountOfParticlesToEmit = (int)emissionState.m_ToEmitAccumulator;
+ emissionState.m_ToEmitAccumulator -= (float)amountOfParticlesToEmit;
+
+ // Continuous emits
+ numContinuous = amountOfParticlesToEmit;
+
+ // Bursts
+ t0 = std::max<float> (0.0f, fromT);
+ t1 = std::max<float> (0.0f, toT);
+ if (t1 < t0) // handle loop
+ {
+ amountOfParticlesToEmit += AccumulateBursts (emissionData, 0.0f, t1); // from start to current time
+ t1 = length + epsilon; // from last time to end
+ }
+ amountOfParticlesToEmit += AccumulateBursts (emissionData, t0, t1); // from start to current time
+ }
+ else
+ {
+ float newParticles = AccumulateContinuous (emissionData, length, toT, dt) * Magnitude (velocity); // from start to current time
+ if(newParticles >= epsilon)
+ emissionState.m_ParticleSpacing = 1.0f / newParticles;
+ else
+ emissionState.m_ParticleSpacing = 1.0f;
+
+ emissionState.m_ToEmitAccumulator += newParticles;
+ amountOfParticlesToEmit = (int)emissionState.m_ToEmitAccumulator;
+ emissionState.m_ToEmitAccumulator -= (float)amountOfParticlesToEmit;
+
+ // Continuous emits
+ numContinuous = amountOfParticlesToEmit;
+ }
+}
+
+void EmissionModule::CheckConsistency ()
+{
+ m_EmissionData.rate.SetScalar(std::max<float> (0.0f, m_EmissionData.rate.GetScalar()));
+
+ const size_t count = m_EmissionData.burstCount;
+ for (size_t q = 0; q < count; ++q)
+ {
+ m_EmissionData.burstTime[q] = std::max<float> (0.0f, m_EmissionData.burstTime[q]);
+ }
+}
+
+template<class TransferFunction>
+void EmissionModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+
+ transfer.Transfer (m_EmissionData.type, "m_Type");
+ transfer.Transfer (m_EmissionData.rate, "rate");
+
+ const char* kCountNames [ParticleSystemEmissionData::kMaxNumBursts] = { "cnt0", "cnt1", "cnt2", "cnt3", };
+ const char* kTimeNames [ParticleSystemEmissionData::kMaxNumBursts] = { "time0", "time1", "time2", "time3", };
+ for(int i = 0; i < ParticleSystemEmissionData::kMaxNumBursts; i++)
+ transfer.Transfer (m_EmissionData.burstParticleCount[i], kCountNames[i]);
+
+ for(int i = 0; i < ParticleSystemEmissionData::kMaxNumBursts; i++)
+ transfer.Transfer (m_EmissionData.burstTime[i], kTimeNames[i]);
+
+ transfer.Transfer (m_EmissionData.burstCount, "m_BurstCount"); transfer.Align();
+}
+
+INSTANTIATE_TEMPLATE_TRANSFER(EmissionModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.h b/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.h
new file mode 100644
index 0000000..75ad977
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/EmissionModule.h
@@ -0,0 +1,34 @@
+#ifndef SHURIKENMODULEEMISSION_H
+#define SHURIKENMODULEEMISSION_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+
+class Vector2f;
+
+class EmissionModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (EmissionModule)
+ EmissionModule ();
+
+ enum { kEmissionTypeTime, kEmissionTypeDistance };
+
+ static void Emit (ParticleSystemEmissionState& emissionState, size_t& amountOfParticlesToEmit, size_t& numContinuous, const ParticleSystemEmissionData& emissionData, const Vector3f velocity, float fromT, float toT, float dt, float length);
+ void CheckConsistency ();
+
+ int CalculateMaximumEmitCountEstimate(float deltaTime) const;
+
+ const ParticleSystemEmissionData& GetEmissionDataRef() { return m_EmissionData; }
+ const ParticleSystemEmissionData& GetEmissionDataRef() const { return m_EmissionData; }
+ void GetEmissionDataCopy(ParticleSystemEmissionData* emissionData) { *emissionData = m_EmissionData; };
+ ParticleSystemEmissionData& GetEmissionData() { return m_EmissionData; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ ParticleSystemEmissionData m_EmissionData;
+};
+
+#endif // SHURIKENMODULEEMISSION_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.cpp
new file mode 100644
index 0000000..0b86e79
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.cpp
@@ -0,0 +1,118 @@
+#include "UnityPrefix.h"
+#include "ExternalForcesModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "../ParticleSystemUtils.h"
+#include "Runtime/Graphics/Transform.h"
+#include "Runtime/Geometry/Intersection.h"
+#include "Runtime/Geometry/Sphere.h"
+#include "Runtime/Terrain/Wind.h"
+#include "Runtime/Input/TimeManager.h"
+
+
+void ApplyRadialForce(ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, const Vector3f position, const float radius, const float force, const float dt)
+{
+ for(int q = fromIndex; q < toIndex; q++)
+ {
+ Vector3f toForce = position - ps.position[q];
+ float distanceToForce = Magnitude(toForce);
+ toForce = NormalizeSafe(toForce);
+ float forceFactor = clamp(distanceToForce/radius, 0.0f, 1.0f);
+ forceFactor = 1.0f - forceFactor * forceFactor;
+ Vector3f delta = toForce * force * forceFactor * dt;
+ ps.velocity[q] += delta;
+ }
+}
+
+void ApplyDirectionalForce(ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, const Vector3f position, const Vector3f direction, const float radius, const float force, const float dt)
+{
+ for(int q = fromIndex; q < toIndex; q++)
+ {
+ Vector3f delta = direction * force * dt;
+ ps.velocity[q] += delta;
+ }
+}
+
+ExternalForcesModule::ExternalForcesModule () : ParticleSystemModule(false)
+, m_Multiplier (1.0f)
+{
+}
+
+void ExternalForcesModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt)
+{
+ Matrix4x4f matrix = Matrix4x4f::identity;
+ if(roState.useLocalSpace)
+ Matrix4x4f::Invert_General3D(state.localToWorld, matrix);
+
+ AABB aabb = state.minMaxAABB;
+ for(int i = 0; i < state.numCachedForces; i++)
+ {
+ const ParticleSystemExternalCachedForce& cachedForce = state.cachedForces[i];
+ const Vector3f position = matrix.MultiplyPoint3(cachedForce.position);
+ const Vector3f direction = matrix.MultiplyVector3(cachedForce.direction);
+ const float radius = cachedForce.radius;
+ const float force = cachedForce.forceMain * m_Multiplier;
+
+ const WindZone::WindZoneMode forceType = (WindZone::WindZoneMode)cachedForce.forceType;
+ if(WindZone::Spherical == forceType)
+ {
+ Sphere sphere (position, radius);
+ if(!IntersectAABBSphere (aabb, sphere))
+ continue;
+ ApplyRadialForce(ps, fromIndex, toIndex, position, radius, force, dt);
+ }
+ else if(WindZone::Directional == forceType)
+ ApplyDirectionalForce(ps, fromIndex, toIndex, position, direction, radius, force, dt);
+ }
+}
+
+// TODO: Perform culling here, instead of caching all of them
+void ExternalForcesModule::AllocateAndCache(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state)
+{
+ float time = GetTimeManager ().GetTimeSinceLevelLoad ();
+ WindManager::WindZoneList& windZones = WindManager::GetInstance().GetList();
+
+ state.numCachedForces = 0;
+ for (WindManager::WindZoneList::iterator it = windZones.begin (); it != windZones.end (); ++it)
+ state.numCachedForces++;
+
+ // Allocate
+ state.cachedForces = ALLOC_TEMP_MANUAL(ParticleSystemExternalCachedForce, state.numCachedForces);
+
+ // Cache
+ int i = 0;
+ for (WindManager::WindZoneList::iterator it = windZones.begin (); it != windZones.end (); ++it)
+ {
+ const WindZone& zone = **it;
+ ParticleSystemExternalCachedForce& cachedForce = state.cachedForces[i++];
+ cachedForce.position = zone.GetComponent (Transform).GetPosition();
+ cachedForce.direction = zone.GetComponent (Transform).GetLocalToWorldMatrix().GetAxisZ();
+ cachedForce.forceType = zone.GetMode();
+ cachedForce.radius = zone.GetRadius();
+
+ float phase = time * kPI * zone.GetWindPulseFrequency();
+ float pulse = (cos (phase) + cos (phase * 0.375f) + cos (phase * 0.05f)) * 0.333f;
+ pulse = 1.0f + (pulse * zone.GetWindPulseMagnitude());
+ cachedForce.forceMain = zone.GetWindMain() * pulse;
+ // Maybe implement using turbulence and time based phasing?
+ // ForceTurbulenceMultiply (maybe) // @TODO: Figure out what to do about turbulence. Do perlin field? Expensive but maybe cool! If use it: only do it when turbulence force is set to something
+
+ //cachedForce.force = 1.0f;
+ }
+}
+
+void ExternalForcesModule::FreeCache(ParticleSystemState& state)
+{
+ if(state.cachedForces)
+ FREE_TEMP_MANUAL(state.cachedForces);
+ state.cachedForces = NULL;
+ state.numCachedForces = 0;
+}
+
+template<class TransferFunction>
+void ExternalForcesModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Multiplier, "multiplier");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ExternalForcesModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.h b/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.h
new file mode 100644
index 0000000..2abc949
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ExternalForcesModule.h
@@ -0,0 +1,25 @@
+#ifndef SHURIKENMODULEEXTERNALFORCES_H
+#define SHURIKENMODULEEXTERNALFORCES_H
+
+#include "ParticleSystemModule.h"
+
+class ExternalForcesModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ExternalForcesModule)
+ ExternalForcesModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt);
+ void CheckConsistency() {};
+
+ static void AllocateAndCache(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state);
+ static void FreeCache(ParticleSystemState& state);
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ float m_Multiplier;
+};
+
+#endif // SHURIKENMODULEEXTERNALFORCES_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ForceModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ForceModule.cpp
new file mode 100644
index 0000000..d72da93
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ForceModule.cpp
@@ -0,0 +1,164 @@
+#include "UnityPrefix.h"
+#include "ForceModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "../ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& x, const MinMaxCurve& y, const MinMaxCurve& z, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, bool transform, const Matrix4x4f& matrix, float dt)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemForceCurveId);
+ const float time = NormalizedTime(ps, q);
+ Vector3f f = Vector3f (Evaluate<mode> (x, time, random.x), Evaluate<mode> (y, time, random.y), Evaluate<mode> (z, time, random.z));
+ if(transform)
+ f = matrix.MultiplyVector3 (f);
+ ps.velocity[q] += f * dt;
+ }
+}
+
+template<bool isOptimized>
+void UpdateProceduralTpl(const DualMinMax3DPolyCurves& pos, const DualMinMax3DPolyCurves& vel, ParticleSystemParticles& ps, const Matrix4x4f& matrix, bool transform)
+{
+ const size_t count = ps.array_size();
+ for (int q=0; q<count; q++)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemForceCurveId);
+ float time = NormalizedTime(ps, q);
+ float range = ps.startLifetime[q];
+
+ Vector3f delta;
+ Vector3f velocity;
+ if(isOptimized)
+ {
+ delta = Vector3f (EvaluateDoubleIntegrated(pos.optX, time, random.x), EvaluateDoubleIntegrated(pos.optY, time, random.y), EvaluateDoubleIntegrated(pos.optZ, time, random.z));
+ velocity = Vector3f (EvaluateIntegrated(vel.optX, time, random.x), EvaluateIntegrated(vel.optY, time, random.y), EvaluateIntegrated(vel.optZ, time, random.z));
+ }
+ else
+ {
+ delta = Vector3f (EvaluateDoubleIntegrated(pos.x, time, random.x), EvaluateDoubleIntegrated(pos.y, time, random.y), EvaluateDoubleIntegrated(pos.z, time, random.z));
+ velocity = Vector3f (EvaluateIntegrated(vel.x, time, random.x), EvaluateIntegrated(vel.y, time, random.y), EvaluateIntegrated(vel.z, time, random.z));
+ }
+
+ // Sqr range
+ delta *= range * range;
+ velocity *= range;
+
+ if(transform)
+ {
+ delta = matrix.MultiplyVector3 (delta);
+ velocity = matrix.MultiplyVector3 (velocity);
+ }
+
+ ps.position[q] += delta;
+ ps.velocity[q] += velocity;
+ }
+}
+
+ForceModule::ForceModule () : ParticleSystemModule(false)
+, m_RandomizePerFrame (false)
+, m_InWorldSpace(false)
+{}
+
+void ForceModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt)
+{
+ Matrix4x4f matrix;
+ bool transform = GetTransformationMatrix(matrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+
+ if (m_RandomizePerFrame)
+ {
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float t = NormalizedTime (ps, q);
+ const float randomX = Random01 (m_Random);
+ const float randomY = Random01 (m_Random);
+ const float randomZ = Random01 (m_Random);
+ Vector3f f (Evaluate (m_X, t, randomX), Evaluate (m_Y, t, randomY), Evaluate (m_Z, t, randomZ));
+ if(transform)
+ f = matrix.MultiplyVector3 (f);
+ ps.velocity[q] += f * dt;
+ }
+ }
+ else
+ {
+ bool usesScalar = (m_X.minMaxState == kMMCScalar) && (m_Y.minMaxState == kMMCScalar) && (m_Z.minMaxState == kMMCScalar);
+ bool isOptimized = m_X.IsOptimized() && m_Y.IsOptimized() && m_Z.IsOptimized();
+ bool usesMinMax = m_X.UsesMinMax() && m_Y.UsesMinMax() && m_Z.UsesMinMax();
+ if(usesScalar)
+ UpdateTpl<kEMScalar>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix, dt);
+ else if(isOptimized && usesMinMax)
+ UpdateTpl<kEMOptimizedMinMax>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix, dt);
+ else if(isOptimized)
+ UpdateTpl<kEMOptimized>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix, dt);
+ else
+ UpdateTpl<kEMSlow>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix, dt);
+ }
+}
+
+void ForceModule::UpdateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps)
+{
+ Assert(!m_RandomizePerFrame);
+
+ Matrix4x4f matrix;
+ bool transform = GetTransformationMatrix(matrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+
+ DualMinMax3DPolyCurves posCurves;
+ DualMinMax3DPolyCurves velCurves;
+ if(m_X.IsOptimized() && m_Y.IsOptimized() && m_Z.IsOptimized())
+ {
+ posCurves.optX = m_X.polyCurves; posCurves.optX.DoubleIntegrate();
+ posCurves.optY = m_Y.polyCurves; posCurves.optY.DoubleIntegrate();
+ posCurves.optZ = m_Z.polyCurves; posCurves.optZ.DoubleIntegrate();
+ velCurves.optX = m_X.polyCurves; velCurves.optX.Integrate();
+ velCurves.optY = m_Y.polyCurves; velCurves.optY.Integrate();
+ velCurves.optZ = m_Z.polyCurves; velCurves.optZ.Integrate();
+ UpdateProceduralTpl<true>(posCurves, velCurves, ps, matrix, transform);
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (m_X.editorCurves, m_X.minMaxState));
+ DebugAssert(CurvesSupportProcedural (m_Y.editorCurves, m_Y.minMaxState));
+ DebugAssert(CurvesSupportProcedural (m_Z.editorCurves, m_Z.minMaxState));
+ BuildCurves(posCurves.x, m_X.editorCurves, m_X.GetScalar(), m_X.minMaxState); posCurves.x.DoubleIntegrate();
+ BuildCurves(posCurves.y, m_Y.editorCurves, m_Y.GetScalar(), m_Y.minMaxState); posCurves.y.DoubleIntegrate();
+ BuildCurves(posCurves.z, m_Z.editorCurves, m_Z.GetScalar(), m_Z.minMaxState); posCurves.z.DoubleIntegrate();
+ BuildCurves(velCurves.x, m_X.editorCurves, m_X.GetScalar(), m_X.minMaxState); velCurves.x.Integrate();
+ BuildCurves(velCurves.y, m_Y.editorCurves, m_Y.GetScalar(), m_Y.minMaxState); velCurves.y.Integrate();
+ BuildCurves(velCurves.z, m_Z.editorCurves, m_Z.GetScalar(), m_Z.minMaxState); velCurves.z.Integrate();
+ UpdateProceduralTpl<false>(posCurves, velCurves, ps, matrix, transform);
+ }
+}
+void ForceModule::CalculateProceduralBounds(MinMaxAABB& bounds, const Matrix4x4f& localToWorld, float maxLifeTime)
+{
+ Vector2f xRange = m_X.FindMinMaxDoubleIntegrated();
+ Vector2f yRange = m_Y.FindMinMaxDoubleIntegrated();
+ Vector2f zRange = m_Z.FindMinMaxDoubleIntegrated();
+ bounds.m_Min = Vector3f(xRange.x, yRange.x, zRange.x) * maxLifeTime * maxLifeTime;
+ bounds.m_Max = Vector3f(xRange.y, yRange.y, zRange.y) * maxLifeTime * maxLifeTime;
+
+ if(m_InWorldSpace)
+ {
+ Matrix4x4f matrix;
+ Matrix4x4f::Invert_General3D(localToWorld, matrix);
+ matrix.SetPosition(Vector3f::zero);
+ AABB aabb = bounds;
+ TransformAABBSlow(aabb, matrix, aabb);
+ bounds = aabb;
+ }
+}
+
+template<class TransferFunction>
+void ForceModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_X, "x");
+ transfer.Transfer (m_Y, "y");
+ transfer.Transfer (m_Z, "z");
+ transfer.Transfer (m_InWorldSpace, "inWorldSpace");
+ transfer.Transfer (m_RandomizePerFrame, "randomizePerFrame"); transfer.Align ();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ForceModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ForceModule.h b/Runtime/Graphics/ParticleSystem/Modules/ForceModule.h
new file mode 100644
index 0000000..9ad54ec
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ForceModule.h
@@ -0,0 +1,36 @@
+#ifndef SHURIKENMODULEFORCE_H
+#define SHURIKENMODULEFORCE_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Math/Random/rand.h"
+
+class ForceModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ForceModule)
+ ForceModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt);
+ void UpdateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps);
+ void CalculateProceduralBounds(MinMaxAABB& bounds, const Matrix4x4f& localToWorld, float maxLifeTime);
+ void CheckConsistency() {};
+
+ inline MinMaxCurve& GetXCurve() { return m_X; }
+ inline MinMaxCurve& GetYCurve() { return m_Y; }
+ inline MinMaxCurve& GetZCurve() { return m_Z; }
+ inline bool GetRandomizePerFrame() { return m_RandomizePerFrame; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_X;
+ MinMaxCurve m_Y;
+ MinMaxCurve m_Z;
+ bool m_InWorldSpace;
+ bool m_RandomizePerFrame;
+ Rand m_Random;
+};
+
+#endif // SHURIKENMODULEFORCE_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/InitialModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/InitialModule.cpp
new file mode 100644
index 0000000..680d175
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/InitialModule.cpp
@@ -0,0 +1,193 @@
+#include "UnityPrefix.h"
+#include "InitialModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Interfaces/IPhysics.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+
+InitialModule::InitialModule () : ParticleSystemModule(true)
+, m_GravityModifier(0.0f)
+, m_InheritVelocity(0.0f)
+, m_MaxNumParticles(1000)
+{
+}
+
+Vector3f InitialModule::GetGravity (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state) const
+{
+#if ENABLE_PHYSICS
+ IPhysics* physicsModule = GetIPhysics();
+ if (!physicsModule)
+ return Vector3f::zero;
+
+ Vector3f gravity = m_GravityModifier * physicsModule->GetGravity ();
+ if(roState.useLocalSpace)
+ {
+ Matrix4x4f worldToLocal;
+ Matrix4x4f::Invert_General3D(state.localToWorld, worldToLocal);
+ gravity = worldToLocal.MultiplyVector3(gravity);
+ }
+ return gravity;
+#else
+ return Vector3f::zero;
+#endif
+}
+
+void InitialModule::Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t)
+{
+ DebugAssert(roState.lengthInSec > 0.0001f);
+ const float normalizedT = t / roState.lengthInSec;
+ DebugAssert (normalizedT >= 0.0f);
+ DebugAssert (normalizedT <= 1.0f);
+
+ Rand& random = GetRandom();
+
+ Vector3f origin = matrix.GetPosition ();
+
+ const size_t count = ps.array_size ();
+ for (size_t q = fromIndex; q < count; ++q)
+ {
+ UInt32 randUInt32 = random.Get ();
+ float rand = Rand::GetFloatFromInt (randUInt32);
+ UInt32 randByte = Rand::GetByteFromInt (randUInt32);
+
+ const ColorRGBA32 col = Evaluate (m_Color, normalizedT, randByte);
+ float sz = std::max<float> (0.0f, Evaluate (m_Size, normalizedT, rand));
+ Vector3f vel = matrix.MultiplyVector3 (Vector3f::zAxis);
+ float ttl = std::max<float> (0.0f, Evaluate (m_Lifetime, normalizedT, rand));
+ float rot = Evaluate (m_Rotation, normalizedT, rand);
+
+ ps.position[q] = origin;
+ ps.velocity[q] = vel;
+ ps.animatedVelocity[q] = Vector3f::zero;
+ ps.lifetime[q] = ttl;
+ ps.startLifetime[q] = ttl;
+ ps.size[q] = sz;
+ ps.rotation[q] = rot;
+ if(ps.usesRotationalSpeed)
+ ps.rotationalSpeed[q] = 0.0f;
+ ps.color[q] = col;
+ ps.randomSeed[q] = random.Get(); // One more iteration to avoid visible patterns between random spawned parameters and those used in update
+ if(ps.usesAxisOfRotation)
+ ps.axisOfRotation[q] = Vector3f::zAxis;
+ for(int acc = 0; acc < ps.numEmitAccumulators; acc++)
+ ps.emitAccumulator[acc][q] = 0.0f;
+
+ }
+}
+
+void InitialModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt) const
+{
+ Vector3f gravityDelta = GetGravity(roState, state) * dt;
+ if(!CompareApproximately(gravityDelta, Vector3f::zero, 0.0001f))
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ ps.velocity[q] += gravityDelta;
+
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ ps.animatedVelocity[q] = Vector3f::zero;
+
+ if(ps.usesRotationalSpeed)
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ ps.rotationalSpeed[q] = 0.0f;
+}
+
+void InitialModule::GenerateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const ParticleSystemEmitReplay& emit)
+{
+ size_t count = emit.particlesToEmit;
+ float t = emit.t;
+ float alreadyPassedTime = emit.aliveTime;
+
+ DebugAssert(roState.lengthInSec > 0.0001f);
+ const float normalizedT = t / roState.lengthInSec;
+ DebugAssert (normalizedT >= 0.0f);
+ DebugAssert (normalizedT <= 1.0f);
+
+ Rand& random = GetRandom();
+
+ const Matrix4x4f localToWorld = !roState.useLocalSpace ? state.localToWorld : Matrix4x4f::identity;
+ Vector3f origin = localToWorld.GetPosition ();
+ for (size_t i = 0; i < count; ++i)
+ {
+ UInt32 randUInt32 = random.Get ();
+ float rand = Rand::GetFloatFromInt (randUInt32);
+ UInt32 randByte = Rand::GetByteFromInt (randUInt32);
+
+ float frameOffset = (float(i) + emit.emissionOffset) * emit.emissionGap * float(i < emit.numContinuous);
+
+ const ColorRGBA32 col = Evaluate (m_Color, normalizedT, randByte);
+ float sz = std::max<float> (0.0f, Evaluate (m_Size, normalizedT, rand));
+ Vector3f vel = localToWorld.MultiplyVector3 (Vector3f::zAxis);
+ float ttlStart = std::max<float> (0.0f, Evaluate (m_Lifetime, normalizedT, rand));
+ float ttl = ttlStart - alreadyPassedTime - frameOffset;
+ float rot = Evaluate (m_Rotation, normalizedT, rand);
+
+ if (ttl < 0.0F)
+ continue;
+
+ size_t q = ps.array_size();
+ ps.array_resize(ps.array_size() + 1);
+
+ ps.position[q] = origin;
+ ps.velocity[q] = vel;
+ ps.animatedVelocity[q] = Vector3f::zero;
+ ps.lifetime[q] = ttl;
+ ps.startLifetime[q] = ttlStart;
+ ps.size[q] = sz;
+ ps.rotation[q] = rot;
+ if(ps.usesRotationalSpeed)
+ ps.rotationalSpeed[q] = 0.0f;
+ ps.color[q] = col;
+ ps.randomSeed[q] = random.Get(); // One more iteration to avoid visible patterns between random spawned parameters and those used in update
+ if(ps.usesAxisOfRotation)
+ ps.axisOfRotation[q] = Vector3f::zAxis;
+ for(int acc = 0; acc < ps.numEmitAccumulators; acc++)
+ ps.emitAccumulator[acc][q] = 0.0f;
+ }
+}
+
+void InitialModule::CheckConsistency ()
+{
+ m_Lifetime.SetScalar(clamp<float> (m_Lifetime.GetScalar(), 0.05f, 100000.0f));
+ m_Size.SetScalar(std::max<float> (0.0f, m_Size.GetScalar()));
+ m_MaxNumParticles = std::max<int> (0, m_MaxNumParticles);
+}
+
+void InitialModule::AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState)
+{
+ ResetSeed(roState);
+}
+
+void InitialModule::ResetSeed(const ParticleSystemReadOnlyState& roState)
+{
+ if(roState.randomSeed == 0)
+ m_Random.SetSeed(GetGlobalRandomSeed ());
+ else
+ m_Random.SetSeed(roState.randomSeed);
+}
+
+Rand& InitialModule::GetRandom()
+{
+#if UNITY_EDITOR
+ if(!IsWorldPlaying())
+ return m_EditorRandom;
+ else
+#endif
+ return m_Random;
+}
+
+template<class TransferFunction>
+void InitialModule::Transfer (TransferFunction& transfer)
+{
+ SetEnabled(true); // always enabled
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Lifetime, "startLifetime");
+ transfer.Transfer (m_Speed, "startSpeed");
+ transfer.Transfer (m_Color, "startColor");
+ transfer.Transfer (m_Size, "startSize");
+ transfer.Transfer (m_Rotation, "startRotation");
+ transfer.Transfer (m_GravityModifier, "gravityModifier");
+ transfer.Transfer (m_InheritVelocity, "inheritVelocity");
+ transfer.Transfer (m_MaxNumParticles, "maxNumParticles");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(InitialModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/InitialModule.h b/Runtime/Graphics/ParticleSystem/Modules/InitialModule.h
new file mode 100644
index 0000000..cd602a9
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/InitialModule.h
@@ -0,0 +1,66 @@
+#ifndef SHURIKENMODULEINITIAL_H
+#define SHURIKENMODULEINITIAL_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h"
+#include "Runtime/Math/Random/rand.h"
+
+struct ParticleSystemState;
+
+class InitialModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (InitialModule)
+ InitialModule ();
+
+ void Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t);
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt) const;
+ void GenerateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const ParticleSystemEmitReplay& emit);
+ void CheckConsistency ();
+ void AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState);
+ void ResetSeed(const ParticleSystemReadOnlyState& roState);
+
+ inline MinMaxCurve& GetLifeTimeCurve() { return m_Lifetime; }
+ inline const MinMaxCurve& GetLifeTimeCurve() const { return m_Lifetime; }
+ inline MinMaxCurve& GetSpeedCurve() { return m_Speed; }
+ inline const MinMaxCurve& GetSpeedCurve() const { return m_Speed; }
+ inline MinMaxCurve& GetSizeCurve() { return m_Size; }
+ inline const MinMaxCurve& GetSizeCurve() const { return m_Size; }
+ inline MinMaxCurve& GetRotationCurve() { return m_Rotation; }
+ inline const MinMaxCurve& GetRotationCurve() const { return m_Rotation; }
+ inline MinMaxGradient& GetColor() { return m_Color; }
+ inline const MinMaxGradient& GetColor() const { return m_Color; }
+ inline void SetGravityModifier(float value) { m_GravityModifier = value; }
+ inline float GetGravityModifier() const { return m_GravityModifier; }
+
+ inline void SetMaxNumParticles(int value) { m_MaxNumParticles = value; }
+ inline int GetMaxNumParticles() const { return m_MaxNumParticles; }
+ inline float GetInheritVelocity() const { return m_InheritVelocity; }
+ Vector3f GetGravity (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state) const;
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ Rand& GetRandom();
+
+ MinMaxCurve m_Lifetime;
+ MinMaxCurve m_Speed;
+ MinMaxGradient m_Color;
+ MinMaxCurve m_Size;
+ MinMaxCurve m_Rotation;
+ float m_GravityModifier;
+ float m_InheritVelocity;
+ int m_MaxNumParticles;
+
+ Rand m_Random;
+
+#if UNITY_EDITOR
+public:
+ Rand m_EditorRandom;
+#endif
+};
+
+
+#endif // SHURIKENMODULEINITIAL_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.cpp
new file mode 100644
index 0000000..dee4872
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.cpp
@@ -0,0 +1,141 @@
+#include "UnityPrefix.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "ParticleSystemModule.h"
+
+ParticleSystemReadOnlyState::ParticleSystemReadOnlyState()
+: lengthInSec (5.0f)
+, startDelay (0.0f)
+, speed (1.0f)
+, randomSeed (0)
+, looping (true)
+, prewarm (false)
+, playOnAwake (true)
+, useLocalSpace (true)
+{
+}
+
+void ParticleSystemReadOnlyState::CheckConsistency()
+{
+ lengthInSec = std::max(lengthInSec, 0.1f);
+ lengthInSec = std::min(lengthInSec, 100000.0f); // Very large values can lead to editor locking up due to numerical instability.
+ startDelay = std::max(startDelay, 0.0f);
+ speed = std::max(speed, 0.0f);
+}
+
+template<class TransferFunction>
+void ParticleSystemReadOnlyState::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (lengthInSec);
+ TRANSFER (startDelay);
+ TRANSFER (speed);
+ TRANSFER (randomSeed);
+ TRANSFER (looping);
+ TRANSFER (prewarm);
+ TRANSFER (playOnAwake);
+ transfer.Transfer (useLocalSpace, "moveWithTransform");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ParticleSystemReadOnlyState)
+
+ParticleSystemState::ParticleSystemState ()
+: playing (false)
+, needRestart (true)
+, stopEmitting (false)
+, accumulatedDt (0.0f)
+, delayT (0.0f)
+, t (0.0f)
+, maxSize (0.0f)
+, isSubEmitter (false)
+, recordSubEmits(false)
+, cullTime(0.0)
+, culled(false)
+, numLoops(0)
+, invalidateProcedural(false)
+, supportsProcedural(true)
+, cachedForces(0)
+, numCachedForces(0)
+, cachedSubDataBirth(0)
+, numCachedSubDataBirth(0)
+, cachedSubDataCollision(0)
+, numCachedSubDataCollision(0)
+, cachedSubDataDeath(0)
+, numCachedSubDataDeath(0)
+, cachedCollisionPlanes(0)
+, numCachedCollisionPlanes(0)
+, rayBudget(0)
+, nextParticleToTrace(0)
+{
+ ClearSubEmitterCommandBuffer();
+
+ localToWorld.SetIdentity ();
+ emitterVelocity = Vector3f::zero;
+ emitterScale = Vector3f::one;
+ minMaxAABB = MinMaxAABB (Vector3f::zero, Vector3f::zero);
+}
+
+void ParticleSystemState::Tick (const ParticleSystemReadOnlyState& constState, float dt)
+{
+ t += dt;
+
+ for(int i = 0; i < subEmitterCommandBuffer.commandCount; i++)
+ subEmitterCommandBuffer.commands[i].timeAlive += dt;
+
+ if (!constState.looping)
+ t = std::min<float> (t, constState.lengthInSec);
+ else
+ if(t > constState.lengthInSec)
+ {
+ t -= constState.lengthInSec;
+ numLoops++;
+ }
+}
+
+void ParticleSystemState::ClearSubEmitterCommandBuffer()
+{
+ if(cachedSubDataBirth)
+ {
+ for (int i = 0; i < numCachedSubDataBirth; ++i)
+ {
+ (cachedSubDataBirth+i)->~ParticleSystemSubEmitterData();
+ }
+ FREE_TEMP_MANUAL(cachedSubDataBirth);
+ }
+ if(cachedSubDataCollision)
+ {
+ for (int i = 0; i < numCachedSubDataCollision; ++i)
+ {
+ (cachedSubDataCollision+i)->~ParticleSystemSubEmitterData();
+ }
+ FREE_TEMP_MANUAL(cachedSubDataCollision);
+ }
+ if(cachedSubDataDeath)
+ {
+ for (int i = 0; i < numCachedSubDataDeath; ++i)
+ {
+ (cachedSubDataDeath+i)->~ParticleSystemSubEmitterData();
+ }
+ FREE_TEMP_MANUAL(cachedSubDataDeath);
+ }
+ if(subEmitterCommandBuffer.commands)
+ FREE_TEMP_MANUAL(subEmitterCommandBuffer.commands);
+
+ cachedSubDataBirth = cachedSubDataCollision = cachedSubDataDeath = 0;
+ numCachedSubDataBirth = numCachedSubDataCollision = numCachedSubDataDeath = 0;
+ subEmitterCommandBuffer.commands = 0;
+ subEmitterCommandBuffer.commandCount = subEmitterCommandBuffer.maxCommandCount = 0;
+}
+
+template<class TransferFunction>
+void ParticleSystemState::Transfer (TransferFunction& transfer)
+{
+ TRANSFER_DEBUG (t);
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ParticleSystemState)
+
+
+template<class TransferFunction>
+void ParticleSystemModule::Transfer (TransferFunction& transfer)
+{
+ transfer.Transfer (m_Enabled, "enabled"); transfer.Align ();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(ParticleSystemModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.h b/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.h
new file mode 100644
index 0000000..b4746de
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ParticleSystemModule.h
@@ -0,0 +1,238 @@
+#ifndef SHURIKENMODULE_H
+#define SHURIKENMODULE_H
+
+#include "../ParticleSystemCommon.h"
+#include "../ParticleSystemCurves.h"
+#include "Runtime/Math/Matrix4x4.h"
+#include "Runtime/Geometry/AABB.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+#define ENABLE_MULTITHREADED_PARTICLES ENABLE_MULTITHREADED_CODE
+
+#define DECLARE_MODULE(name) const char* GetName () { return #name; } DEFINE_GET_TYPESTRING(name)
+
+class ParticleSystem;
+class Plane;
+
+struct ParticleSystemEmissionState
+{
+ ParticleSystemEmissionState() { Clear(); }
+ inline void Clear()
+ {
+ m_ToEmitAccumulator = 0.0f;
+ m_ParticleSpacing = 0.0f;
+ }
+ float m_ParticleSpacing;
+ float m_ToEmitAccumulator;
+};
+
+struct ParticleSystemEmissionData
+{
+ enum { kMaxNumBursts = 4 };
+
+ int type;
+ MinMaxCurve rate;
+ float burstTime[kMaxNumBursts];
+ UInt16 burstParticleCount[kMaxNumBursts];
+ UInt8 burstCount;
+};
+
+
+struct ParticleSystemSubEmitterData
+{
+ ParticleSystemSubEmitterData()
+ :maxLifetime(0.0f)
+ ,startDelayInSec(0.0f)
+ ,lengthInSec(0.0f)
+ {}
+
+ ParticleSystemEmissionData emissionData;
+ float maxLifetime;
+ float startDelayInSec;
+ float lengthInSec;
+ ParticleSystem* emitter;
+};
+
+// @TODO: Find "pretty" place for shared structs and enums?
+struct ParticleSystemEmitReplay
+{
+ float t;
+ float aliveTime;
+ float emissionOffset;
+ float emissionGap;
+ int particlesToEmit;
+ size_t numContinuous;
+ UInt32 randomSeed;
+
+ ParticleSystemEmitReplay (float inT, int inParticlesToEmit, float inEmissionOffset, float inEmissionGap, size_t inNumContinuous, UInt32 inRandomSeed)
+ : t (inT), particlesToEmit (inParticlesToEmit), aliveTime(0.0F), emissionOffset(inEmissionOffset), emissionGap(inEmissionGap), numContinuous(inNumContinuous), randomSeed(inRandomSeed)
+ {}
+};
+
+struct SubEmitterEmitCommand
+{
+ SubEmitterEmitCommand(ParticleSystemEmissionState inEmissionState, Vector3f inPosition, Vector3f inVelocity, ParticleSystemSubType inSubEmitterType, int inSubEmitterIndex, int inParticlesToEmit, int inParticlesToEmitContinuous, float inParentT, float inDeltaTime)
+ :emissionState(inEmissionState)
+ ,position(inPosition)
+ ,velocity(inVelocity)
+ ,subEmitterType(inSubEmitterType)
+ ,subEmitterIndex(inSubEmitterIndex)
+ ,particlesToEmit(inParticlesToEmit)
+ ,particlesToEmitContinuous(inParticlesToEmitContinuous)
+ ,deltaTime(inDeltaTime)
+ ,parentT(inParentT)
+ ,timeAlive(0.0f)
+ {
+ }
+
+ ParticleSystemEmissionState emissionState;
+ Vector3f position;
+ Vector3f velocity;
+ ParticleSystemSubType subEmitterType;
+ int subEmitterIndex;
+ int particlesToEmit;
+ int particlesToEmitContinuous;
+ float deltaTime;
+ float parentT; // Used for StartModules
+ float timeAlive;
+};
+
+struct ParticleSystemSubEmitCmdBuffer
+{
+ ParticleSystemSubEmitCmdBuffer()
+ :commands(0)
+ ,commandCount(0)
+ ,maxCommandCount(0)
+ {}
+
+ inline void AddCommand(const ParticleSystemEmissionState& emissionState, const Vector3f& initialPosition, const Vector3f& initialVelocity, const ParticleSystemSubType type, const int index, const int particlesToEmit, const int particlesToEmitContinuous, const float parentT, const float dt)
+ {
+ commands[commandCount++] = SubEmitterEmitCommand(emissionState, initialPosition, initialVelocity, type, index, particlesToEmit, particlesToEmitContinuous, parentT, dt);
+ }
+ inline bool IsFull() { return commandCount >= maxCommandCount; }
+
+ SubEmitterEmitCommand* commands;
+ int commandCount;
+ int maxCommandCount; // Mostly to assert/test against trashing memory
+};
+
+struct ParticleSystemExternalCachedForce
+{
+ Vector3f position;
+ Vector3f direction;
+ int forceType;
+ float radius;
+ float forceMain;
+ float forceTurbulence; // not yet implemented
+};
+
+// @TODO: Maybe there's a better name for this? ParticleSystemSerializedState? Some shit like that :)
+struct ParticleSystemReadOnlyState
+{
+ ParticleSystemReadOnlyState();
+
+ void CheckConsistency();
+
+ float lengthInSec;
+ float startDelay;
+ float speed;
+ UInt32 randomSeed;
+ bool looping;
+ bool prewarm;
+ bool playOnAwake;
+ bool useLocalSpace;
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+};
+
+// Some of these aren't actually state, but more like context. Separate it?
+struct ParticleSystemState
+{
+ // state
+ float accumulatedDt;
+ float delayT;
+ bool playing;
+ bool needRestart;
+ bool stopEmitting;
+ size_t rayBudget;
+ size_t nextParticleToTrace;
+
+ bool GetIsSubEmitter() const { return isSubEmitter; }
+private:
+ // When setting this we need to ensure some other things happen as well
+ bool isSubEmitter;
+
+public:
+
+ bool recordSubEmits;
+
+ // Procedural mode / culling
+ bool supportsProcedural; // With the current parameter set, does the emitter support procedural mode?
+ bool invalidateProcedural; // This is set if anything changes from script at some point when running a system
+ bool culled; // Is particle system currently culled?
+ double cullTime; // Absolute time, so we need as double in case it runs for ages
+ int numLoops; // Number of loops executed
+
+ // per-frame
+ Matrix4x4f localToWorld;
+ Matrix4x4f worldToLocal;
+ Vector3f emitterVelocity;
+ Vector3f emitterScale;
+
+ MinMaxAABB minMaxAABB;
+ float maxSize; // Maximum size of particles due to setting from script
+ float t;
+
+ // Temp alloc stuff
+ ParticleSystemSubEmitterData* cachedSubDataBirth;
+ size_t numCachedSubDataBirth;
+ ParticleSystemSubEmitterData* cachedSubDataCollision;
+ size_t numCachedSubDataCollision;
+ ParticleSystemSubEmitterData* cachedSubDataDeath;
+ size_t numCachedSubDataDeath;
+ ParticleSystemExternalCachedForce* cachedForces;
+ size_t numCachedForces;
+ Plane* cachedCollisionPlanes;
+ size_t numCachedCollisionPlanes;
+ ParticleSystemSubEmitCmdBuffer subEmitterCommandBuffer;
+
+ dynamic_array<ParticleSystemEmitReplay> emitReplay;
+ ParticleSystemEmissionState emissionState;
+
+ ParticleSystemState ();
+
+ void Tick (const ParticleSystemReadOnlyState& constState, float dt);
+ void ClearSubEmitterCommandBuffer();
+
+ void SetIsSubEmitter(bool inIsSubEmitter)
+ {
+ if(inIsSubEmitter)
+ {
+ stopEmitting = true;
+ invalidateProcedural = true;
+ }
+ isSubEmitter = inIsSubEmitter;
+ }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+};
+
+
+class ParticleSystemModule
+{
+public:
+ DECLARE_SERIALIZE (ParticleSystemModule)
+ ParticleSystemModule (bool enabled) : m_Enabled (enabled) {}
+ virtual ~ParticleSystemModule () {}
+
+ inline bool GetEnabled() const { return m_Enabled; }
+ inline void SetEnabled(bool enabled) { m_Enabled = enabled; }
+
+private:
+ // shared data
+ bool m_Enabled;
+};
+
+#endif // SHURIKENMODULE_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.cpp
new file mode 100644
index 0000000..c194808
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.cpp
@@ -0,0 +1,51 @@
+#include "UnityPrefix.h"
+#include "RotationByVelocityModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+
+RotationBySpeedModule::RotationBySpeedModule () : ParticleSystemModule(false)
+, m_Range (0.0f, 1.0f)
+{}
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& curve, ParticleSystemParticles& ps, size_t fromIndex, size_t toIndex, const Vector2f offsetScale)
+{
+ if (!ps.usesRotationalSpeed) return;
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ const float t = InverseLerpFast01 (offsetScale, Magnitude(vel));
+ const float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemRotationBySpeedCurveId);
+ ps.rotationalSpeed[q] += Evaluate<mode> (curve, t, random);
+ }
+}
+
+void RotationBySpeedModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ Vector2f offsetScale = CalculateInverseLerpOffsetScale (m_Range);
+ if (m_Curve.minMaxState == kMMCScalar)
+ UpdateTpl<kEMScalar>(m_Curve, ps, fromIndex, toIndex, offsetScale);
+ else if(m_Curve.IsOptimized() && m_Curve.UsesMinMax())
+ UpdateTpl<kEMOptimizedMinMax>(m_Curve, ps, fromIndex, toIndex, offsetScale);
+ else if(m_Curve.IsOptimized())
+ UpdateTpl<kEMOptimized>(m_Curve, ps, fromIndex, toIndex, offsetScale);
+ else
+ UpdateTpl<kEMSlow>(m_Curve, ps, fromIndex, toIndex, offsetScale);
+}
+
+void RotationBySpeedModule::CheckConsistency ()
+{
+ const float MyEpsilon = 0.001f;
+ m_Range.y = std::max (m_Range.x + MyEpsilon, m_Range.y);
+}
+
+template<class TransferFunction>
+void RotationBySpeedModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "curve");
+ transfer.Transfer (m_Range, "range");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(RotationBySpeedModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.h
new file mode 100644
index 0000000..548f9c3
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/RotationByVelocityModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULEROTATIONBYVELOCITY_H
+#define SHURIKENMODULEROTATIONBYVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Math/Vector2.h"
+
+class RotationBySpeedModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (RotationBySpeedModule)
+ RotationBySpeedModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex);
+ void CheckConsistency ();
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_Curve;
+ Vector2f m_Range;
+};
+
+#endif // SHURIKENMODULEROTATIONBYVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/RotationModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/RotationModule.cpp
new file mode 100644
index 0000000..7f331d6
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/RotationModule.cpp
@@ -0,0 +1,83 @@
+#include "UnityPrefix.h"
+#include "RotationModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Math/Vector2.h"
+
+struct DualMinMaxPolyCurves
+{
+ MinMaxOptimizedPolyCurves optRot;
+ MinMaxPolyCurves rot;
+};
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& curve, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ if ( !ps.usesRotationalSpeed ) return;
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float time = NormalizedTime(ps, q);
+ const float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemRotationCurveId);
+ ps.rotationalSpeed[q] += Evaluate<mode> (curve, time, random);
+ }
+}
+
+template<bool isOptimized>
+void UpdateProceduralTpl(const DualMinMaxPolyCurves& curves, ParticleSystemParticles& ps)
+{
+ const size_t count = ps.array_size ();
+ for (int q=0; q<count; q++)
+ {
+ float time = NormalizedTime(ps, q);
+ float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemRotationCurveId);
+ float range = ps.startLifetime[q];
+ float value;
+ if(isOptimized)
+ value = EvaluateIntegrated (curves.optRot, time, random);
+ else
+ value = EvaluateIntegrated (curves.rot, time, random);
+ ps.rotation[q] += value * range;
+ }
+}
+
+RotationModule::RotationModule() : ParticleSystemModule(false)
+{}
+
+void RotationModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ if (m_Curve.minMaxState == kMMCScalar)
+ UpdateTpl<kEMScalar>(m_Curve, ps, fromIndex, toIndex);
+ else if(m_Curve.IsOptimized() && m_Curve.UsesMinMax())
+ UpdateTpl<kEMOptimizedMinMax>(m_Curve, ps, fromIndex, toIndex);
+ else if(m_Curve.IsOptimized())
+ UpdateTpl<kEMOptimized>(m_Curve, ps, fromIndex, toIndex);
+ else
+ UpdateTpl<kEMSlow>(m_Curve, ps, fromIndex, toIndex);
+
+}
+
+void RotationModule::UpdateProcedural (const ParticleSystemState& state, ParticleSystemParticles& ps)
+{
+ DualMinMaxPolyCurves curves;
+ if(m_Curve.IsOptimized())
+ {
+ curves.optRot = m_Curve.polyCurves; curves.optRot.Integrate();
+ UpdateProceduralTpl<true>(curves, ps);
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (m_Curve.editorCurves, m_Curve.minMaxState));
+ BuildCurves(curves.rot, m_Curve.editorCurves, m_Curve.GetScalar(), m_Curve.minMaxState); curves.rot.Integrate();
+ UpdateProceduralTpl<false>(curves, ps);
+ }
+}
+
+template<class TransferFunction>
+void RotationModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "curve");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(RotationModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/RotationModule.h b/Runtime/Graphics/ParticleSystem/Modules/RotationModule.h
new file mode 100644
index 0000000..494414e
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/RotationModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULEROTATION_H
+#define SHURIKENMODULEROTATION_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+
+class RotationModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (RotationModule)
+
+ RotationModule();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex);
+ void UpdateProcedural (const ParticleSystemState& state, ParticleSystemParticles& ps);
+ void CheckConsistency() {};
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_Curve;
+};
+
+#endif // SHURIKENMODULEROTATION_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp
new file mode 100644
index 0000000..3cdb007
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp
@@ -0,0 +1,650 @@
+#include "UnityPrefix.h"
+#include "ShapeModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystem.h"
+#include "Runtime/Graphics/TriStripper.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Geometry/ComputionalGeometry.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+#include "Runtime/Utilities/StrideIterator.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+enum MeshDistributionMode
+{
+ kDistributionVertex,
+ kDistributionTriangle,
+};
+
+
+/// This gives a random barycentric coord (on the edge of triangle)
+// @TODO: Stupid: Make this in a faster way
+inline Vector3f RandomBarycentricCoordEdge (Rand& rand)
+{
+ float u = rand.GetFloat ();
+ float v = rand.GetFloat ();
+ if (u + v > 1.0F)
+ {
+ u = 1.0F - u;
+ v = 1.0F - v;
+ }
+ float w = 1.0F - u - v;
+
+ int edge = RangedRandom(rand, 0, 2);
+ if(0 == edge)
+ {
+ v += 0.5f * u;
+ w += 0.5f * u;
+ u = 0.0f;
+ }
+ else if(1 == edge)
+ {
+ u += 0.5f * v;
+ w += 0.5f * v;
+ v = 0.0f;
+ }
+ else
+ {
+ u += 0.5f * w;
+ v += 0.5f * w;
+ w = 0.0f;
+ }
+
+ return Vector3f (u, v, w);
+}
+
+
+// TODO: It could make sense to initialize in separated loops. i.e. separate position and velcoity vectors
+inline void EmitterStoreData(const Matrix4x4f& localToWorld, const Vector3f& scale, ParticleSystemParticles& ps, size_t q, Vector3f& pos, Vector3f& n, Rand& random, bool randomDirection)
+{
+ if(randomDirection)
+ n = RandomUnitVector (random);
+
+ n = NormalizeSafe(n);
+
+ pos = Scale(pos, scale);
+
+ Vector3f vel = Magnitude (ps.velocity[q]) * n;
+ vel = localToWorld.MultiplyVector3 (vel);
+
+ // @TODO: Sooo... why multiply point and then undo the result of it? Why not just MultiplyVector?
+ pos = localToWorld.MultiplyPoint3 (pos) - localToWorld.GetPosition();
+ ps.position[q] += pos;
+ ps.velocity[q] = vel;
+
+#if 0 // WIP code for converting to spherical
+ Vector3f sp = ps.position[q];
+ ps.position[q].x = Sqrt(sp.x*sp.x + sp.y*sp.y + sp.z*sp.z);
+ ps.position[q].y = acosf(sp.z/ps.position[q].x);
+ ps.position[q].z = acosf(sp.y/ps.position[q].x);
+#endif
+
+ if(ps.usesAxisOfRotation)
+ {
+ Vector3f tan = Cross (-n, Vector3f::zAxis);
+ if (SqrMagnitude (tan) <= 0.01)
+ tan = Cross (-pos, Vector3f::zAxis);
+ if (SqrMagnitude (tan) <= 0.01)
+ tan = Vector3f::yAxis;
+ ps.axisOfRotation[q] = Normalize (tan);
+ }
+}
+
+inline void EmitterStoreData(const Matrix4x4f& localToWorld, const Vector3f& scale, ParticleSystemParticles& ps, size_t q, Vector3f& pos, Vector3f& n, ColorRGBA32& color, Rand& random, bool randomDirection)
+{
+ EmitterStoreData(localToWorld, scale, ps, q, pos, n, random, randomDirection);
+ ps.color[q] *= color;
+}
+
+
+template<MeshDistributionMode distributionMode>
+void GetPositionMesh (Vector3f& pos,
+ Vector3f& n,
+ ColorRGBA32& color,
+ const ParticleSystemEmitterMeshVertex* vertexData,
+ const int vertexCount,
+ const MeshTriangleData* triangleData,
+ const UInt32 numPrimitives,
+ float totalTriangleArea,
+ Rand& random,
+ bool edge)
+{
+ // position/normal of particle is vertex/vertex normal from mesh
+ if(kDistributionVertex == distributionMode)
+ {
+ int vertexIndex = RangedRandom (random, 0, vertexCount);
+ pos = vertexData[vertexIndex].position;
+ n = vertexData[vertexIndex].normal;
+ color = vertexData[vertexIndex].color;
+ }
+ else if(kDistributionTriangle == distributionMode)
+ {
+ float randomArea = RangedRandom(random, 0.0f, totalTriangleArea);
+ float accArea = 0.0f;
+ UInt32 triangleIndex = 0;
+
+ for(UInt32 i = 0; i < numPrimitives; i++)
+ {
+ const MeshTriangleData& data = triangleData[i];
+ accArea += data.area;
+ if(accArea >= randomArea)
+ {
+ triangleIndex = i;
+ break;
+ }
+ }
+
+ const MeshTriangleData& data = triangleData[triangleIndex];
+ UInt16 a = data.indices[0];
+ UInt16 b = data.indices[1];
+ UInt16 c = data.indices[2];
+
+ Vector3f barycenter;
+ if(edge)
+ barycenter = RandomBarycentricCoordEdge (random);
+ else
+ barycenter = RandomBarycentricCoord (random);
+
+ // Interpolate vertex with barycentric coordinate
+ pos = barycenter.x * vertexData[a].position + barycenter.y * vertexData[b].position + barycenter.z * vertexData[c].position;
+ n = barycenter.x * vertexData[a].normal + barycenter.y * vertexData[b].normal + barycenter.z * vertexData[c].normal;
+
+ // TODO: Don't convert to floats!!!
+ ColorRGBAf color1 = vertexData[a].color;
+ ColorRGBAf color2 = vertexData[b].color;
+ ColorRGBAf color3 = vertexData[c].color;
+ color = barycenter.x * color1 + barycenter.y * color2 + barycenter.z * color3;
+ }
+}
+
+static bool CompareMeshTriangleData (const MeshTriangleData& a, const MeshTriangleData& b)
+{
+ return (a.area > b.area);
+}
+
+static float BuildMeshAreaTable(MeshTriangleData* triData, const StrideIterator<Vector3f> vertices, const UInt16* indices, int numTriangles)
+{
+ float result = 0.0f;
+ for(int i = 0; i < numTriangles; i++)
+ {
+ const UInt16 a = indices[i * 3 + 0];
+ const UInt16 b = indices[i * 3 + 1];
+ const UInt16 c = indices[i * 3 + 2];
+ float area = TriangleArea3D (vertices[a], vertices[b], vertices[c]);
+ result += area;
+
+ triData[i].indices[0] = a;
+ triData[i].indices[1] = b;
+ triData[i].indices[2] = c;
+ triData[i].area = area;
+ }
+
+ return result;
+}
+
+// ------------------------------------------------------------------------------------------
+
+ShapeModule::ShapeModule () : ParticleSystemModule(true)
+, m_Type (kCone)
+, m_RandomDirection (false)
+, m_Angle(25.0f)
+, m_Radius(1.0f)
+, m_Length(5.0f)
+, m_BoxX(1.0f)
+, m_BoxY(1.0f)
+, m_BoxZ(1.0f)
+, m_PlacementMode(kVertex)
+, m_CachedMesh(NULL)
+, m_MeshNode (NULL)
+{
+}
+
+void ShapeModule::Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t)
+{
+ DebugAssert(roState.lengthInSec > 0.0001f);
+ const float normalizedT = t / roState.lengthInSec;
+ DebugAssert (normalizedT >= 0.0f);
+ DebugAssert (normalizedT <= 1.0f);
+
+ Rand& random = GetRandom();
+
+ if (m_Type == kMesh)
+ {
+ if(!m_CachedMesh)
+ return;
+
+ if(!m_CachedVertexData.size())
+ return;
+
+ if(!m_CachedTriangleData.size())
+ return;
+
+ const ParticleSystemEmitterMeshVertex* vertexData = &m_CachedVertexData[0];
+ const int vertexCount = m_CachedVertexData.size();
+ size_t count = ps.array_size ();
+ switch(m_PlacementMode)
+ {
+ case kVertex:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos;
+ Vector3f n;
+ ColorRGBA32 color;
+ GetPositionMesh<kDistributionVertex>(pos, n, color, vertexData, vertexCount, NULL, 0, m_CachedTotalTriangleArea, random, false);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kEdge:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos;
+ Vector3f n;
+ ColorRGBA32 color;
+ GetPositionMesh<kDistributionTriangle>(pos, n, color, vertexData, vertexCount, m_CachedTriangleData.begin(), m_CachedTriangleData.size(), m_CachedTotalTriangleArea, random, true);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kTriangle:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos;
+ Vector3f n;
+ ColorRGBA32 color;
+ GetPositionMesh<kDistributionTriangle>(pos, n, color, vertexData, vertexCount, m_CachedTriangleData.begin(), m_CachedTriangleData.size(), m_CachedTotalTriangleArea, random, false);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection);
+ }
+ break;
+ }
+ default:
+ {
+ DebugAssert(0 && "PlacementMode Not Supported");
+ }
+ }
+ }
+ else
+ {
+ const float r = m_Radius;
+
+ float a = Deg2Rad (m_Angle);
+ float sinA = Sin (a);
+ float cosA = Cos (a);
+ float length = m_Length;
+
+ const size_t count = ps.array_size ();
+ switch(m_Type)
+ {
+ case kSphere:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomPointInsideUnitSphere (random) * r;
+ Vector3f n = pos;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kSphereShell:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomUnitVector(random) * r;
+ Vector3f n = pos;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kHemiSphere:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomPointInsideUnitSphere (random) * r;
+ if (pos.z < 0.0f)
+ pos.z *= -1.0f;
+ Vector3f n = pos;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kHemiSphereShell:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomUnitVector (random) * r;
+ if (pos.z < 0.0f)
+ pos.z *= -1.0f;
+ Vector3f n = pos;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kCone:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector2f posXY = RandomPointInsideUnitCircle (random);
+ Vector2f nXY;
+ if(m_RandomDirection)
+ nXY = RandomPointInsideUnitCircle (random) * sinA;
+ else
+ nXY = Vector2f(posXY.x, posXY.y)* sinA;
+ Vector3f n (nXY.x, nXY.y, cosA);
+ Vector3f pos (posXY.x * r, posXY.y * r, 0.0f);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, false);
+ }
+ break;
+ }
+ case kConeShell:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random));
+
+ Vector2f nXY;
+ if(m_RandomDirection)
+ nXY = RandomPointInsideUnitCircle (random) * sinA;
+ else
+ nXY = Vector2f(posXY.x, posXY.y)* sinA;
+ Vector3f n (nXY.x, nXY.y, cosA);
+ Vector3f pos (posXY.x * r, posXY.y * r, 0.0f);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, false);
+ }
+ break;
+ }
+ case kConeVolume:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector2f posXY = RandomPointInsideUnitCircle (random);
+ Vector2f nXY = Vector2f(posXY.x, posXY.y)* sinA;
+ Vector3f n (nXY.x, nXY.y, cosA);
+ Vector3f pos (posXY.x * r, posXY.y * r, 0.0f);
+ pos += length * Random01(random) * NormalizeSafe(n);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kConeVolumeShell:
+ {
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random));
+ Vector2f nXY = Vector2f(posXY.x, posXY.y)* sinA;
+ Vector3f n (nXY.x, nXY.y, cosA);
+ Vector3f pos = Vector3f(posXY.x * r, posXY.y * r, 0.0f);
+ pos += length * Random01(random) * NormalizeSafe(n);
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ break;
+ }
+ case kBox:
+ {
+ const Vector3f extents (0.5f * m_BoxX, 0.5f * m_BoxY, 0.5f * m_BoxZ);
+ for (int q = fromIndex; q < count; ++q)
+ {
+ Vector3f pos = RandomPointInsideCube (random, extents);
+ Vector3f n = Vector3f::zAxis;
+ EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection);
+ }
+ }
+ break;
+ default:
+ {
+ DebugAssert(0 && "Shape not supported");
+ }
+ }
+ }
+}
+
+void ShapeModule::CalculateProceduralBounds(MinMaxAABB& bounds, const Vector3f& emitterScale, Vector2f minMaxBounds) const
+{
+ DebugAssert(minMaxBounds.x <= minMaxBounds.y);
+
+ switch(m_Type)
+ {
+ case kSphere:
+ case kSphereShell:
+ bounds.m_Max = Vector3f(m_Radius, m_Radius, m_Radius);
+ bounds.m_Min = -bounds.m_Max;
+ break;
+ case kHemiSphere:
+ case kHemiSphereShell:
+ bounds.m_Max = Vector3f(m_Radius, m_Radius, m_Radius);
+ bounds.m_Min = Vector3f(-m_Radius, -m_Radius, 0.0f);
+ break;
+ case kCone:
+ case kConeShell:
+ bounds.m_Max = Vector3f(m_Radius, m_Radius, 0.0f);
+ bounds.m_Min = -bounds.m_Max;
+ break;
+ case kConeVolume:
+ case kConeVolumeShell:
+ {
+ const float a = Deg2Rad (m_Angle);
+ const float coneRadius2 = m_Radius + m_Length * Sin (a);
+ const float coneLength = m_Length * Cos (a);
+ bounds.m_Max = Vector3f(coneRadius2, coneRadius2, coneLength);
+ bounds.m_Min = -Vector3f(coneRadius2, coneRadius2, 0.0f);
+ break;
+ }
+ case kBox:
+ bounds.m_Max = Vector3f(m_BoxX, m_BoxY, m_BoxZ) * 0.5f;
+ bounds.m_Min = -bounds.m_Max;
+ break;
+ case kMesh:
+ {
+ if(m_CachedMesh)
+ bounds = m_CachedMesh->GetBounds(0);
+ else
+ bounds = MinMaxAABB(Vector3f::zero, Vector3f::zero);
+ break;
+ }
+ default:
+ {
+ AssertBreak(!"Shape not implemented.");
+ }
+ }
+
+ bounds.m_Min = Scale(bounds.m_Min, emitterScale);
+ bounds.m_Max = Scale(bounds.m_Max, emitterScale);
+
+ MinMaxAABB speedBounds;
+
+ // Cone and cone shell random direction only deviate inside the bound
+ if(m_RandomDirection && (m_Type != kCone) && (m_Type != kConeShell))
+ {
+ speedBounds.m_Max = Vector3f::one;
+ speedBounds.m_Min = -Vector3f::one;
+ minMaxBounds = Abs(minMaxBounds);
+ }
+ else
+ {
+ switch(m_Type)
+ {
+ case kSphere:
+ case kSphereShell:
+ case kMesh:
+ speedBounds.m_Max = Vector3f::one;
+ speedBounds.m_Min = -Vector3f::one;
+ break;
+ case kHemiSphere:
+ case kHemiSphereShell:
+ speedBounds.m_Max = Vector3f::one;
+ speedBounds.m_Min = Vector3f(-1.0f, -1.0f, 0.0f);
+ break;
+ case kCone:
+ case kConeShell:
+ case kConeVolume:
+ case kConeVolumeShell:
+ {
+ const float a = Deg2Rad (m_Angle);
+ const float sinA = Sin (a);
+ speedBounds.m_Max = Vector3f(sinA, sinA, 1.0f);
+ speedBounds.m_Min = Vector3f(-sinA, -sinA, 0.0f);
+ break;
+ }
+ case kBox:
+ speedBounds.m_Max = Vector3f::zAxis;
+ speedBounds.m_Min = Vector3f::zero;
+ break;
+ default:
+ {
+ AssertBreak(!"Shape not implemented.");
+ }
+ }
+ }
+
+ MinMaxAABB speedBound;
+ speedBound.m_Min = bounds.m_Min + speedBounds.m_Min * minMaxBounds.y;
+ speedBound.m_Max = bounds.m_Max + speedBounds.m_Max * minMaxBounds.y;
+ bounds.Encapsulate(speedBound);
+
+ MinMaxAABB negSpeedBound;
+ negSpeedBound.m_Min = speedBounds.m_Min * minMaxBounds.x;
+ negSpeedBound.m_Max = speedBounds.m_Max * minMaxBounds.x;
+ speedBound.m_Min = min(negSpeedBound.m_Min, negSpeedBound.m_Max);
+ speedBound.m_Max = max(negSpeedBound.m_Min, negSpeedBound.m_Max);
+ bounds.Encapsulate(speedBound);
+}
+
+void ShapeModule::CheckConsistency ()
+{
+ m_Type = clamp<int> (m_Type, kSphere, kMax-1);
+ m_PlacementMode = clamp<int> (m_PlacementMode, kVertex, kModeMax-1);
+
+ m_Angle = clamp(m_Angle, 0.0f, 90.0f);
+ m_Radius = max(0.01f, m_Radius);
+ m_Length = max(0.0f, m_Length);
+ m_BoxX = max(0.0f, m_BoxX);
+ m_BoxY = max(0.0f, m_BoxY);
+ m_BoxZ = max(0.0f, m_BoxZ);
+}
+
+void ShapeModule::AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState)
+{
+ m_MeshNode.RemoveFromList();
+ m_MeshNode.SetData(system);
+ m_CachedMesh = m_Mesh;
+ if (m_CachedMesh != NULL)
+ m_CachedMesh->AddObjectUser( m_MeshNode );
+ DidModifyMeshData();
+
+ ResetSeed(roState);
+}
+
+void ShapeModule::ResetSeed(const ParticleSystemReadOnlyState& roState)
+{
+ if(roState.randomSeed == 0)
+ m_Random.SetSeed(GetGlobalRandomSeed ());
+ else
+ m_Random.SetSeed(roState.randomSeed);
+}
+
+void ShapeModule::DidDeleteMesh (ParticleSystem* system)
+{
+ m_CachedMesh = NULL;
+}
+
+void ShapeModule::DidModifyMeshData ()
+{
+ if (m_CachedMesh == NULL)
+ {
+ m_CachedTriangleData.resize_uninitialized(0);
+ m_CachedVertexData.resize_uninitialized(0);
+ m_CachedTotalTriangleArea = 0;
+ return;
+ }
+
+
+ const StrideIterator<Vector3f> vertexBuffer = m_CachedMesh->GetVertexBegin();
+ const UInt16* indexBuffer = m_CachedMesh->GetSubMeshBuffer16(0);
+ const SubMesh& submesh = m_CachedMesh->GetSubMeshFast (0);
+ if (submesh.topology == kPrimitiveTriangleStripDeprecated)
+ {
+ const int numTriangles = CountTrianglesInStrip(indexBuffer, submesh.indexCount);
+ const int capacity = numTriangles * 3;
+ UNITY_TEMP_VECTOR(UInt16) tempIndices(capacity);
+ Destripify(indexBuffer, submesh.indexCount, &tempIndices[0], capacity);
+ m_CachedTriangleData.resize_uninitialized(numTriangles);
+ m_CachedTotalTriangleArea = BuildMeshAreaTable(m_CachedTriangleData.begin(), vertexBuffer, &tempIndices[0], numTriangles);
+ }
+ else if (submesh.topology == kPrimitiveTriangles)
+ {
+ const int numTriangles = submesh.indexCount/3;
+ m_CachedTriangleData.resize_uninitialized(numTriangles);
+ m_CachedTotalTriangleArea = BuildMeshAreaTable(m_CachedTriangleData.begin(), vertexBuffer, indexBuffer, numTriangles);
+ }
+ else
+ {
+ m_CachedMesh = NULL;
+ }
+
+ // Optimization: This sorts so big triangles comes before small, which means finding the right triangle is faster
+ std::sort(m_CachedTriangleData.begin(), m_CachedTriangleData.begin() + m_CachedTriangleData.size(), CompareMeshTriangleData);
+
+ // Cache vertices
+ const int vertexCount = m_CachedMesh->GetVertexCount();
+ const StrideIterator<Vector3f> vertices = m_CachedMesh->GetVertexBegin();
+ const StrideIterator<Vector3f> normals = m_CachedMesh->GetNormalBegin();
+ const StrideIterator<ColorRGBA32> colors = m_CachedMesh->GetColorBegin();
+ m_CachedVertexData.resize_uninitialized(vertexCount);
+ for(int i = 0; i < vertexCount; i++)
+ {
+ m_CachedVertexData[i].position = vertices[i];
+
+ if(!normals.IsNull())
+ m_CachedVertexData[i].normal = normals[i];
+ else
+ m_CachedVertexData[i].normal = Vector3f::zero;
+
+ if(!colors.IsNull())
+ m_CachedVertexData[i].color = colors[i];
+ else
+ m_CachedVertexData[i].color = ColorRGBA32(0xffffffff);
+ }
+}
+
+Rand& ShapeModule::GetRandom()
+{
+#if UNITY_EDITOR
+ if(!IsWorldPlaying())
+ return m_EditorRandom;
+ else
+#endif
+ return m_Random;
+}
+
+template<class TransferFunction>
+void ShapeModule::Transfer (TransferFunction& transfer)
+{
+ transfer.SetVersion(2);
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Type, "type");
+
+ // Primitive
+ transfer.Transfer(m_Radius, "radius");
+ transfer.Transfer(m_Angle, "angle");
+ transfer.Transfer(m_Length, "length");
+ transfer.Transfer(m_BoxX, "boxX");
+ transfer.Transfer(m_BoxY, "boxY");
+ transfer.Transfer(m_BoxZ, "boxZ");
+
+ // Mesh
+ transfer.Transfer (m_PlacementMode, "placementMode");
+ TRANSFER (m_Mesh);
+
+ transfer.Transfer (m_RandomDirection, "randomDirection"); transfer.Align();
+
+ // In Unity 3.5 all cone emitters had random direction set to false, but behaved as if it was true
+ if(transfer.IsOldVersion(1))
+ if(kCone == m_Type)
+ m_RandomDirection = true;
+}
+
+INSTANTIATE_TEMPLATE_TRANSFER(ShapeModule)
+
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.h b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.h
new file mode 100644
index 0000000..1f95d33
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.h
@@ -0,0 +1,80 @@
+#ifndef SHURIKENMODULESHAPE_H
+#define SHURIKENMODULESHAPE_H
+
+#include "Runtime/BaseClasses/BaseObject.h"
+#include "ParticleSystemModule.h"
+#include "Runtime/Math/Random/rand.h"
+#include "Runtime/Utilities/LinkedList.h"
+
+struct MeshTriangleData
+{
+ float area;
+ UInt16 indices[3];
+};
+
+struct ParticleSystemEmitterMeshVertex
+{
+ Vector3f position;
+ Vector3f normal;
+ ColorRGBA32 color;
+};
+
+class Mesh;
+
+class ShapeModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (ShapeModule)
+ ShapeModule ();
+
+ enum MeshPlacementMode { kVertex, kEdge, kTriangle, kModeMax };
+ enum { kSphere, kSphereShell, kHemiSphere, kHemiSphereShell, kCone, kBox, kMesh, kConeShell, kConeVolume, kConeVolumeShell, kMax };
+
+ void AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState);
+ void ResetSeed(const ParticleSystemReadOnlyState& roState);
+ void DidModifyMeshData ();
+ void DidDeleteMesh (ParticleSystem* system);
+
+ PPtr<Mesh> GetMeshEmitterShape () { return m_Mesh; }
+
+ void Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t);
+ void CalculateProceduralBounds(MinMaxAABB& bounds, const Vector3f& emitterScale, Vector2f minMaxBounds) const;
+ void CheckConsistency ();
+
+ inline void SetShapeType(int type) { m_Type = type; };
+ inline void SetRadius(float radius) { m_Radius = radius; };
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ Rand& GetRandom();
+
+ int m_Type;
+
+ // Primitive stuff
+ float m_Radius;
+ float m_Angle;
+ float m_Length;
+ float m_BoxX;
+ float m_BoxY;
+ float m_BoxZ;
+
+ // Mesh stuff
+ int m_PlacementMode;
+ PPtr<Mesh> m_Mesh;
+ Mesh* m_CachedMesh;
+ dynamic_array<ParticleSystemEmitterMeshVertex> m_CachedVertexData;
+ dynamic_array<MeshTriangleData> m_CachedTriangleData;
+ float m_CachedTotalTriangleArea;
+ ListNode<Object> m_MeshNode;
+
+ bool m_RandomDirection;
+ Rand m_Random;
+#if UNITY_EDITOR
+public:
+ Rand m_EditorRandom;
+#endif
+};
+
+#endif // SHURIKENMODULESHAPE_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.cpp
new file mode 100644
index 0000000..21cd5df
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.cpp
@@ -0,0 +1,50 @@
+#include "UnityPrefix.h"
+#include "SizeByVelocityModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+SizeBySpeedModule::SizeBySpeedModule () : ParticleSystemModule(false)
+, m_Range (0.0f, 1.0f)
+{}
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& curve, const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex, const Vector2f offsetScale)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const Vector3f vel = ps.velocity[q] + ps.animatedVelocity[q];
+ const float t = InverseLerpFast01 (offsetScale, Magnitude (vel));
+ const float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemSizeBySpeedCurveId);
+ tempSize[q] *= max<float> (0.0f, Evaluate<mode> (curve, t, random));
+ }
+}
+
+void SizeBySpeedModule::Update (const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex)
+{
+ DebugAssert(toIndex <= ps.array_size ());
+ Vector2f offsetScale = CalculateInverseLerpOffsetScale(m_Range);
+ if (m_Curve.minMaxState == kMMCScalar)
+ UpdateTpl<kEMScalar> (m_Curve, ps, tempSize, fromIndex, toIndex, offsetScale);
+ else if (m_Curve.IsOptimized() && m_Curve.UsesMinMax())
+ UpdateTpl<kEMOptimizedMinMax> (m_Curve, ps, tempSize, fromIndex, toIndex, offsetScale);
+ else if(m_Curve.IsOptimized())
+ UpdateTpl<kEMOptimized> (m_Curve, ps, tempSize, fromIndex, toIndex, offsetScale);
+ else
+ UpdateTpl<kEMSlow> (m_Curve, ps, tempSize, fromIndex, toIndex, offsetScale);
+}
+
+void SizeBySpeedModule::CheckConsistency ()
+{
+ const float MyEpsilon = 0.001f;
+ m_Range.x = std::min (m_Range.x, m_Range.y - MyEpsilon);
+}
+
+template<class TransferFunction>
+void SizeBySpeedModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "curve");
+ transfer.Transfer (m_Range, "range");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(SizeBySpeedModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.h
new file mode 100644
index 0000000..c8ad729
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SizeByVelocityModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULESIZEBYVELOCITY_H
+#define SHURIKENMODULESIZEBYVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Math/Vector2.h"
+
+class SizeBySpeedModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (SizeBySpeedModule)
+ SizeBySpeedModule ();
+
+ void Update (const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex);
+ void CheckConsistency ();
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_Curve;
+ Vector2f m_Range;
+};
+
+#endif // SHURIKENMODULESIZEBYVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SizeModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/SizeModule.cpp
new file mode 100644
index 0000000..ad08a4a
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SizeModule.cpp
@@ -0,0 +1,41 @@
+#include "UnityPrefix.h"
+#include "SizeModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& curve, const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float time = NormalizedTime(ps, q);
+ const float random = GenerateRandom(ps.randomSeed[q] + kParticleSystemSizeCurveId);
+ tempSize[q] *= max<float>(0.0f, Evaluate<mode> (curve, time, random));
+ }
+}
+
+SizeModule::SizeModule() : ParticleSystemModule(false)
+{}
+
+void SizeModule::Update (const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex)
+{
+ DebugAssert(toIndex <= ps.array_size ());
+ if(m_Curve.minMaxState == kMMCScalar)
+ UpdateTpl<kEMScalar>(m_Curve, ps, tempSize, fromIndex, toIndex);
+ else if (m_Curve.IsOptimized() && m_Curve.UsesMinMax ())
+ UpdateTpl<kEMOptimizedMinMax>(m_Curve, ps, tempSize, fromIndex, toIndex);
+ else if(m_Curve.IsOptimized())
+ UpdateTpl<kEMOptimized>(m_Curve, ps, tempSize, fromIndex, toIndex);
+ else
+ UpdateTpl<kEMSlow>(m_Curve, ps, tempSize, fromIndex, toIndex);
+}
+
+template<class TransferFunction>
+void SizeModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "curve");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(SizeModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SizeModule.h b/Runtime/Graphics/ParticleSystem/Modules/SizeModule.h
new file mode 100644
index 0000000..3c92634
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SizeModule.h
@@ -0,0 +1,27 @@
+#ifndef SHURIKENMODULESIZE_H
+#define SHURIKENMODULESIZE_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+
+class SizeModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (SizeModule)
+
+ SizeModule();
+
+ void Update (const ParticleSystemParticles& ps, float* tempSize, size_t fromIndex, size_t toIndex);
+
+ void CheckConsistency () {};
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_Curve;
+};
+
+#endif // SHURIKENMODULESIZE_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SubModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/SubModule.cpp
new file mode 100644
index 0000000..895c444
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SubModule.cpp
@@ -0,0 +1,148 @@
+#include "UnityPrefix.h"
+#include "SubModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "../ParticleSystemUtils.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystem.h" // Only because of PPtr comparison
+
+bool IsUsingSubEmitter(ParticleSystem* emitter)
+{
+ const void* shurikenSelf = NULL;
+ return emitter != shurikenSelf;
+}
+
+SubModule::SubModule () : ParticleSystemModule(false)
+{
+}
+
+void SubModule::Update (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, size_t toIndex, float dt) const
+{
+ Assert(state.numCachedSubDataBirth <= kParticleSystemMaxNumEmitAccumulators);
+ for(int i = 0; i < state.numCachedSubDataBirth; i++)
+ {
+ const ParticleSystemSubEmitterData& data = state.cachedSubDataBirth[i];
+ //const bool culled = (data.maxLifetime < state.accumulatedDt);
+ const float startDelay = data.startDelayInSec;
+ const float length = data.lengthInSec;
+ for (int q = fromIndex; q < toIndex; ++q)
+ {
+ const float t = std::max(0.0f, ps.startLifetime[q] - ps.lifetime[q] - dt) - startDelay;
+ const bool started = (t >= 0.0f);
+ const bool ended = (t >= length);
+ if(started && !ended)
+ {
+ ParticleSystemEmissionState emissionState;
+ emissionState.m_ToEmitAccumulator = ps.emitAccumulator[i][q];
+ RecordEmit(emissionState, data, roState, state, ps, kParticleSystemSubTypeBirth, i, q, t, dt, length);
+ ps.emitAccumulator[i][q] = emissionState.m_ToEmitAccumulator;
+ }
+ }
+ }
+}
+
+void SubModule::RemoveDuplicatePtrs (ParticleSystem** shurikens)
+{
+ for(int i = 0; i < kParticleSystemMaxSubTotal-1; i++)
+ for(int j = i+1; j < kParticleSystemMaxSubTotal; j++)
+ if(shurikens[i] && (shurikens[i] == shurikens[j]))
+ shurikens[i] = NULL;
+}
+
+// This can not be cached between frames since the referenced particle systems might be deleted at any point.
+int SubModule::GetSubEmitterPtrs (ParticleSystem** subEmitters) const
+{
+ for(int i = 0; i < kParticleSystemMaxSubTotal; i++)
+ subEmitters[i] = NULL;
+
+ int numSubEmitters = 0;
+ for(int i = 0; i < kParticleSystemMaxSubBirth; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersBirth[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ for(int i = 0; i < kParticleSystemMaxSubCollision; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersCollision[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ for(int i = 0; i < kParticleSystemMaxSubDeath; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersDeath[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ return numSubEmitters;
+}
+
+int SubModule::GetSubEmitterPtrsBirth (ParticleSystem** subEmitters) const
+{
+ int numSubEmitters = 0;
+ for(int i = 0; i < kParticleSystemMaxSubBirth; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersBirth[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ return numSubEmitters;
+}
+
+int SubModule::GetSubEmitterPtrsCollision (ParticleSystem** subEmitters) const
+{
+ int numSubEmitters = 0;
+ for(int i = 0; i < kParticleSystemMaxSubCollision; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersCollision[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ return numSubEmitters;
+}
+
+int SubModule::GetSubEmitterPtrsDeath(ParticleSystem** subEmitters) const
+{
+ int numSubEmitters = 0;
+ for(int i = 0; i < kParticleSystemMaxSubDeath; i++)
+ {
+ ParticleSystem* subParticleSystem = m_SubEmittersDeath[i];
+ if (IsUsingSubEmitter(subParticleSystem))
+ subEmitters[numSubEmitters++] = subParticleSystem;
+ }
+ return numSubEmitters;
+}
+
+int SubModule::GetSubEmitterTypeCount(ParticleSystemSubType type) const
+{
+ int count = 0;
+ const void* shurikenSelf = NULL;
+ if(type == kParticleSystemSubTypeBirth) {
+ for(int i = 0; i < kParticleSystemMaxSubBirth; i++)
+ if(m_SubEmittersBirth[i] != shurikenSelf)
+ count++;
+ } else if(type == kParticleSystemSubTypeCollision) {
+ for(int i = 0; i < kParticleSystemMaxSubCollision; i++)
+ if(m_SubEmittersCollision[i] != shurikenSelf)
+ count++;
+ } else if(type == kParticleSystemSubTypeDeath) {
+ for(int i = 0; i < kParticleSystemMaxSubDeath; i++)
+ if(m_SubEmittersDeath[i] != shurikenSelf)
+ count++;
+ } else {
+ Assert(!"Sub emitter type not implemented.");
+ }
+ return count;
+}
+
+template<class TransferFunction>
+void SubModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_SubEmittersBirth[0], "subEmitterBirth");
+ transfer.Transfer (m_SubEmittersBirth[1], "subEmitterBirth1");
+ transfer.Transfer (m_SubEmittersCollision[0], "subEmitterCollision");
+ transfer.Transfer (m_SubEmittersCollision[1], "subEmitterCollision1");
+ transfer.Transfer (m_SubEmittersDeath[0], "subEmitterDeath");
+ transfer.Transfer (m_SubEmittersDeath[1], "subEmitterDeath1");
+}
+INSTANTIATE_TEMPLATE_TRANSFER(SubModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/SubModule.h b/Runtime/Graphics/ParticleSystem/Modules/SubModule.h
new file mode 100644
index 0000000..093c8ba
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/SubModule.h
@@ -0,0 +1,38 @@
+#ifndef SHURIKENMODULESUB_H
+#define SHURIKENMODULESUB_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/BaseClasses/BaseObject.h"
+
+class ParticleSystem;
+struct ParticleSystemParticles;
+
+
+class SubModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (SubModule)
+ SubModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, size_t toIndex, float dt) const;
+ void CheckConsistency() {};
+
+ int GetSubEmitterTypeCount(ParticleSystemSubType type) const;
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+ static void RemoveDuplicatePtrs (ParticleSystem** shurikens);
+ int GetSubEmitterPtrs (ParticleSystem** shurikens) const;
+ int GetSubEmitterPtrsBirth (ParticleSystem** shurikens) const;
+ int GetSubEmitterPtrsCollision (ParticleSystem** shurikens) const;
+ int GetSubEmitterPtrsDeath(ParticleSystem** shurikens) const;
+
+
+private:
+ PPtr<ParticleSystem> m_SubEmittersBirth[kParticleSystemMaxSubBirth];
+ PPtr<ParticleSystem> m_SubEmittersCollision[kParticleSystemMaxSubCollision];
+ PPtr<ParticleSystem> m_SubEmittersDeath[kParticleSystemMaxSubDeath];
+};
+
+#endif // SHURIKENMODULESUB_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/UVModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/UVModule.cpp
new file mode 100644
index 0000000..67d49b3
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/UVModule.cpp
@@ -0,0 +1,105 @@
+#include "UnityPrefix.h"
+#include "UVModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateWholeSheetTpl(float cycles, const MinMaxCurve& curve, const ParticleSystemParticles& ps, float* tempSheetIndex, size_t fromIndex, size_t toIndex)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ tempSheetIndex[q] = Repeat (cycles * Evaluate(curve, NormalizedTime(ps, q), GenerateRandom(ps.randomSeed[q] + kParticleSystemUVCurveId)), 1.0f);
+}
+
+UVModule::UVModule () : ParticleSystemModule(false)
+, m_TilesX (1), m_TilesY (1)
+, m_AnimationType (kWholeSheet)
+, m_RowIndex (0)
+, m_Cycles (1.0f)
+, m_RandomRow (true)
+{}
+
+void UVModule::Update (const ParticleSystemParticles& ps, float* tempSheetIndex, size_t fromIndex, size_t toIndex)
+{
+ const float cycles = m_Cycles;
+
+ DebugAssert(toIndex <= ps.array_size ());
+ if (m_AnimationType == kSingleRow) // row
+ {
+ int rows = m_TilesY;
+ float animRange = (1.0f / (m_TilesX * rows)) * m_TilesX;
+ if(m_RandomRow)
+ {
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float t = cycles * Evaluate(m_Curve, NormalizedTime(ps, q), GenerateRandom(ps.randomSeed[q] + kParticleSystemUVCurveId));
+ const float x = Repeat (t, 1.0f);
+ const float randomValue = GenerateRandom(ps.randomSeed[q] + kParticleSystemUVRowSelectionId);
+ const float startRow = Floorf (randomValue * rows);
+ float from = startRow * animRange;
+ float to = from + animRange;
+ tempSheetIndex[q] = Lerp (from, to, x);
+ }
+ }
+ else
+ {
+ const float startRow = Floorf(m_RowIndex * animRange * rows);
+ float from = startRow * animRange;
+ float to = from + animRange;
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ const float t = cycles * Evaluate(m_Curve, NormalizedTime(ps, q), GenerateRandom(ps.randomSeed[q] + kParticleSystemUVCurveId));
+ const float x = Repeat (t, 1.0f);
+ tempSheetIndex[q] = Lerp (from, to, x);
+ }
+ }
+ }
+ else if (m_AnimationType == kWholeSheet) // grid || row
+ {
+ if(m_Curve.minMaxState == kMMCScalar)
+ UpdateWholeSheetTpl<kEMScalar>(m_Cycles, m_Curve, ps, tempSheetIndex, fromIndex, toIndex);
+ else if (m_Curve.IsOptimized() && m_Curve.UsesMinMax ())
+ UpdateWholeSheetTpl<kEMOptimizedMinMax>(m_Cycles, m_Curve, ps, tempSheetIndex, fromIndex, toIndex);
+ else if(m_Curve.IsOptimized())
+ UpdateWholeSheetTpl<kEMOptimized>(m_Cycles, m_Curve, ps, tempSheetIndex, fromIndex, toIndex);
+ else
+ UpdateWholeSheetTpl<kEMSlow>(m_Cycles, m_Curve, ps, tempSheetIndex, fromIndex, toIndex);
+ }
+ else
+ {
+ Assert(!"Animation mode not implemented!");
+ }
+}
+
+void UVModule::CheckConsistency ()
+{
+ m_AnimationType = clamp<int> (m_AnimationType, 0, kNumAnimationTypes-1);
+ m_TilesX = std::max<int> (1, m_TilesX);
+ m_TilesY = std::max<int> (1, m_TilesY);
+ m_Cycles = std::max<int> (1, (int)m_Cycles);
+ m_RowIndex = clamp<int> (m_RowIndex, 0, m_TilesY-1);
+ m_Curve.SetScalar(clamp<float> (m_Curve.GetScalar(), 0.0f, 1.0f));
+}
+
+void UVModule::GetNumTiles(int& uvTilesX, int& uvTilesY) const
+{
+ uvTilesX = m_TilesX;
+ uvTilesY = m_TilesY;
+}
+
+template<class TransferFunction>
+void UVModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_Curve, "frameOverTime");
+ transfer.Transfer (m_TilesX, "tilesX");
+ transfer.Transfer (m_TilesY, "tilesY");
+ transfer.Transfer (m_AnimationType, "animationType");
+ transfer.Transfer (m_RowIndex, "rowIndex");
+ transfer.Transfer (m_Cycles, "cycles");
+ transfer.Transfer (m_RandomRow, "randomRow"); transfer.Align ();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(UVModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/UVModule.h b/Runtime/Graphics/ParticleSystem/Modules/UVModule.h
new file mode 100644
index 0000000..5ed9e7f
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/UVModule.h
@@ -0,0 +1,36 @@
+#ifndef SHURIKENMODULEUV_H
+#define SHURIKENMODULEUV_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/PolynomialCurve.h"
+
+struct ParticleSystemParticles;
+
+class UVModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (UVModule)
+ UVModule ();
+
+ void Update (const ParticleSystemParticles& ps, float* tempSheetIndex, size_t fromIndex, size_t toIndex);
+ void CheckConsistency ();
+
+ inline MinMaxCurve& GetCurve() { return m_Curve; }
+
+ void GetNumTiles(int& uvTilesX, int& uvTilesY) const;
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ enum { kWholeSheet, kSingleRow, kNumAnimationTypes };
+
+ MinMaxCurve m_Curve;
+ int m_TilesX, m_TilesY;
+ int m_AnimationType;
+ int m_RowIndex;
+ float m_Cycles;
+ bool m_RandomRow;
+};
+
+#endif // SHURIKENMODULEUV_H
diff --git a/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.cpp
new file mode 100644
index 0000000..edb7ed9
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.cpp
@@ -0,0 +1,127 @@
+#include "UnityPrefix.h"
+#include "VelocityModule.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "../ParticleSystemUtils.h"
+#include "Runtime/Math/Random/Random.h"
+#include "Runtime/Math/Matrix4x4.h"
+
+template<ParticleSystemCurveEvalMode mode>
+void UpdateTpl(const MinMaxCurve& x, const MinMaxCurve& y, const MinMaxCurve& z, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, bool transform, const Matrix4x4f& matrix)
+{
+ for (size_t q = fromIndex; q < toIndex; ++q)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemVelocityCurveId);
+
+ const float normalizedTime = NormalizedTime (ps, q);
+ Vector3f vel = Vector3f (Evaluate<mode> (x, normalizedTime, random.x), Evaluate<mode> (y, normalizedTime, random.y), Evaluate<mode> (z, normalizedTime, random.z));
+ if(transform)
+ vel = matrix.MultiplyVector3 (vel);
+ ps.animatedVelocity[q] += vel;
+ }
+}
+
+template<bool isOptimized>
+void UpdateProceduralTpl(const DualMinMax3DPolyCurves& curves, const MinMaxCurve& x, const MinMaxCurve& y, const MinMaxCurve& z, ParticleSystemParticles& ps, const Matrix4x4f& matrix, bool transform)
+{
+ const size_t count = ps.array_size ();
+ for (int q=0;q<count;q++)
+ {
+ Vector3f random;
+ GenerateRandom3(random, ps.randomSeed[q] + kParticleSystemVelocityCurveId);
+ const float time = NormalizedTime(ps, q);
+
+ Vector3f delta;
+ if(isOptimized)
+ delta = Vector3f(EvaluateIntegrated(curves.optX, time, random.x), EvaluateIntegrated(curves.optY, time, random.y), EvaluateIntegrated(curves.optZ, time, random.z)) * ps.startLifetime[q];
+ else
+ delta = Vector3f(EvaluateIntegrated(curves.x, time, random.x), EvaluateIntegrated(curves.y, time, random.y), EvaluateIntegrated(curves.z, time, random.z)) * ps.startLifetime[q];
+
+ Vector3f velocity (Evaluate(x, time, random.x), Evaluate(y, time, random.y), Evaluate(z, time, random.z));
+ if(transform)
+ {
+ delta = matrix.MultiplyVector3 (delta);
+ velocity = matrix.MultiplyVector3 (velocity);
+ }
+ ps.position[q] += delta;
+ ps.animatedVelocity[q] += velocity;
+ }
+}
+
+VelocityModule::VelocityModule () : ParticleSystemModule(false)
+, m_InWorldSpace (false)
+{}
+
+void VelocityModule::Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex)
+{
+ Matrix4x4f matrix;
+ bool transform = GetTransformationMatrix(matrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+
+ bool usesScalar = (m_X.minMaxState == kMMCScalar) && (m_Y.minMaxState == kMMCScalar) && (m_Z.minMaxState == kMMCScalar);
+ bool isOptimized = m_X.IsOptimized() && m_Y.IsOptimized() && m_Z.IsOptimized();
+ bool usesMinMax = m_X.UsesMinMax() && m_Y.UsesMinMax() && m_Z.UsesMinMax();
+ if(usesScalar)
+ UpdateTpl<kEMScalar>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix);
+ else if(isOptimized && usesMinMax)
+ UpdateTpl<kEMOptimizedMinMax>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix);
+ else if(isOptimized)
+ UpdateTpl<kEMOptimized>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix);
+ else
+ UpdateTpl<kEMSlow>(m_X, m_Y, m_Z, ps, fromIndex, toIndex, transform, matrix);
+
+}
+
+void VelocityModule::UpdateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps)
+{
+ Matrix4x4f matrix;
+ bool transform = GetTransformationMatrix(matrix, !roState.useLocalSpace, m_InWorldSpace, state.localToWorld);
+
+ DualMinMax3DPolyCurves curves;
+ if(m_X.IsOptimized() && m_Y.IsOptimized() && m_Z.IsOptimized())
+ {
+ curves.optX = m_X.polyCurves; curves.optX.Integrate();
+ curves.optY = m_Y.polyCurves; curves.optY.Integrate();
+ curves.optZ = m_Z.polyCurves; curves.optZ.Integrate();
+ UpdateProceduralTpl<true>(curves, m_X, m_Y, m_Z, ps, matrix, transform);
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (m_X.editorCurves, m_X.minMaxState));
+ DebugAssert(CurvesSupportProcedural (m_Y.editorCurves, m_Y.minMaxState));
+ DebugAssert(CurvesSupportProcedural (m_Z.editorCurves, m_Z.minMaxState));
+ BuildCurves(curves.x, m_X.editorCurves, m_X.GetScalar(), m_X.minMaxState); curves.x.Integrate();
+ BuildCurves(curves.y, m_Y.editorCurves, m_Y.GetScalar(), m_Y.minMaxState); curves.y.Integrate();
+ BuildCurves(curves.z, m_Z.editorCurves, m_Z.GetScalar(), m_Z.minMaxState); curves.z.Integrate();
+ UpdateProceduralTpl<false>(curves, m_X, m_Y, m_Z, ps, matrix, transform);
+ }
+}
+
+void VelocityModule::CalculateProceduralBounds(MinMaxAABB& bounds, const Matrix4x4f& localToWorld, float maxLifeTime)
+{
+ Vector2f xRange = m_X.FindMinMaxIntegrated();
+ Vector2f yRange = m_Y.FindMinMaxIntegrated();
+ Vector2f zRange = m_Z.FindMinMaxIntegrated();
+ bounds.m_Min = Vector3f(xRange.x, yRange.x, zRange.x) * maxLifeTime;
+ bounds.m_Max = Vector3f(xRange.y, yRange.y, zRange.y) * maxLifeTime;
+ if(m_InWorldSpace)
+ {
+ Matrix4x4f matrix;
+ Matrix4x4f::Invert_General3D(localToWorld, matrix);
+ matrix.SetPosition(Vector3f::zero);
+ AABB aabb = bounds;
+ TransformAABBSlow(aabb, matrix, aabb);
+ bounds = aabb;
+ }
+}
+
+template<class TransferFunction>
+void VelocityModule::Transfer (TransferFunction& transfer)
+{
+ ParticleSystemModule::Transfer (transfer);
+ transfer.Transfer (m_X, "x");
+ transfer.Transfer (m_Y, "y");
+ transfer.Transfer (m_Z, "z");
+ transfer.Transfer (m_InWorldSpace, "inWorldSpace"); transfer.Align();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(VelocityModule)
diff --git a/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.h b/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.h
new file mode 100644
index 0000000..bf76f28
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/Modules/VelocityModule.h
@@ -0,0 +1,36 @@
+#ifndef SHURIKENMODULEVELOCITY_H
+#define SHURIKENMODULEVELOCITY_H
+
+#include "ParticleSystemModule.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h"
+#include "Runtime/Math/Random/rand.h"
+
+class VelocityModule : public ParticleSystemModule
+{
+public:
+ DECLARE_MODULE (VelocityModule)
+ VelocityModule ();
+
+ void Update (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex);
+ void UpdateProcedural (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps);
+ void CalculateProceduralBounds(MinMaxAABB& bounds, const Matrix4x4f& localToWorld, float maxLifeTime);
+ void CheckConsistency() {};
+
+ inline MinMaxCurve& GetXCurve() { return m_X; };
+ inline MinMaxCurve& GetYCurve() { return m_Y; };
+ inline MinMaxCurve& GetZCurve() { return m_Z; };
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+private:
+ MinMaxCurve m_X;
+ MinMaxCurve m_Y;
+ MinMaxCurve m_Z;
+ bool m_InWorldSpace;
+
+ Rand m_Random;
+};
+
+
+#endif // SHURIKENMODULEVELOCITY_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.cpp b/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.cpp
new file mode 100644
index 0000000..dfefc6a
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.cpp
@@ -0,0 +1,122 @@
+#include "UnityPrefix.h"
+#include "ParticleCollisionEvents.h"
+
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Threads/Thread.h"
+
+CollisionEvents::CollisionEvents (): currentCollisionEventThreadArray(0)
+{
+}
+
+void CollisionEvents::Clear ()
+{
+ collisionEvents[0].clear ();
+ collisionEvents[1].clear ();
+}
+
+bool CollisionEvents::AddEvent (const ParticleCollisionEvent& event)
+{
+ GetCollisionEventThreadArray ().push_back (event);
+ return true;
+}
+
+int CollisionEvents::GetCollisionEventCount () const
+{
+ return GetCollisionEventScriptArray ().size ();
+}
+
+void CollisionEvents::SwapCollisionEventArrays ()
+{
+ Assert (Thread::CurrentThreadIsMainThread ());
+ currentCollisionEventThreadArray = (currentCollisionEventThreadArray+1)%2;
+ collisionEvents[currentCollisionEventThreadArray].clear ();
+}
+
+dynamic_array<ParticleCollisionEvent>& CollisionEvents::GetCollisionEventThreadArray ()
+{
+ return collisionEvents[currentCollisionEventThreadArray];
+}
+
+const dynamic_array<ParticleCollisionEvent>& CollisionEvents::GetCollisionEventScriptArray () const
+{
+ const int currentCollisionEventScriptArray = (currentCollisionEventThreadArray+1)%2;
+ return collisionEvents[currentCollisionEventScriptArray];
+}
+
+struct SortCollisionEventsByGameObject {
+ bool operator()( const ParticleCollisionEvent& ra, const ParticleCollisionEvent& rb ) const;
+};
+
+bool SortCollisionEventsByGameObject::operator()( const ParticleCollisionEvent& ra, const ParticleCollisionEvent& rb ) const
+{
+ return ra.m_RigidBodyOrColliderInstanceID < rb.m_RigidBodyOrColliderInstanceID;
+}
+
+void CollisionEvents::SortCollisionEventThreadArray ()
+{
+ std::sort (GetCollisionEventThreadArray ().begin (), GetCollisionEventThreadArray ().end (), SortCollisionEventsByGameObject ());
+}
+
+static GameObject* GetGameObjectFromInstanceID (int instanceId)
+{
+ Object* temp = Object::IDToPointer (instanceId);
+ if ( temp )
+ {
+ return (reinterpret_cast<Unity::Component*> (temp))->GetGameObjectPtr ();
+ }
+ return NULL;
+}
+
+static int GetGameObjectIDFromInstanceID (int instanceId)
+{
+ Object* temp = Object::IDToPointer (instanceId);
+ if ( temp )
+ {
+ return (reinterpret_cast<Unity::Component*> (temp))->GetGameObjectInstanceID ();
+ }
+ return 0;
+}
+
+void CollisionEvents::SendCollisionEvents (Unity::Component& particleSystem) const
+{
+ const dynamic_array<ParticleCollisionEvent>& scriptEventArray = GetCollisionEventScriptArray ();
+ GameObject* pParticleSystem = &particleSystem.GetGameObject ();
+ GameObject* collideeGO = NULL;
+ int currentId = -1;
+ for (int e = 0; e < scriptEventArray.size (); ++e)
+ {
+ if (currentId != scriptEventArray[e].m_RigidBodyOrColliderInstanceID)
+ {
+ collideeGO = GetGameObjectFromInstanceID (scriptEventArray[e].m_RigidBodyOrColliderInstanceID);
+ if (!collideeGO)
+ continue;
+ currentId = scriptEventArray[e].m_RigidBodyOrColliderInstanceID;
+ pParticleSystem->SendMessage (kParticleCollisionEvent, collideeGO, ClassID (GameObject)); // send message to particle system
+ collideeGO->SendMessage (kParticleCollisionEvent, pParticleSystem, ClassID (GameObject)); // send message to object collided with
+ }
+ }
+}
+
+int CollisionEvents::GetCollisionEvents (int instanceId, MonoParticleCollisionEvent* collisionEvents, int size) const
+{
+ const dynamic_array<ParticleCollisionEvent>& scriptEventArray = GetCollisionEventScriptArray ();
+ dynamic_array<ParticleCollisionEvent>::const_iterator iter = scriptEventArray.begin ();
+ for (; iter != scriptEventArray.end (); ++iter)
+ {
+ if (instanceId == GetGameObjectIDFromInstanceID (iter->m_RigidBodyOrColliderInstanceID))
+ {
+ int count = 0;
+ while (iter != scriptEventArray.end () && GetGameObjectIDFromInstanceID (iter->m_RigidBodyOrColliderInstanceID) == instanceId && count < size)
+ {
+ collisionEvents[count].m_Intersection = iter->m_Intersection;
+ collisionEvents[count].m_Normal = iter->m_Normal;
+ collisionEvents[count].m_Velocity = iter->m_Velocity;
+ collisionEvents[count].m_ColliderInstanceID = iter->m_ColliderInstanceID;
+ count++;
+ iter++;
+ }
+ return count;
+ }
+ }
+ return 0;
+}
diff --git a/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h b/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h
new file mode 100644
index 0000000..98260ee
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Mono/MonoScript.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+
+struct ParticleCollisionEvent
+{
+ ParticleCollisionEvent (const Vector3f& intersection, const Vector3f& normal, const Vector3f& velocity, int colliderInstanceID, int rigidBodyOrColliderInstanceID);
+ Vector3f m_Intersection;
+ Vector3f m_Normal;
+ Vector3f m_Velocity;
+ int m_ColliderInstanceID;
+ int m_RigidBodyOrColliderInstanceID; // This can be a Collider or a RigidBody component
+};
+
+struct MonoParticleCollisionEvent
+{
+ Vector3f m_Intersection;
+ Vector3f m_Normal;
+ Vector3f m_Velocity;
+ int m_ColliderInstanceID;
+};
+
+struct CollisionEvents
+{
+ CollisionEvents ();
+ dynamic_array<ParticleCollisionEvent> collisionEvents[2];
+ int currentCollisionEventThreadArray;
+
+ void Clear ();
+ bool AddEvent (const ParticleCollisionEvent& event);
+ int GetCollisionEventCount () const;
+ void SwapCollisionEventArrays ();
+ void SortCollisionEventThreadArray ();
+ void SendCollisionEvents (Unity::Component& particleSystem) const;
+ int GetCollisionEvents (int instanceId, MonoParticleCollisionEvent* collisionEvents, int size) const;
+ dynamic_array<ParticleCollisionEvent>& GetCollisionEventThreadArray ();
+ const dynamic_array<ParticleCollisionEvent>& GetCollisionEventScriptArray () const;
+};
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
+
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystem.h b/Runtime/Graphics/ParticleSystem/ParticleSystem.h
new file mode 100644
index 0000000..10c8f2a
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystem.h
@@ -0,0 +1,298 @@
+#ifndef SHURIKEN_H
+#define SHURIKEN_H
+
+#include "Runtime/BaseClasses/GameObject.h"
+#include "ParticleSystemParticle.h"
+#include "Runtime/Utilities/LinkedList.h"
+#include "Modules/InitialModule.h"
+#include "Modules/ShapeModule.h"
+#include "Modules/EmissionModule.h"
+
+struct ParticleSystemEmitReplay;
+struct ParticleSystemState;
+class ParticleSystemModule;
+class SizeModule;
+class RotationModule;
+class ColorModule;
+class UVModule;
+class VelocityModule;
+class ForceModule;
+class ExternalForcesModule;
+class ClampVelocityModule;
+class SizeBySpeedModule;
+class RotationBySpeedModule;
+class ColorBySpeedModule;
+class CollisionModule;
+class SubModule;
+
+enum ParticleSystemSimulationSpace {
+ kSimLocal = 0,
+ kSimWorld = 1,
+};
+
+// TODO: rename
+struct ParticleSystemThreadScratchPad
+{
+ ParticleSystemThreadScratchPad ()
+ : deltaTime (1.0f)
+ {}
+
+ float deltaTime;
+};
+
+enum { kGoodQualityDelay = 0, kMediumQualityDelay = 0, kLowQualityDelay = 4 };
+struct RayBudgetState
+{
+ void SetQuality(int quality)
+ {
+ if ( m_Quality != quality )
+ {
+ switch (quality)
+ {
+ case 0:
+ m_QualityFrameDelay = kGoodQualityDelay;
+ break;
+ case 1:
+ m_QualityFrameDelay = kMediumQualityDelay;
+ break;
+ case 2:
+ m_QualityFrameDelay = kLowQualityDelay;
+ break;
+ default:
+ m_QualityFrameDelay = kGoodQualityDelay;
+ }
+ m_FramesRemaining = m_QualityFrameDelay;
+ m_Quality = quality;
+ };
+ }
+ RayBudgetState() { m_Quality=m_QualityFrameDelay=m_FramesRemaining=0; }
+ bool ReceiveRays() const { return m_FramesRemaining==0; }
+ void Update() { m_FramesRemaining = ( m_FramesRemaining ? m_FramesRemaining-1 : m_QualityFrameDelay ); }
+ int m_Quality;
+ int m_QualityFrameDelay;
+ int m_FramesRemaining;
+};
+
+class ParticleSystem : public Unity::Component
+{
+public:
+ REGISTER_DERIVED_CLASS (ParticleSystem, Unity::Component)
+ DECLARE_OBJECT_SERIALIZE (ParticleSystem)
+
+ enum
+ {
+ kParticleBuffer0,
+#if UNITY_EDITOR // Double buffered + interpolation
+ kParticleBuffer1,
+#endif
+ kNumParticleBuffers,
+ };
+
+ ParticleSystem (MemLabelId label, ObjectCreationMode mode);
+
+ // ParticleSystem (); declared-by-macro
+
+ void SmartReset ();
+ void AddParticleSystemRenderer ();
+
+ void Deactivate (DeactivateOperation operation);
+ void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ #if UNITY_EDITOR
+ // ParticleSystemRenderer always goes with ParticleSystem
+ virtual int GetCoupledComponentClassID() const { return ClassID(ParticleSystemRenderer); }
+ #endif
+
+
+ static void* UpdateFunction (void* rawData);
+
+ static void BeginUpdateAll();
+ static void EndUpdateAll();
+ static void Update(ParticleSystem& system, float deltaTime, bool fixedTimeStep, bool useProcedural, int rayBudget = 0);
+
+ static bool SystemWannaRayCast(const ParticleSystem& system);
+ static bool SystemWillRayCast(const ParticleSystem& system);
+ static void AssignRayBudgets();
+
+ static void SyncJobs();
+
+ static void Emit(ParticleSystem& system, const SubEmitterEmitCommand& command, ParticleSystemEmitMode emitMode);
+
+ // Client interface
+ void Simulate (float t, bool restart); // Fastforwards the particle system by simulating particles over given period of time, then pauses it.
+ void Play (bool autoPrewarm = true);
+ void Stop ();
+ void Pause ();
+ void Emit (int count);
+ void EmitParticleExternal(ParticleSystemParticle* particle);
+ void Clear (bool updateBounds = true);
+ void AutoPrewarm();
+
+ bool IsAlive () const;
+ bool IsPlaying () const;
+ bool IsPaused () const;
+ bool IsStopped () const;
+ float GetStartDelay() const;
+ void SetStartDelay(float value);
+ bool GetLoop () const;
+ void SetLoop (bool loop);
+ bool GetPlayOnAwake () const;
+ void SetPlayOnAwake (bool playOnAwake);
+ ParticleSystemSimulationSpace GetSimulationSpace () const;
+ void SetSimulationSpace (ParticleSystemSimulationSpace simulationSpace);
+ float GetSecPosition () const;
+ void SetSecPosition (float pos);
+ float GetLengthInSec () const;
+ void SetPlaybackSpeed (float speed);
+ float GetPlaybackSpeed () const;
+ void SetRayBudget (int rayBudget);
+ int GetRayBudget() const;
+
+ bool GetEnableEmission() const;
+ void SetEnableEmission(bool value);
+ float GetEmissionRate() const;
+ void SetEmissionRate(float value);
+ float GetStartSpeed() const;
+ void SetStartSpeed(float value);
+ float GetStartSize() const;
+ void SetStartSize(float value);
+ ColorRGBAf GetStartColor() const;
+ void SetStartColor(ColorRGBAf value);
+ float GetStartRotation() const;
+ void SetStartRotation(float value);
+ float GetStartLifeTime() const;
+ void SetStartLifeTime(float value);
+ float GetGravityModifier() const;
+ void SetGravityModifier(float value);
+ UInt32 GetRandomSeed() const;
+ void SetRandomSeed(UInt32 value);
+ int GetMaxNumParticles() const;
+ void SetMaxNumParticles(int value);
+
+ ShapeModule& GetShapeModule () { return m_ShapeModule; }
+
+
+ Matrix4x4f GetLocalToWorldMatrix() const;
+
+ void GetNumTiles(int& uvTilesX, int& uvTilesY) const;
+
+ void AllocateAllStructuresOfArrays();
+ void SetParticlesExternal (ParticleSystemParticle* particles, int size);
+ void GetParticlesExternal (ParticleSystemParticle* particles, int size);
+
+ int GetSafeCollisionEventSize () const;
+ int GetCollisionEventsExternal (int instanceID, MonoParticleCollisionEvent* collisionEvents, int size) const;
+
+ ParticleSystemParticles& GetParticles (int index = -1);
+ const ParticleSystemParticles& GetParticles (int index = -1) const;
+ size_t GetParticleCount () const;
+
+ ParticleSystemThreadScratchPad& GetThreadScratchPad () { return m_ThreadScratchpad; }
+
+ static void InitializeClass ();
+ static void CleanupClass () {};
+
+ void DidModifyMesh ();
+ void DidDeleteMesh ();
+
+#if UNITY_EDITOR
+ void TransformChanged();
+#endif
+
+ void RendererBecameVisible();
+ void RendererBecameInvisible();
+
+ static size_t EmitFromData (ParticleSystemEmissionState& emissionState, size_t& numContinuous, const ParticleSystemEmissionData& emissionData, const Vector3f velocity, float fromT, float toT, float dt, float length);
+private:
+ static void Update0 (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, float dt, bool fixedTimeStep);
+ static void Update1 (ParticleSystem& system, ParticleSystemParticles& ps, float dt, bool fixedTimeStep, bool useProcedural, int rayBudget = 0);
+ static void Update2 (ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, bool fixedTimeStep);
+ static void Update1Incremental(ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt, bool useProcedural);
+
+ static size_t EmitFromModules (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemEmissionState& emissionState, size_t& numContinuous, const Vector3f velocity, float fromT, float toT, float dt);
+ static void 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);
+ static void StartParticles(ParticleSystem& system, ParticleSystemParticles& ps, const float prevT, const float t, const float dt, const size_t numContinuous, size_t amountOfParticlesToEmit, float frameOffset);
+ static void StartParticlesProcedural(ParticleSystem& system, ParticleSystemParticles& ps, const float prevT, const float t, const float dt, const size_t numContinuous, size_t amountOfParticlesToEmit, float frameOffset);
+ static void UpdateProcedural(ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps);
+ static void UpdateModulesPreSimulationIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, const size_t toIndex, float dt);
+ static void UpdateModulesPostSimulationIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, float dt);
+ static void UpdateModulesIncremental (const ParticleSystem& system, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t fromIndex, float dt);
+ static void UpdateModulesNonIncremental (const ParticleSystem& system, const ParticleSystemParticles& ps, ParticleSystemParticlesTempData& psTemp, size_t fromIndex, size_t toIndex);
+ static void SimulateParticles (const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, const size_t fromIndex, float dt);
+ static void PlaybackSubEmitterCommandBuffer(const ParticleSystem& shuriken, ParticleSystemState& state, bool fixedTimeStep);
+ static void UpdateBounds(const ParticleSystem& system, const ParticleSystemParticles& ps, ParticleSystemState& state);
+
+ static void AddStagingBuffer(ParticleSystem& system);
+ static int CalculateMaximumSubEmitterEmitCount(ParticleSystem& shuriken, ParticleSystemState& state, float deltaTime, bool fixedTimeStep);
+ static int SetupSubEmitters(ParticleSystem& shuriken, ParticleSystemState& state);
+
+ static bool CheckSupportsProcedural(const ParticleSystem& system);
+ static bool DetermineSupportsProcedural(const ParticleSystem& system);
+
+ bool ComputePrewarmStartParameters(float& prewarmTime, float t);
+
+ void SetUsesAxisOfRotation();
+ void SetUsesEmitAccumulator(int numAccumulators);
+ void SetUsesRotationalSpeed();
+ void KeepUpdating();
+ void Cull();
+
+ size_t AddNewParticles(ParticleSystemParticles& particles, size_t newParticles) const;
+ size_t LimitParticleCount(size_t requestSize) const;
+ float CalculateSubEmitterMaximumLifeTime(float parentLifeTime) const;
+
+ bool GetIsDistanceEmitter() const;
+
+ void AddToManager();
+ void RemoveFromManager();
+
+ ParticleSystemParticles m_Particles[kNumParticleBuffers];
+ ParticleSystemParticles m_ParticlesStaging; // staging buffer for emitting into the emitter
+ ParticleSystemReadOnlyState* m_ReadOnlyState;
+ ParticleSystemState* m_State;
+ InitialModule m_InitialModule;
+ ShapeModule m_ShapeModule;
+ EmissionModule m_EmissionModule;
+
+ // Dependent on energy value
+ SizeModule* m_SizeModule;
+ RotationModule* m_RotationModule; // @TODO: Requires outputs angular velocity and thus requires integration (Inconsistent with other modules in this group)
+ ColorModule* m_ColorModule;
+ UVModule* m_UVModule;
+
+ // Dependent on energy value
+ VelocityModule* m_VelocityModule;
+ ForceModule* m_ForceModule;
+ ExternalForcesModule* m_ExternalForcesModule;
+
+ // Depends on velocity and modifies velocity
+ ClampVelocityModule* m_ClampVelocityModule;
+
+ // Dependent on velocity value
+ SizeBySpeedModule* m_SizeBySpeedModule;
+ RotationBySpeedModule* m_RotationBySpeedModule;
+ ColorBySpeedModule* m_ColorBySpeedModule;
+
+ // Dependent on a position and velocity
+ CollisionModule* m_CollisionModule;
+
+ SubModule* m_SubModule;
+
+ ParticleSystemThreadScratchPad m_ThreadScratchpad;
+
+ RayBudgetState m_RayBudgetState;
+ int m_RayBudget;
+
+private:
+ int m_EmittersIndex;
+
+#if UNITY_EDITOR
+public:
+ int m_EditorRandomSeedIndex;
+ ListNode<ParticleSystem> m_EditorListNode;
+ friend class ParticleSystemEditor;
+#endif
+ friend class ParticleSystemRenderer;
+};
+
+#endif // SHURIKEN_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemCommon.h b/Runtime/Graphics/ParticleSystem/ParticleSystemCommon.h
new file mode 100644
index 0000000..fd59987
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemCommon.h
@@ -0,0 +1,48 @@
+#ifndef PARTICLESYSTEMCOMMON_H
+#define PARTICLESYSTEMCOMMON_H
+
+enum ParticleSystemSubType
+{
+ kParticleSystemSubTypeBirth,
+ kParticleSystemSubTypeCollision,
+ kParticleSystemSubTypeDeath,
+};
+
+enum ParticleSystemEmitMode
+{
+ kParticleSystemEMDirect,
+ kParticleSystemEMStaging,
+};
+
+enum
+{
+ kParticleSystemMaxSubBirth = 2,
+ kParticleSystemMaxSubCollision = 2,
+ kParticleSystemMaxSubDeath = 2,
+ kParticleSystemMaxSubTotal = kParticleSystemMaxSubBirth + kParticleSystemMaxSubCollision + kParticleSystemMaxSubDeath,
+};
+
+// Curve id's needed to offset randomness for curves, to avoid visible patterns due to only storing 1 random value per particle
+enum ParticleSystemRandomnessIds
+{
+ // Curves
+ kParticleSystemClampVelocityCurveId = 0x13371337,
+ kParticleSystemForceCurveId = 0x12460f3b,
+ kParticleSystemRotationCurveId = 0x6aed452e,
+ kParticleSystemRotationBySpeedCurveId = 0xdec4aea1,
+ kParticleSystemStartSpeedCurveId = 0x96aa4de3,
+ kParticleSystemSizeCurveId = 0x8d2c8431,
+ kParticleSystemSizeBySpeedCurveId = 0xf3857f6f,
+ kParticleSystemVelocityCurveId = 0xe0fbd834,
+ kParticleSystemUVCurveId = 0x13740583,
+
+ // Gradient
+ kParticleSystemColorGradientId = 0x591bc05c,
+ kParticleSystemColorByVelocityGradientId = 0x40eb95e4,
+
+ // Misc
+ kParticleSystemMeshSelectionId = 0xbc524e5f,
+ kParticleSystemUVRowSelectionId = 0xaf502044,
+};
+
+#endif // PARTICLESYSTEMCOMMON_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.cpp
new file mode 100644
index 0000000..3ac445c
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.cpp
@@ -0,0 +1,196 @@
+#include "UnityPrefix.h"
+#include "ParticleSystemCurves.h"
+#include "Runtime/Math/Polynomials.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+// Calculates the min max range of an animation curve analytically
+static void CalculateCurveRangesValue(Vector2f& minMaxValue, const AnimationCurve& curve)
+{
+ const int keyCount = curve.GetKeyCount();
+ if (keyCount == 0)
+ return;
+
+ if (keyCount == 1)
+ {
+ CalculateMinMax(minMaxValue, curve.GetKey(0).value);
+ }
+ else
+ {
+ int segmentCount = keyCount - 1;
+ CalculateMinMax(minMaxValue, curve.GetKey(0).value);
+ for (int i = 0;i<segmentCount;i++)
+ {
+ DebugAssert(i+1 < keyCount);
+ AnimationCurve::Cache cache;
+ curve.CalculateCacheData(cache, i, i + 1, 0.0F);
+
+ // Differentiate polynomial
+ float a = 3.0f * cache.coeff[0];
+ float b = 2.0f * cache.coeff[1];
+ float c = 1.0f * cache.coeff[2];
+
+ const float start = curve.GetKey(i).time;
+ const float end = curve.GetKey(i+1).time;
+
+ float roots[2];
+ int numRoots = QuadraticPolynomialRootsGeneric(a, b, c, roots[0], roots[1]);
+ for(int r = 0; r < numRoots; r++)
+ if((roots[r] >= 0.0f) && ((roots[r] + start) < end))
+ CalculateMinMax(minMaxValue, Polynomial::EvalSegment(roots[r], cache.coeff));
+ CalculateMinMax(minMaxValue, Polynomial::EvalSegment(end-start, cache.coeff));
+ }
+ }
+}
+
+bool BuildCurves (MinMaxOptimizedPolyCurves& polyCurves, const MinMaxAnimationCurves& editorCurves, float scalar, short minMaxState)
+{
+ bool isOptimizedCurve = polyCurves.max.BuildOptimizedCurve(editorCurves.max, scalar);
+ if ((minMaxState != kMMCTwoCurves) && (minMaxState != kMMCTwoConstants))
+ isOptimizedCurve = isOptimizedCurve && polyCurves.min.BuildOptimizedCurve(editorCurves.max, scalar);
+ else
+ isOptimizedCurve = isOptimizedCurve && polyCurves.min.BuildOptimizedCurve(editorCurves.min, scalar);
+ return isOptimizedCurve;
+}
+
+void BuildCurves (MinMaxPolyCurves& polyCurves, const MinMaxAnimationCurves& editorCurves, float scalar, short minMaxState)
+{
+ polyCurves.max.BuildCurve(editorCurves.max, scalar);
+ if ((minMaxState != kMMCTwoCurves) && (minMaxState != kMMCTwoConstants))
+ polyCurves.min.BuildCurve(editorCurves.max, scalar);
+ else
+ polyCurves.min.BuildCurve(editorCurves.min, scalar);
+}
+
+bool CurvesSupportProcedural (const MinMaxAnimationCurves& editorCurves, short minMaxState)
+{
+ bool isValid = PolynomialCurve::IsValidCurve(editorCurves.max);
+ if ((minMaxState != kMMCTwoCurves) && (minMaxState != kMMCTwoConstants))
+ return isValid;
+ else
+ return isValid && PolynomialCurve::IsValidCurve(editorCurves.min);
+}
+
+
+void MinMaxOptimizedPolyCurves::Integrate ()
+{
+ max.Integrate ();
+ min.Integrate ();
+}
+
+void MinMaxOptimizedPolyCurves::DoubleIntegrate ()
+{
+ max.DoubleIntegrate ();
+ min.DoubleIntegrate ();
+}
+
+Vector2f MinMaxOptimizedPolyCurves::FindMinMaxIntegrated()
+{
+ Vector2f minRange = min.FindMinMaxIntegrated();
+ Vector2f maxRange = max.FindMinMaxIntegrated();
+ Vector2f result = Vector2f(std::min(minRange.x, maxRange.x), std::max(minRange.y, maxRange.y));
+ return result;
+}
+
+Vector2f MinMaxOptimizedPolyCurves::FindMinMaxDoubleIntegrated()
+{
+ Vector2f minRange = min.FindMinMaxDoubleIntegrated();
+ Vector2f maxRange = max.FindMinMaxDoubleIntegrated();
+ Vector2f result = Vector2f(std::min(minRange.x, maxRange.x), std::max(minRange.y, maxRange.y));
+ return result;
+}
+
+void MinMaxPolyCurves::Integrate ()
+{
+ max.Integrate ();
+ min.Integrate ();
+}
+
+void MinMaxPolyCurves::DoubleIntegrate ()
+{
+ max.DoubleIntegrate ();
+ min.DoubleIntegrate ();
+}
+
+Vector2f MinMaxPolyCurves::FindMinMaxIntegrated()
+{
+ Vector2f minRange = min.FindMinMaxIntegrated();
+ Vector2f maxRange = max.FindMinMaxIntegrated();
+ Vector2f result = Vector2f(std::min(minRange.x, maxRange.x), std::max(minRange.y, maxRange.y));
+ return result;
+}
+
+Vector2f MinMaxPolyCurves::FindMinMaxDoubleIntegrated()
+{
+ Vector2f minRange = min.FindMinMaxDoubleIntegrated();
+ Vector2f maxRange = max.FindMinMaxDoubleIntegrated();
+ Vector2f result = Vector2f(std::min(minRange.x, maxRange.x), std::max(minRange.y, maxRange.y));
+ return result;
+}
+
+MinMaxCurve::MinMaxCurve ()
+: scalar (1.0f)
+, minMaxState (kMMCScalar)
+, isOptimizedCurve(false)
+{
+ SetPolynomialCurveToValue (editorCurves.max, polyCurves.max, 1.0f);
+ SetPolynomialCurveToValue (editorCurves.min, polyCurves.min, 0.0f);
+}
+
+Vector2f MinMaxCurve::FindMinMax() const
+{
+ Vector2f result = Vector2f(std::numeric_limits<float>::infinity (), -std::numeric_limits<float>::infinity ());
+ CalculateCurveRangesValue(result, editorCurves.max);
+ if((minMaxState == kMMCTwoCurves) || (minMaxState == kMMCTwoConstants))
+ CalculateCurveRangesValue(result, editorCurves.min);
+ return result * GetScalar();
+}
+
+Vector2f MinMaxCurve::FindMinMaxIntegrated() const
+{
+ if(IsOptimized())
+ {
+ MinMaxOptimizedPolyCurves integrated = polyCurves;
+ integrated.Integrate();
+ return integrated.FindMinMaxIntegrated();
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (editorCurves, minMaxState));
+ MinMaxPolyCurves integrated;
+ BuildCurves(integrated, editorCurves, GetScalar(), minMaxState);
+ integrated.Integrate();
+ return integrated.FindMinMaxIntegrated();
+ }
+}
+
+Vector2f MinMaxCurve::FindMinMaxDoubleIntegrated() const
+{
+ if(IsOptimized())
+ {
+ MinMaxOptimizedPolyCurves doubleIntegrated = polyCurves;
+ doubleIntegrated.DoubleIntegrate();
+ return doubleIntegrated.FindMinMaxDoubleIntegrated();
+ }
+ else
+ {
+ DebugAssert(CurvesSupportProcedural (editorCurves, minMaxState));
+ MinMaxPolyCurves doubleIntegrated;
+ BuildCurves(doubleIntegrated, editorCurves, GetScalar(), minMaxState);
+ doubleIntegrated.DoubleIntegrate();
+ return doubleIntegrated.FindMinMaxDoubleIntegrated();
+ }
+}
+
+template<class TransferFunction>
+void MinMaxCurve::Transfer (TransferFunction& transfer)
+{
+ transfer.Transfer (scalar, "scalar");
+ transfer.Transfer (editorCurves.max, "maxCurve");
+ transfer.Transfer (editorCurves.min, "minCurve");
+ TRANSFER (minMaxState); transfer.Align ();
+ if (transfer.IsReading ())
+ isOptimizedCurve = BuildCurves(polyCurves, editorCurves, scalar, minMaxState);
+}
+INSTANTIATE_TEMPLATE_TRANSFER(MinMaxCurve)
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h b/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h
new file mode 100644
index 0000000..5d59eb2
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemCurves.h
@@ -0,0 +1,187 @@
+#ifndef SHURIKENCURVES_H
+#define SHURIKENCURVES_H
+
+#include "ParticleSystemParticle.h"
+#include "PolynomialCurve.h"
+#include "Runtime/Math/AnimationCurve.h"
+
+
+struct MinMaxCurve;
+
+// Some profile numbers from a run with 250,000 particles evaluating 3 velocity properties each on Intel i7-2600 CPU @ 3.4 GHz
+// Scalar: 4.6 ms
+// Optimized curve: 7.2 ms
+// Random between 2 scalars: 9.5 ms
+// Random between 2 curves: 9.5 ms
+// Non-optimized curve: 10.0 ms
+// Random between 2 non-optimized curves: 12.0 ms
+
+enum ParticleSystemCurveEvalMode
+{
+ kEMScalar,
+ kEMOptimized,
+ kEMOptimizedMinMax,
+ kEMSlow,
+};
+
+enum MinMaxCurveState
+{
+ kMMCScalar = 0,
+ kMMCCurve = 1,
+ kMMCTwoCurves = 2,
+ kMMCTwoConstants = 3
+};
+
+struct MinMaxOptimizedPolyCurves
+{
+ void Integrate();
+ void DoubleIntegrate();
+ Vector2f FindMinMaxIntegrated();
+ Vector2f FindMinMaxDoubleIntegrated();
+
+ OptimizedPolynomialCurve max;
+ OptimizedPolynomialCurve min;
+};
+
+inline float EvaluateIntegrated (const MinMaxOptimizedPolyCurves& curves, float t, float factor)
+{
+ const float v0 = curves.min.EvaluateIntegrated (t);
+ const float v1 = curves.max.EvaluateIntegrated (t);
+ return Lerp (v0, v1, factor);
+}
+
+inline float EvaluateDoubleIntegrated (const MinMaxOptimizedPolyCurves& curves, float t, float factor)
+{
+ const float v0 = curves.min.EvaluateDoubleIntegrated (t);
+ const float v1 = curves.max.EvaluateDoubleIntegrated (t);
+ return Lerp (v0, v1, factor);
+}
+
+struct MinMaxPolyCurves
+{
+ void Integrate();
+ void DoubleIntegrate();
+ Vector2f FindMinMaxIntegrated();
+ Vector2f FindMinMaxDoubleIntegrated();
+
+ PolynomialCurve max;
+ PolynomialCurve min;
+};
+
+inline float EvaluateIntegrated (const MinMaxPolyCurves& curves, float t, float factor)
+{
+ const float v0 = curves.min.EvaluateIntegrated (t);
+ const float v1 = curves.max.EvaluateIntegrated (t);
+ return Lerp (v0, v1, factor);
+}
+
+inline float EvaluateDoubleIntegrated (const MinMaxPolyCurves& curves, float t, float factor)
+{
+ const float v0 = curves.min.EvaluateDoubleIntegrated (t);
+ const float v1 = curves.max.EvaluateDoubleIntegrated (t);
+ return Lerp (v0, v1, factor);
+}
+
+struct MinMaxAnimationCurves
+{
+ bool SupportsProcedural ();
+
+ AnimationCurve max;
+ AnimationCurve min;
+};
+
+bool BuildCurves (MinMaxOptimizedPolyCurves& polyCurves, const MinMaxAnimationCurves& editorCurves, float scalar, short minMaxState);
+void BuildCurves (MinMaxPolyCurves& polyCurves, const MinMaxAnimationCurves& editorCurves, float scalar, short minMaxState);
+bool CurvesSupportProcedural (const MinMaxAnimationCurves& editorCurves, short minMaxState);
+
+struct MinMaxCurve
+{
+ MinMaxOptimizedPolyCurves polyCurves;
+private:
+ float scalar; // Since scalar is baked into the optimized curve we use the setter function to modify it.
+
+public:
+ short minMaxState; // see enum MinMaxCurveState
+ bool isOptimizedCurve;
+
+ MinMaxAnimationCurves editorCurves;
+
+ MinMaxCurve ();
+
+ inline float GetScalar() const { return scalar; }
+ inline void SetScalar(float value) { scalar = value; BuildCurves(polyCurves, editorCurves, scalar, minMaxState); }
+
+ bool IsOptimized () const { return isOptimizedCurve; }
+ bool UsesMinMax () const { return (minMaxState == kMMCTwoCurves) || (minMaxState == kMMCTwoConstants); }
+
+ DEFINE_GET_TYPESTRING (MinMaxCurve)
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+
+ Vector2f FindMinMax() const;
+ Vector2f FindMinMaxIntegrated() const;
+ Vector2f FindMinMaxDoubleIntegrated() const;
+};
+
+inline float EvaluateSlow (const MinMaxCurve& curve, float t, float factor)
+{
+ const float v = curve.editorCurves.max.Evaluate(t) * curve.GetScalar ();
+ if (curve.minMaxState == kMMCTwoCurves)
+ return Lerp (curve.editorCurves.min.Evaluate(t) * curve.GetScalar (), v, factor);
+ else
+ return v;
+}
+
+template<ParticleSystemCurveEvalMode mode>
+inline float Evaluate (const MinMaxCurve& curve, float t, float factor = 1.0F)
+{
+ if(mode == kEMScalar)
+ {
+ return curve.GetScalar();
+ }
+ if(mode == kEMOptimized)
+ {
+ DebugAssert(curve.isOptimizedCurve);
+ return curve.polyCurves.max.Evaluate (t);
+ }
+ else if (mode == kEMOptimizedMinMax)
+ {
+ DebugAssert(curve.isOptimizedCurve);
+ const float v0 = curve.polyCurves.min.Evaluate (t);
+ const float v1 = curve.polyCurves.max.Evaluate (t);
+ return Lerp (v0, v1, factor);
+ }
+ else if (mode == kEMSlow)
+ {
+ return EvaluateSlow (curve, t, factor);
+ }
+}
+
+inline float Evaluate (const MinMaxCurve& curve, float t, float randomValue = 1.0F)
+{
+ if (curve.minMaxState == kMMCScalar)
+ return curve.GetScalar ();
+
+ if (curve.minMaxState == kMMCTwoConstants)
+ return Lerp ( curve.editorCurves.min.GetKey(0).value * curve.GetScalar (),
+ curve.editorCurves.max.GetKey(0).value * curve.GetScalar (), randomValue);
+
+ DebugAssert(t <= 1.0F && t >= 0.0F);
+ if (curve.isOptimizedCurve)
+ return Evaluate<kEMOptimizedMinMax> (curve, t, randomValue);
+ else
+ return Evaluate<kEMSlow> (curve, t, randomValue);
+}
+
+struct DualMinMax3DPolyCurves
+{
+ MinMaxOptimizedPolyCurves optX;
+ MinMaxOptimizedPolyCurves optY;
+ MinMaxOptimizedPolyCurves optZ;
+ MinMaxPolyCurves x;
+ MinMaxPolyCurves y;
+ MinMaxPolyCurves z;
+};
+
+#endif // SHURIKENCURVES_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.cpp
new file mode 100644
index 0000000..efe95c0
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.cpp
@@ -0,0 +1,27 @@
+#include "UnityPrefix.h"
+#include "ParticleSystemGradients.h"
+#include "Runtime/BaseClasses/ObjectDefines.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+
+MinMaxGradient::MinMaxGradient()
+: minMaxState (kMMGColor), minColor (255,255,255,255), maxColor (255,255,255,255)
+{
+}
+
+void MinMaxGradient::InitializeOptimized(OptimizedMinMaxGradient& g)
+{
+ maxGradient.InitializeOptimized(g.max);
+ if(minMaxState == kMMGRandomBetweenTwoGradients)
+ minGradient.InitializeOptimized(g.min);
+}
+
+template<class TransferFunction>
+void MinMaxGradient::Transfer (TransferFunction& transfer)
+{
+ TRANSFER (maxGradient);
+ TRANSFER (minGradient);
+ TRANSFER (minColor);
+ TRANSFER (maxColor);
+ TRANSFER (minMaxState); transfer.Align ();
+}
+INSTANTIATE_TEMPLATE_TRANSFER(MinMaxGradient)
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h b/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h
new file mode 100644
index 0000000..bb74cde
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemGradients.h
@@ -0,0 +1,87 @@
+#ifndef SHURIKENGRADIENTS_H
+#define SHURIKENGRADIENTS_H
+
+#include "Runtime/Math/Gradient.h"
+
+enum MinMaxGradientEvalMode
+{
+ kGEMGradient,
+ kGEMGradientMinMax,
+ kGEMSlow,
+};
+
+enum MinMaxGradientState
+{
+ kMMGColor = 0,
+ kMMGGradient = 1,
+ kMMGRandomBetweenTwoColors = 2,
+ kMMGRandomBetweenTwoGradients = 3
+};
+
+struct OptimizedMinMaxGradient
+{
+ OptimizedGradient max;
+ OptimizedGradient min;
+};
+
+inline ColorRGBA32 EvaluateGradient (const OptimizedMinMaxGradient& g, float t)
+{
+ return g.max.Evaluate(t);
+}
+
+inline ColorRGBA32 EvaluateRandomGradient (const OptimizedMinMaxGradient& g, float t, UInt32 factor)
+{
+ return Lerp (g.min.Evaluate(t), g.max.Evaluate(t), factor);
+}
+
+struct MinMaxGradient
+{
+ GradientNEW maxGradient;
+ GradientNEW minGradient;
+ ColorRGBA32 minColor; // we have the colors separate to prevent destroying the gradients
+ ColorRGBA32 maxColor;
+ short minMaxState; // see enum State
+
+ MinMaxGradient();
+
+ DEFINE_GET_TYPESTRING (MinMaxGradient)
+
+ void InitializeOptimized(OptimizedMinMaxGradient& g);
+
+ template<class TransferFunction>
+ void Transfer (TransferFunction& transfer);
+};
+
+inline ColorRGBA32 EvaluateColor (const MinMaxGradient& gradient)
+{
+ return gradient.maxColor;
+}
+
+inline ColorRGBA32 EvaluateGradient (const MinMaxGradient& gradient, float t)
+{
+ return gradient.maxGradient.Evaluate(t);
+}
+
+inline ColorRGBA32 EvaluateRandomColor (const MinMaxGradient& gradient, UInt32 factor)
+{
+ return Lerp (gradient.minColor, gradient.maxColor, factor);
+}
+
+inline ColorRGBA32 EvaluateRandomGradient (const MinMaxGradient& gradient, float t, UInt32 factor)
+{
+ return Lerp (gradient.minGradient.Evaluate(t), gradient.maxGradient.Evaluate(t), factor);
+}
+
+inline ColorRGBA32 Evaluate (const MinMaxGradient& gradient, float t, UInt32 factor = 0xff)
+{
+ if (gradient.minMaxState == kMMGColor)
+ return EvaluateColor(gradient);
+ else if (gradient.minMaxState == kMMGGradient)
+ return EvaluateGradient(gradient, t);
+ else if (gradient.minMaxState == kMMGRandomBetweenTwoColors)
+ return EvaluateRandomColor(gradient, factor);
+ else // gradient.minMaxState == kMMGRandomBetweenTwoGradients
+ return EvaluateRandomGradient(gradient, t, factor);
+}
+
+#endif // SHURIKENGRADIENTS_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.cpp
new file mode 100644
index 0000000..d10bee0
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.cpp
@@ -0,0 +1,323 @@
+#include "UnityPrefix.h"
+#include "ParticleSystemParticle.h"
+
+#include "Runtime/Dynamics/Collider.h"
+#include "Runtime/Scripting/ScriptingUtility.h"
+#include "Runtime/Threads/Thread.h"
+
+void InitializeEmitAccumulator(dynamic_array<float>& emitAccumulator, const size_t count)
+{
+ emitAccumulator.resize_uninitialized(count);
+ memset(emitAccumulator.begin(), 0, count * sizeof(float));
+}
+
+ParticleCollisionEvent::ParticleCollisionEvent (const Vector3f& intersection, const Vector3f& normal, const Vector3f& velocity, int colliderInstanceID, int rigidBodyOrColliderInstanceID)
+{
+ m_Intersection = intersection;
+ m_Normal = normal;
+ m_Velocity = velocity;
+ m_ColliderInstanceID = colliderInstanceID;
+ m_RigidBodyOrColliderInstanceID = rigidBodyOrColliderInstanceID;
+}
+
+size_t ParticleSystemParticles::GetParticleSize()
+{
+ // @TODO: How do we guard against elements we remove
+ return sizeof(ParticleSystemParticle);
+}
+
+void ParticleSystemParticles::SetUsesAxisOfRotation()
+{
+ usesAxisOfRotation = true;
+ const size_t count = position.size();
+ axisOfRotation.resize_uninitialized(count);
+ for(int i = 0; i < count; i++)
+ axisOfRotation[i] = Vector3f::yAxis;
+}
+
+void ParticleSystemParticles::SetUsesRotationalSpeed()
+{
+ usesRotationalSpeed = true;
+ const size_t count = position.size();
+ rotationalSpeed.resize_uninitialized(count);
+ for(int i = 0; i < count; i++)
+ rotationalSpeed[i] = 0.0f;
+}
+
+void ParticleSystemParticles::SetUsesCollisionEvents (bool wantUsesCollisionEvents)
+{
+ if (usesCollisionEvents == wantUsesCollisionEvents) return;
+ usesCollisionEvents = wantUsesCollisionEvents;
+ if (!usesCollisionEvents)
+ {
+ collisionEvents.Clear ();
+ }
+}
+
+bool ParticleSystemParticles::GetUsesCollisionEvents () const
+{
+ return usesCollisionEvents;
+}
+
+void ParticleSystemParticles::SetUsesEmitAccumulator(int numAccumulators)
+{
+ Assert(numAccumulators <= kParticleSystemMaxNumEmitAccumulators);
+ const size_t count = position.size();
+ for(int i = numEmitAccumulators; i < numAccumulators; i++)
+ InitializeEmitAccumulator(emitAccumulator[i], count);
+
+ numEmitAccumulators = numAccumulators;
+}
+
+void ParticleSystemParticles::CopyFromArrayAOS(ParticleSystemParticle* particles, int size)
+{
+ Assert(usesAxisOfRotation);
+ Assert(numEmitAccumulators == kParticleSystemMaxNumEmitAccumulators);
+ for(int i = 0; i < size; i++)
+ {
+ (*this).position[i] = particles[i].position;
+ (*this).velocity[i] = particles[i].velocity;
+ (*this).animatedVelocity[i] = particles[i].animatedVelocity;
+ (*this).axisOfRotation[i] = particles[i].axisOfRotation;
+ (*this).rotation[i] = particles[i].rotation;
+ if(usesRotationalSpeed)
+ (*this).rotationalSpeed[i] = particles[i].rotationalSpeed;
+ (*this).size[i] = particles[i].size;
+ (*this).color[i] = particles[i].color;
+ (*this).randomSeed[i] = particles[i].randomSeed;
+ (*this).lifetime[i] = particles[i].lifetime;
+ (*this).startLifetime[i] = particles[i].startLifetime;
+ for(int acc = 0; acc < kParticleSystemMaxNumEmitAccumulators; acc++)
+ (*this).emitAccumulator[acc][i] = particles[i].emitAccumulator[acc];
+ }
+}
+
+void ParticleSystemParticles::CopyToArrayAOS(ParticleSystemParticle* particles, int size)
+{
+ Assert(usesAxisOfRotation);
+ Assert(numEmitAccumulators == kParticleSystemMaxNumEmitAccumulators);
+ for(int i = 0; i < size; i++)
+ {
+ particles[i].position = (*this).position[i];
+ particles[i].velocity = (*this).velocity[i];
+ particles[i].animatedVelocity = (*this).animatedVelocity[i];
+ particles[i].axisOfRotation = (*this).axisOfRotation[i];
+ particles[i].rotation = (*this).rotation[i];
+ if(usesRotationalSpeed)
+ particles[i].rotationalSpeed = (*this).rotationalSpeed[i];
+ particles[i].size = (*this).size[i];
+ particles[i].color = (*this).color[i];
+ particles[i].randomSeed = (*this).randomSeed[i];
+ particles[i].lifetime = (*this).lifetime[i];
+ particles[i].startLifetime = (*this).startLifetime[i];
+ for(int acc = 0; acc < kParticleSystemMaxNumEmitAccumulators; acc++)
+ particles[i].emitAccumulator[acc] = (*this).emitAccumulator[acc][i];
+ }
+}
+
+void ParticleSystemParticles::AddParticle(ParticleSystemParticle* particle)
+{
+ const size_t count = array_size();
+ array_resize(count+1);
+ position[count] = particle->position;
+ velocity[count] = particle->velocity;
+ animatedVelocity[count] = Vector3f::zero;
+ lifetime[count] = particle->lifetime;
+ startLifetime[count] = particle->startLifetime;
+ size[count] = particle->size;
+ rotation[count] = particle->rotation;
+ if(usesRotationalSpeed)
+ rotationalSpeed[count] = particle->rotationalSpeed;
+ color[count] = particle->color;
+ randomSeed[count] = particle->randomSeed;
+ if(usesAxisOfRotation)
+ axisOfRotation[count] = particle->axisOfRotation;
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ emitAccumulator[acc][count] = 0.0f;
+}
+
+size_t ParticleSystemParticles::array_size () const
+{
+ return position.size();
+}
+
+void ParticleSystemParticles::array_resize (size_t i)
+{
+ position.resize_uninitialized(i);
+ velocity.resize_uninitialized(i);
+ animatedVelocity.resize_uninitialized(i);
+ rotation.resize_uninitialized(i);
+ if(usesRotationalSpeed)
+ rotationalSpeed.resize_uninitialized(i);
+ size.resize_uninitialized(i);
+ color.resize_uninitialized(i);
+ randomSeed.resize_uninitialized(i);
+ lifetime.resize_uninitialized(i);
+ startLifetime.resize_uninitialized(i);
+ if(usesAxisOfRotation)
+ axisOfRotation.resize_uninitialized(i);
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ emitAccumulator[acc].resize_uninitialized(i);
+}
+
+void ParticleSystemParticles::element_swap(size_t i, size_t j)
+{
+ std::swap(position[i], position[j]);
+ std::swap(velocity[i], velocity[j]);
+ std::swap(animatedVelocity[i], animatedVelocity[j]);
+ std::swap(rotation[i], rotation[j]);
+ if(usesRotationalSpeed)
+ std::swap(rotationalSpeed[i], rotationalSpeed[j]);
+ std::swap(size[i], size[j]);
+ std::swap(color[i], color[j]);
+ std::swap(randomSeed[i], randomSeed[j]);
+ std::swap(lifetime[i], lifetime[j]);
+ std::swap(startLifetime[i], startLifetime[j]);
+ if(usesAxisOfRotation)
+ std::swap(axisOfRotation[i], axisOfRotation[j]);
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ std::swap(emitAccumulator[acc][i], emitAccumulator[acc][j]);
+}
+
+void ParticleSystemParticles::element_assign(size_t i, size_t j)
+{
+ position[i] = position[j];
+ velocity[i] = velocity[j];
+ animatedVelocity[i] = animatedVelocity[j];
+ rotation[i] = rotation[j];
+ if(usesRotationalSpeed)
+ rotationalSpeed[i] = rotationalSpeed[j];
+ size[i] = size[j];
+ color[i] = color[j];
+ randomSeed[i] = randomSeed[j];
+ lifetime[i] = lifetime[j];
+ startLifetime[i] = startLifetime[j];
+ if(usesAxisOfRotation)
+ axisOfRotation[i] = axisOfRotation[j];
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ emitAccumulator[acc][i] = emitAccumulator[acc][j];
+}
+
+void ParticleSystemParticles::array_assign_external(void* data, const int numParticles)
+{
+#define SHURIKEN_INCREMENT_ASSIGN_PTRS(element, type) { beginPtr = endPtr; endPtr += numParticles * sizeof(type); (element).assign_external((type*)beginPtr, (type*)endPtr); }
+
+ UInt8* beginPtr = 0;
+ UInt8* endPtr = (UInt8*)data;
+
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(position, Vector3f);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(velocity, Vector3f);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(animatedVelocity, Vector3f);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(rotation, float);
+ if(usesRotationalSpeed)
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(rotationalSpeed, float);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(size, float);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(color, ColorRGBA32);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(randomSeed, UInt32);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(lifetime, float);
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(startLifetime, float);
+ if(usesAxisOfRotation)
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(axisOfRotation, Vector3f);
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ SHURIKEN_INCREMENT_ASSIGN_PTRS(emitAccumulator[acc], float);
+#undef SHURIKEN_INCREMENT_ASSIGN_PTRS
+}
+
+void ParticleSystemParticles::array_merge_preallocated(const ParticleSystemParticles& rhs, const int offset, const bool needAxisOfRotation, const bool needEmitAccumulator)
+{
+#define SHURIKEN_COPY_DATA(element, type) memcpy(&element[offset], &rhs.element[0], count * sizeof(type))
+
+ const size_t count = rhs.array_size();
+ if(0 == count)
+ return;
+
+ Assert((rhs.array_size() + offset) <= array_size());
+ SHURIKEN_COPY_DATA(position, Vector3f);
+ SHURIKEN_COPY_DATA(velocity, Vector3f);
+ SHURIKEN_COPY_DATA(animatedVelocity, Vector3f);
+ SHURIKEN_COPY_DATA(rotation, float);
+ if(usesRotationalSpeed)
+ SHURIKEN_COPY_DATA(rotationalSpeed, float);
+ SHURIKEN_COPY_DATA(size, float);
+ SHURIKEN_COPY_DATA(color, ColorRGBA32);
+ SHURIKEN_COPY_DATA(randomSeed, UInt32);
+ SHURIKEN_COPY_DATA(lifetime, float);
+ SHURIKEN_COPY_DATA(startLifetime, float);
+
+ if(needAxisOfRotation)
+ {
+ Assert(usesAxisOfRotation && rhs.usesAxisOfRotation);
+ SHURIKEN_COPY_DATA(axisOfRotation, Vector3f);
+ }
+ if(needEmitAccumulator)
+ {
+ Assert(numEmitAccumulators && rhs.numEmitAccumulators);
+ Assert(numEmitAccumulators == rhs.numEmitAccumulators);
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ SHURIKEN_COPY_DATA(emitAccumulator[acc], float);
+ }
+
+#undef SHURIKEN_COPY_DATA
+}
+
+
+void ParticleSystemParticles::array_assign(const ParticleSystemParticles& rhs)
+{
+ position.assign(rhs.position.begin(), rhs.position.end());
+ velocity.assign(rhs.velocity.begin(), rhs.velocity.end());
+ animatedVelocity.assign(rhs.animatedVelocity.begin(), rhs.animatedVelocity.end());
+ rotation.assign(rhs.rotation.begin(), rhs.rotation.end());
+ if(usesRotationalSpeed)
+ rotationalSpeed.assign(rhs.rotationalSpeed.begin(), rhs.rotationalSpeed.end());
+ size.assign(rhs.size.begin(), rhs.size.end());
+ color.assign(rhs.color.begin(), rhs.color.end());
+ randomSeed.assign(rhs.randomSeed.begin(), rhs.randomSeed.end());
+ lifetime.assign(rhs.lifetime.begin(), rhs.lifetime.end());
+ startLifetime.assign(rhs.startLifetime.begin(), rhs.startLifetime.end());
+ if(usesAxisOfRotation)
+ axisOfRotation.assign(rhs.axisOfRotation.begin(), rhs.axisOfRotation.end());
+ for(int acc = 0; acc < numEmitAccumulators; acc++)
+ emitAccumulator[acc].assign(rhs.emitAccumulator[acc].begin(), rhs.emitAccumulator[acc].end());
+}
+
+void ParticleSystemParticles::array_lerp(ParticleSystemParticles& output, const ParticleSystemParticles& a, const ParticleSystemParticles& b, float factor)
+{
+ DebugAssert(a.array_size() == b.array_size()); // else it doesn't really make sense
+ DebugAssert(a.usesRotationalSpeed == b.usesRotationalSpeed);
+
+ const int count = a.array_size();
+ output.array_resize(count);
+
+ // Note: Not all data is interpolated here intentionally, because it doesn't change anything or because it's incorrect
+
+ #define SHURIKEN_LERP_DATA(element, type) for(int i = 0; i < count; i++) output.element[i] = Lerp(a.element[i], b.element[i], factor)
+ SHURIKEN_LERP_DATA(position, Vector3f);
+ SHURIKEN_LERP_DATA(velocity, Vector3f);
+ SHURIKEN_LERP_DATA(animatedVelocity, Vector3f);
+
+ SHURIKEN_LERP_DATA(rotation, float);
+ if(a.usesRotationalSpeed)
+ SHURIKEN_LERP_DATA(rotationalSpeed, float);
+ SHURIKEN_LERP_DATA(lifetime, float);
+
+ #undef SHURIKEN_LERP_DATA
+}
+
+ParticleSystemParticlesTempData::ParticleSystemParticlesTempData()
+:color(0)
+,size(0)
+,sheetIndex(0)
+,particleCount(0)
+{}
+
+void ParticleSystemParticlesTempData::element_swap(size_t i, size_t j)
+{
+ DebugAssert(i <= particleCount);
+ DebugAssert(j <= particleCount);
+
+ std::swap(color[i], color[j]);
+ std::swap(size[i], size[j]);
+ if(sheetIndex)
+ std::swap(sheetIndex[i], sheetIndex[j]);
+}
+
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h b/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h
new file mode 100644
index 0000000..65d6894
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemParticle.h
@@ -0,0 +1,116 @@
+#ifndef SHURIKENPARTICLE_H
+#define SHURIKENPARTICLE_H
+
+#include "Runtime/Graphics/ParticleSystem/ParticleCollisionEvents.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Utilities/dynamic_array.h"
+
+class Collider;
+// Keep in sync with struct ParticleSystem.Particle
+enum{ kParticleSystemMaxNumEmitAccumulators = 2 };
+
+// TODO: Optimization:
+// . Store startLifetime as 1.0f/startLifeTime and store lifetime as 1.0f - lifetime.
+// This means that a lot of fdivs turn into muls and mads turns into nothing (NormalizedTime will become cheaper).
+// Remember: script must still convert into legacy format.
+
+// Keep in sync with struct ParticleSystem.Particle
+struct ParticleSystemParticle
+{
+ Vector3f position;
+ Vector3f velocity;
+ Vector3f animatedVelocity;
+ Vector3f axisOfRotation;
+ float rotation;
+ float rotationalSpeed;
+ float size;
+ ColorRGBA32 color;
+ UInt32 randomSeed;
+ float lifetime;
+ float startLifetime;
+ float emitAccumulator[kParticleSystemMaxNumEmitAccumulators];
+};
+
+typedef dynamic_array<Vector3f> ParticleSystemVector3Array;
+typedef dynamic_array<float> ParticleSystemFloatArray;
+typedef dynamic_array<ColorRGBA32> ParticleSystemColor32Array;
+typedef dynamic_array<UInt32> ParticleSystemUInt32Array;
+
+// Keep in sync with struct ParticleSystem.Particle
+struct ParticleSystemParticles
+{
+ ParticleSystemParticles()
+ :numEmitAccumulators(0)
+ ,usesAxisOfRotation(false)
+ ,usesRotationalSpeed(false)
+ ,usesCollisionEvents(false)
+ ,currentCollisionEventThreadArray(0)
+ {}
+
+ ParticleSystemVector3Array position;
+ ParticleSystemVector3Array velocity;
+ ParticleSystemVector3Array animatedVelocity; // Would actually only need this when modules with force and velocity curves are used
+ ParticleSystemVector3Array axisOfRotation;
+ ParticleSystemFloatArray rotation;
+ ParticleSystemFloatArray rotationalSpeed;
+ ParticleSystemFloatArray size;
+ ParticleSystemColor32Array color;
+ ParticleSystemUInt32Array randomSeed;
+ ParticleSystemFloatArray lifetime;
+ ParticleSystemFloatArray startLifetime;
+ ParticleSystemFloatArray emitAccumulator[kParticleSystemMaxNumEmitAccumulators]; // Usage: Only needed if particle system has time sub emitter
+ CollisionEvents collisionEvents;
+
+ bool usesAxisOfRotation;
+ bool usesRotationalSpeed;
+ bool usesCollisionEvents;
+ int currentCollisionEventThreadArray;
+ int numEmitAccumulators;
+
+ void AddParticle(ParticleSystemParticle* particle);
+
+ void SetUsesAxisOfRotation ();
+ void SetUsesRotationalSpeed();
+
+ void SetUsesCollisionEvents(bool usesCollisionEvents);
+ bool GetUsesCollisionEvents() const;
+ void SetUsesEmitAccumulator (int numAccumulators);
+
+ static size_t GetParticleSize();
+
+ size_t array_size () const;
+ void array_resize (size_t i);
+ void element_swap(size_t i, size_t j);
+ void element_assign(size_t i, size_t j);
+ void array_assign_external(void* data, const int numParticles);
+ void array_merge_preallocated(const ParticleSystemParticles& rhs, const int offset, const bool needAxisOfRotation, const bool needEmitAccumulator);
+ void array_assign(const ParticleSystemParticles& rhs);
+ static void array_lerp(ParticleSystemParticles& output, const ParticleSystemParticles& a, const ParticleSystemParticles& b, float factor);
+
+ void CopyFromArrayAOS(ParticleSystemParticle* particles, int size);
+ void CopyToArrayAOS(ParticleSystemParticle* particles, int size);
+};
+
+struct ParticleSystemParticlesTempData
+{
+ ParticleSystemParticlesTempData();
+ void element_swap(size_t i, size_t j);
+
+ ColorRGBA32* color;
+ float* size;
+ float* sheetIndex;
+ size_t particleCount;
+};
+
+inline float NormalizedTime (const ParticleSystemParticles& ps, size_t i)
+{
+ return (ps.startLifetime[i] - ps.lifetime[i]) / ps.startLifetime[i];
+}
+
+inline float NormalizedTime (float wholeTime, float currentTime)
+{
+ return (wholeTime - currentTime) / wholeTime;
+}
+
+#endif // SHURIKENPARTICLE_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.cpp
new file mode 100644
index 0000000..7b14e62
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.cpp
@@ -0,0 +1,1241 @@
+#include "UnityPrefix.h"
+#include "ParticleSystem.h"
+#include "ParticleSystemParticle.h"
+#include "ParticleSystemRenderer.h"
+#include "ParticleSystemUtils.h"
+#include "Modules/SubModule.h"
+#include "Modules/UVModule.h"
+#include "Runtime/Camera/Camera.h"
+#include "Runtime/Camera/RenderManager.h"
+#include "Runtime/Camera/Renderqueue.h"
+#include "Runtime/Shaders/VBO.h"
+#include "Runtime/GfxDevice/GfxDevice.h"
+#include "Runtime/Graphics/DrawUtil.h"
+#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h"
+#include "Runtime/Profiler/Profiler.h"
+#include "Runtime/Profiler/ExternalGraphicsProfiler.h"
+#include "Runtime/Threads/JobScheduler.h"
+#include "Runtime/Filters/Misc/LineBuilder.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/GfxDevice/ChannelAssigns.h"
+#include "Runtime/Graphics/TriStripper.h"
+#include "Runtime/Misc/BuildSettings.h"
+#include "Runtime/BaseClasses/GameObject.h"
+
+IMPLEMENT_CLASS_INIT_ONLY (ParticleSystemRenderer)
+IMPLEMENT_OBJECT_SERIALIZE (ParticleSystemRenderer)
+
+PROFILER_INFORMATION(gParticlesSort, "ParticleSystem.Sort", kProfilerParticles)
+PROFILER_INFORMATION(gParticlesSingleProfile, "ParticleSystem.RenderSingle", kProfilerParticles)
+PROFILER_INFORMATION(gParticlesBatchProfile, "ParticleSystem.RenderBatch", kProfilerParticles)
+PROFILER_INFORMATION(gSubmitVBOParticleProfile, "Mesh.SubmitVBO", kProfilerRender)
+
+#define DEBUG_PARTICLE_SORTING (0)
+#if UNITY_WII
+#define kMaxNumParticlesPerBatch (65536/6)
+#else
+#define kMaxNumParticlesPerBatch (min<int>(kDynamicBatchingIndicesThreshold/6, VBO::kMaxQuads))
+#endif
+
+struct ParticleSystemVertex
+{
+ Vector3f vert;
+ Vector3f normal;
+ ColorRGBA32 color;
+ Vector2f uv;
+ Vector4f tangent; // Here, we put 2nd uv + blend factor
+};
+
+struct ParticleSystemGeomConstInputData
+{
+ Matrix4x4f m_ViewMatrix;
+ Vector3f m_CameraVelocity;
+ Object* m_Renderer;
+ UInt16 const* m_MeshIndexBuffer[ParticleSystemRendererData::kMaxNumParticleMeshes];
+ int m_MeshIndexCount[ParticleSystemRendererData::kMaxNumParticleMeshes];
+ int m_NumTilesX;
+ int m_NumTilesY;
+ float maxPlaneScale;
+ float maxOrthoSize;
+ float numUVFrame;
+ float animUScale;
+ float animVScale;
+ Vector3f xSpan;
+ Vector3f ySpan;
+ bool usesSheetIndex;
+ float bentNormalFactor;
+ Vector3f bentNormalVector;
+};
+
+inline void ScaleMatrix(Matrix4x4f& matrix, float scale)
+{
+ matrix.m_Data[0] *= scale;
+ matrix.m_Data[1] *= scale;
+ matrix.m_Data[2] *= scale;
+ matrix.m_Data[4] *= scale;
+ matrix.m_Data[5] *= scale;
+ matrix.m_Data[6] *= scale;
+ matrix.m_Data[8] *= scale;
+ matrix.m_Data[9] *= scale;
+ matrix.m_Data[10] *= scale;
+}
+
+struct ParticleSort
+{
+ inline static void SetValues(ParticleSort& sort, UInt32 inIndex, int inIntValue)
+ {
+ sort.index = inIndex;
+ sort.intValue = inIntValue;
+ }
+
+ inline static bool CompareValue (const ParticleSort& left, const ParticleSort& right)
+ {
+ return (left.intValue < right.intValue);
+ }
+
+ inline static void Swap(ParticleSort* oneOfThem, ParticleSort* theOtherOne)
+ {
+ ParticleSort temp = *oneOfThem;
+ *oneOfThem = *theOtherOne;
+ *theOtherOne = temp;
+ }
+
+ UInt32 index;
+ int intValue;
+};
+
+void GenerateSortIndices(ParticleSort* indices, const Vector3f& distFactor, const ParticleSystemParticles& ps, ParticleSystemSortMode sortMode)
+{
+ const size_t particleCount = ps.array_size();
+ if(IS_CONTENT_NEWER_OR_SAME (kUnityVersion4_0_a1))
+ {
+ if(sortMode == kSSMByDistance)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)(Dot (distFactor, ps.position[i]) * 40000.0f));
+ else if(sortMode == kSSMOldestFirst)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)((ps.startLifetime[i]- ps.lifetime[i]) * -40000.0f));
+ else if(sortMode == kSSMYoungestFirst)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)((ps.startLifetime[i]- ps.lifetime[i]) * 40000.0f));
+ }
+ else
+ {
+ // 3.5 used lifetime - this is pretty broken if you have random lifetimes, as you get random sorting
+ if(sortMode == kSSMByDistance)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)(Dot (distFactor, ps.position[i]) * 40000.0f));
+ else if(sortMode == kSSMOldestFirst)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)(ps.lifetime[i] * 40000.0f));
+ else if(sortMode == kSSMYoungestFirst)
+ for(int i = 0; i < particleCount; i++)
+ ParticleSort::SetValues(indices[i], i, (int)(ps.lifetime[i] * -40000.0f));
+ }
+}
+
+template<bool sortTempData>
+void ApplySortRemap(ParticleSort* particleSortIndexBuffer, ParticleSystemParticlesTempData* tempData, ParticleSystemParticles& ps)
+{
+ const size_t count = ps.array_size();
+ for(int i = 0; i < count; i++)
+ {
+ int dst = particleSortIndexBuffer[i].intValue;
+ while(i != dst)
+ {
+ ParticleSort::Swap(&particleSortIndexBuffer[i], &particleSortIndexBuffer[dst]);
+ ps.element_swap(i, dst);
+ if(sortTempData)
+ tempData->element_swap(i, dst);
+
+ dst = particleSortIndexBuffer[i].intValue;
+ }
+ }
+}
+
+void Sort (const Matrix4x4f& matrix, ParticleSystemParticles& ps, ParticleSystemSortMode mode, ParticleSystemParticlesTempData* tempData, bool sortTempData)
+{
+ PROFILER_AUTO_GFX(gParticlesSort, 0);
+
+ DebugAssert(mode != kSSMNone);
+
+ const Vector3f distFactor = Vector3f (matrix.Get (2, 0), matrix.Get (2, 1), + matrix.Get (2, 2));
+ const size_t count = ps.array_size();
+
+ ParticleSort* particleSortIndexBuffer;
+ ALLOC_TEMP(particleSortIndexBuffer, ParticleSort, count);
+ GenerateSortIndices(&particleSortIndexBuffer[0], distFactor, ps, mode);
+
+ // Sort
+ std::sort(&particleSortIndexBuffer[0], &particleSortIndexBuffer[0] + count, ParticleSort::CompareValue);
+
+ // Create inverse mapping
+ for(int i = 0; i < count; i++)
+ particleSortIndexBuffer[particleSortIndexBuffer[i].index].intValue = i;
+
+ if(sortTempData)
+ ApplySortRemap<true>(particleSortIndexBuffer, tempData, ps);
+ else
+ ApplySortRemap<false>(particleSortIndexBuffer, tempData, ps);
+}
+
+struct ParticleMeshData
+{
+ int vertexCount;
+ StrideIterator<Vector3f> positions;
+ StrideIterator<Vector3f> normals;
+ StrideIterator<Vector4f> tangents;
+ StrideIterator<ColorRGBA32> colors;
+ StrideIterator<Vector2f> texCoords;
+ int indexCount;
+ const UInt16* indexBuffer;
+};
+
+template<bool hasNormals, bool hasTangents>
+void TransformParticleMesh(const ParticleMeshData& src, ColorRGBA32 particleColor,
+ const Matrix4x4f& xform, const Matrix4x4f& xformNoScale, UInt8** dest)
+{
+ for(int vertex = 0; vertex < src.vertexCount; vertex++)
+ {
+ // Vertex format is position, color, uv, and optional normals and tangents
+ xform.MultiplyPoint3(src.positions[vertex], *reinterpret_cast<Vector3f*>(*dest));
+ *dest += sizeof(Vector3f);
+ if (hasNormals)
+ {
+ xformNoScale.MultiplyVector3(src.normals[vertex], *reinterpret_cast<Vector3f*>(*dest));
+ *dest += sizeof(Vector3f);
+ }
+ *reinterpret_cast<ColorRGBA32*>(*dest) = particleColor * src.colors[vertex];
+ *dest += sizeof(ColorRGBA32);
+ *reinterpret_cast<Vector2f*>(*dest) = src.texCoords[vertex];
+ *dest += sizeof(Vector2f);
+ // Tangent is last in vertex format
+ if (hasTangents)
+ {
+ Vector3f newTangent = xformNoScale.MultiplyVector3((const Vector3f&)src.tangents[vertex]);
+ *reinterpret_cast<Vector4f*>(*dest) = Vector4f(newTangent, src.tangents[vertex].w);
+ *dest += sizeof(Vector4f);
+ }
+ }
+}
+
+
+ParticleSystemRenderer::ParticleSystemRenderer (MemLabelId label, ObjectCreationMode mode)
+: Super(kRendererParticleSystem, label, mode)
+, m_LocalSpaceAABB (Vector3f::zero, Vector3f::zero)
+{
+ SetVisible (false);
+
+ for (int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; ++i)
+ m_Data.cachedMeshUserNode[i].SetData (this);
+
+#if UNITY_EDITOR
+ m_EditorEnabled = true;
+#endif
+}
+
+ParticleSystemRenderer::~ParticleSystemRenderer ()
+{
+}
+
+void ParticleSystemRenderer::InitializeClass ()
+{
+ REGISTER_MESSAGE_PTR (ParticleSystemRenderer, kDidDeleteMesh, OnDidDeleteMesh, Mesh);
+}
+
+void ParticleSystemRenderer::AwakeFromLoad (AwakeFromLoadMode awakeMode)
+{
+ Super::AwakeFromLoad (awakeMode);
+ UpdateCachedMesh ();
+}
+
+void ParticleSystemRenderer::UpdateCachedMesh ()
+{
+ int dst = 0;
+ for(int src = 0; src < ParticleSystemRendererData::kMaxNumParticleMeshes; src++)
+ {
+ m_Data.cachedMesh[src] = NULL;
+ m_Data.cachedMeshUserNode[src].RemoveFromList ();
+
+ Mesh* mesh = m_Mesh[src];
+ if (mesh)
+ {
+ if (mesh->GetSubMeshCount() == 1)
+ {
+ m_Data.cachedMesh[dst] = mesh;
+ const SubMesh& sm = mesh->GetSubMeshFast(0);
+ const UInt16* buffer = mesh->GetSubMeshBuffer16(0);
+
+ if (sm.topology == kPrimitiveTriangleStripDeprecated)
+ {
+ const int capacity = CountTrianglesInStrip(buffer, sm.indexCount) * 3;
+ m_CachedIndexBuffer[dst].resize_uninitialized(capacity);
+ Destripify(buffer, sm.indexCount, m_CachedIndexBuffer[dst].begin(), capacity);
+ }
+ else if (sm.topology == kPrimitiveTriangles)
+ {
+ const int capacity = sm.indexCount;
+ m_CachedIndexBuffer[dst].resize_uninitialized(capacity);
+ memcpy(m_CachedIndexBuffer[dst].begin(), buffer, capacity*kVBOIndexSize);
+ }
+ else
+ {
+ m_CachedIndexBuffer[dst].resize_uninitialized(0);
+ }
+
+ // Hook into mesh's user notifications.
+ mesh->AddObjectUser (m_Data.cachedMeshUserNode[dst]);
+
+ dst++;
+ }
+ else
+ {
+ m_Data.cachedMesh[src] = NULL;
+ m_CachedIndexBuffer[src].resize_uninitialized(0);
+ AssertString ("Particle system meshes will only work with exactly one (1) sub mesh");
+ }
+ }
+ }
+}
+
+void ParticleSystemRenderer::OnDidDeleteMesh (Mesh* mesh)
+{
+ // Clear out cached pointer to mesh.
+ for (int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; ++i)
+ {
+ if (m_Data.cachedMesh[i] != mesh)
+ continue;
+
+ m_Data.cachedMesh[i] = NULL;
+ m_Data.cachedMeshUserNode[i].RemoveFromList ();
+ }
+}
+
+void ParticleSystemRenderer::GetLocalAABB (AABB& result)
+{
+ result = m_LocalSpaceAABB;
+}
+
+void ParticleSystemRenderer::GetWorldAABB (AABB& result)
+{
+ TransformAABB (m_LocalSpaceAABB, GetTransform ().GetPosition (), GetTransform ().GetRotation (), result);
+}
+
+float ParticleSystemRenderer::GetSortingFudge () const
+{
+ return m_Data.sortingFudge;
+}
+
+void ParticleSystemRenderer::CheckConsistency ()
+{
+ Super::CheckConsistency ();
+ m_Data.maxParticleSize = std::max (0.0F, m_Data.maxParticleSize);
+ m_Data.normalDirection = clamp<float>(m_Data.normalDirection, 0.0f, 1.0f);
+}
+
+void ParticleSystemRenderer::Reset ()
+{
+ Super::Reset ();
+ m_Data.renderMode = kSRMBillboard;
+ m_Data.lengthScale = 2.0F;
+ m_Data.velocityScale = 0.0F;
+ m_Data.cameraVelocityScale = 0.0F;
+ m_Data.maxParticleSize = 0.5F;
+ m_Data.sortingFudge = 0.0F;
+ m_Data.sortMode = kSSMNone;
+ m_Data.normalDirection = 1.0f;
+
+ for(int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; i++)
+ m_Mesh[i] = NULL;
+ m_LocalSpaceAABB.SetCenterAndExtent (Vector3f::zero, Vector3f::zero);
+
+
+
+#if UNITY_EDITOR
+ m_EditorEnabled = true;
+#endif
+}
+
+void ParticleSystemRenderer::UpdateRenderer ()
+{
+ ParticleSystem* system = QueryComponent(ParticleSystem);
+ if (system)
+ {
+ SetVisible (true);
+ BoundsChanged();
+ }
+ else
+ {
+ UpdateManagerState (false);
+ }
+
+ Super::UpdateRenderer ();
+}
+
+void ParticleSystemRenderer::Update (const AABB& aabb)
+{
+ m_LocalSpaceAABB = aabb;
+ UpdateManagerState (true);
+}
+
+void ParticleSystemRenderer::RendererBecameVisible()
+{
+ Super::RendererBecameVisible();
+
+ ParticleSystem* system = QueryComponent(ParticleSystem);
+ if(system)
+ system->RendererBecameVisible();
+}
+
+void ParticleSystemRenderer::RendererBecameInvisible()
+{
+ Super::RendererBecameInvisible();
+
+ ParticleSystem* system = QueryComponent(ParticleSystem);
+ if(system)
+ system->RendererBecameInvisible();
+}
+
+void ParticleSystemRenderer::UpdateLocalAABB()
+{
+ AABB aabb;
+ GetLocalAABB(aabb);
+ m_TransformInfo.localAABB = aabb;
+}
+
+inline Rectf GetFrameUV (int index, int tilesX, float animUScale, float animVScale)
+{
+ int vIdx = index / tilesX;
+ int uIdx = index - vIdx * tilesX; // slightly faster than index % m_UVAnimation.xTile
+ float uOffset = (float)uIdx * animUScale;
+ float vOffset = 1.0f - animVScale - (float)vIdx * animVScale;
+
+ return Rectf(uOffset, vOffset, animUScale, animVScale);
+}
+
+template<ParticleSystemRenderMode renderMode>
+void GenerateParticleGeometry (ParticleSystemVertex* vbPtr,
+ const ParticleSystemGeomConstInputData& constData,
+ const ParticleSystemRendererData& rendererData,
+ const ParticleSystemParticles& ps,
+ const ParticleSystemParticlesTempData& psTemp,
+ size_t startIndex,
+ size_t endIndex,
+ const Matrix4x4f& worldViewMatrix,
+ const Matrix4x4f& viewToWorldMatrix)
+{
+ float maxPlaneScale = constData.maxPlaneScale;
+ float maxOrthoSize = constData.maxOrthoSize;
+ float numUVFrame = constData.numUVFrame;
+ Vector3f xSpan = constData.xSpan;
+ Vector3f ySpan = constData.ySpan;
+ Vector3f cameraVelocity = constData.m_CameraVelocity * rendererData.cameraVelocityScale;
+ int numTilesX = constData.m_NumTilesX;
+ float animUScale = constData.animUScale;
+ float animVScale = constData.animVScale;
+ bool usesSheetIndex = constData.usesSheetIndex;
+ float lengthScale = rendererData.lengthScale;
+ float velocityScale = rendererData.velocityScale;
+
+ float bentNormalFactor = constData.bentNormalFactor;
+ Vector3f bentNormalVector = constData.bentNormalVector;
+
+ Vector2f uv[4] = { Vector2f(0.0f, 1.0f),
+ Vector2f(1.0f, 1.0f),
+ Vector2f(1.0f, 0.0f),
+ Vector2f(0.0f, 0.0f)};
+ Vector4f uv2[4] = { Vector4f(0.0f, 1.0f, 0.0f, 0.0f),
+ Vector4f(1.0f, 1.0f, 0.0f, 0.0f),
+ Vector4f(1.0f, 0.0f, 0.0f, 0.0f),
+ Vector4f(0.0f, 0.0f, 0.0f, 0.0f)};
+
+ float invAnimVScale = 1.0f - animVScale;
+
+ for( int i = startIndex; i < endIndex; ++i )
+ {
+ Vector3f vert[4];
+ Vector3f n0, n1;
+
+ Vector3f position;
+ worldViewMatrix.MultiplyPoint3 (ps.position[i], position);
+
+ // Constrain the size to be a fraction of the viewport size.
+ // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1]
+ // Also all valid z's are negative so we just negate the whole equation
+ float maxWorldSpaceLength = position.z * maxPlaneScale + maxOrthoSize;
+ float hsize = std::min (psTemp.size[i], maxWorldSpaceLength) * 0.5f;
+ if (renderMode == kSRMBillboard)
+ {
+ float s = Sin (ps.rotation[i]);
+ float c = Cos (ps.rotation[i]);
+ n0 = Vector3f(-c+s, s+c, 0.0f);
+ n1 = Vector3f( c+s, -s+c, 0.0f);
+ vert[0] = position + n0 * hsize;
+ vert[1] = position + n1 * hsize;
+ vert[2] = position - n0 * hsize;
+ vert[3] = position - n1 * hsize;
+ }
+ else if (renderMode == kSRMBillboardFixedHorizontal || renderMode == kSRMBillboardFixedVertical)
+ {
+ float s = Sin (ps.rotation[i]+0.78539816339744830961566084581988f);
+ float c = Cos (ps.rotation[i]+0.78539816339744830961566084581988f);
+ n0 = xSpan*c + ySpan*s;
+ n1 = ySpan*c - xSpan*s;
+ vert[0] = position + n0 * hsize;
+ vert[1] = position + n1 * hsize;
+ vert[2] = position - n0 * hsize;
+ vert[3] = position - n1 * hsize;
+ }
+ else if (renderMode == kSRMStretch3D)
+ {
+ //RH BUG FOR LATER: Here we see the stretching bug as described by case no 434115...this is a Flash VM error, where a writeFloat (or readFloat) fails.
+ Vector3f velocity;
+ worldViewMatrix.MultiplyVector3(ps.velocity[i] + ps.animatedVelocity[i], velocity);
+ velocity -= cameraVelocity;
+ float sqrVelocity = SqrMagnitude (velocity);
+
+ Vector2f delta;
+ Vector3f endProj;
+ bool nonZeroVelocity = sqrVelocity > Vector3f::epsilon;
+ if (nonZeroVelocity)
+ {
+ endProj = position - velocity * (velocityScale + FastInvSqrt (sqrVelocity) * (lengthScale * psTemp.size[i]));
+ delta.x = position.z*endProj.y - position.y*endProj.z;
+ delta.y = position.x*endProj.z - position.z*endProj.x;
+ delta = NormalizeFast(delta);
+ }
+ else
+ {
+ endProj = position;
+ delta = Vector2f::xAxis;
+ }
+ n0 = n1 = Vector3f(delta.x, delta.y, 0.0f);
+ vert[0] = position + n0 * hsize;
+ vert[1] = endProj + n1 * hsize;
+ vert[2] = endProj - n0 * hsize;
+ vert[3] = position - n1 * hsize;
+ }
+
+ // UV animation
+ float sheetIndex;
+ if(usesSheetIndex)
+ {
+ // TODO: Pretty much the perfect candidate for SIMD
+
+ sheetIndex = psTemp.sheetIndex[i] * numUVFrame;
+ Assert (psTemp.sheetIndex[i] >= 0.0f && psTemp.sheetIndex[i] <= 1.0f);
+
+ const int index0 = FloorfToIntPos (sheetIndex);
+ const int index1 = index0 + 1;
+ Vector2f offset0, offset1;
+ const float blend = sheetIndex - (float)index0;
+
+ int vIdx = index0 / numTilesX;
+ int uIdx = index0 - vIdx * numTilesX;
+ offset0.x = (float)uIdx * animUScale;
+ offset0.y = invAnimVScale - (float)vIdx * animVScale;
+
+ vIdx = index1 / numTilesX;
+ uIdx = index1 - vIdx * numTilesX;
+ offset1.x = (float)uIdx * animUScale;
+ offset1.y = invAnimVScale - (float)vIdx * animVScale;
+
+ uv[0].Set(offset0.x, offset0.y + animVScale );
+ uv[1].Set(offset0.x + animUScale, offset0.y + animVScale );
+ uv[2].Set(offset0.x + animUScale, offset0.y );
+ uv[3].Set(offset0.x, offset0.y );
+
+ uv2[0].Set(offset1.x, offset1.y + animVScale, blend, 0.0f );
+ uv2[1].Set(offset1.x + animUScale, offset1.y + animVScale, blend, 0.0f );
+ uv2[2].Set(offset1.x + animUScale, offset1.y, blend, 0.0f );
+ uv2[3].Set(offset1.x, offset1.y, blend, 0.0f );
+ }
+
+ n0 = viewToWorldMatrix.MultiplyVector3(n0 * bentNormalFactor);
+ n1 = viewToWorldMatrix.MultiplyVector3(n1 * bentNormalFactor);
+
+ ColorRGBA32 color = psTemp.color[i];
+
+ vbPtr[0].vert = vert[0];
+ vbPtr[0].normal = bentNormalVector + n0;
+ vbPtr[0].color = color;
+ vbPtr[0].uv = uv[0];
+ vbPtr[0].tangent = uv2[0];
+
+ vbPtr[1].vert = vert[1];
+ vbPtr[1].normal = bentNormalVector + n1;
+ vbPtr[1].color = color;
+ vbPtr[1].uv = uv[1];
+ vbPtr[1].tangent = uv2[1];
+
+ vbPtr[2].vert = vert[2];
+ vbPtr[2].normal = bentNormalVector - n0;
+ vbPtr[2].color = color;
+ vbPtr[2].uv = uv[2];
+ vbPtr[2].tangent = uv2[2];
+
+ vbPtr[3].vert = vert[3];
+ vbPtr[3].normal = bentNormalVector - n1;
+ vbPtr[3].color = color;
+ vbPtr[3].uv = uv[3];
+ vbPtr[3].tangent = uv2[3];
+
+ // Next four vertices
+ vbPtr += 4;
+ }
+}
+
+static void DrawMeshParticles (const ParticleSystemGeomConstInputData& constInput, const ParticleSystemRendererData& rendererData, const Matrix4x4f& worldMatrix, const ParticleSystemParticles& ps, const ParticleSystemParticlesTempData& psTemp, const ChannelAssigns& channels)
+{
+ int numMeshes = 0;
+ ParticleMeshData particleMeshes[ParticleSystemRendererData::kMaxNumParticleMeshes];
+ Vector3f defaultNormal(0, 0, 0);
+ Vector4f defaultTangent(0, 0, 0, 0);
+ ColorRGBA32 defaultColor(255, 255, 255, 255);
+ Vector2f defaultTexCoords(0, 0);
+ for(int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; i++)
+ {
+ if(constInput.m_MeshIndexCount[i] == 0)
+ break;
+ const Mesh* mesh = rendererData.cachedMesh[i];
+ if(mesh == NULL || !mesh->HasVertexData())
+ break;
+ ParticleMeshData& dest = particleMeshes[i];
+ dest.vertexCount = mesh->GetVertexCount();
+ dest.positions = mesh->GetVertexBegin();
+ dest.normals = mesh->GetNormalBegin();
+ if (dest.normals.IsNull())
+ dest.normals = StrideIterator<Vector3f>(&defaultNormal, 0);
+ dest.tangents = mesh->GetTangentBegin();
+ if (dest.tangents.IsNull())
+ dest.tangents = StrideIterator<Vector4f>(&defaultTangent, 0);
+ dest.texCoords = mesh->GetUvBegin();
+ if (dest.texCoords.IsNull())
+ dest.texCoords = StrideIterator<Vector2f>(&defaultTexCoords, 0);
+ dest.colors = mesh->GetColorBegin();
+ if (dest.colors.IsNull())
+ dest.colors = StrideIterator<ColorRGBA32>(&defaultColor, 0);
+ dest.indexCount = constInput.m_MeshIndexCount[i];
+ dest.indexBuffer = constInput.m_MeshIndexBuffer[i];
+ numMeshes++;
+ }
+
+ if(0 == numMeshes)
+ return;
+
+ GfxDevice& device = GetGfxDevice();
+
+ Matrix4x4f viewMatrix;
+ CopyMatrix (device.GetViewMatrix (), viewMatrix.GetPtr ());
+
+ const size_t particleCount = ps.array_size ();
+
+ float probability = 1.0f / (float)numMeshes;
+
+ // @TODO: We should move all these platform dependent numbers into Gfx specific code and get it from there.
+ const int kMaxVertices = 65536;
+
+#if UNITY_WII
+ const int kMaxIndices = 65536;
+#else
+ const int kMaxIndices = kDynamicBatchingIndicesThreshold;
+#endif
+
+ int particleOffset = 0;
+ while (particleOffset < particleCount)
+ {
+ int numVertices = 0;
+ int numIndices = 0;
+ int particleCountBatch = 0;
+
+ // Figure out batch size
+ for(int i = particleOffset; i < particleCount; i++)
+ {
+ const float randomValue = GenerateRandom(ps.randomSeed[i] + kParticleSystemMeshSelectionId);
+ int lastNumVertices = 0;
+ int lastNumIndices = 0;
+ for(int j = 0; j < numMeshes; j++)
+ {
+ const float lower = probability * j;
+ const float upper = probability * (j + 1);
+ if((randomValue >= lower) && (randomValue <= upper))
+ {
+ lastNumVertices = particleMeshes[j].vertexCount;
+ lastNumIndices = particleMeshes[j].indexCount;
+ break;
+ }
+ }
+ if((numVertices >= kMaxVertices) || (numIndices >= kMaxIndices))
+ {
+ break;
+ }
+ else
+ {
+ numVertices += lastNumVertices;
+ numIndices += lastNumIndices;
+ particleCountBatch++;
+ }
+ }
+
+ const int vertexCount = numVertices;
+ const int indexCount = numIndices;
+
+ // Figure out if normals and tangents are needed by shader
+ UInt32 normalTangentMask = channels.GetSourceMap() & VERTEX_FORMAT2(Normal, Tangent);
+
+ // Tangents requires normals
+ if( normalTangentMask & VERTEX_FORMAT1(Tangent) )
+ normalTangentMask |= VERTEX_FORMAT1(Normal);
+
+ // Get VBO chunk
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ UInt8* vbPtr = NULL;
+ UInt16* ibPtr = NULL;
+ const UInt32 mandatoryChannels = VERTEX_FORMAT3(Vertex, Color, TexCoord0);
+ if( !vbo.GetChunk( mandatoryChannels | normalTangentMask,
+ vertexCount, indexCount,
+ DynamicVBO::kDrawIndexedTriangles,
+ (void**)&vbPtr, (void**)&ibPtr ) )
+ {
+ return;
+ }
+
+ int vertexOffset = 0;
+ int indexOffset = 0;
+ const int startIndex = particleOffset;
+ const int endIndex = particleOffset + particleCountBatch;
+ for( int i = startIndex; i < endIndex; ++i )
+ {
+ const Vector3f position = ps.position[i];
+ const float rotation = ps.rotation[i];
+ const float size = psTemp.size[i];
+ const Vector3f axisOfRotation = NormalizeSafe (ps.axisOfRotation[i], Vector3f::yAxis);
+ const ColorRGBA32 particleColor = psTemp.color[i];
+
+ // Only shared part is actually rotation. xformNoScale doesn't need a translation, so no need to copy that data
+ Matrix4x4f xformNoScale;
+ xformNoScale.SetTR (position, AxisAngleToQuaternion (axisOfRotation, rotation));
+
+ Matrix4x4f xform = xformNoScale;
+ ScaleMatrix(xform, size);
+
+ // Figure out which mesh to use
+ const float randomValue = GenerateRandom(ps.randomSeed[i] + kParticleSystemMeshSelectionId);
+ int meshIndex = 0;
+ for(int j = 0; j < numMeshes; j++)
+ {
+ const float lower = probability * j;
+ const float upper = probability * (j + 1);
+ if((randomValue >= lower) && (randomValue <= upper))
+ {
+ meshIndex = j;
+ break;
+ }
+ }
+
+ const ParticleMeshData& mesh = particleMeshes[meshIndex];
+
+ // Fill up vbo here
+ if( normalTangentMask == VERTEX_FORMAT2(Normal, Tangent) )
+ TransformParticleMesh<true, true>(mesh, particleColor, xform, xformNoScale, &vbPtr);
+ else if( normalTangentMask == VERTEX_FORMAT1(Normal) )
+ TransformParticleMesh<true, false>(mesh, particleColor, xform, xformNoScale, &vbPtr);
+ else if( normalTangentMask == 0 )
+ TransformParticleMesh<false, false>(mesh, particleColor, xform, xformNoScale, &vbPtr);
+ else
+ ErrorString("Invalid normalTangentMask");
+
+ const int meshIndexMax = mesh.indexCount - 2;
+ for(int index = 0; index < meshIndexMax; index+=3)
+ {
+ ibPtr[index+0] = mesh.indexBuffer[index+0] + vertexOffset;
+ ibPtr[index+1] = mesh.indexBuffer[index+1] + vertexOffset;
+ ibPtr[index+2] = mesh.indexBuffer[index+2] + vertexOffset;
+ }
+ ibPtr += mesh.indexCount;
+
+ vertexOffset += mesh.vertexCount;
+ indexOffset += mesh.indexCount;
+ }
+
+ vbo.ReleaseChunk (vertexCount, indexCount);
+ device.SetViewMatrix(viewMatrix.GetPtr());
+ device.SetWorldMatrix(worldMatrix.GetPtr());
+ vbo.DrawChunk (channels);
+ GPU_TIMESTAMP();
+
+ particleOffset += particleCountBatch;
+ }
+}
+
+static void DrawParticlesInternal(const ParticleSystemGeomConstInputData& constData, const ParticleSystemRendererData& rendererData, const Matrix4x4f& worldViewMatrix, const Matrix4x4f& viewToWorldMatrix, const ParticleSystemParticles& ps, const ParticleSystemParticlesTempData& psTemp, ParticleSystemVertex* vbPtr, const size_t particleOffset, const size_t numParticles, int renderMode)
+{
+ const size_t endIndex = particleOffset + numParticles;
+
+ if (renderMode == kSRMBillboard)
+ GenerateParticleGeometry<kSRMBillboard> (vbPtr, constData, rendererData, ps, psTemp, particleOffset, endIndex, worldViewMatrix, viewToWorldMatrix);
+ if (renderMode == kSRMStretch3D)
+ GenerateParticleGeometry<kSRMStretch3D> (vbPtr, constData, rendererData, ps, psTemp, particleOffset, endIndex, worldViewMatrix, viewToWorldMatrix);
+ if (renderMode == kSRMBillboardFixedHorizontal)
+ GenerateParticleGeometry<kSRMBillboardFixedHorizontal> (vbPtr, constData, rendererData, ps, psTemp, particleOffset, endIndex, worldViewMatrix, viewToWorldMatrix);
+ if (renderMode == kSRMBillboardFixedVertical)
+ GenerateParticleGeometry<kSRMBillboardFixedVertical> (vbPtr, constData, rendererData, ps, psTemp, particleOffset, endIndex, worldViewMatrix, viewToWorldMatrix);
+}
+
+static void DrawParticles(const ParticleSystemGeomConstInputData& constData, const ParticleSystemRendererData& rendererData, const Matrix4x4f& worldViewMatrix, const Matrix4x4f& viewToWorldMatrix, const ParticleSystemParticles& ps, const ParticleSystemParticlesTempData& psTemp, const ChannelAssigns& channels, ParticleSystemVertex* vbPtr)
+{
+ GfxDevice& device = GetGfxDevice();
+ const size_t particleCount = ps.array_size();
+
+ if(vbPtr)
+ {
+ DrawParticlesInternal(constData, rendererData, worldViewMatrix, viewToWorldMatrix, ps, psTemp, vbPtr, 0, particleCount, rendererData.renderMode);
+ }
+ else
+ {
+ int particleOffset = 0;
+ while (particleOffset < particleCount)
+ {
+ const int particleCountBatch = min(kMaxNumParticlesPerBatch, (int)particleCount - particleOffset);
+
+ // Get VBO chunk
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor) | (1<<kShaderChannelTangent),
+ particleCountBatch * 4, 0,
+ DynamicVBO::kDrawQuads,
+ (void**)&vbPtr, NULL ) )
+ {
+ continue;
+ }
+
+ DrawParticlesInternal(constData, rendererData, worldViewMatrix, viewToWorldMatrix, ps, psTemp, vbPtr, particleOffset, particleCountBatch, rendererData.renderMode);
+ particleOffset += particleCountBatch;
+
+ vbo.ReleaseChunk (particleCountBatch * 4, 0);
+
+ // Draw
+ device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity
+
+ PROFILER_BEGIN(gSubmitVBOParticleProfile, constData.m_Renderer)
+ vbo.DrawChunk (channels);
+ GPU_TIMESTAMP();
+ PROFILER_END
+
+ device.SetViewMatrix(constData.m_ViewMatrix.GetPtr ());
+ }
+ }
+}
+
+void ParticleSystemRenderer::CalculateTotalParticleCount(UInt32& totalNumberOfParticles, ParticleSystem& system, bool first)
+{
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(!renderer || first)
+ {
+ totalNumberOfParticles += system.GetParticleCount();
+
+ Transform* t = system.QueryComponent (Transform);
+ if (t == NULL)
+ return;
+ for (Transform::iterator i=t->begin ();i != t->end ();i++)
+ {
+ ParticleSystem* child = (**i).QueryComponent(ParticleSystem);
+ if (child)
+ CalculateTotalParticleCount(totalNumberOfParticles, *child, false);
+ }
+ }
+}
+
+void ParticleSystemRenderer::CombineParticleBuffersRec(int& offset, ParticleSystemParticles& ps, ParticleSystemParticlesTempData& psTemp, ParticleSystem& system, bool first, bool needsAxisOfRotation)
+{
+ ParticleSystemRenderer* renderer = system.QueryComponent(ParticleSystemRenderer);
+ if(!renderer || first)
+ {
+ int particleCount = system.GetParticleCount();
+ ps.array_merge_preallocated(system.GetParticles(), offset, needsAxisOfRotation, false);
+
+ if(system.m_ReadOnlyState->useLocalSpace)
+ {
+ Matrix4x4f localToWorld = system.GetComponent (Transform).GetLocalToWorldMatrixNoScale ();
+
+ int endIndex = offset + particleCount;
+ for(int i = offset; i < endIndex; i++)
+ ps.position[i] = localToWorld.MultiplyPoint3(ps.position[i]);
+ for(int i = offset; i < endIndex; i++)
+ ps.velocity[i] = localToWorld.MultiplyVector3(ps.velocity[i]);
+ for(int i = offset; i < endIndex; i++)
+ ps.animatedVelocity[i] = localToWorld.MultiplyVector3(ps.animatedVelocity[i]);
+ if(ps.usesAxisOfRotation)
+ for(int i = offset; i < endIndex; i++)
+ ps.axisOfRotation[i] = localToWorld.MultiplyVector3(ps.axisOfRotation[i]);
+ }
+
+ ParticleSystem::UpdateModulesNonIncremental(system, ps, psTemp, offset, offset + particleCount);
+ offset += particleCount;
+
+ Transform* t = system.QueryComponent (Transform);
+ if (t == NULL)
+ return;
+ for (Transform::iterator i=t->begin ();i != t->end ();i++)
+ {
+ ParticleSystem* child = (**i).QueryComponent(ParticleSystem);
+ if (child)
+ CombineParticleBuffersRec(offset, ps, psTemp, *child, false, needsAxisOfRotation);
+ }
+ }
+}
+
+void ParticleSystemRenderer::SetUsesAxisOfRotationRec(ParticleSystem& shuriken, bool first)
+{
+ ParticleSystemRenderer* renderer = shuriken.QueryComponent(ParticleSystemRenderer);
+ if(!renderer || first)
+ {
+ shuriken.SetUsesAxisOfRotation();
+
+ Transform* t = shuriken.QueryComponent (Transform);
+ if (t == NULL)
+ return;
+ for (Transform::iterator i=t->begin ();i != t->end ();i++)
+ {
+ ParticleSystem* shuriken = (**i).QueryComponent(ParticleSystem);
+ if (shuriken)
+ SetUsesAxisOfRotationRec(*shuriken, false);
+ }
+ }
+}
+
+void ParticleSystemRenderer::CombineBoundsRec(ParticleSystem& shuriken, MinMaxAABB& aabb, bool first)
+{
+ ParticleSystemRenderer* renderer = shuriken.QueryComponent(ParticleSystemRenderer);
+ if(!renderer || first)
+ {
+ AABB result = shuriken.m_State->minMaxAABB;
+ if(!shuriken.m_ReadOnlyState->useLocalSpace)
+ InverseTransformAABB (result, renderer->GetTransform().GetPosition (), renderer->GetTransform().GetRotation (), result);
+
+ if(first)
+ aabb = result;
+ else
+ aabb.Encapsulate(result);
+
+ Transform* t = shuriken.QueryComponent (Transform);
+ if (t == NULL)
+ return;
+ for (Transform::iterator i=t->begin ();i != t->end ();i++)
+ {
+ ParticleSystem* shuriken = (**i).QueryComponent(ParticleSystem);
+ if (shuriken)
+ CombineBoundsRec(*shuriken, aabb, false);
+ }
+ }
+}
+
+void ParticleSystemRenderer::Render (int/* materialIndex*/, const ChannelAssigns& channels)
+{
+ ParticleSystem::SyncJobs();
+
+ ParticleSystem* system = QueryComponent(ParticleSystem);
+ if(!system)
+ return;
+
+ // Can't render without an active camera (case 568930)
+ // Can remove check when we finally kill Renderer.Render()
+ if (!GetCurrentCameraPtr())
+ return;
+
+ PROFILER_AUTO_GFX(gParticlesSingleProfile, this);
+
+ UInt32 numParticles = 0;
+ CalculateTotalParticleCount(numParticles, *system, true);
+ if(numParticles)
+ RenderInternal(*system, *this, channels, 0, numParticles);
+}
+
+void ParticleSystemRenderer::RenderMultiple (const BatchInstanceData* instances, size_t count, const ChannelAssigns& channels)
+{
+ ParticleSystem::SyncJobs();
+
+ size_t numParticlesBatch = 0;
+
+ BatchInstanceData const* instancesEnd = instances + count;
+ BatchInstanceData const* iBatchBegin = instances;
+ BatchInstanceData const* iBatchEnd = instances;
+ while(iBatchEnd != instancesEnd)
+ {
+ Assert(iBatchEnd->renderer->GetRendererType() == kRendererParticleSystem);
+ ParticleSystemRenderer* psRenderer = (ParticleSystemRenderer*)iBatchEnd->renderer;
+ Assert(psRenderer->GetRenderMode() != kSRMMesh);
+ ParticleSystem* system = psRenderer->QueryComponent(ParticleSystem);
+ if (!system )
+ {
+ iBatchEnd++;
+ continue;
+ }
+ UInt32 numParticles = 0;
+ psRenderer->CalculateTotalParticleCount(numParticles, *system, true);
+
+ if((numParticlesBatch + numParticles) <= kMaxNumParticlesPerBatch)
+ {
+ numParticlesBatch += numParticles;
+ iBatchEnd++;
+ }
+ else
+ {
+ if(numParticlesBatch)
+ {
+ RenderBatch(iBatchBegin, iBatchEnd - iBatchBegin, numParticlesBatch, channels);
+ numParticlesBatch = 0;
+ iBatchBegin = iBatchEnd;
+ }
+ else // Can't fit in one draw call
+ {
+ RenderBatch(iBatchEnd, 1, numParticles, channels);
+ iBatchEnd++;
+ iBatchBegin = iBatchEnd;
+ }
+ }
+ }
+
+ if((iBatchBegin != iBatchEnd) && numParticlesBatch)
+ RenderBatch(iBatchBegin, iBatchEnd - iBatchBegin, numParticlesBatch, channels);
+}
+
+void ParticleSystemRenderer::RenderBatch (const BatchInstanceData* instances, size_t count, size_t numParticles, const ChannelAssigns& channels)
+{
+ DebugAssert(numParticles);
+
+ GfxDevice& device = GetGfxDevice();
+
+ const MaterialPropertyBlock* customProps = count > 0 ? instances[0].renderer->GetCustomProperties() : NULL;
+ if (customProps)
+ device.SetMaterialProperties (*customProps);
+
+ Matrix4x4f viewMatrix;
+ CopyMatrix (device.GetViewMatrix (), viewMatrix.GetPtr ());
+
+ ParticleSystemVertex* vbPtr = 0;
+ DynamicVBO& vbo = device.GetDynamicVBO();
+ if(numParticles <= kMaxNumParticlesPerBatch)
+ {
+ if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor) | (1<<kShaderChannelTangent),
+ numParticles * 4, 0,
+ DynamicVBO::kDrawQuads,
+ (void**)&vbPtr, NULL ) )
+ {
+ return;
+ }
+ }
+
+ PROFILER_AUTO_GFX(gParticlesBatchProfile, 0);
+
+ // Allocate VBO if count is not above threshold. Else just pass null down
+ BatchInstanceData const* iBatchBegin = instances;
+ BatchInstanceData const* instancesEnd = instances + count;
+ size_t particleOffset = 0;
+ while (iBatchBegin != instancesEnd)
+ {
+ Assert(iBatchBegin->renderer->GetRendererType() == kRendererParticleSystem);
+ ParticleSystemRenderer* psRenderer = (ParticleSystemRenderer*)iBatchBegin->renderer;
+ Assert(psRenderer->GetRenderMode() != kSRMMesh);
+ ParticleSystem* system = psRenderer->QueryComponent(ParticleSystem);
+ UInt32 particleCountTotal = 0;
+ if (system)
+ {
+ // It would be nice to filter out NULL particle systems earlier, but we don't (case 504744)
+ CalculateTotalParticleCount(particleCountTotal, *system, true);
+ if(particleCountTotal)
+ RenderInternal(*system, *psRenderer, channels, vbPtr + particleOffset * 4, particleCountTotal);
+ }
+ iBatchBegin++;
+ particleOffset += particleCountTotal;
+ }
+
+ if(vbPtr)
+ {
+ vbo.ReleaseChunk (numParticles * 4, 0);
+
+ // Draw
+ device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity
+
+ PROFILER_BEGIN(gSubmitVBOParticleProfile, 0)
+ vbo.DrawChunk (channels);
+ GPU_TIMESTAMP();
+ PROFILER_END
+
+ if (count > 1)
+ device.AddBatchingStats(numParticles * 2, numParticles * 4, count);
+
+ device.SetViewMatrix(viewMatrix.GetPtr());
+ }
+}
+
+void ParticleSystemRenderer::RenderInternal (ParticleSystem& system, const ParticleSystemRenderer& renderer, const ChannelAssigns& channels, ParticleSystemVertex* vbPtr, UInt32 particleCountTotal)
+{
+ Assert(particleCountTotal);
+
+#if UNITY_EDITOR
+ if (!renderer.m_EditorEnabled)
+ return;
+#endif
+
+ GfxDevice& device = GetGfxDevice();
+
+ // Render matrix
+ Matrix4x4f viewMatrix;
+ CopyMatrix (device.GetViewMatrix (), viewMatrix.GetPtr ());
+
+ ParticleSystemParticles* ps = &system.GetParticles ();
+ size_t particleCount = ps->array_size ();
+ AssertBreak(particleCountTotal >= particleCount);
+
+ UInt8* combineBuffer = 0;
+ ParticleSystemParticles combineParticles;
+ if(particleCountTotal > particleCount)
+ {
+ particleCount = particleCountTotal;
+ combineBuffer = ALLOC_TEMP_MANUAL(UInt8, particleCountTotal * ParticleSystemParticles::GetParticleSize());
+ combineParticles.array_assign_external((void*)&combineBuffer[0], particleCountTotal);
+ ps = &combineParticles;
+ }
+
+ if(!particleCount)
+ return;
+
+ const bool needsAxisOfRotation = !renderer.GetScreenSpaceRotation();
+ if(needsAxisOfRotation)
+ {
+ if ( IS_CONTENT_NEWER_OR_SAME (GetNumericVersion ("4.0.0f7")) && !IS_CONTENT_NEWER_OR_SAME (GetNumericVersion ("4.1.1a1")) )
+ {
+ // this was introduced in 4.0 and is wrong, it will effectively force the rotation axis to be the y-axis for all particles, the function is meant only
+ // to initialize the axis array (once) and should only be called from SetUsesAxisOfRotationRec
+ ps->SetUsesAxisOfRotation ();
+ }
+ else
+ {
+ // this is the intended functionality, but was broken for 4.0 and 4.0.1 see above
+ SetUsesAxisOfRotationRec (system, true);
+ }
+ }
+
+ ParticleSystemSortMode sortMode = (ParticleSystemSortMode)renderer.m_Data.sortMode;
+ bool isInLocalSpace = system.m_ReadOnlyState->useLocalSpace && !combineBuffer;
+ Matrix4x4f worldMatrix = Matrix4x4f::identity;
+ if(isInLocalSpace)
+ worldMatrix = system.GetComponent (Transform).GetLocalToWorldMatrixNoScale ();
+
+ ParticleSystemParticlesTempData psTemp;
+ psTemp.color = ALLOC_TEMP_MANUAL(ColorRGBA32, particleCount);
+ psTemp.size = ALLOC_TEMP_MANUAL(float, particleCount);
+ psTemp.sheetIndex = 0;
+ psTemp.particleCount = particleCount;
+ if(combineBuffer)
+ {
+ int offset = 0;
+ CombineParticleBuffersRec(offset, *ps, psTemp, system, true, needsAxisOfRotation);
+ if (kSSMNone != sortMode)
+ Sort(viewMatrix, *ps, sortMode, &psTemp, true);
+ }
+ else
+ {
+ if(system.m_UVModule->GetEnabled())
+ psTemp.sheetIndex = ALLOC_TEMP_MANUAL(float, particleCount);
+
+ if (kSSMNone != sortMode)
+ {
+ Matrix4x4f objectToViewMatrix;
+ MultiplyMatrices3x4(viewMatrix, worldMatrix, objectToViewMatrix);
+ Sort(objectToViewMatrix, *ps, sortMode, 0, false);
+ }
+ ParticleSystem::UpdateModulesNonIncremental(system, *ps, psTemp, 0, particleCount);
+ }
+
+ // Constrain the size to be a fraction of the viewport size.
+ // In perspective case, max size is (z*factorA). In ortho case, max size is just factorB. To have both
+ // without branches, we do (z*factorA+factorB) and set one of factors to zero.
+ float maxPlaneScale = 0.0f;
+ float maxOrthoSize = 0.0f;
+ // Getting the camera isn't totally free, so do it once.
+ const Camera& camera = GetCurrentCamera();
+ if (!camera.GetOrthographic())
+ maxPlaneScale = -camera.CalculateFarPlaneWorldSpaceLength() * renderer.m_Data.maxParticleSize / camera.GetFar();
+ else
+ maxOrthoSize = camera.CalculateFarPlaneWorldSpaceLength() * renderer.m_Data.maxParticleSize;
+
+ int numMeshes = 0;
+ for(int i = 0; i < ParticleSystemRendererData::kMaxNumParticleMeshes; i++)
+ if(renderer.m_Data.cachedMesh[i])
+ numMeshes++;
+
+ ParticleSystemGeomConstInputData constData;
+ constData.m_ViewMatrix = viewMatrix;
+ constData.m_CameraVelocity = viewMatrix.MultiplyVector3(camera.GetVelocity ());
+ constData.m_Renderer = (Object*)&renderer;
+ for(int i = 0; i < numMeshes; i++)
+ {
+ constData.m_MeshIndexBuffer[i] = renderer.m_CachedIndexBuffer[i].begin();
+ constData.m_MeshIndexCount[i] = renderer.m_CachedIndexBuffer[i].size();
+ AssertBreak((constData.m_MeshIndexCount[i] % 3) == 0);
+ }
+
+ system.GetNumTiles(constData.m_NumTilesX, constData.m_NumTilesY);
+ constData.maxPlaneScale = maxPlaneScale;
+ constData.maxOrthoSize = maxOrthoSize;
+ constData.numUVFrame = constData.m_NumTilesX * constData.m_NumTilesY;
+ constData.animUScale = 1.0f / (float)constData.m_NumTilesX;
+ constData.animVScale = 1.0f / (float)constData.m_NumTilesY;
+ constData.xSpan = Vector3f(-1.0f,0.0f,0.0f);
+ constData.ySpan = Vector3f(0.0f,0.0f,1.0f);
+ if (renderer.m_Data.renderMode == kSRMBillboardFixedVertical)
+ {
+ constData.ySpan = Vector3f(0.0f,1.0f,0.0f);
+ const Vector3f zSpan = viewMatrix.MultiplyVector3 (Vector3f::zAxis);// (RotateVectorByQuat (cameraRotation, Vector3f(0.0f,0.0f,1.0f));
+ constData.xSpan = NormalizeSafe (Cross (constData.ySpan, zSpan));
+ }
+ constData.xSpan = viewMatrix.MultiplyVector3(constData.xSpan);
+ constData.ySpan = viewMatrix.MultiplyVector3(constData.ySpan);
+ constData.usesSheetIndex = psTemp.sheetIndex != NULL;
+
+ const float bentNormalAngle = renderer.m_Data.normalDirection * 90.0f * kDeg2Rad;
+ const float scale = (renderer.m_Data.renderMode == kSRMBillboard) ? 0.707106781f : 1.0f;
+
+ Matrix4x4f viewToWorldMatrix;
+ Matrix4x4f::Invert_General3D(viewMatrix, viewToWorldMatrix);
+
+ Matrix4x4f worldViewMatrix;
+ MultiplyMatrices4x4(&viewMatrix, &worldMatrix, &worldViewMatrix);
+
+ Vector3f billboardNormal = Vector3f::zAxis;
+ if((renderer.m_Data.renderMode == kSRMBillboardFixedHorizontal) || (renderer.m_Data.renderMode == kSRMBillboardFixedVertical))
+ billboardNormal = viewMatrix.MultiplyVector3 (NormalizeSafe (Cross (constData.xSpan, constData.ySpan)));
+ constData.bentNormalVector = viewToWorldMatrix.MultiplyVector3(Sin(bentNormalAngle) * billboardNormal);
+ constData.bentNormalFactor = Cos(bentNormalAngle) * scale;
+
+ if (renderer.m_Data.renderMode == kSRMMesh)
+ DrawMeshParticles (constData, renderer.m_Data, worldMatrix, *ps, psTemp, channels);
+ else
+ DrawParticles(constData, renderer.m_Data, worldViewMatrix, viewToWorldMatrix, *ps, psTemp, channels, vbPtr);
+
+ FREE_TEMP_MANUAL(psTemp.color);
+ FREE_TEMP_MANUAL(psTemp.size);
+ if(psTemp.sheetIndex)
+ FREE_TEMP_MANUAL(psTemp.sheetIndex);
+ if(combineBuffer)
+ FREE_TEMP_MANUAL(combineBuffer);
+}
+
+template<class TransferFunction> inline
+void ParticleSystemRenderer::Transfer (TransferFunction& transfer)
+{
+ Super::Transfer (transfer);
+ transfer.Transfer (m_Data.renderMode, "m_RenderMode");
+ transfer.Transfer (m_Data.maxParticleSize, "m_MaxParticleSize");
+ transfer.Transfer (m_Data.cameraVelocityScale, "m_CameraVelocityScale");
+ transfer.Transfer (m_Data.velocityScale, "m_VelocityScale");
+ transfer.Transfer (m_Data.lengthScale, "m_LengthScale");
+ transfer.Transfer (m_Data.sortingFudge, "m_SortingFudge");
+ transfer.Transfer (m_Data.normalDirection, "m_NormalDirection");
+ transfer.Transfer (m_Data.sortMode, "m_SortMode");
+ transfer.Transfer (m_Mesh[0], "m_Mesh");
+ transfer.Transfer (m_Mesh[1], "m_Mesh1");
+ transfer.Transfer (m_Mesh[2], "m_Mesh2");
+ transfer.Transfer (m_Mesh[3], "m_Mesh3");
+}
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h b/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h
new file mode 100644
index 0000000..07bcb0d
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h
@@ -0,0 +1,134 @@
+#ifndef SHURIKENRENDERER_H
+#define SHURIKENRENDERER_H
+
+#include "Runtime/Filters/Renderer.h"
+#include "Runtime/Math/Color.h"
+#include "Runtime/Math/Rect.h"
+#include "Runtime/Math/Vector3.h"
+#include "Runtime/Math/Vector2.h"
+
+
+
+
+class Mesh;
+class MinMaxAABB;
+struct ParticleSystemVertex;
+
+struct ParticleSystemRendererData
+{
+ // Must match the one in RendererModuleUI.cs
+ enum { kMaxNumParticleMeshes = 4 };
+
+ int renderMode; ///< enum { Billboard = 0, Stretched = 1, Horizontal Billboard = 2, Vertical Billboard = 3, Mesh = 4 }
+ int sortMode; ///< enum { None = 0, By Distance = 1, Youngest First = 2, Oldest First = 3 }
+ float maxParticleSize; ///< How large is a particle allowed to be on screen at most? 1 is entire viewport. 0.5 is half viewport.
+ float cameraVelocityScale; ///< How much the camera motion is factored in when determining particle stretching.
+ float velocityScale; ///< When Stretch Particles is enabled, defines the length of the particle compared to its velocity.
+ float lengthScale; ///< When Stretch Particles is enabled, defines the length of the particle compared to its width.
+ float sortingFudge; ///< Lower the number, most likely that these particles will appear in front of other transparent objects, including other particles.
+ float normalDirection; ///< Value between 0.0 and 1.0. If 1.0 is used, normals will point towards camera. If 0.0 is used, normals will point out in the corner direction of the particle.
+ Mesh* cachedMesh[kMaxNumParticleMeshes];
+
+ /// Node hooked into the mesh user list of cached meshes so we get notified
+ /// when a mesh goes away.
+ ///
+ /// NOTE: Must be initialized properly after construction to point to the
+ /// ParticleSystemRenderer.
+ ListNode<Object> cachedMeshUserNode[kMaxNumParticleMeshes];
+};
+
+
+enum ParticleSystemRenderMode {
+ kSRMBillboard = 0,
+ kSRMStretch3D = 1,
+ kSRMBillboardFixedHorizontal = 2,
+ kSRMBillboardFixedVertical = 3,
+ kSRMMesh = 4,
+};
+
+enum ParticleSystemSortMode
+{
+ kSSMNone,
+ kSSMByDistance,
+ kSSMYoungestFirst,
+ kSSMOldestFirst,
+};
+
+struct ParticleSystemParticles;
+struct ParticleSystemParticlesTempData;
+class ParticleSystem;
+struct ParticleSystemGeomConstInputData;
+class ParticleSystemRenderer : public Renderer {
+public:
+ REGISTER_DERIVED_CLASS (ParticleSystemRenderer, Renderer)
+ DECLARE_OBJECT_SERIALIZE (ParticleSystemRenderer)
+ static void InitializeClass ();
+
+ ParticleSystemRenderer (MemLabelId label, ObjectCreationMode mode);
+ // ParticleSystemRenderer(); declared-by-macro
+
+ virtual void Render (int materialIndex, const ChannelAssigns& channels);
+ static void RenderMultiple (const BatchInstanceData* instances, size_t count, const ChannelAssigns& channels);
+
+ virtual void GetLocalAABB (AABB& result);
+ virtual void GetWorldAABB (AABB& result);
+ virtual float GetSortingFudge () const;
+
+ virtual void CheckConsistency ();
+ virtual void Reset ();
+
+ virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode);
+
+ void Update (const AABB& aabb);
+ void UpdateLocalAABB();
+
+ virtual void RendererBecameVisible();
+ virtual void RendererBecameInvisible();
+
+ static void SetUsesAxisOfRotationRec(ParticleSystem& system, bool first);
+ static void CombineBoundsRec(ParticleSystem& shuriken, MinMaxAABB& aabb, bool first);
+
+ GET_SET_DIRTY (ParticleSystemRenderMode, RenderMode, m_Data.renderMode) ;
+ GET_SET_DIRTY (ParticleSystemSortMode, SortMode, m_Data.sortMode) ;
+ GET_SET_DIRTY (float, MaxParticleSize, m_Data.maxParticleSize) ;
+ GET_SET_DIRTY (float, CameraVelocityScale, m_Data.cameraVelocityScale) ;
+ GET_SET_DIRTY (float, VelocityScale, m_Data.velocityScale) ;
+ GET_SET_DIRTY (float, LengthScale, m_Data.lengthScale) ;
+ void SetMesh (PPtr<Mesh> mesh) { m_Mesh[0] = mesh; SetDirty(); UpdateCachedMesh (); }
+ PPtr<Mesh> GetMesh () const { return m_Mesh[0]; }
+
+ const PPtr<Mesh>* GetMeshes () const { return m_Mesh; }
+ const ParticleSystemRendererData& GetData() const { return m_Data; }
+
+#if UNITY_EDITOR
+ bool GetEditorEnabled() const { return m_EditorEnabled; }
+ void SetEditorEnabled(bool value) { m_EditorEnabled = value; }
+#endif
+
+ // For mesh we use world space rotation, else screen space
+ bool GetScreenSpaceRotation() const { return m_Data.renderMode != kSRMMesh; };
+private:
+ // from Renderer
+ virtual void UpdateRenderer ();
+ void UpdateCachedMesh ();
+ void OnDidDeleteMesh (Mesh* mesh);
+
+private:
+ static void CalculateTotalParticleCount(UInt32& totalNumberOfParticles, ParticleSystem& shuriken, bool first);
+ static void CombineParticleBuffersRec(int& offset, ParticleSystemParticles& particles, ParticleSystemParticlesTempData& psTemp, ParticleSystem& shuriken, bool first, bool needsAxisOfRotation);
+
+ static void RenderBatch (const BatchInstanceData* instances, size_t count, size_t numParticles, const ChannelAssigns& channels);
+ static void RenderInternal (ParticleSystem& system, const ParticleSystemRenderer& renderer, const ChannelAssigns& channels, ParticleSystemVertex* vbPtr, UInt32 particleCountTotal);
+
+ ParticleSystemRendererData m_Data;
+ dynamic_array<UInt16> m_CachedIndexBuffer[ParticleSystemRendererData::kMaxNumParticleMeshes];
+ PPtr<Mesh> m_Mesh[ParticleSystemRendererData::kMaxNumParticleMeshes];
+
+ AABB m_LocalSpaceAABB;
+
+#if UNITY_EDITOR
+ bool m_EditorEnabled;
+#endif
+};
+
+#endif // SHURIKENRENDERER_H
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemRendererTests.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemRendererTests.cpp
new file mode 100644
index 0000000..47db7ee
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemRendererTests.cpp
@@ -0,0 +1,29 @@
+#include "UnityPrefix.h"
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "Runtime/Graphics/ParticleSystem/ParticleSystemRenderer.h"
+#include "Runtime/Filters/Mesh/LodMesh.h"
+#include "Runtime/Testing/TestFixtures.h"
+
+
+SUITE (ParticleSystemRendererTests)
+{
+ typedef ObjectTestFixture<ParticleSystemRenderer> Fixture;
+
+ TEST_FIXTURE (Fixture, DeletingMeshClearsOutCachedMeshPointers)
+ {
+ // Arrange.
+ PPtr<Mesh> mesh (NEW_OBJECT_RESET_AND_AWAKE (Mesh));
+ m_ObjectUnderTest->SetMesh (mesh);
+
+ // Act.
+ DestroySingleObject (mesh);
+
+ // Assert.
+ CHECK (m_ObjectUnderTest->GetData().cachedMesh[0] == NULL);
+ }
+}
+
+#endif
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.cpp b/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.cpp
new file mode 100644
index 0000000..032cd19
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.cpp
@@ -0,0 +1,113 @@
+#include "UnityPrefix.h"
+#include "ParticleSystemUtils.h"
+#include "ParticleSystem.h"
+#include "ParticleSystemCurves.h"
+#include "Modules/ParticleSystemModule.h"
+#include "Runtime/BaseClasses/IsPlaying.h"
+
+#if UNITY_EDITOR
+#include "Editor/Src/ParticleSystem/ParticleSystemEditor.h"
+#endif
+
+UInt32 randomSeed = 0x1337;
+
+UInt32 GetGlobalRandomSeed ()
+{
+ return ++randomSeed;
+}
+
+void ResetGlobalRandomSeed ()
+{
+ randomSeed = 0x1337;
+}
+
+Vector2f CalculateInverseLerpOffsetScale (const Vector2f& range)
+{
+ Assert (range.x < range.y);
+ float scale = 1.0F / (range.y - range.x);
+ return Vector2f (scale, -range.x * scale);
+}
+
+void CalculatePositionAndVelocity(Vector3f& initialPosition, Vector3f& initialVelocity, const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, const ParticleSystemParticles& ps, const size_t index)
+{
+ initialPosition = ps.position[index];
+ initialVelocity = ps.velocity[index] + ps.animatedVelocity[index];
+ if(roState.useLocalSpace)
+ {
+ // If we are in local space, transform to world space to make independent of this emitters transform
+ initialPosition = state.localToWorld.MultiplyPoint3(initialPosition);
+ initialVelocity = state.localToWorld.MultiplyVector3(initialVelocity);
+ }
+}
+
+void KillParticle(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t index, size_t& particleCount)
+{
+ Assert(particleCount > 0);
+
+ for(int i = 0; i < state.numCachedSubDataDeath; i++)
+ {
+ ParticleSystemEmissionState emissionState;
+ RecordEmit(emissionState, state.cachedSubDataDeath[i], roState, state, ps, kParticleSystemSubTypeDeath, i, index, 0.0f, 0.0001f, 1.0f);
+ }
+
+ ps.element_assign (index, particleCount - 1);
+ --particleCount;
+
+}
+
+void RecordEmit(ParticleSystemEmissionState& emissionState, const ParticleSystemSubEmitterData& subEmitterData, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, ParticleSystemSubType type, int subEmitterIndex, size_t particleIndex, float t, float dt, float length)
+{
+ size_t numContinuous = 0;
+ Vector3f initialPosition;
+ Vector3f initialVelocity;
+ CalculatePositionAndVelocity(initialPosition, initialVelocity, roState, state, ps, particleIndex);
+ int amountOfParticlesToEmit = ParticleSystem::EmitFromData (emissionState, numContinuous, subEmitterData.emissionData, initialVelocity, t, std::min(t + dt, length), dt, length);
+ if(amountOfParticlesToEmit)
+ {
+ if(!state.recordSubEmits)
+ ParticleSystem::Emit(*subEmitterData.emitter, SubEmitterEmitCommand(emissionState, initialPosition, initialVelocity, type, subEmitterIndex, amountOfParticlesToEmit, numContinuous, t, dt), kParticleSystemEMStaging);
+ else if(!state.subEmitterCommandBuffer.IsFull())
+ state.subEmitterCommandBuffer.AddCommand(emissionState, initialPosition, initialVelocity, type, subEmitterIndex, amountOfParticlesToEmit, numContinuous, t, dt);
+ }
+}
+
+bool GetTransformationMatrix(Matrix4x4f& output, const bool isSystemInWorld, const bool isCurveInWorld, const Matrix4x4f& localToWorld)
+{
+ if(isCurveInWorld != isSystemInWorld)
+ {
+ if(isSystemInWorld)
+ output = localToWorld;
+ else
+ Matrix4x4f::Invert_General3D(localToWorld, output);
+ return true;
+ }
+ else
+ {
+ output = Matrix4x4f::identity;
+ return false;
+ }
+}
+
+bool GetTransformationMatrices(Matrix4x4f& output, Matrix4x4f& outputInverse, const bool isSystemInWorld, const bool isCurveInWorld, const Matrix4x4f& localToWorld)
+{
+ if(isCurveInWorld != isSystemInWorld)
+ {
+ if(isSystemInWorld)
+ {
+ output = localToWorld;
+ Matrix4x4f::Invert_General3D(localToWorld, outputInverse);
+ }
+ else
+ {
+ Matrix4x4f::Invert_General3D(localToWorld, output);
+ outputInverse = localToWorld;
+ }
+ return true;
+ }
+ else
+ {
+ output = Matrix4x4f::identity;
+ outputInverse = Matrix4x4f::identity;
+ return false;
+ }
+}
diff --git a/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h b/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h
new file mode 100644
index 0000000..45ec0a3
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h
@@ -0,0 +1,48 @@
+#ifndef SHURIKENUTILS_H
+#define SHURIKENUTILS_H
+
+#include "ParticleSystemCommon.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Math/Random/Random.h"
+
+class Matrix4x4f;
+struct ParticleSystemParticles;
+struct ParticleSystemReadOnlyState;
+struct ParticleSystemState;
+struct ParticleSystemEmissionState;
+struct ParticleSystemSubEmitterData;
+
+inline float InverseLerpFast01 (const Vector2f& scaleOffset, float v)
+{
+ return clamp01 (v * scaleOffset.x + scaleOffset.y);
+}
+
+inline float GenerateRandom(UInt32 randomIn)
+{
+ Rand rand(randomIn);
+ return Random01(rand);
+}
+
+inline void GenerateRandom3(Vector3f& randomOut, UInt32 randomIn)
+{
+ Rand rand(randomIn);
+ randomOut.x = Random01(rand);
+ randomOut.y = Random01(rand);
+ randomOut.z = Random01(rand);
+}
+
+inline UInt8 GenerateRandomByte (UInt32 seed)
+{
+ Rand rand (seed);
+ return Rand::GetByteFromInt (rand.Get ());
+}
+
+UInt32 GetGlobalRandomSeed ();
+void ResetGlobalRandomSeed ();
+Vector2f CalculateInverseLerpOffsetScale (const Vector2f& range);
+void KillParticle(const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, size_t index, size_t& particleCount);
+void RecordEmit(ParticleSystemEmissionState& emissionState, const ParticleSystemSubEmitterData& subEmitterData, const ParticleSystemReadOnlyState& roState, ParticleSystemState& state, ParticleSystemParticles& ps, ParticleSystemSubType type, int subEmitterIndex, size_t particleIndex, float t, float dt, float length);
+bool GetTransformationMatrix(Matrix4x4f& output, const bool isSystemInWorld, const bool isCurveInWorld, const Matrix4x4f& localToWorld);
+bool GetTransformationMatrices(Matrix4x4f& output, Matrix4x4f& outputInverse, const bool isSystemInWorld, const bool isCurveInWorld, const Matrix4x4f& localToWorld);
+
+#endif // SHURIKENUTILS_H
diff --git a/Runtime/Graphics/ParticleSystem/PolynomialCurve.cpp b/Runtime/Graphics/ParticleSystem/PolynomialCurve.cpp
new file mode 100644
index 0000000..f20b0ac
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/PolynomialCurve.cpp
@@ -0,0 +1,405 @@
+#include "UnityPrefix.h"
+#include "PolynomialCurve.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Math/Polynomials.h"
+#include "Runtime/Math/AnimationCurve.h"
+
+static void DoubleIntegrateSegment (float* coeff)
+{
+ coeff[0] /= 20.0F;
+ coeff[1] /= 12.0F;
+ coeff[2] /= 6.0F;
+ coeff[3] /= 2.0F;
+}
+
+static void IntegrateSegment (float* coeff)
+{
+ coeff[0] /= 4.0F;
+ coeff[1] /= 3.0F;
+ coeff[2] /= 2.0F;
+ coeff[3] /= 1.0F;
+}
+
+void CalculateMinMax(Vector2f& minmax, float value)
+{
+ minmax.x = std::min(minmax.x, value);
+ minmax.y = std::max(minmax.y, value);
+}
+
+void ConstrainToPolynomialCurve (AnimationCurve& curve)
+{
+ const int max = OptimizedPolynomialCurve::kMaxPolynomialKeyframeCount;
+
+ // Maximum 3 keys
+ if (curve.GetKeyCount () > max)
+ curve.RemoveKeys(curve.begin() + max, curve.end());
+
+ // Clamp begin and end to 0...1 range
+ if (curve.GetKeyCount () >= 2)
+ {
+ curve.GetKey(0).time = 0;
+ curve.GetKey(curve.GetKeyCount ()-1).time = 1;
+ }
+}
+
+bool IsValidPolynomialCurve (const AnimationCurve& curve)
+{
+ // Maximum 3 keys
+ if (curve.GetKeyCount () > OptimizedPolynomialCurve::kMaxPolynomialKeyframeCount)
+ return false;
+ // One constant key can always be representated
+ else if (curve.GetKeyCount () <= 1)
+ return true;
+ // First and last keyframe must be at 0 and 1 time
+ else
+ {
+ float beginTime = curve.GetKey(0).time;
+ float endTime = curve.GetKey(curve.GetKeyCount ()-1).time;
+
+ return CompareApproximately(beginTime, 0.0F, 0.0001F) && CompareApproximately(endTime, 1.0F, 0.0001F);
+ }
+}
+
+void SetPolynomialCurveToValue (AnimationCurve& a, OptimizedPolynomialCurve& c, float value)
+{
+ AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, value), AnimationCurve::Keyframe(1.0f, value) };
+ a.Assign(keys, keys + 2);
+ c.BuildOptimizedCurve(a, 1.0f);
+}
+
+void SetPolynomialCurveToLinear (AnimationCurve& a, OptimizedPolynomialCurve& c)
+{
+ AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, 0.0f), AnimationCurve::Keyframe(1.0f, 1.0f) };
+ keys[0].inSlope = 0.0f; keys[0].outSlope = 1.0f;
+ keys[1].inSlope = 1.0f; keys[1].outSlope = 0.0f;
+ a.Assign(keys, keys + 2);
+ c.BuildOptimizedCurve(a, 1.0f);
+}
+
+bool OptimizedPolynomialCurve::BuildOptimizedCurve (const AnimationCurve& editorCurve, float scale)
+{
+ if (!IsValidPolynomialCurve(editorCurve))
+ return false;
+
+ const size_t keyCount = editorCurve.GetKeyCount ();
+
+ timeValue = 1.0F;
+ memset(segments, 0, sizeof(segments));
+
+ // Handle corner case 1 & 0 keyframes
+ if (keyCount == 0)
+ ;
+ else if (keyCount == 1)
+ {
+ // Set constant value coefficient
+ for (int i=0;i<kSegmentCount;i++)
+ segments[i].coeff[3] = editorCurve.GetKey(0).value * scale;
+ }
+ else
+ {
+ float segmentStartTime[kSegmentCount];
+
+ for (int i=0;i<kSegmentCount;i++)
+ {
+ bool hasSegment = i+1 < keyCount;
+ if (hasSegment)
+ {
+ AnimationCurve::Cache cache;
+ editorCurve.CalculateCacheData(cache, i, i + 1, 0.0F);
+
+ memcpy(segments[i].coeff, cache.coeff, sizeof(Polynomial));
+ segmentStartTime[i] = editorCurve.GetKey(i).time;
+ }
+ else
+ {
+ memcpy(segments[i].coeff, segments[i-1].coeff, sizeof(Polynomial));
+ segmentStartTime[i] = 1.0F;//timeValue[i-1];
+ }
+ }
+
+ // scale curve
+ for (int i=0;i<kSegmentCount;i++)
+ {
+ segments[i].coeff[0] *= scale;
+ segments[i].coeff[1] *= scale;
+ segments[i].coeff[2] *= scale;
+ segments[i].coeff[3] *= scale;
+ }
+
+ // Timevalue 0 is always 0.0F. No need to store it.
+ timeValue = segmentStartTime[1];
+
+ #if !UNITY_RELEASE
+ // Check that UI editor curve matches polynomial curve except if the
+ // scale happens to be infinite (trying to subtract infinity values from
+ // each other yields NaN with IEEE floats).
+ if (scale != std::numeric_limits<float>::infinity () &&
+ scale != -std::numeric_limits<float>::infinity())
+ {
+ for (int i=0;i<=50;i++)
+ {
+ // The very last element at 1.0 can be different when using step curves.
+ // The AnimationCurve implementation will sample the position of the last key.
+ // The OptimizedPolynomialCurve will keep value of the previous key (Continuing the trajectory of the segment)
+ // In practice this is probably not a problem, since you don't sample 1.0 because then the particle will be dead.
+ // thus we just don't do the automatic assert when the curves are not in sync in that case.
+ float t = std::min(i / 50.0F, 0.99999F);
+ float dif;
+
+ DebugAssert((dif = Abs(Evaluate(t) - editorCurve.Evaluate(t) * scale)) < 0.01F);
+ }
+ }
+ #endif
+ }
+
+ return true;
+}
+
+void OptimizedPolynomialCurve::Integrate ()
+{
+ for (int i=0;i<kSegmentCount;i++)
+ IntegrateSegment(segments[i].coeff);
+}
+
+void OptimizedPolynomialCurve::DoubleIntegrate ()
+{
+ Polynomial velocity0 = segments[0];
+ IntegrateSegment (velocity0.coeff);
+
+ velocityValue = Polynomial::EvalSegment(timeValue, velocity0.coeff) * timeValue;
+
+ for (int i=0;i<kSegmentCount;i++)
+ DoubleIntegrateSegment(segments[i].coeff);
+}
+
+Vector2f OptimizedPolynomialCurve::FindMinMaxDoubleIntegrated() const
+{
+ // Because of velocityValue * t, this becomes a quartic polynomial (4th order polynomial).
+ // TODO: Find all roots of quartic polynomial
+ Vector2f result = Vector2f::zero;
+ const int numSteps = 20;
+ const float delta = 1.0f / float(numSteps);
+ float acc = delta;
+ for(int i = 0; i < numSteps; i++)
+ {
+ CalculateMinMax(result, EvaluateDoubleIntegrated(acc));
+ acc += delta;
+ }
+ return result;
+}
+
+// Find the maximum of the integrated curve (x: min, y: max)
+Vector2f OptimizedPolynomialCurve::FindMinMaxIntegrated() const
+{
+ Vector2f result = Vector2f::zero;
+
+ float start[kSegmentCount] = {0.0f, timeValue};
+ float end[kSegmentCount] = {timeValue, 1.0f};
+ for(int i = 0; i < kSegmentCount; i++)
+ {
+ // Differentiate coefficients
+ float a = 4.0f*segments[i].coeff[0];
+ float b = 3.0f*segments[i].coeff[1];
+ float c = 2.0f*segments[i].coeff[2];
+ float d = 1.0f*segments[i].coeff[3];
+
+ float roots[3];
+ int numRoots = CubicPolynomialRootsGeneric(roots, a, b, c, d);
+ for(int r = 0; r < numRoots; r++)
+ {
+ float root = roots[r] + start[i];
+ if((root >= start[i]) && (root < end[i]))
+ CalculateMinMax(result, EvaluateIntegrated(root));
+ }
+
+ // TODO: Don't use eval integrated, use eval segment (and integrate in loop)
+ CalculateMinMax(result, EvaluateIntegrated(end[i]));
+ }
+ return result;
+}
+
+// Find the maximum of a double integrated curve (x: min, y: max)
+Vector2f PolynomialCurve::FindMinMaxDoubleIntegrated() const
+{
+ // Because of velocityValue * t, this becomes a quartic polynomial (4th order polynomial).
+ // TODO: Find all roots of quartic polynomial
+ Vector2f result = Vector2f::zero;
+ const int numSteps = 20;
+ const float delta = 1.0f / float(numSteps);
+ float acc = delta;
+ for(int i = 0; i < numSteps; i++)
+ {
+ CalculateMinMax(result, EvaluateDoubleIntegrated(acc));
+ acc += delta;
+ }
+ return result;
+}
+
+Vector2f PolynomialCurve::FindMinMaxIntegrated() const
+{
+ Vector2f result = Vector2f::zero;
+
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < segmentCount; i++)
+ {
+ // Differentiate coefficients
+ float a = 4.0f*segments[i].coeff[0];
+ float b = 3.0f*segments[i].coeff[1];
+ float c = 2.0f*segments[i].coeff[2];
+ float d = 1.0f*segments[i].coeff[3];
+
+ float roots[3];
+ int numRoots = CubicPolynomialRootsGeneric(roots, a, b, c, d);
+ for(int r = 0; r < numRoots; r++)
+ {
+ float root = roots[r] + prevTimeValue;
+ if((root >= prevTimeValue) && (root < times[i]))
+ CalculateMinMax(result, EvaluateIntegrated(root));
+ }
+
+ // TODO: Don't use eval integrated, use eval segment (and integrate in loop)
+ CalculateMinMax(result, EvaluateIntegrated(times[i]));
+ prevTimeValue = times[i];
+ }
+ return result;
+}
+
+bool PolynomialCurve::IsValidCurve(const AnimationCurve& editorCurve)
+{
+ int keyCount = editorCurve.GetKeyCount();
+ int segmentCount = keyCount - 1;
+ if(editorCurve.GetKey(0).time != 0.0f)
+ segmentCount++;
+ if(editorCurve.GetKey(keyCount-1).time != 1.0f)
+ segmentCount++;
+ return segmentCount <= kMaxNumSegments;
+}
+
+bool PolynomialCurve::BuildCurve(const AnimationCurve& editorCurve, float scale)
+{
+ int keyCount = editorCurve.GetKeyCount();
+ segmentCount = 1;
+
+ const float kMaxTime = 1.01f;
+
+ memset(segments, 0, sizeof(segments));
+ memset(integrationCache, 0, sizeof(integrationCache));
+ memset(doubleIntegrationCache, 0, sizeof(doubleIntegrationCache));
+ memset(times, 0, sizeof(times));
+ times[0] = kMaxTime;
+
+ // Handle corner case 1 & 0 keyframes
+ if (keyCount == 0)
+ ;
+ else if (keyCount == 1)
+ {
+ // Set constant value coefficient
+ segments[0].coeff[3] = editorCurve.GetKey(0).value * scale;
+ }
+ else
+ {
+ segmentCount = keyCount - 1;
+ int segmentOffset = 0;
+
+ // Add extra key to start if it doesn't match up
+ if(editorCurve.GetKey(0).time != 0.0f)
+ {
+ segments[0].coeff[3] = editorCurve.GetKey(0).value;
+ times[0] = editorCurve.GetKey(0).time;
+ segmentOffset = 1;
+ }
+
+ for (int i = 0;i<segmentCount;i++)
+ {
+ DebugAssert(i+1 < keyCount);
+ AnimationCurve::Cache cache;
+ editorCurve.CalculateCacheData(cache, i, i + 1, 0.0F);
+ memcpy(segments[i+segmentOffset].coeff, cache.coeff, 4 * sizeof(float));
+ times[i+segmentOffset] = editorCurve.GetKey(i+1).time;
+ }
+ segmentCount += segmentOffset;
+
+ // Add extra key to start if it doesn't match up
+ if(editorCurve.GetKey(keyCount-1).time != 1.0f)
+ {
+ segments[segmentCount].coeff[3] = editorCurve.GetKey(keyCount-1).value;
+ segmentCount++;
+ }
+
+ // Fixup last key time value
+ times[segmentCount-1] = kMaxTime;
+
+ for (int i = 0;i<segmentCount;i++)
+ {
+ segments[i].coeff[0] *= scale;
+ segments[i].coeff[1] *= scale;
+ segments[i].coeff[2] *= scale;
+ segments[i].coeff[3] *= scale;
+ }
+ }
+
+ DebugAssert(segmentCount <= kMaxNumSegments);
+
+#if !UNITY_RELEASE
+ // Check that UI editor curve matches polynomial curve
+ for (int i=0;i<=10;i++)
+ {
+ // The very last element at 1.0 can be different when using step curves.
+ // The AnimationCurve implementation will sample the position of the last key.
+ // The PolynomialCurve will keep value of the previous key (Continuing the trajectory of the segment)
+ // In practice this is probably not a problem, since you don't sample 1.0 because then the particle will be dead.
+ // thus we just don't do the automatic assert when the curves are not in sync in that case.
+ float t = std::min(i / 50.0F, 0.99999F);
+ float dif;
+ DebugAssert((dif = Abs(Evaluate(t) - editorCurve.Evaluate(t) * scale)) < 0.01F);
+ }
+#endif
+ return true;
+}
+
+void GenerateIntegrationCache(PolynomialCurve& curve)
+{
+ curve.integrationCache[0] = 0.0f;
+ float prevTimeValue0 = curve.times[0];
+ float prevTimeValue1 = 0.0f;
+ for (int i=1;i<curve.segmentCount;i++)
+ {
+ float coeff[4];
+ memcpy(coeff, curve.segments[i-1].coeff, 4*sizeof(float));
+ IntegrateSegment (coeff);
+ float time = prevTimeValue0 - prevTimeValue1;
+ curve.integrationCache[i] = curve.integrationCache[i-1] + Polynomial::EvalSegment(time, coeff) * time;
+ prevTimeValue1 = prevTimeValue0;
+ prevTimeValue0 = curve.times[i];
+ }
+}
+
+// Expects double integrated segments and valid integration cache
+void GenerateDoubleIntegrationCache(PolynomialCurve& curve)
+{
+ float sum = 0.0f;
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < curve.segmentCount; i++)
+ {
+ curve.doubleIntegrationCache[i] = sum;
+ float time = curve.times[i] - prevTimeValue;
+ time = std::max(time, 0.0f);
+ sum += Polynomial::EvalSegment(time, curve.segments[i].coeff) * time * time + curve.integrationCache[i] * time;
+ prevTimeValue = curve.times[i];
+ }
+}
+
+void PolynomialCurve::Integrate ()
+{
+ GenerateIntegrationCache(*this);
+ for (int i=0;i<segmentCount;i++)
+ IntegrateSegment(segments[i].coeff);
+}
+
+void PolynomialCurve::DoubleIntegrate ()
+{
+ GenerateIntegrationCache(*this);
+ for (int i=0;i<segmentCount;i++)
+ DoubleIntegrateSegment(segments[i].coeff);
+ GenerateDoubleIntegrationCache(*this);
+}
diff --git a/Runtime/Graphics/ParticleSystem/PolynomialCurve.h b/Runtime/Graphics/ParticleSystem/PolynomialCurve.h
new file mode 100644
index 0000000..d9e7270
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/PolynomialCurve.h
@@ -0,0 +1,229 @@
+#ifndef POLYONOMIAL_CURVE_H
+#define POLYONOMIAL_CURVE_H
+
+template<class T>
+class AnimationCurveTpl;
+typedef AnimationCurveTpl<float> AnimationCurve;
+class Vector2f;
+
+struct Polynomial
+{
+ static float EvalSegment (float t, const float* coeff)
+ {
+ return (t * (t * (t * coeff[0] + coeff[1]) + coeff[2])) + coeff[3];
+ }
+
+ float coeff[4];
+};
+
+// Smaller, optimized version
+struct OptimizedPolynomialCurve
+{
+ enum { kMaxPolynomialKeyframeCount = 3, kSegmentCount = kMaxPolynomialKeyframeCount-1, };
+
+ Polynomial segments[kSegmentCount];
+
+ float timeValue;
+ float velocityValue;
+
+ // Evaluate double integrated Polynomial curve.
+ // Example: position = EvaluateDoubleIntegrated (normalizedTime) * startEnergy^2
+ // Use DoubleIntegrate function to for example turn a force curve into a position curve.
+ // Expects that t is in the 0...1 range.
+ float EvaluateDoubleIntegrated (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+ float res0, res1;
+
+ // All segments are added together. At t = 0, the integrated curve is always zero.
+
+ // 0 segment is sampled up to the 1 keyframe
+ // First key is always assumed to be at 0 time
+ float t1 = std::min(t, timeValue);
+
+ // 1 segment is sampled from 1 key to 2 key
+ // Last key is always assumed to be at 1 time
+ float t2 = std::max(0.0F, t - timeValue);
+
+ res0 = Polynomial::EvalSegment(t1, segments[0].coeff) * t1 * t1;
+ res1 = Polynomial::EvalSegment(t2, segments[1].coeff) * t2 * t2;
+
+ float finalResult = res0 + res1;
+
+ // Add velocity of previous segments
+ finalResult += velocityValue * std::max(t - timeValue, 0.0F);
+
+ return finalResult;
+ }
+
+ // Evaluate integrated Polynomial curve.
+ // Example: position = EvaluateIntegrated (normalizedTime) * startEnergy
+ // Use Integrate function to for example turn a velocity curve into a position curve.
+ // Expects that t is in the 0...1 range.
+ float EvaluateIntegrated (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+ float res0, res1;
+
+ // All segments are added together. At t = 0, the integrated curve is always zero.
+
+ // 0 segment is sampled up to the 1 keyframe
+ // First key is always assumed to be at 0 time
+ float t1 = std::min(t, timeValue);
+
+ // 1 segment is sampled from 1 key to 2 key
+ // Last key is always assumed to be at 1 time
+ float t2 = std::max(0.0F, t - timeValue);
+
+ res0 = Polynomial::EvalSegment(t1, segments[0].coeff) * t1;
+ res1 = Polynomial::EvalSegment(t2, segments[1].coeff) * t2;
+
+ return (res0 + res1);
+ }
+
+ // Evaluate the curve
+ // extects that t is in the 0...1 range
+ float Evaluate (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+
+ float res0 = Polynomial::EvalSegment(t, segments[0].coeff);
+ float res1 = Polynomial::EvalSegment(t - timeValue, segments[1].coeff);
+
+ float result;
+ if (t > timeValue)
+ result = res1;
+ else
+ result = res0;
+
+ return result;
+ }
+
+ // Find the maximum of a double integrated curve (x: min, y: max)
+ Vector2f FindMinMaxDoubleIntegrated() const;
+
+ // Find the maximum of the integrated curve (x: min, y: max)
+ Vector2f FindMinMaxIntegrated() const;
+
+ // Precalculates polynomials from the animation curve and a scale factor
+ bool BuildOptimizedCurve (const AnimationCurve& editorCurve, float scale);
+
+ // Integrates a velocity curve to be a position curve.
+ // You have to call EvaluateIntegrated to evaluate the curve
+ void Integrate ();
+
+ // Integrates a velocity curve to be a position curve.
+ // You have to call EvaluateDoubleIntegrated to evaluate the curve
+ void DoubleIntegrate ();
+
+ // Add a constant force to a velocity curve
+ // Assumes that you have already called Integrate on the velocity curve.
+ void AddConstantForceToVelocityCurve (float gravity)
+ {
+ for (int i=0;i<kSegmentCount;i++)
+ segments[i].coeff[1] += 0.5F * gravity;
+ }
+};
+
+// Bigger, not so optimized version
+struct PolynomialCurve
+{
+ enum{ kMaxNumSegments = 8 };
+
+ Polynomial segments[kMaxNumSegments]; // Cached polynomial coefficients
+ float integrationCache[kMaxNumSegments]; // Cache of integrated values up until start of segments
+ float doubleIntegrationCache[kMaxNumSegments]; // Cache of double integrated values up until start of segments
+ float times[kMaxNumSegments]; // Time value for end of segment
+
+ int segmentCount;
+
+ // Find the maximum of a double integrated curve (x: min, y: max)
+ Vector2f FindMinMaxDoubleIntegrated() const;
+
+ // Find the maximum of the integrated curve (x: min, y: max)
+ Vector2f FindMinMaxIntegrated() const;
+
+ // Precalculates polynomials from the animation curve and a scale factor
+ bool BuildCurve(const AnimationCurve& editorCurve, float scale);
+
+ // Integrates a velocity curve to be a position curve.
+ // You have to call EvaluateIntegrated to evaluate the curve
+ void Integrate ();
+
+ // Integrates a velocity curve to be a position curve.
+ // You have to call EvaluateDoubleIntegrated to evaluate the curve
+ void DoubleIntegrate ();
+
+ // Evaluates if it is possible to represent animation curve as PolynomialCurve
+ static bool IsValidCurve(const AnimationCurve& editorCurve);
+
+ // Evaluate double integrated Polynomial curve.
+ // Example: position = EvaluateDoubleIntegrated (normalizedTime) * startEnergy^2
+ // Use DoubleIntegrate function to for example turn a force curve into a position curve.
+ // Expects that t is in the 0...1 range.
+ float EvaluateDoubleIntegrated (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < segmentCount; i++)
+ {
+ if(t <= times[i])
+ {
+ const float time = t - prevTimeValue;
+ return doubleIntegrationCache[i] + integrationCache[i] * time + Polynomial::EvalSegment(time, segments[i].coeff) * time * time;
+ }
+ prevTimeValue = times[i];
+ }
+ DebugAssert(!"PolyCurve: Outside segment range!");
+ return 1.0f;
+ }
+
+ // Evaluate integrated Polynomial curve.
+ // Example: position = EvaluateIntegrated (normalizedTime) * startEnergy
+ // Use Integrate function to for example turn a velocity curve into a position curve.
+ // Expects that t is in the 0...1 range.
+ float EvaluateIntegrated (float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < segmentCount; i++)
+ {
+ if(t <= times[i])
+ {
+ const float time = t - prevTimeValue;
+ return integrationCache[i] + Polynomial::EvalSegment(time, segments[i].coeff) * time;
+ }
+ prevTimeValue = times[i];
+ }
+ DebugAssert(!"PolyCurve: Outside segment range!");
+ return 1.0f;
+ }
+
+ // Evaluate the curve
+ // extects that t is in the 0...1 range
+ float Evaluate(float t) const
+ {
+ DebugAssert(t >= -0.01F && t <= 1.01F);
+
+ float prevTimeValue = 0.0f;
+ for(int i = 0; i < segmentCount; i++)
+ {
+ if(t <= times[i])
+ return Polynomial::EvalSegment(t - prevTimeValue, segments[i].coeff);
+ prevTimeValue = times[i];
+ }
+ DebugAssert(!"PolyCurve: Outside segment range!");
+ return 1.0f;
+ }
+};
+
+
+void SetPolynomialCurveToValue (AnimationCurve& a, OptimizedPolynomialCurve& c, float value);
+void SetPolynomialCurveToLinear (AnimationCurve& a, OptimizedPolynomialCurve& c);
+void ConstrainToPolynomialCurve (AnimationCurve& curve);
+bool IsValidPolynomialCurve (const AnimationCurve& curve);
+void CalculateMinMax(Vector2f& minmax, float value);
+
+#endif // POLYONOMIAL_CURVE_H
diff --git a/Runtime/Graphics/ParticleSystem/PolynomialCurveTests.cpp b/Runtime/Graphics/ParticleSystem/PolynomialCurveTests.cpp
new file mode 100644
index 0000000..2a74710
--- /dev/null
+++ b/Runtime/Graphics/ParticleSystem/PolynomialCurveTests.cpp
@@ -0,0 +1,259 @@
+#include "UnityPrefix.h"
+#include "Configuration/UnityConfigure.h"
+
+#if ENABLE_UNIT_TESTS
+
+#include "External/UnitTest++/src/UnitTest++.h"
+#include "Runtime/Animation/AnimationCurveUtility.h"
+#include "Runtime/Math/Vector2.h"
+#include "Runtime/Graphics/ParticleSystem/PolynomialCurve.h"
+
+static float PhysicsSimulate (float gravity, float velocity, float time)
+{
+ return velocity * time + gravity * time * time * 0.5F;
+}
+
+SUITE (PolynomialCurveTests)
+{
+TEST (PolynomialCurve_ConstantIntegrate)
+{
+ AnimationCurve editorCurve;
+ OptimizedPolynomialCurve curve;
+ PolynomialCurve polyCurve;
+
+ SetPolynomialCurveToValue(editorCurve, curve, 1.0f);
+ curve.Integrate();
+ polyCurve.BuildCurve(editorCurve, 1.0f);
+ polyCurve.Integrate();
+
+ CHECK_CLOSE(0.0F, curve.EvaluateIntegrated(0.0F), 0.0001F);
+ CHECK_CLOSE(0.5F, curve.EvaluateIntegrated(0.5F), 0.0001F);
+ CHECK_CLOSE(1.0F, curve.EvaluateIntegrated(1.0F), 0.0001F);
+
+ CHECK_CLOSE(0.0F, polyCurve.EvaluateIntegrated(0.0F), 0.0001F);
+ CHECK_CLOSE(0.5F, polyCurve.EvaluateIntegrated(0.5F), 0.0001F);
+ CHECK_CLOSE(1.0F, polyCurve.EvaluateIntegrated(1.0F), 0.0001F);
+
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 1.0f), curve.FindMinMaxIntegrated(), 0.0001F));
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 1.01f), polyCurve.FindMinMaxIntegrated(), 0.0001F));
+
+
+ SetPolynomialCurveToLinear(editorCurve, curve);
+ curve.Integrate();
+ polyCurve.BuildCurve(editorCurve, 1.0f);
+ polyCurve.Integrate();
+
+ CHECK_CLOSE(0.0F, curve.EvaluateIntegrated(0.0F), 0.0001F);
+ CHECK_CLOSE(0.125F, curve.EvaluateIntegrated(0.5F), 0.0001F);
+ CHECK_CLOSE(0.5F, curve.EvaluateIntegrated(1.0F), 0.0001F);
+
+ CHECK_CLOSE(0.0F, polyCurve.EvaluateIntegrated(0.0F), 0.0001F);
+ CHECK_CLOSE(0.125F, polyCurve.EvaluateIntegrated(0.5F), 0.0001F);
+ CHECK_CLOSE(0.5F, polyCurve.EvaluateIntegrated(1.0F), 0.0001F);
+
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), curve.FindMinMaxIntegrated(), 0.0001F));
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.51005f), polyCurve.FindMinMaxIntegrated(), 0.0001F));
+}
+
+// @TODO: Add generic poly
+TEST (PolynomialCurve_ConstantDoubleIntegrate)
+{
+ AnimationCurve editorCurve;
+ OptimizedPolynomialCurve curve;
+ PolynomialCurve polyCurve;
+
+ SetPolynomialCurveToValue(editorCurve, curve, 1.0f);
+ curve.DoubleIntegrate();
+
+ polyCurve.BuildCurve(editorCurve, 1.0f);
+ polyCurve.DoubleIntegrate();
+
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.00F), curve.EvaluateDoubleIntegrated(0.00F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.25F), curve.EvaluateDoubleIntegrated(0.25F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.50F), curve.EvaluateDoubleIntegrated(0.50F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.75F), curve.EvaluateDoubleIntegrated(0.75F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 1.00F), curve.EvaluateDoubleIntegrated(1.00F), 0.0001F);
+
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.00F), polyCurve.EvaluateDoubleIntegrated(0.00F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.25F), polyCurve.EvaluateDoubleIntegrated(0.25F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.50F), polyCurve.EvaluateDoubleIntegrated(0.50F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.75F), polyCurve.EvaluateDoubleIntegrated(0.75F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 1.00F), polyCurve.EvaluateDoubleIntegrated(1.00F), 0.0001F);
+
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), curve.FindMinMaxDoubleIntegrated(), 0.0001F));
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), polyCurve.FindMinMaxDoubleIntegrated(), 0.0001F));
+
+ AnimationCurve::Keyframe keys[3] = { AnimationCurve::Keyframe(0.0f, 1.0f), AnimationCurve::Keyframe(0.5f, 1.0f), AnimationCurve::Keyframe(1.0f, 1.0f) };
+ editorCurve.Assign(keys, keys + 3);
+ curve.BuildOptimizedCurve(editorCurve, 1);
+ curve.DoubleIntegrate();
+
+ polyCurve.BuildCurve(editorCurve, 1.0f);
+ polyCurve.DoubleIntegrate();
+
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.00F), curve.EvaluateDoubleIntegrated(0.00F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.25F), curve.EvaluateDoubleIntegrated(0.25F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.50F), curve.EvaluateDoubleIntegrated(0.50F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.75F), curve.EvaluateDoubleIntegrated(0.75F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 1.00F), curve.EvaluateDoubleIntegrated(1.00F), 0.0001F);
+
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.00F), polyCurve.EvaluateDoubleIntegrated(0.00F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.25F), polyCurve.EvaluateDoubleIntegrated(0.25F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.50F), polyCurve.EvaluateDoubleIntegrated(0.50F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 0.75F), polyCurve.EvaluateDoubleIntegrated(0.75F), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(1, 0, 1.00F), polyCurve.EvaluateDoubleIntegrated(1.00F), 0.0001F);
+
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), curve.FindMinMaxDoubleIntegrated(), 0.0001F));
+ CHECK_EQUAL(true, CompareApproximately(Vector2f(0.0f, 0.5f), polyCurve.FindMinMaxDoubleIntegrated(), 0.0001F));
+}
+
+static float EvaluateGravityIntegrated (OptimizedPolynomialCurve& gravityCurve, float time, float maximumRange)
+{
+ float res = gravityCurve.EvaluateDoubleIntegrated(time / maximumRange) * maximumRange * maximumRange;
+ return res;
+}
+
+TEST (PolynomialCurve_GravityIntegrate)
+{
+ const float kGravity = -9.81f;
+ AnimationCurve editorCurve;
+ OptimizedPolynomialCurve gravityCurve;
+ SetPolynomialCurveToValue(editorCurve, gravityCurve, kGravity);
+ gravityCurve.DoubleIntegrate();
+
+ const float kMaxRange = 5.0F;
+
+ CHECK_CLOSE(PhysicsSimulate(kGravity, 0.0F, 0.0F), EvaluateGravityIntegrated(gravityCurve, 0.0F, kMaxRange), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(kGravity, 0.0F, 0.5F), EvaluateGravityIntegrated(gravityCurve, 0.5F, kMaxRange), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(kGravity, 0.0F, 1.0F), EvaluateGravityIntegrated(gravityCurve, 1.0F, kMaxRange), 0.0001F);
+ CHECK_CLOSE(PhysicsSimulate(kGravity, 0.0F, 5.0F), EvaluateGravityIntegrated(gravityCurve, 5.0F, kMaxRange), 0.0001F);
+}
+
+float TriangleShapeIntegralFirstHalf (float x)
+{
+ return (2.0F / 3.0F) *x*x*x - 0.5F*x*x;
+}
+
+// Test that a simple triangle shape 0,-1 to 0.5,1 to 1,-1
+// Gives expected results during double integration
+TEST (PolynomialCurve_TriangleShapeDoubleIntegrate)
+{
+ OptimizedPolynomialCurve curve;
+ AnimationCurve::Keyframe keys[3] = { AnimationCurve::Keyframe(0.0f, -1.0f), AnimationCurve::Keyframe(0.5f, 1.0f), AnimationCurve::Keyframe(1.0f, -1.0f) };
+ AnimationCurve editorCurve;
+ editorCurve.Assign(keys, keys + 3);
+
+ RecalculateSplineSlopeLinear(editorCurve);
+
+ curve.BuildOptimizedCurve(editorCurve, 1.0F);
+
+ CHECK_CLOSE(-1, curve.Evaluate(0), 0.0001F);
+ CHECK_CLOSE(0, curve.Evaluate(0.25F), 0.0001F);
+ CHECK_CLOSE(1.0F, curve.Evaluate(0.5F), 0.0001F);
+ CHECK_CLOSE(0.0F, curve.Evaluate(0.75F), 0.0001F);
+ CHECK_CLOSE(-1, curve.Evaluate(1), 0.0001F);
+
+ curve.DoubleIntegrate();
+
+ CHECK_CLOSE(TriangleShapeIntegralFirstHalf(0), EvaluateGravityIntegrated(curve, 0.0F , 1.0F), 0.0001F);
+ CHECK_CLOSE(TriangleShapeIntegralFirstHalf(0.25F), EvaluateGravityIntegrated(curve, 0.25F, 1.0F), 0.0001F);
+ CHECK_CLOSE(TriangleShapeIntegralFirstHalf(0.5F), EvaluateGravityIntegrated(curve, 0.5F , 1.0F), 0.0001F);
+ CHECK_CLOSE(-0.0208333, EvaluateGravityIntegrated(curve, 0.75F, 1.0F), 0.0001F);
+ CHECK_CLOSE(0, EvaluateGravityIntegrated(curve, 1.0F , 1.0F), 0.0001F);
+}
+
+void CompareIntegrateCurve (const AnimationCurve& curve, const OptimizedPolynomialCurve& integratedCurve)
+{
+ CHECK_CLOSE(0, integratedCurve.EvaluateIntegrated(0), 0.0001F);
+
+ int intervals = 100;
+
+ float sum = 0.0F;
+ for (int i=1;i<intervals;i++)
+ {
+ float t = (float)i / (float)intervals;
+ float dt = 1.0F / (float)intervals;
+
+ float avgValue = curve.Evaluate(t - dt * 0.5F);
+ sum += avgValue * dt;
+
+ float integratedValue = integratedCurve.EvaluateIntegrated(t);
+ CHECK_CLOSE(sum, integratedValue, 0.0001F);
+ }
+}
+
+void CompareDoubleIntegrateCurve (const AnimationCurve& curve, const OptimizedPolynomialCurve& integratedCurve)
+{
+ CHECK_CLOSE(0, integratedCurve.EvaluateIntegrated(0), 0.0001F);
+
+ int intervals = 1000;
+
+ float integratedSum = 0.0F;
+ float sum = 0.0F;
+ for (int i=1;i<intervals;i++)
+ {
+ float t = (float)i / (float)intervals;
+ float dt = 1.0F / (float)intervals;
+
+ float avgValue = curve.Evaluate(t - dt * 0.5F);
+ sum += avgValue * dt;
+ integratedSum += sum * dt;
+
+ float integratedValue = integratedCurve.EvaluateDoubleIntegrated(t);
+ CHECK_CLOSE(integratedSum, integratedValue, 0.001F);
+ }
+}
+
+void CompareIntegrateCurve (const AnimationCurve::Keyframe* keys, int size)
+{
+ AnimationCurve sourceCurve;
+ sourceCurve.Assign(keys, keys + size);
+
+ OptimizedPolynomialCurve curve;
+ AnimationCurve editorCurve;
+ editorCurve = sourceCurve;
+ curve.BuildOptimizedCurve(editorCurve, 1);
+ curve.Integrate();
+
+ CompareIntegrateCurve(sourceCurve, curve);
+}
+
+void CompareDoubleIntegrateCurve (const AnimationCurve::Keyframe* keys, int size)
+{
+ AnimationCurve sourceCurve;
+ sourceCurve.Assign(keys, keys + size);
+
+ OptimizedPolynomialCurve curve;
+ AnimationCurve editorCurve;
+ editorCurve = sourceCurve;
+ curve.BuildOptimizedCurve(editorCurve, 1);
+ curve.DoubleIntegrate();
+
+ CompareDoubleIntegrateCurve(sourceCurve, curve);
+}
+
+TEST (PolynomialCurve_TriangleCurve)
+{
+ AnimationCurve sourceCurve;
+ AnimationCurve::Keyframe keys[3] = { AnimationCurve::Keyframe(0.0f, 0.0f), AnimationCurve::Keyframe(0.5f, 1.0f), AnimationCurve::Keyframe(1.0f, 0.0f) };
+ sourceCurve.Assign(keys, keys + 3);
+ RecalculateSplineSlopeLinear(sourceCurve);
+
+ CompareIntegrateCurve(&sourceCurve.GetKey(0), sourceCurve.GetKeyCount());
+ CompareDoubleIntegrateCurve(&sourceCurve.GetKey(0), sourceCurve.GetKeyCount());
+}
+
+
+TEST (PolynomialCurve_LineCurve)
+{
+ AnimationCurve sourceCurve;
+ AnimationCurve::Keyframe keys[2] = { AnimationCurve::Keyframe(0.0f, 0.0f), AnimationCurve::Keyframe(1.0f, 1.0f) };
+ sourceCurve.Assign(keys, keys + 2);
+ RecalculateSplineSlopeLinear(sourceCurve);
+
+ CompareIntegrateCurve(&sourceCurve.GetKey(0), sourceCurve.GetKeyCount());
+ CompareDoubleIntegrateCurve(&sourceCurve.GetKey(0), sourceCurve.GetKeyCount());
+}
+}
+
+#endif