diff options
Diffstat (limited to 'Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp')
-rw-r--r-- | Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp new file mode 100644 index 0000000..3cdb007 --- /dev/null +++ b/Runtime/Graphics/ParticleSystem/Modules/ShapeModule.cpp @@ -0,0 +1,650 @@ +#include "UnityPrefix.h" +#include "ShapeModule.h" +#include "Runtime/Graphics/ParticleSystem/ParticleSystemUtils.h" +#include "Runtime/Graphics/ParticleSystem/ParticleSystem.h" +#include "Runtime/Graphics/TriStripper.h" +#include "Runtime/Math/Vector2.h" +#include "Runtime/Math/Random/Random.h" +#include "Runtime/Geometry/ComputionalGeometry.h" +#include "Runtime/BaseClasses/IsPlaying.h" +#include "Runtime/Utilities/StrideIterator.h" +#include "Runtime/Filters/Mesh/LodMesh.h" +#include "Runtime/Serialize/TransferFunctions/SerializeTransfer.h" + +enum MeshDistributionMode +{ + kDistributionVertex, + kDistributionTriangle, +}; + + +/// This gives a random barycentric coord (on the edge of triangle) +// @TODO: Stupid: Make this in a faster way +inline Vector3f RandomBarycentricCoordEdge (Rand& rand) +{ + float u = rand.GetFloat (); + float v = rand.GetFloat (); + if (u + v > 1.0F) + { + u = 1.0F - u; + v = 1.0F - v; + } + float w = 1.0F - u - v; + + int edge = RangedRandom(rand, 0, 2); + if(0 == edge) + { + v += 0.5f * u; + w += 0.5f * u; + u = 0.0f; + } + else if(1 == edge) + { + u += 0.5f * v; + w += 0.5f * v; + v = 0.0f; + } + else + { + u += 0.5f * w; + v += 0.5f * w; + w = 0.0f; + } + + return Vector3f (u, v, w); +} + + +// TODO: It could make sense to initialize in separated loops. i.e. separate position and velcoity vectors +inline void EmitterStoreData(const Matrix4x4f& localToWorld, const Vector3f& scale, ParticleSystemParticles& ps, size_t q, Vector3f& pos, Vector3f& n, Rand& random, bool randomDirection) +{ + if(randomDirection) + n = RandomUnitVector (random); + + n = NormalizeSafe(n); + + pos = Scale(pos, scale); + + Vector3f vel = Magnitude (ps.velocity[q]) * n; + vel = localToWorld.MultiplyVector3 (vel); + + // @TODO: Sooo... why multiply point and then undo the result of it? Why not just MultiplyVector? + pos = localToWorld.MultiplyPoint3 (pos) - localToWorld.GetPosition(); + ps.position[q] += pos; + ps.velocity[q] = vel; + +#if 0 // WIP code for converting to spherical + Vector3f sp = ps.position[q]; + ps.position[q].x = Sqrt(sp.x*sp.x + sp.y*sp.y + sp.z*sp.z); + ps.position[q].y = acosf(sp.z/ps.position[q].x); + ps.position[q].z = acosf(sp.y/ps.position[q].x); +#endif + + if(ps.usesAxisOfRotation) + { + Vector3f tan = Cross (-n, Vector3f::zAxis); + if (SqrMagnitude (tan) <= 0.01) + tan = Cross (-pos, Vector3f::zAxis); + if (SqrMagnitude (tan) <= 0.01) + tan = Vector3f::yAxis; + ps.axisOfRotation[q] = Normalize (tan); + } +} + +inline void EmitterStoreData(const Matrix4x4f& localToWorld, const Vector3f& scale, ParticleSystemParticles& ps, size_t q, Vector3f& pos, Vector3f& n, ColorRGBA32& color, Rand& random, bool randomDirection) +{ + EmitterStoreData(localToWorld, scale, ps, q, pos, n, random, randomDirection); + ps.color[q] *= color; +} + + +template<MeshDistributionMode distributionMode> +void GetPositionMesh (Vector3f& pos, + Vector3f& n, + ColorRGBA32& color, + const ParticleSystemEmitterMeshVertex* vertexData, + const int vertexCount, + const MeshTriangleData* triangleData, + const UInt32 numPrimitives, + float totalTriangleArea, + Rand& random, + bool edge) +{ + // position/normal of particle is vertex/vertex normal from mesh + if(kDistributionVertex == distributionMode) + { + int vertexIndex = RangedRandom (random, 0, vertexCount); + pos = vertexData[vertexIndex].position; + n = vertexData[vertexIndex].normal; + color = vertexData[vertexIndex].color; + } + else if(kDistributionTriangle == distributionMode) + { + float randomArea = RangedRandom(random, 0.0f, totalTriangleArea); + float accArea = 0.0f; + UInt32 triangleIndex = 0; + + for(UInt32 i = 0; i < numPrimitives; i++) + { + const MeshTriangleData& data = triangleData[i]; + accArea += data.area; + if(accArea >= randomArea) + { + triangleIndex = i; + break; + } + } + + const MeshTriangleData& data = triangleData[triangleIndex]; + UInt16 a = data.indices[0]; + UInt16 b = data.indices[1]; + UInt16 c = data.indices[2]; + + Vector3f barycenter; + if(edge) + barycenter = RandomBarycentricCoordEdge (random); + else + barycenter = RandomBarycentricCoord (random); + + // Interpolate vertex with barycentric coordinate + pos = barycenter.x * vertexData[a].position + barycenter.y * vertexData[b].position + barycenter.z * vertexData[c].position; + n = barycenter.x * vertexData[a].normal + barycenter.y * vertexData[b].normal + barycenter.z * vertexData[c].normal; + + // TODO: Don't convert to floats!!! + ColorRGBAf color1 = vertexData[a].color; + ColorRGBAf color2 = vertexData[b].color; + ColorRGBAf color3 = vertexData[c].color; + color = barycenter.x * color1 + barycenter.y * color2 + barycenter.z * color3; + } +} + +static bool CompareMeshTriangleData (const MeshTriangleData& a, const MeshTriangleData& b) +{ + return (a.area > b.area); +} + +static float BuildMeshAreaTable(MeshTriangleData* triData, const StrideIterator<Vector3f> vertices, const UInt16* indices, int numTriangles) +{ + float result = 0.0f; + for(int i = 0; i < numTriangles; i++) + { + const UInt16 a = indices[i * 3 + 0]; + const UInt16 b = indices[i * 3 + 1]; + const UInt16 c = indices[i * 3 + 2]; + float area = TriangleArea3D (vertices[a], vertices[b], vertices[c]); + result += area; + + triData[i].indices[0] = a; + triData[i].indices[1] = b; + triData[i].indices[2] = c; + triData[i].area = area; + } + + return result; +} + +// ------------------------------------------------------------------------------------------ + +ShapeModule::ShapeModule () : ParticleSystemModule(true) +, m_Type (kCone) +, m_RandomDirection (false) +, m_Angle(25.0f) +, m_Radius(1.0f) +, m_Length(5.0f) +, m_BoxX(1.0f) +, m_BoxY(1.0f) +, m_BoxZ(1.0f) +, m_PlacementMode(kVertex) +, m_CachedMesh(NULL) +, m_MeshNode (NULL) +{ +} + +void ShapeModule::Start (const ParticleSystemReadOnlyState& roState, const ParticleSystemState& state, ParticleSystemParticles& ps, const Matrix4x4f& matrix, size_t fromIndex, float t) +{ + DebugAssert(roState.lengthInSec > 0.0001f); + const float normalizedT = t / roState.lengthInSec; + DebugAssert (normalizedT >= 0.0f); + DebugAssert (normalizedT <= 1.0f); + + Rand& random = GetRandom(); + + if (m_Type == kMesh) + { + if(!m_CachedMesh) + return; + + if(!m_CachedVertexData.size()) + return; + + if(!m_CachedTriangleData.size()) + return; + + const ParticleSystemEmitterMeshVertex* vertexData = &m_CachedVertexData[0]; + const int vertexCount = m_CachedVertexData.size(); + size_t count = ps.array_size (); + switch(m_PlacementMode) + { + case kVertex: + { + for (int q = fromIndex; q < count; ++q) + { + Vector3f pos; + Vector3f n; + ColorRGBA32 color; + GetPositionMesh<kDistributionVertex>(pos, n, color, vertexData, vertexCount, NULL, 0, m_CachedTotalTriangleArea, random, false); + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection); + } + break; + } + case kEdge: + { + for (int q = fromIndex; q < count; ++q) + { + Vector3f pos; + Vector3f n; + ColorRGBA32 color; + GetPositionMesh<kDistributionTriangle>(pos, n, color, vertexData, vertexCount, m_CachedTriangleData.begin(), m_CachedTriangleData.size(), m_CachedTotalTriangleArea, random, true); + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection); + } + break; + } + case kTriangle: + { + for (int q = fromIndex; q < count; ++q) + { + Vector3f pos; + Vector3f n; + ColorRGBA32 color; + GetPositionMesh<kDistributionTriangle>(pos, n, color, vertexData, vertexCount, m_CachedTriangleData.begin(), m_CachedTriangleData.size(), m_CachedTotalTriangleArea, random, false); + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, color, random, m_RandomDirection); + } + break; + } + default: + { + DebugAssert(0 && "PlacementMode Not Supported"); + } + } + } + else + { + const float r = m_Radius; + + float a = Deg2Rad (m_Angle); + float sinA = Sin (a); + float cosA = Cos (a); + float length = m_Length; + + const size_t count = ps.array_size (); + switch(m_Type) + { + case kSphere: + { + for (int q = fromIndex; q < count; ++q) + { + Vector3f pos = RandomPointInsideUnitSphere (random) * r; + Vector3f n = pos; + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection); + } + break; + } + case kSphereShell: + { + for (int q = fromIndex; q < count; ++q) + { + Vector3f pos = RandomUnitVector(random) * r; + Vector3f n = pos; + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection); + } + break; + } + case kHemiSphere: + { + for (int q = fromIndex; q < count; ++q) + { + Vector3f pos = RandomPointInsideUnitSphere (random) * r; + if (pos.z < 0.0f) + pos.z *= -1.0f; + Vector3f n = pos; + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection); + } + break; + } + case kHemiSphereShell: + { + for (int q = fromIndex; q < count; ++q) + { + Vector3f pos = RandomUnitVector (random) * r; + if (pos.z < 0.0f) + pos.z *= -1.0f; + Vector3f n = pos; + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection); + } + break; + } + case kCone: + { + for (int q = fromIndex; q < count; ++q) + { + Vector2f posXY = RandomPointInsideUnitCircle (random); + Vector2f nXY; + if(m_RandomDirection) + nXY = RandomPointInsideUnitCircle (random) * sinA; + else + nXY = Vector2f(posXY.x, posXY.y)* sinA; + Vector3f n (nXY.x, nXY.y, cosA); + Vector3f pos (posXY.x * r, posXY.y * r, 0.0f); + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, false); + } + break; + } + case kConeShell: + { + for (int q = fromIndex; q < count; ++q) + { + Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random)); + + Vector2f nXY; + if(m_RandomDirection) + nXY = RandomPointInsideUnitCircle (random) * sinA; + else + nXY = Vector2f(posXY.x, posXY.y)* sinA; + Vector3f n (nXY.x, nXY.y, cosA); + Vector3f pos (posXY.x * r, posXY.y * r, 0.0f); + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, false); + } + break; + } + case kConeVolume: + { + for (int q = fromIndex; q < count; ++q) + { + Vector2f posXY = RandomPointInsideUnitCircle (random); + Vector2f nXY = Vector2f(posXY.x, posXY.y)* sinA; + Vector3f n (nXY.x, nXY.y, cosA); + Vector3f pos (posXY.x * r, posXY.y * r, 0.0f); + pos += length * Random01(random) * NormalizeSafe(n); + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection); + } + break; + } + case kConeVolumeShell: + { + for (int q = fromIndex; q < count; ++q) + { + Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random)); + Vector2f nXY = Vector2f(posXY.x, posXY.y)* sinA; + Vector3f n (nXY.x, nXY.y, cosA); + Vector3f pos = Vector3f(posXY.x * r, posXY.y * r, 0.0f); + pos += length * Random01(random) * NormalizeSafe(n); + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection); + } + break; + } + case kBox: + { + const Vector3f extents (0.5f * m_BoxX, 0.5f * m_BoxY, 0.5f * m_BoxZ); + for (int q = fromIndex; q < count; ++q) + { + Vector3f pos = RandomPointInsideCube (random, extents); + Vector3f n = Vector3f::zAxis; + EmitterStoreData(matrix, state.emitterScale, ps, q, pos, n, random, m_RandomDirection); + } + } + break; + default: + { + DebugAssert(0 && "Shape not supported"); + } + } + } +} + +void ShapeModule::CalculateProceduralBounds(MinMaxAABB& bounds, const Vector3f& emitterScale, Vector2f minMaxBounds) const +{ + DebugAssert(minMaxBounds.x <= minMaxBounds.y); + + switch(m_Type) + { + case kSphere: + case kSphereShell: + bounds.m_Max = Vector3f(m_Radius, m_Radius, m_Radius); + bounds.m_Min = -bounds.m_Max; + break; + case kHemiSphere: + case kHemiSphereShell: + bounds.m_Max = Vector3f(m_Radius, m_Radius, m_Radius); + bounds.m_Min = Vector3f(-m_Radius, -m_Radius, 0.0f); + break; + case kCone: + case kConeShell: + bounds.m_Max = Vector3f(m_Radius, m_Radius, 0.0f); + bounds.m_Min = -bounds.m_Max; + break; + case kConeVolume: + case kConeVolumeShell: + { + const float a = Deg2Rad (m_Angle); + const float coneRadius2 = m_Radius + m_Length * Sin (a); + const float coneLength = m_Length * Cos (a); + bounds.m_Max = Vector3f(coneRadius2, coneRadius2, coneLength); + bounds.m_Min = -Vector3f(coneRadius2, coneRadius2, 0.0f); + break; + } + case kBox: + bounds.m_Max = Vector3f(m_BoxX, m_BoxY, m_BoxZ) * 0.5f; + bounds.m_Min = -bounds.m_Max; + break; + case kMesh: + { + if(m_CachedMesh) + bounds = m_CachedMesh->GetBounds(0); + else + bounds = MinMaxAABB(Vector3f::zero, Vector3f::zero); + break; + } + default: + { + AssertBreak(!"Shape not implemented."); + } + } + + bounds.m_Min = Scale(bounds.m_Min, emitterScale); + bounds.m_Max = Scale(bounds.m_Max, emitterScale); + + MinMaxAABB speedBounds; + + // Cone and cone shell random direction only deviate inside the bound + if(m_RandomDirection && (m_Type != kCone) && (m_Type != kConeShell)) + { + speedBounds.m_Max = Vector3f::one; + speedBounds.m_Min = -Vector3f::one; + minMaxBounds = Abs(minMaxBounds); + } + else + { + switch(m_Type) + { + case kSphere: + case kSphereShell: + case kMesh: + speedBounds.m_Max = Vector3f::one; + speedBounds.m_Min = -Vector3f::one; + break; + case kHemiSphere: + case kHemiSphereShell: + speedBounds.m_Max = Vector3f::one; + speedBounds.m_Min = Vector3f(-1.0f, -1.0f, 0.0f); + break; + case kCone: + case kConeShell: + case kConeVolume: + case kConeVolumeShell: + { + const float a = Deg2Rad (m_Angle); + const float sinA = Sin (a); + speedBounds.m_Max = Vector3f(sinA, sinA, 1.0f); + speedBounds.m_Min = Vector3f(-sinA, -sinA, 0.0f); + break; + } + case kBox: + speedBounds.m_Max = Vector3f::zAxis; + speedBounds.m_Min = Vector3f::zero; + break; + default: + { + AssertBreak(!"Shape not implemented."); + } + } + } + + MinMaxAABB speedBound; + speedBound.m_Min = bounds.m_Min + speedBounds.m_Min * minMaxBounds.y; + speedBound.m_Max = bounds.m_Max + speedBounds.m_Max * minMaxBounds.y; + bounds.Encapsulate(speedBound); + + MinMaxAABB negSpeedBound; + negSpeedBound.m_Min = speedBounds.m_Min * minMaxBounds.x; + negSpeedBound.m_Max = speedBounds.m_Max * minMaxBounds.x; + speedBound.m_Min = min(negSpeedBound.m_Min, negSpeedBound.m_Max); + speedBound.m_Max = max(negSpeedBound.m_Min, negSpeedBound.m_Max); + bounds.Encapsulate(speedBound); +} + +void ShapeModule::CheckConsistency () +{ + m_Type = clamp<int> (m_Type, kSphere, kMax-1); + m_PlacementMode = clamp<int> (m_PlacementMode, kVertex, kModeMax-1); + + m_Angle = clamp(m_Angle, 0.0f, 90.0f); + m_Radius = max(0.01f, m_Radius); + m_Length = max(0.0f, m_Length); + m_BoxX = max(0.0f, m_BoxX); + m_BoxY = max(0.0f, m_BoxY); + m_BoxZ = max(0.0f, m_BoxZ); +} + +void ShapeModule::AwakeFromLoad (ParticleSystem* system, const ParticleSystemReadOnlyState& roState) +{ + m_MeshNode.RemoveFromList(); + m_MeshNode.SetData(system); + m_CachedMesh = m_Mesh; + if (m_CachedMesh != NULL) + m_CachedMesh->AddObjectUser( m_MeshNode ); + DidModifyMeshData(); + + ResetSeed(roState); +} + +void ShapeModule::ResetSeed(const ParticleSystemReadOnlyState& roState) +{ + if(roState.randomSeed == 0) + m_Random.SetSeed(GetGlobalRandomSeed ()); + else + m_Random.SetSeed(roState.randomSeed); +} + +void ShapeModule::DidDeleteMesh (ParticleSystem* system) +{ + m_CachedMesh = NULL; +} + +void ShapeModule::DidModifyMeshData () +{ + if (m_CachedMesh == NULL) + { + m_CachedTriangleData.resize_uninitialized(0); + m_CachedVertexData.resize_uninitialized(0); + m_CachedTotalTriangleArea = 0; + return; + } + + + const StrideIterator<Vector3f> vertexBuffer = m_CachedMesh->GetVertexBegin(); + const UInt16* indexBuffer = m_CachedMesh->GetSubMeshBuffer16(0); + const SubMesh& submesh = m_CachedMesh->GetSubMeshFast (0); + if (submesh.topology == kPrimitiveTriangleStripDeprecated) + { + const int numTriangles = CountTrianglesInStrip(indexBuffer, submesh.indexCount); + const int capacity = numTriangles * 3; + UNITY_TEMP_VECTOR(UInt16) tempIndices(capacity); + Destripify(indexBuffer, submesh.indexCount, &tempIndices[0], capacity); + m_CachedTriangleData.resize_uninitialized(numTriangles); + m_CachedTotalTriangleArea = BuildMeshAreaTable(m_CachedTriangleData.begin(), vertexBuffer, &tempIndices[0], numTriangles); + } + else if (submesh.topology == kPrimitiveTriangles) + { + const int numTriangles = submesh.indexCount/3; + m_CachedTriangleData.resize_uninitialized(numTriangles); + m_CachedTotalTriangleArea = BuildMeshAreaTable(m_CachedTriangleData.begin(), vertexBuffer, indexBuffer, numTriangles); + } + else + { + m_CachedMesh = NULL; + } + + // Optimization: This sorts so big triangles comes before small, which means finding the right triangle is faster + std::sort(m_CachedTriangleData.begin(), m_CachedTriangleData.begin() + m_CachedTriangleData.size(), CompareMeshTriangleData); + + // Cache vertices + const int vertexCount = m_CachedMesh->GetVertexCount(); + const StrideIterator<Vector3f> vertices = m_CachedMesh->GetVertexBegin(); + const StrideIterator<Vector3f> normals = m_CachedMesh->GetNormalBegin(); + const StrideIterator<ColorRGBA32> colors = m_CachedMesh->GetColorBegin(); + m_CachedVertexData.resize_uninitialized(vertexCount); + for(int i = 0; i < vertexCount; i++) + { + m_CachedVertexData[i].position = vertices[i]; + + if(!normals.IsNull()) + m_CachedVertexData[i].normal = normals[i]; + else + m_CachedVertexData[i].normal = Vector3f::zero; + + if(!colors.IsNull()) + m_CachedVertexData[i].color = colors[i]; + else + m_CachedVertexData[i].color = ColorRGBA32(0xffffffff); + } +} + +Rand& ShapeModule::GetRandom() +{ +#if UNITY_EDITOR + if(!IsWorldPlaying()) + return m_EditorRandom; + else +#endif + return m_Random; +} + +template<class TransferFunction> +void ShapeModule::Transfer (TransferFunction& transfer) +{ + transfer.SetVersion(2); + ParticleSystemModule::Transfer (transfer); + transfer.Transfer (m_Type, "type"); + + // Primitive + transfer.Transfer(m_Radius, "radius"); + transfer.Transfer(m_Angle, "angle"); + transfer.Transfer(m_Length, "length"); + transfer.Transfer(m_BoxX, "boxX"); + transfer.Transfer(m_BoxY, "boxY"); + transfer.Transfer(m_BoxZ, "boxZ"); + + // Mesh + transfer.Transfer (m_PlacementMode, "placementMode"); + TRANSFER (m_Mesh); + + transfer.Transfer (m_RandomDirection, "randomDirection"); transfer.Align(); + + // In Unity 3.5 all cone emitters had random direction set to false, but behaved as if it was true + if(transfer.IsOldVersion(1)) + if(kCone == m_Type) + m_RandomDirection = true; +} + +INSTANTIATE_TEMPLATE_TRANSFER(ShapeModule) + |