diff options
Diffstat (limited to 'Runtime/Filters/Particles')
-rw-r--r-- | Runtime/Filters/Particles/EllipsoidParticleEmitter.cpp | 110 | ||||
-rw-r--r-- | Runtime/Filters/Particles/EllipsoidParticleEmitter.h | 35 | ||||
-rw-r--r-- | Runtime/Filters/Particles/MeshParticleEmitter.cpp | 353 | ||||
-rw-r--r-- | Runtime/Filters/Particles/MeshParticleEmitter.h | 42 | ||||
-rw-r--r-- | Runtime/Filters/Particles/ParticleAnimator.cpp | 216 | ||||
-rw-r--r-- | Runtime/Filters/Particles/ParticleAnimator.h | 57 | ||||
-rw-r--r-- | Runtime/Filters/Particles/ParticleEmitter.cpp | 455 | ||||
-rw-r--r-- | Runtime/Filters/Particles/ParticleEmitter.h | 151 | ||||
-rw-r--r-- | Runtime/Filters/Particles/ParticleRenderer.cpp | 630 | ||||
-rw-r--r-- | Runtime/Filters/Particles/ParticleRenderer.h | 103 | ||||
-rw-r--r-- | Runtime/Filters/Particles/ParticleStruct.h | 59 | ||||
-rw-r--r-- | Runtime/Filters/Particles/WorldParticleCollider.cpp | 197 | ||||
-rw-r--r-- | Runtime/Filters/Particles/WorldParticleCollider.h | 33 |
13 files changed, 2441 insertions, 0 deletions
diff --git a/Runtime/Filters/Particles/EllipsoidParticleEmitter.cpp b/Runtime/Filters/Particles/EllipsoidParticleEmitter.cpp new file mode 100644 index 0000000..0401fa2 --- /dev/null +++ b/Runtime/Filters/Particles/EllipsoidParticleEmitter.cpp @@ -0,0 +1,110 @@ +#include "UnityPrefix.h" +#include "Runtime/Core/Callbacks/GlobalCallbacks.h" +#include "EllipsoidParticleEmitter.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Math/Random/Random.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" + +using namespace std; + +Rand gEllipsoidEmitterRand (3); + +EllipsoidParticleEmitter::EllipsoidParticleEmitter (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_Ellipsoid = Vector3f (1, 1, 1); + m_MinEmitterRange = 0.0F; +} + +EllipsoidParticleEmitter::~EllipsoidParticleEmitter () +{ +} + +static void ResetEllipsoidEmitterRand () +{ + gEllipsoidEmitterRand.SetSeed (3); +} + +void EllipsoidParticleEmitter::InitializeClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Register(ResetEllipsoidEmitterRand); +} + +void EllipsoidParticleEmitter::CleanupClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Unregister(ResetEllipsoidEmitterRand); +} + + +void EllipsoidParticleEmitter::SetupParticles ( + ParticleArray& particles, + const Vector3f& velocityOffset, + const Matrix3x3f& rotation, + int firstIndex) +{ + float deltaTime = GetDeltaTime (); + MinMaxAABB& aabb = m_PrivateInfo.aabb; + for (int i = firstIndex;i<particles.size ();i++) + { + SetupParticle (particles[i], velocityOffset, rotation, deltaTime); + aabb.Encapsulate (particles[i].position); + } +} + +void EllipsoidParticleEmitter::SetupParticle ( + Particle& p, + const Vector3f& velocityOffset, + const Matrix3x3f& rotation, + float deltaTime) +{ + InitParticleEnergy(gEllipsoidEmitterRand, p, deltaTime); + + // Set particle starting position + p.position = m_PreviousEmitterPos; + p.position += velocityOffset * RangedRandom (gEllipsoidEmitterRand, 0.0F, deltaTime); + p.position += (m_EmitterPos - m_PreviousEmitterPos) * Random01 (gEllipsoidEmitterRand); + Vector3f insideEllipsoidPosition = RandomPointBetweenEllipsoid (gEllipsoidEmitterRand, m_Ellipsoid, m_MinEmitterRange); + p.position += rotation.MultiplyVector3 (insideEllipsoidPosition); + + // Set velocity + p.velocity = velocityOffset; + p.velocity += rotation.MultiplyVector3 (RandomPointInsideEllipsoid (gEllipsoidEmitterRand, m_RndVelocity)); + + p.rotation = m_RndInitialRotations ? RangedRandom (gEllipsoidEmitterRand, 0.0f, 2*kPI):0.0F; + float angularVelocity = m_AngularVelocity; +#if SUPPORT_REPRODUCE_LOG + if (m_RndAngularVelocity > Vector3f::epsilon) +#endif + angularVelocity += RangedRandom (gEllipsoidEmitterRand, -m_RndAngularVelocity, m_RndAngularVelocity); + p.angularVelocity = Deg2Rad(angularVelocity); + +// Vector3f ellipsoidRelativeDirection = NormalizeSafe (insideEllipsoidPosition); +// p.velocity += rotation.MultiplyVector3 (Cross (ellipsoidRelativeDirection, info.tangentVelocity)); + + Vector3f uTangent (insideEllipsoidPosition.z, 0.0F, -insideEllipsoidPosition.x); + Vector3f vTangent (insideEllipsoidPosition.x, 0.0F, -insideEllipsoidPosition.y); + uTangent = NormalizeSafe (uTangent); + vTangent = NormalizeSafe (vTangent); + Vector3f normal = NormalizeSafe (insideEllipsoidPosition); + p.velocity += rotation.MultiplyVector3 (uTangent * m_TangentVelocity.x); + p.velocity += rotation.MultiplyVector3 (vTangent * m_TangentVelocity.y); + p.velocity += rotation.MultiplyVector3 (normal * m_TangentVelocity.z); + + p.color = ColorRGBA32 (255, 255, 255, 255); + + // Set size + p.size = RangedRandom (gEllipsoidEmitterRand, m_MinSize, m_MaxSize); +} + +IMPLEMENT_CLASS_HAS_INIT (EllipsoidParticleEmitter) +IMPLEMENT_OBJECT_SERIALIZE (EllipsoidParticleEmitter) + +template<class TransferFunction> inline +void EllipsoidParticleEmitter::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.Align(); + TRANSFER_SIMPLE (m_Ellipsoid); + TRANSFER (m_MinEmitterRange); +} diff --git a/Runtime/Filters/Particles/EllipsoidParticleEmitter.h b/Runtime/Filters/Particles/EllipsoidParticleEmitter.h new file mode 100644 index 0000000..8a93dda --- /dev/null +++ b/Runtime/Filters/Particles/EllipsoidParticleEmitter.h @@ -0,0 +1,35 @@ +#ifndef ELLIPSOIDPARTICLEEMITTER_H +#define ELLIPSOIDPARTICLEEMITTER_H + +#include "ParticleEmitter.h" +#include "Runtime/Math/Vector3.h" +#include "ParticleStruct.h" + +class Matrix4x4f; + + + +class EllipsoidParticleEmitter : public ParticleEmitter +{ +public: + REGISTER_DERIVED_CLASS (EllipsoidParticleEmitter, ParticleEmitter) + DECLARE_OBJECT_SERIALIZE (EllipsoidParticleEmitter) + EllipsoidParticleEmitter (MemLabelId label, ObjectCreationMode mode); + + static void InitializeClass (); + static void CleanupClass (); + +private: + + void SetupParticle (Particle& p, const Vector3f& velocityOffset, const Matrix3x3f& rotation, float deltaTime); + virtual void SetupParticles (ParticleArray& particles, const Vector3f& velocityOffset, + const Matrix3x3f& rotation, int firstIndex); + +public: + Vector3f m_Ellipsoid; ///< Size of emission area + float m_MinEmitterRange;///< [0...1] relative range to maxEmitterSize where particles will not be spawned + // 0 means that a full ellipsoid will be filled with particles + // 1 means only the outline will be filled with particles +}; + +#endif diff --git a/Runtime/Filters/Particles/MeshParticleEmitter.cpp b/Runtime/Filters/Particles/MeshParticleEmitter.cpp new file mode 100644 index 0000000..16ea50c --- /dev/null +++ b/Runtime/Filters/Particles/MeshParticleEmitter.cpp @@ -0,0 +1,353 @@ +#include "UnityPrefix.h" +#include "MeshParticleEmitter.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Math/Random/Random.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Filters/Mesh/MeshUtility.h" +#include "Runtime/Core/Callbacks/GlobalCallbacks.h" + +using namespace std; + +static Rand gMeshEmitterRand (4); + +MeshParticleEmitter::MeshParticleEmitter (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_VertexIndex = false; +} + +MeshParticleEmitter::~MeshParticleEmitter () +{ +} + +void MeshParticleEmitter::Reset () +{ + Super::Reset(); + m_InterpolateTriangles = false; + m_Systematic = false; + m_MinNormalVelocity = 0.0F; + m_MaxNormalVelocity = 0.0F; +} + +void MeshParticleEmitter::SetupParticles ( + ParticleArray& particles, + const Vector3f& velocityOffset, + const Matrix3x3f& rotation, + int firstIndex) +{ + Mesh* mesh = m_Mesh; + + MinMaxAABB& aabb = m_PrivateInfo.aabb; + + Matrix4x4f scale; + GetComponent (Transform).CalculateTransformMatrixScaleDelta (scale); + + Matrix3x3f rotationAndScale = Matrix3x3f(scale); + rotationAndScale = rotation * rotationAndScale; + rotationAndScale.InvertTranspose (); + + float deltaTime = GetDeltaTime (); + + // If there's an invalid mesh, then just emit from the center + if (mesh == NULL || mesh->GetSubMeshCount () == 0 || mesh->GetSubMeshFast (0).indexCount == 0 || !mesh->HasVertexData()) + { + Vector3f v; + StrideIterator<Vector3f> vertices(&v, 0); + StrideIterator<Vector3f> normals; + + for (int i = firstIndex;i<particles.size ();i++) + { + SetupParticle (0, particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals); + aabb.Encapsulate (particles[i].position); + } + return; + } + + SubMesh& submesh = mesh->GetSubMeshFast (0); + int vertexCount = mesh->GetVertexCount(); + + StrideIterator<Vector3f> vertices = mesh->GetVertexBegin(); + StrideIterator<Vector3f> normals = mesh->GetNormalBegin(); + const UInt16* buffer = mesh->GetSubMeshBuffer16(0); + + if (!m_Systematic) + { + if (m_InterpolateTriangles) + { + if (submesh.topology == kPrimitiveTriangleStripDeprecated) + { + for (int i = firstIndex;i<particles.size ();i++) + { + SetupParticleStrip (particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals, buffer, submesh.indexCount); + aabb.Encapsulate (particles[i].position); + } + } + else if (submesh.topology == kPrimitiveTriangles) + { + for (int i = firstIndex;i<particles.size ();i++) + { + SetupParticleTri (particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals, buffer, submesh.indexCount/3); + aabb.Encapsulate (particles[i].position); + } + } + } + else { + if (vertexCount != 0) + { + for (int i = firstIndex;i<particles.size ();i++) { + SetupParticle (RangedRandom (gMeshEmitterRand, 0, vertexCount), particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals); + aabb.Encapsulate (particles[i].position); + } + } + } + } + else { + if (vertexCount != 0) + { + // Just in case the mesh changed while the particle emitter was running + if (m_VertexIndex >= vertexCount) + m_VertexIndex = 0; + + for (int i = firstIndex;i<particles.size ();i++) { + SetupParticle (m_VertexIndex, particles[i], velocityOffset, scale, rotation, rotationAndScale, deltaTime, vertices, normals); + aabb.Encapsulate (particles[i].position); + + m_VertexIndex++; + if (m_VertexIndex >= vertexCount) + m_VertexIndex = 0; + } + } + } +} + +void MeshParticleEmitter::SetupParticle ( + int vertexIndex, + Particle& p, + const Vector3f& velocityOffset, + const Matrix4x4f& scale, + const Matrix3x3f& rotation, + const Matrix3x3f& normalTransform, + float deltaTime, + StrideIterator<Vector3f> vertices, + StrideIterator<Vector3f> normals) +{ + InitParticleEnergy(gMeshEmitterRand, p, deltaTime); + + // position/normal of particle is vertex/vertex normal from mesh + Vector3f positionOnMesh = vertices[vertexIndex]; + positionOnMesh = scale.MultiplyPoint3 (positionOnMesh); + + Vector3f normal; + if (!normals.IsNull ()) + { + normal = normalTransform.MultiplyVector3 (normals[vertexIndex]); + normal = NormalizeFast (normal); + } + else + normal = Vector3f(0,0,0); + + + // Set particle starting position + p.position = m_PreviousEmitterPos; + p.position += velocityOffset * RangedRandom (gMeshEmitterRand, 0.0F, deltaTime); + p.position += (m_EmitterPos - m_PreviousEmitterPos) * Random01 (gMeshEmitterRand); + p.position += rotation.MultiplyVector3 (positionOnMesh); + + // Set velocity + p.velocity = velocityOffset + normal * RangedRandom (gMeshEmitterRand, m_MinNormalVelocity, m_MaxNormalVelocity); + p.velocity += rotation.MultiplyVector3 (RandomPointInsideEllipsoid (gMeshEmitterRand, m_RndVelocity)); + + p.rotation = m_RndInitialRotations ? RangedRandom (gMeshEmitterRand, 0.0f, 2*kPI):0.0F; + float angularVelocity = m_AngularVelocity; +#if SUPPORT_REPRODUCE_LOG + if (m_RndAngularVelocity > Vector3f::epsilon) +#endif + angularVelocity += RangedRandom (gMeshEmitterRand, -m_RndAngularVelocity, m_RndAngularVelocity); + p.angularVelocity = Deg2Rad(angularVelocity); + + p.color = ColorRGBA32 (255, 255, 255, 255); + + // Set size + p.size = RangedRandom (gMeshEmitterRand, m_MinSize, m_MaxSize); +} + +void MeshParticleEmitter::SetupParticleTri ( + Particle& p, + const Vector3f& velocityOffset, + const Matrix4x4f& scale, + const Matrix3x3f& rotation, + const Matrix3x3f& normalTransform, + float deltaTime, + StrideIterator<Vector3f> vertices, + StrideIterator<Vector3f> normals, + const UInt16* indices, + int triCount) +{ + InitParticleEnergy(gMeshEmitterRand, p, deltaTime); + + int triIndex = RangedRandom (gMeshEmitterRand, 0, (int)triCount); + const UInt16* face = indices + triIndex * 3; + Vector3f barycenter = RandomBarycentricCoord (gMeshEmitterRand); + + // Interpolate vertex with barycentric coordinate + Vector3f positionOnMesh = barycenter.x * vertices[face[0]] + barycenter.y * vertices[face[1]] + barycenter.z * vertices[face[2]]; + positionOnMesh = scale.MultiplyPoint3 (positionOnMesh); + + Vector3f normal; + if (!normals.IsNull ()) + { + // Interpolate normal with barycentric coordinate + Vector3f const& normal1 = normals[face[0]]; + Vector3f const& normal2 = normals[face[1]]; + Vector3f const& normal3 = normals[face[2]]; + normal = barycenter.x * normal1 + barycenter.y * normal2 + barycenter.z * normal3; + normal = normalTransform.MultiplyVector3 (normal); + normal = NormalizeFast (normal); + } + else + normal = Vector3f(0,0,0); + + // Set particle starting position + p.position = m_PreviousEmitterPos; + p.position += velocityOffset * RangedRandom (gMeshEmitterRand, 0.0F, deltaTime); + p.position += (m_EmitterPos - m_PreviousEmitterPos) * Random01 (gMeshEmitterRand); + p.position += rotation.MultiplyVector3 (positionOnMesh); + + // Set velocity + p.velocity = velocityOffset + normal * RangedRandom (gMeshEmitterRand, m_MinNormalVelocity, m_MaxNormalVelocity); + p.velocity += rotation.MultiplyVector3 (RandomPointInsideEllipsoid (gMeshEmitterRand, m_RndVelocity)); + + p.rotation = m_RndInitialRotations ? RangedRandom (gMeshEmitterRand, 0.0f, 2*kPI):0.0F; + float angularVelocity = m_AngularVelocity; +#if SUPPORT_REPRODUCE_LOG + if (m_RndAngularVelocity > Vector3f::epsilon) +#endif + angularVelocity += RangedRandom (gMeshEmitterRand, -m_RndAngularVelocity, m_RndAngularVelocity); + p.angularVelocity = Deg2Rad(angularVelocity); + + p.color = ColorRGBA32 (255, 255, 255, 255); + + // Set size + p.size = RangedRandom (gMeshEmitterRand, m_MinSize, m_MaxSize); +} + +void MeshParticleEmitter::SetupParticleStrip ( + Particle& p, + const Vector3f& velocityOffset, + const Matrix4x4f& scale, + const Matrix3x3f& rotation, + const Matrix3x3f& normalTransform, + float deltaTime, + StrideIterator<Vector3f> vertices, + StrideIterator<Vector3f> normals, + const UInt16* strip, + int stripSize) +{ + InitParticleEnergy(gMeshEmitterRand, p, deltaTime); + + // Extract indices from tristrip + int stripIndex = RangedRandom (gMeshEmitterRand, 2, stripSize); + UInt16 a = strip[stripIndex-2]; + UInt16 b = strip[stripIndex-1]; + UInt16 c = strip[stripIndex]; + // Ignore degenerate triangles + while (a == b || a == c || b == c) + { + stripIndex = RangedRandom (gMeshEmitterRand, 2, stripSize); + a = strip[stripIndex-2]; + b = strip[stripIndex-1]; + c = strip[stripIndex]; + while (a == b || a == c || b == c) + { + stripIndex++; + if (stripIndex >= stripSize) + break; + a = strip[stripIndex-2]; + b = strip[stripIndex-1]; + c = strip[stripIndex]; + } + } + + Vector3f barycenter = RandomBarycentricCoord (gMeshEmitterRand); + + // Interpolate vertex with barycentric coordinate + Vector3f positionOnMesh = barycenter.x * vertices[a] + barycenter.y * vertices[b] + barycenter.z * vertices[c]; + positionOnMesh = scale.MultiplyPoint3 (positionOnMesh); + + + Vector3f normal; + if (!normals.IsNull ()) + { + // Interpolate normal with barycentric coordinate + Vector3f normal1 = normals[a]; + Vector3f normal2 = normals[b]; + Vector3f normal3 = normals[c]; + normal = barycenter.x * normal1 + barycenter.y * normal2 + barycenter.z * normal3; + normal = normalTransform.MultiplyVector3 (normal); + normal = NormalizeFast (normal); + } + else + { + normal = Vector3f(0,0,0); + } + + // Set particle starting position + p.position = m_PreviousEmitterPos; + p.position += velocityOffset * RangedRandom (gMeshEmitterRand, 0.0F, deltaTime); + p.position += (m_EmitterPos - m_PreviousEmitterPos) * Random01 (gMeshEmitterRand); + p.position += rotation.MultiplyVector3 (positionOnMesh); + + // Set velocity + p.velocity = velocityOffset + normal * RangedRandom (gMeshEmitterRand, m_MinNormalVelocity, m_MaxNormalVelocity); + p.velocity += rotation.MultiplyVector3 (RandomPointInsideEllipsoid (gMeshEmitterRand, m_RndVelocity)); + + p.rotation = m_RndInitialRotations ? RangedRandom (gMeshEmitterRand, 0.0f, 2*kPI):0.0F; + float angularVelocity = m_AngularVelocity; +#if SUPPORT_REPRODUCE_LOG + if (m_RndAngularVelocity > Vector3f::epsilon) +#endif + angularVelocity += RangedRandom (gMeshEmitterRand, -m_RndAngularVelocity, m_RndAngularVelocity); + p.angularVelocity = Deg2Rad(angularVelocity); + + p.color = ColorRGBA32 (255, 255, 255, 255); + + // Set size + p.size = RangedRandom (gMeshEmitterRand, m_MinSize, m_MaxSize); +} + +static void ResetRandSeedForMeshParticleEmitter () +{ + gMeshEmitterRand.SetSeed (4); +} + +void MeshParticleEmitter::InitializeClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Register(ResetRandSeedForMeshParticleEmitter); +} + +void MeshParticleEmitter::CleanupClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Unregister(ResetRandSeedForMeshParticleEmitter); +} + +IMPLEMENT_CLASS_HAS_INIT (MeshParticleEmitter) +IMPLEMENT_OBJECT_SERIALIZE (MeshParticleEmitter) + +template<class TransferFunction> inline +void MeshParticleEmitter::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + TRANSFER (m_InterpolateTriangles); + TRANSFER (m_Systematic); + transfer.Align(); + TRANSFER (m_MinNormalVelocity); + TRANSFER (m_MaxNormalVelocity); + TRANSFER (m_Mesh); +} + +void MeshParticleEmitter::SetMesh (PPtr<Mesh> mesh) +{ + m_Mesh = mesh; + SetDirty(); +} diff --git a/Runtime/Filters/Particles/MeshParticleEmitter.h b/Runtime/Filters/Particles/MeshParticleEmitter.h new file mode 100644 index 0000000..6ac74c1 --- /dev/null +++ b/Runtime/Filters/Particles/MeshParticleEmitter.h @@ -0,0 +1,42 @@ +#ifndef MESHPARTICLEEMITTER_H +#define MESHPARTICLEEMITTER_H + +#include "ParticleEmitter.h" +#include "ParticleStruct.h" +#include "Runtime/Filters/Mesh/LodMesh.h" + + + +class MeshParticleEmitter : public ParticleEmitter +{ +public: + REGISTER_DERIVED_CLASS (MeshParticleEmitter, ParticleEmitter) + DECLARE_OBJECT_SERIALIZE (MeshParticleEmitter) + MeshParticleEmitter (MemLabelId label, ObjectCreationMode mode); + + void SetMesh (PPtr<Mesh> mesh); + PPtr<Mesh> GetMesh () { return m_Mesh; } + + virtual void Reset (); + + static void InitializeClass (); + static void CleanupClass (); + +private: + void SetupParticle (int vertexIndex, Particle& p, const Vector3f& velocityOffset, const Matrix4x4f& scale, const Matrix3x3f& rotation, const Matrix3x3f& normalTransform, float deltaTime, StrideIterator<Vector3f> vertices, StrideIterator<Vector3f> normals); + void SetupParticleTri (Particle& p, const Vector3f& velocityOffset, const Matrix4x4f& scale, const Matrix3x3f& rotation, const Matrix3x3f& normalTransform, float deltaTime, StrideIterator<Vector3f> vertices, StrideIterator<Vector3f> normals, const UInt16* faces, int triCount); + void SetupParticleStrip (Particle& p, const Vector3f& velocityOffset, const Matrix4x4f& scale, const Matrix3x3f& rotation, const Matrix3x3f& normalTransform, float deltaTime, StrideIterator<Vector3f> vertices, StrideIterator<Vector3f> normals, const UInt16* strip, int stripSize); + + virtual void SetupParticles (ParticleArray& particles, const Vector3f& velocityOffset, + const Matrix3x3f& rotation, int firstIndex); + +private: + bool m_InterpolateTriangles; + bool m_Systematic; + float m_MinNormalVelocity; + float m_MaxNormalVelocity; + int m_VertexIndex; + PPtr<Mesh> m_Mesh; +}; + +#endif diff --git a/Runtime/Filters/Particles/ParticleAnimator.cpp b/Runtime/Filters/Particles/ParticleAnimator.cpp new file mode 100644 index 0000000..95ceb3a --- /dev/null +++ b/Runtime/Filters/Particles/ParticleAnimator.cpp @@ -0,0 +1,216 @@ +#include "UnityPrefix.h" +#include "ParticleAnimator.h" +#include "Runtime/Math/Random/Random.h" +#include "Runtime/Input/TimeManager.h" +#include "ParticleStruct.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/GameCode/DestroyDelayed.h" +#include "Runtime/BaseClasses/IsPlaying.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Core/Callbacks/GlobalCallbacks.h" + +static Rand gParticleAnimRand (2); + +void ParticleAnimator::GetColorAnimation(ColorRGBAf *col) const +{ + for(int i=0;i<kColorKeys;i++) + col[i] = m_ColorAnimation[i]; +} + +void ParticleAnimator::SetColorAnimation(ColorRGBAf *col) +{ + for(int i=0;i<kColorKeys;i++) + m_ColorAnimation[i] = col[i]; +} + +void ParticleAnimator::UpdateAnimator( ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime ) +{ + // Quit updating if no new emissions and no old particles + if( particles.empty() ) + { + m_EnergylossFraction = 0.0F; + + if( WillAutoDestructIfNoParticles( privateInfo ) ) + DestroyObjectDelayed (GetGameObjectPtr()); + return; + } + if( m_Autodestruct == 1 ) + m_Autodestruct = 2; + + if( !m_StopSimulation ) + { + privateInfo.aabb.Init (); + UpdateParticles( particles, privateInfo, deltaTime ); + + //Particle size is multipled to calculate the maximum size + //Handle cases where grow size is negative also + float endSize = privateInfo.maxEmitterParticleSize * pow (1.0F + m_SizeGrow, privateInfo.maxEnergy); + privateInfo.maxParticleSize = m_SizeGrow < 0.0f ? privateInfo.maxEmitterParticleSize : endSize; + } +} + +bool ParticleAnimator::WillAutoDestructIfNoParticles( const PrivateParticleInfo& privateInfo ) const +{ + // Autodestruct destroys the GO if enabled AND either of those: + // * Particles have been emitted, but are all dead now. + // * Emit was once On, but now is Off. + if( m_Autodestruct != 0 && IsWorldPlaying() ) + { + if( m_Autodestruct == 2 || (privateInfo.hadEverEmitOn && !privateInfo.isEmitOn) ) + return true; + } + return false; +} + + +void ParticleAnimator::UpdateParticles (ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime) const +{ + Vector3f dtForce = m_Force * deltaTime; + Vector3f rotationAxis; + if (privateInfo.useWorldSpace) + rotationAxis = (m_WorldRotationAxis + GetComponent(Transform).TransformDirection(m_LocalRotationAxis)) * deltaTime; + else + rotationAxis = (GetComponent(Transform).InverseTransformDirection(m_WorldRotationAxis) + m_LocalRotationAxis) * deltaTime; + + float damping = pow (m_Damping, deltaTime); + Vector3f rndForce = m_RndForce * deltaTime; + + + const float kColorScale = kColorKeys - 1.0f; + + // The color keys are inverted + ColorRGBA32 colorAnimation[kColorKeys] = { m_ColorAnimation[4], m_ColorAnimation[3], m_ColorAnimation[2], m_ColorAnimation[1], m_ColorAnimation[0] }; + float sizeScale = pow (1.0F + m_SizeGrow, deltaTime); + float maxColorAnimationT = (float)kColorKeys - 1.0F - Vector3f::epsilon; + + bool doesAnimateColor = m_DoesAnimateColor; + int particleSize = particles.size (); + int i = 0; + while (i < particleSize) + { + Particle& p = particles[i]; + + // Update Alpha + p.energy -= deltaTime; + + if (p.energy <= 0.0f) { + // Kill particle (replace particle i with last, and continue updating the moved particle) + KillParticle (particles, i); + --particleSize; + continue; + } + + if( doesAnimateColor ) + { + // [0..kColorKeys-1] for [0..maxEnergy] + float t = p.energy * kColorScale / p.startEnergy; + t = FloatMin (t, maxColorAnimationT); + int baseColor = FloorfToIntPos (t); + DebugAssertIf( baseColor < 0 || baseColor >= kColorKeys - 1 ); + float frac = t - (float)baseColor; + int intFrac = RoundfToIntPos (frac * 255.0F); + p.color = Lerp (colorAnimation[baseColor], colorAnimation[baseColor + 1], intFrac); + } + + // Update Velocity + p.velocity *= damping; + + p.velocity += dtForce; + + p.velocity += RandomPointInsideCube (gParticleAnimRand, rndForce); + + p.velocity += Cross (rotationAxis, p.velocity); + + // Update position + p.position += p.velocity * deltaTime; + + // Update Size + p.size *= sizeScale; + + // Update Bounding Box + privateInfo.aabb.Encapsulate (p.position); + + p.rotation += p.angularVelocity * deltaTime; + + Prefetch(&particles[i] + 8); + Prefetch(&particles[particleSize-1]); + + i++; + } +} + +ParticleAnimator::ParticleAnimator(MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ + m_ColorAnimation[0] = ColorRGBA32 (255, 255, 255, 10); + m_ColorAnimation[1] = ColorRGBA32 (255, 255, 255, 180); + m_ColorAnimation[2] = ColorRGBA32 (255, 255, 255, 255); + m_ColorAnimation[3] = ColorRGBA32 (255, 255, 255, 180); + m_ColorAnimation[4] = ColorRGBA32 (255, 255, 255, 10); + + m_WorldRotationAxis = Vector3f::zero; + m_LocalRotationAxis = Vector3f::zero; + m_DoesAnimateColor = true; + m_RndForce = Vector3f::zero; + m_Force = Vector3f::zero; + m_SizeGrow = 0.0f; + m_Damping = 1.0f; + m_Autodestruct = 0; + m_StopSimulation = false; + m_EnergylossFraction = 0.0F; +} + +ParticleAnimator::~ParticleAnimator () +{ +} + +IMPLEMENT_CLASS_HAS_INIT (ParticleAnimator) +IMPLEMENT_OBJECT_SERIALIZE (ParticleAnimator) + +static void ResetRandSeed () +{ + gParticleAnimRand.SetSeed (2); +} + +void ParticleAnimator::InitializeClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Register(ResetRandSeed); +} + +void ParticleAnimator::CleanupClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Unregister(ResetRandSeed); +} + +template<class TransferFunction> inline +void ParticleAnimator::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + + transfer.Transfer (m_DoesAnimateColor, "Does Animate Color?"); + transfer.Align(); + transfer.Transfer( m_ColorAnimation[0], "colorAnimation[0]", kSimpleEditorMask ); + transfer.Transfer( m_ColorAnimation[1], "colorAnimation[1]", kSimpleEditorMask ); + transfer.Transfer( m_ColorAnimation[2], "colorAnimation[2]", kSimpleEditorMask ); + transfer.Transfer( m_ColorAnimation[3], "colorAnimation[3]", kSimpleEditorMask ); + transfer.Transfer( m_ColorAnimation[4], "colorAnimation[4]", kSimpleEditorMask ); + DebugAssertIf (kColorKeys != 5); + + transfer.Transfer( m_WorldRotationAxis, "worldRotationAxis" ); + transfer.Transfer( m_LocalRotationAxis, "localRotationAxis" ); + transfer.Transfer( m_SizeGrow, "sizeGrow", kSimpleEditorMask ); + transfer.Transfer( m_RndForce, "rndForce", kSimpleEditorMask ); + transfer.Transfer( m_Force, "force", kSimpleEditorMask ); + transfer.Transfer( m_Damping, "damping", kSimpleEditorMask ); m_Damping = clamp<float> (m_Damping, 0.0f, 1.0f); + transfer.Transfer( m_StopSimulation, "stopSimulation", kHideInEditorMask ); + + bool autodestruct = m_Autodestruct; + transfer.Transfer (autodestruct, "autodestruct"); + if (transfer.IsReading ()) + { + if (autodestruct == 0) + m_Autodestruct = 0; + else if (m_Autodestruct == 0) + m_Autodestruct = 1; + } +} diff --git a/Runtime/Filters/Particles/ParticleAnimator.h b/Runtime/Filters/Particles/ParticleAnimator.h new file mode 100644 index 0000000..dde7b9d --- /dev/null +++ b/Runtime/Filters/Particles/ParticleAnimator.h @@ -0,0 +1,57 @@ +#ifndef PARTICLEANIMATOR_H +#define PARTICLEANIMATOR_H + +#include "Runtime/BaseClasses/GameObject.h" +#include "Runtime/Math/Color.h" +#include "ParticleStruct.h" + + + +class ParticleAnimator : public Unity::Component +{ +public: + REGISTER_DERIVED_CLASS (ParticleAnimator, Unity::Component) + DECLARE_OBJECT_SERIALIZE (ParticleAnimator) + + ParticleAnimator(MemLabelId label, ObjectCreationMode mode); + + static void InitializeClass (); + static void CleanupClass (); + + enum { kColorKeys = 5 }; + + GET_SET_DIRTY (Vector3f, WorldRotationAxis, m_WorldRotationAxis) + GET_SET_DIRTY (Vector3f, LocalRotationAxis, m_LocalRotationAxis) + GET_SET_DIRTY (Vector3f, RndForce, m_RndForce) + GET_SET_DIRTY (Vector3f, Force, m_Force) + GET_SET_DIRTY (float, Damping, m_Damping) + GET_SET_DIRTY (float, SizeGrow, m_SizeGrow) + GET_SET_DIRTY (bool, Autodestruct, m_Autodestruct) + GET_SET_DIRTY (bool, DoesAnimateColor, m_DoesAnimateColor) + GET_SET_DIRTY (bool, stopSimulation, m_StopSimulation) + + void GetColorAnimation(ColorRGBAf *col) const; + void SetColorAnimation(ColorRGBAf *col); + + void UpdateAnimator( ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime ); + + bool WillAutoDestructIfNoParticles( const PrivateParticleInfo& privateInfo ) const; + +private: + void UpdateParticles (ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime) const; + +private: + Vector3f m_WorldRotationAxis; // axis around which the particle rotates, worldspace + Vector3f m_LocalRotationAxis; // axis around which the particle rotates, localspace + Vector3f m_RndForce;// rnd force + Vector3f m_Force; // gravity value + float m_Damping; // damping value + float m_SizeGrow; // Grows the size of the particle by sizeGrow per second. A value of 1.0 doubles the particle size every second. + ColorRGBA32 m_ColorAnimation[kColorKeys]; // Animates the color through the color keys + int m_Autodestruct; + bool m_DoesAnimateColor; + bool m_StopSimulation; + float m_EnergylossFraction; +}; + +#endif diff --git a/Runtime/Filters/Particles/ParticleEmitter.cpp b/Runtime/Filters/Particles/ParticleEmitter.cpp new file mode 100644 index 0000000..e6507e7 --- /dev/null +++ b/Runtime/Filters/Particles/ParticleEmitter.cpp @@ -0,0 +1,455 @@ +#include "UnityPrefix.h" +#include "ParticleEmitter.h" +#include "Runtime/Input/TimeManager.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Math/Random/Random.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Serialize/PersistentManager.h" +#include "ParticleAnimator.h" +#include "WorldParticleCollider.h" +#include "ParticleRenderer.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Core/Callbacks/GlobalCallbacks.h" + + +using namespace std; + +static Rand gEmitterRand (5); + +enum { kMaxParticleCount = 65000 / 4 }; +const float kMaxEnergy = 1e20f; + +typedef List< ListNode<ParticleEmitter> > ParticleEmitterList; +static ParticleEmitterList gActiveEmitters; + + +ParticleEmitter::ParticleEmitter (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +, m_EmittersListNode( this ) +{ + m_EmissionFrac = 0.0F; + m_Emit = true; + m_Enabled = true; + m_OneShot = false; + m_PrivateInfo.hadEverEmitOn = false; + m_PrivateInfo.isEmitOn = false; + + m_MinSize = 0.1F; + m_MaxSize = 0.1F; + m_MinEnergy = 3.0F; + m_MaxEnergy = 3.0F; + m_MinEmission = 50.0F; + m_MaxEmission = 50.0F; + m_UseWorldSpace = true; + m_EmitterVelocityScale = 0.05F; + m_WorldVelocity = Vector3f::zero; + m_LocalVelocity = Vector3f::zero; + m_TangentVelocity = Vector3f::zero; + m_RndVelocity = Vector3f::zero; + m_RndAngularVelocity = 0.0F; + m_AngularVelocity = 0.0F; + m_RndInitialRotations = false; + m_FirstFrame = true; + m_PrivateInfo.aabb.Init (); +} + +ParticleEmitter::~ParticleEmitter () +{ +} + +void ParticleEmitter::UpdateManagerState( bool updateParticles ) +{ + if( updateParticles == m_EmittersListNode.IsInList() ) + return; + if( updateParticles ) + gActiveEmitters.push_back(m_EmittersListNode); + else + m_EmittersListNode.RemoveFromList(); +} + +void ParticleEmitter::UpdateAllParticleSystems() +{ + const float deltaTimeEpsilon = 0.0001f; + float deltaTime = GetDeltaTime(); + if(deltaTime < deltaTimeEpsilon) + return; + + ParticleEmitterList::iterator next; + for( ParticleEmitterList::iterator i = gActiveEmitters.begin(); i != gActiveEmitters.end(); i = next ) + { + next = i; + next++; + ParticleEmitter& emitter = **i; + emitter.UpdateParticleSystem(deltaTime); + } +} + + + +void ParticleEmitter::ResetEmitterPos () +{ + if (m_UseWorldSpace) + m_EmitterPos = GetComponent (Transform).GetPosition (); + else + m_EmitterPos = Vector3f::zero; + m_PreviousEmitterPos = m_EmitterPos; +} + + +void ParticleEmitter::Deactivate( DeactivateOperation operation ) +{ + Super::Deactivate(operation); + + m_PrivateInfo.hadEverEmitOn = false; + UpdateManagerState( false ); +} + +PROFILER_INFORMATION(gParticleEmitterProfile, "Particle.Update", kProfilerParticles) + +void ParticleEmitter::UpdateParticleSystem(float deltaTime) +{ + PROFILER_AUTO(gParticleEmitterProfile, this) + + if( !IsActive() ) + { + AssertStringObject( "UpdateParticle system should not happen on disabled GO", this ); + return; + } + + //@TODO: REMOVE FROM LIST PROPERLY TO SAVE ITERATING THROUGH ALL PARTICLES + if (m_Enabled) + { + m_PrivateInfo.maxParticleSize = m_MaxSize; + m_PrivateInfo.maxEmitterParticleSize = m_MaxSize; + m_PrivateInfo.maxEnergy = clamp( m_MaxEnergy, 0.0f, kMaxEnergy ); + m_PrivateInfo.useWorldSpace = m_UseWorldSpace; + if( m_Emit ) + m_PrivateInfo.hadEverEmitOn = true; + m_PrivateInfo.isEmitOn = m_Emit; + + // + // Emit particles. + + ParticleAnimator* animator = QueryComponent(ParticleAnimator); + if( IsEmitting() ) + { + // We don't want to emit anymore if we have OneShot and auto destruct + // in particle animator is true. Otherwise we'd emit when we have no particles, and + // there will never be zero particles, hence no auto destruct ever! + bool willAutoDestruct = false; + if( animator && m_OneShot && m_Particles.empty() ) + willAutoDestruct = animator->WillAutoDestructIfNoParticles( m_PrivateInfo ); + if( !willAutoDestruct ) + TimedEmit( deltaTime ); + } + + // + // Update particle animator + + if( animator ) + animator->UpdateAnimator( m_Particles, m_PrivateInfo, deltaTime ); + + // + // Update world particle collider + + WorldParticleCollider* collider = QueryComponent(WorldParticleCollider); + if( collider ) + collider->UpdateParticleCollider( m_Particles, m_PrivateInfo, deltaTime ); + + // + // Update renderer + + ParticleRenderer* renderer = QueryComponent(ParticleRenderer); + if( renderer ) + renderer->UpdateParticleRenderer(); + } +} + +void ParticleEmitter::SetEmit (bool emit) +{ + if (m_Emit == emit) + return; + m_Emit = emit; + UpdateManagerState( IsActive() ); + if (emit) { + ResetEmitterPos (); + } +} + +void ParticleEmitter::SetEnabled (bool enabled) +{ + if (m_Enabled != enabled) + { + m_Enabled = enabled; + SetDirty(); + } +} + +void ParticleEmitter::Emit (unsigned int newParticleCount, float invDeltaTime) +{ + if (newParticleCount <= 0) + return; + + if (m_FirstFrame) + { + ResetEmitterPos(); + m_FirstFrame = false; + } + + unsigned int firstNewIndex = m_Particles.size (); + newParticleCount = min<unsigned int> (newParticleCount + firstNewIndex, kMaxParticleCount); + + if (newParticleCount != firstNewIndex) + { + m_Particles.resize (newParticleCount); + + Vector3f velocityOffset; + Matrix3x3f localRotation; + + CalcOffsets (&velocityOffset, &localRotation, invDeltaTime); + // Setup particles + SetupParticles (m_Particles, velocityOffset, localRotation, firstNewIndex); + } +} + + +void ParticleEmitter::Emit (const Vector3f &pos, const Vector3f &dir, float size, float energy, const ColorRGBA32 &color, float rotation, float angularVelocity) { + + if (m_Particles.size() >= kMaxParticleCount) + return; + + Particle p; + p.position = pos; + p.velocity = dir; + p.size = size; + p.rotation = Deg2Rad(rotation); + p.angularVelocity = Deg2Rad(angularVelocity); +// p.energy = SecondsToEnergy (energy + GetDeltaTime ()) + 1; + p.energy = energy; + p.startEnergy = energy; + p.color = color; + m_Particles.push_back (p); + m_PrivateInfo.aabb.Encapsulate( pos ); + UpdateManagerState( IsActive() ); +} + + +void ParticleEmitter::ClearParticles () +{ + m_Particles.clear(); + UpdateManagerState( IsActive() ); +} + +void ParticleEmitter::TimedEmit (float deltaTime) +{ + // Reserve enough memory to hold all particles that could ever be emitted with the current settings + int maxParticles = 0; + if (!m_OneShot) + { + maxParticles = CeilfToIntPos( min<float>(m_MaxEmission * m_MaxEnergy, kMaxParticleCount) ); + } + else + { + maxParticles = RoundfToIntPos( min<float>(m_MaxEmission, kMaxParticleCount) ); + } + + m_Particles.reserve (maxParticles); + + // Calculate how many new particles to emit + // never emit more particles than the capacity. + // the capacity can sometimes be too small if deltaTime is very large + // or minEnergy == maxEnergy or minEmissions == maxEmissions + int newParticleCount = 0; + + float emission = min<float> ( RangedRandom (gEmitterRand, m_MinEmission, m_MaxEmission), maxParticles ); + if (!m_OneShot) + { + float newParticleCountf = emission * deltaTime + m_EmissionFrac; + newParticleCount = FloorfToIntPos (newParticleCountf); + m_EmissionFrac = newParticleCountf - (float)newParticleCount; + } + else if(m_Particles.size () == 0) + { + newParticleCount = RoundfToIntPos(emission); + } + + newParticleCount = min<int> (m_Particles.capacity () - m_Particles.size (), newParticleCount); + + // Calculate velocity and rotation that is used to setup the particles + // We do this here so we don't get confused by calls to Emit from outside the filter loop. + if (m_UseWorldSpace) + { + m_PreviousEmitterPos = m_EmitterPos; + m_EmitterPos = GetComponent (Transform).GetPosition (); + } + else + { + m_PreviousEmitterPos = Vector3f::zero; + m_EmitterPos = Vector3f::zero; + } + + if (newParticleCount > 0) + { + Emit (newParticleCount, CalcInvDeltaTime(deltaTime)); + } +} + +void ParticleEmitter::CalcOffsets (Vector3f *velocityOffset, Matrix3x3f *localRotation, float invDeltaTime) { + Transform& t = GetComponent (Transform); + if (m_UseWorldSpace) + { + m_EmitterPos = t.GetPosition (); + + QuaternionToMatrix (t.GetRotation (), *localRotation); + + *velocityOffset = localRotation->MultiplyVector3 (m_LocalVelocity); + *velocityOffset += m_WorldVelocity; + *velocityOffset += (m_EmitterPos - m_PreviousEmitterPos) * invDeltaTime * m_EmitterVelocityScale; + } + else { + localRotation->SetIdentity (); + + *velocityOffset = m_LocalVelocity; + *velocityOffset += t.InverseTransformDirection (m_WorldVelocity); + } +} + +template<class TransferFunction> +void ParticleEmitter::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.SetVersion(2); + transfer.Transfer (m_Enabled, "m_Enabled", kHideInEditorMask); + TRANSFER_SIMPLE (m_Emit); + + transfer.Align(); + transfer.Transfer (m_MinSize, "minSize", kSimpleEditorMask); + transfer.Transfer (m_MaxSize, "maxSize", kSimpleEditorMask); + // WARNING: energy/emission might be inf + // when using fastmath on ps3 or equivalent on other platform - these will be handled incorrectly + // in that case we need to patch them on build here + transfer.Transfer (m_MinEnergy, "minEnergy", kSimpleEditorMask); + transfer.Transfer (m_MaxEnergy, "maxEnergy", kSimpleEditorMask); + transfer.Transfer (m_MinEmission, "minEmission", kSimpleEditorMask); + transfer.Transfer (m_MaxEmission, "maxEmission", kSimpleEditorMask); + transfer.Transfer (m_WorldVelocity, "worldVelocity", kSimpleEditorMask); + transfer.Transfer (m_LocalVelocity, "localVelocity", kSimpleEditorMask); + transfer.Transfer (m_RndVelocity, "rndVelocity", kSimpleEditorMask); + transfer.Transfer (m_EmitterVelocityScale, "emitterVelocityScale"); + // Emitter velocity scale was not frame rate independent! + if (transfer.IsOldVersion(1)) + m_EmitterVelocityScale /= 40.0F; + + transfer.Transfer (m_TangentVelocity, "tangentVelocity"); + transfer.Transfer (m_AngularVelocity, "angularVelocity", kSimpleEditorMask); + transfer.Transfer (m_RndAngularVelocity, "rndAngularVelocity", kSimpleEditorMask); + transfer.Transfer (m_RndInitialRotations, "rndRotation", kSimpleEditorMask); + transfer.Transfer (m_UseWorldSpace, "Simulate in Worldspace?"); + transfer.Transfer (m_OneShot, "m_OneShot"); +} + +void ParticleEmitter::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + if (IsActive()) + ResetEmitterPos(); + if (m_OneShot) + ClearParticles (); + UpdateManagerState( IsActive() ); +} + +void ParticleEmitter::ReadParticles( SimpleParticle* __restrict particle, int base, int count ) const +{ + if (base < 0 || base + count > m_Particles.size()) + { + ErrorString("Reading out of bounds particles"); + return; + } + + count += base; + for (int i=0;i<count;++i) + { + SimpleParticle& output = particle[i]; + const Particle& input = m_Particles[i + base]; + output.position = input.position; + output.velocity = input.velocity; + output.size = input.size; + output.rotation = Rad2Deg (input.rotation); + output.angularVelocity = Rad2Deg (input.angularVelocity); + output.energy =input.energy; + output.startEnergy = input.startEnergy; + output.color = input.color; + + Prefetch(&particle[i] + 8); + Prefetch(&particle[i + base] + 8); + } +} + +void ParticleEmitter::WriteParticles( const SimpleParticle* __restrict particle, /*int base,*/ int count ) +{ + if (count > kMaxParticleCount) + { + ErrorString(Format("You are assigning more than %d particles", kMaxParticleCount)); + count = kMaxParticleCount; + } + + MinMaxAABB& aabb = m_PrivateInfo.aabb; + aabb.Init(); + + m_Particles.resize(count); + + int newSize = 0; + for (int i=0;i<count;++i) + { + const SimpleParticle& input = particle[i]; + Particle& output = m_Particles[newSize]; + + output.position = input.position; + aabb.Encapsulate( input.position ); + output.velocity = input.velocity; + output.size = input.size; + output.rotation = Deg2Rad(input.rotation); + output.angularVelocity = Deg2Rad(input.angularVelocity); + output.energy = input.energy; + output.startEnergy = FloatMax(input.startEnergy, input.energy); + output.color = input.color; + + if (output.energy > 0.0f ) + newSize++; + + Prefetch(&particle[i] + 8); + } + + m_Particles.resize(newSize); +} + + +void ParticleEmitter::InitParticleEnergy(Rand& r, Particle& p, float dt) +{ + // if any if energies is infinity - both energy and startEnergy will be infinity too. + // that would be fine, but we're not quite ready to handle Inf properly + + + // add deltatime - it will be removed in particle animator + p.startEnergy = clamp ( RangedRandom (r, m_MinEnergy, m_MaxEnergy), 0.0f, kMaxEnergy ); + p.energy = p.startEnergy + dt + kTimeEpsilon; +} + +static void ResetRandSeedForEmitter () +{ + gEmitterRand.SetSeed (5); +} + +void ParticleEmitter::InitializeClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Register(ResetRandSeedForEmitter); +} + +void ParticleEmitter::CleanupClass () +{ + GlobalCallbacks::Get().resetRandomAfterLevelLoad.Unregister(ResetRandSeedForEmitter); +} + +IMPLEMENT_CLASS_HAS_INIT (ParticleEmitter) +IMPLEMENT_OBJECT_SERIALIZE (ParticleEmitter) +INSTANTIATE_TEMPLATE_TRANSFER (ParticleEmitter) diff --git a/Runtime/Filters/Particles/ParticleEmitter.h b/Runtime/Filters/Particles/ParticleEmitter.h new file mode 100644 index 0000000..3622fe2 --- /dev/null +++ b/Runtime/Filters/Particles/ParticleEmitter.h @@ -0,0 +1,151 @@ +#ifndef PARTICLEEMITTER_H +#define PARTICLEEMITTER_H + +#include "Runtime/BaseClasses/GameObject.h" +#include "ParticleStruct.h" +#include "Runtime/Utilities/LinkedList.h" + + +class Rand; + +#define GET_SET_DIRTY_MIN(TYPE,PROP_NAME,VAR_NAME,MIN_VAL) void Set##PROP_NAME (TYPE val) { VAR_NAME = std::max(val,MIN_VAL); SetDirty(); } TYPE Get##PROP_NAME () const { return (TYPE)VAR_NAME; } + + + +class ParticleEmitter : public Unity::Component +{ +public: + REGISTER_DERIVED_ABSTRACT_CLASS (ParticleEmitter, Unity::Component) + DECLARE_OBJECT_SERIALIZE (ParticleEmitter) + + ParticleEmitter (MemLabelId label, ObjectCreationMode mode); + + void SetEmit (bool emit); + bool IsEmitting () const { return m_Emit; } + + GET_SET_DIRTY_MIN (float, MinSize, m_MinSize, 0.0f); + GET_SET_DIRTY_MIN (float, MaxSize, m_MaxSize, 0.0f); + GET_SET_DIRTY_MIN (float, MinEnergy, m_MinEnergy, 0.0f); + GET_SET_DIRTY_MIN (float, MaxEnergy, m_MaxEnergy, 0.0f); + GET_SET_DIRTY_MIN (float, MinEmission, m_MinEmission, 0.0f); + GET_SET_DIRTY_MIN (float, MaxEmission, m_MaxEmission, 0.0f); + + GET_SET_DIRTY (float, EmitterVelocityScale, m_EmitterVelocityScale); + GET_SET_DIRTY (Vector3f, WorldVelocity, m_WorldVelocity); + GET_SET_DIRTY (Vector3f, LocalVelocity, m_LocalVelocity); + GET_SET_DIRTY (Vector3f, RndVelocity, m_RndVelocity); + GET_SET_DIRTY (bool, RndRotation, m_RndInitialRotations); + GET_SET_DIRTY (float, AngularVelocity, m_AngularVelocity); + + float GetRndAngularVelocity () const { return m_RndAngularVelocity; } + void SetRndAngularVelocity (float val) { if (val < 0) val = 0; m_RndAngularVelocity = val; SetDirty(); } + + bool GetUseWorldSpace () const { return m_UseWorldSpace; } + void SetUseWorldSpace (bool val) { m_UseWorldSpace = val; } // TODO: set dirty? + + // Emit particleCount particles + void Emit (unsigned int particleCount, float invDeltaTime); + + // This is needed, so that subsequent calls to Emit() will each only emit at the current location, and not along the line + // between the current and last location. + void EmitResetEmitterPos (int particleCount, float invDeltaTime) { ResetEmitterPos(); Emit (particleCount, invDeltaTime); } + + // Emit one particle + void Emit (const Vector3f &pos, const Vector3f &dir, float size, float energy, const ColorRGBA32 &color, float rotation, float angularVelocity); + + void Deactivate (DeactivateOperation operation); + void AwakeFromLoad (AwakeFromLoadMode awakeMode); + + // clear all particles. + void ClearParticles (); + + void ReadParticles( SimpleParticle*__restrict particle, int baseIndex, int count ) const; + void WriteParticles( const SimpleParticle* __restrict particle, int count ); + + int GetParticleCount() const { return m_Particles.size(); } + + static void UpdateAllParticleSystems(); + void UpdateParticleSystem(float deltaTime); + + const ParticleArray& GetParticles() const { return m_Particles; } + ParticleArray& GetParticles() { return m_Particles; } + const PrivateParticleInfo& GetPrivateInfo() const { return m_PrivateInfo; } + + bool GetEnabled () const { return m_Enabled; } + void SetEnabled (bool enabled); + + static void InitializeClass (); + static void CleanupClass (); + +protected: + // Sets "does this emitter have to be updated next frame" flags. + void UpdateManagerState( bool updateParticles ); + +private: + // Subclasses have to override this function to place particles + virtual void SetupParticles (ParticleArray& particles, const Vector3f& velocityOffset, + const Matrix3x3f& rotation, int firstIndex) = 0; + + // Emit particles + void TimedEmit (float deltaTime); + + void ResetEmitterPos (); + void CalcOffsets (Vector3f *velocityOffset, Matrix3x3f *localRotation, float invDeltaTime); + +protected: + Vector3f m_EmitterPos; + Vector3f m_PreviousEmitterPos; + +protected: + ParticleArray m_Particles; + PrivateParticleInfo m_PrivateInfo; + float m_EmissionFrac; + + float m_MinSize; ///< minimum size range {0, infinity } + float m_MaxSize; ///< maximum size range {0, infinity } + float m_MinEnergy; ///< minimum energy range { 0, infinity } + float m_MaxEnergy; ///< maximum energy range { 0, infinity } + + float m_MinEmission; ///< minimum emissions per second range { 0, infinity } + float m_MaxEmission; ///< maximum emissions per second range { 0, infinity } + + float m_EmitterVelocityScale;///< Scales velocity of the emitter + + Vector3f m_WorldVelocity; + + Vector3f m_LocalVelocity; + + Vector3f m_TangentVelocity; + + Vector3f m_RndVelocity; + + bool m_UseWorldSpace; ///< Are particles simulated in WorldSpace or in LocalSpace? + + bool m_RndInitialRotations; ///< Should initial particle rotations be randomized? + float m_RndAngularVelocity; + float m_AngularVelocity; + + bool m_Enabled; + bool m_Emit; + bool m_OneShot; + bool m_FirstFrame; + +protected: + + void InitParticleEnergy(Rand& r, Particle& p, float dt); + + #if DOXYGEN + ///////// @TODO: DO EASIER BACKWARDS COMPATIBILITY: THEN WE DONT NEED THIS CRAP + float minSize; ///< minimum size range {0, infinity } + float maxSize; ///< minimum size range {0, infinity } + float minEnergy; ///< minimum size range {0, infinity } + float maxEnergy; ///< minimum size range {0, infinity } + float minEmission; ///< minimum size range {0, infinity } + float maxEmission; ///< minimum size range {0, infinity } + #endif + +private: + ListNode<ParticleEmitter> m_EmittersListNode; +}; + +#endif diff --git a/Runtime/Filters/Particles/ParticleRenderer.cpp b/Runtime/Filters/Particles/ParticleRenderer.cpp new file mode 100644 index 0000000..3a638e9 --- /dev/null +++ b/Runtime/Filters/Particles/ParticleRenderer.cpp @@ -0,0 +1,630 @@ +#include "UnityPrefix.h" +#include "ParticleRenderer.h" +#include "ParticleEmitter.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Filters/Misc/LineBuilder.h" +#include "Runtime/Camera/Camera.h" +#include "ParticleStruct.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Geometry/AABB.h" +#include "Runtime/Camera/RenderManager.h" +#include "Runtime/Shaders/Shader.h" +#include "Runtime/GfxDevice/GfxDevice.h" +#include "Runtime/Shaders/VBO.h" +#include "Runtime/Shaders/GraphicsCaps.h" +#include "Runtime/Profiler/Profiler.h" +#include "Runtime/Profiler/ExternalGraphicsProfiler.h" + +IMPLEMENT_CLASS (ParticleRenderer) +IMPLEMENT_OBJECT_SERIALIZE (ParticleRenderer) + +// The distance of particle Corner from the center +// sin (45 deg), or sqrt(0.5) +#define kMaxParticleSizeFactor 0.707106781186548f + +ParticleRenderer::ParticleRenderer (MemLabelId label, ObjectCreationMode mode) +: Super(kRendererParticle, label, mode) +{ + SetVisible (false); + m_UVFrames = NULL; +} + +ParticleRenderer::~ParticleRenderer () +{ + if (m_UVFrames) + UNITY_FREE(kMemParticles, m_UVFrames); +} + +struct ParticleVertex { + Vector3f vert; + Vector3f normal; + ColorRGBA32 color; + Vector2f uv; +}; + +void ParticleRenderer::AwakeFromLoad (AwakeFromLoadMode awakeMode) +{ + Super::AwakeFromLoad (awakeMode); + ParticleRenderer::GenerateUVFrames (); +} + +void ParticleRenderer::SetUVFrames (const Rectf *uvFrames, int numFrames) +{ + m_NumUVFrames = numFrames; + + if (m_UVFrames) + UNITY_FREE(kMemParticles, m_UVFrames); + + size_t size; + size = sizeof(Rectf) * m_NumUVFrames; + + if (m_NumUVFrames != 0 && m_NumUVFrames != size / sizeof(Rectf)) + { + m_UVFrames = NULL; + m_NumUVFrames = 0; + } + else + { + m_UVFrames = (Rectf*)UNITY_MALLOC(kMemParticles, size); + memcpy(m_UVFrames, uvFrames, numFrames*sizeof(Rectf)); + } +} + +void ParticleRenderer::GenerateUVFrames () +{ + if(m_UVAnimation.xTile < 1) + m_UVAnimation.xTile = 1; + if(m_UVAnimation.yTile < 1) + m_UVAnimation.yTile = 1; + + m_NumUVFrames = (m_UVAnimation.xTile * m_UVAnimation.yTile); + float animUScale = 1.0f / m_UVAnimation.xTile; + float animVScale = 1.0f / m_UVAnimation.yTile; + + if (m_UVFrames) + UNITY_FREE(kMemParticles, m_UVFrames); + + if(m_NumUVFrames == 1) + m_NumUVFrames = 0; + SET_ALLOC_OWNER(this); + m_UVFrames = (Rectf*)UNITY_MALLOC(kMemParticles, m_NumUVFrames * sizeof(Rectf)); + + for(int index=0;index<m_NumUVFrames;index++) + { + int vIdx = index / m_UVAnimation.xTile; + int uIdx = index - vIdx * m_UVAnimation.xTile; // slightly faster than index % m_UVAnimation.xTile + float uOffset = (float)uIdx * animUScale; + float vOffset = 1.0f - animVScale - (float)vIdx * animVScale; + + m_UVFrames[index] = Rectf(uOffset, vOffset, animUScale, animVScale); + } +} + +void ParticleRenderer::SetUVAnimationXTile (int v) +{ + v = std::max(v,1); + if (v == m_UVAnimation.xTile) + return; + m_UVAnimation.xTile = v; + SetDirty (); + GenerateUVFrames (); +} +void ParticleRenderer::SetUVAnimationYTile (int v) +{ + v = std::max(v,1); + if (v == m_UVAnimation.yTile) + return; + m_UVAnimation.yTile = v; + SetDirty (); + GenerateUVFrames (); +} +void ParticleRenderer::SetUVAnimationCycles (float v) +{ + v = std::max(v,0.0f); + if (v == m_UVAnimation.cycles) + return; + m_UVAnimation.cycles = v; + SetDirty (); +} + + + +PROFILER_INFORMATION(gParticlesProfile, "ParticleRenderer.Render", kProfilerParticles) +PROFILER_INFORMATION(gSubmitVBOProfileParticle, "Mesh.SubmitVBO", kProfilerRender) + +#pragma message ("Optimize particle systems to use templates for inner loops and build optimized inner loops without ifs") + +void ParticleRenderer::Render (int/* materialIndex*/, const ChannelAssigns& channels) +{ + + ParticleEmitter* emitter = QueryComponent(ParticleEmitter); + if( !emitter ) + return; + + PROFILER_AUTO_GFX(gParticlesProfile, this) + + GfxDevice& device = GetGfxDevice(); + Matrix4x4f matrix; + + ParticleArray& particles = emitter->GetParticles(); + const PrivateParticleInfo& privateInfo = emitter->GetPrivateInfo(); + if( particles.empty() ) + return; + + Camera& cam = GetCurrentCamera (); + + Vector3f xSpan, ySpan; + if(m_StretchParticles == kBillboardFixedHorizontal) + { + xSpan = Vector3f(1.0f,0.0f,0.0f); + ySpan = Vector3f(0.0f,0.0f,1.0f); + } + + if(m_StretchParticles == kBillboardFixedVertical) + { + ySpan = Vector3f(0.0f,1.0f,0.0f); + Vector3f zSpan = RotateVectorByQuat (cam.GetComponent (Transform).GetRotation(), Vector3f(0.0f,0.0f,1.0f)); + xSpan = NormalizeSafe( Cross(ySpan, zSpan) ); + } + + Vector3f cameraVelocity; + if (privateInfo.useWorldSpace) + { + CopyMatrix (device.GetViewMatrix (), matrix.GetPtr()); + cameraVelocity = cam.GetVelocity (); + } + else + { + Matrix4x4f mat, temp; + CopyMatrix (device.GetViewMatrix (), temp.GetPtr()); + mat = GetComponent (Transform).GetLocalToWorldMatrixNoScale(); + MultiplyMatrices4x4 (&temp, &mat, &matrix); + cameraVelocity = GetComponent (Transform).InverseTransformDirection (cam.GetVelocity ()); + } + + // Constrain the size to be a fraction of the viewport size. + // In perspective case, max size is (z*factorA). In ortho case, max size is just factorB. To have both + // without branches, we do (z*factorA+factorB) and set one of factors to zero. + float maxPlaneScale; + float maxOrthoSize; + if (!cam.GetOrthographic()) { + maxPlaneScale = -cam.CalculateFarPlaneWorldSpaceLength () * m_MaxParticleSize / cam.GetFar (); + maxOrthoSize = 0.0f; + } else { + maxPlaneScale = 0.0f; + maxOrthoSize = cam.CalculateFarPlaneWorldSpaceLength () * m_MaxParticleSize; + } + + /// Sort the particles + if (m_StretchParticles == kSortedBillboard && !particles.empty ()) + { + static vector<float> s_Dist; + if (s_Dist.size() < particles.size()) { + s_Dist.resize (0); + s_Dist.resize (particles.size()); + } + + // Calculate all distances + int i; + Vector3f distFactor = Vector3f (matrix.Get (2, 0), matrix.Get (2, 1), + matrix.Get (2, 2)); + for (i = 0; i < particles.size(); i++) + s_Dist[i] = Dot (distFactor, particles[i].position); + + // Bubblesort them + i = 0; + while (i < particles.size() - 1) + { + if (s_Dist[i] > s_Dist[i + 1]) + { + std::swap (s_Dist[i], s_Dist[i + 1]); + std::swap (particles[i], particles[i + 1]); + if (i > 0) + i -= 2; + } + i++; + } + } + + // Calculate parameters for UV animation + const int animFullTexCount = int(m_NumUVFrames * m_UVAnimation.cycles); + + // Get VBO chunk + const int particleCount = particles.size(); + DynamicVBO& vbo = device.GetDynamicVBO(); + ParticleVertex* vbPtr; + if( !vbo.GetChunk( (1<<kShaderChannelVertex) | (1<<kShaderChannelNormal) | (1<<kShaderChannelTexCoord0) | (1<<kShaderChannelColor), + particleCount * 4, 0, + DynamicVBO::kDrawQuads, + (void**)&vbPtr, NULL ) ) + { + return; + } + + int billboardRenderMode = m_StretchParticles; + + if (billboardRenderMode == kSortedBillboard) + billboardRenderMode = kBillboard; + if (billboardRenderMode == kBillboardFixedVertical) + billboardRenderMode = kBillboardFixedHorizontal; + + // Fill vertex buffer with particles + for( int i = 0; i < particleCount; ++i ) + { + const Particle& p = particles[i]; + Vector3f vert[4]; + Vector2f uv[4]; + + if (p.rotation != 0) + { + if (billboardRenderMode == kBillboard) + billboardRenderMode = kBillboardRotated; + else if (billboardRenderMode == kBillboardFixedHorizontal) + billboardRenderMode = kBillboardFixedRotated; + } + // Positions + // @TODO: get rid of the branch here + switch (billboardRenderMode) { + case kBillboard: + { + // Project point and create quad + Vector3f center = matrix.MultiplyPoint3 (p.position); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = center.z * maxPlaneScale + maxOrthoSize; + float size = std::min (p.size, maxWorldSpaceLength); + float halfSize = size * 0.5f; + + vert[0].Set( center.x - halfSize, center.y + halfSize, center.z ); + vert[1].Set( center.x + halfSize, center.y + halfSize, center.z ); + vert[2].Set( center.x + halfSize, center.y - halfSize, center.z ); + vert[3].Set( center.x - halfSize, center.y - halfSize, center.z ); + } + break; + case kBillboardFixedHorizontal: + { + // Project point and create quad + Vector3f center = matrix.MultiplyPoint3 (p.position); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = (center.z - p.size * kMaxParticleSizeFactor) * maxPlaneScale + maxOrthoSize; + float size = std::min (p.size, maxWorldSpaceLength); + float halfSize = size * 0.5f; + + vert[0] = matrix.MultiplyPoint3 ( p.position - halfSize * xSpan + halfSize * ySpan ); + vert[1] = matrix.MultiplyPoint3 ( p.position + halfSize * xSpan + halfSize * ySpan ); + vert[2] = matrix.MultiplyPoint3 ( p.position + halfSize * xSpan - halfSize * ySpan ); + vert[3] = matrix.MultiplyPoint3 ( p.position - halfSize * xSpan - halfSize * ySpan ); + } + break; + case kBillboardRotated: + { + // Project point and create quad + // Particles can be rotated by animation curve and width & height can be animated individually using an animation curve + Vector3f center = matrix.MultiplyPoint3 (p.position); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = center.z * maxPlaneScale + maxOrthoSize; + + float s = Sin (p.rotation); + float c = Cos (p.rotation); + + float x00 = c; + float x01 = s; + float x10 = -s; + float x11 = c; + + float size = std::min (p.size, maxWorldSpaceLength); + float halfSize = size * 0.5f; + + #define MUL(w,h) center.x + x00 * w + x01 * h, center.y + x10 * w + x11 * h, center.z + + vert[0].Set(MUL(-halfSize, halfSize)); + vert[1].Set(MUL( halfSize, halfSize)); + vert[2].Set(MUL( halfSize, -halfSize)); + vert[3].Set(MUL(-halfSize, -halfSize)); + #undef MUL + } + break; + case kBillboardFixedRotated: + { + // Project point and create quad + Vector3f center = matrix.MultiplyPoint3 (p.position); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = center.z * maxPlaneScale + maxOrthoSize; + + float s = Sin (p.rotation); + float c = Cos (p.rotation); + + float size = std::min (p.size, maxWorldSpaceLength); + float halfSize = size * 0.5f; + + vert[0] = matrix.MultiplyPoint3 ( p.position + halfSize * xSpan * c + halfSize * ySpan * s ); + vert[1] = matrix.MultiplyPoint3 ( p.position + halfSize * ySpan * c - halfSize * xSpan * s ); + vert[2] = matrix.MultiplyPoint3 ( p.position - halfSize * xSpan * c - halfSize * ySpan * s ); + vert[3] = matrix.MultiplyPoint3 ( p.position - halfSize * ySpan * c + halfSize * xSpan * s ); + } + break; + + case kStretch3D: + { + Vector3f velocity = p.velocity - cameraVelocity * m_CameraVelocityScale; + float sqrVelocity = SqrMagnitude (velocity); + + float size = p.size; + + float lineScale; + if (sqrVelocity > Vector3f::epsilon) + lineScale = m_VelocityScale + FastInvSqrt (sqrVelocity) * (m_LengthScale * size); + else + lineScale = 0.0F; + + Vector3f lineDelta = velocity * lineScale; + Vector3f begProj = matrix.MultiplyPoint3 (p.position); + Vector3f endProj = matrix.MultiplyPoint3 (p.position - lineDelta); + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = endProj.z * maxPlaneScale + maxOrthoSize; + size = std::min (size, maxWorldSpaceLength); + float halfSize = size * 0.5F; + + Vector2f delta = Calculate2DLineExtrusion(begProj, begProj - endProj, halfSize); + + vert[0].Set( begProj.x + delta.x, begProj.y + delta.y, begProj.z ); + vert[1].Set( endProj.x + delta.x, endProj.y + delta.y, endProj.z ); + vert[2].Set( endProj.x - delta.x, endProj.y - delta.y, endProj.z ); + vert[3].Set( begProj.x - delta.x, begProj.y - delta.y, begProj.z ); + } + break; + case kStretch2D: + { + Vector3f velocity = p.velocity - cameraVelocity * m_CameraVelocityScale; + float sqrVelocity = SqrMagnitude (velocity); + + float size = p.size; + + float lineScale; + if (sqrVelocity > Vector3f::epsilon) + lineScale = m_VelocityScale + FastInvSqrt (sqrVelocity) * (m_LengthScale * size); + else + lineScale = 0.0F; + + Vector3f lineDelta = velocity * lineScale; + Vector3f begProj = matrix.MultiplyPoint3 (p.position); + Vector3f endProj = matrix.MultiplyPoint3 (p.position + lineDelta); + + float dx = endProj.x - begProj.x; + float dy = endProj.y - begProj.y; + + float sqrProjectedLength = dx*dx + dy*dy; + + if (sqrProjectedLength < Vector3f::epsilon) + { + dx = 1.0F; + dy = 0.0F; + sqrProjectedLength = 1.0F; + } + + // Constrain the size to be a fraction of the viewport size. + // v[0].z * / farPlaneZ * farPlaneWorldSpaceLength * maxLength[0...1] + // Also all valid z's are negative so we just negate the whole equation + float maxWorldSpaceLength = endProj.z * maxPlaneScale + maxOrthoSize; + size = std::min (size, maxWorldSpaceLength); + float halfSize = size * 0.5F; + + float lengthInv = halfSize * FastInvSqrt (sqrProjectedLength); + // Orthogonal 2D-vector to sdx0 + float sdx1 = -dy * lengthInv; + float sdy1 = dx * lengthInv; + + // scale with velocity + vert[0].Set( begProj.x + sdx1, begProj.y + sdy1, begProj.z ); + vert[1].Set( endProj.x + sdx1, endProj.y + sdy1, endProj.z ); + vert[2].Set( endProj.x - sdx1, endProj.y - sdy1, endProj.z ); + vert[3].Set( begProj.x - sdx1, begProj.y - sdy1, begProj.z ); + } + break; + } + + + // UVs + if(m_NumUVFrames > 0) + { + const float alpha = 1.0f - clamp01 (p.energy / p.startEnergy); + unsigned int index = (unsigned int)(alpha * animFullTexCount); + + Rectf *r = m_UVFrames+(index % m_NumUVFrames); + uv[0].Set( r->x, r->y + r->height ); + uv[1].Set( r->x + r->width, r->y + r->height ); + uv[2].Set( r->x + r->width, r->y ); + uv[3].Set( r->x, r->y ); + } + else + { + uv[0].Set( 0, 1 ); + uv[1].Set( 1, 1 ); + uv[2].Set( 1, 0 ); + uv[3].Set( 0, 0 ); + } + + + // Swizzle color of the renderer requires it + ColorRGBA32 color = p.color; + color = device.ConvertToDeviceVertexColor(color); + + // Now, write out the vertex structures sequentially (important when writing into dynamic VBO) + // @TODO: this place seems to be heavy in the profile (even more than branches above), optimize somehow! + vbPtr[0].vert = vert[0]; + vbPtr[0].normal.Set( 0.0f, 0.0f, 1.0f ); + vbPtr[0].color = color; + vbPtr[0].uv = uv[0]; + + vbPtr[1].vert = vert[1]; + vbPtr[1].normal.Set( 0.0f, 0.0f, 1.0f ); + vbPtr[1].color = color; + vbPtr[1].uv = uv[1]; + + vbPtr[2].vert = vert[2]; + vbPtr[2].normal.Set( 0.0f, 0.0f, 1.0f ); + vbPtr[2].color = color; + vbPtr[2].uv = uv[2]; + + vbPtr[3].vert = vert[3]; + vbPtr[3].normal.Set( 0.0f, 0.0f, 1.0f ); + vbPtr[3].color = color; + vbPtr[3].uv = uv[3]; + + // Next four vertices + vbPtr += 4; + } + + vbo.ReleaseChunk( particleCount * 4, 0 ); + + // Draw + float matView[16]; + CopyMatrix(device.GetViewMatrix(), matView); + device.SetViewMatrix (Matrix4x4f::identity.GetPtr()); // implicitly sets world to identity + + if (m_CustomProperties) + device.SetMaterialProperties (*m_CustomProperties); + + PROFILER_BEGIN(gSubmitVBOProfileParticle, this) + vbo.DrawChunk (channels); + GPU_TIMESTAMP(); + PROFILER_END + + device.SetViewMatrix(matView); +} + +void ParticleRenderer::AdjustBoundsForStretch( const ParticleEmitter& emitter, MinMaxAABB& aabb ) const +{ + Assert(m_StretchParticles == kStretch3D); + + const ParticleArray& particles = emitter.GetParticles(); + const size_t particleCount = particles.size(); + const float velocityScale = m_VelocityScale; + const float lengthScale = m_LengthScale; + const Particle* p = &particles[0]; + + for( size_t i = 0; i < particleCount; ++i, ++p ) { + float sqrVelocity = SqrMagnitude (p->velocity); + if (sqrVelocity > Vector3f::epsilon) { + float scale = velocityScale + FastInvSqrt (sqrVelocity) * lengthScale * p->size; + aabb.Encapsulate( p->position - p->velocity * scale ); + } + } +} + +void ParticleRenderer::UpdateTransformInfo () +{ + const Transform& transform = GetTransform(); + if (m_TransformDirty) + { + m_TransformInfo.invScale = 1.0f; + // will return a cached matrix most of the time + m_TransformInfo.transformType = transform.CalculateTransformMatrix (m_TransformInfo.worldMatrix);; + } + + if (m_BoundsDirty) + { + ParticleEmitter* emitter = QueryComponent(ParticleEmitter); + if (!emitter) + { + m_TransformInfo.localAABB.SetCenterAndExtent(Vector3f::zero, Vector3f::zero); + m_TransformInfo.worldAABB.SetCenterAndExtent(Vector3f::zero, Vector3f::zero); + return; + } + + const PrivateParticleInfo& info = emitter->GetPrivateInfo(); + MinMaxAABB aabb = info.aabb; + if (m_StretchParticles == kStretch3D) + AdjustBoundsForStretch (*emitter, aabb); + aabb.Expand (info.maxParticleSize * kMaxParticleSizeFactor); + + if (info.useWorldSpace) + { + m_TransformInfo.worldAABB = aabb; + InverseTransformAABB (m_TransformInfo.worldAABB, transform.GetPosition(), transform.GetRotation(), m_TransformInfo.localAABB); + } + else + { + m_TransformInfo.localAABB = aabb; + TransformAABB (m_TransformInfo.localAABB, transform.GetPosition(), transform.GetRotation(), m_TransformInfo.worldAABB); + } + } +} + + +void ParticleRenderer::UpdateRenderer() +{ + ParticleEmitter* emitter = QueryComponent(ParticleEmitter); + if( emitter ) + { + bool empty = emitter->GetParticles().empty(); + SetVisible( !empty ); + if( !empty ) + BoundsChanged(); + } + else + { + UpdateManagerState( false ); + } + + Super::UpdateRenderer(); +} + +void ParticleRenderer::UpdateParticleRenderer() +{ + UpdateManagerState( true ); +} + + +template<class TransferFunction> inline +void ParticleRenderer::Transfer (TransferFunction& transfer) +{ + Super::Transfer (transfer); + transfer.SetVersion (2); + TRANSFER (m_CameraVelocityScale); + TRANSFER_SIMPLE (m_StretchParticles); + TRANSFER_SIMPLE (m_LengthScale); + TRANSFER (m_VelocityScale); + TRANSFER (m_MaxParticleSize); + + if (transfer.IsCurrentVersion()) { + transfer.Transfer (m_UVAnimation, "UV Animation"); + } else { + transfer.Transfer (m_UVAnimation.xTile, "m_AnimatedTextureCount"); + } +} + +void ParticleRenderer::CheckConsistency () +{ + Super::CheckConsistency (); + m_MaxParticleSize = std::max (0.0F, m_MaxParticleSize); + m_UVAnimation.xTile = std::max (1, m_UVAnimation.xTile); + m_UVAnimation.yTile = std::max (1, m_UVAnimation.yTile); + m_UVAnimation.cycles = std::max (0.0F, m_UVAnimation.cycles); +} + +void ParticleRenderer::Reset () +{ + Super::Reset (); + m_StretchParticles = kBillboard; + m_LengthScale = 2.0F; + m_VelocityScale = 0.0F; + m_MaxParticleSize = 0.25F; + m_UVAnimation.xTile = 1; + m_UVAnimation.yTile = 1; + m_UVAnimation.cycles = 1; + m_CameraVelocityScale = 0.0; +} diff --git a/Runtime/Filters/Particles/ParticleRenderer.h b/Runtime/Filters/Particles/ParticleRenderer.h new file mode 100644 index 0000000..d63661c --- /dev/null +++ b/Runtime/Filters/Particles/ParticleRenderer.h @@ -0,0 +1,103 @@ +#ifndef PARTICLERENDERER_H +#define PARTICLERENDERER_H + +#include <vector> +#include "Runtime/Filters/Renderer.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Math/Rect.h" +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/AnimationCurve.h" +using std::vector; +class ParticleEmitter; +class MinMaxAABB; + + + + +enum ParticleRenderMode { + kBillboard = 0, + kStretch2D = 1, + kStretch3D = 3, + kSortedBillboard = 2, + kBillboardFixedHorizontal = 4, + kBillboardFixedVertical = 5, + + /// Internal modes + kBillboardRotated = 1000, + kBillboardFixedRotated = 1001 +}; + +class ParticleRenderer : public Renderer { +public: + REGISTER_DERIVED_CLASS (ParticleRenderer, Renderer) + DECLARE_OBJECT_SERIALIZE (ParticleRenderer) + + ParticleRenderer (MemLabelId label, ObjectCreationMode mode); + // ~ParticleRenderer(); declared-by-macro + + virtual void Render (int materialIndex, const ChannelAssigns& channels); + + // Can operate in either local or world space, so we need to fill whole transform info ourselves + virtual void UpdateTransformInfo(); + + virtual void CheckConsistency (); + virtual void Reset (); + + virtual void AwakeFromLoad (AwakeFromLoadMode awakeMode); + + GET_SET_DIRTY (ParticleRenderMode, RenderMode, m_StretchParticles) ; + GET_SET_DIRTY (float, LengthScale, m_LengthScale) ; + GET_SET_DIRTY (float, VelocityScale, m_VelocityScale) ; + GET_SET_DIRTY (float, CameraVelocityScale, m_CameraVelocityScale) ; + GET_SET_DIRTY (float, MaxParticleSize, m_MaxParticleSize) ; + int GetUVAnimationXTile() const { return m_UVAnimation.xTile; } + int GetUVAnimationYTile() const { return m_UVAnimation.yTile; } + float GetUVAnimationCycles() const { return m_UVAnimation.cycles; } + void SetUVAnimationXTile (int v); + void SetUVAnimationYTile (int v); + void SetUVAnimationCycles (float v); + + void UpdateParticleRenderer(); + + Rectf *GetUVFrames() {return m_UVFrames;}; + int GetNumUVFrames() {return m_NumUVFrames;}; + + void SetUVFrames(const Rectf *uvFrames, int numFrames); + +private: + // from Renderer + virtual void UpdateRenderer(); + + void AdjustBoundsForStretch( const ParticleEmitter& emitter, MinMaxAABB& aabb ) const; + +protected: + struct UVAnimation { + int xTile; ///< Number of texture tiles in the X direction. + int yTile; ///< Number of texture tiles in the Y direction. + float cycles; ///< Number of cycles over a particle's life span. + DECLARE_SERIALIZE (UVAnimation) + }; + void SetBufferSize (int particleCount); + void GenerateUVFrames (); + + int m_StretchParticles; ///< enum { Billboard = 0, Stretched = 3, Sorted Billboard = 2, Horizontal Billboard = 4, Vertical Billboard = 5 } Should the particles be stretched along their velocity? + float m_LengthScale; ///< When Stretch Particles is enabled, defines the length of the particle compared to its width. + float m_VelocityScale; ///< When Stretch Particles is enabled, defines the length of the particle compared to its velocity. + float m_MaxParticleSize; ///< How large is a particle allowed to be on screen at most? 1 is entire viewport. 0.5 is half viewport. + UVAnimation m_UVAnimation; ///< Tiled UV settings. + + float m_CameraVelocityScale; ///< How much the camera motion is factored in when determining particle stretching + + int m_NumUVFrames; //uv tiles for uv animation + Rectf *m_UVFrames; +}; + +template<class TransferFunc> +void ParticleRenderer::UVAnimation::Transfer (TransferFunc& transfer) { + transfer.Transfer (xTile, "x Tile", kSimpleEditorMask); + transfer.Transfer (yTile, "y Tile", kSimpleEditorMask); + TRANSFER_SIMPLE (cycles); +} + +#endif diff --git a/Runtime/Filters/Particles/ParticleStruct.h b/Runtime/Filters/Particles/ParticleStruct.h new file mode 100644 index 0000000..cafd2dc --- /dev/null +++ b/Runtime/Filters/Particles/ParticleStruct.h @@ -0,0 +1,59 @@ +#ifndef PARTICLESTRUCT_H +#define PARTICLESTRUCT_H + +#include "Runtime/Math/Vector3.h" +#include "Runtime/Math/Color.h" +#include "Runtime/Geometry/AABB.h" +#if UNITY_WII +#include "Runtime/Misc/Allocator.h" +#endif + +struct Particle +{ + Vector3f position; //12 + Vector3f velocity; //12 + float size; //4 + float rotation; //4 + float angularVelocity; //4 + float energy; //4 + float startEnergy; //4 + ColorRGBA32 color; //4 +}; + +struct SimpleParticle +{ + Vector3f position; + Vector3f velocity; + float size; + float rotation; + float angularVelocity; + float energy; + float startEnergy; + ColorRGBAf color; +}; + + +// 24 bytes +typedef UNITY_VECTOR(kMemParticles, Particle) ParticleArray; + +struct PrivateParticleInfo +{ + MinMaxAABB aabb; + float maxEmitterParticleSize;// Maximum size of any particle of emitted particles + float maxParticleSize;// max particle size of any particle after particle animation is done + float maxEnergy; + bool useWorldSpace; + bool hadEverEmitOn; // had "emit" flag ever set? + bool isEmitOn; // is "emit" flag currently set? +}; + + +const float kTimeEpsilon = 0.001f; + +inline void KillParticle (ParticleArray& array, int i) +{ + array[i] = array.back (); + array.pop_back (); +} + +#endif diff --git a/Runtime/Filters/Particles/WorldParticleCollider.cpp b/Runtime/Filters/Particles/WorldParticleCollider.cpp new file mode 100644 index 0000000..a209036 --- /dev/null +++ b/Runtime/Filters/Particles/WorldParticleCollider.cpp @@ -0,0 +1,197 @@ +#include "UnityPrefix.h" +#include "Configuration/UnityConfigure.h" +#include "WorldParticleCollider.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" +#include "Runtime/Geometry/Ray.h" +#include "Runtime/Input/TimeManager.h" +#include "ParticleStruct.h" +#include "Runtime/Graphics/Transform.h" +#include "Runtime/Interfaces/IRaycast.h" + +using namespace std; + +#pragma message ("Support collides with") +///@TODO: SUPPORT COLLIDES WITH + +WorldParticleCollider::WorldParticleCollider (MemLabelId label, ObjectCreationMode mode) +: Super(label, mode) +{ +} + +WorldParticleCollider::~WorldParticleCollider () +{ +} + +void WorldParticleCollider::Reset () +{ + Super::Reset (); + m_BounceFactor = 0.5; + m_MinKillVelocity = 0.0F; + m_CollisionEnergyLoss = 0.0F; + + m_CollidesWith.m_Bits = -1; + m_SendCollisionMessage = false; +} + +IMPLEMENT_CLASS (WorldParticleCollider) +IMPLEMENT_OBJECT_SERIALIZE (WorldParticleCollider) + +template<class T> inline +void WorldParticleCollider::Transfer (T& transfer) +{ + Super::Transfer (transfer); + TRANSFER (m_BounceFactor); + TRANSFER (m_CollisionEnergyLoss); + TRANSFER (m_CollidesWith); + TRANSFER (m_SendCollisionMessage); + transfer.Align(); + TRANSFER (m_MinKillVelocity); +} + +void WorldParticleCollider::UpdateParticleCollider( ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime ) +{ + int particleCount = particles.size (); + + float sqrMinKillVelocity = m_MinKillVelocity * m_MinKillVelocity; + // not sure if multiplication of infinity works on every platform + AssertIf (std::numeric_limits<float>::infinity () * std::numeric_limits<float>::infinity () != std::numeric_limits<float>::infinity ()); + + if (privateInfo.useWorldSpace) + { + for (int i=0;i<particleCount;i++) + { + Vector3f& position = particles[i].position; + Vector3f& velocity = particles[i].velocity; + Vector3f delta = velocity * deltaTime; + + float psize = 0.5f * particles[i].size; + float poffset = 0.51f * particles[i].size; + + Ray ray; + ray.SetOrigin (position - delta); + float deltaLength = Magnitude (delta); + if (deltaLength < Vector3f::epsilon) + continue; + + ray.SetDirection (delta / deltaLength); + + float checkLen = deltaLength + psize; + float t = checkLen; + + #if ENABLE_PHYSICS + HitInfo hit; + IRaycast *raycast = GetRaycastInterface(); + if(raycast) + { + if(raycast->Raycast(ray, t, m_CollidesWith.m_Bits, hit)) + { + Particle& particle = particles[i]; + + // Reflect velocity and apply velocity * time left for particle to position + // Test and factor changes into other particle collider + velocity = ReflectVector (m_BounceFactor * velocity, hit.normal); + float fractionLeftForReflection = (checkLen - t) / deltaLength; + + // Place particle slightly above the surface. Otherwise in next frame raycast can + // detect collision again and reflect particle back! + position = hit.intersection + hit.normal * poffset + velocity * (deltaTime * fractionLeftForReflection); + + if (m_SendCollisionMessage) + { + AssertIf (hit.colliderInstanceID == 0); + PPtr<Component> collider_pptr(hit.colliderInstanceID); + Component* collider = collider_pptr; + SendMessage (kParticleCollisionEvent, &collider->GetGameObject (), ClassID (GameObject)); + collider->SendMessage (kParticleCollisionEvent, &GetGameObject (), ClassID (GameObject)); + } + // Update energy + particle.energy -= m_CollisionEnergyLoss; + float sqrVelocity = SqrMagnitude (velocity); + if (particle.energy <= 0.0f || sqrVelocity < sqrMinKillVelocity) { + // Kill particle (replace particle i with last, and continue updating the moved particle) + KillParticle (particles, i); + particleCount = particles.size (); + i--; + continue; + } + privateInfo.aabb.Encapsulate (position); + } + } + + #endif // ENABLE_PHYSICS + } + } + else + { + Matrix4x4f localToWorld = GetComponent (Transform).GetLocalToWorldMatrixNoScale (); + for (int i=0;i<particleCount;i++) + { + Vector3f& position = particles[i].position; + Vector3f& velocity = particles[i].velocity; + + float psize = 0.5f * particles[i].size; + float poffset = 0.51f * particles[i].size; + + Vector3f worldSpaceDelta = localToWorld.MultiplyVector3 (velocity * deltaTime); + Vector3f worldPosition = localToWorld.MultiplyPoint3 (position); + + Ray ray; + ray.SetOrigin (worldPosition - worldSpaceDelta); + float deltaLength = Magnitude (worldSpaceDelta); + if (deltaLength < Vector3f::epsilon) + continue; + + ray.SetDirection (worldSpaceDelta / deltaLength); + + float checkLen = deltaLength + psize; + float t = checkLen; + + #if ENABLE_PHYSICS + HitInfo hit; + IRaycast *raycast = GetRaycastInterface(); + if(raycast) + { + if(raycast->Raycast(ray, t, m_CollidesWith.m_Bits, hit)) + { + Particle& particle = particles[i]; + + // Reflect velocity and apply velocity * time left for particle to position + Vector3f worldVelocity = localToWorld.MultiplyVector3 (velocity); + // Test and factor changes into other particle collider + worldVelocity = ReflectVector (m_BounceFactor * worldVelocity, hit.normal); + float fractionLeftForReflection = (checkLen - t) / deltaLength; + + // Place particle slightly above the surface. Otherwise in next frame raycast can + // detect collision again and reflect particle back! + worldPosition = hit.intersection + hit.normal * poffset + worldVelocity * (deltaTime * fractionLeftForReflection); + + position = localToWorld.InverseMultiplyPoint3Affine (worldPosition); + velocity = localToWorld.InverseMultiplyVector3Affine (worldVelocity); + + if (m_SendCollisionMessage) + { + AssertIf (hit.colliderInstanceID == 0); + PPtr<Component> collider_pptr(hit.colliderInstanceID); + Component* collider = collider_pptr; + SendMessage (kParticleCollisionEvent, &collider->GetGameObject (), ClassID (GameObject)); + collider->SendMessage (kParticleCollisionEvent, &GetGameObject (), ClassID (GameObject)); + } + + // Update energy + particle.energy -= m_CollisionEnergyLoss; + float sqrVelocity = SqrMagnitude (velocity); + if (particle.energy <= 0.0f || sqrVelocity < sqrMinKillVelocity) { + // Kill particle (replace particle i with last, and continue updating the moved particle) + KillParticle (particles, i); + particleCount = particles.size (); + i--; + continue; + } + + privateInfo.aabb.Encapsulate (position); + } + } + #endif // ENABLE_PHYSICS + } + } +} diff --git a/Runtime/Filters/Particles/WorldParticleCollider.h b/Runtime/Filters/Particles/WorldParticleCollider.h new file mode 100644 index 0000000..f6db8af --- /dev/null +++ b/Runtime/Filters/Particles/WorldParticleCollider.h @@ -0,0 +1,33 @@ +#ifndef WORLDPARTICLECOLLIDER_H +#define WORLDPARTICLECOLLIDER_H + +#include "Runtime/BaseClasses/GameObject.h" +#include "ParticleStruct.h" + + + +class WorldParticleCollider : public Unity::Component +{ +public: + REGISTER_DERIVED_CLASS (WorldParticleCollider, Unity::Component) + DECLARE_OBJECT_SERIALIZE (WorldParticleCollider) + + WorldParticleCollider (MemLabelId label, ObjectCreationMode mode); + + virtual void Reset (); + + void UpdateParticleCollider( ParticleArray& particles, PrivateParticleInfo& privateInfo, float deltaTime ); + +private: + /// The velocity at which a particle is killed after the bounced velocity is calculated + float m_MinKillVelocity; + float m_BounceFactor; + /// Seconds of energy a particle loses when colliding + float m_CollisionEnergyLoss; + /// Collides the particles with every collider whose layerMask & m_CollidesWith != 0 + BitField m_CollidesWith; + /// Should we send out a collision message for every particle that has collided? + bool m_SendCollisionMessage; +}; + +#endif |