diff options
Diffstat (limited to 'Runtime/Graphics/ParticleSystem/Modules')
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 |