summaryrefslogtreecommitdiff
path: root/Runtime/Graphics/ParticleSystem/Modules
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Graphics/ParticleSystem/Modules')
-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
34 files changed, 3651 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