// Copyright Epic Games, Inc. All Rights Reserved. #include "MuR/OpMeshClipWithMesh.h" #include "MuR/MeshPrivate.h" #include "MuR/ImagePrivate.h" #include "MuR/Layout.h" #include "MuR/ConvertData.h" #include "MuR/Platform.h" #include "MuR/MutableTrace.h" #include "MuR/MutableRuntimeModule.h" #include "Math/Ray.h" #include "TriangleTypes.h" #include "BoxTypes.h" #include "Intersection/IntrRay3Triangle3.h" #include "MathUtil.h" #include "IntVectorTypes.h" #include "Math/UnrealMathUtility.h" #include "Spatial/PointHashGrid3.h" namespace mu { namespace { //--------------------------------------------------------------------------------------------- //! Create a map from vertices into vertices, collapsing vertices that have the same position, //! This version uses UE Containers to return. //--------------------------------------------------------------------------------------------- void MeshCreateCollapsedVertexMap(const FMesh* pMesh, TArray& OutCollapsedVertices, TArray& OutVertices) { MUTABLE_CPUPROFILER_SCOPE(MeshCreateCollapsedVertexMap); const int32 NumVertices = pMesh->GetVertexCount(); // Used to speed up vertex comparison UE::Geometry::TPointHashGrid3f VertHash(0.01f, INDEX_NONE); VertHash.Reserve(NumVertices); // Info to collect. Vertices and collapsed vertices OutVertices.SetNumUninitialized(NumVertices); OutCollapsedVertices.Init(INDEX_NONE, NumVertices); // Get Vertices mu::UntypedMeshBufferIteratorConst ItPosition = mu::UntypedMeshBufferIteratorConst(pMesh->GetVertexBuffers(), mu::EMeshBufferSemantic::Position); FVector3f* VertexData = OutVertices.GetData(); for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { *VertexData = ItPosition.GetAsVec3f(); VertHash.InsertPointUnsafe(VertexIndex, *VertexData); ++ItPosition; ++VertexData; } // Find unique vertices TArray NearbyVertices; for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) { if (OutCollapsedVertices[VertexIndex] != INDEX_NONE) { continue; } const FVector3f& Vertex = OutVertices[VertexIndex]; NearbyVertices.Reset(); VertHash.FindPointsInBall(Vertex, TMathUtilConstants::ZeroTolerance, [&Vertex, &OutVertices](const int32& Other) -> float {return FVector3f::DistSquared(OutVertices[Other], Vertex); }, NearbyVertices); for (int32 NearbyVertexIndex : NearbyVertices) { OutCollapsedVertices[NearbyVertexIndex] = VertexIndex; } } } /** Return true if the mesh is closed. Usually used to validate clipping meshes. */ bool IsMeshClosed( const FMesh* Mesh, const TArray& CollapsedVertexMap) { MUTABLE_CPUPROFILER_SCOPE(IsMeshClosed); if (!Mesh) { return true; } int32 FaceCount = Mesh->GetFaceCount(); // Acumulate edges using FEdge = TPair; TMap< FEdge, int32 > FaceCountPerEdge; UntypedMeshBufferIteratorConst ItClipMesh(Mesh->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); for (int32 FaceIndex = 0; FaceIndex < FaceCount; ++FaceIndex) { int32 Face[3]; Face[0] = CollapsedVertexMap[ItClipMesh.GetAsUINT32()]; ++ItClipMesh; Face[1] = CollapsedVertexMap[ItClipMesh.GetAsUINT32()]; ++ItClipMesh; Face[2] = CollapsedVertexMap[ItClipMesh.GetAsUINT32()]; ++ItClipMesh; for (int32 EdgeIndex=0; EdgeIndex <3; ++EdgeIndex) { int32 v0 = Face[EdgeIndex]; int32 v1 = Face[(EdgeIndex+1)%3]; if(v0==v1) { // Degenerated mesh return false; } FEdge Edge; Edge.Key = FMath::Min( v0, v1 ); Edge.Value = FMath::Max( v0, v1 ); int32& Count = FaceCountPerEdge.FindOrAdd(Edge); ++Count; } } // See if every edge has 2 faces for( const TPair& Entry: FaceCountPerEdge) { if (Entry.Value!=2) { return false; } } return true; } //--------------------------------------------------------------------------------------------- //! Remove all unused vertices from a mesh, and fix its index buffers. //--------------------------------------------------------------------------------------------- void MeshRemoveUnusedVertices( FMesh* pMesh ) { MUTABLE_CPUPROFILER_SCOPE(MeshRemoveUnusedVertices); // Mark used vertices TArray used; used.SetNumZeroed(pMesh->GetVertexCount()); UntypedMeshBufferIteratorConst iti(pMesh->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); int IndexCount = pMesh->GetIndexCount(); for (int i = 0; i < IndexCount; ++i) { uint32 Index = iti.GetAsUINT32(); ++iti; used[Index] = true; } // Build vertex map TArray oldToNewVertex; oldToNewVertex.SetNumUninitialized(pMesh->GetVertexCount()); int32 totalNewVertices = 0; for (int32 v = 0; vGetVertexCount(); ++v) { if (used[v]) { oldToNewVertex[v] = totalNewVertices; ++totalNewVertices; } else { oldToNewVertex[v] = -1; } } // Remove unused vertices and build index map for (int b = 0; bGetVertexBuffers().GetBufferCount(); ++b) { int elemSize = pMesh->GetVertexBuffers().GetElementSize(b); const uint8* pSourceData = pMesh->GetVertexBuffers().GetBufferData(b); uint8* pData = pMesh->GetVertexBuffers().GetBufferData(b); for (int v = 0; vGetVertexCount(); ++v) { if (oldToNewVertex[v]!=-1) { uint8* pElemData = pData + elemSize*oldToNewVertex[v]; const uint8* pElemSourceData = pSourceData + elemSize*v; // Avoid warning for overlapping memcpy in valgrind if (pElemData != pElemSourceData) { memcpy(pElemData, pElemSourceData, elemSize); } } } } pMesh->GetVertexBuffers().SetElementCount(totalNewVertices); // Update indices UntypedMeshBufferIteratorConst ito(pMesh->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); if (ito.GetFormat() == EMeshBufferFormat::UInt32) { for (int i = 0; i < IndexCount; ++i) { uint32 Index = *(uint32*)ito.ptr(); int32 NewIndex = oldToNewVertex[Index]; check(NewIndex >= 0); *(uint32*)ito.ptr() = (uint32)NewIndex; ++ito; } } else if (ito.GetFormat() == EMeshBufferFormat::UInt16) { for (int i = 0; i < IndexCount; ++i) { uint16 Index = *(uint16*)ito.ptr(); int32 NewIndex = oldToNewVertex[Index]; check(NewIndex >= 0); *(uint16*)ito.ptr() = (uint16)NewIndex; ++ito; } } else { checkf(false, TEXT("Format not implemented.") ); } // \todo: face buffers? } inline int32 GetNumIntersections( const FRay3f& Ray, const TArray& Vertices, const TArray& Faces, const TArray& CollapsedVertexMap, TArray& InOutVertexAlreadyIntersected, TSet& InOutEdgeAlreadyIntersected, const float DynamicEpsilon ) { using namespace UE::Geometry; MUTABLE_CPUPROFILER_SCOPE(GetNumIntersections); int32 NumIntersections = 0; FMemory::Memzero( InOutVertexAlreadyIntersected.GetData(), InOutVertexAlreadyIntersected.Num() ); InOutEdgeAlreadyIntersected.Empty(); auto GetCollapsedVertex = [&CollapsedVertexMap, &Vertices]( const uint32 V ) -> const FVector3f& { return Vertices[ CollapsedVertexMap[V] ]; }; int32 FaceCount = Faces.Num() / 3; UE::Geometry::FIntrRay3Triangle3f Intersector(Ray, UE::Geometry::FTriangle3f()); // Check vertex against all ClipMorph faces for (int32 Face = 0; Face < FaceCount; ++Face) { const uint32 VertexIndexs[3] = { Faces[3 * Face], Faces[3 * Face + 1], Faces[3 * Face + 2] }; const FVector3f& V0 = GetCollapsedVertex(VertexIndexs[0]); const FVector3f& V1 = GetCollapsedVertex(VertexIndexs[1]); const FVector3f& V2 = GetCollapsedVertex(VertexIndexs[2]); Intersector.Triangle = UE::Geometry::FTriangle3f( V0, V1, V2 ); if ( Intersector.Find() ) { // Find if close to edge using barycentric coordinates form intersector. // Is the Dynamic Epsilon needed?. Intersector bary coords are in the range [0.0, 1.0], // furthermore, IntrRay3Triangle3f::TriangleBaryCoords are double pressison, not sure // is intentional or is a bug ( is double even when the FReal type is float ) const bool IntersectsEdge01 = FMath::IsNearlyZero( Intersector.TriangleBaryCoords.Z, DynamicEpsilon ); const bool IntersectsEdge02 = FMath::IsNearlyZero( Intersector.TriangleBaryCoords.Y, DynamicEpsilon ); const bool IntersectsEdge12 = FMath::IsNearlyZero( Intersector.TriangleBaryCoords.X, DynamicEpsilon ); int32 IntersectedTriangleVertexId = -1; IntersectedTriangleVertexId = (IntersectsEdge01 & IntersectsEdge02) ? 0 : IntersectedTriangleVertexId; IntersectedTriangleVertexId = (IntersectsEdge01 & IntersectsEdge12) ? 1 : IntersectedTriangleVertexId; IntersectedTriangleVertexId = (IntersectsEdge02 & IntersectsEdge12) ? 2 : IntersectedTriangleVertexId; bool bIsAlreadyIntersected = false; if ( IntersectedTriangleVertexId >= 0 ) { const int32 CollapsedVertIndex = CollapsedVertexMap[VertexIndexs[IntersectedTriangleVertexId]]; bIsAlreadyIntersected = InOutVertexAlreadyIntersected[CollapsedVertIndex] == 0; InOutVertexAlreadyIntersected[CollapsedVertIndex] = true; } else if ( IntersectsEdge01 | IntersectsEdge02 | IntersectsEdge12 ) { int32 EdgeV0 = (IntersectsEdge01 | IntersectsEdge02) ? 0 : 1; int32 EdgeV1 = IntersectsEdge01 ? 1 : 2; const int32 CollapsedEdgeVertIndex0 = CollapsedVertexMap[VertexIndexs[EdgeV0]]; const int32 CollapsedEdgeVertIndex1 = CollapsedVertexMap[VertexIndexs[EdgeV1]]; const uint64 EdgeKey = ( ( (uint64)FMath::Max( CollapsedEdgeVertIndex0, CollapsedEdgeVertIndex1 ) ) << 32 ) | (uint64)FMath::Min( CollapsedEdgeVertIndex0, CollapsedEdgeVertIndex1 ); InOutEdgeAlreadyIntersected.FindOrAdd( EdgeKey, &bIsAlreadyIntersected ); } NumIntersections += (int32)(!bIsAlreadyIntersected); } } return NumIntersections; } void MeshUVMaskClassifyVertices(TBitArray<>& VertexClipped, const FMesh* Base, const FImage* Mask, const uint8 LayoutIndex) { using namespace UE::Geometry; MUTABLE_CPUPROFILER_SCOPE(MeshUVMaskClassifyVertices); uint32 VertexCount = Base->GetVertexCount(); // Stores whether each vertex in the original mesh in the clip mesh volume VertexClipped.SetNum(VertexCount, false); // Now go through all vertices in the mesh and record whether they are inside or outside of the ClipMesh const FMeshBufferSet& MBSPriv = Base->GetVertexBuffers(); for (int32 b = 0; b < MBSPriv.Buffers.Num(); ++b) { const TArray& Channels = MBSPriv.Buffers[b].Channels; for (int32 c = 0; c < Channels.Num(); ++c) { EMeshBufferSemantic Sem = Channels[c].Semantic; if (Sem != EMeshBufferSemantic::TexCoords) { continue; } int32 SemIndex = Channels[c].SemanticIndex; if (SemIndex != LayoutIndex) { continue; } UntypedMeshBufferIteratorConst It(Base->GetVertexBuffers(), Sem, SemIndex); for (uint32 V = 0; V < VertexCount; ++V) { // \TODO: This could be optimized. FVector2f UV = It.GetAsVec2f(); // \TODO: This could also be optimized FVector4f Color = Mask->Sample(UV); VertexClipped[V] = Color[0] >= 0.5f; ++It; } break; } } } void MeshLayoutMaskClassifyVertices(TBitArray<>& VertexClipped, const FMesh* Base, const FLayout* Mask, const uint8 LayoutIndex) { using namespace UE::Geometry; MUTABLE_CPUPROFILER_SCOPE(MeshLayoutMaskClassifyVertices); uint32 VertexCount = Base->GetVertexCount(); // Stores whether each vertex in the original mesh in the clip mesh volume VertexClipped.SetNum(VertexCount, false); // Now go through all vertices in the mesh and record whether they are inside or outside of the ClipMesh const FMeshBufferSet& MBSPriv = Base->GetVertexBuffers(); for (int32 b = 0; b < MBSPriv.Buffers.Num(); ++b) { const TArray& Channels = MBSPriv.Buffers[b].Channels; for (int32 c = 0; c < Channels.Num(); ++c) { EMeshBufferSemantic Sem = Channels[c].Semantic; if (Sem != EMeshBufferSemantic::TexCoords) { continue; } int32 SemIndex = Channels[c].SemanticIndex; if (SemIndex != LayoutIndex) { continue; } UntypedMeshBufferIteratorConst It(Base->GetVertexBuffers(), Sem, SemIndex); for (uint32 V = 0; V < VertexCount; ++V) { // \TODO: This could be optimized. FVector2f UV = It.GetAsVec2f(); FVector2f Cell = UV * FVector2f(Mask->Size.X, Mask->Size.Y); // \TODO: This could also be optimized for (const FLayoutBlock& Block : Mask->Blocks) { if ( float(Block.Min.X) <= Cell.X && float(Block.Min.Y) <= Cell.Y && float(Block.Min.X + Block.Size.X) >= Cell.X && float(Block.Min.Y + Block.Size.Y) >= Cell.Y ) { VertexClipped[V] = true; break; } } ++It; } break; } } } /** Make a mask with the indices of the vertices with 0 in the IncludedVertices array. */ void CreateMask(FMesh* Result, const FMesh* Base, const TArray& IncludedVertices) { int32 MaskVertexCount = 0; for (uint8 b : IncludedVertices) { if (!b) { ++MaskVertexCount; } } Result->GetVertexBuffers().SetElementCount(MaskVertexCount); Result->GetVertexBuffers().SetBufferCount(1); Result->MeshIDPrefix = Base->MeshIDPrefix; EMeshBufferSemantic Semantic = EMeshBufferSemantic::VertexIndex; int32 SemanticIndex = 0; int32 Components = 1; int32 Offsets = 0; bool bMakeRelativeIds = !Base->AreVertexIdsExplicit(); if (bMakeRelativeIds) { EMeshBufferFormat Format = EMeshBufferFormat::UInt32; Result->GetVertexBuffers().SetBuffer( 0, sizeof(uint32), 1, &Semantic, &SemanticIndex, &Format, &Components, &Offsets ); // Fill the buffer uint32* Data = reinterpret_cast(Result->GetVertexBuffers().GetBufferData(0)); MeshVertexIdIteratorConst ItBase(Base); for (int32 v = 0; v < IncludedVertices.Num(); ++v) { if (!IncludedVertices[v]) { uint64 ID = ItBase.Get(); *Data = uint32(ID & 0xffffffff); ++Data; } ++ItBase; } } else { EMeshBufferFormat Format = EMeshBufferFormat::UInt64; Result->GetVertexBuffers().SetBuffer( 0, sizeof(uint64), 1, &Semantic, &SemanticIndex, &Format, &Components, &Offsets ); // Fill the buffer uint64* Data = reinterpret_cast( Result->GetVertexBuffers().GetBufferData(0) ); MeshVertexIdIteratorConst ItBase(Base); for (int32 v = 0; v < IncludedVertices.Num(); ++v) { if (!IncludedVertices[v]) { *Data = ItBase.Get(); ++Data; } ++ItBase; } } } }} // namespace mu::anonymous namespace mu { bool IsMeshClosed(const FMesh* Mesh) { if (!Mesh) { return true; } const int32 VCount = Mesh->GetVertexBuffers().GetElementCount(); TArray Vertices; Vertices.SetNumUninitialized(VCount); TArray CollapsedVertexMap; CollapsedVertexMap.AddUninitialized(VCount); MeshCreateCollapsedVertexMap(Mesh, CollapsedVertexMap, Vertices); return IsMeshClosed(Mesh, CollapsedVertexMap); } /** Core Geometry version */ void MeshClipMeshClassifyVertices(TBitArray<>& VertexInClipMesh, const FMesh* Base, const FMesh* ClipMesh) { using namespace UE::Geometry; MUTABLE_CPUPROFILER_SCOPE(MeshClipMeshClassifyVertices); const int32 VCount = ClipMesh->GetVertexBuffers().GetElementCount(); const int32 FCount = ClipMesh->GetFaceCount(); int32 OrigVertCount = Base->GetVertexBuffers().GetElementCount(); // Stores whether each vertex in the original mesh in the clip mesh volume VertexInClipMesh.SetNum(OrigVertCount, false); if (VCount == 0) { return; } TArray Vertices; // ClipMesh vertex cache Vertices.SetNumUninitialized(VCount); TArray Faces; // ClipMesh face cache Faces.SetNumUninitialized(FCount * 3); // Map in ClipMesh from vertices to the one they are collapsed to because they are very // similar, if they aren't collapsed then they are mapped to themselves TArray CollapsedVertexMap; CollapsedVertexMap.AddUninitialized(VCount); MeshCreateCollapsedVertexMap( ClipMesh, CollapsedVertexMap, Vertices ); #if !UE_BUILD_SHIPPING if (!IsMeshClosed(ClipMesh, CollapsedVertexMap)) { UE_LOG(LogMutableCore, Warning, TEXT("Mesh operation with a mesh that is not closed as required.")); } #endif // Create cache of the faces UntypedMeshBufferIteratorConst ItClipMesh(ClipMesh->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); for ( int32 F = 0; F < FCount; ++F ) { Faces[3 * F] = ItClipMesh.GetAsUINT32(); ++ItClipMesh; Faces[3 * F + 1] = ItClipMesh.GetAsUINT32(); ++ItClipMesh; Faces[3 * F + 2] = ItClipMesh.GetAsUINT32(); ++ItClipMesh; } // Create a bounding box of the clip mesh UE::Geometry::FAxisAlignedBox3f ClipMeshBoundingBox = UE::Geometry::FAxisAlignedBox3f::Empty(); for ( const FVector3f& Vert : Vertices ) { ClipMeshBoundingBox.Contain( Vert ); } // Dynamic distance epsilon to support different engines const float MaxDimensionBoundingBox = ClipMeshBoundingBox.DiagonalLength(); // 0.000001 is the value that helps to achieve the dynamic epsilon, do not change it const float DynamicEpsilon = 0.000001f * MaxDimensionBoundingBox * (MaxDimensionBoundingBox < 1.0f ? MaxDimensionBoundingBox : 1.0f); // Create an acceleration grid to avoid testing all clip-mesh triangles. // This assumes that the testing ray direction is Z constexpr int32 GRID_SIZE = 8; TUniquePtr[]> GridFaces( new TArray[GRID_SIZE*GRID_SIZE] ); const FVector2f GridCellSize = FVector2f( ClipMeshBoundingBox.Width(), ClipMeshBoundingBox.Height() ) / (float)GRID_SIZE; for ( int32 I = 0; I < GRID_SIZE; ++I ) { for ( int32 J = 0; J < GRID_SIZE; ++J ) { const FVector2f BBoxMin = FVector2f(ClipMeshBoundingBox.Min.X, ClipMeshBoundingBox.Min.Y) + GridCellSize * FVector2f(I, J); FAxisAlignedBox2f CellBox( BBoxMin, BBoxMin + GridCellSize ); TArray& CellFaces = GridFaces[I+J*GRID_SIZE]; CellFaces.Empty(FCount/GRID_SIZE); for (int32 F = 0; F < FCount; ++F) { // Imprecise, conservative classification of faces. const FVector3f& V0 = Vertices[ Faces[3*F + 0] ]; const FVector3f& V1 = Vertices[ Faces[3*F + 1] ]; const FVector3f& V2 = Vertices[ Faces[3*F + 2] ]; FAxisAlignedBox2f FaceBox; FaceBox.Contain( FVector2f( V0.X, V0.Y ) ); FaceBox.Contain( FVector2f( V1.X, V1.Y ) ); FaceBox.Contain( FVector2f( V2.X, V2.Y ) ); if (CellBox.Intersects(FaceBox)) { CellFaces.Add( Faces[3*F + 0] ); CellFaces.Add( Faces[3*F + 1] ); CellFaces.Add( Faces[3*F + 2] ); } } } } // Now go through all vertices in the mesh and record whether they are inside or outside of the ClipMesh uint32 DestVertexCount = Base->GetVertexCount(); const FMeshBufferSet& MBSPriv2 = Base->GetVertexBuffers(); for (int32 b = 0; b < MBSPriv2.Buffers.Num(); ++b) { for (int32 c = 0; c < MBSPriv2.Buffers[b].Channels.Num(); ++c) { EMeshBufferSemantic Sem = MBSPriv2.Buffers[b].Channels[c].Semantic; int32 SemIndex = MBSPriv2.Buffers[b].Channels[c].SemanticIndex; UntypedMeshBufferIteratorConst It(Base->GetVertexBuffers(), Sem, SemIndex); TArray VertexAlreadyIntersected; VertexAlreadyIntersected.AddZeroed(VCount); TSet EdgeAlreadyIntersected; switch ( Sem ) { case EMeshBufferSemantic::Position: for (uint32 V = 0; V < DestVertexCount; ++V) { FVector3f Vertex(0.0f, 0.0f, 0.0f); for (int32 Offset = 0; Offset < 3; ++Offset) { ConvertData(Offset, &Vertex[0], EMeshBufferFormat::Float32, It.ptr(), It.GetFormat()); } const FVector2f ClipBBoxMin = FVector2f( ClipMeshBoundingBox.Min.X, ClipMeshBoundingBox.Min.Y); const FVector2f ClipBBoxSize = FVector2f( ClipMeshBoundingBox.Width(), ClipMeshBoundingBox.Height() ); const FVector2i HPos = FVector2i( ( ( FVector2f( Vertex.X, Vertex.Y ) - ClipBBoxMin ) / ClipBBoxSize ) * (float)GRID_SIZE ); const FVector2i CellCoord = FVector2i( FMath::Clamp( HPos.X, 0, GRID_SIZE - 1 ), FMath::Clamp( HPos.Y, 0, GRID_SIZE - 1 ) ); // Early discard test: if the vertex is not inside the bounding box of the clip mesh, it won't be clipped. const bool bContainsVertex = ClipMeshBoundingBox.Contains( Vertex ); if ( bContainsVertex ) { // Optimised test // Z-direction. Don't change this without reviewing the acceleration structure. FVector3f RayDir = FVector3f(0.0f, 0.0f, 1.0f); const int32 CellIndex = CellCoord.X + CellCoord.Y*GRID_SIZE; int32 NumIntersections = GetNumIntersections( FRay3f( Vertex, RayDir ), Vertices, GridFaces[CellIndex], CollapsedVertexMap, VertexAlreadyIntersected, EdgeAlreadyIntersected, DynamicEpsilon); // Full test BLEH, debug // int32 FullNumIntersections = GetNumIntersections( // FRay3f( Vertex, RayDir ), // Vertices, // Faces, // CollapsedVertexMap, // VertexAlreadyIntersected, // EdgeAlreadyIntersected, // DynamicEpsilon); VertexInClipMesh[V] = NumIntersections % 2 == 1; // This may be used to debug degenerated cases if the conditional above is also removed. // \todo: make sure it works well // if (!bContainsVertex && VertexInClipMesh[V]) // { // assert(false); // } } ++It; } break; default: break; } } } } void MeshClipWithMesh(FMesh* Result, const FMesh* pBase, const FMesh* ClipMesh, bool& bOutSuccess) { MUTABLE_CPUPROFILER_SCOPE(MeshClipWithMesh); bOutSuccess = true; uint32 VCount = ClipMesh->GetVertexBuffers().GetElementCount(); if (!VCount) { bOutSuccess = false; return; // OutSuccess false indicates the pBase can be reused in this case. } Result->CopyFrom(*pBase); TBitArray<> VertexInClipMesh; // Stores whether each vertex in the original mesh is in the clip mesh volume MeshClipMeshClassifyVertices(VertexInClipMesh, pBase, ClipMesh); // Now remove all the faces from the result mesh that have all the vertices outside the clip volume UntypedMeshBufferIteratorConst ItBase(Result->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); UntypedMeshBufferIterator ItDest(Result->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); int32 AFaceCount = Result->GetFaceCount(); UntypedMeshBufferIteratorConst Ito(Result->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); for (int32 F = 0; F < AFaceCount; ++F) { uint32 OV[3] = {0, 0, 0}; OV[0] = Ito.GetAsUINT32(); ++Ito; OV[1] = Ito.GetAsUINT32(); ++Ito; OV[2] = Ito.GetAsUINT32(); ++Ito; bool AllVertsIn = VertexInClipMesh[OV[0]] && VertexInClipMesh[OV[1]] && VertexInClipMesh[OV[2]]; if (!AllVertsIn) { if (ItDest.ptr() != ItBase.ptr()) { FMemory::Memcpy(ItDest.ptr(), ItBase.ptr(), ItBase.GetElementSize() * 3); } ItDest += 3; } ItBase += 3; } SIZE_T RemovedIndices = ItBase - ItDest; check(RemovedIndices % 3 == 0); Result->GetIndexBuffers().SetElementCount(AFaceCount * 3 - (int32)RemovedIndices); MeshRemoveUnusedVertices(Result); } void MeshMaskClipMesh(FMesh* Result, const FMesh* pBase, const FMesh* pClipMesh, bool& bOutSuccess) { MUTABLE_CPUPROFILER_SCOPE(MeshMaskClipMesh); bOutSuccess = true; uint32 VCount = pClipMesh->GetVertexBuffers().GetElementCount(); if (!VCount) { bOutSuccess = false; return; } TBitArray<> VertexInClipMesh; // Stores whether each vertex in the original mesh in in the clip mesh volume MeshClipMeshClassifyVertices( VertexInClipMesh, pBase, pClipMesh ); // We only remove vertices if all their faces are clipped TArray VertexWithFaceNotClipped; VertexWithFaceNotClipped.AddZeroed( VertexInClipMesh.Num() ); UntypedMeshBufferIteratorConst Ito(pBase->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); int32 AFaceCount = pBase->GetFaceCount(); for (int32 F = 0; F < AFaceCount; ++F) { uint32 OV[3] = { 0, 0, 0 }; OV[0] = Ito.GetAsUINT32(); ++Ito; OV[1] = Ito.GetAsUINT32(); ++Ito; OV[2] = Ito.GetAsUINT32(); ++Ito; bool bFaceClipped = VertexInClipMesh[OV[0]] && VertexInClipMesh[OV[1]] && VertexInClipMesh[OV[2]]; if (!bFaceClipped) { VertexWithFaceNotClipped[OV[0]] = true; VertexWithFaceNotClipped[OV[1]] = true; VertexWithFaceNotClipped[OV[2]] = true; } } CreateMask(Result, pBase, VertexWithFaceNotClipped); } void MakeMeshMaskFromUVMask(FMesh* Result, const FMesh* Base, const FMesh* BaseForUVs, const FImage* Mask, uint8 LayoutIndex, bool& bOutSuccess) { MUTABLE_CPUPROFILER_SCOPE(MeshMaskUVMask); check(Result && Base && Mask && BaseForUVs); check(Base->VertexBuffers.GetElementCount() == BaseForUVs->VertexBuffers.GetElementCount()); bOutSuccess = true; // Stores whether each vertex in the original mesh is in the clip mesh volume TBitArray<> VertexClipped; MeshUVMaskClassifyVertices(VertexClipped, BaseForUVs, Mask, LayoutIndex); // We only remove vertices if all their faces are clipped TArray VertexWithFaceNotClipped; VertexWithFaceNotClipped.AddZeroed(VertexClipped.Num()); UntypedMeshBufferIteratorConst Ito(Base->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); int32 FaceCount = Base->GetFaceCount(); for (int32 F = 0; F < FaceCount; ++F) { uint32 OV[3] = { 0, 0, 0 }; OV[0] = Ito.GetAsUINT32(); ++Ito; OV[1] = Ito.GetAsUINT32(); ++Ito; OV[2] = Ito.GetAsUINT32(); ++Ito; bool bFaceClipped = VertexClipped[OV[0]] && VertexClipped[OV[1]] && VertexClipped[OV[2]]; if (!bFaceClipped) { VertexWithFaceNotClipped[OV[0]] = true; VertexWithFaceNotClipped[OV[1]] = true; VertexWithFaceNotClipped[OV[2]] = true; } } CreateMask(Result, Base, VertexWithFaceNotClipped); } void MakeMeshMaskFromLayout(FMesh* Result, const FMesh* Base, const FMesh* BaseForUVs, const FLayout* Mask, uint8 LayoutIndex, bool& bOutSuccess) { MUTABLE_CPUPROFILER_SCOPE(MakeMeshMaskFromLayout); check(Result && Base && Mask && BaseForUVs); //check(Base->VertexBuffers.GetElementCount()== BaseForUVs->VertexBuffers.GetElementCount()); if (Base->VertexBuffers.GetElementCount() != BaseForUVs->VertexBuffers.GetElementCount()) { ensure(false); return; } bOutSuccess = true; // Stores whether each vertex in the original mesh is in the clip mesh volume TBitArray<> VertexClipped; MeshLayoutMaskClassifyVertices(VertexClipped, BaseForUVs, Mask, LayoutIndex); // We only remove vertices if all their faces are clipped TArray VertexWithFaceNotClipped; VertexWithFaceNotClipped.AddZeroed(VertexClipped.Num()); UntypedMeshBufferIteratorConst Ito(Base->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); int32 FaceCount = Base->GetFaceCount(); for (int32 F = 0; F < FaceCount; ++F) { uint32 OV[3] = { 0, 0, 0 }; OV[0] = Ito.GetAsUINT32(); ++Ito; OV[1] = Ito.GetAsUINT32(); ++Ito; OV[2] = Ito.GetAsUINT32(); ++Ito; bool bFaceClipped = VertexClipped[OV[0]] && VertexClipped[OV[1]] && VertexClipped[OV[2]]; if (!bFaceClipped) { VertexWithFaceNotClipped[OV[0]] = true; VertexWithFaceNotClipped[OV[1]] = true; VertexWithFaceNotClipped[OV[2]] = true; } } CreateMask(Result, Base, VertexWithFaceNotClipped); } void MeshMaskDiff(FMesh* Result, const FMesh* pBase, const FMesh* pFragment, bool& bOutSuccess) { MUTABLE_CPUPROFILER_SCOPE(MeshMaskDiff); bOutSuccess = true; uint32 vcount = pFragment->GetVertexBuffers().GetElementCount(); if (!vcount) { bOutSuccess = false; return; } int sourceFaceCount = pBase->GetFaceCount(); int sourceVertexCount = pBase->GetVertexCount(); int fragmentFaceCount = pFragment->GetFaceCount(); // Make a tolerance proportional to the mesh bounding box size // TODO: Use precomputed bounding box box< FVector3f > aabbox; if ( fragmentFaceCount > 0 ) { MeshBufferIteratorConst itp( pFragment->GetVertexBuffers(), EMeshBufferSemantic::Position ); aabbox.min = itp.GetAsVec3f(); ++itp; for ( int v=1; vGetVertexBuffers().GetElementCount(); ++v ) { aabbox.Bound(itp.GetAsVec3f()); ++itp; } } float tolerance = 1e-5f * aabbox.size.Length(); FMesh::FVertexMatchMap vertexMap; pFragment->GetVertexMap( *pBase, vertexMap, tolerance ); // Classify the target faces in buckets along the Y axis #define NUM_BUCKETS 128 #define AXIS 1 TArray buckets[ NUM_BUCKETS ]; float bucketStart = aabbox.min[AXIS]; float bucketSize = aabbox.size[AXIS] / NUM_BUCKETS; float bucketThreshold = ( 4 * tolerance ) / bucketSize; UntypedMeshBufferIteratorConst iti( pFragment->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex ); MeshBufferIteratorConst itp( pFragment->GetVertexBuffers(), EMeshBufferSemantic::Position ); for ( int tf=0; tf faceClipped; faceClipped.SetNumZeroed(sourceFaceCount); UntypedMeshBufferIteratorConst ito( pBase->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex ); MeshBufferIteratorConst itop( pBase->GetVertexBuffers(), EMeshBufferSemantic::Position ); UntypedMeshBufferIteratorConst itti( pFragment->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex ); for ( int f=0; f vertex_with_face_not_clipped; vertex_with_face_not_clipped.SetNumZeroed(sourceVertexCount); UntypedMeshBufferIteratorConst itoi(pBase->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex); int aFaceCount = pBase->GetFaceCount(); for (int f = 0; f < aFaceCount; ++f) { FUint32Vector3 ov; ov[0] = itoi.GetAsUINT32(); ++itoi; ov[1] = itoi.GetAsUINT32(); ++itoi; ov[2] = itoi.GetAsUINT32(); ++itoi; if (!faceClipped[f]) { vertex_with_face_not_clipped[ov[0]] = true; vertex_with_face_not_clipped[ov[1]] = true; vertex_with_face_not_clipped[ov[2]] = true; } } CreateMask(Result, pBase, vertex_with_face_not_clipped); } }