Files
UnrealEngine/Engine/Plugins/Mutable/Source/MutableRuntime/Private/MuR/OpMeshClipWithMesh.cpp
2025-05-18 13:04:45 +08:00

1044 lines
36 KiB
C++

// 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<int32>& OutCollapsedVertices, TArray<FVector3f>& OutVertices)
{
MUTABLE_CPUPROFILER_SCOPE(MeshCreateCollapsedVertexMap);
const int32 NumVertices = pMesh->GetVertexCount();
// Used to speed up vertex comparison
UE::Geometry::TPointHashGrid3f<int32> 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<int32> 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<float>::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<int32>& CollapsedVertexMap)
{
MUTABLE_CPUPROFILER_SCOPE(IsMeshClosed);
if (!Mesh)
{
return true;
}
int32 FaceCount = Mesh->GetFaceCount();
// Acumulate edges
using FEdge = TPair<int32, int32>;
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<FEdge, int32>& 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<uint8> 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<int32> oldToNewVertex;
oldToNewVertex.SetNumUninitialized(pMesh->GetVertexCount());
int32 totalNewVertices = 0;
for (int32 v = 0; v<pMesh->GetVertexCount(); ++v)
{
if (used[v])
{
oldToNewVertex[v] = totalNewVertices;
++totalNewVertices;
}
else
{
oldToNewVertex[v] = -1;
}
}
// Remove unused vertices and build index map
for (int b = 0; b<pMesh->GetVertexBuffers().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; v<pMesh->GetVertexCount(); ++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<FVector3f>& Vertices,
const TArray<uint32>& Faces,
const TArray<int32>& CollapsedVertexMap,
TArray<uint8>& InOutVertexAlreadyIntersected,
TSet<uint64>& 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<mu::FMeshBufferChannel>& 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<mu::FMeshBufferChannel>& 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<uint8>& 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<uint32*>(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<uint64*>( 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<FVector3f> Vertices;
Vertices.SetNumUninitialized(VCount);
TArray<int32> 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<FVector3f> Vertices; // ClipMesh vertex cache
Vertices.SetNumUninitialized(VCount);
TArray<uint32> 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<int32> 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<TArray<uint32>[]> GridFaces( new TArray<uint32>[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<uint32>& 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<uint8> VertexAlreadyIntersected;
VertexAlreadyIntersected.AddZeroed(VCount);
TSet<uint64> 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<uint8> 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<uint8> 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<uint8> 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<EMeshBufferFormat::Float32,float,3> itp( pFragment->GetVertexBuffers(), EMeshBufferSemantic::Position );
aabbox.min = itp.GetAsVec3f();
++itp;
for ( int v=1; v<pFragment->GetVertexBuffers().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<int> 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<EMeshBufferFormat::Float32,float,3> itp( pFragment->GetVertexBuffers(), EMeshBufferSemantic::Position );
for ( int tf=0; tf<fragmentFaceCount; tf++ )
{
uint32 index0 = iti.GetAsUINT32(); ++iti;
uint32 index1 = iti.GetAsUINT32(); ++iti;
uint32 index2 = iti.GetAsUINT32(); ++iti;
float y = ( (*(itp+index0))[AXIS] + (*(itp+index1))[AXIS] + (*(itp+index2))[AXIS] ) / 3;
float fbucket = (y-bucketStart) / bucketSize;
int bucket = FMath::Min( NUM_BUCKETS-1, FMath::Max( 0, (int)fbucket ) );
buckets[bucket].Add(tf);
int hibucket = FMath::Min( NUM_BUCKETS-1, FMath::Max( 0, (int)(fbucket+bucketThreshold) ) );
if (hibucket!=bucket)
{
buckets[hibucket].Add(tf);
}
int lobucket = FMath::Min( NUM_BUCKETS-1, FMath::Max( 0, (int)(fbucket-bucketThreshold) ) );
if (lobucket!=bucket)
{
buckets[lobucket].Add(tf);
}
}
// LogDebug("Box : min %.3f, %.3f, %.3f size %.3f,%.3f,%.3f\n",
// aabbox.min[0], aabbox.min[1], aabbox.min[2],
// aabbox.size[0], aabbox.size[1], aabbox.size[2] );
// for ( int b=0; b<NUM_BUCKETS; ++b )
// {
// LogDebug("bucket : %d\n", buckets[b].size() );
// }
TArray<uint8> faceClipped;
faceClipped.SetNumZeroed(sourceFaceCount);
UntypedMeshBufferIteratorConst ito( pBase->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex );
MeshBufferIteratorConst<EMeshBufferFormat::Float32,float,3> itop( pBase->GetVertexBuffers(), EMeshBufferSemantic::Position );
UntypedMeshBufferIteratorConst itti( pFragment->GetIndexBuffers(), EMeshBufferSemantic::VertexIndex );
for ( int f=0; f<sourceFaceCount; ++f )
{
bool hasFace = false;
FUint32Vector3 ov;
ov[0] = ito.GetAsUINT32(); ++ito;
ov[1] = ito.GetAsUINT32(); ++ito;
ov[2] = ito.GetAsUINT32(); ++ito;
// find the bucket for this face
float y = ( (*(itop+ov[0]))[AXIS] + (*(itop+ov[1]))[AXIS] + (*(itop+ov[2]))[AXIS] ) / 3;
float fbucket = (y-bucketStart) / bucketSize;
int bucket = FMath::Min( NUM_BUCKETS-1, FMath::Max( 0, (int)fbucket ) );
for ( int32 btf=0; !hasFace && btf<buckets[bucket].Num(); btf++ )
{
int tf = buckets[bucket][btf];
FUint32Vector3 v;
v[0] = (itti+3*tf+0).GetAsUINT32();
v[1] = (itti+3*tf+1).GetAsUINT32();
v[2] = (itti+3*tf+2).GetAsUINT32();
hasFace = true;
for ( int vi=0; hasFace && vi<3; ++vi )
{
hasFace = vertexMap.DoMatch(v[vi],ov[0])
|| vertexMap.DoMatch(v[vi],ov[1])
|| vertexMap.DoMatch(v[vi],ov[2]);
}
}
if ( hasFace )
{
faceClipped[f] = true;
}
}
// We only remove vertices if all their faces are clipped
TArray<uint8> 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);
}
}