// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "SkeletalMeshTypes.h" #include "Chaos/AABBTree.h" DECLARE_LOG_CATEGORY_EXTERN(LogClothingMeshUtils, Log, All); struct FClothPhysicalMeshData; struct FPointWeightMap; namespace ClothingMeshUtils { struct FMeshToMeshFilterSet { TSet SourceTriangles; // Set of triangle index in the source ClothMeshDesc TSet TargetVertices; // Set of vertex index in the target ClothMeshDesc }; class ClothMeshDesc { public: ClothMeshDesc(TConstArrayView InPositions, TConstArrayView InNormals, TConstArrayView InIndices) : Positions(InPositions) , Normals(InNormals) , Indices(InIndices) , bHasValidBVH(false) { } ClothMeshDesc(TConstArrayView InPositions, TConstArrayView InNormals, TConstArrayView InTangents, TConstArrayView InIndices) : Positions(InPositions) , Normals(InNormals) , Tangents(InTangents) , Indices(InIndices) , bHasValidBVH(false) { } /* Construct a mesh descriptor with re-calculated (averaged) normals to match the simulation output. */ ClothMeshDesc(TConstArrayView InPositions, TConstArrayView InIndices) : Positions(InPositions) , Indices(InIndices) , bHasValidBVH(false) { ComputeAveragedNormals(); Normals = TConstArrayView(AveragedNormals); } const TConstArrayView& GetPositions() const { return Positions; } const TConstArrayView& GetNormals() const { return Normals; } const TConstArrayView& GetTangents() const { return Tangents; } const TConstArrayView& GetIndices() const { return Indices; } /** Return whether the mesh descriptor has a valid number of normals and triangle indices. */ bool HasValidMesh() const { return Positions.Num() == Normals.Num() && Indices.Num() % 3 == 0; } /** Return whether this descriptor has been initialized with tangent information. */ bool HasTangents() const { return Positions.Num() == Tangents.Num(); } /** Return whether this descriptor has been initialized with the normals generated from the averaged triangle normals. */ bool HasAveragedNormals() const { return Normals.Num() == AveragedNormals.Num(); } /** Find the distance to the specified triangle from a point. */ CLOTHINGSYSTEMRUNTIMECOMMON_API float DistanceToTriangle(const FVector& Position, int32 TriangleBaseIndex) const; /** Return the current list of filtered triangles. */ TArray& GetFilteredTriangles() const { return FilteredTriangles; } /** Create an array with all source triangles whose filter set from the given array containts the specified target vertex. */ void GatherAllSourceTrianglesForTargetVertex(const TArray& FilterSets, int32 TargetVertex) const; /** Find the closest triangles from the specified point. */ CLOTHINGSYSTEMRUNTIMECOMMON_API TArray FindCandidateTriangles(const FVector& InPoint, float InTolerance = KINDA_SMALL_NUMBER) const; /** * Return the max edge length for each edge coming out of a mesh vertex. * Useful for guiding the search radius when searching for nearest triangles. */ CLOTHINGSYSTEMRUNTIMECOMMON_API TConstArrayView GetMaxEdgeLengths() const; private: CLOTHINGSYSTEMRUNTIMECOMMON_API void ComputeAveragedNormals(); struct FClothBvEntry; TConstArrayView Positions; TConstArrayView Normals; TConstArrayView Tangents; TConstArrayView Indices; TArray AveragedNormals; mutable bool bHasValidBVH; mutable Chaos::TAABBTree, false> BVH; mutable TArray MaxEdgeLengths; mutable TArray FilteredTriangles; }; /** * Static method for calculating a skinned mesh result from source data */ void CLOTHINGSYSTEMRUNTIMECOMMON_API SkinPhysicsMesh( const TArray& BoneMap, // UClothingAssetCommon::UsedBoneIndices const FClothPhysicalMeshData& InMesh, const FTransform& PostTransform, // Final transform to apply to component space positions and normals const FMatrix44f* InBoneMatrices, const int32 InNumBoneMatrices, TArray& OutPositions, TArray& OutNormals); /** * Given mesh information for two meshes, generate a list of skinning data to embed TargetMesh in SourceMesh * * @param OutMeshToMeshVertData - Final skinning data * @param TargetMesh - Mesh data for the mesh we are embedding * @param SourceMesh - Mesh data for the mesh we are embedding into * @param MaxDistances - Fixed positions mask used to update the vertex contributions, or null if the update is not required * @param bUseSmoothTransitions - Set blend weight to smoothen transitions between the fixed and deformed vertices when updating the vertex contributions * @param bUseMultipleInfluences - Whether to take a weighted average of influences from multiple source triangles * @param KernelMaxDistance - Max distance parameter for weighting kernel for when using multiple influences * @param FilterSets - A set of source triangle indices to filter the search from, will use all source triangles if empty */ void CLOTHINGSYSTEMRUNTIMECOMMON_API GenerateMeshToMeshVertData( TArray& OutMeshToMeshVertData, const ClothMeshDesc& TargetMesh, const ClothMeshDesc& SourceMesh, const FPointWeightMap* MaxDistances, bool bUseSmoothTransitions, bool bUseMultipleInfluences, float KernelMaxDistance, const TArray& FilterSets = TArray()); /** * Computes how much each vertex contributes to the final mesh. The final mesh is a blend * between the cloth and the skinned mesh. */ void CLOTHINGSYSTEMRUNTIMECOMMON_API ComputeVertexContributions( TArray &InOutSkinningData, const FPointWeightMap* const InMaxDistances, const bool bInSmoothTransition, const bool bInUseMultipleInfluences = false); /** * Given a triangle ABC with normals at each vertex NA, NB and NC, get a barycentric coordinate * and corresponding distance from the triangle encoded in an FVector4 where the components are * (BaryX, BaryY, BaryZ, Dist) * @param A - Position of triangle vertex A * @param B - Position of triangle vertex B * @param C - Position of triangle vertex C * @param NA - Normal at vertex A * @param NB - Normal at vertex B * @param NC - Normal at vertex C * @param Point - Point to calculate Bary+Dist for */ FVector4f GetPointBaryAndDist( const FVector3f& A, const FVector3f& B, const FVector3f& C, const FVector3f& Point); /** * Given a triangle ABC with normals at each vertex NA, NB and NC, get a barycentric coordinate * and corresponding distance from the triangle encoded in an FVector4 where the components are * (BaryX, BaryY, BaryZ, Dist) * @param A - Position of triangle vertex A * @param B - Position of triangle vertex B * @param C - Position of triangle vertex C * @param NA - Normal at vertex A * @param NB - Normal at vertex B * @param NC - Normal at vertex C * @param Point - Point to calculate Bary+Dist for */ FVector4f GetPointBaryAndDistWithNormals( const FVector3f& A, const FVector3f& B, const FVector3f& C, const FVector3f& NA, const FVector3f& NB, const FVector3f& NC, const FVector3f& Point); /** * Object used to map vertex parameters between two meshes using the * same barycentric mesh to mesh mapping data we use for clothing */ class FVertexParameterMapper { public: UE_NONCOPYABLE(FVertexParameterMapper) FVertexParameterMapper(TConstArrayView InMesh0Positions, TConstArrayView InMesh0Normals, TConstArrayView InMesh1Positions, TConstArrayView InMesh1Normals, TConstArrayView InMesh1Indices) : Mesh0Positions(InMesh0Positions) , Mesh0Normals(InMesh0Normals) , Mesh1Positions(InMesh1Positions) , Mesh1Normals(InMesh1Normals) , Mesh1Indices(InMesh1Indices) { } /** Generic mapping function, can be used to map any type with a provided callable */ template void Map(TConstArrayView& SourceData, TArray& DestData, const Lambda& Func) { // Enforce the interp func signature (returns T and takes a bary and 3 Ts) // If you hit this then either the return type isn't T or your arguments aren't convertible to T static_assert(std::is_same_v(), DeclVal(), DeclVal(), DeclVal()))>::Type>, "Invalid Lambda signature passed to Map"); const int32 NumMesh0Positions = Mesh0Positions.Num(); const int32 NumMesh0Normals = Mesh0Normals.Num(); const int32 NumMesh1Positions = Mesh1Positions.Num(); const int32 NumMesh1Normals = Mesh1Normals.Num(); const int32 NumMesh1Indices = Mesh1Indices.Num(); // Validate mesh data check(NumMesh0Positions == NumMesh0Normals); check(NumMesh1Positions == NumMesh1Normals); check(NumMesh1Indices % 3 == 0); check(SourceData.Num() == NumMesh1Positions); if(DestData.Num() != NumMesh0Positions) { DestData.Reset(); DestData.AddUninitialized(NumMesh0Positions); } TArray EmbeddedPositions; TArray SourceIndices; GenerateEmbeddedPositions(EmbeddedPositions, SourceIndices); for(int32 DestVertIndex = 0 ; DestVertIndex < NumMesh0Positions ; ++DestVertIndex) { // Truncate the distance from the position data FVector Bary = EmbeddedPositions[DestVertIndex]; const int32 SourceTriBaseIdx = DestVertIndex * 3; T A = SourceData[SourceIndices[SourceTriBaseIdx + 0]]; T B = SourceData[SourceIndices[SourceTriBaseIdx + 1]]; T C = SourceData[SourceIndices[SourceTriBaseIdx + 2]]; T& DestVal = DestData[DestVertIndex]; // If we're super close to a vertex just take it's value. // Otherwise call the provided interp lambda FVector DiffVec = FVector::OneVector - Bary; if(FMath::Abs(DiffVec.X) <= SMALL_NUMBER) { DestVal = A; } else if(FMath::Abs(DiffVec.Y) <= SMALL_NUMBER) { DestVal = B; } else if(FMath::Abs(DiffVec.Z) <= SMALL_NUMBER) { DestVal = C; } else { DestVal = Func((FVector3f)Bary, A, B, C); } } } /** * Remap float scalar values. * This transfers the values from source to dest by matching the mesh topologies using * barycentric coordinates of the dest mesh (Mesh0) positions over the source mesh (Mesh1). */ CLOTHINGSYSTEMRUNTIMECOMMON_API void Map(TConstArrayView Source, TArray& Dest); private: /** * Embeds a list of positions into a source mesh * @param OutEmbeddedPositions Embedded version of the original positions, a barycentric coordinate and distance along the normal of the triangle * @param OutSourceIndices Source index list for the embedded positions, 3 per position to denote the source triangle */ CLOTHINGSYSTEMRUNTIMECOMMON_API void GenerateEmbeddedPositions(TArray& OutEmbeddedPositions, TArray& OutSourceIndices); TConstArrayView Mesh0Positions; TConstArrayView Mesh0Normals; TConstArrayView Mesh1Positions; TConstArrayView Mesh1Normals; TConstArrayView Mesh1Indices; }; }