Files
UnrealEngine/Engine/Plugins/Mutable/Source/MutableRuntime/Internal/MuR/OpMeshMerge.h
2025-05-18 13:04:45 +08:00

1175 lines
38 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "MuR/MeshPrivate.h"
#include "MuR/Platform.h"
#include "MuR/MutableMath.h"
#include "MuR/OpMeshFormat.h"
#include "MuR/MutableTrace.h"
namespace mu
{
struct FMeshMergeScratchMeshes
{
TSharedPtr<FMesh> FirstReformat;
TSharedPtr<FMesh> SecondReformat;
};
//---------------------------------------------------------------------------------------------
//! Merge two meshes into one new mesh
//---------------------------------------------------------------------------------------------
inline void MeshMerge(FMesh* Result, const FMesh* pFirst, const FMesh* pSecond, bool bMergeSurfaces, FMeshMergeScratchMeshes& ScratchMeshes)
{
MUTABLE_CPUPROFILER_SCOPE(MeshMerge);
// Should never happen, but fixes static analysis warnings.
if (!(pFirst && pSecond))
{
return;
}
// Indices
//-----------------
if (pFirst->GetIndexBuffers().GetBufferCount() > 0)
{
MUTABLE_CPUPROFILER_SCOPE(Indices);
const int32 FirstCount = pFirst->GetIndexBuffers().GetElementCount();
const int32 SecondCount = pSecond->GetIndexBuffers().GetElementCount();
if (pFirst->IndexBuffers.IsDescriptor() || pSecond->IndexBuffers.IsDescriptor())
{
EnumAddFlags(Result->IndexBuffers.Flags, EMeshBufferSetFlags::IsDescriptor);
}
Result->GetIndexBuffers().SetElementCount(FirstCount + SecondCount);
check(pFirst->GetIndexBuffers().GetBufferCount() <= 1);
check(pSecond->GetIndexBuffers().GetBufferCount() <= 1);
Result->GetIndexBuffers().SetBufferCount(1);
FMeshBuffer& ResultIndexBuffer = Result->GetIndexBuffers().Buffers[0];
const FMeshBuffer& FirstIndexBuffer = pFirst->GetIndexBuffers().Buffers[0];
const FMeshBuffer& SecondIndexBuffer = pSecond->GetIndexBuffers().Buffers[0];
// Avoid unused variable warnings
(void)FirstIndexBuffer;
(void)SecondIndexBuffer;
// This will be changed below if need to change the format of the index buffers.
EMeshBufferFormat IndexBufferFormat = EMeshBufferFormat::None;
if (FirstCount && SecondCount)
{
check(!FirstIndexBuffer.Channels.IsEmpty());
// We need to know the total number of vertices in case we need to adjust the index buffer format.
const uint64 TotalVertexCount = pFirst->GetVertexBuffers().GetElementCount() + pSecond->GetVertexBuffers().GetElementCount();
const uint64 MaxValueBits = GetMeshFormatData(pFirst->GetIndexBuffers().Buffers[0].Channels[0].Format).MaxValueBits;
const uint64 MaxSupportedVertices = uint64(1) << MaxValueBits;
if (TotalVertexCount > MaxSupportedVertices)
{
IndexBufferFormat = TotalVertexCount > MAX_uint16 ? EMeshBufferFormat::UInt32 : EMeshBufferFormat::UInt16;
}
}
if (IndexBufferFormat != EMeshBufferFormat::None)
{
// We only support vertex indices in case of having to change the format.
check(FirstIndexBuffer.Channels.Num() == 1);
ResultIndexBuffer.Channels.SetNum(1);
ResultIndexBuffer.Channels[0].Semantic = EMeshBufferSemantic::VertexIndex;
ResultIndexBuffer.Channels[0].Format = IndexBufferFormat;
ResultIndexBuffer.Channels[0].ComponentCount = 1;
ResultIndexBuffer.Channels[0].SemanticIndex = 0;
ResultIndexBuffer.Channels[0].Offset = 0;
ResultIndexBuffer.ElementSize = GetMeshFormatData(IndexBufferFormat).SizeInBytes;
}
else if (FirstCount)
{
ResultIndexBuffer.Channels = FirstIndexBuffer.Channels;
ResultIndexBuffer.ElementSize = FirstIndexBuffer.ElementSize;
}
else if (SecondCount)
{
ResultIndexBuffer.Channels = SecondIndexBuffer.Channels;
ResultIndexBuffer.ElementSize = SecondIndexBuffer.ElementSize;
}
check(ResultIndexBuffer.Channels.Num() == 1);
check(ResultIndexBuffer.Channels[0].Semantic == EMeshBufferSemantic::VertexIndex);
if (!Result->IndexBuffers.IsDescriptor())
{
ResultIndexBuffer.Data.SetNum(ResultIndexBuffer.ElementSize * (FirstCount + SecondCount));
if (!ResultIndexBuffer.Data.IsEmpty())
{
if (FirstCount)
{
if (IndexBufferFormat == EMeshBufferFormat::None
|| IndexBufferFormat == FirstIndexBuffer.Channels[0].Format)
{
FMemory::Memcpy(&ResultIndexBuffer.Data[0],
&FirstIndexBuffer.Data[0],
FirstIndexBuffer.ElementSize * FirstCount);
}
else
{
// Conversion required
const uint8_t* pSource = &FirstIndexBuffer.Data[0];
uint8_t* pDest = &ResultIndexBuffer.Data[0];
switch (IndexBufferFormat)
{
case EMeshBufferFormat::UInt32:
{
switch (FirstIndexBuffer.Channels[0].Format)
{
case EMeshBufferFormat::UInt16:
{
for (int32 v = 0; v < FirstCount; ++v)
{
*(uint32_t*)pDest = *(const uint16*)pSource;
pSource += FirstIndexBuffer.ElementSize;
pDest += ResultIndexBuffer.ElementSize;
}
break;
}
case EMeshBufferFormat::UInt8:
{
for (int32 v = 0; v < FirstCount; ++v)
{
*(uint32_t*)pDest = *(const uint8_t*)pSource;
pSource += FirstIndexBuffer.ElementSize;
pDest += ResultIndexBuffer.ElementSize;
}
break;
}
default:
checkf(false, TEXT("Format not supported."));
break;
}
break;
}
case EMeshBufferFormat::UInt16:
{
switch (FirstIndexBuffer.Channels[0].Format)
{
case EMeshBufferFormat::UInt8:
{
for (int32 v = 0; v < FirstCount; ++v)
{
*(uint16*)pDest = *(const uint8_t*)pSource;
pSource += FirstIndexBuffer.ElementSize;
pDest += ResultIndexBuffer.ElementSize;
}
break;
}
default:
checkf(false, TEXT("Format not supported."));
break;
}
break;
}
default:
checkf(false, TEXT("Format not supported."));
break;
}
}
}
if (SecondCount)
{
const uint8_t* pSource = &SecondIndexBuffer.Data[0];
uint8_t* pDest = &ResultIndexBuffer.Data[ResultIndexBuffer.ElementSize * FirstCount];
uint32_t firstVertexCount = pFirst->GetVertexBuffers().GetElementCount();
if (IndexBufferFormat == EMeshBufferFormat::None
|| IndexBufferFormat == SecondIndexBuffer.Channels[0].Format)
{
switch (SecondIndexBuffer.Channels[0].Format)
{
case EMeshBufferFormat::Int32:
case EMeshBufferFormat::UInt32:
case EMeshBufferFormat::NInt32:
case EMeshBufferFormat::NUInt32:
{
for (int32 v = 0; v < SecondCount; ++v)
{
*(uint32_t*)pDest = firstVertexCount + *(const uint32_t*)pSource;
pSource += SecondIndexBuffer.ElementSize;
pDest += ResultIndexBuffer.ElementSize;
}
break;
}
case EMeshBufferFormat::Int16:
case EMeshBufferFormat::UInt16:
case EMeshBufferFormat::NInt16:
case EMeshBufferFormat::NUInt16:
{
for (int32 v = 0; v < SecondCount; ++v)
{
*(uint16*)pDest = uint16(firstVertexCount) + *(const uint16*)pSource;
pSource += SecondIndexBuffer.ElementSize;
pDest += ResultIndexBuffer.ElementSize;
}
break;
}
case EMeshBufferFormat::Int8:
case EMeshBufferFormat::UInt8:
case EMeshBufferFormat::NInt8:
case EMeshBufferFormat::NUInt8:
{
for (int32 v = 0; v < SecondCount; ++v)
{
*(uint8_t*)pDest = uint8_t(firstVertexCount) + *(const uint8_t*)pSource;
pSource += SecondIndexBuffer.ElementSize;
pDest += ResultIndexBuffer.ElementSize;
}
break;
}
default:
checkf(false, TEXT("Format not supported."));
break;
}
}
else
{
// Format conversion required
switch (IndexBufferFormat)
{
case EMeshBufferFormat::UInt32:
{
switch (SecondIndexBuffer.Channels[0].Format)
{
case EMeshBufferFormat::Int16:
case EMeshBufferFormat::UInt16:
case EMeshBufferFormat::NInt16:
case EMeshBufferFormat::NUInt16:
{
for (int32 v = 0; v < SecondCount; ++v)
{
*(uint32_t*)pDest = uint32_t(firstVertexCount) + *(const uint16*)pSource;
pSource += SecondIndexBuffer.ElementSize;
pDest += ResultIndexBuffer.ElementSize;
}
break;
}
case EMeshBufferFormat::Int8:
case EMeshBufferFormat::UInt8:
case EMeshBufferFormat::NInt8:
case EMeshBufferFormat::NUInt8:
{
for (int32 v = 0; v < SecondCount; ++v)
{
*(uint32_t*)pDest = uint32_t(firstVertexCount) + *(const uint8_t*)pSource;
pSource += SecondIndexBuffer.ElementSize;
pDest += ResultIndexBuffer.ElementSize;
}
break;
}
default:
checkf(false, TEXT("Format not supported."));
break;
}
break;
}
case EMeshBufferFormat::UInt16:
{
switch (SecondIndexBuffer.Channels[0].Format)
{
case EMeshBufferFormat::Int8:
case EMeshBufferFormat::UInt8:
case EMeshBufferFormat::NInt8:
case EMeshBufferFormat::NUInt8:
{
for (int32 v = 0; v < SecondCount; ++v)
{
*(uint16*)pDest = uint16(firstVertexCount) + *(const uint8_t*)pSource;
pSource += SecondIndexBuffer.ElementSize;
pDest += ResultIndexBuffer.ElementSize;
}
break;
}
default:
checkf(false, TEXT("Format not supported."));
break;
}
break;
}
default:
checkf(false, TEXT("Format not supported."));
break;
}
}
}
}
}
}
// Layouts
//-----------------
{
MUTABLE_CPUPROFILER_SCOPE(Layouts);
int32 ResultLayoutCount = FMath::Max(pFirst->Layouts.Num(),pSecond->Layouts.Num());
Result->Layouts.SetNum(ResultLayoutCount);
for (int32 LayoutIndex = 0; LayoutIndex < ResultLayoutCount; ++LayoutIndex)
{
TSharedPtr<FLayout> pR;
if (LayoutIndex < pFirst->Layouts.Num())
{
const FLayout* pF = pFirst->Layouts[LayoutIndex].Get();
pR = pF->Clone();
}
if (LayoutIndex < pSecond->Layouts.Num())
{
const FLayout* pS = pSecond->Layouts[LayoutIndex].Get();
if (!pR)
{
pR = pS->Clone();
}
else
{
pR->Blocks.Append(pS->Blocks);
}
}
Result->Layouts[LayoutIndex] = pR;
}
}
// Skeleton
//---------------------------
// Add SkeletonIDs
Result->SkeletonIDs = pFirst->SkeletonIDs;
for (const int32 SkeletonID : pSecond->SkeletonIDs)
{
Result->SkeletonIDs.AddUnique(SkeletonID);
}
// Do they have the same skeleton?
bool bMergeSkeletons = pFirst->GetSkeleton() != pSecond->GetSkeleton();
// Are they different skeletons but with the same data?
if (bMergeSkeletons && pFirst->GetSkeleton() && pSecond->GetSkeleton())
{
bMergeSkeletons = !(*pFirst->GetSkeleton() == *pSecond->GetSkeleton());
}
if (bMergeSkeletons)
{
MUTABLE_CPUPROFILER_SCOPE(MergeSkeleton);
TSharedPtr<FSkeleton> ResultSkeleton;
TSharedPtr<const FSkeleton> FirstSkeleton = pFirst->GetSkeleton();
TSharedPtr<const FSkeleton> SecondSkeleton = pSecond->GetSkeleton();
const int32 NumBonesFirst = FirstSkeleton ? FirstSkeleton->GetBoneCount() : 0;
const int32 NumBonesSecond = SecondSkeleton ? SecondSkeleton->GetBoneCount() : 0;
ResultSkeleton = FirstSkeleton ? FirstSkeleton->Clone() : MakeShared<FSkeleton>();
Result->SetSkeleton(ResultSkeleton);
TArray<uint16> SecondToResultBoneIndices;
SecondToResultBoneIndices.SetNumUninitialized(NumBonesSecond);
// Merge pSecond and build the remap array
for (int32 SecondBoneIndex = 0; SecondBoneIndex < NumBonesSecond; ++SecondBoneIndex)
{
const FBoneName& BoneNameId = SecondSkeleton->BoneIds[SecondBoneIndex];
int32 Index = ResultSkeleton->FindBone(BoneNameId);
// Add a new bone
if (Index == INDEX_NONE)
{
Index = ResultSkeleton->BoneIds.Add(BoneNameId);
// Add an incorrect index, to be fixed below in case the parent index is later in the bone array.
ResultSkeleton->BoneParents.Add(SecondSkeleton->BoneParents[SecondBoneIndex]);
#if WITH_EDITOR
if (SecondSkeleton->DebugBoneNames.IsValidIndex(SecondBoneIndex))
{
ResultSkeleton->DebugBoneNames.Add(SecondSkeleton->DebugBoneNames[SecondBoneIndex]);
}
#endif
}
SecondToResultBoneIndices[SecondBoneIndex] = (uint16)Index;
}
// Fix second mesh bone parents
for (int32 ob = NumBonesFirst; ob < ResultSkeleton->BoneParents.Num(); ++ob)
{
int16 secondMeshIndex = ResultSkeleton->BoneParents[ob];
if (secondMeshIndex != INDEX_NONE)
{
ResultSkeleton->BoneParents[ob] = SecondToResultBoneIndices[secondMeshIndex];
}
}
}
else
{
Result->SetSkeleton(pFirst->GetSkeleton());
}
// Surfaces
//---------------------------
// Remap bone indices if we merge surfaces since bonemaps will be merged too.
bool bRemapBoneIndices = false;
TArray<uint16> RemappedBoneMapIndices;
// Used to know the format of the bone index buffer
uint32 MaxNumBonesInBoneMaps = 0;
const int32 NumSecondBonesInBoneMap = pSecond->BoneMap.Num();
{
MUTABLE_CPUPROFILER_SCOPE(Surfaces);
const int32 NumFirstBonesInBoneMap = pFirst->BoneMap.Num();
Result->BoneMap = pFirst->BoneMap;
if (bMergeSurfaces)
{
// Merge BoneMaps
RemappedBoneMapIndices.SetNumUninitialized(NumSecondBonesInBoneMap);
for (uint16 SecondBoneMapIndex = 0; SecondBoneMapIndex < NumSecondBonesInBoneMap; ++SecondBoneMapIndex)
{
const int32 BoneMapIndex = Result->BoneMap.AddUnique(pSecond->BoneMap[SecondBoneMapIndex]);
RemappedBoneMapIndices[SecondBoneMapIndex] = BoneMapIndex;
bRemapBoneIndices = bRemapBoneIndices || BoneMapIndex != SecondBoneMapIndex;
}
FMeshSurface& NewSurface = Result->Surfaces.AddDefaulted_GetRef();
NewSurface.BoneMapCount = Result->BoneMap.Num();
int32 NumFirstSubMeshes = 0;
for (const FMeshSurface& Surf : pFirst->Surfaces)
{
NewSurface.SubMeshes.Append(Surf.SubMeshes);
NumFirstSubMeshes += Surf.SubMeshes.Num();
}
for (const FMeshSurface& Surf : pSecond->Surfaces)
{
NewSurface.SubMeshes.Append(Surf.SubMeshes);
}
// Fix surface Submesh ranges.
if (NumFirstSubMeshes > 0)
{
const int32 NumResultSubMeshes = NewSurface.SubMeshes.Num();
const FSurfaceSubMesh LastFromFirstMesh = pFirst->Surfaces.Last().SubMeshes.Last();
for (int32 SecondSubMeshIndex = NumFirstSubMeshes; SecondSubMeshIndex < NumResultSubMeshes; ++SecondSubMeshIndex)
{
NewSurface.SubMeshes[SecondSubMeshIndex].VertexBegin += LastFromFirstMesh.VertexEnd;
NewSurface.SubMeshes[SecondSubMeshIndex].VertexEnd += LastFromFirstMesh.VertexEnd;
NewSurface.SubMeshes[SecondSubMeshIndex].IndexBegin += LastFromFirstMesh.IndexEnd;
NewSurface.SubMeshes[SecondSubMeshIndex].IndexEnd += LastFromFirstMesh.IndexEnd;
}
}
}
else
{
// Add the BoneMap of the second mesh
Result->BoneMap.Append(pSecond->BoneMap);
// Add pFirst surfaces
Result->Surfaces = pFirst->Surfaces;
const int32 FirstVertexEnd = pFirst->GetVertexCount();
const int32 FirstIndexEnd = pFirst->GetIndexCount();
check(pSecond->Surfaces.Num() == 1);
FMeshSurface& NewSurface = Result->Surfaces.Add_GetRef(pSecond->Surfaces[0]);
for (FSurfaceSubMesh& SubMesh : NewSurface.SubMeshes)
{
SubMesh.VertexBegin += FirstVertexEnd;
SubMesh.VertexEnd += FirstVertexEnd;
SubMesh.IndexBegin += FirstIndexEnd;
SubMesh.IndexEnd += FirstIndexEnd;
}
NewSurface.BoneMapIndex += NumFirstBonesInBoneMap;
}
for (const FMeshSurface& Surface : Result->Surfaces)
{
MaxNumBonesInBoneMaps = FMath::Max(MaxNumBonesInBoneMaps, Surface.BoneMapCount);
}
Result->BoneMap.Shrink();
}
// Pose
//---------------------------
if (Result->GetSkeleton())
{
MUTABLE_CPUPROFILER_SCOPE(Pose);
if (Result->GetSkeleton())
{
Result->BonePoses.Reserve(Result->GetSkeleton()->GetBoneCount());
}
// Copy poses from the first mesh
Result->BonePoses = pFirst->BonePoses;
// Add or override bone poses
for (const FMesh::FBonePose& SecondBonePose : pSecond->BonePoses)
{
const int32 ResultBoneIndex = Result->FindBonePose(SecondBonePose.BoneId);
if (ResultBoneIndex != INDEX_NONE)
{
FMesh::FBonePose& ResultBonePose = Result->BonePoses[ResultBoneIndex];
// TODO: Not sure how to tune this priority, review it.
// For now use a similar strategy as before.
auto ComputeBoneMergePriority = [](const FMesh::FBonePose& BonePose)
{
return (EnumHasAnyFlags(BonePose.BoneUsageFlags, EBoneUsageFlags::Skinning) ? 1 : 0) +
(EnumHasAnyFlags(BonePose.BoneUsageFlags, EBoneUsageFlags::Reshaped) ? 1 : 0);
};
if (ComputeBoneMergePriority(ResultBonePose) < ComputeBoneMergePriority(SecondBonePose))
{
//ResultBonePose.BoneName = SecondBonePose.BoneName;
ResultBonePose.BoneTransform = SecondBonePose.BoneTransform;
// Merge usage flags
EnumAddFlags(ResultBonePose.BoneUsageFlags, SecondBonePose.BoneUsageFlags);
}
}
else
{
Result->BonePoses.Add(SecondBonePose);
}
}
Result->BonePoses.Shrink();
}
// PhysicsBodies
//---------------------------
{
MUTABLE_CPUPROFILER_SCOPE(PhysicsBodies);
// Appends InPhysicsBody to OutPhysicsBody removing Bodies that are equal, have same bone and customId and its properies are identical.
auto AppendPhysicsBodiesUnique = [](FPhysicsBody& OutPhysicsBody, const FPhysicsBody& InPhysicsBody) -> bool
{
TArray<FBoneName>& OutBones = OutPhysicsBody.BoneIds;
TArray<int32>& OutCustomIds = OutPhysicsBody.BodiesCustomIds;
TArray<FPhysicsBodyAggregate>& OutBodies = OutPhysicsBody.Bodies;
const TArray<FBoneName>& InBones = InPhysicsBody.BoneIds;
const TArray<int32>& InCustomIds = InPhysicsBody.BodiesCustomIds;
const TArray<FPhysicsBodyAggregate>& InBodies = InPhysicsBody.Bodies;
const int32 InBodyCount = InPhysicsBody.GetBodyCount();
const int32 OutBodyCount = OutPhysicsBody.GetBodyCount();
bool bModified = false;
for (int32 InBodyIndex = 0; InBodyIndex < InBodyCount; ++InBodyIndex)
{
int32 FoundIndex = INDEX_NONE;
for (int32 OutBodyIndex = 0; OutBodyIndex < OutBodyCount; ++OutBodyIndex)
{
if (InCustomIds[InBodyIndex] == OutCustomIds[OutBodyIndex] && InBones[InBodyIndex] == OutBones[OutBodyIndex])
{
FoundIndex = OutBodyIndex;
break;
}
}
if (FoundIndex == INDEX_NONE)
{
OutBones.Add(InBones[InBodyIndex]);
OutCustomIds.Add(InCustomIds[InBodyIndex]);
OutBodies.Add(InBodies[InBodyIndex]);
bModified |= true;
continue;
}
for (const FSphereBody& Body : InBodies[InBodyIndex].Spheres)
{
const int32 NumBeforeAddition = OutBodies[FoundIndex].Spheres.Num();
bModified |= NumBeforeAddition == OutBodies[FoundIndex].Spheres.AddUnique(Body);
}
for (const FBoxBody& Body : InBodies[InBodyIndex].Boxes)
{
const int32 NumBeforeAddition = OutBodies[FoundIndex].Boxes.Num();
bModified |= NumBeforeAddition == OutBodies[FoundIndex].Boxes.AddUnique(Body);
}
for (const FSphylBody& Body : InBodies[InBodyIndex].Sphyls)
{
const int32 NumBeforeAddition = OutBodies[FoundIndex].Sphyls.Num();
bModified |= NumBeforeAddition == OutBodies[FoundIndex].Sphyls.AddUnique(Body);
}
for (const FTaperedCapsuleBody& Body : InBodies[InBodyIndex].TaperedCapsules)
{
const int32 NumBeforeAddition = OutBodies[FoundIndex].TaperedCapsules.Num();
bModified |= NumBeforeAddition == OutBodies[FoundIndex].TaperedCapsules.AddUnique(Body);
}
for (const FConvexBody& Body : InBodies[InBodyIndex].Convex)
{
const int32 NumBeforeAddition = OutBodies[FoundIndex].Convex.Num();
bModified |= NumBeforeAddition == OutBodies[FoundIndex].Convex.AddUnique(Body);
}
}
return bModified;
};
TTuple<TSharedPtr<const FPhysicsBody>, bool> SharedResultPhysicsBody = Invoke([&]()
-> TTuple<TSharedPtr<const FPhysicsBody>, bool>
{
if (pFirst->GetPhysicsBody() == pSecond->GetPhysicsBody())
{
return MakeTuple(pFirst->GetPhysicsBody(), true);
}
if (pFirst->GetPhysicsBody() && !pSecond->GetPhysicsBody())
{
return MakeTuple(pFirst->GetPhysicsBody(), true);
}
if (!pFirst->GetPhysicsBody() && pSecond->GetPhysicsBody())
{
return MakeTuple(pSecond->GetPhysicsBody(), true);
}
return MakeTuple(nullptr, false);
});
if (SharedResultPhysicsBody.Get<1>())
{
// Only one or non of the meshes has physics, share the result.
Result->SetPhysicsBody(SharedResultPhysicsBody.Get<0>());
}
else
{
check(pFirst->GetPhysicsBody() && pSecond->GetPhysicsBody());
TSharedPtr<FPhysicsBody> MergedResultPhysicsBody = pFirst->GetPhysicsBody()->Clone();
MergedResultPhysicsBody->bBodiesModified =
AppendPhysicsBodiesUnique(*MergedResultPhysicsBody, *pSecond->GetPhysicsBody()) ||
pFirst->GetPhysicsBody()->bBodiesModified || pSecond->GetPhysicsBody()->bBodiesModified;
Result->SetPhysicsBody(MergedResultPhysicsBody);
}
// Additional physics bodies.
const int32 MaxAdditionalPhysicsResultNum = pFirst->AdditionalPhysicsBodies.Num() + pSecond->AdditionalPhysicsBodies.Num();
Result->AdditionalPhysicsBodies.Reserve(MaxAdditionalPhysicsResultNum);
Result->AdditionalPhysicsBodies.Append(pFirst->AdditionalPhysicsBodies);
// Not very many additional bodies expected, do a quadratic search to have unique bodies based on external id.
const int32 NumSecondAdditionalBodies = pSecond->AdditionalPhysicsBodies.Num();
for (int32 Index = 0; Index < NumSecondAdditionalBodies; ++Index)
{
const int32 CustomIdToMerge = pSecond->AdditionalPhysicsBodies[Index]->CustomId;
const TSharedPtr<const FPhysicsBody>* Found = pFirst->AdditionalPhysicsBodies.FindByPredicate(
[CustomIdToMerge](const TSharedPtr<const FPhysicsBody>& Body) { return CustomIdToMerge == Body->CustomId; });
// TODO: current usages do not expect collisions, but same Id collision with bodies modified in differnet ways
// may need to be contemplated at some point.
if (!Found)
{
Result->AdditionalPhysicsBodies.Add(pSecond->AdditionalPhysicsBodies[Index]);
}
}
}
// This affects both vertex IDs and layout block ids.
bool bNeedsExplicitIds = pFirst->MeshIDPrefix != pSecond->MeshIDPrefix;
// These two extra checks are necessary for corner cases of meshes merging with fragments of themselves that
// undergo different operations.
if (!bNeedsExplicitIds)
{
UntypedMeshBufferIteratorConst FirstIDs(pFirst->VertexBuffers, EMeshBufferSemantic::VertexIndex, 0);
UntypedMeshBufferIteratorConst SecondIDs(pSecond->VertexBuffers, EMeshBufferSemantic::VertexIndex, 0);
bNeedsExplicitIds = FirstIDs.GetFormat() != SecondIDs.GetFormat();
}
if (!bNeedsExplicitIds)
{
UntypedMeshBufferIteratorConst FirstIDs(pFirst->VertexBuffers, EMeshBufferSemantic::LayoutBlock, 0);
UntypedMeshBufferIteratorConst SecondIDs(pSecond->VertexBuffers, EMeshBufferSemantic::LayoutBlock, 0);
bNeedsExplicitIds = FirstIDs.GetFormat() != SecondIDs.GetFormat();
}
if (!bNeedsExplicitIds)
{
// This is needed in case a mesh merges with itself.
Result->MeshIDPrefix = pFirst->MeshIDPrefix;
}
// Vertices
//-----------------
{
MUTABLE_CPUPROFILER_SCOPE(Vertices);
const int32 FirstBufferCount = pFirst->VertexBuffers.Buffers.Num();
const int32 SecondBufferCount = pSecond->VertexBuffers.Buffers.Num();
const int32 FirstVertexCount = pFirst->GetVertexBuffers().GetElementCount();
const int32 SecondVertexCount = pSecond->GetVertexBuffers().GetElementCount();
// Check if the format of the BoneIndex buffer has to change
bool bChangeBoneIndicesFormat = false;
EMeshBufferFormat BoneIndexFormat = MaxNumBonesInBoneMaps > MAX_uint8 ? EMeshBufferFormat::UInt16 : EMeshBufferFormat::UInt8;
bChangeBoneIndicesFormat = pFirst->GetVertexBuffers().HasAnySemanticWithDifferentFormat(EMeshBufferSemantic::BoneIndices, BoneIndexFormat);
if (!bChangeBoneIndicesFormat)
{
bChangeBoneIndicesFormat = pSecond->GetVertexBuffers().HasAnySemanticWithDifferentFormat(EMeshBufferSemantic::BoneIndices, BoneIndexFormat);
}
// Step 1: Set up the initial vertex buffer structure of the result mesh.
//-----------------------------------------------------------------------
{
MUTABLE_CPUPROFILER_SCOPE(ResultFormatSetup);
// Start with the structure of the first mesh
Result->GetVertexBuffers().SetBufferCount(FirstBufferCount);
for (int32 BufferIndex = 0; BufferIndex < FirstBufferCount; ++BufferIndex)
{
Result->VertexBuffers.Buffers[BufferIndex].Channels = pFirst->VertexBuffers.Buffers[BufferIndex].Channels;
Result->VertexBuffers.Buffers[BufferIndex].ElementSize = pFirst->VertexBuffers.Buffers[BufferIndex].ElementSize;
}
// See if we need to add additional buffers from the second mesh (like vertex colours or additional UV Channels)
// This is a bit ad-hoc: we only add buffers containing all new channels
for (const FMeshBuffer& SecondBuf : pSecond->GetVertexBuffers().Buffers)
{
bool bSomeChannel = false;
bool bAllNewChannels = true;
for (const FMeshBufferChannel& SecondChan : SecondBuf.Channels)
{
// Skip system buffers
if (SecondChan.Semantic == EMeshBufferSemantic::VertexIndex
||
SecondChan.Semantic == EMeshBufferSemantic::LayoutBlock)
{
continue;
}
bSomeChannel = true;
int32 FoundBuffer = -1;
int32 FoundChannel = -1;
pFirst->GetVertexBuffers().FindChannel(SecondChan.Semantic, SecondChan.SemanticIndex, &FoundBuffer, &FoundChannel);
if (FoundBuffer >= 0)
{
// There's at least one channel that already exists in the first mesh. Don't add the buffer.
bAllNewChannels = false;
continue;
}
// If there are additional UV channels try to add them
if (!bAllNewChannels && SecondChan.Semantic == EMeshBufferSemantic::TexCoords
&&
SecondChan.SemanticIndex > 0)
{
// Add additional UV channels if the previous one is found.
FMeshBufferSet& ResultVertexBuffers = Result->GetVertexBuffers();
ResultVertexBuffers.FindChannel(EMeshBufferSemantic::TexCoords, SecondChan.SemanticIndex - 1, &FoundBuffer, &FoundChannel);
if (FoundBuffer >= 0)
{
FMeshBuffer& Buffer = ResultVertexBuffers.Buffers[FoundBuffer];
Buffer.Channels.Insert(SecondChan, FoundChannel + 1);
ResultVertexBuffers.UpdateOffsets(FoundBuffer);
}
}
}
if (bSomeChannel && bAllNewChannels)
{
int32 NewBufferIndex = Result->GetVertexBuffers().Buffers.Emplace();
Result->VertexBuffers.Buffers[NewBufferIndex].Channels = SecondBuf.Channels;
Result->VertexBuffers.Buffers[NewBufferIndex].ElementSize = SecondBuf.ElementSize;
}
}
// See if we need to enlarge the components of any of the result channels
int32 ResultBufferCount = Result->GetVertexBuffers().GetBufferCount();
for (int32 BufferIndex = 0; BufferIndex < FMath::Min(ResultBufferCount, FirstBufferCount); ++BufferIndex)
{
// Expand component counts in vertex channels of the format mesh
FMeshBuffer& result = Result->GetVertexBuffers().Buffers[BufferIndex];
bool bResetOffsets = false;
for (int32 c = 0; c < result.Channels.Num(); ++c)
{
int32 sb = -1;
int32 sc = -1;
pSecond->GetVertexBuffers().FindChannel
(
result.Channels[c].Semantic,
result.Channels[c].SemanticIndex,
&sb, &sc
);
if (sb >= 0)
{
const FMeshBuffer& second = pSecond->GetVertexBuffers().Buffers[sb];
if (second.Channels[sc].ComponentCount>result.Channels[c].ComponentCount)
{
result.Channels[c].ComponentCount = second.Channels[sc].ComponentCount;
bResetOffsets = true;
}
}
}
// Reset the channel offsets if necessary
if (bResetOffsets)
{
Result->GetVertexBuffers().UpdateOffsets(BufferIndex);
}
}
// Change the format of the bone indices buffer
if (bChangeBoneIndicesFormat)
{
// Iterate all vertex buffers and update the format
FMeshBufferSet& VertexBuffers = Result->GetVertexBuffers();
for (int32 VertexBufferIndex = 0; VertexBufferIndex < VertexBuffers.Buffers.Num(); ++VertexBufferIndex)
{
FMeshBuffer& result = VertexBuffers.Buffers[VertexBufferIndex];
const int32 ChannelsCount = VertexBuffers.GetBufferChannelCount(VertexBufferIndex);
for (int32 ChannelIndex = 0; ChannelIndex < ChannelsCount; ++ChannelIndex)
{
if (result.Channels[ChannelIndex].Semantic == EMeshBufferSemantic::BoneIndices)
{
result.Channels[ChannelIndex].Format = BoneIndexFormat;
// Reset offsets
int32 offset = 0;
for (int32 AuxChannelIndex = 0; AuxChannelIndex < ChannelsCount; ++AuxChannelIndex)
{
result.Channels[AuxChannelIndex].Offset = (uint8_t)offset;
offset += result.Channels[AuxChannelIndex].ComponentCount
*
GetMeshFormatData(result.Channels[AuxChannelIndex].Format).SizeInBytes;
}
result.ElementSize = offset;
}
}
}
}
// Manage vertex IDs
if (bNeedsExplicitIds)
{
// Make sure the result format is suitable for the explicit IDs
Result->MakeIdsExplicit();
}
}
// Step 2: Fill the result buffers
//-----------------------------------------------------------------------
if (pFirst->VertexBuffers.IsDescriptor() || pSecond->VertexBuffers.IsDescriptor())
{
EnumAddFlags(Result->VertexBuffers.Flags, EMeshBufferSetFlags::IsDescriptor);
}
Result->VertexBuffers.SetElementCount(FirstVertexCount + SecondVertexCount);
if (!Result->VertexBuffers.IsDescriptor())
{
MUTABLE_CPUPROFILER_SCOPE(ResultFill);
// We have the final result vertex buffers structure: allocate the memory for them.
int32 ResultBufferCount = Result->GetVertexBuffers().GetBufferCount();
for (int32 ResultBufferIndex = 0; ResultBufferIndex < ResultBufferCount; ++ResultBufferIndex)
{
FMeshBuffer& ResultBuffer = Result->VertexBuffers.Buffers[ResultBufferIndex];
// TODO: Don't assume the buffer order in First and Second matches Result, for more opportunities of fast-path
bool bFirstFastPath = (ResultBufferIndex < FirstBufferCount)
&&
pFirst->VertexBuffers.HasSameFormat(ResultBufferIndex, Result->VertexBuffers, ResultBufferIndex);
int32 FirstResultBufferSize = ResultBuffer.ElementSize * FirstVertexCount;
uint8* Destination = ResultBuffer.Data.GetData();
if (bFirstFastPath)
{
MUTABLE_CPUPROFILER_SCOPE(FirstFastPath);
const FMeshBuffer& FirstBuffer = pFirst->VertexBuffers.Buffers[ResultBufferIndex];
check(FirstResultBufferSize == FirstBuffer.Data.Num());
FMemory::Memcpy(Destination, FirstBuffer.Data.GetData(), FirstResultBufferSize);
}
else
{
MUTABLE_CPUPROFILER_SCOPE(FirstSlowPath);
int32 OffsetElements = 0;
MeshFormatBuffer(pFirst->VertexBuffers, ResultBuffer, OffsetElements, true, pFirst->MeshIDPrefix);
}
bool bSecondFastPath = (ResultBufferIndex < SecondBufferCount)
&&
pSecond->VertexBuffers.HasSameFormat(ResultBufferIndex, Result->VertexBuffers, ResultBufferIndex);
int32 SecondResultBufferSize = ResultBuffer.ElementSize * SecondVertexCount;
Destination = ResultBuffer.Data.GetData() + FirstResultBufferSize;
if (bSecondFastPath)
{
MUTABLE_CPUPROFILER_SCOPE(SecondFastPath);
const FMeshBuffer& SecondBuffer = pSecond->VertexBuffers.Buffers[ResultBufferIndex];
check(SecondResultBufferSize == SecondBuffer.Data.Num());
FMemory::Memcpy(Destination, SecondBuffer.Data.GetData(), SecondResultBufferSize);
}
else
{
MUTABLE_CPUPROFILER_SCOPE(SecondSlowPath);
int32 OffsetElements = FirstVertexCount;
MeshFormatBuffer(pSecond->VertexBuffers, ResultBuffer, OffsetElements, true, pSecond->MeshIDPrefix);
}
}
// TODO: This still needs to be optimized
if (bRemapBoneIndices)
{
MUTABLE_CPUPROFILER_SCOPE(RemapBones);
// We need to remap the bone indices of the second mesh vertices that we already copied to result
check(!RemappedBoneMapIndices.IsEmpty());
// Iterate all vertex buffers and update the format
FMeshBufferSet& VertexBuffers = Result->GetVertexBuffers();
for (int32 VertexBufferIndex = 0; VertexBufferIndex < VertexBuffers.Buffers.Num(); ++VertexBufferIndex)
{
FMeshBuffer& ResultBuffer = VertexBuffers.Buffers[VertexBufferIndex];
const int32 ElemSize = VertexBuffers.GetElementSize(VertexBufferIndex);
const int32 FirstSize = FirstVertexCount * ElemSize;
const int32 ChannelsCount = VertexBuffers.GetBufferChannelCount(VertexBufferIndex);
for (int32 ChannelIndex = 0; ChannelIndex < ChannelsCount; ++ChannelIndex)
{
if (ResultBuffer.Channels[ChannelIndex].Semantic != EMeshBufferSemantic::BoneIndices)
{
continue;
}
int32 ResultOffset = FirstSize + ResultBuffer.Channels[ChannelIndex].Offset;
const int32 NumComponents = ResultBuffer.Channels[ChannelIndex].ComponentCount;
// Bone indices may need remapping
for (int32 VertexIndex = 0; VertexIndex < pSecond->GetVertexCount(); ++VertexIndex)
{
switch (BoneIndexFormat)
{
case EMeshBufferFormat::Int8:
case EMeshBufferFormat::UInt8:
{
uint8* pD = reinterpret_cast<uint8*>(&ResultBuffer.Data[ResultOffset]);
for (int32 ComponentIndex = 0; ComponentIndex < NumComponents; ++ComponentIndex)
{
uint8 BoneMapIndex = pD[ComponentIndex];
// be defensive
if (BoneMapIndex < NumSecondBonesInBoneMap)
{
pD[ComponentIndex] = (uint8)RemappedBoneMapIndices[BoneMapIndex];
}
else
{
pD[ComponentIndex] = 0;
}
}
ResultOffset += ElemSize;
break;
}
case EMeshBufferFormat::Int16:
case EMeshBufferFormat::UInt16:
{
uint16* pD = reinterpret_cast<uint16*>(&ResultBuffer.Data[ResultOffset]);
for (int32 ComponentIndex = 0; ComponentIndex < NumComponents; ++ComponentIndex)
{
uint16 BoneMapIndex = pD[ComponentIndex];
// be defensive
if (BoneMapIndex < NumSecondBonesInBoneMap)
{
pD[ComponentIndex] = (uint16)RemappedBoneMapIndices[BoneMapIndex];
}
else
{
pD[ComponentIndex] = 0;
}
}
ResultOffset += ElemSize;
break;
}
case EMeshBufferFormat::Int32:
case EMeshBufferFormat::UInt32:
{
// Unreal does not support 32 bit bone indices
checkf(false, TEXT("Format not supported."));
break;
}
default:
checkf(false, TEXT("Format not supported."));
}
}
}
}
}
}
}
// Tags
Result->Tags = pFirst->Tags;
for (const FString& SecondTag : pSecond->Tags)
{
Result->Tags.AddUnique(SecondTag);
}
// Streamed Resources
Result->StreamedResources = pFirst->StreamedResources;
const int32 NumStreamedResources = pSecond->StreamedResources.Num();
for (int32 Index = 0; Index < NumStreamedResources; ++Index)
{
Result->StreamedResources.AddUnique(pSecond->StreamedResources[Index]);
}
}
//---------------------------------------------------------------------------------------------
//!
//---------------------------------------------------------------------------------------------
inline void ExtendSkeleton( FSkeleton* pBase, const FSkeleton* pOther )
{
TMap<int32,int32> otherToResult;
int32 initialBones = pBase->GetBoneCount();
for (int32 b = 0; pOther && b < pOther->GetBoneCount(); ++b)
{
int32 resultBoneIndex = pBase->FindBone(pOther->GetBoneName(b));
if (resultBoneIndex < 0)
{
int32 newIndex = pBase->BoneIds.Num();
otherToResult.Add(b, newIndex);
pBase->BoneIds.Add(pOther->BoneIds[b]);
// Will be remapped below
pBase->BoneParents.Add(pOther->BoneParents[b]);
#if WITH_EDITOR
if (pOther->DebugBoneNames.IsValidIndex(b))
{
pBase->DebugBoneNames.Add(pOther->DebugBoneNames[b]);
}
#endif
}
else
{
otherToResult.Add(b, resultBoneIndex);
}
}
// Fix bone parent indices of the bones added from pOther
for ( int32 b=initialBones;b<pBase->GetBoneCount(); ++b)
{
int16_t sourceIndex = pBase->BoneParents[b];
if (sourceIndex>=0)
{
pBase->BoneParents[b] = (int16_t)otherToResult[ sourceIndex ];
}
}
}
//---------------------------------------------------------------------------------------------
//! Return null if there is no need to remap the mesh
//---------------------------------------------------------------------------------------------
inline void MeshRemapSkeleton(FMesh* Result, const FMesh* SourceMesh, TSharedPtr<const FSkeleton> Skeleton, bool& bOutSuccess)
{
bOutSuccess = true;
if (SourceMesh->GetVertexCount() == 0 || !SourceMesh->GetSkeleton() || SourceMesh->GetSkeleton()->GetBoneCount() == 0)
{
bOutSuccess = false;
return;
}
Result->CopyFrom(*SourceMesh);
Result->SetSkeleton(Skeleton);
}
}