// // ShapeModule.cpp // cocos2d_libs // // Created by 徐俊杰 on 2020/4/24. // #include "rparticle/Modules/ShapeModule.h" //#include "UnityPrefix.h" //#include "rparticle/Modules/ShapeModule.h" #include "rparticle/ParticleSystemUtils.h" #include "rparticle/RParticleSystem.h" //#include "Runtime/Graphics/TriStripper.h" //#include "Runtime/Math/Vector2.h" #include "rparticle/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 "rparticle/Serialize/TransferFunctions/SerializeTransfer.h" #define zero ZERO #define one ONE #define yAxis UNIT_Y #define zAxis UNIT_Z // MinMaxAABB #define m_Max _max #define m_Min _min NS_RRP_BEGIN static const float kMinRadius = 0.001f; 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 = ScaleVec3(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; } //TODO: Mesh stuff /* template 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 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_RadiusThickness(1.0f) , m_BurstSpread(false) , m_SpreadSpaceThickness(1.0f) , m_Length(5.0f) , m_Arc(360.0f) , m_BoxX(1.0f) , m_BoxY(1.0f) , m_BoxZ(1.0f) //TODO: Mesh stuff /* , 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(); //TODO: Mesh stuff /* 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(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(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(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: { float innerRadiusThickness = std::pow(1.0f - m_RadiusThickness, 3.0f); for (int q = fromIndex; q < count; ++q) { float rr = std::pow(RangedRandom(m_Random, innerRadiusThickness, 1.0f), 1.0f / 3.0f); Vector3f pos = RandomUnitVector (random) * (r * rr); 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: { float innerRadiusThickness = std::pow(1.0f - m_RadiusThickness, 3.0f); for (int q = fromIndex; q < count; ++q) { float rr = std::pow(RangedRandom(m_Random, innerRadiusThickness, 1.0f), 1.0f / 3.0f); Vector3f pos = RandomUnitVector (random) * (r * rr); 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: { const float innerRadiusThickness = std::max(kMinRadius, 1.0f - m_RadiusThickness); for (int q = fromIndex; q < count; ++q) { float rr = RangedRandom(m_Random, innerRadiusThickness, 1.0f); Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random)) * rr; 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: { const float innerRadiusThickness = std::max(kMinRadius, 1.0f - m_RadiusThickness); for (int q = fromIndex; q < count; ++q) { float rr = RangedRandom(m_Random, innerRadiusThickness, 1.0f); Vector2f posXY = NormalizeSafe(RandomUnitVector2 (random)) * rr; 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 kCircle: { float arc = CC_DEGREES_TO_RADIANS(m_Arc); float innerRadiusThicknessSqr = std::sqrt(1.0f - m_RadiusThickness); auto burstCount = count - fromIndex; float randomAngle = m_BurstSpread ? Random01(m_Random) * (1 - m_SpreadSpaceThickness) : 0; for (int q = fromIndex; q < count; ++q) { // angle float ra = 0.0f; if (m_BurstSpread) { ra = (Random01(m_Random) * m_SpreadSpaceThickness + randomAngle + q - fromIndex) * (arc / burstCount); } else { ra = Random01(m_Random) * arc; } // distance from center float radiusRandom = std::sqrt(Lerp(innerRadiusThicknessSqr, 1.0f, m_Random.GetFloat())); float rr = (radiusRandom * r); // generate float sra = sin(ra); float cra = cos(ra); Vector3f pos(cra * rr, sra * rr, 0); Vector3f n(cra, sra, 0); 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: { //TODO: Mesh stuff /* if(m_CachedMesh) bounds = m_CachedMesh->GetBounds(0); else bounds = MinMaxAABB(Vector3f::zero, Vector3f::zero); */ break; } default: { AssertBreak(!"Shape not implemented."); } } bounds.m_Min = ScaleVec3(bounds.m_Min, emitterScale); bounds.m_Max = ScaleVec3(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 (m_Type, kSphere, kMax-1); //TODO: Mesh stuff // m_PlacementMode = clamp (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 (RParticleSystem* system, const ParticleSystemReadOnlyState& roState) { //TODO: Mesh stuff /* 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 (RParticleSystem* system) { //TODO: Mesh stuff // m_CachedMesh = NULL; } void ShapeModule::DidModifyMeshData () { //TODO: Mesh stuff /* if (m_CachedMesh == NULL) { m_CachedTriangleData.resize_uninitialized(0); m_CachedVertexData.resize_uninitialized(0); m_CachedTotalTriangleArea = 0; return; } const StrideIterator 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 vertices = m_CachedMesh->GetVertexBegin(); const StrideIterator normals = m_CachedMesh->GetNormalBegin(); const StrideIterator 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 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_RadiusThickness, "radiusThickness"); 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"); //TODO: Mesh stuff /*( // 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) NS_RRP_END