Files
UnrealEngine/Engine/Plugins/Runtime/MeshModelingToolset/Source/ModelingComponents/Private/ConversionUtils/GeometryCacheToDynamicMesh.cpp
2025-05-18 13:04:45 +08:00

457 lines
16 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ConversionUtils/GeometryCacheToDynamicMesh.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "DynamicMesh/DynamicMeshAttributeSet.h"
#include "DynamicMesh/MeshTransforms.h"
#include "GeometryCache.h"
#include "GeometryCacheHelpers.h"
#include "GeometryCacheMeshData.h"
#include "GeometryCacheTrackStreamable.h"
#include "ToDynamicMesh.h"
namespace UE::Conversion
{
// internal helpers
namespace Private
{
template<typename T, typename CustomInterpF>
void InterpolateArrayHelperCustomFn(int32 Start, TArrayView<T> UpdateSource, TConstArrayView<T> Toward, CustomInterpF InterpF)
{
for (int32 Idx = 0; Idx < Toward.Num(); ++Idx)
{
InterpF(UpdateSource[Start + Idx], Toward[Idx]);
}
}
template<typename T>
void InterpolateArrayHelper(int32 Start, TArrayView<T> UpdateSource, TConstArrayView<T> Toward, float SourceWt, float TowardWt)
{
for (int32 Idx = 0; Idx < Toward.Num(); ++Idx)
{
UpdateSource[Start + Idx] = UpdateSource[Start + Idx] * SourceWt + Toward[Idx] * TowardWt;
}
}
inline FVector4f ToLinearVec4(const FColor& Color)
{
FLinearColor Linear = Color.ReinterpretAsLinear();
return FVector4f(Linear.R, Linear.G, Linear.B, Linear.A);
}
}
// Struct for the ToDynamicMesh converter, holding combined flattened buffers of the geometry cache tracks vertex and triangle data
// w/ the interface that TToDynamicMesh expects
struct FGeometryCacheMeshBufferWrapper
{
// Typedefs expected by TToDynamicMesh
typedef int32 TriIDType;
typedef int32 VertIDType;
typedef int32 WedgeIDType;
typedef int32 UVIDType;
typedef int32 NormalIDType;
typedef int32 ColorIDType;
// Per vertex data
TArray<FVector3f> Positions;
TArray<FPackedNormal> Normals, Tangents;
TArray<FVector2f> UVs;
TArray<FColor> Colors;
// Per triangle data
TArray<UE::Geometry::FIndex3i> Triangles;
TArray<int32> MaterialIndices;
TArray<int32> TriSourceIndices;
// Note these are currently just identity maps, but the ToDynamicMesh converter template expects them
TArray<int32> VertIDs, TriIDs;
TArray<int32> EmptyArray;
FGeometryCacheMeshBufferWrapper(TConstArrayView<FGeometryCacheMeshData> MeshDataTracks)
{
int32 NumV = 0, NumT = 0;
bool bHasUVs = false;
bool bHasColors = false;
bool bHasNormals = false;
bool bHasTangents = false;
for (const FGeometryCacheMeshData& Track : MeshDataTracks)
{
NumT += Track.Indices.Num() / 3;
NumV += Track.Positions.Num();
bHasUVs = bHasUVs || Track.VertexInfo.bHasUV0;
bHasColors = bHasColors || Track.VertexInfo.bHasColor0;
bHasNormals = bHasNormals || Track.VertexInfo.bHasTangentZ;
bHasTangents = bHasTangents || Track.VertexInfo.bHasTangentX;
}
Positions.SetNumZeroed(NumV);
if (bHasNormals)
{
Normals.Init(FPackedNormal(FVector3f::ZAxisVector), NumV);
}
if (bHasTangents)
{
Tangents.Init(FPackedNormal(FVector3f::XAxisVector), NumV);
}
if (bHasColors)
{
Colors.Init(FColor::White, NumV);
}
if (bHasUVs)
{
UVs.SetNumZeroed(NumV);
}
VertIDs.Reserve(NumV);
Triangles.Reserve(NumT);
TriIDs.Reserve(NumT);
MaterialIndices.SetNumZeroed(NumT);
TriSourceIndices.SetNumZeroed(NumT);
for (int32 TrackIdx = 0, BaseV = 0, BaseT = 0; TrackIdx < MeshDataTracks.Num(); TrackIdx++)
{
const FGeometryCacheMeshData& Track = MeshDataTracks[TrackIdx];
for (int32 TrackVertIdx = 0; TrackVertIdx < Track.Positions.Num(); ++TrackVertIdx)
{
VertIDs.Add(BaseV + TrackVertIdx);
Positions[BaseV + TrackVertIdx] = Track.Positions[TrackVertIdx];
}
if (Track.VertexInfo.bHasUV0)
{
for (int32 TrackVertIdx = 0; TrackVertIdx < Track.TextureCoordinates.Num(); ++TrackVertIdx)
{
UVs[BaseV + TrackVertIdx] = Track.TextureCoordinates[TrackVertIdx];
}
}
if (Track.VertexInfo.bHasColor0)
{
for (int32 TrackVertIdx = 0; TrackVertIdx < Track.Colors.Num(); ++TrackVertIdx)
{
Colors[BaseV + TrackVertIdx] = Track.Colors[TrackVertIdx];
}
}
if (Track.VertexInfo.bHasTangentZ)
{
for (int32 TrackVertIdx = 0; TrackVertIdx < Track.TangentsZ.Num(); ++TrackVertIdx)
{
Normals[BaseV + TrackVertIdx] = Track.TangentsZ[TrackVertIdx];
}
}
if (Track.VertexInfo.bHasTangentX)
{
for (int32 TrackVertIdx = 0; TrackVertIdx < Track.TangentsX.Num(); ++TrackVertIdx)
{
Tangents[BaseV + TrackVertIdx] = Track.TangentsX[TrackVertIdx];
}
}
const int32 TrackNumT = Track.Indices.Num() / 3;
for (int32 TrackTriWedgeIdx = 0; TrackTriWedgeIdx + 2 < Track.Indices.Num(); TrackTriWedgeIdx += 3)
{
TriIDs.Add((int32)Triangles.Emplace((int32)Track.Indices[TrackTriWedgeIdx], (int32)Track.Indices[TrackTriWedgeIdx + 1], (int32)Track.Indices[TrackTriWedgeIdx + 2]));
}
for (int32 TriIdx = BaseT, TriEndIdx = BaseT + TrackNumT; TriIdx < TriEndIdx; ++TriIdx)
{
TriSourceIndices[TriIdx] = TrackIdx;
}
for (const FGeometryCacheMeshBatchInfo& BatchInfo : Track.BatchesInfo)
{
for (int32 TrackTriIdx = BatchInfo.StartIndex, EndIdx = BatchInfo.StartIndex + BatchInfo.NumTriangles; TrackTriIdx < EndIdx; ++TrackTriIdx)
{
MaterialIndices[BaseT + TrackTriIdx] = BatchInfo.MaterialIndex;
}
}
BaseT += TrackNumT;
BaseV += Track.Positions.Num();
}
}
int32 NumTris() const
{
return TriIDs.Num();
}
int32 NumVerts() const
{
return VertIDs.Num();
}
int32 NumUVLayers() const
{
return UVs.IsEmpty() ? 0 : 1;
}
const TArray<VertIDType>& GetVertIDs() const
{
return VertIDs;
}
FVector3d GetPosition(VertIDType VtxID) const
{
return static_cast<FVector3d>(Positions[VtxID]);
}
const TArray<TriIDType>& GetTriIDs() const
{
return TriIDs;
}
bool GetTri(TriIDType TriID, VertIDType& VID0, VertIDType& VID1, VertIDType& VID2) const
{
UE::Geometry::FIndex3i Tri = Triangles[(int32)TriID];
VID0 = Tri.A;
VID1 = Tri.B;
VID2 = Tri.C;
return true;
}
int32 GetMaterialIndex(TriIDType TriID) const
{
return MaterialIndices[TriID];
}
int32 GetTrackIndex(TriIDType TriID) const
{
return TriSourceIndices[TriID];
}
bool HasNormals() const { return !Normals.IsEmpty(); }
bool HasTangents() const { return !Tangents.IsEmpty(); }
bool HasBiTangents() const { return HasNormals() && HasTangents(); }
bool HasColors() const { return !Colors.IsEmpty(); }
inline FVector2f GetVertexUV(int32 VID) const
{
return UVs[VID];
}
inline FVector3f GetVertexNormal(int32 VID) const
{
return Normals[VID].ToFVector3f();
}
inline FVector3f GetVertexTangent(int32 VID) const
{
return Tangents[VID].ToFVector3f();
}
inline FVector3f GetVertexBiTangent(int32 VID) const
{
const FVector3f TangentX = Tangents[VID].ToFVector3f();
const FVector4f Normal = Normals[VID].ToFVector4f();
const float OrientationSign = Normal.W;
const FVector3f TangentY = (static_cast<FVector3f>(Normal).Cross(TangentX)).GetSafeNormal() * OrientationSign;
return TangentY;
}
inline FVector4f GetVertexColor(int32 VID) const
{
return Private::ToLinearVec4(Colors[VID]);
}
//
// Wedge methods just return per-vertex attributes
//
void GetWedgeIDs(const TriIDType& TriID, WedgeIDType& WID0, WedgeIDType& WID1, WedgeIDType& WID2) const { GetTri(TriID, WID0, WID1, WID2); }
FVector2f GetWedgeUV(int32 UVLayerIndex, WedgeIDType WID) const { return GetVertexUV(WID); }
FVector3f GetWedgeNormal(WedgeIDType WID) const { return GetVertexNormal(WID); }
FVector3f GetWedgeTangent(WedgeIDType WID) const { return GetVertexTangent(WID); }
FVector3f GetWedgeBiTangent(WedgeIDType WID) const { return GetVertexBiTangent(WID); }
FVector4f GetWedgeColor(WedgeIDType WID) const { return GetVertexColor(WID); }
//
// No skin weights or bones
//
int32 NumSkinWeightAttributes() const { return 0; }
UE::AnimationCore::FBoneWeights GetVertexSkinWeight(int32 SkinWeightAttributeIndex, VertIDType VertexID) const { checkNoEntry(); return UE::AnimationCore::FBoneWeights(); }
FName GetSkinWeightAttributeName(int32 SkinWeightAttributeIndex) const { checkNoEntry(); return FName(); };
int32 GetNumBones() const { return 0; }
FName GetBoneName(int32 BoneIdx) const { checkNoEntry(); return FName(); }
int32 GetBoneParentIndex(int32 BoneIdx) const { checkNoEntry(); return INDEX_NONE; }
FTransform GetBonePose(int32 BoneIdx) const { checkNoEntry(); return FTransform::Identity; }
FVector4f GetBoneColor(int32 BoneIdx) const { checkNoEntry(); return FVector4f::One(); }
//
// Shared attribute accessors return per-vertex data
//
const TArray<int32>& GetUVIDs(int32 LayerID) const { return NumUVLayers() > 0 ? VertIDs : EmptyArray; }
FVector2f GetUV(int32 LayerID, UVIDType UVID) const { return GetVertexUV(UVID); }
bool GetUVTri(int32 LayerID, const TriIDType& TID, UVIDType& ID0, UVIDType& ID1, UVIDType& ID2) const { GetTri(TID, ID0, ID1, ID2); return true; }
const TArray<int32>& GetNormalIDs() const { return HasNormals() ? VertIDs : EmptyArray; }
FVector3f GetNormal(NormalIDType ID) const { return GetVertexNormal(ID); }
bool GetNormalTri(const TriIDType& TID, NormalIDType& ID0, NormalIDType& ID1, NormalIDType& ID2) const { GetTri(TID, ID0, ID1, ID2); return true; }
const TArray<int32>& GetTangentIDs() const { return HasTangents() ? VertIDs : EmptyArray; }
FVector3f GetTangent(NormalIDType ID) const { return GetVertexTangent(ID); }
bool GetTangentTri(const TriIDType& TID, NormalIDType& ID0, NormalIDType& ID1, NormalIDType& ID2) const { GetTri(TID, ID0, ID1, ID2); return true; }
const TArray<int32>& GetBiTangentIDs() const { return HasBiTangents() ? VertIDs : EmptyArray; }
FVector3f GetBiTangent(NormalIDType ID) const { return GetVertexBiTangent(ID); }
bool GetBiTangentTri(const TriIDType& TID, NormalIDType& ID0, NormalIDType& ID1, NormalIDType& ID2) const { GetTri(TID, ID0, ID1, ID2); return true; }
const TArray<int32>& GetColorIDs() const { return HasColors() ? VertIDs : EmptyArray; }
FVector4f GetColor(ColorIDType ID) const { return GetVertexColor(ID); }
bool GetColorTri(const TriIDType& TID, ColorIDType& ID0, ColorIDType& ID1, ColorIDType& ID2) const { GetTri(TID, ID0, ID1, ID2); return true; }
// No weight maps
int32 NumWeightMapLayers() const { return 0; }
float GetVertexWeight(int32 WeightMapIndex, int32 SrcVertID) const { return 0; }
FName GetWeightMapName(int32 WeightMapIndex) const { return FName(); }
};
bool GeometryCacheToDynamicMesh(const UGeometryCache& GeometryCache, Geometry::FDynamicMesh3& MeshOut, const FGeometryCacheToDynamicMeshOptions& Options)
{
static const IConsoleVariable* InterpFramesCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("GeometryCache.InterpolateFrames"));
bool bInterpolateFrames = InterpFramesCVar ? InterpFramesCVar->GetBool() : true;
bool bUseInterpolate = bInterpolateFrames && Options.bAllowInterpolation;
MeshOut.Clear();
TArray<FGeometryCacheMeshData> MeshData;
TArray<int32> SourceTrackIdx;
// Helper struct to keep track of what frames we need if we're interpolating
struct FFrameInfo
{
int32 FrameIndex = INDEX_NONE, NextFrameIndex = INDEX_NONE;
float InterpFactor = 0;
};
TArray<FFrameInfo> FrameInfos;
float UseTime = Options.Time;
// Note: The Track->GetMeshDataAtTime method does not account for looping, so apply the looping beforehand if applicable
if (Options.bLooping)
{
UseTime = GeometyCacheHelpers::WrapAnimationTime(UseTime, GeometryCache.CalculateDuration());
}
bool bAnyNonZeroInterp = false;
for (int32 TrackIdx = 0; TrackIdx < GeometryCache.Tracks.Num(); TrackIdx++)
{
FGeometryCacheMeshData TrackMeshData;
UGeometryCacheTrack* Track = GeometryCache.Tracks[TrackIdx];
FFrameInfo FrameInfo;
// GetMeshDataAtTime does not interpolate for us, so if we want to interpolate we need to fetch the relevant sample indices manually
if (bUseInterpolate)
{
// The interface for getting the frames to interpolate is only on UGeometryCacheTrackStreamable, not on the base UGeometryCacheTrack
if (UGeometryCacheTrackStreamable* StreamableTrack = Cast<UGeometryCacheTrackStreamable>(Track))
{
StreamableTrack->FindSampleIndexesFromTime(UseTime, Options.bLooping, Options.bReversed, FrameInfo.FrameIndex, FrameInfo.NextFrameIndex, FrameInfo.InterpFactor);
}
}
if (FrameInfo.FrameIndex != INDEX_NONE ? Track->GetMeshDataAtSampleIndex(FrameInfo.FrameIndex, TrackMeshData) : Track->GetMeshDataAtTime(UseTime, TrackMeshData))
{
if (FrameInfo.InterpFactor != 0)
{
bAnyNonZeroInterp = true;
}
MeshData.Add(MoveTemp(TrackMeshData));
SourceTrackIdx.Add(TrackIdx);
FrameInfos.Add(FrameInfo);
}
}
// Do initial conversion to buffers
FGeometryCacheMeshBufferWrapper Wrapper(MeshData);
// If we might need to interpolate, look through tracks for compatible frames to interpolate
if (bAnyNonZeroInterp)
{
for (int32 SourceIdx = 0, BaseV = 0; SourceIdx < SourceTrackIdx.Num(); BaseV += MeshData[SourceIdx++].Positions.Num())
{
int32 TrackIdx = SourceTrackIdx[SourceIdx];
FFrameInfo FrameInfo = FrameInfos[SourceIdx];
// if FrameInfo indicates we don't need interpolation, skip to next track
if (FrameInfo.InterpFactor == 0)
{
continue;
}
float CurWt = 1.f - FrameInfo.InterpFactor;
bool bTopoCompat = false;
UGeometryCacheTrack* Track = GeometryCache.Tracks[TrackIdx];
FGeometryCacheMeshData NextData;
if (!Track->GetMeshDataAtSampleIndex(FrameInfo.NextFrameIndex, NextData))
{
continue;
}
// Only interpolate if vertices are 1:1
// Note the rendering code uses an abstracted IsTopologyCompatible method instead, but we can't access it from here
// and in practice it is implemented by checking the vertex counts, so we just do that.
if (NextData.Positions.Num() != MeshData[SourceIdx].Positions.Num())
{
continue;
}
Private::InterpolateArrayHelper<FVector3f>(BaseV, Wrapper.Positions, NextData.Positions, CurWt, FrameInfo.InterpFactor);
if (NextData.VertexInfo.bHasUV0)
{
Private::InterpolateArrayHelper<FVector2f>(BaseV, Wrapper.UVs, NextData.TextureCoordinates, CurWt, FrameInfo.InterpFactor);
}
// Interpolation matching what is implemented in the geometry cache scene proxy
const VectorRegister4Float WeightA = VectorSetFloat1(CurWt);
const VectorRegister4Float WeightB = VectorSetFloat1(FrameInfo.InterpFactor);
const VectorRegister4Float Half = VectorSetFloat1(0.5f);
const uint32 SignMask = 0x80808080u;
auto InterpTangentFn = [WeightA, WeightB, Half, SignMask](FPackedNormal& A, const FPackedNormal& B) -> void
{
uint32 TangentXA = A.Vector.Packed ^ SignMask;
uint32 TangentXB = B.Vector.Packed ^ SignMask;
VectorRegister4Float InterpolatedTangentX = VectorMultiplyAdd(VectorLoadByte4(&TangentXA), WeightA,
VectorMultiplyAdd(VectorLoadByte4(&TangentXB), WeightB, Half)); // +0.5f so truncation becomes round to nearest.
uint32 PackedInterpolatedTangentX;
VectorStoreByte4(InterpolatedTangentX, &PackedInterpolatedTangentX);
A.Vector.Packed = PackedInterpolatedTangentX ^ SignMask; // Convert back to signed
};
if (NextData.VertexInfo.bHasTangentX)
{
Private::InterpolateArrayHelperCustomFn<FPackedNormal>(BaseV, Wrapper.Tangents, NextData.TangentsX, InterpTangentFn);
}
if (NextData.VertexInfo.bHasTangentZ)
{
Private::InterpolateArrayHelperCustomFn<FPackedNormal>(BaseV, Wrapper.Normals, NextData.TangentsZ, InterpTangentFn);
}
auto InterpColorFn = [WeightA, WeightB, Half](FColor& A, const FColor& B) -> void
{
VectorRegister4Float InterpolatedColor = VectorMultiplyAdd(VectorLoadByte4(&A), WeightA,
VectorMultiplyAdd(VectorLoadByte4(&B), WeightB, Half)); // +0.5f so truncation becomes round to nearest.
VectorStoreByte4(InterpolatedColor, &A);
};
if (NextData.VertexInfo.bHasColor0)
{
Private::InterpolateArrayHelperCustomFn<FColor>(BaseV, Wrapper.Colors, NextData.Colors, InterpColorFn);
}
}
}
// Run the standard ToDynamicMesh converter
auto TriToGroupID = [&Wrapper](const int32& SrcTriID)->int32 { return Wrapper.GetTrackIndex(SrcTriID) + 1; };
auto TriToMaterialID = [&Wrapper](const int32& SrcTriID)->int32 { return Wrapper.GetMaterialIndex(SrcTriID); };
UE::Geometry::TToDynamicMesh<FGeometryCacheMeshBufferWrapper> Converter;
Converter.Convert(MeshOut, Wrapper, TriToGroupID, TriToMaterialID, Options.bWantTangents);
return true;
}
} // end namespace UE::Conversion