Files
UnrealEngine/Engine/Plugins/Mutable/Source/MutableRuntime/Private/MuR/OpMeshTransformWithBone.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

331 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MuR/OpMeshTransformWithBone.h"
#include "OpMeshClipWithMesh.h"
#include "MuR/Mesh.h"
#include "MuR/MeshPrivate.h"
#include "MuR/MutableTrace.h"
#include "MuR/ParallelExecutionUtils.h"
#include "PackedNormal.h"
namespace UE::Mutable::Private
{
void MeshTransformWithBoneInline(FMesh* Mesh, const FMatrix44f& Transform, const FBoneName& InBoneName, const float InThresholdFactor)
{
MUTABLE_CPUPROFILER_SCOPE(MeshTransformWithBone);
if (!Mesh || Mesh->GetVertexCount() == 0 || Mesh->IsReference())
{
return;
}
TSharedPtr<const FSkeleton> BaseSkeleton = Mesh->GetSkeleton();
const int32 BaseBoneIndex = BaseSkeleton ? BaseSkeleton->FindBone(InBoneName.Id) : INDEX_NONE;
if (BaseBoneIndex == INDEX_NONE)
{
return;
}
TBitArray<> AffectedBoneMapIndices;
// Find affected bones
{
const int32 NumBonesBoneMap = Mesh->BoneMap.Num();
const TArray<FBoneName>& BoneMap = Mesh->BoneMap;
AffectedBoneMapIndices.SetNum(NumBonesBoneMap, false);
const int32 BoneCount = BaseSkeleton->GetBoneCount();
TBitArray<> AffectedSkeletonBones;
AffectedSkeletonBones.SetNum(BoneCount, false);
for (int32 BoneIndex = 0; BoneIndex < BoneCount; ++BoneIndex)
{
const int32 ParentBoneIndex = BaseSkeleton->GetBoneParent(BoneIndex);
check(ParentBoneIndex < BoneIndex);
const bool bIsBoneAffected = (AffectedSkeletonBones.IsValidIndex(ParentBoneIndex) && AffectedSkeletonBones[ParentBoneIndex])
|| BoneIndex == BaseBoneIndex;
AffectedSkeletonBones[BoneIndex] = bIsBoneAffected;
}
for (int32 BoneIndex = 0; BoneIndex < NumBonesBoneMap; ++BoneIndex)
{
const FBoneName& BoneName = BoneMap[BoneIndex];
const int32 SkeletonIndex = BaseSkeleton->FindBone(BoneName);
if (SkeletonIndex != INDEX_NONE && AffectedSkeletonBones[SkeletonIndex])
{
AffectedBoneMapIndices[BoneIndex] = true;
}
}
}
// Get pointers to skinning data
UntypedMeshBufferIteratorConst BoneIndicesIterBegin(Mesh->VertexBuffers, EMeshBufferSemantic::BoneIndices, 0);
UntypedMeshBufferIteratorConst BoneWeightsIterBegin(Mesh->VertexBuffers, EMeshBufferSemantic::BoneWeights, 0);
if (!BoneIndicesIterBegin.ptr() || !BoneWeightsIterBegin.ptr())
{
// No skinning data
return;
}
const int32 NumVertices = Mesh->GetVertexCount();
const int32 NumWeights = BoneWeightsIterBegin.GetComponents();
const uint16 MinWeightThreshold = (BoneWeightsIterBegin.GetFormat() == EMeshBufferFormat::NUInt8 ? MAX_uint8 : MAX_uint16) * InThresholdFactor;
// Classify which vertices in the SourceMesh are completely bounded by the BoundingMesh geometry.
// If no BoundingMesh is provided, this defaults to act exactly like UE::Mutable::Private::MeshTransform
TBitArray<> VertexInBoneHierarchy;
VertexInBoneHierarchy.Init(false, NumVertices);
{
for (const FMeshSurface& Surface : Mesh->Surfaces)
{
const int32 AffectedBoneMapIndex = AffectedBoneMapIndices.FindFrom(true, int32(Surface.BoneMapIndex), int32(Surface.BoneMapIndex + Surface.BoneMapCount));
if (AffectedBoneMapIndex == INDEX_NONE)
{
// Skip. No bones affected in this surface, vertices won't be affected either.
continue;
}
const int32 FirstBoneIndexInBoneMap = Surface.BoneMapIndex;
const int32 VertexStart = Surface.SubMeshes[0].VertexBegin;
const int32 VertexEnd = Surface.SubMeshes.Last().VertexEnd;
for (int32 VertexIndex = VertexStart; VertexIndex < VertexEnd; ++VertexIndex)
{
int8 NumValidWeights = 0;
switch (BoneWeightsIterBegin.GetFormat())
{
case EMeshBufferFormat::NUInt8:
{
// Skin weights are ordered from highest to lowest. Break once the weight value falls below the threshold.
const uint8* Weights = (BoneWeightsIterBegin + VertexIndex).ptr();
for (; NumValidWeights < NumWeights && *Weights >= (uint8)MinWeightThreshold; ++NumValidWeights, ++Weights);
break;
}
case EMeshBufferFormat::NUInt16:
{
// Skin weights are ordered from highest to lowest. Break once the weight value falls below the threshold.
const uint16* Weights = reinterpret_cast<const uint16*>((BoneWeightsIterBegin + VertexIndex).ptr());
for (; NumValidWeights < NumWeights && *Weights >= MinWeightThreshold; ++NumValidWeights, ++Weights);
break;
}
default: unimplemented();
}
switch (BoneIndicesIterBegin.GetFormat())
{
case EMeshBufferFormat::UInt8:
{
const uint8* Indices = (BoneIndicesIterBegin + VertexIndex).ptr();
for (int32 Index = 0; Index < NumValidWeights; ++Index)
{
if (AffectedBoneMapIndices[FirstBoneIndexInBoneMap + *Indices])
{
VertexInBoneHierarchy[VertexIndex] = true;
break;
}
++Indices;
}
break;
}
case EMeshBufferFormat::UInt16:
{
const uint16* Indices = reinterpret_cast<const uint16*>((BoneIndicesIterBegin + VertexIndex).ptr());
for (int32 Index = 0; Index < NumValidWeights; ++Index)
{
if (AffectedBoneMapIndices[FirstBoneIndexInBoneMap + *Indices])
{
VertexInBoneHierarchy[VertexIndex] = true;
break;
}
++Indices;
}
break;
}
default: unimplemented();
}
}
}
}
// Get pointers to vertex position data
const MeshBufferIterator<EMeshBufferFormat::Float32, float, 3> PositionIterBegin(Mesh->VertexBuffers, EMeshBufferSemantic::Position, 0);
check(PositionIterBegin.ptr());
check(PositionIterBegin.GetFormat() == EMeshBufferFormat::Float32 && PositionIterBegin.GetComponents() == 3);
const FMatrix44f TransformInvT = Transform.Inverse().GetTransposed();
constexpr int32 NumVerticesPerBatch = 1 << 13;
// Tangent frame buffers are optional.
const UntypedMeshBufferIterator NormalIterBegin(Mesh->VertexBuffers, EMeshBufferSemantic::Normal, 0);
const UntypedMeshBufferIterator TangentIterBegin(Mesh->VertexBuffers, EMeshBufferSemantic::Tangent, 0);
const UntypedMeshBufferIterator BiNormalIterBegin(Mesh->VertexBuffers, EMeshBufferSemantic::Binormal, 0);
auto ProcessVertexBatch =
[
PositionIterBegin,
NormalIterBegin,
TangentIterBegin,
BiNormalIterBegin,
NumVertices,
NumVerticesPerBatch,
&VertexInBoneHierarchy,
Transform,
TransformInvT
](int32 BatchId)
{
const uint8 NumNormalComponents = FMath::Min(NormalIterBegin.GetComponents(), 3); // Due to quantization, the serialized component W may not be zero. Must be zero to avoid being affected by the transform position.
const uint8 NumTangentComponents = FMath::Min(TangentIterBegin.GetComponents(), 3); // Due to quantization, the serialized component W may not be zero. Must be zero to avoid being affected by the transform position.
const uint8 NumBiNormalComponents = FMath::Min(BiNormalIterBegin.GetComponents(), 3); // Due to quantization, the serialized component W may not be zero. Must be zero to avoid being affected by the transform position.
const EMeshBufferFormat NormalFormat = NormalIterBegin.GetFormat();
const EMeshBufferFormat TangentFormat = TangentIterBegin.GetFormat();
const int32 BatchBeginVertIndex = BatchId * NumVerticesPerBatch;
const int32 BatchEndVertIndex = FMath::Min(BatchBeginVertIndex + NumVerticesPerBatch, NumVertices);
const bool bHasOptimizedBuffers = NormalFormat == EMeshBufferFormat::PackedDirS8_W_TangentSign && TangentFormat == EMeshBufferFormat::PackedDirS8;
for (int32 VertexIndex = VertexInBoneHierarchy.FindFrom(true, BatchBeginVertIndex, BatchEndVertIndex); VertexIndex >= 0;)
{
const int32 AffectedSpanBegin = VertexIndex;
VertexIndex = VertexInBoneHierarchy.FindFrom(false, VertexIndex, BatchEndVertIndex);
// At the end of the buffer we may not find a false element, in that case
// FindForm returns INDEX_NONE, set the vertex at the range end.
VertexIndex = VertexIndex >= 0 && VertexIndex < BatchEndVertIndex ? VertexIndex : BatchEndVertIndex;
const int32 AffectedSpanEnd = VertexIndex;
// VertexIndex may be one past the end of the array, VertexIndex will become INDEX_NONE
// and the loop will finish.
VertexIndex = VertexInBoneHierarchy.FindFrom(true, VertexIndex, BatchEndVertIndex);
VertexIndex = VertexIndex < BatchEndVertIndex ? VertexIndex : INDEX_NONE;
MeshBufferIterator<EMeshBufferFormat::Float32, float, 3> PositionIter = (PositionIterBegin + AffectedSpanBegin);
for (int32 Index = AffectedSpanBegin; Index < AffectedSpanEnd; ++Index)
{
FVector3f* Position = reinterpret_cast<FVector3f*>(PositionIter.ptr());
*Position = Transform.TransformFVector4(*Position);
PositionIter++;
}
if (bHasOptimizedBuffers)
{
UntypedMeshBufferIterator TangentIter = TangentIterBegin + AffectedSpanBegin;
UntypedMeshBufferIterator NormalIter = NormalIterBegin + AffectedSpanBegin;
for (int32 Index = AffectedSpanBegin; Index < AffectedSpanEnd; ++Index)
{
// Tangents
FPackedNormal* PackedTangent = reinterpret_cast<FPackedNormal*>(TangentIter.ptr());
FVector4f Tangent = TransformInvT.TransformFVector4(PackedTangent->ToFVector3f());
*PackedTangent = *reinterpret_cast<FVector3f*>(&Tangent);
TangentIter++;
// Normals
FPackedNormal* PackedNormal = reinterpret_cast<FPackedNormal*>(NormalIter.ptr());
int8 W = PackedNormal->Vector.W;
FVector4f Normal = TransformInvT.TransformFVector4(PackedNormal->ToFVector3f());
*PackedNormal = *reinterpret_cast<FVector3f*>(&Normal);
PackedNormal->Vector.W = W;
NormalIter++;
}
}
else
{
MUTABLE_CPUPROFILER_SCOPE(MeshTransform_Vertices_SlowPath);
if (NormalIterBegin.ptr())
{
UntypedMeshBufferIterator it = NormalIterBegin + AffectedSpanBegin;
for (int32 Index = AffectedSpanBegin; Index < AffectedSpanEnd; ++Index)
{
FVector4f value(0.0f, 0.0f, 0.0f, 0.0f);
for (uint8 i = 0; i < NumNormalComponents; ++i)
{
ConvertData(i, &value[0], EMeshBufferFormat::Float32, it.ptr(), it.GetFormat());
}
value = TransformInvT.TransformFVector4(value);
// Notice that 4th component is not modified.
for (uint8 i = 0; i < NumNormalComponents; ++i)
{
ConvertData(i, it.ptr(), it.GetFormat(), &value[0], EMeshBufferFormat::Float32);
}
it++;
}
}
if (TangentIterBegin.ptr())
{
UntypedMeshBufferIterator it = TangentIterBegin + AffectedSpanBegin;
for (int32 Index = AffectedSpanBegin; Index < AffectedSpanEnd; ++Index)
{
FVector4f value(0.0f, 0.0f, 0.0f, 0.0f);
for (uint8 i = 0; i < NumTangentComponents; ++i)
{
ConvertData(i, &value[0], EMeshBufferFormat::Float32, it.ptr(), it.GetFormat());
}
value = TransformInvT.TransformFVector4(value);
// Notice that 4th component is not modified.
for (uint8 i = 0; i < NumTangentComponents; ++i)
{
ConvertData(i, it.ptr(), it.GetFormat(), &value[0], EMeshBufferFormat::Float32);
}
it++;
}
}
if (BiNormalIterBegin.ptr())
{
UntypedMeshBufferIterator it = BiNormalIterBegin + AffectedSpanBegin;
for (int32 Index = AffectedSpanBegin; Index < AffectedSpanEnd; ++Index)
{
FVector4f value(0.0f, 0.0f, 0.0f, 0.0f);
for (uint8 i = 0; i < NumBiNormalComponents; ++i)
{
ConvertData(i, &value[0], EMeshBufferFormat::Float32, it.ptr(), it.GetFormat());
}
value = TransformInvT.TransformFVector4(value);
// Notice that 4th component is not modified.
for (uint8 i = 0; i < NumBiNormalComponents; ++i)
{
ConvertData(i, it.ptr(), it.GetFormat(), &value[0], EMeshBufferFormat::Float32);
}
it++;
}
}
}
}
};
const int32 NumBatches = FMath::DivideAndRoundUp<int32>(NumVertices, NumVerticesPerBatch);
ParallelExecutionUtils::InvokeBatchParallelFor(NumBatches, ProcessVertexBatch);
}
}