1627 lines
42 KiB
C++
1627 lines
42 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "MuR/Mesh.h"
|
|
|
|
#include "Containers/UnrealString.h"
|
|
#include "HAL/LowLevelMemTracker.h"
|
|
#include "HAL/PlatformCrt.h"
|
|
#include "HAL/UnrealMemory.h"
|
|
#include "MuR/MeshPrivate.h"
|
|
#include "MuR/MutableTrace.h"
|
|
#include "MuR/OpMeshClipWithMesh.h"
|
|
#include "Spatial/PointHashGrid3.h"
|
|
|
|
namespace mu
|
|
{
|
|
|
|
MUTABLE_IMPLEMENT_ENUM_SERIALISABLE(EBoneUsageFlags);
|
|
MUTABLE_IMPLEMENT_ENUM_SERIALISABLE(EMeshBufferType);
|
|
MUTABLE_IMPLEMENT_ENUM_SERIALISABLE(EShapeBindingMethod);
|
|
MUTABLE_IMPLEMENT_ENUM_SERIALISABLE(EVertexColorUsage);
|
|
|
|
|
|
void FMesh::Serialise(const FMesh* MeshPtr, FOutputArchive& Arch)
|
|
{
|
|
//MeshPtr->m_pD->CheckIntegrity();
|
|
Arch << *MeshPtr;
|
|
}
|
|
|
|
|
|
TSharedPtr<FMesh> FMesh::StaticUnserialise(FInputArchive& Arch)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(MeshUnserialise)
|
|
LLM_SCOPE_BYNAME(TEXT("MutableRuntime"));
|
|
|
|
TSharedPtr<FMesh> Result = MakeShared<FMesh>();
|
|
Arch >> *Result;
|
|
|
|
//Result->m_pD->CheckIntegrity();
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
TSharedPtr<FMesh> FMesh::CreateAsReference(uint32 ID, bool bForceLoad)
|
|
{
|
|
TSharedPtr<FMesh> Result = MakeShared<FMesh>();
|
|
Result->ReferenceID = ID;
|
|
|
|
EnumAddFlags(Result->Flags, EMeshFlags::IsResourceReference);
|
|
if (bForceLoad)
|
|
{
|
|
EnumAddFlags(Result->Flags, EMeshFlags::IsResourceForceLoad);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
bool FMesh::IsReference() const
|
|
{
|
|
return EnumHasAnyFlags(Flags, EMeshFlags::IsResourceReference);
|
|
}
|
|
|
|
|
|
bool FMesh::IsForceLoad() const
|
|
{
|
|
return EnumHasAnyFlags(Flags, EMeshFlags::IsResourceForceLoad);
|
|
}
|
|
|
|
|
|
uint32 FMesh::GetReferencedMesh() const
|
|
{
|
|
ensure(IsReference());
|
|
return ReferenceID;
|
|
}
|
|
|
|
|
|
void FMesh::SetReferencedMorph(const FString& MorphName)
|
|
{
|
|
ReferencedMorph = MorphName;
|
|
}
|
|
|
|
|
|
const FString& FMesh::GetReferencedMorph() const
|
|
{
|
|
return ReferencedMorph;
|
|
}
|
|
|
|
|
|
TSharedPtr<FMesh> FMesh::Clone() const
|
|
{
|
|
//MUTABLE_CPUPROFILER_SCOPE(MeshClone);
|
|
return Clone(EMeshCopyFlags::AllFlags);
|
|
}
|
|
|
|
|
|
TSharedPtr<FMesh> FMesh::Clone(EMeshCopyFlags InFlags) const
|
|
{
|
|
//MUTABLE_CPUPROFILER_SCOPE(MeshClone);
|
|
LLM_SCOPE_BYNAME(TEXT("MutableRuntime"));
|
|
|
|
TSharedPtr<FMesh> Result = MakeShared<FMesh>();
|
|
|
|
Result->CopyFrom(*this, InFlags);
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
void FMesh::CopyFrom(const FMesh& From, EMeshCopyFlags InFlags)
|
|
{
|
|
//MUTABLE_CPUPROFILER_SCOPE(CopyFrom);
|
|
|
|
InternalId = From.InternalId;
|
|
Flags = From.Flags;
|
|
ReferenceID = From.ReferenceID;
|
|
|
|
MeshIDPrefix = From.MeshIDPrefix;
|
|
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithSurfaces))
|
|
{
|
|
Surfaces = From.Surfaces;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithSkeleton))
|
|
{
|
|
Skeleton = From.Skeleton;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithPhysicsBody))
|
|
{
|
|
PhysicsBody = From.PhysicsBody;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithTags))
|
|
{
|
|
Tags = From.Tags;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithStreamedResources))
|
|
{
|
|
StreamedResources = From.StreamedResources;
|
|
}
|
|
|
|
// Copy the main buffers
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithVertexBuffers))
|
|
{
|
|
VertexBuffers = From.VertexBuffers;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithIndexBuffers))
|
|
{
|
|
IndexBuffers = From.IndexBuffers;
|
|
}
|
|
|
|
// Copy additional buffers
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithAdditionalBuffers))
|
|
{
|
|
AdditionalBuffers = From.AdditionalBuffers;
|
|
}
|
|
|
|
// Copy the layout
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithLayouts))
|
|
{
|
|
Layouts = From.Layouts;
|
|
}
|
|
// The skeleton is not copied because it is not owned by this mesh and it is always assumed
|
|
// to be shared.
|
|
|
|
// physics body doen't need to be deep copied either as they are also assumed to be shared.
|
|
|
|
// Copy bone poses
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithPoses))
|
|
{
|
|
BonePoses = From.BonePoses;
|
|
}
|
|
|
|
// Copy BoneMap
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithBoneMap))
|
|
{
|
|
BoneMap = From.BoneMap;
|
|
}
|
|
|
|
// Copy SkeletonIDs
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithSkeletonIDs))
|
|
{
|
|
SkeletonIDs = From.SkeletonIDs;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(InFlags, EMeshCopyFlags::WithAdditionalPhysics))
|
|
{
|
|
AdditionalPhysicsBodies = From.AdditionalPhysicsBodies;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
uint32 FMesh::GetId() const
|
|
{
|
|
return InternalId;
|
|
}
|
|
|
|
|
|
int FMesh::GetVertexCount() const
|
|
{
|
|
return GetVertexBuffers().GetElementCount();
|
|
}
|
|
|
|
|
|
FMeshBufferSet& FMesh::GetVertexBuffers()
|
|
{
|
|
return VertexBuffers;
|
|
}
|
|
|
|
|
|
const FMeshBufferSet& FMesh::GetVertexBuffers() const
|
|
{
|
|
return VertexBuffers;
|
|
}
|
|
|
|
|
|
bool FMesh::AreVertexIdsImplicit() const
|
|
{
|
|
// Is there a buffer for vertex ids?
|
|
int32 BufferIndex = -1;
|
|
int32 ChannelIndex = -1;
|
|
VertexBuffers.FindChannel(EMeshBufferSemantic::VertexIndex, 0, &BufferIndex, &ChannelIndex);
|
|
|
|
return (MeshIDPrefix != 0) && (BufferIndex < 0) && (ChannelIndex < 0);
|
|
}
|
|
|
|
|
|
bool FMesh::AreVertexIdsExplicit() const
|
|
{
|
|
// Is there a buffer for vertex ids?
|
|
int32 BufferIndex = -1;
|
|
int32 ChannelIndex = -1;
|
|
VertexBuffers.FindChannel(EMeshBufferSemantic::VertexIndex, 0, &BufferIndex, &ChannelIndex);
|
|
|
|
bool bExplicit =
|
|
(BufferIndex >= 0) && (ChannelIndex >= 0) &&
|
|
(VertexBuffers.Buffers[BufferIndex].Channels[ChannelIndex].Format == EMeshBufferFormat::UInt64);
|
|
|
|
if (bExplicit)
|
|
{
|
|
check(MeshIDPrefix == 0);
|
|
}
|
|
|
|
return bExplicit;
|
|
}
|
|
|
|
|
|
void FMesh::MakeVertexIdsRelative()
|
|
{
|
|
check(AreVertexIdsImplicit());
|
|
|
|
int32 NewBuffer = VertexBuffers.GetBufferCount();
|
|
VertexBuffers.SetBufferCount(NewBuffer + 1);
|
|
EMeshBufferSemantic Semantic = EMeshBufferSemantic::VertexIndex;
|
|
int32 SemanticIndex = 0;
|
|
EMeshBufferFormat Format = EMeshBufferFormat::UInt32;
|
|
int32 Components = 1;
|
|
int32 Offset = 0;
|
|
VertexBuffers.SetBuffer(NewBuffer, sizeof(uint32), 1, &Semantic, &SemanticIndex, &Format, &Components, &Offset);
|
|
uint32* IdDataPtr = reinterpret_cast<uint32*>(VertexBuffers.GetBufferData(NewBuffer));
|
|
|
|
int32 VertexCount = GetVertexCount();
|
|
for (int32 Index = 0; Index < VertexCount; ++Index)
|
|
{
|
|
(*IdDataPtr++) = Index;
|
|
}
|
|
}
|
|
|
|
|
|
void FMesh::MakeIdsExplicit()
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(Mesh_MakeIdsExplicit);
|
|
|
|
int32 VertexCount = GetVertexCount();
|
|
check(VertexCount == 0);
|
|
|
|
// Vertex IDs
|
|
{
|
|
bool bHasRelativeVertexIndices = false;
|
|
|
|
int32 OldBufferIndex = -1;
|
|
int32 OldChannelIndex = -1;
|
|
VertexBuffers.FindChannel(EMeshBufferSemantic::VertexIndex, 0, &OldBufferIndex, &OldChannelIndex);
|
|
bool bHasVertexIndices = (OldBufferIndex >= 0 && OldChannelIndex >= 0);
|
|
if (bHasVertexIndices)
|
|
{
|
|
check(OldChannelIndex == 0 && VertexBuffers.Buffers[OldBufferIndex].Channels.Num() == 1);
|
|
|
|
FMeshBuffer& Buffer = VertexBuffers.Buffers[OldBufferIndex];
|
|
Buffer.Channels[0].Format = EMeshBufferFormat::UInt64;
|
|
Buffer.ElementSize = sizeof(uint64);
|
|
}
|
|
|
|
else
|
|
{
|
|
// The mesh has implicit Ids
|
|
// Create a new buffer with explicit ids
|
|
FMeshBuffer& Buffer = VertexBuffers.Buffers.Emplace_GetRef();
|
|
Buffer.ElementSize = sizeof(uint64);
|
|
FMeshBufferChannel& Channel = Buffer.Channels.Emplace_GetRef();
|
|
Channel.Semantic = EMeshBufferSemantic::VertexIndex;
|
|
Channel.SemanticIndex= 0;
|
|
Channel.Format = EMeshBufferFormat::UInt64;
|
|
Channel.ComponentCount = 1;
|
|
Channel.Offset = 0;
|
|
}
|
|
}
|
|
|
|
// Layout block IDs
|
|
{
|
|
for (FMeshBuffer& Buffer : VertexBuffers.Buffers)
|
|
{
|
|
for (FMeshBufferChannel& Channel : Buffer.Channels)
|
|
{
|
|
if (Channel.Semantic != EMeshBufferSemantic::LayoutBlock)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
check(Buffer.Channels.Num() == 1);
|
|
check(Buffer.Channels[0].Offset == 0);
|
|
|
|
Buffer.Channels[0].Format = EMeshBufferFormat::UInt64;
|
|
Buffer.ElementSize = sizeof(uint64);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Final cleanup
|
|
MeshIDPrefix = 0;
|
|
}
|
|
|
|
|
|
TSharedPtr<const FSkeleton> FMesh::GetSkeleton() const
|
|
{
|
|
return Skeleton;
|
|
}
|
|
|
|
|
|
void FMesh::SetSkeleton(TSharedPtr<const FSkeleton> InSkeleton)
|
|
{
|
|
Skeleton = InSkeleton;
|
|
}
|
|
|
|
|
|
TSharedPtr<const FPhysicsBody> FMesh::GetPhysicsBody() const
|
|
{
|
|
return PhysicsBody;
|
|
}
|
|
|
|
|
|
void FMesh::SetPhysicsBody(TSharedPtr<const FPhysicsBody> InPhysicsBody)
|
|
{
|
|
PhysicsBody = InPhysicsBody;
|
|
}
|
|
|
|
|
|
int32 FMesh::AddAdditionalPhysicsBody(TSharedPtr<const FPhysicsBody> Body)
|
|
{
|
|
return AdditionalPhysicsBodies.Add(Body);
|
|
}
|
|
|
|
|
|
TSharedPtr<const FPhysicsBody> FMesh::GetAdditionalPhysicsBody(int32 Index) const
|
|
{
|
|
check(AdditionalPhysicsBodies.IsValidIndex(Index));
|
|
|
|
return AdditionalPhysicsBodies[Index];
|
|
}
|
|
|
|
|
|
int32 FMesh::GetFaceCount() const
|
|
{
|
|
return GetIndexBuffers().GetElementCount() / 3;
|
|
}
|
|
|
|
|
|
int32 FMesh::GetIndexCount() const
|
|
{
|
|
return GetIndexBuffers().GetElementCount();
|
|
}
|
|
|
|
|
|
FMeshBufferSet& FMesh::GetIndexBuffers()
|
|
{
|
|
return IndexBuffers;
|
|
}
|
|
|
|
|
|
const FMeshBufferSet& FMesh::GetIndexBuffers() const
|
|
{
|
|
return IndexBuffers;
|
|
}
|
|
|
|
|
|
int32 FMesh::GetSurfaceCount() const
|
|
{
|
|
return Surfaces.Num();
|
|
}
|
|
|
|
|
|
void FMesh::GetSurface(
|
|
int32 SurfaceIndex,
|
|
int32& OutFirstVertex, int32& OutVertexCount,
|
|
int32& OutFirstIndex, int32& OutIndexCount,
|
|
int32& OutBoneIndex, int32& OutBoneCount) const
|
|
{
|
|
int32 Count = GetSurfaceCount();
|
|
|
|
if (SurfaceIndex >= 0 && SurfaceIndex < Count)
|
|
{
|
|
if (SurfaceIndex < Surfaces.Num())
|
|
{
|
|
const FMeshSurface& Surf = Surfaces[SurfaceIndex];
|
|
|
|
check(Surf.SubMeshes.Num());
|
|
|
|
// Surfaces submeshes are sorted and have no gaps.
|
|
OutFirstVertex = Surf.SubMeshes[0].VertexBegin;
|
|
OutVertexCount = Surf.SubMeshes.Last().VertexEnd - OutFirstVertex;
|
|
OutFirstIndex = Surf.SubMeshes[0].IndexBegin;
|
|
OutIndexCount = Surf.SubMeshes.Last().IndexEnd - OutFirstIndex;
|
|
OutBoneIndex = Surf.BoneMapIndex;
|
|
OutBoneCount = Surf.BoneMapCount;
|
|
}
|
|
else if (!Surfaces.Num())
|
|
{
|
|
// No surfaces defined, means only one surface using all the mesh
|
|
OutFirstVertex = 0;
|
|
OutVertexCount = GetVertexCount();
|
|
OutFirstIndex = 0;
|
|
OutIndexCount = GetIndexCount();
|
|
OutBoneIndex = 0;
|
|
OutBoneCount = BoneMap.Num();
|
|
}
|
|
else
|
|
{
|
|
check(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
check(false);
|
|
OutFirstVertex = 0;
|
|
OutVertexCount = 0;
|
|
OutFirstIndex = 0;
|
|
OutIndexCount = 0;
|
|
OutBoneIndex = 0;
|
|
OutBoneCount = 0;
|
|
}
|
|
}
|
|
|
|
|
|
uint32 FMesh::GetSurfaceId(int32 SurfaceIndex) const
|
|
{
|
|
if (SurfaceIndex >= 0 && SurfaceIndex < Surfaces.Num())
|
|
{
|
|
const FMeshSurface& Surf = Surfaces[SurfaceIndex];
|
|
return Surf.Id;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void FMesh::AddLayout(TSharedPtr<const FLayout> InLayout)
|
|
{
|
|
LLM_SCOPE_BYNAME(TEXT("MutableRuntime"));
|
|
Layouts.Add(InLayout);
|
|
}
|
|
|
|
|
|
int32 FMesh::GetLayoutCount() const
|
|
{
|
|
return Layouts.Num();
|
|
}
|
|
|
|
|
|
TSharedPtr<const FLayout> FMesh::GetLayout(int32 LayoutIndex) const
|
|
{
|
|
check(Layouts.IsValidIndex(LayoutIndex));
|
|
|
|
return Layouts[LayoutIndex];
|
|
}
|
|
|
|
|
|
void FMesh::SetLayout(int32 LayoutIndex, TSharedPtr<const FLayout> InLayout)
|
|
{
|
|
check(Layouts.IsValidIndex(LayoutIndex));
|
|
|
|
Layouts[LayoutIndex] = InLayout;
|
|
}
|
|
|
|
|
|
int32 FMesh::GetTagCount() const
|
|
{
|
|
return Tags.Num();
|
|
}
|
|
|
|
|
|
void FMesh::SetTagCount(int32 Count)
|
|
{
|
|
LLM_SCOPE_BYNAME(TEXT("MutableRuntime"));
|
|
Tags.SetNum(Count);
|
|
}
|
|
|
|
|
|
const FString& FMesh::GetTag(int32 TagIndex) const
|
|
{
|
|
check(Tags.IsValidIndex(TagIndex));
|
|
|
|
if (Tags.IsValidIndex(TagIndex))
|
|
{
|
|
return Tags[TagIndex];
|
|
}
|
|
else
|
|
{
|
|
static FString NullString;
|
|
return NullString;
|
|
}
|
|
}
|
|
|
|
|
|
void FMesh::SetTag(int32 TagIndex, const FString& Name)
|
|
{
|
|
check(Tags.IsValidIndex(TagIndex));
|
|
LLM_SCOPE_BYNAME(TEXT("MutableRuntime"));
|
|
|
|
if (Tags.IsValidIndex(TagIndex))
|
|
{
|
|
Tags[TagIndex] = Name;
|
|
}
|
|
}
|
|
|
|
|
|
void FMesh::AddStreamedResource(uint64 ResourceId)
|
|
{
|
|
StreamedResources.AddUnique(ResourceId);
|
|
}
|
|
|
|
|
|
const TArray<uint64>& FMesh::GetStreamedResources() const
|
|
{
|
|
return StreamedResources;
|
|
}
|
|
|
|
|
|
int32 FMesh::FindBonePose(const FBoneName& BoneId) const
|
|
{
|
|
return BonePoses.IndexOfByPredicate([BoneId](const FBonePose& Pose) { return Pose.BoneId == BoneId; });
|
|
}
|
|
|
|
|
|
void mu::FMesh::SetBonePoseCount(int32 count)
|
|
{
|
|
LLM_SCOPE_BYNAME(TEXT("MutableRuntime"));
|
|
BonePoses.SetNum(count);
|
|
}
|
|
|
|
|
|
int32 mu::FMesh::GetBonePoseCount() const
|
|
{
|
|
return BonePoses.Num();
|
|
}
|
|
|
|
|
|
void mu::FMesh::SetBonePose(int32 Index, const FBoneName& BoneId, FTransform3f Transform, EBoneUsageFlags BoneUsageFlags)
|
|
{
|
|
check(BonePoses.IsValidIndex(Index));
|
|
if (BonePoses.IsValidIndex(Index))
|
|
{
|
|
BonePoses[Index] = FBonePose{ BoneId, BoneUsageFlags, Transform };
|
|
}
|
|
}
|
|
|
|
|
|
const FBoneName& FMesh::GetBonePoseId(int32 Index) const
|
|
{
|
|
check(BonePoses.IsValidIndex(Index));
|
|
return BonePoses[Index].BoneId;
|
|
}
|
|
|
|
|
|
void mu::FMesh::GetBonePoseTransform(int32 BoneIndex, FTransform3f& Transform) const
|
|
{
|
|
check(BoneIndex >= 0 && BoneIndex < BonePoses.Num());
|
|
Transform = BoneIndex > INDEX_NONE ? BonePoses[BoneIndex].BoneTransform : FTransform3f::Identity;
|
|
}
|
|
|
|
|
|
EBoneUsageFlags FMesh::GetBoneUsageFlags(int32 BoneIndex) const
|
|
{
|
|
check(BoneIndex >= 0 && BoneIndex < BonePoses.Num());
|
|
return BoneIndex > INDEX_NONE ? BonePoses[BoneIndex].BoneUsageFlags : EBoneUsageFlags::None;
|
|
}
|
|
|
|
|
|
void FMesh::SetBoneMap(const TArray<FBoneName>& InBoneMap)
|
|
{
|
|
BoneMap = InBoneMap;
|
|
}
|
|
|
|
|
|
const TArray<FBoneName>& FMesh::GetBoneMap() const
|
|
{
|
|
return BoneMap;
|
|
}
|
|
|
|
|
|
int32 FMesh::GetSkeletonIDsCount() const
|
|
{
|
|
return SkeletonIDs.Num();
|
|
}
|
|
|
|
|
|
int32 FMesh::GetSkeletonID(int32 SkeletonIndex) const
|
|
{
|
|
return SkeletonIDs.IsValidIndex(SkeletonIndex) ? SkeletonIDs[SkeletonIndex] : INDEX_NONE;
|
|
}
|
|
|
|
|
|
void FMesh::AddSkeletonID(int32 SkeletonID)
|
|
{
|
|
check(SkeletonID != INDEX_NONE);
|
|
SkeletonIDs.AddUnique(SkeletonID);
|
|
}
|
|
|
|
|
|
int32 FMesh::GetDataSize() const
|
|
{
|
|
// TODO: review if other mesh fields like additional physics assets
|
|
// are relevant and add them to the count.
|
|
|
|
// Should be allocation sizes used for this?
|
|
int32 AdditionalBuffersSize = 0;
|
|
for (const TPair<EMeshBufferType, FMeshBufferSet>& AdditionalBuffer : AdditionalBuffers)
|
|
{
|
|
AdditionalBuffersSize += AdditionalBuffer.Value.GetDataSize();
|
|
}
|
|
|
|
return sizeof(FMesh)
|
|
+ IndexBuffers.GetDataSize()
|
|
+ VertexBuffers.GetDataSize()
|
|
+ BonePoses.Num() * sizeof(FBonePose)
|
|
+ AdditionalBuffersSize;
|
|
}
|
|
|
|
|
|
bool FMesh::HasCompatibleFormat(const FMesh* Other) const
|
|
{
|
|
bool bCompatible = true;
|
|
|
|
bCompatible = Layouts.Num() == Other->Layouts.Num();
|
|
bCompatible = bCompatible && VertexBuffers.GetBufferCount() == Other->VertexBuffers.GetBufferCount();
|
|
|
|
// Indices
|
|
//-----------------
|
|
if (IndexBuffers.GetElementCount() > 0 && Other->GetIndexCount() > 0)
|
|
{
|
|
check(IndexBuffers.Buffers.Num() == 1);
|
|
check(Other->GetIndexBuffers().Buffers.Num() == 1);
|
|
check(IndexBuffers.GetBufferChannelCount(0) == 1);
|
|
check(Other->GetIndexBuffers().GetBufferChannelCount(0) == 1);
|
|
|
|
const FMeshBuffer& Dest = IndexBuffers.Buffers[0];
|
|
const FMeshBuffer& Source = Other->GetIndexBuffers().Buffers[0];
|
|
|
|
bCompatible = bCompatible && Dest.Channels[0].Format == Source.Channels[0].Format;
|
|
}
|
|
|
|
|
|
// Layouts
|
|
//-----------------
|
|
// TODO?
|
|
|
|
|
|
// Vertices
|
|
//-----------------
|
|
const int32 NumVertexBuffers = VertexBuffers.GetBufferCount();
|
|
for (int32 VertexBufferIndex = 0; VertexBufferIndex < NumVertexBuffers; ++VertexBufferIndex)
|
|
{
|
|
const FMeshBuffer& Dest = VertexBuffers.Buffers[VertexBufferIndex];
|
|
const FMeshBuffer& Source = Other->GetVertexBuffers().Buffers[VertexBufferIndex];
|
|
|
|
// TODO: More checks about channels formats and semantics
|
|
//bCompatible = bCompatible && GetVertexBufferElementSize(VertexBufferIndex) == Other->GetVertexBufferElementSize(VertexBufferIndex);
|
|
bCompatible = bCompatible && Dest.Channels.Num() == Source.Channels.Num();
|
|
}
|
|
|
|
return bCompatible;
|
|
}
|
|
|
|
|
|
UE::Math::TIntVector3<uint32> FMesh::GetFaceVertexIndices(int32 FaceIndex) const
|
|
{
|
|
UE::Math::TIntVector3<uint32> Result;
|
|
|
|
MeshBufferIteratorConst<EMeshBufferFormat::UInt32, uint32, 1> Iter(IndexBuffers, EMeshBufferSemantic::VertexIndex);
|
|
Iter += FaceIndex*3;
|
|
|
|
Result[0] = (*Iter)[0];
|
|
++Iter;
|
|
|
|
Result[1] = (*Iter)[0];
|
|
++Iter;
|
|
|
|
Result[2] = (*Iter)[0];
|
|
++Iter;
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
bool FMesh::FVertexMatchMap::DoMatch(int32 Vertex, int32 OtherVertex) const
|
|
{
|
|
if (Vertex >= 0 && Vertex < FirstMatch.Num())
|
|
{
|
|
int32 Start = FirstMatch[Vertex];
|
|
int32 End = Vertex + 1 < FirstMatch.Num() ? FirstMatch[Vertex + 1] : Matches.Num();
|
|
|
|
bool bResult = false;
|
|
while (!bResult && Start < End)
|
|
{
|
|
if (Matches[Start] == OtherVertex)
|
|
{
|
|
bResult = true;
|
|
}
|
|
|
|
++Start;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void FMesh::GetVertexMap(const FMesh& Other, FVertexMatchMap& VertexMap, float Tolerance) const
|
|
{
|
|
int32 VertexCount = VertexBuffers.GetElementCount();
|
|
VertexMap.FirstMatch.SetNum(VertexCount);
|
|
VertexMap.Matches.SetNum(VertexCount + (VertexCount >> 2));
|
|
|
|
int32 OtherVertexCount = Other.VertexBuffers.GetElementCount();
|
|
|
|
if (!VertexCount || !OtherVertexCount)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
MeshBufferIteratorConst<EMeshBufferFormat::Float32, float, 3> ItPosition(VertexBuffers, EMeshBufferSemantic::Position);
|
|
MeshBufferIteratorConst<EMeshBufferFormat::Float32, float, 3> ItOtherPositionBegin(Other.VertexBuffers, EMeshBufferSemantic::Position);
|
|
|
|
|
|
// Bucket the other mesh
|
|
#define MUTABLE_NUM_BUCKETS 256
|
|
#define MUTABLE_BUCKET_CHANNEL 0
|
|
|
|
float RangeMin = TNumericLimits<float>::Max();
|
|
float RangeMax = -TNumericLimits<float>::Max();
|
|
MeshBufferIteratorConst< EMeshBufferFormat::Float32, float, 3 > ItOtherPosition = ItOtherPositionBegin;
|
|
|
|
for (int32 OtherVertex = 0; OtherVertex < OtherVertexCount; ++OtherVertex)
|
|
{
|
|
float V = (*ItOtherPosition)[MUTABLE_BUCKET_CHANNEL];
|
|
RangeMin = FMath::Min(RangeMin, V);
|
|
RangeMax = FMath::Max(RangeMax, V);
|
|
++ItOtherPosition;
|
|
}
|
|
RangeMin -= Tolerance;
|
|
RangeMax += Tolerance;
|
|
|
|
TArray<int32> Buckets[MUTABLE_NUM_BUCKETS];
|
|
for (int32 BucketIndex = 0; BucketIndex < MUTABLE_NUM_BUCKETS; ++BucketIndex)
|
|
{
|
|
Buckets[BucketIndex].Reserve(OtherVertexCount/MUTABLE_NUM_BUCKETS*2);
|
|
}
|
|
|
|
float BucketSize = (RangeMax-RangeMin)/float(MUTABLE_NUM_BUCKETS);
|
|
ItOtherPosition = ItOtherPositionBegin;
|
|
for (int32 OtherVertex = 0; OtherVertex < OtherVertexCount; ++OtherVertex)
|
|
{
|
|
float V = (*ItOtherPosition)[MUTABLE_BUCKET_CHANNEL];
|
|
|
|
int32 Bucket0 = FMath::FloorToInt((V-Tolerance-RangeMin)/BucketSize);
|
|
Bucket0 = FMath::Min(MUTABLE_NUM_BUCKETS-1, FMath::Max(0, Bucket0));
|
|
Buckets[Bucket0].Add(OtherVertex);
|
|
|
|
int32 Bucket1 = FMath::FloorToInt((V + Tolerance - RangeMin)/BucketSize);
|
|
Bucket1 = FMath::Min(MUTABLE_NUM_BUCKETS-1, FMath::Max(0, Bucket1));
|
|
|
|
if (Bucket1 != Bucket0)
|
|
{
|
|
Buckets[Bucket1].Add(OtherVertex);
|
|
}
|
|
|
|
++ItOtherPosition;
|
|
}
|
|
|
|
// TODO Compare only positions?
|
|
|
|
// Use buckets
|
|
for (int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex)
|
|
{
|
|
VertexMap.FirstMatch[VertexIndex] = VertexMap.Matches.Num();
|
|
|
|
float VBucket = (*ItPosition)[MUTABLE_BUCKET_CHANNEL];
|
|
int32 Bucket = FMath::FloorToInt((VBucket - RangeMin)/BucketSize);
|
|
|
|
if (Bucket >= 0 && Bucket < MUTABLE_NUM_BUCKETS)
|
|
{
|
|
int32 BucketVertexCount = Buckets[Bucket].Num();
|
|
for (int32 OtherVertex = 0; OtherVertex < BucketVertexCount; ++OtherVertex)
|
|
{
|
|
int32 OtherVertexIndex = Buckets[Bucket][OtherVertex];
|
|
FVector3f Position = (ItOtherPositionBegin + OtherVertexIndex).GetAsVec3f();
|
|
|
|
bool bSame = true;
|
|
for (int32 Dim = 0; bSame && Dim < 3; ++Dim)
|
|
{
|
|
float Diff = FMath::Abs((*ItPosition)[Dim] - Position[Dim]);
|
|
bSame = Diff <= Tolerance;
|
|
}
|
|
|
|
if (bSame)
|
|
{
|
|
VertexMap.Matches.Add(OtherVertexIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
++ItPosition;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void FMesh::EnsureSurfaceData()
|
|
{
|
|
if (!Surfaces.Num() && VertexBuffers.GetElementCount())
|
|
{
|
|
FMeshSurface& NewSurface = Surfaces.Emplace_GetRef();
|
|
|
|
FSurfaceSubMesh& SubMesh = NewSurface.SubMeshes.Emplace_GetRef();
|
|
SubMesh.VertexBegin = 0;
|
|
SubMesh.VertexEnd = VertexBuffers.GetElementCount();
|
|
SubMesh.IndexBegin = 0;
|
|
SubMesh.IndexEnd = IndexBuffers.GetElementCount();
|
|
|
|
NewSurface.BoneMapCount = BoneMap.Num();
|
|
}
|
|
}
|
|
|
|
|
|
void FMesh::ResetBufferIndices()
|
|
{
|
|
VertexBuffers.ResetBufferIndices();
|
|
IndexBuffers.ResetBufferIndices();
|
|
}
|
|
|
|
MUTABLE_IMPLEMENT_POD_SERIALISABLE(FSurfaceSubMesh);
|
|
MUTABLE_IMPLEMENT_POD_VECTOR_SERIALISABLE(FSurfaceSubMesh);
|
|
|
|
void FMeshSurface::Serialise(FOutputArchive& Arch) const
|
|
{
|
|
Arch << SubMeshes;
|
|
|
|
Arch << BoneMapIndex;
|
|
Arch << BoneMapCount;
|
|
Arch << Id;
|
|
}
|
|
|
|
|
|
void FMeshSurface::Unserialise(FInputArchive& Arch)
|
|
{
|
|
Arch >> SubMeshes;
|
|
|
|
Arch >> BoneMapIndex;
|
|
Arch >> BoneMapCount;
|
|
Arch >> Id;
|
|
}
|
|
|
|
|
|
void FMesh::FBonePose::Serialise(FOutputArchive& Arch) const
|
|
{
|
|
Arch << BoneId;
|
|
Arch << BoneUsageFlags;
|
|
Arch << BoneTransform;
|
|
}
|
|
|
|
|
|
void FMesh::FBonePose::Unserialise(FInputArchive& Arch)
|
|
{
|
|
Arch >> BoneId;
|
|
Arch >> BoneUsageFlags;
|
|
Arch >> BoneTransform;
|
|
}
|
|
|
|
|
|
void FMesh::Serialise(FOutputArchive& Arch) const
|
|
{
|
|
Arch << IndexBuffers;
|
|
Arch << VertexBuffers;
|
|
Arch << AdditionalBuffers;
|
|
Arch << Layouts;
|
|
|
|
Arch << SkeletonIDs;
|
|
|
|
Arch << Skeleton;
|
|
Arch << PhysicsBody;
|
|
|
|
Arch << uint32(Flags);
|
|
Arch << Surfaces;
|
|
|
|
Arch << Tags;
|
|
Arch << StreamedResources;
|
|
|
|
Arch << BonePoses;
|
|
Arch << BoneMap;
|
|
|
|
Arch << AdditionalPhysicsBodies;
|
|
|
|
Arch << MeshIDPrefix;
|
|
|
|
if ( IsReference() )
|
|
{
|
|
Arch << ReferenceID;
|
|
Arch << ReferencedMorph;
|
|
}
|
|
}
|
|
|
|
|
|
void FMesh::Unserialise(FInputArchive& Arch)
|
|
{
|
|
Arch >> IndexBuffers;
|
|
Arch >> VertexBuffers;
|
|
Arch >> AdditionalBuffers;
|
|
Arch >> Layouts;
|
|
|
|
Arch >> SkeletonIDs;
|
|
|
|
Arch >> Skeleton;
|
|
Arch >> PhysicsBody;
|
|
|
|
uint32 Temp;
|
|
Arch >> Temp;
|
|
Flags = static_cast<EMeshFlags>(Temp);
|
|
|
|
Arch >> Surfaces;
|
|
|
|
Arch >> Tags;
|
|
Arch >> StreamedResources;
|
|
|
|
Arch >> BonePoses;
|
|
Arch >> BoneMap;
|
|
|
|
Arch >> AdditionalPhysicsBodies;
|
|
|
|
Arch >> MeshIDPrefix;
|
|
|
|
if (IsReference())
|
|
{
|
|
Arch >> ReferenceID;
|
|
Arch >> ReferencedMorph;
|
|
}
|
|
}
|
|
|
|
|
|
bool FMesh::IsSimilar(const FMesh& Other) const
|
|
{
|
|
// Some meshes are just vertex indices (masks) we don't consider them for similarity,
|
|
// because the kind of vertex channel data they store is the kind that is ignored.
|
|
if (IndexBuffers.GetElementCount() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bEqual = IndexBuffers == Other.IndexBuffers;
|
|
bEqual = bEqual && (ReferenceID == Other.ReferenceID);
|
|
|
|
if (bEqual && Skeleton != Other.Skeleton)
|
|
{
|
|
if (Skeleton && Other.Skeleton)
|
|
{
|
|
bEqual = (*Skeleton == *Other.Skeleton);
|
|
}
|
|
else
|
|
{
|
|
bEqual = false;
|
|
}
|
|
}
|
|
|
|
if (bEqual && PhysicsBody != Other.PhysicsBody)
|
|
{
|
|
if (PhysicsBody && Other.PhysicsBody)
|
|
{
|
|
bEqual = (*PhysicsBody == *Other.PhysicsBody);
|
|
}
|
|
else
|
|
{
|
|
bEqual = false;
|
|
}
|
|
}
|
|
|
|
bEqual = bEqual && (Surfaces == Other.Surfaces);
|
|
bEqual = bEqual && (Tags == Other.Tags);
|
|
|
|
// Special comparison for vertex buffers
|
|
if (bEqual)
|
|
{
|
|
bEqual = VertexBuffers.IsSimilarRobust(Other.VertexBuffers, false);
|
|
}
|
|
|
|
return bEqual;
|
|
}
|
|
|
|
|
|
void FMesh::CheckIntegrity() const
|
|
{
|
|
#ifdef MUTABLE_DEBUG
|
|
|
|
{
|
|
int32 BufferIndex = -1;
|
|
int32 ChannelIndex = -1;
|
|
VertexBuffers.FindChannel(EMeshBufferSemantic::VertexIndex, 0, &BufferIndex, &ChannelIndex);
|
|
if (BufferIndex >= 0 && ChannelIndex >= 0)
|
|
{
|
|
EMeshBufferFormat IdFormat = VertexBuffers.m_buffers[BufferIndex].m_channels[ChannelIndex].m_format;
|
|
if (IdFormat == EMeshBufferFormat::UInt64)
|
|
{
|
|
check(MeshIDPrefix==0);
|
|
}
|
|
else if (IdFormat == EMeshBufferFormat::UInt32)
|
|
{
|
|
check(MeshIDPrefix != 0);
|
|
}
|
|
else
|
|
{
|
|
check(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check vertex indices
|
|
{
|
|
for ( int32 b=0; b<IndexBuffers.GetBufferCount(); ++b )
|
|
{
|
|
int32 elemSize = IndexBuffers.GetElementSize( b );
|
|
|
|
for ( int32 c=0; c<IndexBuffers.GetBufferChannelCount(b); ++c )
|
|
{
|
|
EMeshBufferSemantic semantic;
|
|
int32 semanticIndex = 0;
|
|
EMeshBufferFormat format;
|
|
int32 components;
|
|
int32 offset = 0;
|
|
IndexBuffers.GetChannel( b, c, &semantic, &semanticIndex, &format, &components, &offset );
|
|
|
|
if ( semantic==EMeshBufferSemantic::VertexIndex )
|
|
{
|
|
int32 icount = IndexBuffers.GetElementCount();
|
|
int32 elemCount = VertexBuffers.GetElementCount();
|
|
for ( int32 indexIndex = 0; indexIndex < icount; ++indexIndex )
|
|
{
|
|
const uint8* pData = IndexBuffers.GetBufferData( b ) + elemSize*indexIndex + offset;
|
|
|
|
switch (format)
|
|
{
|
|
case EMeshBufferFormat::UInt32:
|
|
{
|
|
uint32 index = *(const uint32*)pData;
|
|
check( index < uint32( elemCount ) );
|
|
break;
|
|
}
|
|
case EMeshBufferFormat::UInt16:
|
|
{
|
|
uint16 index = *(const uint16*)pData;
|
|
check( index < uint16( elemCount ) );
|
|
break;
|
|
}
|
|
case EMeshBufferFormat::UInt8:
|
|
{
|
|
uint8 index = *(const uint8*)pData;
|
|
check( index < uint8( elemCount ) );
|
|
break;
|
|
}
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Check bone indices, if there are bones. Bones could have been removed for later addition as an optimisation.
|
|
// For all the attributes in this mesh
|
|
int32 boneCount = Skeleton ? Skeleton->GetBoneCount() : 0;
|
|
if ( Skeleton && boneCount )
|
|
{
|
|
for ( int32 b = 0; b < VertexBuffers.GetBufferCount(); ++b )
|
|
{
|
|
int32 channelCount = VertexBuffers.GetBufferChannelCount( b );
|
|
for ( int32 c = 0; c < channelCount; ++c )
|
|
{
|
|
EMeshBufferSemantic semantic;
|
|
int32 semanticIndex = 0;
|
|
EMeshBufferFormat format;
|
|
int32 components;
|
|
int32 offset = 0;
|
|
VertexBuffers.GetChannel( b, c, &semantic, &semanticIndex, &format, &components, &offset );
|
|
|
|
// If it is not one of the relevant semantics
|
|
if (
|
|
//semantic!=EMeshBufferSemantic::Position &&
|
|
//semantic!=EMeshBufferSemantic::TexCoords &&
|
|
//semantic!=EMeshBufferSemantic::Normal &&
|
|
//semantic!=EMeshBufferSemantic::Tangent &&
|
|
//semantic!=EMeshBufferSemantic::Binormal &&
|
|
semantic!=EMeshBufferSemantic::BoneIndices
|
|
// && semantic!=EMeshBufferSemantic::BoneWeights
|
|
)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int32 elemCount = VertexBuffers.GetElementCount();
|
|
int32 elemSize = VertexBuffers.GetElementSize( b );
|
|
|
|
for (int32 vertexIndex = 0; vertexIndex < elemCount; ++vertexIndex )
|
|
{
|
|
const uint8* pData = VertexBuffers.GetBufferData( b ) + elemSize*vertexIndex + offset;
|
|
|
|
switch (format)
|
|
{
|
|
case EMeshBufferFormat::UInt8:
|
|
{
|
|
for ( int32 d = 0; d < components; ++d )
|
|
{
|
|
uint8 index = *pData;
|
|
check( index < uint64(boneCount) );
|
|
++pData;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EMeshBufferFormat::UInt16:
|
|
{
|
|
for ( int32 d = 0; d < components; ++d )
|
|
{
|
|
uint16 index = *(uint16*)pData;
|
|
check( index < uint64( boneCount ) );
|
|
pData+=2;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EMeshBufferFormat::UInt32:
|
|
{
|
|
for ( int32 d = 0; d < components; ++d )
|
|
{
|
|
uint32 index = *(uint32*)pData;
|
|
check( index < uint64( boneCount ) );
|
|
pData+=4;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
check( false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
bool FMesh::IsClosed() const
|
|
{
|
|
return IsMeshClosed(this);
|
|
}
|
|
|
|
|
|
static bool StaticMeshFormatIdentify_Project(const FMesh* InMesh)
|
|
{
|
|
// This format is used internally for the mesh project
|
|
bool bResult = true;
|
|
|
|
// The first vertex buffer must be texcoords(2f), position(3f), normal(3f)
|
|
// all tightly packed
|
|
bResult &= InMesh->VertexBuffers.GetBufferCount() >= 1;
|
|
|
|
if (bResult)
|
|
{
|
|
bResult &= InMesh->VertexBuffers.Buffers[0].Channels.Num() == 3;
|
|
}
|
|
|
|
if (bResult)
|
|
{
|
|
const FMeshBufferChannel& Channel = InMesh->VertexBuffers.Buffers[0].Channels[0];
|
|
|
|
bResult &= Channel.Semantic == EMeshBufferSemantic::TexCoords;
|
|
bResult &= Channel.Format == EMeshBufferFormat::Float32;
|
|
bResult &= Channel.ComponentCount == 2;
|
|
//we don't really care about the semantic index
|
|
//bResult &= Channel.SemanticIndex == 0;
|
|
bResult &= Channel.Offset == 0;
|
|
}
|
|
|
|
if (bResult)
|
|
{
|
|
const FMeshBufferChannel& Channel = InMesh->VertexBuffers.Buffers[0].Channels[1];
|
|
|
|
bResult &= Channel.Semantic == EMeshBufferSemantic::Position;
|
|
bResult &= Channel.Format == EMeshBufferFormat::Float32;
|
|
bResult &= Channel.ComponentCount == 3;
|
|
bResult &= Channel.SemanticIndex == 0;
|
|
bResult &= Channel.Offset == 8;
|
|
}
|
|
|
|
if (bResult)
|
|
{
|
|
const FMeshBufferChannel& Channel = InMesh->VertexBuffers.Buffers[0].Channels[2];
|
|
|
|
bResult &= Channel.Semantic == EMeshBufferSemantic::Normal;
|
|
bResult &= Channel.Format == EMeshBufferFormat::Float32;
|
|
bResult &= Channel.ComponentCount == 3;
|
|
bResult &= Channel.SemanticIndex == 0;
|
|
bResult &= Channel.Offset == 20;
|
|
}
|
|
|
|
// The first index buffer must be just index buffers u32
|
|
if (!InMesh->IndexBuffers.Buffers.IsEmpty())
|
|
{
|
|
if (bResult)
|
|
{
|
|
bResult &= InMesh->IndexBuffers.Buffers[0].Channels.Num() >= 1;
|
|
}
|
|
|
|
if (bResult)
|
|
{
|
|
const FMeshBufferChannel& Channel = InMesh->IndexBuffers.Buffers[0].Channels[0];
|
|
|
|
bResult &= Channel.Semantic == EMeshBufferSemantic::VertexIndex;
|
|
bResult &= Channel.Format == EMeshBufferFormat::UInt32;
|
|
bResult &= Channel.ComponentCount == 1;
|
|
bResult &= Channel.SemanticIndex == 0;
|
|
bResult &= Channel.Offset == 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bResult = false;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
static bool StaticMeshFormatIdentify_ProjectWrapping(const FMesh* InMesh)
|
|
{
|
|
// This format is used internally for the mesh project
|
|
bool bResult = true;
|
|
|
|
// The first vertex buffer must be texcoords(2f), position(3f), normal(3f), layoutBlock(uint32_t)
|
|
// all tightly packed
|
|
bResult &= InMesh->VertexBuffers.GetBufferCount() >= 2;
|
|
|
|
if (bResult)
|
|
{
|
|
bResult &= InMesh->VertexBuffers.Buffers[0].Channels.Num() == 3;
|
|
}
|
|
|
|
if (bResult)
|
|
{
|
|
const FMeshBufferChannel& Channel = InMesh->VertexBuffers.Buffers[0].Channels[0];
|
|
|
|
bResult &= Channel.Semantic == EMeshBufferSemantic::TexCoords;
|
|
bResult &= Channel.Format == EMeshBufferFormat::Float32;
|
|
bResult &= Channel.ComponentCount == 2;
|
|
// we don't really care about the semantic index as long as there is only one
|
|
// bResult &= Channel.SemanticIndex == 0;
|
|
bResult &= Channel.Offset == 0;
|
|
}
|
|
|
|
if (bResult)
|
|
{
|
|
const FMeshBufferChannel& Channel = InMesh->VertexBuffers.Buffers[0].Channels[1];
|
|
|
|
bResult &= Channel.Semantic == EMeshBufferSemantic::Position;
|
|
bResult &= Channel.Format == EMeshBufferFormat::Float32;
|
|
bResult &= Channel.ComponentCount == 3;
|
|
bResult &= Channel.SemanticIndex == 0;
|
|
bResult &= Channel.Offset == 8;
|
|
}
|
|
|
|
if (bResult)
|
|
{
|
|
const FMeshBufferChannel& Channel = InMesh->VertexBuffers.Buffers[0].Channels[2];
|
|
|
|
bResult &= Channel.Semantic == EMeshBufferSemantic::Normal;
|
|
bResult &= Channel.Format == EMeshBufferFormat::Float32;
|
|
bResult &= Channel.ComponentCount == 3;
|
|
bResult &= Channel.SemanticIndex == 0;
|
|
bResult &= Channel.Offset == 20;
|
|
}
|
|
|
|
// Block IDs
|
|
if (bResult)
|
|
{
|
|
const FMeshBufferChannel& Channel = InMesh->VertexBuffers.Buffers[1].Channels[0];
|
|
|
|
bResult &= Channel.Semantic == EMeshBufferSemantic::LayoutBlock;
|
|
// We don't care about the layout block id format. We need to support them all.
|
|
//bResult &= Channel.Format == EMeshBufferFormat::UInt64;
|
|
bResult &= Channel.ComponentCount == 1;
|
|
// we don't really care about the semantic index as long as there is only one
|
|
// bResult &= Channel.SemanticIndex == 0;
|
|
bResult &= Channel.Offset == 0;
|
|
}
|
|
|
|
// The first index buffer must be just index buffers u32
|
|
if (bResult)
|
|
{
|
|
bResult &= InMesh->IndexBuffers.Buffers[0].Channels.Num() >= 1;
|
|
}
|
|
|
|
if (bResult)
|
|
{
|
|
const FMeshBufferChannel& Channel = InMesh->IndexBuffers.Buffers[0].Channels[0];
|
|
|
|
bResult &= Channel.Semantic == EMeshBufferSemantic::VertexIndex;
|
|
bResult &= Channel.Format == EMeshBufferFormat::UInt32;
|
|
bResult &= Channel.ComponentCount == 1;
|
|
bResult &= Channel.SemanticIndex == 0;
|
|
bResult &= Channel.Offset == 0;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
|
|
void FMesh::ResetStaticFormatFlags() const
|
|
{
|
|
EnumRemoveFlags(Flags, EMeshFlags::ProjectFormat);
|
|
EnumRemoveFlags(Flags, EMeshFlags::ProjectWrappingFormat);
|
|
|
|
if (StaticMeshFormatIdentify_Project(this))
|
|
{
|
|
EnumAddFlags(Flags, EMeshFlags::ProjectFormat);
|
|
}
|
|
|
|
if (StaticMeshFormatIdentify_ProjectWrapping(this))
|
|
{
|
|
EnumAddFlags(Flags, EMeshFlags::ProjectWrappingFormat);
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
void LogBuffer(FString& Out, const FMeshBufferSet& BufferSet, int32 BufferElementLimit)
|
|
{
|
|
uint32 ElemCount = BufferSet.ElementCount;
|
|
Out += FString::Printf(TEXT(" Set with %d buffers and %d elements\n"), BufferSet.Buffers.Num(), ElemCount);
|
|
|
|
for(const FMeshBuffer& Buffer : BufferSet.Buffers)
|
|
{
|
|
Out += FString::Printf(TEXT(" Buffer with %d channels and %d elementsize\n"), Buffer.Channels.Num(), Buffer.ElementSize);
|
|
|
|
const uint8* DataPtr = Buffer.Data.GetData();
|
|
if (!DataPtr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (const FMeshBufferChannel& Channel : Buffer.Channels)
|
|
{
|
|
Out += FString::Printf(TEXT(" Channel with format: %d semantic: %d %d, components: %d, offset: %d\n"),
|
|
Channel.Format, Channel.Semantic, Channel.SemanticIndex, Channel.ComponentCount, Channel.Offset);
|
|
|
|
for (int32 ElemIndex = 0; uint32(ElemIndex) < ElemCount && ElemIndex < BufferElementLimit; ++ElemIndex)
|
|
{
|
|
Out += " ";
|
|
|
|
const uint8* ChanDataPtr = DataPtr + Buffer.ElementSize*ElemIndex + Channel.Offset;
|
|
for (uint32 CompIndex = 0; CompIndex < Channel.ComponentCount; ++CompIndex)
|
|
{
|
|
Out += "\t";
|
|
|
|
switch (Channel.Format)
|
|
{
|
|
case EMeshBufferFormat::UInt32:
|
|
case EMeshBufferFormat::NUInt32:
|
|
{
|
|
Out += FString::Printf(TEXT("%d"), *(const uint32*)ChanDataPtr);
|
|
ChanDataPtr += 4;
|
|
break;
|
|
}
|
|
case EMeshBufferFormat::UInt16:
|
|
case EMeshBufferFormat::NUInt16:
|
|
{
|
|
Out += FString::Printf(TEXT("%d"), *(const uint16*)ChanDataPtr);
|
|
ChanDataPtr += 2;
|
|
break;
|
|
}
|
|
case EMeshBufferFormat::UInt8:
|
|
case EMeshBufferFormat::NUInt8:
|
|
{
|
|
Out += FString::Printf(TEXT("%d"), *(const uint8*)ChanDataPtr);
|
|
ChanDataPtr += 1;
|
|
break;
|
|
}
|
|
case EMeshBufferFormat::Float32:
|
|
{
|
|
Out += FString::Printf(TEXT("%.3f"), *(const float*)ChanDataPtr);
|
|
ChanDataPtr += 4;
|
|
break;
|
|
}
|
|
case EMeshBufferFormat::Float16:
|
|
{
|
|
float Converted = *((const FFloat16*)ChanDataPtr);
|
|
Out += FString::Printf(TEXT("%.3f"), Converted);
|
|
ChanDataPtr += 2;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Out += ",";
|
|
}
|
|
Out += "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMesh::Log( FString& out, int32 BufferElementLimit) const
|
|
{
|
|
out += "Mesh:\n";
|
|
|
|
out += "Indices:\n";
|
|
LogBuffer( out, IndexBuffers, BufferElementLimit);
|
|
|
|
out += "Vertices:\n";
|
|
LogBuffer( out, VertexBuffers, BufferElementLimit);
|
|
}
|
|
|
|
|
|
void GetUVIsland(TArray<FTriangleInfo>& InTriangles,
|
|
const uint32 InFirstTriangle,
|
|
TArray<uint32>& OutTriangleIndices,
|
|
const TArray<FVector2f>& InUVs,
|
|
const TMultiMap<int32, uint32>& InVertexToTriangleMap)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(GetUVIsland);
|
|
|
|
const uint32 NumTriangles = (uint32)InTriangles.Num();
|
|
|
|
OutTriangleIndices.Reserve(NumTriangles);
|
|
OutTriangleIndices.Add(InFirstTriangle);
|
|
|
|
TArray<bool> SkipTriangles;
|
|
SkipTriangles.Init(false, NumTriangles);
|
|
|
|
TArray<uint32> PendingTriangles;
|
|
PendingTriangles.Reserve(NumTriangles / 64);
|
|
PendingTriangles.Add(InFirstTriangle);
|
|
|
|
while (!PendingTriangles.IsEmpty())
|
|
{
|
|
const uint32 TriangleIndex = PendingTriangles.Pop();
|
|
|
|
// Triangle about to be proccessed, mark as skip;
|
|
SkipTriangles[TriangleIndex] = true;
|
|
|
|
bool ConnectedEdges[3] = { false, false, false };
|
|
|
|
const FTriangleInfo& Triangle = InTriangles[TriangleIndex];
|
|
|
|
// Find Triangles connected to edges 0 and 2
|
|
int32 CollapsedVertex1 = Triangle.CollapsedIndices[1];
|
|
int32 CollapsedVertex2 = Triangle.CollapsedIndices[2];
|
|
|
|
TArray<uint32> FoundTriangleIndices;
|
|
InVertexToTriangleMap.MultiFind(Triangle.CollapsedIndices[0], FoundTriangleIndices);
|
|
|
|
for (uint32 OtherTriangleIndex : FoundTriangleIndices)
|
|
{
|
|
const FTriangleInfo& OtherTriangle = InTriangles[OtherTriangleIndex];
|
|
|
|
for (int32 OtherIndex = 0; OtherIndex < 3; ++OtherIndex)
|
|
{
|
|
const int32 OtherCollapsedIndex = OtherTriangle.CollapsedIndices[OtherIndex];
|
|
if (OtherCollapsedIndex == CollapsedVertex1)
|
|
{
|
|
// Check if the vertex is in the same UV Island
|
|
if (!SkipTriangles[OtherTriangleIndex]
|
|
&& InUVs[Triangle.Indices[1]].Equals(InUVs[OtherTriangle.Indices[OtherIndex]], 0.00001f))
|
|
{
|
|
OutTriangleIndices.Add(OtherTriangleIndex);
|
|
PendingTriangles.Add(OtherTriangleIndex);
|
|
SkipTriangles[OtherTriangleIndex] = true;
|
|
}
|
|
|
|
// Connected but already processed or in another island
|
|
break;
|
|
}
|
|
|
|
if (OtherCollapsedIndex == CollapsedVertex2)
|
|
{
|
|
// Check if the vertex is in the same UV Island
|
|
if (!SkipTriangles[OtherTriangleIndex]
|
|
&& InUVs[Triangle.Indices[2]].Equals(InUVs[OtherTriangle.Indices[OtherIndex]], 0.00001f))
|
|
{
|
|
OutTriangleIndices.Add(OtherTriangleIndex);
|
|
PendingTriangles.Add(OtherTriangleIndex);
|
|
SkipTriangles[OtherTriangleIndex] = true;
|
|
}
|
|
|
|
// Connected but already processed or in another UV Island
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Find the triangle connected to edge 1
|
|
FoundTriangleIndices.Reset();
|
|
InVertexToTriangleMap.MultiFind(CollapsedVertex1, FoundTriangleIndices);
|
|
|
|
for (uint32 OtherTriangleIndex : FoundTriangleIndices)
|
|
{
|
|
const FTriangleInfo& OtherTriangle = InTriangles[OtherTriangleIndex];
|
|
|
|
for (int32 OtherIndex = 0; OtherIndex < 3; ++OtherIndex)
|
|
{
|
|
const int32 OtherCollapsedIndex = OtherTriangle.CollapsedIndices[OtherIndex];
|
|
if (OtherCollapsedIndex == CollapsedVertex2)
|
|
{
|
|
// Check if the vertex belong to the same UV island
|
|
if (!SkipTriangles[OtherTriangleIndex]
|
|
&& InUVs[Triangle.Indices[2]].Equals(InUVs[OtherTriangle.Indices[OtherIndex]], 0.00001f))
|
|
{
|
|
OutTriangleIndices.Add(OtherTriangleIndex);
|
|
PendingTriangles.Add(OtherTriangleIndex);
|
|
SkipTriangles[OtherTriangleIndex] = true;
|
|
}
|
|
|
|
// Connected but already processed or in another island
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void MeshCreateCollapsedVertexMap(const mu::FMesh* Mesh, TArray<int32>& CollapsedVertices)
|
|
{
|
|
MUTABLE_CPUPROFILER_SCOPE(LayoutUV_CreateCollapsedVertexMap);
|
|
|
|
const int32 NumVertices = Mesh->GetVertexCount();
|
|
CollapsedVertices.Reserve(NumVertices);
|
|
|
|
UE::Geometry::TPointHashGrid3f<int32> VertHash(0.01f, INDEX_NONE);
|
|
VertHash.Reserve(NumVertices);
|
|
|
|
TArray<FVector3f> Vertices;
|
|
Vertices.SetNumUninitialized(NumVertices);
|
|
|
|
mu::UntypedMeshBufferIteratorConst ItPosition = mu::UntypedMeshBufferIteratorConst(Mesh->GetVertexBuffers(), mu::EMeshBufferSemantic::Position);
|
|
|
|
FVector3f* VertexData = Vertices.GetData();
|
|
|
|
for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
|
|
{
|
|
*VertexData = ItPosition.GetAsVec3f();
|
|
VertHash.InsertPointUnsafe(VertexIndex, *VertexData);
|
|
|
|
++ItPosition;
|
|
++VertexData;
|
|
}
|
|
|
|
// Find unique vertices
|
|
CollapsedVertices.Init(INDEX_NONE, NumVertices);
|
|
|
|
TArray<int32> NearbyVertices;
|
|
for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex)
|
|
{
|
|
if (CollapsedVertices[VertexIndex] != INDEX_NONE)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FVector3f& Vertex = Vertices[VertexIndex];
|
|
|
|
NearbyVertices.Reset();
|
|
VertHash.FindPointsInBall(Vertex, 0.00001,
|
|
[&Vertex, &Vertices](const int32& Other) -> float {return FVector3f::DistSquared(Vertices[Other], Vertex); },
|
|
NearbyVertices);
|
|
|
|
// Find equals
|
|
for (int32 NearbyVertexIndex : NearbyVertices)
|
|
{
|
|
CollapsedVertices[NearbyVertexIndex] = VertexIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|