Files
UnrealEngine/Engine/Plugins/Interchange/Runtime/Source/Parsers/Fbx/Private/FbxAnimation.cpp
2025-05-18 13:04:45 +08:00

1182 lines
46 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "FbxAnimation.h"
#include "Async/ParallelFor.h"
#include "CoreMinimal.h"
#include "FbxAPI.h"
#include "FbxConvert.h"
#include "FbxHelper.h"
#include "FbxInclude.h"
#include "FbxMesh.h"
#include "Fbx/InterchangeFbxMessages.h"
#include "InterchangeAnimationTrackSetNode.h"
#include "InterchangeCommonAnimationPayload.h"
#include "InterchangeMeshNode.h"
#include "InterchangeResultsContainer.h"
#include "InterchangeSceneNode.h"
#include "MeshDescription.h"
#include "Misc/FileHelper.h"
#include "Misc/ScopeLock.h"
#include "Nodes/InterchangeBaseNodeContainer.h"
#include "Serialization/LargeMemoryWriter.h"
#include "SkeletalMeshAttributes.h"
#include "StaticMeshAttributes.h"
#define LOCTEXT_NAMESPACE "InterchangeFbxMesh"
namespace UE::Interchange::Private
{
bool IsStepCurve(FbxAnimCurveNode* AnimCurveNode)
{
const uint32 ChannelCount = AnimCurveNode->GetChannelsCount();
for (uint32 ChannelIndex = 0; ChannelIndex < ChannelCount; ++ChannelIndex)
{
const uint32 ChannelCurveCount = AnimCurveNode->GetCurveCount(ChannelIndex);
for (uint32 CurveIndex = 0; CurveIndex < ChannelCurveCount; ++CurveIndex)
{
if (FbxAnimCurve* CurrentAnimCurve = AnimCurveNode->GetCurve(ChannelIndex, CurveIndex))
{
int32 KeyCount = CurrentAnimCurve->KeyGetCount();
for (int32 KeyIndex = 0; KeyIndex < KeyCount; ++KeyIndex)
{
FbxAnimCurveKey Key = CurrentAnimCurve->KeyGet(KeyIndex);
FbxAnimCurveDef::EInterpolationType KeyInterpMode = Key.GetInterpolation();
if (KeyInterpMode != FbxAnimCurveDef::EInterpolationType::eInterpolationConstant)
{
return false;
}
}
}
}
}
return true;
}
bool ImportCurve(const FbxAnimCurve* SourceFloatCurves, const float ScaleValue, TArray<FInterchangeCurveKey>& DestinationFloatCurve)
{
if (!SourceFloatCurves)
{
return true;
}
const float DefaultCurveWeight = FbxAnimCurveDef::sDEFAULT_WEIGHT;
//We use the non const to query the left and right derivative of the key, for whatever reason those FBX API functions are not const
FbxAnimCurve* NonConstSourceFloatCurves = const_cast<FbxAnimCurve*>(SourceFloatCurves);
int32 KeyCount = SourceFloatCurves->KeyGetCount();
for (int32 KeyIndex = 0; KeyIndex < KeyCount; ++KeyIndex)
{
FbxAnimCurveKey Key = SourceFloatCurves->KeyGet(KeyIndex);
FbxTime KeyTime = Key.GetTime();
const float KeyTimeValue = static_cast<float>(KeyTime.GetSecondDouble());
float Value = Key.GetValue() * ScaleValue;
FInterchangeCurveKey& InterchangeCurveKey = DestinationFloatCurve.AddDefaulted_GetRef();
InterchangeCurveKey.Time = KeyTimeValue;
InterchangeCurveKey.Value = Value;
const bool bIncludeOverrides = true;
FbxAnimCurveDef::ETangentMode KeyTangentMode = Key.GetTangentMode(bIncludeOverrides);
FbxAnimCurveDef::EInterpolationType KeyInterpMode = Key.GetInterpolation();
FbxAnimCurveDef::EWeightedMode KeyTangentWeightMode = Key.GetTangentWeightMode();
EInterchangeCurveInterpMode NewInterpMode = EInterchangeCurveInterpMode::Linear;
EInterchangeCurveTangentMode NewTangentMode = EInterchangeCurveTangentMode::Auto;
EInterchangeCurveTangentWeightMode NewTangentWeightMode = EInterchangeCurveTangentWeightMode::WeightedNone;
float RightTangent = NonConstSourceFloatCurves->KeyGetRightDerivative(KeyIndex) * ScaleValue;
float LeftTangent = NonConstSourceFloatCurves->KeyGetLeftDerivative(KeyIndex) * ScaleValue;
float RightTangentWeight = 0.0f;
float LeftTangentWeight = 0.0f; //This one is dependent on the previous key.
bool bLeftWeightActive = false;
bool bRightWeightActive = false;
const bool bPreviousKeyValid = KeyIndex > 0;
const bool bNextKeyValid = KeyIndex < KeyCount - 1;
float PreviousValue = 0.0f;
float PreviousKeyTimeValue = 0.0f;
float NextValue = 0.0f;
float NextKeyTimeValue = 0.0f;
if (bPreviousKeyValid)
{
FbxAnimCurveKey PreviousKey = SourceFloatCurves->KeyGet(KeyIndex - 1);
FbxTime PreviousKeyTime = PreviousKey.GetTime();
PreviousKeyTimeValue = static_cast<float>(PreviousKeyTime.GetSecondDouble());
PreviousValue = PreviousKey.GetValue() * ScaleValue;
//The left tangent is driven by the previous key. If the previous key have a the NextLeftweight or both flag weighted mode, it mean the next key is weighted on the left side
bLeftWeightActive = (PreviousKey.GetTangentWeightMode() & FbxAnimCurveDef::eWeightedNextLeft) > 0;
if (bLeftWeightActive)
{
LeftTangentWeight = PreviousKey.GetDataFloat(FbxAnimCurveDef::eNextLeftWeight);
}
}
if (bNextKeyValid)
{
FbxAnimCurveKey NextKey = SourceFloatCurves->KeyGet(KeyIndex + 1);
FbxTime NextKeyTime = NextKey.GetTime();
NextKeyTimeValue = static_cast<float>(NextKeyTime.GetSecondDouble());
NextValue = NextKey.GetValue() * ScaleValue;
bRightWeightActive = (KeyTangentWeightMode & FbxAnimCurveDef::eWeightedRight) > 0;
if (bRightWeightActive)
{
//The right tangent weight should be use only if we are not the last key since the last key do not have a right tangent.
//Use the current key to gather the right tangent weight
RightTangentWeight = Key.GetDataFloat(FbxAnimCurveDef::eRightWeight);
}
}
// When this flag is true, the tangent is flat if the value has the same value as the previous or next key.
const bool bTangentGenericClamp = (KeyTangentMode & FbxAnimCurveDef::eTangentGenericClamp);
//Time independent tangent this is consider has a spline tangent key
const bool bTangentGenericTimeIndependent = (KeyTangentMode & FbxAnimCurveDef::ETangentMode::eTangentGenericTimeIndependent);
// When this flag is true, the tangent is flat if the value is outside of the [previous key, next key] value range.
//Clamp progressive is (eTangentGenericClampProgressive |eTangentGenericTimeIndependent)
const bool bTangentGenericClampProgressive = (KeyTangentMode & FbxAnimCurveDef::ETangentMode::eTangentGenericClampProgressive) == FbxAnimCurveDef::ETangentMode::eTangentGenericClampProgressive;
if (KeyTangentMode & FbxAnimCurveDef::eTangentGenericBreak)
{
NewTangentMode = EInterchangeCurveTangentMode::Break;
}
else if (KeyTangentMode & FbxAnimCurveDef::eTangentUser)
{
NewTangentMode = EInterchangeCurveTangentMode::User;
}
switch (KeyInterpMode)
{
case FbxAnimCurveDef::eInterpolationConstant://! Constant value until next key.
NewInterpMode = EInterchangeCurveInterpMode::Constant;
break;
case FbxAnimCurveDef::eInterpolationLinear://! Linear progression to next key.
NewInterpMode = EInterchangeCurveInterpMode::Linear;
break;
case FbxAnimCurveDef::eInterpolationCubic://! Cubic progression to next key.
NewInterpMode = EInterchangeCurveInterpMode::Cubic;
// get tangents
{
bool bIsFlatTangent = false;
bool bIsComputedTangent = false;
if (bTangentGenericClampProgressive)
{
if (bPreviousKeyValid && bNextKeyValid)
{
const float PreviousNextHalfDelta = (NextValue - PreviousValue) * 0.5f;
const float PreviousNextAverage = PreviousValue + PreviousNextHalfDelta;
// If the value is outside of the previous-next value range, the tangent is flat.
bIsFlatTangent = FMath::Abs(Value - PreviousNextAverage) >= FMath::Abs(PreviousNextHalfDelta);
}
else
{
//Start/End tangent with the ClampProgressive flag are flat.
bIsFlatTangent = true;
}
}
else if (bTangentGenericClamp && (bPreviousKeyValid || bNextKeyValid))
{
if (bPreviousKeyValid && PreviousValue == Value)
{
bIsFlatTangent = true;
}
if (bNextKeyValid)
{
bIsFlatTangent |= Value == NextValue;
}
}
else if (bTangentGenericTimeIndependent)
{
//Spline tangent key, because bTangentGenericClampProgressive include bTangentGenericTimeIndependent, we must treat this case after bTangentGenericClampProgressive
if (KeyCount == 1)
{
bIsFlatTangent = true;
}
else
{
//Spline tangent key must be User mode since we want to keep the tangents provide by the fbx key left and right derivatives
NewTangentMode = EInterchangeCurveTangentMode::User;
}
}
if (bIsFlatTangent)
{
RightTangent = 0;
LeftTangent = 0;
//To force flat tangent we need to set the tangent mode to user
NewTangentMode = EInterchangeCurveTangentMode::User;
}
}
break;
}
//auto with weighted give the wrong result, so when auto is weighted we set user mode and set the Right tangent equal to the left tangent.
//Auto has only the left tangent set
if (NewTangentMode == EInterchangeCurveTangentMode::Auto && (bLeftWeightActive || bRightWeightActive))
{
NewTangentMode = EInterchangeCurveTangentMode::User;
RightTangent = LeftTangent;
}
if (NewTangentMode != EInterchangeCurveTangentMode::Auto)
{
const bool bEqualTangents = FMath::IsNearlyEqual(LeftTangent, RightTangent);
//If tangents are different then broken.
if (bEqualTangents)
{
NewTangentMode = EInterchangeCurveTangentMode::User;
}
else
{
NewTangentMode = EInterchangeCurveTangentMode::Break;
}
}
//Only cubic interpolation allow weighted tangents
if (KeyInterpMode == FbxAnimCurveDef::eInterpolationCubic)
{
if (bLeftWeightActive && bRightWeightActive)
{
NewTangentWeightMode = EInterchangeCurveTangentWeightMode::WeightedBoth;
}
else if (bLeftWeightActive)
{
NewTangentWeightMode = EInterchangeCurveTangentWeightMode::WeightedArrive;
RightTangentWeight = DefaultCurveWeight;
}
else if (bRightWeightActive)
{
NewTangentWeightMode = EInterchangeCurveTangentWeightMode::WeightedLeave;
LeftTangentWeight = DefaultCurveWeight;
}
else
{
NewTangentWeightMode = EInterchangeCurveTangentWeightMode::WeightedNone;
LeftTangentWeight = DefaultCurveWeight;
RightTangentWeight = DefaultCurveWeight;
}
auto ComputeWeightInternal = [](float TimeA, float TimeB, const float TangentSlope, const float TangentWeight)
{
const float X = TimeA - TimeB;
const float Y = TangentSlope * X;
return FMath::Sqrt(X * X + Y * Y) * TangentWeight;
};
if (!FMath::IsNearlyZero(LeftTangentWeight))
{
if (bPreviousKeyValid)
{
LeftTangentWeight = ComputeWeightInternal(KeyTimeValue, PreviousKeyTimeValue, LeftTangent, LeftTangentWeight);
}
else
{
LeftTangentWeight = 0.0f;
}
}
if (!FMath::IsNearlyZero(RightTangentWeight))
{
if (bNextKeyValid)
{
RightTangentWeight = ComputeWeightInternal(NextKeyTimeValue, KeyTimeValue, RightTangent, RightTangentWeight);
}
else
{
RightTangentWeight = 0.0f;
}
}
}
const bool bForceDisableTangentRecompute = false; //No need to recompute all the tangents of the curve every time we change de key.
InterchangeCurveKey.InterpMode = NewInterpMode;
InterchangeCurveKey.TangentMode = NewTangentMode;
InterchangeCurveKey.TangentWeightMode = NewTangentWeightMode;
InterchangeCurveKey.ArriveTangent = LeftTangent;
InterchangeCurveKey.LeaveTangent = RightTangent;
InterchangeCurveKey.ArriveTangentWeight = LeftTangentWeight;
InterchangeCurveKey.LeaveTangentWeight = RightTangentWeight;
}
return true;
}
template<typename AttributeType>
void FillStepCurveAttribute(TArray<float>& OutFrameTimes, TArray<AttributeType>& OutFrameValues, const FbxAnimCurve* FbxCurve, TFunctionRef<AttributeType(const FbxAnimCurveKey*, const FbxTime*)> EvaluationFunction)
{
const int32 KeyCount = FbxCurve ? FbxCurve->KeyGetCount() : 0;
if (KeyCount > 0)
{
OutFrameTimes.Reserve(KeyCount);
OutFrameValues.Reserve(KeyCount);
const FbxTime StartTime = FbxCurve->KeyGet(0).GetTime();
for (int32 KeyIndex = 0; KeyIndex < KeyCount; ++KeyIndex)
{
FbxAnimCurveKey Key = FbxCurve->KeyGet(KeyIndex);
FbxTime KeyTime = Key.GetTime() - StartTime;
OutFrameTimes.Add(KeyTime.GetSecondDouble());
OutFrameValues.Add(EvaluationFunction(&Key, &KeyTime));
}
}
else
{
OutFrameTimes.Add(0);
OutFrameValues.Add(EvaluationFunction(nullptr, nullptr));
}
}
template<typename T>
void ImportStepCurve(const FbxAnimCurve* SourceCurves, FbxProperty& Property, FInterchangeStepCurve& DestinationCurve)
{
TArray<T> StepCurveValues;
FillStepCurveAttribute<T>(DestinationCurve.KeyTimes, StepCurveValues, SourceCurves, [&Property](const FbxAnimCurveKey* Key, const FbxTime* KeyTime)
{
if(Key)
{
return static_cast<T>(Key->GetValue());
}
else
{
return static_cast<T>(Property.Get<T>());
}
});
if constexpr(std::is_same_v<T, bool>)
{
DestinationCurve.BooleanKeyValues = StepCurveValues;
}
else if constexpr(std::is_floating_point_v<T>)
{
check(false); //Float curve payload should be extract as FInterchangeCurve since we can interpolate them
}
else if constexpr(sizeof(T) == sizeof(uint8))
{
DestinationCurve.ByteKeyValues = StepCurveValues;
}
else if constexpr(std::is_integral_v<T> && sizeof(T) > sizeof(uint8))
{
DestinationCurve.IntegerKeyValues = StepCurveValues;
}
}
void ImportStringStepCurve(const FbxAnimCurve* SourceCurves, FbxProperty& Property, FInterchangeStepCurve& DestinationCurve)
{
TArray<FString> StepCurveValues;
FillStepCurveAttribute<FString>(DestinationCurve.KeyTimes, StepCurveValues, SourceCurves, [&Property](const FbxAnimCurveKey* Key, const FbxTime* KeyTime)
{
if (KeyTime)
{
FbxPropertyValue& EvaluatedValue = Property.EvaluateValue(*KeyTime);
FbxString StringValue;
EvaluatedValue.Get(&StringValue, EFbxType::eFbxString);
return FString(UTF8_TO_TCHAR(StringValue));
}
else
{
return FString(UTF8_TO_TCHAR(Property.Get<FbxString>()));
}
});
DestinationCurve.StringKeyValues = StepCurveValues;
}
struct FGetFbxTransformCurvesParameters
{
FGetFbxTransformCurvesParameters(FbxScene* InSDKScene, FbxNode* InNode)
{
SDKScene = InSDKScene;
check(SDKScene);
Node = InNode;
check(Node);
}
FbxScene* SDKScene = nullptr;
FbxNode* Node = nullptr;
bool IsNodeAnimated = false;
FbxAnimStack* CurrentAnimStack = nullptr;
};
void GetFbxTransformCurves(FGetFbxTransformCurvesParameters& Parameters, const int32& AnimationIndex)
{
if (!ensure(Parameters.SDKScene) || !ensure(Parameters.Node))
{
return;
}
//Get the node transform curve keys, the transform components are separate into float curve
//Translation X
//Translation Y
//Translation Z
//Euler X
//Euler Y
//Euler Z
//Scale X
//Scale Y
//Scale Z
Parameters.IsNodeAnimated = false;
int32 NumAnimations = Parameters.SDKScene->GetSrcObjectCount<FbxAnimStack>();
if (AnimationIndex >= NumAnimations)
{
return;
}
int32 NumberOfChannels = 9;
Parameters.CurrentAnimStack = (FbxAnimStack*)Parameters.SDKScene->GetSrcObject<FbxAnimStack>(AnimationIndex);
int32 NumLayers = Parameters.CurrentAnimStack->GetMemberCount();
for (int LayerIndex = 0; LayerIndex < NumLayers && !Parameters.IsNodeAnimated; LayerIndex++)
{
FbxAnimLayer* AnimLayer = (FbxAnimLayer*)Parameters.CurrentAnimStack->GetMember(LayerIndex);
TArray<FbxAnimCurve*> TransformChannelCurves;
TransformChannelCurves.Reserve(NumberOfChannels);
// Display curves specific to properties
TransformChannelCurves.Add(Parameters.Node->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, false));
TransformChannelCurves.Add(Parameters.Node->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, false));
TransformChannelCurves.Add(Parameters.Node->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, false));
TransformChannelCurves.Add(Parameters.Node->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, false));
TransformChannelCurves.Add(Parameters.Node->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, false));
TransformChannelCurves.Add(Parameters.Node->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, false));
TransformChannelCurves.Add(Parameters.Node->LclScaling.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, false));
TransformChannelCurves.Add(Parameters.Node->LclScaling.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, false));
TransformChannelCurves.Add(Parameters.Node->LclScaling.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, false));
if (!Parameters.IsNodeAnimated)
{
for (int32 ChannelIndex = 0; ChannelIndex < NumberOfChannels; ++ChannelIndex)
{
if (TransformChannelCurves[ChannelIndex])
{
Parameters.IsNodeAnimated = true;
break;
}
}
}
}
}
bool FAnimationPayloadContext::FetchPayloadToFile(FFbxParser& Parser, const FString& PayloadFilepath)
{
if (AttributeFetchPayloadData.IsSet() || AttributeNodeTransformFetchPayloadData.IsSet())
{
return InternalFetchCurveNodePayloadToFile(Parser, PayloadFilepath);
}
else if (MorphTargetFetchPayloadData.IsSet())
{
return InternalFetchMorphTargetCurvePayloadToFile(Parser, PayloadFilepath);
}
return false;
}
bool FAnimationPayloadContext::FetchAnimationBakeTransformPayloadForTime(FFbxParser& Parser, const FbxTime Currenttime, FTransform& OutLocalTransform)
{
if (!ensure(NodeTransformFetchPayloadData.IsSet()))
{
UInterchangeResultError_Generic* Message = Parser.AddMessage<UInterchangeResultError_Generic>();
Message->Text = LOCTEXT("NodeTransformFetchPayloadData_NotSet", "Cannot fetch FBX animation transform payload because the FBX FNodeTransformFetchPayloadData is not set.");
return false;
}
FNodeTransformFetchPayloadData& FetchPayloadData = NodeTransformFetchPayloadData.GetValue();
if (!ensure(FetchPayloadData.Node))
{
UInterchangeResultError_Generic* Message = Parser.AddMessage<UInterchangeResultError_Generic>();
Message->InterchangeKey = Parser.GetFbxHelper()->GetFbxNodeHierarchyName(FetchPayloadData.Node);
Message->Text = LOCTEXT("FBXNodeNull", "Cannot fetch FBX animation transform payload because the FBX node is null.");
return false;
}
bool bNanErrorLogged = false;
auto LogNanError = [&bNanErrorLogged, &FetchPayloadData, &Parser]()
{
if (bNanErrorLogged)
{
return;
}
UInterchangeResultError_Generic* Message = Parser.AddMessage<UInterchangeResultError_Generic>();
Message->InterchangeKey = Parser.GetFbxHelper()->GetFbxNodeHierarchyName(FetchPayloadData.Node);
Message->Text = LOCTEXT("BoneTransformNan", "Error when fetching FBX animation bake transforms payload, some transform contain NAN.");
bNanErrorLogged = true;
};
FbxNode* ParentNode = FetchPayloadData.Node->GetParent();
if (ParentNode)
{
FbxAMatrix NodeTransform = FetchPayloadData.Node->EvaluateGlobalTransform(Currenttime);
FTransform GlobalTransform = UE::Interchange::Private::FFbxConvert::ConvertTransform<FTransform, FVector, FQuat>(NodeTransform);
if (GlobalTransform.ContainsNaN())
{
LogNanError();
GlobalTransform.SetIdentity();
}
FbxAMatrix ParentTransform = ParentNode->EvaluateGlobalTransform(Currenttime);
FTransform ParentGlobalTransform = UE::Interchange::Private::FFbxConvert::ConvertTransform<FTransform, FVector, FQuat>(ParentTransform);
if (ParentGlobalTransform.ContainsNaN())
{
LogNanError();
ParentGlobalTransform.SetIdentity();
}
OutLocalTransform = GlobalTransform.GetRelativeTransform(ParentGlobalTransform);
}
else
{
FbxAMatrix& LocalMatrix = FetchPayloadData.Node->EvaluateLocalTransform(Currenttime);
FbxVector4 NewLocalT = LocalMatrix.GetT();
FbxVector4 NewLocalS = LocalMatrix.GetS();
FbxQuaternion NewLocalQ = LocalMatrix.GetQ();
OutLocalTransform.SetTranslation(UE::Interchange::Private::FFbxConvert::ConvertPos<FVector>(NewLocalT));
OutLocalTransform.SetScale3D(UE::Interchange::Private::FFbxConvert::ConvertScale<FVector>(NewLocalS));
OutLocalTransform.SetRotation(UE::Interchange::Private::FFbxConvert::ConvertRotToQuat<FQuat>(NewLocalQ));
}
return true;
}
FbxAnimStack* FAnimationPayloadContext::GetAnimStack()
{
if (NodeTransformFetchPayloadData.IsSet())
{
return NodeTransformFetchPayloadData->CurrentAnimStack;
}
return nullptr;
}
bool FAnimationPayloadContext::InternalFetchCurveNodePayloadToFile(FFbxParser& Parser, const FString& PayloadFilepath)
{
if (AttributeFetchPayloadData.IsSet())
{
FAttributeFetchPayloadData& FetchPayloadData = AttributeFetchPayloadData.GetValue();
if (!ensure(FetchPayloadData.Node != nullptr))
{
UInterchangeResultError_Generic* Message = Parser.AddMessage<UInterchangeResultError_Generic>();
Message->InterchangeKey = Parser.GetFbxHelper()->GetFbxNodeHierarchyName(FetchPayloadData.Node);
Message->Text = LOCTEXT("InternalFetchCurveNodePayloadToFile_FBXNodeNull", "Cannot fetch FBX animation curve payload because the FBX node is null.");
return false;
}
if (!ensure(FetchPayloadData.AnimCurves != nullptr))
{
UInterchangeResultError_Generic* Message = Parser.AddMessage<UInterchangeResultError_Generic>();
Message->InterchangeKey = Parser.GetFbxHelper()->GetFbxNodeHierarchyName(FetchPayloadData.Node);
Message->Text = LOCTEXT("InternalFetchCurveNodePayloadToFile_FBXCurveNull", "Cannot fetch FBX user attribute animation curve payload because the FBX anim curve node is null.");
return false;
}
if (FetchPayloadData.bAttributeTypeIsStepCurveAnimation)
{
//Fetch TArray<FInterchangeStepCurve> step curve, no interpolation
TArray<FInterchangeStepCurve> InterchangeStepCurves;
const uint32 ChannelCount = FetchPayloadData.AnimCurves->GetChannelsCount();
for (uint32 ChannelIndex = 0; ChannelIndex < ChannelCount; ++ChannelIndex)
{
const uint32 ChannelCurveCount = FetchPayloadData.AnimCurves->GetCurveCount(ChannelIndex);
for (uint32 CurveIndex = 0; CurveIndex < ChannelCurveCount; ++CurveIndex)
{
if (FbxAnimCurve* CurrentAnimCurve = FetchPayloadData.AnimCurves->GetCurve(ChannelIndex, CurveIndex))
{
switch (FetchPayloadData.PropertyType)
{
case EFbxType::eFbxBool:
ImportStepCurve<bool>(CurrentAnimCurve, FetchPayloadData.Property, InterchangeStepCurves.AddDefaulted_GetRef());
case EFbxType::eFbxChar:
case EFbxType::eFbxUChar:
case EFbxType::eFbxEnum:
ImportStepCurve<uint8>(CurrentAnimCurve, FetchPayloadData.Property, InterchangeStepCurves.AddDefaulted_GetRef());
case EFbxType::eFbxShort:
case EFbxType::eFbxUShort:
case EFbxType::eFbxInt:
case EFbxType::eFbxUInt:
case EFbxType::eFbxLongLong:
case EFbxType::eFbxULongLong:
ImportStepCurve<int32>(CurrentAnimCurve, FetchPayloadData.Property, InterchangeStepCurves.AddDefaulted_GetRef());
break;
case EFbxType::eFbxHalfFloat:
case EFbxType::eFbxFloat:
case EFbxType::eFbxDouble:
case EFbxType::eFbxDouble2:
case EFbxType::eFbxDouble3:
case EFbxType::eFbxDouble4:
check(false); //Float curve payload should be extract as FInterchangeCurve since we can interpolate them
break;
case EFbxType::eFbxString:
ImportStringStepCurve(CurrentAnimCurve, FetchPayloadData.Property, InterchangeStepCurves.AddDefaulted_GetRef());
break;
}
}
}
}
{
FLargeMemoryWriter Ar;
Ar << InterchangeStepCurves;
uint8* ArchiveData = Ar.GetData();
int64 ArchiveSize = Ar.TotalSize();
TArray64<uint8> Buffer(ArchiveData, ArchiveSize);
FFileHelper::SaveArrayToFile(Buffer, *PayloadFilepath);
}
}
else
{
//Fetch TArray<FInterchangeCurve> which are float curves with interpolation
TArray<FInterchangeCurve> InterchangeCurves;
const uint32 ChannelCount = FetchPayloadData.AnimCurves->GetChannelsCount();
for (uint32 ChannelIndex = 0; ChannelIndex < ChannelCount; ++ChannelIndex)
{
const uint32 ChannelCurveCount = FetchPayloadData.AnimCurves->GetCurveCount(ChannelIndex);
for (uint32 CurveIndex = 0; CurveIndex < ChannelCurveCount; ++CurveIndex)
{
if (FbxAnimCurve* CurrentAnimCurve = FetchPayloadData.AnimCurves->GetCurve(ChannelIndex, CurveIndex))
{
ImportCurve(CurrentAnimCurve, 1.0f, InterchangeCurves.AddDefaulted_GetRef().Keys);
}
}
}
{
FLargeMemoryWriter Ar;
Ar << InterchangeCurves;
uint8* ArchiveData = Ar.GetData();
int64 ArchiveSize = Ar.TotalSize();
TArray64<uint8> Buffer(ArchiveData, ArchiveSize);
FFileHelper::SaveArrayToFile(Buffer, *PayloadFilepath);
}
}
return true;
}
if (AttributeNodeTransformFetchPayloadData.IsSet())
{
FAttributeNodeTransformFetchPayloadData& FetchPayloadData = AttributeNodeTransformFetchPayloadData.GetValue();
//Fetch TArray<FInterchangeCurve> which are float curves with interpolation
TArray<FInterchangeCurve> InterchangeCurves;
auto ParseInterchangeCurveCurve = [&InterchangeCurves](FbxAnimCurveNode* AnimCurveNode, const char* pChannelName, float Scale = 1.0f)
{
bool bInterchangeCurveAdded = false;
if (AnimCurveNode)
{
int ChannelIndex = AnimCurveNode->GetChannelIndex(pChannelName);
if (ChannelIndex != -1)
{
if (FbxAnimCurve* CurrentAnimCurve = AnimCurveNode->GetCurve(ChannelIndex))
{
ImportCurve(CurrentAnimCurve, Scale, InterchangeCurves.AddDefaulted_GetRef().Keys);
bInterchangeCurveAdded = true;
}
}
}
if (!bInterchangeCurveAdded)
{
InterchangeCurves.AddDefaulted();
}
};
auto ResetPivotsPrePostRotationsAndSetRotationOrder = [](FbxNode* Node, double FrameRate)
{
//This function now clears out all pivots, post and pre rotations and set's the
//RotationOrder to XYZ.
//Updated per the latest documentation
//https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_C35D98CB_5148_4B46_82D1_51077D8970EE_htm
if (!ensure(Node))
{
return;
}
// Activate pivot converting
Node->SetPivotState(FbxNode::eSourcePivot, FbxNode::ePivotActive);
Node->SetPivotState(FbxNode::eDestinationPivot, FbxNode::ePivotActive);
FbxVector4 Zero(0, 0, 0);
// We want to set all these to 0 and bake them into the transforms.
Node->SetPostRotation(FbxNode::eDestinationPivot, Zero);
Node->SetPreRotation(FbxNode::eDestinationPivot, Zero);
Node->SetRotationOffset(FbxNode::eDestinationPivot, Zero);
Node->SetScalingOffset(FbxNode::eDestinationPivot, Zero);
Node->SetRotationPivot(FbxNode::eDestinationPivot, Zero);
Node->SetScalingPivot(FbxNode::eDestinationPivot, Zero);
Node->SetRotationOrder(FbxNode::eDestinationPivot, eEulerXYZ);
//When we support other orders do this.
//EFbxRotationOrder ro;
//Node->GetRotationOrder(FbxNode::eSourcePivot, ro);
//Node->SetRotationOrder(FbxNode::eDestinationPivot, ro);
//Most DCC's don't have this but 3ds Max does
Node->SetGeometricTranslation(FbxNode::eDestinationPivot, Zero);
Node->SetGeometricRotation(FbxNode::eDestinationPivot, Zero);
Node->SetGeometricScaling(FbxNode::eDestinationPivot, Zero);
//NOTE THAT ConvertPivotAnimationRecursive did not seem to work when getting the local transform values!!!
Node->ConvertPivotAnimationRecursive(nullptr, FbxNode::eDestinationPivot, FrameRate);
};
ResetPivotsPrePostRotationsAndSetRotationOrder(FetchPayloadData.Node, FetchPayloadData.FrameRate);
ParseInterchangeCurveCurve(FetchPayloadData.TranlsationCurveNode, FBXSDK_CURVENODE_COMPONENT_X);
ParseInterchangeCurveCurve(FetchPayloadData.TranlsationCurveNode, FBXSDK_CURVENODE_COMPONENT_Y, -1.0f); //FBX to UE conversion with scale -1.0
ParseInterchangeCurveCurve(FetchPayloadData.TranlsationCurveNode, FBXSDK_CURVENODE_COMPONENT_Z);
ParseInterchangeCurveCurve(FetchPayloadData.RotationCurveNode, FBXSDK_CURVENODE_COMPONENT_X);
ParseInterchangeCurveCurve(FetchPayloadData.RotationCurveNode, FBXSDK_CURVENODE_COMPONENT_Y, -1.0f); //FBX to UE conversion with scale -1.0
ParseInterchangeCurveCurve(FetchPayloadData.RotationCurveNode, FBXSDK_CURVENODE_COMPONENT_Z, -1.0f); //FBX to UE conversion with scale -1.0
ParseInterchangeCurveCurve(FetchPayloadData.ScaleCurveNode, FBXSDK_CURVENODE_COMPONENT_X);
ParseInterchangeCurveCurve(FetchPayloadData.ScaleCurveNode, FBXSDK_CURVENODE_COMPONENT_Y);
ParseInterchangeCurveCurve(FetchPayloadData.ScaleCurveNode, FBXSDK_CURVENODE_COMPONENT_Z);
{
FLargeMemoryWriter Ar;
Ar << InterchangeCurves;
uint8* ArchiveData = Ar.GetData();
int64 ArchiveSize = Ar.TotalSize();
TArray64<uint8> Buffer(ArchiveData, ArchiveSize);
FFileHelper::SaveArrayToFile(Buffer, *PayloadFilepath);
}
return true;
}
return false;
}
bool FAnimationPayloadContext::InternalFetchMorphTargetCurvePayloadToFile(FFbxParser& Parser, const FString& PayloadFilepath)
{
check(MorphTargetFetchPayloadData.IsSet());
FMorphTargetFetchPayloadData& FetchPayloadData = MorphTargetFetchPayloadData.GetValue();
TArray<FInterchangeCurve> InterchangeCurves;
if (InternalFetchMorphTargetCurvePayload(Parser, InterchangeCurves))
{
FLargeMemoryWriter Ar;
Ar << InterchangeCurves;
Ar << FetchPayloadData.InbetweenCurveNames;
Ar << FetchPayloadData.InbetweenFullWeights;
uint8* ArchiveData = Ar.GetData();
int64 ArchiveSize = Ar.TotalSize();
TArray64<uint8> Buffer(ArchiveData, ArchiveSize);
FFileHelper::SaveArrayToFile(Buffer, *PayloadFilepath);
return true;
}
return false;
}
bool FAnimationPayloadContext::InternalFetchMorphTargetCurvePayload(FFbxParser& Parser, TArray<FInterchangeCurve>& InterchangeCurves)
{
check(MorphTargetFetchPayloadData.IsSet());
FMorphTargetFetchPayloadData& FetchPayloadData = MorphTargetFetchPayloadData.GetValue();
if (!FetchPayloadData.SDKScene)
{
UInterchangeResultError_Generic* Message = Parser.AddMessage<UInterchangeResultError_Generic>();
Message->Text = LOCTEXT("InternalFetchMorphTargetCurvePayloadToFile_FBXSDKSceneNull", "InternalFetchMorphTargetCurvePayloadToFile, fbx sdk is nullptr.");
return false;
}
FbxGeometry* Geometry = FetchPayloadData.SDKScene->GetGeometry(FetchPayloadData.GeometryIndex);
if (!Geometry)
{
UInterchangeResultError_Generic* Message = Parser.AddMessage<UInterchangeResultError_Generic>();
Message->Text = LOCTEXT("InternalFetchMorphTargetCurvePayloadToFile_FBXGeometryNull", "Cannot fetch FBX geometry from the scene.");
return false;
}
FbxAnimCurve* AnimCurve = Geometry->GetShapeChannel(FetchPayloadData.MorphTargetIndex, FetchPayloadData.ChannelIndex, FetchPayloadData.AnimLayer);
//Morph target curve in fbx are between 0 and 100, in Unreal we are between 0 and 1, so we must scale
//The curve with 0.01
constexpr float ScaleCurve = 0.01f;
return ImportCurve(AnimCurve, ScaleCurve, InterchangeCurves.AddDefaulted_GetRef().Keys);
}
bool FFbxAnimation::AddSkeletalTransformAnimation(UInterchangeBaseNodeContainer& NodeContainer
, FbxScene* SDKScene
, FFbxParser& Parser
, FbxNode* Node
, UInterchangeSceneNode* SceneNode
, TMap<FString, TSharedPtr<FPayloadContextBase>>& PayloadContexts
, UInterchangeSkeletalAnimationTrackNode* SkeletalAnimationTrackNode
, const int32& AnimationIndex)
{
FGetFbxTransformCurvesParameters Parameters(SDKScene, Node);
GetFbxTransformCurves(Parameters, AnimationIndex);
if (!Parameters.IsNodeAnimated)
{
//If we have a joint under the root skeleton and there is some animation in the parent hierarchy
//we have to enable IsNodeAnimated so it get bake correctly and generate the appropriate curves
FString RootSkeletonNodeUid;
if (SkeletalAnimationTrackNode->GetCustomSkeletonNodeUid(RootSkeletonNodeUid))
{
const UInterchangeSceneNode* ParentNode = Cast<UInterchangeSceneNode>(NodeContainer.GetNode(SceneNode->GetParentUid()));
//Search up the hierarchy if we found any animated parent we have to animate the children
while (ParentNode)
{
if (SkeletalAnimationTrackNode->IsNodeAnimatedWithBakedCurve(ParentNode->GetUniqueID()))
{
Parameters.IsNodeAnimated = true;
break;
}
if (ParentNode->GetUniqueID().Equals(RootSkeletonNodeUid))
{
break;
}
ParentNode = Cast<UInterchangeSceneNode>(NodeContainer.GetNode(ParentNode->GetParentUid()));
}
}
}
if (Parameters.IsNodeAnimated)
{
FString PayLoadKey = Parser.GetFbxHelper()->GetFbxNodeHierarchyName(Node) + TEXT("_") + FString::FromInt(AnimationIndex) + TEXT("_SkeletalAnimationPayloadKey");
if (ensure(!PayloadContexts.Contains(PayLoadKey)))
{
TSharedPtr<FAnimationPayloadContext> AnimPayload = MakeShared<FAnimationPayloadContext>();
FNodeTransformFetchPayloadData FetchPayloadData;
FetchPayloadData.Node = Node;
FetchPayloadData.CurrentAnimStack = Parameters.CurrentAnimStack;
AnimPayload->NodeTransformFetchPayloadData = FetchPayloadData;
PayloadContexts.Add(PayLoadKey, AnimPayload);
}
SkeletalAnimationTrackNode->SetAnimationPayloadKeyForSceneNodeUid(SceneNode->GetUniqueID(), PayLoadKey, EInterchangeAnimationPayLoadType::BAKED);
}
return Parameters.IsNodeAnimated;
}
void FFbxAnimation::AddNodeAttributeCurvesAnimation(FFbxParser& Parser
, FbxNode* Node
, FbxProperty& Property
, FbxAnimCurveNode* AnimCurveNode
, UInterchangeSceneNode* SceneNode
, TMap<FString, TSharedPtr<FPayloadContextBase>>& PayloadContexts
, EFbxType PropertyType
, TOptional<FString>& OutPayloadKey
, TOptional<bool>& OutIsStepCurve)
{
const FString PropertyName = Parser.GetFbxHelper()->GetFbxPropertyName(Property);
const FString PayLoadKey = Parser.GetFbxHelper()->GetFbxNodeHierarchyName(Node) + PropertyName + TEXT("_AnimationPayloadKey");
if (ensure(!PayloadContexts.Contains(PayLoadKey)))
{
TSharedPtr<FAnimationPayloadContext> AnimPayload = MakeShared<FAnimationPayloadContext>();
FAttributeFetchPayloadData FetchPayloadData;
FetchPayloadData.Node = Node;
FetchPayloadData.AnimCurves = AnimCurveNode;
//Only curves with Constant interpolation on all keys are deemed as StepCurves.
OutIsStepCurve = !IsFbxPropertyTypeDecimal(PropertyType) && IsStepCurve(AnimCurveNode);
FetchPayloadData.bAttributeTypeIsStepCurveAnimation = OutIsStepCurve.GetValue();
FetchPayloadData.PropertyType = PropertyType;
FetchPayloadData.Property = Property;
AnimPayload->AttributeFetchPayloadData = FetchPayloadData;
PayloadContexts.Add(PayLoadKey, AnimPayload);
OutPayloadKey = PayLoadKey;
}
}
void FFbxAnimation::AddRigidTransformAnimation(FFbxParser& Parser
, FbxNode* Node
, FbxAnimCurveNode* TranslationCurveNode
, FbxAnimCurveNode* RotationCurveNode
, FbxAnimCurveNode* ScaleCurveNode
, TMap<FString, TSharedPtr<FPayloadContextBase>>& PayloadContexts
, TOptional<FString>& OutPayloadKey)
{
const FString PayLoadKey = Parser.GetFbxHelper()->GetFbxNodeHierarchyName(Node) + TEXT("_RigidAnimationPayloadKey");
if (ensure(!PayloadContexts.Contains(PayLoadKey)))
{
TSharedPtr<FAnimationPayloadContext> AnimPayload = MakeShared<FAnimationPayloadContext>();
FAttributeNodeTransformFetchPayloadData FetchPayloadData;
FetchPayloadData.FrameRate = Parser.GetFrameRate();
FetchPayloadData.Node = Node;
FetchPayloadData.TranlsationCurveNode = TranslationCurveNode;
FetchPayloadData.RotationCurveNode = RotationCurveNode;
FetchPayloadData.ScaleCurveNode = ScaleCurveNode;
//Any property that is not decimal should import with step curve
AnimPayload->AttributeNodeTransformFetchPayloadData = FetchPayloadData;
PayloadContexts.Add(PayLoadKey, AnimPayload);
OutPayloadKey = PayLoadKey;
}
}
void FFbxAnimation::AddMorphTargetCurvesAnimation(FbxScene* SDKScene
, FFbxParser& Parser
, UInterchangeSkeletalAnimationTrackNode* SkeletalAnimationTrackNode
, TMap<FString, TSharedPtr<FPayloadContextBase>>& PayloadContexts
, const FMorphTargetAnimationBuildingData& MorphTargetAnimationBuildingData)
{
FString PayLoadKey = MorphTargetAnimationBuildingData.MorphTargetNodeUid
+ TEXT("\\") + (MorphTargetAnimationBuildingData.InterchangeMeshNode ? MorphTargetAnimationBuildingData.InterchangeMeshNode->GetUniqueID() : FString()) //Same shape can be animated on different mesh node
+ TEXT("\\") + FString::FromInt(MorphTargetAnimationBuildingData.AnimationIndex)
+ TEXT("\\") + FString::FromInt(MorphTargetAnimationBuildingData.MorphTargetIndex)
+ TEXT("\\") + FString::FromInt(MorphTargetAnimationBuildingData.ChannelIndex)
+ TEXT("_CurveAnimationPayloadKey");
if (ensure(!PayloadContexts.Contains(PayLoadKey)))
{
TSharedPtr<FAnimationPayloadContext> AnimPayload = MakeShared<FAnimationPayloadContext>();
FMorphTargetFetchPayloadData FetchPayloadData;
FetchPayloadData.SDKScene = SDKScene;
FetchPayloadData.GeometryIndex = MorphTargetAnimationBuildingData.GeometryIndex;
FetchPayloadData.MorphTargetIndex = MorphTargetAnimationBuildingData.MorphTargetIndex;
FetchPayloadData.ChannelIndex = MorphTargetAnimationBuildingData.ChannelIndex;
FetchPayloadData.AnimLayer = MorphTargetAnimationBuildingData.AnimLayer;
FetchPayloadData.InbetweenCurveNames = MorphTargetAnimationBuildingData.InbetweenCurveNames;
FetchPayloadData.InbetweenFullWeights = MorphTargetAnimationBuildingData.InbetweenFullWeights;
AnimPayload->MorphTargetFetchPayloadData = FetchPayloadData;
PayloadContexts.Add(PayLoadKey, AnimPayload);
}
SkeletalAnimationTrackNode->SetAnimationPayloadKeyForMorphTargetNodeUid(MorphTargetAnimationBuildingData.MorphTargetNodeUid, PayLoadKey, EInterchangeAnimationPayLoadType::MORPHTARGETCURVE);
}
bool FFbxAnimation::IsFbxPropertyTypeSupported(EFbxType PropertyType)
{
switch (PropertyType)
{
case EFbxType::eFbxBool:
case EFbxType::eFbxChar: //!< 8 bit signed integer.
case EFbxType::eFbxUChar: //!< 8 bit unsigned integer.
case EFbxType::eFbxShort: //!< 16 bit signed integer.
case EFbxType::eFbxUShort: //!< 16 bit unsigned integer.
case EFbxType::eFbxInt: //!< 32 bit signed integer.
case EFbxType::eFbxUInt: //!< 32 bit unsigned integer.
case EFbxType::eFbxLongLong: //!< 64 bit signed integer.
case EFbxType::eFbxULongLong: //!< 64 bit unsigned integer.
case EFbxType::eFbxHalfFloat: //!< 16 bit floating point.
case EFbxType::eFbxFloat: //!< Floating point value.
case EFbxType::eFbxDouble: //!< Double width floating point value.
case EFbxType::eFbxDouble2: //!< Vector of two double values.
case EFbxType::eFbxDouble3: //!< Vector of three double values.
case EFbxType::eFbxDouble4: //!< Vector of four double values.
case EFbxType::eFbxEnum: //!< Enumeration.
case EFbxType::eFbxString: //!< String.
return true;
}
return false;
};
bool FFbxAnimation::IsFbxPropertyTypeDecimal(EFbxType PropertyType)
{
switch (PropertyType)
{
case EFbxType::eFbxHalfFloat:
case EFbxType::eFbxFloat:
case EFbxType::eFbxDouble:
case EFbxType::eFbxDouble2:
case EFbxType::eFbxDouble3:
case EFbxType::eFbxDouble4:
return true;
}
return false;
}
//PayloadQueries arriving here should be of the same start/stop and frequency:
bool FFbxAnimation::FetchAnimationBakeTransformPayload(FFbxParser& Parser, FbxScene* SDKScene,
TMap<FString, TSharedPtr<FPayloadContextBase>>& PayloadContexts,
const TArray<const UE::Interchange::FAnimationPayloadQuery*>& PayloadQueries,
const FString& ResultFolder, FCriticalSection* ResultPayloadsCriticalSection,
TAtomic<int64>& UniqueIdCounter, TMap<FString, FString>& ResultPayloads,
TArray<FText>& OutErrorMessages)
{
//Helper Structs:
struct FPayloadQueryHelper
{
const UE::Interchange::FAnimationPayloadQuery* PayloadQuery;
TSharedPtr<FPayloadContextBase> PayloadContext;
FPayloadQueryHelper(const UE::Interchange::FAnimationPayloadQuery* InPayloadQuery, TSharedPtr<FPayloadContextBase> InPayloadContext)
: PayloadQuery(InPayloadQuery)
, PayloadContext(InPayloadContext)
{
}
};
struct FPayloadDataHelper
{
FString QueryHashString; //Used for payload file name
FAnimationPayloadData PayloadData;
FPayloadDataHelper(const FString& InHashString, const FString& InSceneNodeUID, const FInterchangeAnimationPayLoadKey& InPayloadKey)
: QueryHashString(InHashString)
, PayloadData(InSceneNodeUID, InPayloadKey)
{
}
};
//If no PayloadQueries then return
if (PayloadQueries.Num() == 0)
{
return true;
}
//Get Timings and Number of Frames for the current PayloadQueries
// Note: PayloadQueries arriving here(FetchAnimationBakeTransformPayloadInternal) should be of the same start/stop and frequency, so we can grab the first one's:
FAnimationTimeDescription TimeDescription = PayloadQueries[0]->TimeDescription;
if (!ensure(!FMath::IsNearlyZero(TimeDescription.BakeFrequency)))
{
OutErrorMessages.Add(LOCTEXT("BakeFrequencyZero", "Cannot fetch FBX animation bake transforms payload because the bake frequency is zero."));
return false;
}
FbxTime StartTime;
StartTime.SetSecondDouble(TimeDescription.RangeStartSecond);
FbxTime EndTime;
EndTime.SetSecondDouble(TimeDescription.RangeStopSecond);
if (!ensure(TimeDescription.RangeStopSecond > TimeDescription.RangeStartSecond))
{
OutErrorMessages.Add(LOCTEXT("InvalidRange", "Cannot fetch FBX animation bake transforms payload because the bake range is invalid."));
return false;
}
const double TimeStepSecond = 1.0 / TimeDescription.BakeFrequency;
FbxTime TimeStep = 0;
TimeStep.SetSecondDouble(TimeStepSecond);
const double SequenceLength = FMath::Max<double>(TimeDescription.RangeStopSecond - TimeDescription.RangeStartSecond, TimeStepSecond);
const int32 NumFrame = FMath::RoundToInt32(SequenceLength * TimeDescription.BakeFrequency);
int32 BakeKeyCount = NumFrame + 1;
ensure(NumFrame >= 0);
//Acquire PayloadContexts and AnimationStacks for the PayloadQueries (also group the FPayloadQueryHelper per AnimationStacks):
// PayloadQueryHelpers maps AnimStacks to FPaloayQueryHelper (which contains a PayloadQuery and the PayloadContext that belongs to it)
TMap<FbxAnimStack*, TArray<FPayloadQueryHelper>> PayloadQueryHelpers;
{
for (const UE::Interchange::FAnimationPayloadQuery* PayloadQuery : PayloadQueries)
{
if (!PayloadContexts.Contains(PayloadQuery->PayloadKey.UniqueId))
{
OutErrorMessages.Add(FText::Format(LOCTEXT("CannotRetrievePayload", "Cannot retrieve payload; payload key['{0}'] doesn't have any context."), FText::FromString(PayloadQuery->PayloadKey.UniqueId)));
continue;
}
TSharedPtr<FPayloadContextBase> PayloadContext = PayloadContexts.FindChecked(PayloadQuery->PayloadKey.UniqueId);
FbxAnimStack* AnimStack = PayloadContext->GetAnimStack();
if (!AnimStack)
{
continue;
}
FString ResultPayloadUniqueId = PayloadQuery->GetHashString();
{
FScopeLock Lock(ResultPayloadsCriticalSection);
//If we already have extract this mesh, no need to extract again
if (ResultPayloads.Contains(ResultPayloadUniqueId))
{
continue;
}
}
TArray<FPayloadQueryHelper>& PayloadHelpersPerAnimStack = PayloadQueryHelpers.FindOrAdd(AnimStack);
PayloadHelpersPerAnimStack.Add(FPayloadQueryHelper(PayloadQuery, PayloadContexts.FindChecked(PayloadQuery->PayloadKey.UniqueId)));
}
}
//Iterate on the AnimStacked groups:
for (TPair<FbxAnimStack*, TArray<FPayloadQueryHelper>>& AnimStackPayloadQueryHelpersPair : PayloadQueryHelpers)
{
SDKScene->SetCurrentAnimationStack(AnimStackPayloadQueryHelpersPair.Key);
TArray<FPayloadQueryHelper>& PayloadQueryHelpersForAnimStack = AnimStackPayloadQueryHelpersPair.Value;
//Initialize PayloadDataHelpers (contains the HashString for the Query and the FAnimationPayloadData
TArray<FPayloadDataHelper> PayloadDataHelpersForAnimStack;
PayloadDataHelpersForAnimStack.Empty(PayloadQueryHelpersForAnimStack.Num());
for (FPayloadQueryHelper& PayloadQueryHelper : PayloadQueryHelpersForAnimStack)
{
FPayloadDataHelper PayloadDataHelper(PayloadQueryHelper.PayloadQuery->GetHashString(), PayloadQueryHelper.PayloadQuery->SceneNodeUniqueID, PayloadQueryHelper.PayloadQuery->PayloadKey);
PayloadDataHelper.PayloadData.BakeFrequency = PayloadQueryHelper.PayloadQuery->TimeDescription.BakeFrequency;
PayloadDataHelper.PayloadData.RangeStartTime = PayloadQueryHelper.PayloadQuery->TimeDescription.RangeStartSecond;
PayloadDataHelper.PayloadData.RangeEndTime = PayloadQueryHelper.PayloadQuery->TimeDescription.RangeStopSecond;
PayloadDataHelper.PayloadData.Transforms.SetNum(BakeKeyCount);
PayloadDataHelpersForAnimStack.Add(PayloadDataHelper);
}
//Acquire Bone Transforms:
//Time iteration
FbxTime CurrentTime = StartTime;
for (size_t FrameIndex = 0; FrameIndex < BakeKeyCount; FrameIndex++, CurrentTime+=TimeStep)
{
//Bone iteration:
for (size_t PayloadDataIndex = 0; PayloadDataIndex < PayloadDataHelpersForAnimStack.Num(); PayloadDataIndex++)
{
FPayloadQueryHelper& PayloadQueryHelper = PayloadQueryHelpersForAnimStack[PayloadDataIndex];
FPayloadDataHelper& PayloadDataHelper = PayloadDataHelpersForAnimStack[PayloadDataIndex];
FTransform Transform;
if (PayloadQueryHelper.PayloadContext->FetchAnimationBakeTransformPayloadForTime(Parser, CurrentTime, Transform))
{
PayloadDataHelper.PayloadData.Transforms[FrameIndex] = Transform;
}
else
{
if (FrameIndex == 0)
{
PayloadDataHelper.PayloadData.Transforms[FrameIndex] = FTransform::Identity;
}
else
{
PayloadDataHelper.PayloadData.Transforms[FrameIndex] = PayloadDataHelper.PayloadData.Transforms[FrameIndex - 1];
}
}
}
}
//Write out results:
ParallelFor(PayloadDataHelpersForAnimStack.Num(), [&PayloadDataHelpersForAnimStack, &ResultPayloads, &ResultPayloadsCriticalSection, &ResultFolder, &UniqueIdCounter](int32 PayloadDataHelperIndex)
{
FPayloadDataHelper& PayloadDataHelper = PayloadDataHelpersForAnimStack[PayloadDataHelperIndex];
FString QueryHashString = PayloadDataHelper.QueryHashString; //PayloadQuery's GetHashString()
FString PayloadFilepathCopy;
{
FScopeLock Lock(ResultPayloadsCriticalSection);
//If we already have extract this mesh, no need to extract again
if (ResultPayloads.Contains(QueryHashString))
{
return;
}
FString& PayloadFilepath = ResultPayloads.FindOrAdd(QueryHashString);
//To avoid file path with too many character, we hash the payloadKey so we have a deterministic length for the file path.
PayloadFilepath = ResultFolder + TEXT("/") + QueryHashString + FString::FromInt(UniqueIdCounter.IncrementExchange()) + TEXT(".payload");
//Copy the map filename key because we are multithreaded and the TMap can be reallocated
PayloadFilepathCopy = PayloadFilepath;
}
FString PayloadFilepath = PayloadFilepathCopy;
FLargeMemoryWriter Ar;
PayloadDataHelper.PayloadData.SerializeBaked(Ar);
uint8* ArchiveData = Ar.GetData();
int64 ArchiveSize = Ar.TotalSize();
TArray64<uint8> Buffer(ArchiveData, ArchiveSize);
FFileHelper::SaveArrayToFile(Buffer, *PayloadFilepath);
});
}
return true;
}
}//ns UE::Interchange::Private
#undef LOCTEXT_NAMESPACE