// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "MuR/ConvertData.h" #include "MuR/MeshPrivate.h" #include "Math/Plane.h" #include "Math/Ray.h" #include "Math/NumericLimits.h" #include "IndexTypes.h" #include "MuR/MutableTrace.h" #include "MuR/Operations.h" #include "Spatial/MeshAABBTree3.h" #include "MuR/OpMeshSmoothing.h" // TODO: Make the handling of rotations an option. It is more expensive on CPU and memory, and for some // cases it is not required at all. // TODO: Face stretch to scale the deformation per-vertex? // TODO: Support multiple binding influences per vertex, to have smoother deformations. // TODO: Support multiple binding sets, to have different shapes deformations at once. // TODO: Deformation mask, to select the intensisty of the deformation per-vertex. // TODO: This is a reference implementation with ample roof for optimization. namespace mu { template struct FShapeMeshDescriptor { TArray> Positions; TArray> Normals; TArray Triangles; }; using FShapeMeshDescriptorBind = FShapeMeshDescriptor; struct FShapeMeshAdapter { const FShapeMeshDescriptorBind& Mesh; FShapeMeshAdapter(const FShapeMeshDescriptorBind& InMesh ) : Mesh(InMesh) {} int32 MaxTriangleID() const { return Mesh.Triangles.Num(); } int32 MaxVertexID() const { return Mesh.Positions.Num(); } bool IsTriangle(int32 Index) const { return Mesh.Triangles.IsValidIndex(Index); } bool IsVertex(int32 Index) const { return Mesh.Positions.IsValidIndex(Index); } int32 TriangleCount() const { return Mesh.Triangles.Num(); } FORCEINLINE int32 VertexCount() const { return Mesh.Positions.Num(); } FORCEINLINE uint64 GetChangeStamp() const { return 1; } FORCEINLINE UE::Geometry::FIndex3i GetTriangle(int32 Index) const { return Mesh.Triangles[Index]; } FORCEINLINE FVector3d GetVertex(int32 Index) const { return Mesh.Positions[Index]; } FORCEINLINE void GetTriVertices(int32 TriIndex, FVector3d& V0, FVector3d& V1, FVector3d& V2) const { const UE::Geometry::FIndex3i& Indices = Mesh.Triangles[TriIndex]; V0 = Mesh.Positions[Indices.A]; V1 = Mesh.Positions[Indices.B]; V2 = Mesh.Positions[Indices.C]; } }; using FShapeMeshTree = UE::Geometry::TMeshAABBTree3; // Structure used for vertex bing data in vertexbuffers for reshape operations. struct FReshapeVertexBindingData { // Barycentric coordinates on the shape triangle float S, T; // Distance along the normals of the shape triangle. FVector3f D; // Index of the shape triangle. int32 Triangle; // Used to calculate the rotation to apply to the reshaped vertex tangent space. float NS, NT; FVector3f NormalD; // Bind point, if the point belongs to a rigid cluster, the attachment point, otherwise // the original point. FVector3f AttachmentPoint; // Weight of the effect for this binding. Ranged between 0 and 1 where 0 denotes no effect at all and 1 // full effect. This weight should be proportional to the confidence we have that the binding data is valid. float Weight; }; static_assert(sizeof(FReshapeVertexBindingData) == 4*15); struct FReshapeVertexBindingDataBufferDescriptor { constexpr static int32 ElementSize = sizeof(FReshapeVertexBindingData); constexpr static int32 Channels = 4; constexpr static EMeshBufferSemantic Semantics[Channels] = { EMeshBufferSemantic::BarycentricCoords, EMeshBufferSemantic::Distance, EMeshBufferSemantic::TriangleIndex, EMeshBufferSemantic::Other }; constexpr static EMeshBufferFormat Formats[Channels] = { EMeshBufferFormat::Float32, EMeshBufferFormat::Float32, EMeshBufferFormat::Int32, EMeshBufferFormat::Float32 }; constexpr static int32 Components[Channels] = { 2, 3, 1, 2+3+3+1 }; constexpr static int32 Offsets[Channels] = { 0, 4*2, (2+3)*4, (2+3+1)*4}; const int32 SemanticIndices[Channels] = { 0, 0, 0, 0 }; FReshapeVertexBindingDataBufferDescriptor(int32 DataSetIndex) : SemanticIndices{ DataSetIndex, DataSetIndex, DataSetIndex, DataSetIndex } { } }; struct FReshapePointBindingData { float S, T; FVector3f D; int32 Triangle; float Weight; }; static_assert(sizeof(FReshapePointBindingData) == 24+4); struct FReshapePointBindingDataBufferDescriptor { constexpr static int32 ElementSize = sizeof(FReshapeVertexBindingData); constexpr static int32 Channels = 4; constexpr static EMeshBufferSemantic Semantics[Channels] = { EMeshBufferSemantic::BarycentricCoords, EMeshBufferSemantic::Distance, EMeshBufferSemantic::TriangleIndex, EMeshBufferSemantic::Other }; constexpr static EMeshBufferFormat Formats[Channels] = { EMeshBufferFormat::Float32, EMeshBufferFormat::Float32, EMeshBufferFormat::Int32, EMeshBufferFormat::Float32 }; constexpr static int32 Components[Channels] = { 2, 3, 1, 1 }; constexpr static int32 Offsets[Channels] = { 0, 8, 20, 24 }; const int32 SemanticIndices[Channels] = { 0, 0, 0, 0 }; FReshapePointBindingDataBufferDescriptor(int32 DataSetIndex) : SemanticIndices{ DataSetIndex, DataSetIndex, DataSetIndex, DataSetIndex } { } }; struct FIntBufferDescriptor { constexpr static int32 ElementSize = sizeof(int32); constexpr static int32 Channels = 1; constexpr static EMeshBufferSemantic Semantics[Channels] = { EMeshBufferSemantic::Other }; constexpr static EMeshBufferFormat Formats[Channels] = { EMeshBufferFormat::Int32 }; constexpr static int32 Components[Channels] = { 1 }; constexpr static int32 Offsets[Channels] = { 0 }; constexpr static int32 SemanticIndices[Channels] = { 0 }; FIntBufferDescriptor() { } }; using ReshapePoseBindingType = FReshapePointBindingData; using ReshapePhysicsBindingType = FReshapePointBindingData; // Structure used for vertex bing data in vertexbuffers for Clip deform operations. struct FClipDeformVertexBindingData { // Barycentric coordinates on the shape triangle float S, T; // Index of the shape triangle. int32 Triangle; float Weight; }; static_assert(sizeof(FClipDeformVertexBindingData) == 16); struct FClipDeformVertexBindingDataBufferDescriptor { constexpr static int32 Channels = 3; constexpr static EMeshBufferSemantic Semantics[Channels] = { EMeshBufferSemantic::BarycentricCoords, EMeshBufferSemantic::TriangleIndex, EMeshBufferSemantic::Other }; constexpr static EMeshBufferFormat Formats[Channels] = { EMeshBufferFormat::Float32, EMeshBufferFormat::Int32, EMeshBufferFormat::Float32 }; constexpr static int32 Components[Channels] = { 2, 1, 1 }; constexpr static int32 Offsets[Channels] = { 0, 8, 12 }; const int32 SemanticIndices[Channels] = { 0, 0, 0 }; FClipDeformVertexBindingDataBufferDescriptor(int32 DataSetIndex) : SemanticIndices{ DataSetIndex, DataSetIndex, DataSetIndex } { } }; struct FMeshBindColorChannelUsageMasks { uint32 MaskWeight = 0; uint32 ClusterId = 0; }; //--------------------------------------------------------------------------------------------- //! Generate the mesh-shape binding data for Reshape operations //--------------------------------------------------------------------------------------------- inline float GetVertexMaskWeight(const UntypedMeshBufferIteratorConst& ColorIter, const FMeshBindColorChannelUsageMasks& ChannelUsages) { check(ColorIter.GetFormat() == EMeshBufferFormat::UInt8 || ColorIter.GetFormat() == EMeshBufferFormat::NUInt8); check(ColorIter.GetComponents() == 4); check(ColorIter.ptr()); const uint32 Value = *reinterpret_cast(ColorIter.ptr()); return static_cast((Value >> FMath::CountTrailingZeros(ChannelUsages.MaskWeight) & 0xFF)) / 255.0f; } inline uint32 GetVertexClusterId(const UntypedMeshBufferIteratorConst& ColorIter, const FMeshBindColorChannelUsageMasks& ChannelUsages) { check(ColorIter.GetFormat() == EMeshBufferFormat::UInt8 || ColorIter.GetFormat() == EMeshBufferFormat::NUInt8); check(ColorIter.GetComponents() == 4); check(ColorIter.ptr()); const uint32 MaskedValue = *reinterpret_cast(ColorIter.ptr()) & ChannelUsages.ClusterId; // Set all unused bits to 1 so we have a consistent value, in that case white, for the non clustered vertices. return MaskedValue | ~ChannelUsages.ClusterId; } FORCEINLINE FVector2f ComputeBarycentric(const FVector3f& Point, const FVector3f& A, const FVector3f& B, const FVector3f& C) { const FVector3f TriNorm = FVector3f::CrossProduct(B - A, C - A); const float TriNormSizeSquared = TriNorm.SizeSquared(); // Return the center of the triangle if the area is very small. if (TriNormSizeSquared <= UE_SMALL_NUMBER) { return FVector2f(1.0f/3.0f); } const float AreaABCInv = FMath::InvSqrt(TriNormSizeSquared); const FVector3f N = TriNorm * AreaABCInv; const float AreaPBC = FVector3f::DotProduct(N, FVector3f::CrossProduct(B - Point, C - Point)); const float AreaPCA = FVector3f::DotProduct(N, FVector3f::CrossProduct(C - Point, A - Point)); return FVector2f(AreaPBC, AreaPCA) * AreaABCInv; } inline void BindReshapePoint( FShapeMeshTree& ShapeMeshTree, const FVector3f& Point, const FVector3f& Normal, float MaskWeight, FReshapeVertexBindingData& OutBindData, const float ValidityTolerance = UE_KINDA_SMALL_NUMBER) { const FShapeMeshDescriptorBind& ShapeMesh = ShapeMeshTree.GetMesh()->Mesh; OutBindData.S = 0.0f; OutBindData.T = 0.0f; OutBindData.Triangle = -1; OutBindData.AttachmentPoint = Point; float Weight = MaskWeight; if (FMath::IsNearlyZero(MaskWeight)) { return; } double DistSqr = 0.0; int32 FoundIndex = ShapeMeshTree.FindNearestTriangle(FVector3d(Point), DistSqr); if (FoundIndex < 0) { return; } // Calculate the binding data of the base mesh vertex to its bound shape triangle UE::Geometry::FIndex3i Triangle = ShapeMesh.Triangles[FoundIndex]; // Project on the triangle, but using the vertex normals. // See reference implementation for details. FVector3f TriangleA = (FVector3f)ShapeMesh.Positions[Triangle.A]; FVector3f TriangleB = (FVector3f)ShapeMesh.Positions[Triangle.B]; FVector3f TriangleC = (FVector3f)ShapeMesh.Positions[Triangle.C]; FPlane4f TrianglePlane(TriangleA, TriangleB, TriangleC); FVector3f PlaneNormal = TrianglePlane.GetNormal(); FPlane4f VertexPlane(Point, PlaneNormal); FPlane4f NormalPlane(Point + Normal, PlaneNormal); // T1 = Triangle projected on the vertex plane along the triangle vertex normals OutBindData.D = FVector3f { FMath::RayPlaneIntersectionParam(TriangleA, ShapeMesh.Normals[Triangle.A], VertexPlane), FMath::RayPlaneIntersectionParam(TriangleB, ShapeMesh.Normals[Triangle.B], VertexPlane), FMath::RayPlaneIntersectionParam(TriangleC, ShapeMesh.Normals[Triangle.C], VertexPlane) }; OutBindData.NormalD = FVector3f { FMath::RayPlaneIntersectionParam(TriangleA, ShapeMesh.Normals[Triangle.A], NormalPlane), FMath::RayPlaneIntersectionParam(TriangleB, ShapeMesh.Normals[Triangle.B], NormalPlane), FMath::RayPlaneIntersectionParam(TriangleC, ShapeMesh.Normals[Triangle.C], NormalPlane) }; FVector2f PositionBarycentric = ComputeBarycentric(Point, TriangleA + ShapeMesh.Normals[Triangle.A] * OutBindData.D.X, TriangleB + ShapeMesh.Normals[Triangle.B] * OutBindData.D.Y, TriangleC + ShapeMesh.Normals[Triangle.C] * OutBindData.D.Z); OutBindData.S = PositionBarycentric.X; OutBindData.T = PositionBarycentric.Y; FVector2f NormalBarycentric = ComputeBarycentric((Point + Normal), TriangleA + ShapeMesh.Normals[Triangle.A] * OutBindData.NormalD.X, TriangleB + ShapeMesh.Normals[Triangle.B] * OutBindData.NormalD.Y, TriangleC + ShapeMesh.Normals[Triangle.C] * OutBindData.NormalD.Z); OutBindData.NS = NormalBarycentric.X; OutBindData.NT = NormalBarycentric.Y; OutBindData.Triangle = FoundIndex; OutBindData.Weight = FMath::Clamp(MaskWeight, 0.0f, 1.0f); OutBindData.Triangle = FMath::IsNearlyZero(OutBindData.Weight) ? -1 : OutBindData.Triangle; } //--------------------------------------------------------------------------------------------- //! Find mesh clusters. //! Colour {1,1,1,1} is reserved for the non rigid cluster //--------------------------------------------------------------------------------------------- inline void FindRigidClusters(const FMesh* Mesh, const FMeshBindColorChannelUsageMasks& ColorUsageMasks, TArray>& OutClusters, int32& OutNonRigidClusterIdx) { MUTABLE_CPUPROFILER_SCOPE(MeshFindRigidClusters); constexpr uint32 NonRigidId = ~0; OutNonRigidClusterIdx = -1; const UntypedMeshBufferIteratorConst ItColorBase(Mesh->GetVertexBuffers(), EMeshBufferSemantic::Color); if (ItColorBase.ptr()) { const int32 VertexCount = Mesh->GetVertexCount(); { int32 VertexIndex = 0; for (; VertexIndex < VertexCount; ++VertexIndex) { if (NonRigidId != GetVertexClusterId(ItColorBase + VertexIndex, ColorUsageMasks)) { break; } } // If all equal to the non rigid we are done. if (VertexIndex == VertexCount) { return; } } OutClusters.Empty(16); TArray>& ClusterData = OutClusters; TMap ClusterSet; for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex) { const uint32 ClusterId = GetVertexClusterId(ItColorBase + VertexIndex, ColorUsageMasks); int32& ClusterIdx = ClusterSet.FindOrAdd(ClusterId, INDEX_NONE); TArray& Cluster = ClusterIdx < 0 ? ClusterData.Emplace_GetRef() : ClusterData[ClusterIdx]; if (ClusterIdx < 0) { ClusterIdx = ClusterData.Num() - 1; Cluster.Reserve(32); } Cluster.Add(VertexIndex); } // Cluster id 0xFFFFFFFF is reserved for the nonrigid cluster. The choice if this value is not arbitrary, // meshes without colour will get white as default. const int32* NonRigidClusterIdxFound = ClusterSet.Find(NonRigidId); // If not found, add an empty cluster for the non rigid id. OutNonRigidClusterIdx = NonRigidClusterIdxFound ? *NonRigidClusterIdxFound : ClusterData.Emplace(); } } inline void FindBindingForCluster( const FMesh* BaseMesh, FShapeMeshTree& ShapeMeshTree, const TArray& Cluster, FReshapeVertexBindingData& OutBindingData, float BindTolerance) { const UntypedMeshBufferIteratorConst ItPositionBase(BaseMesh->GetVertexBuffers(), EMeshBufferSemantic::Position); FBox3f ClusterBoundingBox(ForceInit); for (const int32& V : Cluster) { ClusterBoundingBox += (ItPositionBase + V).GetAsVec3f(); } FVector3f BoundPoint = ClusterBoundingBox.GetCenter(); // Mask weight is set on a vertex by vertex basis, ignore weight for the shared data. // This will be filled in afterwards. constexpr float MaskWeight = 1.0f; BindReshapePoint(ShapeMeshTree, BoundPoint, FVector3f::ZAxisVector, MaskWeight, OutBindingData, BindTolerance); } inline TTuple, TArray, TArray> BindPhysicsBodies( TArray PhysicsBodies, FShapeMeshTree& ShapeMeshTree, const FMesh* pMesh, const TArray& PhysicsToDeform ) { TTuple, TArray, TArray> ReturnValue; TArray& BodiesToDeformIndices = ReturnValue.Get<1>(); TArray& BodiesToDeformOffsets = ReturnValue.Get<2>(); const int32 NumPhysicsBodies = PhysicsBodies.Num(); BodiesToDeformIndices.Reserve(NumPhysicsBodies * PhysicsToDeform.Num()); BodiesToDeformOffsets.SetNum(NumPhysicsBodies + 1); BodiesToDeformOffsets[0] = 0; for (int32 PhysicsBodyIndex = 0; PhysicsBodyIndex < NumPhysicsBodies; ++PhysicsBodyIndex) { if (const FPhysicsBody* Body = PhysicsBodies[PhysicsBodyIndex]) { const int32 NumBodies = PhysicsBodies[PhysicsBodyIndex]->GetBodyCount(); for (int32 BodyIndex = 0; BodyIndex < NumBodies; ++BodyIndex) { if (PhysicsToDeform.Contains(Body->GetBodyBoneId(BodyIndex))) { BodiesToDeformIndices.Add(BodyIndex); } } } BodiesToDeformOffsets[PhysicsBodyIndex + 1] = BodiesToDeformIndices.Num(); } int32 TotalNumPoints = 0; for (int32 PhysicsBodyIndex = 0; PhysicsBodyIndex < NumPhysicsBodies; ++PhysicsBodyIndex) { const int32 IndicesBegin = BodiesToDeformOffsets[PhysicsBodyIndex]; const int32 IndicesEnd = BodiesToDeformOffsets[PhysicsBodyIndex + 1]; TArrayView BodyIndices = TArrayView( BodiesToDeformIndices.GetData() + IndicesBegin, IndicesEnd - IndicesBegin); const FPhysicsBody* Body = PhysicsBodies[PhysicsBodyIndex]; int32 BodyNumPoints = 0; for (const int32 I : BodyIndices) { BodyNumPoints += Body->GetSphereCount(I) * 6; BodyNumPoints += Body->GetBoxCount(I) * 14; BodyNumPoints += Body->GetSphylCount(I) * 14; BodyNumPoints += Body->GetTaperedCapsuleCount(I) * 14; const int32 ConvexCount = Body->GetConvexCount(I); for (int C = 0; C < ConvexCount; ++C) { TArrayView Vertices; TArrayView Indices; FTransform3f Transform; Body->GetConvex(I, C, Vertices, Indices, Transform); BodyNumPoints += Vertices.Num(); } } TotalNumPoints += BodyNumPoints; } TArray Points; Points.SetNumUninitialized(TotalNumPoints); // Bone transform needs to be applied to the body's sample points so they are in mesh space. // Create a point soup to be deformed based on the shapes in the aggregate. int32 AddedPoints = 0; for (int32 PhysicsBodyIndex = 0; PhysicsBodyIndex < NumPhysicsBodies; ++PhysicsBodyIndex) { const FPhysicsBody* Body = PhysicsBodies[PhysicsBodyIndex]; const int32 IndicesBegin = BodiesToDeformOffsets[PhysicsBodyIndex]; const int32 IndicesEnd = BodiesToDeformOffsets[PhysicsBodyIndex + 1]; TArrayView BodyIndices = TArrayView( BodiesToDeformIndices.GetData() + IndicesBegin, IndicesEnd - IndicesBegin); for (const int32 B : BodyIndices) { int32 BoneIndex = pMesh->FindBonePose(Body->GetBodyBoneId(B)); FTransform3f T = FTransform3f::Identity; if (BoneIndex > 0) { pMesh->GetBonePoseTransform(BoneIndex, T); } const int32 SphereCount = Body->GetSphereCount(B); for (int32 I = 0; I < SphereCount; ++I, AddedPoints += 6) { FVector3f P; float R; Body->GetSphere(B, I, P, R); Points[AddedPoints + 0] = T.TransformPosition(P + FVector3f(R, 0.0f, 0.0f)); Points[AddedPoints + 1] = T.TransformPosition(P - FVector3f(R, 0.0f, 0.0f)); Points[AddedPoints + 2] = T.TransformPosition(P + FVector3f(0.0f, R, 0.0f)); Points[AddedPoints + 3] = T.TransformPosition(P - FVector3f(0.0f, R, 0.0f)); Points[AddedPoints + 4] = T.TransformPosition(P + FVector3f(0.0f, 0.0f, R)); Points[AddedPoints + 5] = T.TransformPosition(P - FVector3f(0.0f, 0.0f, R)); } const int32 BoxCount = Body->GetBoxCount(B); for (int32 I = 0; I < BoxCount; ++I, AddedPoints += 14) { FVector3f P; FQuat4f Q; FVector3f S; Body->GetBox(B, I, P, Q, S); const FVector3f BasisX = Q.RotateVector(FVector3f::UnitX()); const FVector3f BasisY = Q.RotateVector(FVector3f::UnitY()); const FVector3f BasisZ = Q.RotateVector(FVector3f::UnitZ()); Points[AddedPoints + 0] = T.TransformPosition(P + BasisX * S.X + BasisY * S.Y + BasisZ * S.Z); Points[AddedPoints + 1] = T.TransformPosition(P + BasisX * S.X - BasisY * S.Y + BasisZ * S.Z); Points[AddedPoints + 2] = T.TransformPosition(P - BasisX * S.X + BasisY * S.Y + BasisZ * S.Z); Points[AddedPoints + 3] = T.TransformPosition(P - BasisX * S.X - BasisY * S.Y + BasisZ * S.Z); Points[AddedPoints + 4] = T.TransformPosition(P + BasisX * S.X + BasisY * S.Y - BasisZ * S.Z); Points[AddedPoints + 5] = T.TransformPosition(P + BasisX * S.X - BasisY * S.Y - BasisZ * S.Z); Points[AddedPoints + 6] = T.TransformPosition(P - BasisX * S.X + BasisY * S.Y - BasisZ * S.Z); Points[AddedPoints + 7] = T.TransformPosition(P - BasisX * S.X - BasisY * S.Y - BasisZ * S.Z); Points[AddedPoints + 8] = T.TransformPosition(P + BasisX * S.X); Points[AddedPoints + 9] = T.TransformPosition(P + BasisY * S.Y); Points[AddedPoints + 10] = T.TransformPosition(P + BasisZ * S.Z); Points[AddedPoints + 11] = T.TransformPosition(P - BasisX * S.X); Points[AddedPoints + 12] = T.TransformPosition(P - BasisY * S.Y); Points[AddedPoints + 13] = T.TransformPosition(P - BasisZ * S.Z); } const int32 SphylCount = Body->GetSphylCount(B); for (int32 I = 0; I < SphylCount; ++I, AddedPoints += 14) { FVector3f P; FQuat4f Q; float R; float L; Body->GetSphyl(B, I, P, Q, R, L); const float H = L * 0.5f; const FVector3f BasisX = Q.RotateVector(FVector3f::UnitX()); const FVector3f BasisY = Q.RotateVector(FVector3f::UnitY()); const FVector3f BasisZ = Q.RotateVector(FVector3f::UnitZ()); // Top and Bottom Points[AddedPoints + 0] = T.TransformPosition(P + BasisZ * (H + R)); Points[AddedPoints + 1] = T.TransformPosition(P - BasisZ * (H + R)); // Top ring Points[AddedPoints + 2] = T.TransformPosition(P + BasisX * R + BasisZ * H); Points[AddedPoints + 3] = T.TransformPosition(P - BasisX * R + BasisZ * H); Points[AddedPoints + 4] = T.TransformPosition(P + BasisY * R + BasisZ * H); Points[AddedPoints + 5] = T.TransformPosition(P - BasisY * R + BasisZ * H); // Center ring Points[AddedPoints + 6] = T.TransformPosition(P + BasisX * R); Points[AddedPoints + 7] = T.TransformPosition(P - BasisX * R); Points[AddedPoints + 8] = T.TransformPosition(P + BasisY * R); Points[AddedPoints + 9] = T.TransformPosition(P - BasisY * R); // Bottom ring Points[AddedPoints + 10] = T.TransformPosition(P + BasisX * R - BasisZ * H); Points[AddedPoints + 11] = T.TransformPosition(P - BasisX * R - BasisZ * H); Points[AddedPoints + 12] = T.TransformPosition(P + BasisY * R - BasisZ * H); Points[AddedPoints + 13] = T.TransformPosition(P - BasisY * R - BasisZ * H); } const int32 TaperedCapsuleCount = Body->GetTaperedCapsuleCount(B); for (int32 I = 0; I < TaperedCapsuleCount; ++I, AddedPoints += 14) { FVector3f P; FQuat4f Q; float R0; float R1; float L; Body->GetTaperedCapsule(B, I, P, Q, R0, R1, L); const float H = L * 0.5f; const float RCenter = (R0 + R1) * 0.5f; const FVector3f BasisX = Q.RotateVector(FVector3f::UnitX()); const FVector3f BasisY = Q.RotateVector(FVector3f::UnitY()); const FVector3f BasisZ = Q.RotateVector(FVector3f::UnitZ()); // Top and Bottom Points[AddedPoints + 0] = T.TransformPosition(P + BasisZ * (H + R0)); Points[AddedPoints + 1] = T.TransformPosition(P - BasisZ * (H + R1)); // Top ring Points[AddedPoints + 2] = T.TransformPosition(P + BasisX * R0 + BasisZ * H); Points[AddedPoints + 3] = T.TransformPosition(P - BasisX * R0 + BasisZ * H); Points[AddedPoints + 4] = T.TransformPosition(P + BasisY * R0 + BasisZ * H); Points[AddedPoints + 5] = T.TransformPosition(P - BasisY * R0 + BasisZ * H); // Center ring Points[AddedPoints + 6] = T.TransformPosition(P + BasisX * RCenter); Points[AddedPoints + 7] = T.TransformPosition(P - BasisX * RCenter); Points[AddedPoints + 8] = T.TransformPosition(P + BasisY * RCenter); Points[AddedPoints + 9] = T.TransformPosition(P - BasisY * RCenter); // Bottom ring Points[AddedPoints + 10] = T.TransformPosition(P + BasisX * R1 - BasisZ * H); Points[AddedPoints + 11] = T.TransformPosition(P - BasisX * R1 - BasisZ * H); Points[AddedPoints + 12] = T.TransformPosition(P + BasisY * R1 - BasisZ * H); Points[AddedPoints + 13] = T.TransformPosition(P - BasisY * R1 - BasisZ * H); } const int32 ConvexCount = Body->GetConvexCount(B); for (int32 I = 0; I < ConvexCount; ++I) { TArrayView VerticesView; TArrayView IndicesView; FTransform3f ConvexT; Body->GetConvex(B, I, VerticesView, IndicesView, ConvexT); ConvexT = T * ConvexT; for (const FVector3f& P : VerticesView) { Points[AddedPoints++] = ConvexT.TransformPosition(P); } } } } TArray& PhysicsBodyBindData = ReturnValue.Get<0>(); PhysicsBodyBindData.SetNumUninitialized(TotalNumPoints); FReshapeVertexBindingData VertexBindData; for (int32 PointIndex = 0; PointIndex < TotalNumPoints; ++PointIndex) { constexpr float MaskWeight = 1.0f; BindReshapePoint(ShapeMeshTree, Points[PointIndex], FVector3f::ZAxisVector, MaskWeight, VertexBindData, 0.1f); PhysicsBodyBindData[PointIndex] = FReshapePointBindingData { VertexBindData.S, VertexBindData.T, VertexBindData.D, VertexBindData.Triangle, VertexBindData.Weight }; } return ReturnValue; } inline TArray BindVerticesReshape( const FMesh* BaseMesh, FShapeMeshTree& ShapeMeshTree, const FMeshBindColorChannelUsageMasks& ColorUsageMasks) { UE::Geometry::FAxisAlignedBox3d ShapeAABBox = ShapeMeshTree.GetBoundingBox(); const float BindValidityTolerance = ShapeAABBox.MaxDim() < 1.0 ? UE_KINDA_SMALL_NUMBER : static_cast(ShapeAABBox.MaxDim()) * 1e-3f; TArray> VertexClusters; int32 NonRigidClusterIdx = -1; if (ColorUsageMasks.ClusterId != 0) { FindRigidClusters(BaseMesh, ColorUsageMasks, VertexClusters, NonRigidClusterIdx); } // Find nearest shape triangle for each base mesh vertex const int32 MeshVertexCount = BaseMesh->GetVertexCount(); TArray BindData; { MUTABLE_CPUPROFILER_SCOPE(Project); BindData.SetNum(MeshVertexCount); const UntypedMeshBufferIteratorConst ItPositionBase(BaseMesh->GetVertexBuffers(), EMeshBufferSemantic::Position); const UntypedMeshBufferIteratorConst ItNormalBase(BaseMesh->GetVertexBuffers(), EMeshBufferSemantic::Normal); // Disable vertex color reads if the color is not used for mask weights. const UntypedMeshBufferIteratorConst ItColorBase = ColorUsageMasks.MaskWeight == 0 ? UntypedMeshBufferIteratorConst() : UntypedMeshBufferIteratorConst(BaseMesh->GetVertexBuffers(), EMeshBufferSemantic::Color); // Special case for non rigid parts // This indicates that we don't care about rigid parts, // only one clusters is found or there is no cluster data or rigid parts is disabled. if (NonRigidClusterIdx < 0) { const int32 VertexCount = BaseMesh->GetVertexCount(); for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex) { const FVector3f VertexPosition = (ItPositionBase + VertexIndex).GetAsVec3f(); const FVector3f VertexNormal = ItNormalBase.ptr() ? (ItNormalBase + VertexIndex).GetAsVec3f() : FVector3f::ZAxisVector; const float MaskWeight = ItColorBase.ptr() ? GetVertexMaskWeight(ItColorBase + VertexIndex, ColorUsageMasks) : 1.0f; BindReshapePoint(ShapeMeshTree, VertexPosition, VertexNormal, MaskWeight, BindData[VertexIndex], BindValidityTolerance); } } else { TArray& NonRigidCluster = VertexClusters[NonRigidClusterIdx]; const int32 NonRigidVertexCount = NonRigidCluster.Num(); for (int32 I = 0; I < NonRigidVertexCount; ++I) { const int32 VertexIndex = NonRigidCluster[I]; const FVector3f VertexPosition = (ItPositionBase + VertexIndex).GetAsVec3f(); const FVector3f VertexNormal = ItNormalBase.ptr() ? (ItNormalBase + VertexIndex).GetAsVec3f() : FVector3f::ZAxisVector; const float MaskWeight = ItColorBase.ptr() ? GetVertexMaskWeight(ItColorBase + VertexIndex, ColorUsageMasks) : 1.0f; BindReshapePoint(ShapeMeshTree, VertexPosition, VertexNormal, MaskWeight, BindData[VertexIndex], BindValidityTolerance); } // Remove data form the non rigid cluster so it ios not processed in the rigid parts binding step. NonRigidCluster.Reset(); for (const TArray& RigidCluster : VertexClusters) { FReshapeVertexBindingData ClusterBinding; const int32 ClusterVertexCount = RigidCluster.Num(); if (ClusterVertexCount) { FindBindingForCluster(BaseMesh, ShapeMeshTree, RigidCluster, ClusterBinding, BindValidityTolerance); // Copy cluster binding to every vertex of the cluster modifing weight if the vertex color is used. for (int32 I = 0; I < ClusterVertexCount; ++I) { const int32 VertexIndex = RigidCluster[I]; const float MaskWeight = ItColorBase.ptr() ? GetVertexMaskWeight(ItColorBase + VertexIndex, ColorUsageMasks) : 1.0f; ClusterBinding.Weight = FMath::Min(ClusterBinding.Weight, MaskWeight); // Invalidate binding for very small weights. ClusterBinding.Triangle = FMath::IsNearlyZero(ClusterBinding.Weight) ? -1 : ClusterBinding.Triangle; BindData[VertexIndex] = ClusterBinding; } } } } } return BindData; } inline void GenerateAndAddLaplacianData(FMesh& InOutMesh) { MUTABLE_CPUPROFILER_SCOPE(GenerateLaplacianData); // Storage for buffers in a format different than the supported one. // Not used if the buffer data is compatible. The data will always be accessed using a // view regardless of compatibility. TArray ConvertedVerticesStorage; TArray ConvertedIndicesStorage; TArrayView VerticesView; { const UntypedMeshBufferIteratorConst PositionBegin(InOutMesh.GetVertexBuffers(), EMeshBufferSemantic::Position); const int32 NumVertices = InOutMesh.GetVertexBuffers().GetElementCount(); const bool bIsCompatibleBuffer = PositionBegin.GetElementSize() == sizeof(FVector3f) && PositionBegin.GetFormat() == EMeshBufferFormat::Float32 && PositionBegin.GetComponents() == 3; const bool bIsAlignmentGood = reinterpret_cast(PositionBegin.ptr()) % alignof(FVector3f) == 0; if (!bIsCompatibleBuffer || !bIsAlignmentGood) { ConvertedVerticesStorage.SetNumUninitialized(NumVertices); for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { ConvertedVerticesStorage[VertexIndex] = (PositionBegin + VertexIndex).GetAsVec3f(); } VerticesView = TArrayView(ConvertedVerticesStorage.GetData(), NumVertices); } else { VerticesView = TArrayView(reinterpret_cast(PositionBegin.ptr()), NumVertices); } } TArrayView IndicesView; { const UntypedMeshBufferIteratorConst IndicesBegin(InOutMesh.GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); const int32 NumIndices = InOutMesh.GetIndexBuffers().GetElementCount(); const bool bIsCompatibleBuffer = IndicesBegin.GetElementSize() == sizeof(uint32) && IndicesBegin.GetFormat() == EMeshBufferFormat::UInt32 && IndicesBegin.GetComponents() == 1; const bool bIsAlignmentGood = reinterpret_cast(IndicesBegin.ptr()) % alignof(uint32) == 0; if (!bIsCompatibleBuffer || !bIsAlignmentGood) { ConvertedIndicesStorage.SetNumUninitialized(NumIndices); for (int32 I = 0; I < NumIndices; ++I) { ConvertedIndicesStorage[I] = (IndicesBegin + I).GetAsUINT32(); } IndicesView = TArrayView(ConvertedIndicesStorage.GetData(), ConvertedIndicesStorage.Num()); } else { IndicesView = TArrayView(reinterpret_cast(IndicesBegin.ptr()), NumIndices); } } TArray UniqueVertexMap = MakeUniqueVertexMap(VerticesView); TArray>> VertexFaces = BuildVertexFaces(IndicesView, UniqueVertexMap); TMap EdgesFaces = BuildEdgesFaces(IndicesView, UniqueVertexMap); TArray VertexRingsOffsets; TArray VertexRingsData; Tie(VertexRingsOffsets, VertexRingsData) = BuildVertexRings(IndicesView, UniqueVertexMap, VertexFaces, EdgesFaces); FMeshBufferSet MeshLaplacianOffsetsBuffer; MeshLaplacianOffsetsBuffer.SetBufferCount(1); MeshLaplacianOffsetsBuffer.SetElementCount(VertexRingsOffsets.Num()); FMeshBufferSet MeshLaplacianDataBuffer; MeshLaplacianDataBuffer.SetBufferCount(1); MeshLaplacianDataBuffer.SetElementCount(VertexRingsData.Num()); // Don't add this to the vertex buffer set for now, it is currently only used for Laplacian smoothing a and it is removed // right away after use. FMeshBufferSet UniqueVertexMapBuffer; UniqueVertexMapBuffer.SetBufferCount(1); UniqueVertexMapBuffer.SetElementCount(UniqueVertexMap.Num()); const FIntBufferDescriptor BufDesc; MeshLaplacianOffsetsBuffer.SetBuffer(0, BufDesc.ElementSize, BufDesc.Channels, BufDesc.Semantics, BufDesc.SemanticIndices, BufDesc.Formats, BufDesc.Components); MeshLaplacianDataBuffer.SetBuffer(0, BufDesc.ElementSize, BufDesc.Channels, BufDesc.Semantics, BufDesc.SemanticIndices, BufDesc.Formats, BufDesc.Components); UniqueVertexMapBuffer.SetBuffer(0, BufDesc.ElementSize, BufDesc.Channels, BufDesc.Semantics, BufDesc.SemanticIndices, BufDesc.Formats, BufDesc.Components); // TODO: Add a way for buffers to steal memory from temporaries, so we can avoid a copy here. FMemory::Memcpy( UniqueVertexMapBuffer.GetBufferData(0), UniqueVertexMap.GetData(), UniqueVertexMapBuffer.GetDataSize()); FMemory::Memcpy( MeshLaplacianOffsetsBuffer.GetBufferData(0), VertexRingsOffsets.GetData(), MeshLaplacianOffsetsBuffer.GetDataSize()); FMemory::Memcpy( MeshLaplacianDataBuffer.GetBufferData(0), VertexRingsData.GetData(), MeshLaplacianDataBuffer.GetDataSize()); InOutMesh.AdditionalBuffers.Emplace(EMeshBufferType::UniqueVertexMap, MoveTemp(UniqueVertexMapBuffer)); InOutMesh.AdditionalBuffers.Emplace(EMeshBufferType::MeshLaplacianOffsets, MoveTemp(MeshLaplacianOffsetsBuffer)); InOutMesh.AdditionalBuffers.Emplace(EMeshBufferType::MeshLaplacianData, MoveTemp(MeshLaplacianDataBuffer)); } inline TTuple, TArray> BindPose( const FMesh* Mesh, FShapeMeshTree& ShapeMeshTree, const TArray& BonesToDeform ) { UE::Geometry::FAxisAlignedBox3d ShapeAABBox = ShapeMeshTree.GetBoundingBox(); const float BindValidityTolerance = ShapeAABBox.MaxDim() < 1.0 ? UE_KINDA_SMALL_NUMBER : static_cast(ShapeAABBox.MaxDim()) * 1e-3f; TTuple, TArray> ReturnValue; TArray& SkeletonBindDataArray = ReturnValue.Get<0>(); TArray& BoneIndices = ReturnValue.Get<1>(); const int32 BoneCount = Mesh->GetBonePoseCount(); SkeletonBindDataArray.Reserve(BoneCount); BoneIndices.Reserve(BoneCount); for (int32 BoneIndex = 0; BoneIndex < BoneCount; ++BoneIndex) { const EBoneUsageFlags BoneUsageFlags = Mesh->BonePoses[BoneIndex].BoneUsageFlags; if (EnumHasAnyFlags(BoneUsageFlags, EBoneUsageFlags::Root)) { continue; } if (!BonesToDeform.Contains(Mesh->BonePoses[BoneIndex].BoneId)) { continue; } FReshapeVertexBindingData BindData; constexpr float MaskWeight = 1.0f; BindReshapePoint(ShapeMeshTree, Mesh->BonePoses[BoneIndex].BoneTransform.GetLocation(), FVector3f::ZAxisVector, MaskWeight, BindData, BindValidityTolerance); // Only add binding if there is a chance of the bone moving. if (BindData.Weight > UE_SMALL_NUMBER && BindData.Triangle >= 0) { SkeletonBindDataArray.Emplace( FReshapePointBindingData{ BindData.S, BindData.T, BindData.D, BindData.Triangle, BindData.Weight }); BoneIndices.Add(BoneIndex); } } return ReturnValue; } inline FMeshBindColorChannelUsageMasks MakeColorChannelUsageMasks(FMeshBindColorChannelUsages Usages) { FMeshBindColorChannelUsageMasks Masks; // We assume the color will have the FColor layout Masks.MaskWeight = (Usages.B == EMeshBindColorChannelUsage::MaskWeight ? 0x000000FF : 0) | (Usages.G == EMeshBindColorChannelUsage::MaskWeight ? 0x0000FF00 : 0) | (Usages.R == EMeshBindColorChannelUsage::MaskWeight ? 0x00FF0000 : 0) | (Usages.A == EMeshBindColorChannelUsage::MaskWeight ? 0xFF000000 : 0); Masks.ClusterId = (Usages.B == EMeshBindColorChannelUsage::ClusterId ? 0x000000FF : 0) | (Usages.G == EMeshBindColorChannelUsage::ClusterId ? 0x0000FF00 : 0) | (Usages.R == EMeshBindColorChannelUsage::ClusterId ? 0x00FF0000 : 0) | (Usages.A == EMeshBindColorChannelUsage::ClusterId ? 0xFF000000 : 0); // Maximum one weight channel. check(Masks.MaskWeight == 0 || FMath::CountLeadingZeros(Masks.MaskWeight) + FMath::CountTrailingZeros(Masks.MaskWeight) == 32 - 8); // No overlaped channels. check((Masks.ClusterId & Masks.MaskWeight) == 0); return Masks; } //--------------------------------------------------------------------------------------------- //! Generate the mesh-shape binding data //--------------------------------------------------------------------------------------------- inline void MeshBindShapeReshape( FMesh* Result, const FMesh* BaseMesh, const FMesh* ShapeMesh, const TArray& BonesToDeform, const TArray& PhysicsToDeform, EMeshBindShapeFlags BindFlags, FMeshBindColorChannelUsages ColorChannelUsages, bool& bOutSuccess) { MUTABLE_CPUPROFILER_SCOPE(MeshBindShape); bOutSuccess = true; if (!BaseMesh) { bOutSuccess = false; return; } const bool bReshapeVertices = EnumHasAnyFlags(BindFlags, EMeshBindShapeFlags::ReshapeVertices); // TODO: For now bRecomputeNormals is not used when binding, we could skip normal morph data generation. const bool bRecomputeNormals = EnumHasAnyFlags(BindFlags, EMeshBindShapeFlags::RecomputeNormals); const bool bApplyLaplacian = EnumHasAnyFlags(BindFlags, EMeshBindShapeFlags::ApplyLaplacian); const bool bReshapeSkeleton = EnumHasAnyFlags(BindFlags, EMeshBindShapeFlags::ReshapeSkeleton); const bool bReshapePhysics = EnumHasAnyFlags(BindFlags, EMeshBindShapeFlags::ReshapePhysicsVolumes); const FMeshBindColorChannelUsageMasks ColorUsagesMasks = MakeColorChannelUsageMasks(ColorChannelUsages); // Early out if nothing will be modified and the vertices discarted. return null in this // case indicating nothing has modified so the Base Mesh can be reused. const bool bSkeletonModification = BaseMesh->GetSkeleton() && bReshapeSkeleton; const bool bPhysicsModification = (BaseMesh->GetPhysicsBody() || BaseMesh->AdditionalPhysicsBodies.Num()) && bReshapePhysics; if (!bReshapeVertices && !bSkeletonModification && !bPhysicsModification) { bOutSuccess = false; return; } if (!ShapeMesh) { bOutSuccess = false; return; } int32 ShapeVertexCount = ShapeMesh->GetVertexCount(); int32 ShapeTriangleCount = ShapeMesh->GetFaceCount(); if (!ShapeVertexCount || !ShapeTriangleCount) { bOutSuccess = false; return; } FShapeMeshDescriptorBind ShapeMeshDescriptor; { MUTABLE_CPUPROFILER_SCOPE(GenerateVertexQueryData); ShapeMeshDescriptor.Positions.SetNum(ShapeVertexCount); ShapeMeshDescriptor.Normals.SetNum(ShapeVertexCount); // \TODO: Simple but inefficient const UntypedMeshBufferIteratorConst ItPosition(ShapeMesh->GetVertexBuffers(), EMeshBufferSemantic::Position); const UntypedMeshBufferIteratorConst ItNormal(ShapeMesh->GetVertexBuffers(), EMeshBufferSemantic::Normal); for (int32 ShapeVertexIndex = 0; ShapeVertexIndex < ShapeVertexCount; ++ShapeVertexIndex) { FVector3f Position = (ItPosition + ShapeVertexIndex).GetAsVec3f(); ShapeMeshDescriptor.Positions[ShapeVertexIndex] = static_cast(Position); FVector3f Normal = (ItNormal + ShapeVertexIndex).GetAsVec3f(); ShapeMeshDescriptor.Normals[ShapeVertexIndex] = Normal; } } // Generate the temp face query data for the shape // TODO: Index data copy may be saved in most cases. { MUTABLE_CPUPROFILER_SCOPE(GenerateTrianglesQueryData); ShapeMeshDescriptor.Triangles.SetNum(ShapeTriangleCount); // \TODO: Simple but inefficient const UntypedMeshBufferIteratorConst ItIndices(ShapeMesh->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); for (int32 TriangleIndex = 0; TriangleIndex < ShapeTriangleCount; ++TriangleIndex) { UE::Geometry::FIndex3i Triangle; Triangle.A = int32((ItIndices + TriangleIndex*3 + 0).GetAsUINT32()); Triangle.B = int32((ItIndices + TriangleIndex*3 + 1).GetAsUINT32()); Triangle.C = int32((ItIndices + TriangleIndex*3 + 2).GetAsUINT32()); ShapeMeshDescriptor.Triangles[TriangleIndex] = Triangle; } } FShapeMeshAdapter ShapeMeshAdapter(ShapeMeshDescriptor); constexpr bool bAutoBuildTree = false; FShapeMeshTree ShapeMeshTree(&ShapeMeshAdapter, bAutoBuildTree); { MUTABLE_CPUPROFILER_SCOPE(BuildShapeTree); ShapeMeshTree.Build(); } // If no vertices are needed, it is assumed we only want to reshape physics or skeleton. // In that case, remove everything except physics bodies, the skeleton and pose. if (!bReshapeVertices) { constexpr EMeshCopyFlags CopyFlags = EMeshCopyFlags::WithSkeleton | EMeshCopyFlags::WithPhysicsBody | EMeshCopyFlags::WithPoses | EMeshCopyFlags::WithAdditionalPhysics; Result->CopyFrom(*BaseMesh, CopyFlags); } else { Result->CopyFrom(*BaseMesh); } int32 BindingDataIndex = 0; if (bReshapeVertices) { TArray VerticesBindData = BindVerticesReshape(BaseMesh, ShapeMeshTree, ColorUsagesMasks); // Add the binding information to the mesh // \TODO: Check that there is no other binding data. // \TODO: Support specifying the binding data channel for multiple binding support. FMeshBufferSet& VB = Result->GetVertexBuffers(); int32 NewBufferIndex = VB.GetBufferCount(); VB.SetBufferCount(NewBufferIndex + 1); FReshapeVertexBindingDataBufferDescriptor BufDesc(BindingDataIndex); VB.SetBuffer(NewBufferIndex, sizeof(FReshapeVertexBindingData), BufDesc.Channels, BufDesc.Semantics, BufDesc.SemanticIndices, BufDesc.Formats, BufDesc.Components); FMemory::Memcpy(VB.GetBufferData(NewBufferIndex), VerticesBindData.GetData(), VerticesBindData.Num() * sizeof(FReshapeVertexBindingData)); if (bApplyLaplacian) { GenerateAndAddLaplacianData(*Result); } } // Bind the skeleton bones // \TODO: Build bind data only for actually modified bones? if (bReshapeSkeleton && BonesToDeform.Num()) { MUTABLE_CPUPROFILER_SCOPE(BindSkeleton); TTuple, TArray> SkeletonBindingData = BindPose(Result, ShapeMeshTree, BonesToDeform); const TArray& SkeletonBindDataArray = SkeletonBindingData.Get<0>(); const TArray& BoneIndices = SkeletonBindingData.Get<1>(); check(BoneIndices.Num() == SkeletonBindDataArray.Num()); const int32 NumBonesToDeform = SkeletonBindDataArray.Num(); FMeshBufferSet SkeletonBuffer; SkeletonBuffer.SetBufferCount(2); SkeletonBuffer.SetElementCount(SkeletonBindDataArray.Num()); FReshapePointBindingDataBufferDescriptor BufDesc(BindingDataIndex); SkeletonBuffer.SetBuffer(0, sizeof(FReshapePointBindingData), BufDesc.Channels, BufDesc.Semantics, BufDesc.SemanticIndices, BufDesc.Formats, BufDesc.Components); // Bone indices buffer EMeshBufferSemantic BoneSemantics[1] = { EMeshBufferSemantic::Other }; EMeshBufferFormat BoneFormats[1] = { EMeshBufferFormat::Int32 }; int32 BoneSemanticIndices[1] = { 0 }; int32 BoneComponents[1] = { 1 }; int32 BoneOffsets[1] = { 0 }; SkeletonBuffer.SetBuffer(1, sizeof(int32), 1, BoneSemantics, BoneSemanticIndices, BoneFormats, BoneComponents, BoneOffsets); FMemory::Memcpy(SkeletonBuffer.GetBufferData(0), SkeletonBindDataArray.GetData(), NumBonesToDeform * sizeof(FReshapePointBindingData)); FMemory::Memcpy(SkeletonBuffer.GetBufferData(1), BoneIndices.GetData(), NumBonesToDeform * sizeof(int32)); Result->AdditionalBuffers.Emplace(EMeshBufferType::SkeletonDeformBinding, MoveTemp(SkeletonBuffer)); } const FPhysicsBody* ResultPhysicsBody = Result->PhysicsBody.Get(); if (bReshapePhysics && (ResultPhysicsBody || Result->AdditionalPhysicsBodies.Num()) && PhysicsToDeform.Num()) { MUTABLE_CPUPROFILER_SCOPE(BindPhysicsBody); // Gather bodies respecting order, firts main physics body then additional bodies. // Null entries are need to be able to mantin that order. TArray PhysicsBodiesToBind; { PhysicsBodiesToBind.Reserve(Result->AdditionalPhysicsBodies.Num() + 1); PhysicsBodiesToBind.Add(ResultPhysicsBody); for (const TSharedPtr& Body : Result->AdditionalPhysicsBodies) { PhysicsBodiesToBind.Add(Body.Get()); } } TTuple, TArray, TArray> PhysicsBindingData = BindPhysicsBodies(PhysicsBodiesToBind, ShapeMeshTree, Result, PhysicsToDeform); const TArray& PhysicsBindDataArray = PhysicsBindingData.Get<0>(); const TArray& DeformedBodyIndices = PhysicsBindingData.Get<1>(); const TArray& DeformedBodyIndicesOffsets = PhysicsBindingData.Get<2>(); FMeshBufferSet PhysicsBodyBuffer; PhysicsBodyBuffer.SetBufferCount(1); PhysicsBodyBuffer.SetElementCount(PhysicsBindDataArray.Num()); constexpr int32 BindDataIndex = 0; FReshapePointBindingDataBufferDescriptor BufDesc(BindingDataIndex); PhysicsBodyBuffer.SetBuffer(0, sizeof(FReshapePointBindingData), BufDesc.Channels, BufDesc.Semantics, BufDesc.SemanticIndices, BufDesc.Formats, BufDesc.Components, BufDesc.Offsets); FMemory::Memcpy(PhysicsBodyBuffer.GetBufferData(0), PhysicsBindDataArray.GetData(), PhysicsBindDataArray.Num() * sizeof(FReshapePointBindingData)); FIntBufferDescriptor IntBufDesc; FMeshBufferSet PhysicsBodySelectionBuffer; PhysicsBodySelectionBuffer.SetBufferCount(1); PhysicsBodySelectionBuffer.SetElementCount(DeformedBodyIndices.Num()); PhysicsBodySelectionBuffer.SetBuffer(0, sizeof(int32), IntBufDesc.Channels, IntBufDesc.Semantics, IntBufDesc.SemanticIndices, IntBufDesc.Formats, IntBufDesc.Components, IntBufDesc.Offsets); FMemory::Memcpy(PhysicsBodySelectionBuffer.GetBufferData(0), DeformedBodyIndices.GetData(), DeformedBodyIndices.Num() * sizeof(int32)); FMeshBufferSet PhysicsBodySelectionOffsetsBuffer; PhysicsBodySelectionOffsetsBuffer.SetBufferCount(1); PhysicsBodySelectionOffsetsBuffer.SetElementCount(DeformedBodyIndicesOffsets.Num()); PhysicsBodySelectionOffsetsBuffer.SetBuffer(0, sizeof(int32), IntBufDesc.Channels, IntBufDesc.Semantics, IntBufDesc.SemanticIndices, IntBufDesc.Formats, IntBufDesc.Components, IntBufDesc.Offsets); FMemory::Memcpy(PhysicsBodySelectionOffsetsBuffer.GetBufferData(0), DeformedBodyIndicesOffsets.GetData(), DeformedBodyIndicesOffsets.Num() * sizeof(int32)); Result->AdditionalBuffers.Emplace(EMeshBufferType::PhysicsBodyDeformBinding, MoveTemp(PhysicsBodyBuffer)); Result->AdditionalBuffers.Emplace(EMeshBufferType::PhysicsBodyDeformSelection, MoveTemp(PhysicsBodySelectionBuffer)); Result->AdditionalBuffers.Emplace(EMeshBufferType::PhysicsBodyDeformOffsets, MoveTemp(PhysicsBodySelectionOffsetsBuffer)); } } //--------------------------------------------------------------------------------------------- //! Generate the mesh-shape binding data for ClipDeform operations //--------------------------------------------------------------------------------------------- inline void BindClipDeformPointClosestProject( FShapeMeshTree& ShapeMeshTree, const FVector3f& Point, FClipDeformVertexBindingData& OutBindData, float ValidityTolerance ) { const FShapeMeshDescriptorBind& ShapeMesh = ShapeMeshTree.GetMesh()->Mesh; OutBindData.S = 0.0f; OutBindData.T = 0.0f; OutBindData.Weight = 0.0f; OutBindData.Triangle = -1; double DistSqr = 0.0; int32 FoundIndex = ShapeMeshTree.FindNearestTriangle( FVector3d(Point), DistSqr ); if (FoundIndex < 0) { return; } // Calculate the binding data of the base mesh vertex to its bound shape triangle UE::Geometry::FIndex3i TriangleIndices = ShapeMesh.Triangles[FoundIndex]; // Project on the triangle, but using the vertex normals. // See reference implementation for details. UE::Geometry::FTriangle3f ShapeTriangle { (FVector3f)ShapeMesh.Positions[TriangleIndices.A], (FVector3f)ShapeMesh.Positions[TriangleIndices.B], (FVector3f)ShapeMesh.Positions[TriangleIndices.C] }; const FVector3f ShapeTriangleNormal = ShapeTriangle.Normal(); const FPlane4f VertexPlane(Point, ShapeTriangleNormal); const UE::Geometry::FTriangle3f TriangleVertexPlane { FMath::RayPlaneIntersection(ShapeTriangle.V[0], ShapeMesh.Normals[TriangleIndices.A], VertexPlane), FMath::RayPlaneIntersection(ShapeTriangle.V[1], ShapeMesh.Normals[TriangleIndices.B], VertexPlane), FMath::RayPlaneIntersection(ShapeTriangle.V[2], ShapeMesh.Normals[TriangleIndices.C], VertexPlane) }; FVector3f Barycentric = TriangleVertexPlane.GetBarycentricCoords(Point); FVector3f InterpolatedShapeNormal = ShapeMesh.Normals[TriangleIndices.A] * Barycentric.X + ShapeMesh.Normals[TriangleIndices.B] * Barycentric.Y + ShapeMesh.Normals[TriangleIndices.C] * Barycentric.Z; FVector3f BindPoint = ShapeTriangle.BarycentricPoint(Barycentric); FVector3f ProjectedToVertex = (Point - BindPoint); // Compute reprojection value to see if the binding is valid. const float InterpolatedNormalSizeSquared = InterpolatedShapeNormal.SizeSquared(); const float InvInterpolatedNormalSizeSquared = InterpolatedNormalSizeSquared > SMALL_NUMBER ? 1.0f / InterpolatedNormalSizeSquared : 0.0f; float D = FVector3f::DotProduct(ProjectedToVertex, InterpolatedShapeNormal) * InvInterpolatedNormalSizeSquared; const FVector3f ReprojectedPoint = BindPoint + InterpolatedShapeNormal * D; const FVector3f ReprojectedVector = ReprojectedPoint - Point; const float ErrorEstimate = (ReprojectedPoint - Point).GetAbsMax(); // If within the tolerance, 1.0, otherwise linear falloff based on the tolerance // Arbitrary factor, a binding will be considered valid (with its corresponding weight) to ErrorFalloffFactor times the validity tolerance. constexpr float ErrorFalloffFactor = 4.0f; OutBindData.Weight = 1.0f - FMath::Clamp( (ErrorEstimate - ValidityTolerance) / (ValidityTolerance * ErrorFalloffFactor), 0.0f, 1.0f); OutBindData.S = Barycentric.Y; OutBindData.T = Barycentric.Z; // Only move points that bind outside the shape. OutBindData.Triangle = FVector3f::DotProduct(ShapeTriangleNormal, Point - BindPoint) < 0 ? -1 : FoundIndex; } inline void BindClipDeformPointClosestToSurface( const FShapeMeshTree& ShapeMeshTree, const FVector3f& Point, FClipDeformVertexBindingData& OutBindData) { const FShapeMeshDescriptorBind& ShapeMesh = ShapeMeshTree.GetMesh()->Mesh; OutBindData.S = 0.0f; OutBindData.T = 0.0f; OutBindData.Weight = 1.0f; OutBindData.Triangle = -1; const FVector3d P = FVector3d(Point); double DistSqr = 0; int32 FoundTriIndex = ShapeMeshTree.FindNearestTriangle(P, DistSqr); if (FoundTriIndex < 0) { return; } check( FoundTriIndex >= 0); UE::Geometry::FIndex3i Triangle = ShapeMesh.Triangles[FoundTriIndex]; UE::Geometry::FTriangle3d NearestShapeTriangle { ShapeMesh.Positions[Triangle.A], ShapeMesh.Positions[Triangle.B], ShapeMesh.Positions[Triangle.C] }; UE::Geometry::FDistPoint3Triangle3d Dist(P, NearestShapeTriangle); Dist.ComputeResult(); const FVector3d BindPoint = NearestShapeTriangle.BarycentricPoint(Dist.TriangleBaryCoords); OutBindData.S = float(Dist.TriangleBaryCoords.Y); OutBindData.T = float(Dist.TriangleBaryCoords.Z); // Only move points that bind outside the shape. OutBindData.Triangle = FVector3d::DotProduct(NearestShapeTriangle.Normal(), P - BindPoint) < 0 ? -1 : FoundTriIndex; } inline void BindClipDeformPointNormalProject( const FShapeMeshTree& ShapeMeshTree, const FVector3f& Point, const FVector3f& Normal, FClipDeformVertexBindingData& OutBindData) { const FShapeMeshDescriptorBind& ShapeMesh = ShapeMeshTree.GetMesh()->Mesh; OutBindData.S = 0.0f; OutBindData.T = 0.0f; OutBindData.Weight = 1.0f; OutBindData.Triangle = -1; const FRay3d NormalRay = FRay3d(FVector3d(Point), FVector3d(-Normal)); double RayHitDist = 0.0; FVector3d RayHitBarycenticCoords = FVector3d::ZeroVector; int32 TriangleIndex = -1; const bool bHitFound = ShapeMeshTree.FindNearestHitTriangle(NormalRay, RayHitDist, TriangleIndex, RayHitBarycenticCoords); if (!bHitFound || TriangleIndex < 0) { return; } UE::Geometry::FIndex3i ShapeTriangleIndices = ShapeMesh.Triangles[TriangleIndex]; UE::Geometry::FTriangle3d HitShapeTriangle { ShapeMesh.Positions[ShapeTriangleIndices.A], ShapeMesh.Positions[ShapeTriangleIndices.B], ShapeMesh.Positions[ShapeTriangleIndices.C] }; OutBindData.S = float(RayHitBarycenticCoords.Y); OutBindData.T = float(RayHitBarycenticCoords.Z); OutBindData.Triangle = FVector3d::DotProduct(HitShapeTriangle.Normal(), -NormalRay.Direction) < 0 ? -1 : TriangleIndex; } inline TArray BindVerticesClipDeform( const FMesh* BaseMesh, FShapeMeshTree& ShapeMeshTree, EShapeBindingMethod BindingMethod ) { UE::Geometry::FAxisAlignedBox3d ShapeAABBox = ShapeMeshTree.GetBoundingBox(); const float BindValidityTolerance = ShapeAABBox.MaxDim() < 1.0 ? UE_KINDA_SMALL_NUMBER : static_cast(ShapeAABBox.MaxDim()) * 1e-3f; // Find nearest shape triangle for each base mesh vertex const int32 MeshVertexCount = BaseMesh->GetVertexCount(); TArray BindData; { MUTABLE_CPUPROFILER_SCOPE(ClipDeformBind); BindData.SetNum( MeshVertexCount ); UntypedMeshBufferIteratorConst ItPositionBase(BaseMesh->GetVertexBuffers(), EMeshBufferSemantic::Position); UntypedMeshBufferIteratorConst ItNormalBase(BaseMesh->GetVertexBuffers(), EMeshBufferSemantic::Normal); EShapeBindingMethod ActualBindingMethod = BindingMethod == EShapeBindingMethod::ClipDeformNormalProject && !ItNormalBase.ptr() ? EShapeBindingMethod::ClipDeformClosestToSurface : BindingMethod; switch ( ActualBindingMethod ) { case EShapeBindingMethod::ClipDeformNormalProject: { check(ItNormalBase.ptr()); for (int32 VertexIndex = 0; VertexIndex < MeshVertexCount; ++VertexIndex) { FVector3f VertexPosition = (ItPositionBase + VertexIndex).GetAsVec3f(); FVector3f VertexNormal = (ItNormalBase + VertexIndex).GetAsVec3f(); BindClipDeformPointNormalProject(ShapeMeshTree, VertexPosition, VertexNormal, BindData[VertexIndex]); } break; } case EShapeBindingMethod::ClipDeformClosestToSurface: { for (int32 VertexIndex = 0; VertexIndex < MeshVertexCount; ++VertexIndex) { FVector3f VertexPosition = (ItPositionBase + VertexIndex).GetAsVec3f(); BindClipDeformPointClosestToSurface(ShapeMeshTree, VertexPosition, BindData[VertexIndex]); } break; } case EShapeBindingMethod::ClipDeformClosestProject: { for (int32 VertexIndex = 0; VertexIndex < MeshVertexCount; ++VertexIndex) { FVector3f VertexPosition = (ItPositionBase + VertexIndex).GetAsVec3f(); BindClipDeformPointClosestProject(ShapeMeshTree, VertexPosition, BindData[VertexIndex], BindValidityTolerance); } break; } default: { check(false); } }; } return BindData; } inline void MeshBindShapeClipDeform(FMesh* Result, const FMesh* BaseMesh, const FMesh* ShapeMesh, EShapeBindingMethod BindingMethod, bool& bOutSuccess) { MUTABLE_CPUPROFILER_SCOPE(MeshBindShapeClipDeform); bOutSuccess = true; if (!BaseMesh) { bOutSuccess = false; return; } if (!ShapeMesh) { bOutSuccess = false; return; } int32 ShapeVertexCount = ShapeMesh->GetVertexCount(); int ShapeTriangleCount = ShapeMesh->GetFaceCount(); if (!ShapeVertexCount || !ShapeTriangleCount) { bOutSuccess = false; return; } FShapeMeshDescriptorBind ShapeMeshDescriptor; { MUTABLE_CPUPROFILER_SCOPE(GenerateVertexQueryData); ShapeMeshDescriptor.Positions.SetNum(ShapeVertexCount); ShapeMeshDescriptor.Normals.SetNum(ShapeVertexCount); // \TODO: Simple but inefficient UntypedMeshBufferIteratorConst ItPosition(ShapeMesh->GetVertexBuffers(), EMeshBufferSemantic::Position); UntypedMeshBufferIteratorConst ItNormal(ShapeMesh->GetVertexBuffers(), EMeshBufferSemantic::Normal); for (int32 ShapeVertexIndex = 0; ShapeVertexIndex < ShapeVertexCount; ++ShapeVertexIndex) { FVector3f Position = (ItPosition + ShapeVertexIndex).GetAsVec3f(); ShapeMeshDescriptor.Positions[ShapeVertexIndex] = static_cast(Position); FVector3f Normal = (ItNormal + ShapeVertexIndex).GetAsVec3f(); ShapeMeshDescriptor.Normals[ShapeVertexIndex] = Normal; } } // Generate the temp face query data for the shape // TODO: Index data copy may be saved in most cases. { MUTABLE_CPUPROFILER_SCOPE(GenerateTrianglesQueryData); ShapeMeshDescriptor.Triangles.SetNum(ShapeTriangleCount); // \TODO: Simple but inefficient const UntypedMeshBufferIteratorConst ItIndices(ShapeMesh->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); for (int32 TriangleIndex = 0; TriangleIndex < ShapeTriangleCount; ++TriangleIndex) { UE::Geometry::FIndex3i Triangle; Triangle.A = int32((ItIndices + TriangleIndex*3 + 0).GetAsUINT32()); Triangle.B = int32((ItIndices + TriangleIndex*3 + 1).GetAsUINT32()); Triangle.C = int32((ItIndices + TriangleIndex*3 + 2).GetAsUINT32()); ShapeMeshDescriptor.Triangles[TriangleIndex] = Triangle; } } FShapeMeshAdapter ShapeMeshAdapter(ShapeMeshDescriptor); constexpr bool bAutoBuildTree = false; FShapeMeshTree ShapeMeshTree(&ShapeMeshAdapter, bAutoBuildTree); { MUTABLE_CPUPROFILER_SCOPE(BuildShapeTree); ShapeMeshTree.Build(); } Result->CopyFrom(*BaseMesh); TArray VerticesBindData = BindVerticesClipDeform(BaseMesh, ShapeMeshTree, BindingMethod); // Add the binding information to the mesh // \TODO: Check that there is no other binding data. // \TODO: Support specifying the binding data channel for multiple binding support. FMeshBufferSet& VB = Result->GetVertexBuffers(); int NewBufferIndex = VB.GetBufferCount(); VB.SetBufferCount(NewBufferIndex + 1); // \TODO: Multiple binding dataset support int32 BindingDataIndex = 0; FClipDeformVertexBindingDataBufferDescriptor BufDesc(BindingDataIndex); VB.SetBuffer(NewBufferIndex, sizeof(FClipDeformVertexBindingData), BufDesc.Channels, BufDesc.Semantics, BufDesc.SemanticIndices, BufDesc.Formats, BufDesc.Components, BufDesc.Offsets); FMemory::Memcpy(VB.GetBufferData(NewBufferIndex), VerticesBindData.GetData(), VerticesBindData.Num()*sizeof(FClipDeformVertexBindingData)); } }