summaryrefslogtreecommitdiff
path: root/Runtime/Filters/Particles
diff options
context:
space:
mode:
Diffstat (limited to 'Runtime/Filters/Particles')
-rw-r--r--Runtime/Filters/Particles/EllipsoidParticleEmitter.cpp110
-rw-r--r--Runtime/Filters/Particles/EllipsoidParticleEmitter.h35
-rw-r--r--Runtime/Filters/Particles/MeshParticleEmitter.cpp353
-rw-r--r--Runtime/Filters/Particles/MeshParticleEmitter.h42
-rw-r--r--Runtime/Filters/Particles/ParticleAnimator.cpp216
-rw-r--r--Runtime/Filters/Particles/ParticleAnimator.h57
-rw-r--r--Runtime/Filters/Particles/ParticleEmitter.cpp455
-rw-r--r--Runtime/Filters/Particles/ParticleEmitter.h151
-rw-r--r--Runtime/Filters/Particles/ParticleRenderer.cpp630
-rw-r--r--Runtime/Filters/Particles/ParticleRenderer.h103
-rw-r--r--Runtime/Filters/Particles/ParticleStruct.h59
-rw-r--r--Runtime/Filters/Particles/WorldParticleCollider.cpp197
-rw-r--r--Runtime/Filters/Particles/WorldParticleCollider.h33
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