EM_Task/UnrealEd/Private/Fbx/FbxCurveDataImport.cpp

1096 lines
50 KiB
C++
Raw Permalink Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "FbxImporter.h"
#include "Curves/RichCurve.h"
namespace UnFbx
{
FbxNode* GetNodeFromName(const FString& NodeName, FbxNode* NodeToQuery)
{
if (!FCString::Strcmp(*NodeName, UTF8_TO_TCHAR(NodeToQuery->GetName())))
{
return NodeToQuery;
}
int32 NodeCount = NodeToQuery->GetChildCount();
for (int32 NodeIndex = 0; NodeIndex < NodeCount; ++NodeIndex)
{
FbxNode* ReturnNode = GetNodeFromName(NodeName, NodeToQuery->GetChild(NodeIndex));
if (ReturnNode)
{
return ReturnNode;
}
}
return nullptr;
}
FbxNode* GetNodeFromUniqueID(uint64 UniqueID, FbxNode* NodeToQuery)
{
if (UniqueID == NodeToQuery->GetUniqueID())
{
return NodeToQuery;
}
int32 NodeCount = NodeToQuery->GetChildCount();
for (int32 NodeIndex = 0; NodeIndex < NodeCount; ++NodeIndex)
{
FbxNode* ReturnNode = GetNodeFromUniqueID(UniqueID, NodeToQuery->GetChild(NodeIndex));
if (ReturnNode)
{
return ReturnNode;
}
}
return nullptr;
}
void FFbxCurvesAPI::GetAllNodeNameArray(TArray<FString>& AllNodeNames) const
{
AllNodeNames.Empty(TransformData.Num());
for (auto Transform: TransformData)
{
FbxNode* Node = GetNodeFromUniqueID(Transform.Key, Scene->GetRootNode());
if (Node)
{
AllNodeNames.Add(Node->GetName());
}
}
}
void FFbxCurvesAPI::GetAnimatedNodeNameArray(TArray<FString>& AnimatedNodeNames) const
{
AnimatedNodeNames.Empty(CurvesData.Num());
for (auto AnimNodeKvp: CurvesData)
{
AnimatedNodeNames.Add(AnimNodeKvp.Value.Name);
}
}
void FFbxCurvesAPI::GetNodeAnimatedPropertyNameArray(const FString& NodeName, TArray<FString>& AnimatedPropertyNames) const
{
AnimatedPropertyNames.Empty();
for (auto AnimNodeKvp: CurvesData)
{
const FFbxAnimNodeHandle& AnimNodeHandle = AnimNodeKvp.Value;
if (AnimNodeHandle.Name.Compare(NodeName) == 0)
{
for (auto NodePropertyKvp: AnimNodeHandle.NodeProperties)
{
AnimatedPropertyNames.Add(NodePropertyKvp.Key);
}
for (auto AttributePropertyKvp: AnimNodeHandle.AttributeProperties)
{
AnimatedPropertyNames.Add(AttributePropertyKvp.Key);
}
return;
}
}
}
void FFbxCurvesAPI::GetCustomStringPropertyArray(const FString& NodeName, TArray<TPair<FString, FString>>& CustomPropertyPairs) const
{
CustomPropertyPairs.Empty();
FbxNode* Node = GetNodeFromName(NodeName, Scene->GetRootNode());
if (Node)
{
// Import all custom user-defined FBX properties from the FBX node to the object metadata
FbxProperty CurrentProperty = Node->GetFirstProperty();
while (CurrentProperty.IsValid())
{
if (CurrentProperty.GetFlag(FbxPropertyFlags::eUserDefined) && CurrentProperty.GetPropertyDataType().GetType() == eFbxString)
{
FString PropertyValue = UTF8_TO_TCHAR(CurrentProperty.Get<FbxString>().Buffer());
CustomPropertyPairs.Add(TPair<FString, FString>(UTF8_TO_TCHAR(CurrentProperty.GetName()), PropertyValue));
}
CurrentProperty = Node->GetNextProperty(CurrentProperty);
}
}
}
void FFbxCurvesAPI::GetAllNodePropertyCurveHandles(const FString& NodeName, const FString& PropertyName, TArray<FFbxAnimCurveHandle>& PropertyCurveHandles) const
{
PropertyCurveHandles.Empty();
for (auto AnimNodeKvp: CurvesData)
{
const FFbxAnimNodeHandle& AnimNodeHandle = AnimNodeKvp.Value;
if (AnimNodeHandle.Name.Compare(NodeName) == 0)
{
for (auto NodePropertyKvp: AnimNodeHandle.NodeProperties)
{
const FFbxAnimPropertyHandle& AnimPropertyHandle = NodePropertyKvp.Value;
if (AnimPropertyHandle.Name.Compare(PropertyName) == 0)
{
PropertyCurveHandles = AnimPropertyHandle.CurveHandles;
return;
}
}
for (auto AttributePropertyKvp: AnimNodeHandle.AttributeProperties)
{
const FFbxAnimPropertyHandle& AnimPropertyHandle = AttributePropertyKvp.Value;
if (AnimPropertyHandle.Name.Compare(PropertyName) == 0)
{
PropertyCurveHandles = AnimPropertyHandle.CurveHandles;
return;
}
}
return;
}
}
}
void FFbxCurvesAPI::GetCurveHandle(const FString& NodeName, const FString& PropertyName, int32 ChannelIndex, int32 CompositeIndex, FFbxAnimCurveHandle& CurveHandle) const
{
for (auto AnimNodeKvp: CurvesData)
{
const FFbxAnimNodeHandle& AnimNodeHandle = AnimNodeKvp.Value;
if (AnimNodeHandle.Name.Compare(NodeName) == 0)
{
for (auto NodePropertyKvp: AnimNodeHandle.NodeProperties)
{
const FFbxAnimPropertyHandle& AnimPropertyHandle = NodePropertyKvp.Value;
if (AnimPropertyHandle.Name.Compare(PropertyName) == 0)
{
for (const FFbxAnimCurveHandle& CurrentCurveHandle: AnimPropertyHandle.CurveHandles)
{
if (CurrentCurveHandle.ChannelIndex == ChannelIndex && CurrentCurveHandle.CompositeIndex == CompositeIndex)
{
CurveHandle = CurrentCurveHandle;
return;
}
}
return;
}
}
for (auto AttributePropertyKvp: AnimNodeHandle.AttributeProperties)
{
const FFbxAnimPropertyHandle& AnimPropertyHandle = AttributePropertyKvp.Value;
if (AnimPropertyHandle.Name.Compare(PropertyName) == 0)
{
for (const FFbxAnimCurveHandle& CurrentCurveHandle: AnimPropertyHandle.CurveHandles)
{
if (CurrentCurveHandle.ChannelIndex == ChannelIndex && CurrentCurveHandle.CompositeIndex == CompositeIndex)
{
CurveHandle = CurrentCurveHandle;
return;
}
}
return;
}
}
return;
}
}
}
// deprecated
void FFbxCurvesAPI::GetCurveData(const FFbxAnimCurveHandle& CurveHandle, FInterpCurveFloat& CurveData, bool bNegative) const
{
if (CurveHandle.AnimCurve == nullptr)
return;
int32 KeyCount = CurveHandle.AnimCurve->KeyGetCount();
CurveData.Reset();
for (int32 KeyIndex = 0; KeyIndex < KeyCount; ++KeyIndex)
{
FbxAnimCurveKey CurKey = CurveHandle.AnimCurve->KeyGet(KeyIndex);
// Create the curve keys
FInterpCurvePoint<float> UnrealKey;
UnrealKey.InVal = CurKey.GetTime().GetSecondDouble();
UnrealKey.InterpMode = GetUnrealInterpMode(CurKey);
float OutVal = bNegative ? -CurKey.GetValue() : CurKey.GetValue();
float ArriveTangent = 0.0f;
float LeaveTangent = 0.0f;
// Convert the Bezier control points, if available, into Hermite tangents
if (CurKey.GetInterpolation() == FbxAnimCurveDef::eInterpolationCubic)
{
float LeftTangent = CurveHandle.AnimCurve->KeyGetLeftDerivative(KeyIndex);
float RightTangent = CurveHandle.AnimCurve->KeyGetRightDerivative(KeyIndex);
if (KeyIndex > 0)
{
ArriveTangent = LeftTangent * (CurKey.GetTime().GetSecondDouble() - CurveHandle.AnimCurve->KeyGetTime(KeyIndex - 1).GetSecondDouble());
}
if (KeyIndex < KeyCount - 1)
{
LeaveTangent = RightTangent * (CurveHandle.AnimCurve->KeyGetTime(KeyIndex + 1).GetSecondDouble() - CurKey.GetTime().GetSecondDouble());
}
}
UnrealKey.OutVal = OutVal;
UnrealKey.ArriveTangent = ArriveTangent;
UnrealKey.LeaveTangent = LeaveTangent;
// Add this new key to the curve
CurveData.Points.Add(UnrealKey);
}
}
void FFbxCurvesAPI::GetCurveDataForSequencer(const FFbxAnimCurveHandle& CurveHandle, FRichCurve& RichCurve, bool bNegative, float Scale) const
{
const float DefaultCurveWeight = FbxAnimCurveDef::sDEFAULT_WEIGHT;
FbxAnimCurve* FbxCurve = CurveHandle.AnimCurve;
if (FbxCurve)
{
RichCurve.Reset();
for (int32 KeyIndex = 0; KeyIndex < FbxCurve->KeyGetCount(); ++KeyIndex)
{
FbxAnimCurveKey Key = FbxCurve->KeyGet(KeyIndex);
FbxTime KeyTime = Key.GetTime();
float Value = bNegative ? -Key.GetValue() : Key.GetValue();
Value *= Scale;
FKeyHandle NewKeyHandle = RichCurve.AddKey(KeyTime.GetSecondDouble(), Value, false);
FbxAnimCurveDef::ETangentMode KeyTangentMode = Key.GetTangentMode();
FbxAnimCurveDef::EInterpolationType KeyInterpMode = Key.GetInterpolation();
FbxAnimCurveDef::EWeightedMode KeyTangentWeightMode = Key.GetTangentWeightMode();
ERichCurveInterpMode NewInterpMode = RCIM_Linear;
ERichCurveTangentMode NewTangentMode = RCTM_Auto;
ERichCurveTangentWeightMode NewTangentWeightMode = RCTWM_WeightedNone;
float LeaveTangent = 0.f;
float ArriveTangent = 0.f;
float LeaveTangentWeight = DefaultCurveWeight;
float ArriveTangentWeight = DefaultCurveWeight;
float ArriveTimeDiff = 0.f;
float LeaveTimeDiff = 0.f;
bool bEqualTangents = false;
switch (KeyInterpMode)
{
case FbxAnimCurveDef::eInterpolationConstant: //! Constant value until next key.
NewInterpMode = RCIM_Constant;
break;
case FbxAnimCurveDef::eInterpolationLinear: //! Linear progression to next key.
NewInterpMode = RCIM_Linear;
break;
case FbxAnimCurveDef::eInterpolationCubic: //! Cubic progression to next key.
NewInterpMode = RCIM_Cubic;
// get tangents
{
FbxAnimCurveKey CurKey = CurveHandle.AnimCurve->KeyGet(KeyIndex);
float LeftTangent = FbxCurve->KeyGetLeftDerivative(KeyIndex);
float RightTangent = FbxCurve->KeyGetRightDerivative(KeyIndex);
bEqualTangents = FMath::IsNearlyEqual(LeftTangent, RightTangent);
if (KeyIndex > 0)
{
ArriveTimeDiff = CurKey.GetTime().GetSecondDouble() - FbxCurve->KeyGetTime(KeyIndex - 1).GetSecondDouble();
ArriveTangent = LeftTangent * (ArriveTimeDiff);
}
if (KeyIndex < FbxCurve->KeyGetCount() - 1)
{
LeaveTimeDiff = FbxCurve->KeyGetTime(KeyIndex + 1).GetSecondDouble() - CurKey.GetTime().GetSecondDouble();
LeaveTangent = RightTangent * (LeaveTimeDiff);
}
}
break;
}
if (KeyTangentMode & FbxAnimCurveDef::eTangentAuto) // tangent can be auto or broken/user (
{
NewTangentMode = RCTM_Auto;
}
else
{
// no longer use KeyTangentMode & FbxAnimCurveDef::eTangentGenericBreak to see if tangent is broken since it maya broken/unify is just a manipulation state
// instead we manually check to see if the tangents are the same or different, if different then broken.
if (bEqualTangents)
{
NewTangentMode = RCTM_User;
}
else
{
NewTangentMode = RCTM_Break;
}
}
switch (KeyTangentWeightMode)
{
case FbxAnimCurveDef::eWeightedNone: //! Tangent has default weights of 0.333; we define this state as not weighted.
LeaveTangentWeight = ArriveTangentWeight = DefaultCurveWeight;
NewTangentWeightMode = RCTWM_WeightedNone;
break;
case FbxAnimCurveDef::eWeightedRight: //! Right tangent is weighted.
NewTangentWeightMode = RCTWM_WeightedLeave;
LeaveTangentWeight = Key.GetDataFloat(FbxAnimCurveDef::eRightWeight);
ArriveTangentWeight = DefaultCurveWeight;
break;
case FbxAnimCurveDef::eWeightedNextLeft: //! Left tangent is weighted.
NewTangentWeightMode = RCTWM_WeightedArrive;
LeaveTangentWeight = DefaultCurveWeight;
if (KeyIndex > 0)
{
FbxAnimCurveKey PrevKey = FbxCurve->KeyGet(KeyIndex - 1);
ArriveTangentWeight = PrevKey.GetDataFloat(FbxAnimCurveDef::eNextLeftWeight);
}
else
{
ArriveTangentWeight = 0.f;
}
break;
case FbxAnimCurveDef::eWeightedAll: //! Both left and right tangents are weighted.
NewTangentWeightMode = RCTWM_WeightedBoth;
LeaveTangentWeight = Key.GetDataFloat(FbxAnimCurveDef::eRightWeight);
if (KeyIndex > 0)
{
FbxAnimCurveKey PrevKey = FbxCurve->KeyGet(KeyIndex - 1);
ArriveTangentWeight = PrevKey.GetDataFloat(FbxAnimCurveDef::eNextLeftWeight);
}
else
{
ArriveTangentWeight = 0.f;
}
break;
}
RichCurve.SetKeyInterpMode(NewKeyHandle, NewInterpMode, false);
RichCurve.SetKeyTangentMode(NewKeyHandle, NewTangentMode, false);
RichCurve.SetKeyTangentWeightMode(NewKeyHandle, NewTangentWeightMode, false);
FRichCurveKey& NewKey = RichCurve.GetKey(NewKeyHandle);
NewKey.ArriveTangent = ArriveTangent;
NewKey.LeaveTangent = LeaveTangent;
// Tangent Weights in FBX/Maya are normalized X (Time) values.
// Our weights are the length of hypontenuse. So here we do the
// conversion. Note that Specificed Tangent is already Tangent * Time_Difference;
// so we just need to scale it by the normalized weight value.
if (!FMath::IsNearlyZero(ArriveTangentWeight))
{
const float X = ArriveTangentWeight * ArriveTimeDiff;
const float Y = ArriveTangent * ArriveTangentWeight; // timediff already there
ArriveTangentWeight = FMath::Sqrt(Y * Y + X * X);
}
NewKey.ArriveTangentWeight = ArriveTangentWeight;
if (!FMath::IsNearlyZero(LeaveTangentWeight))
{
const float X = LeaveTangentWeight * LeaveTimeDiff;
const float Y = LeaveTangent * LeaveTangentWeight; // timediff already there
LeaveTangentWeight = FMath::Sqrt(Y * Y + X * X);
}
NewKey.LeaveTangentWeight = LeaveTangentWeight;
}
}
// no longer set tangents till end...
RichCurve.AutoSetTangents();
}
void FFbxCurvesAPI::GetCurveData(const FFbxAnimCurveHandle& CurveHandle, FRichCurve& RichCurve, bool bNegative, float Scale) const
{
FbxAnimCurve* FbxCurve = CurveHandle.AnimCurve;
if (FbxCurve)
{
RichCurve.Reset();
// Send a neutral timespan 0 to infinite, the ImportCurve, offset the keytime by doing: (KeyTime - TimeSpan.start), setting TimeSpan.start at zero will not affect the keys time value.
FbxTimeSpan AnimTimeSpan(FBXSDK_TIME_ZERO, FBXSDK_TIME_INFINITE);
const bool bAutoSetTangents = false;
UnFbx::FFbxImporter::ImportCurve(FbxCurve, RichCurve, AnimTimeSpan, Scale, bAutoSetTangents);
}
}
void FFbxCurvesAPI::GetBakeCurveData(const FFbxAnimCurveHandle& CurveHandle, TArray<float>& CurveData, float PeriodTime, float StartTime /*= 0.0f*/, float StopTime /*= -1.0f*/, bool bNegative /*= false*/, float Scale /*=1.0f*/) const
{
// Make sure the parameter are ok
if (CurveHandle.AnimCurve == nullptr || CurveHandle.AnimationTimeSecond > StartTime || PeriodTime <= 0.0001f || (StopTime > 0.0f && StopTime < StartTime))
return;
CurveData.Empty();
double CurrentTime = (double)StartTime;
int LastEvaluateKey = 0;
// Set the stop time
if (StopTime <= 0.0f || StopTime > CurveHandle.AnimationTimeSecond)
{
StopTime = CurveHandle.AnimationTimeSecond;
}
while (CurrentTime < (double)StopTime)
{
FbxTime FbxStepTime;
FbxStepTime.SetSecondDouble(CurrentTime);
float CurveValue = CurveHandle.AnimCurve->Evaluate(FbxStepTime, &LastEvaluateKey) * Scale;
if (bNegative)
CurveValue = -CurveValue;
CurveData.Add(CurveValue);
CurrentTime += (double)PeriodTime;
}
}
// deprecrated
void FFbxCurvesAPI::GetCurveData(const FString& NodeName, const FString& PropertyName, int32 ChannelIndex, int32 CompositeIndex, FInterpCurveFloat& CurveData, bool bNegative) const
{
FFbxAnimCurveHandle CurveHandle;
GetCurveHandle(NodeName, PropertyName, ChannelIndex, CompositeIndex, CurveHandle);
if (CurveHandle.AnimCurve != nullptr)
{
#pragma warning(disable : 4996) // 'function' was declared deprecated
GetCurveData(CurveHandle, CurveData, bNegative);
#pragma warning(default : 4996) // 'function' was declared deprecated
}
else
{
CurveData.Reset();
}
}
void FFbxCurvesAPI::GetCurveDataForSequencer(const FString& NodeName, const FString& PropertyName, int32 ChannelIndex, int32 CompositeIndex, FRichCurve& RichCurve, bool bNegative, float Scale) const
{
FFbxAnimCurveHandle CurveHandle;
GetCurveHandle(NodeName, PropertyName, ChannelIndex, CompositeIndex, CurveHandle);
if (CurveHandle.AnimCurve != nullptr)
{
GetCurveDataForSequencer(CurveHandle, RichCurve, bNegative, Scale);
}
else
{
RichCurve.Reset();
}
}
void FFbxCurvesAPI::GetCurveData(const FString& NodeName, const FString& PropertyName, int32 ChannelIndex, int32 CompositeIndex, FRichCurve& RichCurve, bool bNegative) const
{
FFbxAnimCurveHandle CurveHandle;
GetCurveHandle(NodeName, PropertyName, ChannelIndex, CompositeIndex, CurveHandle);
if (CurveHandle.AnimCurve != nullptr)
{
GetCurveData(CurveHandle, RichCurve, bNegative);
}
else
{
RichCurve.Reset();
}
}
void FFbxCurvesAPI::GetBakeCurveData(const FString& NodeName, const FString& PropertyName, int32 ChannelIndex, int32 CompositeIndex, TArray<float>& CurveData, float PeriodTime, float StartTime /*= 0.0f*/, float StopTime /*= -1.0f*/, bool bNegative /*= false*/, float Scale /*=1.0f*/) const
{
FFbxAnimCurveHandle CurveHandle;
GetCurveHandle(NodeName, PropertyName, ChannelIndex, CompositeIndex, CurveHandle);
if (CurveHandle.AnimCurve != nullptr)
{
GetBakeCurveData(CurveHandle, CurveData, PeriodTime, StartTime, StopTime, bNegative, Scale);
}
else
{
CurveData.Empty();
}
}
// Helper
EInterpCurveMode FFbxCurvesAPI::GetUnrealInterpMode(FbxAnimCurveKey FbxKey) const
{
EInterpCurveMode Mode = CIM_CurveUser;
// Convert the interpolation type from FBX to Unreal.
switch (FbxKey.GetInterpolation())
{
case FbxAnimCurveDef::eInterpolationCubic: {
FbxAnimCurveDef::ETangentMode TangentMode = FbxKey.GetTangentMode(true);
if (TangentMode & (FbxAnimCurveDef::eTangentUser | FbxAnimCurveDef::eTangentTCB | FbxAnimCurveDef::eTangentGenericClamp | FbxAnimCurveDef::eTangentGenericClampProgressive))
{
Mode = CIM_CurveUser;
}
else if (TangentMode & FbxAnimCurveDef::eTangentGenericBreak)
{
Mode = CIM_CurveBreak;
}
else if (TangentMode & FbxAnimCurveDef::eTangentAuto)
{
Mode = CIM_CurveAuto;
}
break;
}
case FbxAnimCurveDef::eInterpolationConstant:
if (FbxKey.GetTangentMode() != (FbxAnimCurveDef::ETangentMode)FbxAnimCurveDef::eConstantStandard)
{
// warning not support
;
}
Mode = CIM_Constant;
break;
case FbxAnimCurveDef::eInterpolationLinear:
Mode = CIM_Linear;
break;
}
return Mode;
}
void ConvertRotationToUnreal(float& Roll, float& Pitch, float& Yaw, bool bIsCamera, bool bIsLight)
{
FRotator AnimRotator(Pitch, Yaw, Roll);
FTransform AnimRotatorTransform;
FTransform UnrealRootRotatorTransform;
AnimRotatorTransform.SetRotation(AnimRotator.Quaternion());
FRotator UnrealRootRotator;
if (bIsCamera)
{
UnrealRootRotator = FFbxDataConverter::GetCameraRotation();
}
else if (bIsLight)
{
UnrealRootRotator = FFbxDataConverter::GetLightRotation();
}
else
{
UnrealRootRotator = FRotator(0.f);
}
UnrealRootRotatorTransform.SetRotation(UnrealRootRotator.Quaternion());
FTransform ResultTransform = UnrealRootRotatorTransform * AnimRotatorTransform;
FRotator ResultRotator = ResultTransform.Rotator();
Roll = ResultRotator.Roll;
Pitch = ResultRotator.Pitch;
Yaw = ResultRotator.Yaw;
}
void FFbxCurvesAPI::GetConvertedTransformCurveData(const FString& NodeName, FInterpCurveFloat& TranslationX, FInterpCurveFloat& TranslationY, FInterpCurveFloat& TranslationZ,
FInterpCurveFloat& EulerRotationX, FInterpCurveFloat& EulerRotationY, FInterpCurveFloat& EulerRotationZ,
FInterpCurveFloat& ScaleX, FInterpCurveFloat& ScaleY, FInterpCurveFloat& ScaleZ,
FTransform& DefaultTransform) const
{
for (auto AnimNodeKvp: CurvesData)
{
const FFbxAnimNodeHandle& AnimNodeHandle = AnimNodeKvp.Value;
if (AnimNodeHandle.Name.Compare(NodeName) == 0)
{
bool bIsCamera = AnimNodeHandle.AttributeType == FbxNodeAttribute::eCamera;
bool bIsLight = AnimNodeHandle.AttributeType == FbxNodeAttribute::eLight;
FFbxAnimCurveHandle TransformCurves[9];
for (auto NodePropertyKvp: AnimNodeHandle.NodeProperties)
{
FFbxAnimPropertyHandle& AnimPropertyHandle = NodePropertyKvp.Value;
for (FFbxAnimCurveHandle& CurveHandle: AnimPropertyHandle.CurveHandles)
{
if (CurveHandle.CurveType != FFbxAnimCurveHandle::NotTransform)
{
TransformCurves[(int32)(CurveHandle.CurveType)] = CurveHandle;
}
}
}
#pragma warning(disable : 4996) // 'function' was declared deprecated
GetCurveData(TransformCurves[0], TranslationX, false);
GetCurveData(TransformCurves[1], TranslationY, true);
GetCurveData(TransformCurves[2], TranslationZ, false);
GetCurveData(TransformCurves[3], EulerRotationX, false);
GetCurveData(TransformCurves[4], EulerRotationY, true);
GetCurveData(TransformCurves[5], EulerRotationZ, true);
GetCurveData(TransformCurves[6], ScaleX, false);
GetCurveData(TransformCurves[7], ScaleY, false);
GetCurveData(TransformCurves[8], ScaleZ, false);
#pragma warning(default : 4996) // 'function' was declared deprecated
if (bIsCamera || bIsLight)
{
int32 CurvePointNum = FMath::Min3<int32>(EulerRotationX.Points.Num(), EulerRotationY.Points.Num(), EulerRotationZ.Points.Num());
// Once the individual Euler channels are imported, then convert the rotation into Unreal coords
for (int32 PointIndex = 0; PointIndex < CurvePointNum; ++PointIndex)
{
FInterpCurvePoint<float>& CurveKeyX = EulerRotationX.Points[PointIndex];
FInterpCurvePoint<float>& CurveKeyY = EulerRotationY.Points[PointIndex];
FInterpCurvePoint<float>& CurveKeyZ = EulerRotationZ.Points[PointIndex];
float Pitch = CurveKeyY.OutVal;
float Yaw = CurveKeyZ.OutVal;
float Roll = CurveKeyX.OutVal;
ConvertRotationToUnreal(Roll, Pitch, Yaw, bIsCamera, bIsLight);
CurveKeyX.OutVal = Roll;
CurveKeyY.OutVal = Pitch;
CurveKeyZ.OutVal = Yaw;
}
if (bIsCamera)
{
// The FInterpCurve code doesn't differentiate between angles and other data, so an interpolation from 179 to -179
// will cause the camera to rotate all the way around through 0 degrees. So here we make a second pass over the
// Euler track to convert the angles into a more interpolation-friendly format.
float CurrentAngleOffset[3] = {0.f, 0.f, 0.f};
for (int32 PointIndex = 1; PointIndex < CurvePointNum; ++PointIndex)
{
FInterpCurvePoint<float>& PrevCurveKeyX = EulerRotationX.Points[PointIndex - 1];
FInterpCurvePoint<float>& PrevCurveKeyY = EulerRotationY.Points[PointIndex - 1];
FInterpCurvePoint<float>& PrevCurveKeyZ = EulerRotationZ.Points[PointIndex - 1];
FInterpCurvePoint<float>& CurveKeyX = EulerRotationX.Points[PointIndex];
FInterpCurvePoint<float>& CurveKeyY = EulerRotationY.Points[PointIndex];
FInterpCurvePoint<float>& CurveKeyZ = EulerRotationZ.Points[PointIndex];
FVector PreviousOutVal = FVector(PrevCurveKeyX.OutVal, PrevCurveKeyY.OutVal, PrevCurveKeyZ.OutVal);
FVector CurrentOutVal = FVector(CurveKeyX.OutVal, CurveKeyY.OutVal, CurveKeyZ.OutVal);
for (int32 AxisIndex = 0; AxisIndex < 3; ++AxisIndex)
{
float DeltaAngle = (CurrentOutVal[AxisIndex] + CurrentAngleOffset[AxisIndex]) - PreviousOutVal[AxisIndex];
if (DeltaAngle >= 180)
{
CurrentAngleOffset[AxisIndex] -= 360;
}
else if (DeltaAngle <= -180)
{
CurrentAngleOffset[AxisIndex] += 360;
}
CurrentOutVal[AxisIndex] += CurrentAngleOffset[AxisIndex];
}
CurveKeyX.OutVal = CurrentOutVal[0];
CurveKeyY.OutVal = CurrentOutVal[1];
CurveKeyZ.OutVal = CurrentOutVal[2];
}
}
}
}
}
FbxNode* Node = GetNodeFromName(NodeName, Scene->GetRootNode());
if (Node)
{
DefaultTransform = TransformData[Node->GetUniqueID()];
}
}
void FFbxCurvesAPI::GetConvertedTransformCurveData(const FString& NodeName, FRichCurve& TranslationX, FRichCurve& TranslationY, FRichCurve& TranslationZ,
FRichCurve& EulerRotationX, FRichCurve& EulerRotationY, FRichCurve& EulerRotationZ,
FRichCurve& ScaleX, FRichCurve& ScaleY, FRichCurve& ScaleZ,
FTransform& DefaultTransform, bool bUseSequencerCurve, float UniformScale) const
{
for (TPair<uint64, FFbxAnimNodeHandle> AnimNodeKvp: CurvesData)
{
const FFbxAnimNodeHandle& AnimNodeHandle = AnimNodeKvp.Value;
if (AnimNodeHandle.Name.Compare(NodeName) == 0)
{
bool bIsCamera = AnimNodeHandle.AttributeType == FbxNodeAttribute::eCamera;
bool bIsLight = AnimNodeHandle.AttributeType == FbxNodeAttribute::eLight;
FFbxAnimCurveHandle TransformCurves[9];
for (auto NodePropertyKvp: AnimNodeHandle.NodeProperties)
{
FFbxAnimPropertyHandle& AnimPropertyHandle = NodePropertyKvp.Value;
for (FFbxAnimCurveHandle& CurveHandle: AnimPropertyHandle.CurveHandles)
{
if (CurveHandle.CurveType != FFbxAnimCurveHandle::NotTransform)
{
TransformCurves[(int32)(CurveHandle.CurveType)] = CurveHandle;
}
}
}
const bool bNegate = true;
if (bUseSequencerCurve)
{
GetCurveDataForSequencer(TransformCurves[0], TranslationX, !bNegate, UniformScale);
GetCurveDataForSequencer(TransformCurves[1], TranslationY, bNegate, UniformScale);
GetCurveDataForSequencer(TransformCurves[2], TranslationZ, !bNegate, UniformScale);
GetCurveDataForSequencer(TransformCurves[3], EulerRotationX, !bNegate);
GetCurveDataForSequencer(TransformCurves[4], EulerRotationY, bNegate);
GetCurveDataForSequencer(TransformCurves[5], EulerRotationZ, bNegate);
GetCurveDataForSequencer(TransformCurves[6], ScaleX, !bNegate, UniformScale);
GetCurveDataForSequencer(TransformCurves[7], ScaleY, !bNegate, UniformScale);
GetCurveDataForSequencer(TransformCurves[8], ScaleZ, !bNegate, UniformScale);
}
else
{
GetCurveData(TransformCurves[0], TranslationX, !bNegate, UniformScale);
GetCurveData(TransformCurves[1], TranslationY, bNegate, UniformScale);
GetCurveData(TransformCurves[2], TranslationZ, !bNegate, UniformScale);
GetCurveData(TransformCurves[3], EulerRotationX, !bNegate);
GetCurveData(TransformCurves[4], EulerRotationY, bNegate);
GetCurveData(TransformCurves[5], EulerRotationZ, bNegate);
GetCurveData(TransformCurves[6], ScaleX, !bNegate, UniformScale);
GetCurveData(TransformCurves[7], ScaleY, !bNegate, UniformScale);
GetCurveData(TransformCurves[8], ScaleZ, !bNegate, UniformScale);
}
if (bIsCamera || bIsLight)
{
{ // extra scope since we can't reset Key Iterators
// need to convert rotations to unreal space. Uses previous FInterpCurvePoint implementation that goes through the minimal number
// of curve keys and sets them together. Obviously if the keys are not at the same times exactly this won't work.
auto EulerRotXIt = EulerRotationX.GetKeyHandleIterator();
auto EulerRotYIt = EulerRotationY.GetKeyHandleIterator();
auto EulerRotZIt = EulerRotationZ.GetKeyHandleIterator();
while (EulerRotXIt && EulerRotYIt && EulerRotZIt)
{
float Pitch = EulerRotationY.GetKeyValue(*EulerRotYIt);
float Yaw = EulerRotationZ.GetKeyValue(*EulerRotZIt);
float Roll = EulerRotationX.GetKeyValue(*EulerRotXIt);
ConvertRotationToUnreal(Roll, Pitch, Yaw, bIsCamera, bIsLight);
EulerRotationX.SetKeyValue(*EulerRotXIt, Roll, false);
EulerRotationY.SetKeyValue(*EulerRotYIt, Pitch, false);
EulerRotationZ.SetKeyValue(*EulerRotZIt, Yaw, false);
++EulerRotXIt;
++EulerRotYIt;
++EulerRotZIt;
}
}
}
if (bIsCamera)
{
// The RichCurve code doesn't differentiate between angles and other data, so an interpolation from 179 to -179
// will cause the camera to rotate all the way around through 0 degrees. So here we make a second pass over the
// Euler track to convert the angles into a more interpolation-friendly format.
float CurrentAngleOffset[3] = {0.f, 0.f, 0.f};
auto EulerRotXIt = EulerRotationX.GetKeyHandleIterator();
auto EulerRotYIt = EulerRotationY.GetKeyHandleIterator();
auto EulerRotZIt = EulerRotationZ.GetKeyHandleIterator();
FVector PreviousOutVal;
FVector CurrentOutVal;
bool bFirst = true;
while (EulerRotXIt && EulerRotYIt && EulerRotZIt)
{
float X = EulerRotationX.GetKeyValue(*EulerRotXIt);
;
float Y = EulerRotationY.GetKeyValue(*EulerRotYIt);
float Z = EulerRotationZ.GetKeyValue(*EulerRotZIt);
if (!bFirst)
{
PreviousOutVal = CurrentOutVal;
CurrentOutVal = FVector(X, Y, Z);
}
else
{
CurrentOutVal = FVector(X, Y, Z);
bFirst = false;
}
for (int32 AxisIndex = 0; AxisIndex < 3; ++AxisIndex)
{
float DeltaAngle = (CurrentOutVal[AxisIndex] + CurrentAngleOffset[AxisIndex]) - PreviousOutVal[AxisIndex];
if (DeltaAngle >= 180)
{
CurrentAngleOffset[AxisIndex] -= 360;
}
else if (DeltaAngle <= -180)
{
CurrentAngleOffset[AxisIndex] += 360;
}
CurrentOutVal[AxisIndex] += CurrentAngleOffset[AxisIndex];
}
EulerRotationX.SetKeyValue(*EulerRotXIt, CurrentOutVal.X, false);
EulerRotationY.SetKeyValue(*EulerRotYIt, CurrentOutVal.Y, false);
EulerRotationZ.SetKeyValue(*EulerRotZIt, CurrentOutVal.Z, false);
++EulerRotXIt;
++EulerRotYIt;
++EulerRotZIt;
}
}
}
}
FbxNode* Node = GetNodeFromName(NodeName, Scene->GetRootNode());
if (Node)
{
DefaultTransform = TransformData[Node->GetUniqueID()];
DefaultTransform.SetLocation(DefaultTransform.GetLocation() * UniformScale);
DefaultTransform.SetScale3D(DefaultTransform.GetScale3D() * UniformScale);
}
}
//////////////////////////////////////////////////////////////////////////
// FFbxImporter: Curve Extraction Implementation
//
void FFbxImporter::PopulateAnimatedCurveData(FFbxCurvesAPI& CurvesAPI)
{
if (Scene == nullptr)
return;
// merge animation layer at first
FbxAnimStack* AnimStack = Scene->GetMember<FbxAnimStack>(0);
if (!AnimStack)
return;
FbxAnimLayer* AnimLayer = AnimStack->GetMember<FbxAnimLayer>(0);
if (AnimLayer == NULL)
return;
FbxNode* RootNode = Scene->GetRootNode();
CurvesAPI.Scene = Scene;
LoadNodeKeyframeAnimationRecursively(CurvesAPI, RootNode);
}
void FFbxImporter::LoadNodeKeyframeAnimationRecursively(FFbxCurvesAPI& CurvesAPI, FbxNode* NodeToQuery)
{
LoadNodeKeyframeAnimation(NodeToQuery, CurvesAPI);
int32 NodeCount = NodeToQuery->GetChildCount();
for (int32 NodeIndex = 0; NodeIndex < NodeCount; ++NodeIndex)
{
FbxNode* ChildNode = NodeToQuery->GetChild(NodeIndex);
LoadNodeKeyframeAnimationRecursively(CurvesAPI, ChildNode);
}
}
void FFbxImporter::LoadNodeKeyframeAnimation(FbxNode* NodeToQuery, FFbxCurvesAPI& CurvesAPI)
{
SetupTransformForNode(NodeToQuery);
int NumAnimations = Scene->GetSrcObjectCount<FbxAnimStack>();
FFbxAnimNodeHandle AnimNodeHandle;
AnimNodeHandle.Name = UTF8_TO_TCHAR(NodeToQuery->GetName());
AnimNodeHandle.UniqueId = NodeToQuery->GetUniqueID();
FbxNodeAttribute* NodeAttribute = NodeToQuery->GetNodeAttribute();
if (NodeAttribute != nullptr)
{
AnimNodeHandle.AttributeType = NodeAttribute->GetAttributeType();
AnimNodeHandle.AttributeUniqueId = NodeAttribute->GetUniqueID();
}
else
{
AnimNodeHandle.AttributeType = FbxNodeAttribute::eUnknown;
AnimNodeHandle.AttributeUniqueId = 0xFFFFFFFFFFFFFFFF;
}
bool IsNodeAnimated = false;
for (int AnimationIndex = 0; AnimationIndex < NumAnimations; AnimationIndex++)
{
FbxAnimStack* animStack = (FbxAnimStack*)Scene->GetSrcObject<FbxAnimStack>(AnimationIndex);
FbxAnimEvaluator* animEvaluator = Scene->GetAnimationEvaluator();
int numLayers = animStack->GetMemberCount();
for (int layerIndex = 0; layerIndex < numLayers; layerIndex++)
{
FbxAnimLayer* AnimLayer = (FbxAnimLayer*)animStack->GetMember(layerIndex);
// Display curves specific to properties
FbxObject* ObjectToQuery = (FbxObject*)NodeToQuery;
FbxAnimCurve* TransformCurves[9];
TransformCurves[0] = NodeToQuery->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, false);
TransformCurves[1] = NodeToQuery->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, false);
TransformCurves[2] = NodeToQuery->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, false);
TransformCurves[3] = NodeToQuery->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, false);
TransformCurves[4] = NodeToQuery->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, false);
TransformCurves[5] = NodeToQuery->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, false);
TransformCurves[6] = NodeToQuery->LclScaling.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, false);
TransformCurves[7] = NodeToQuery->LclScaling.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, false);
TransformCurves[8] = NodeToQuery->LclScaling.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, false);
bool IsNodeProperty = true;
FbxProperty CurrentProperty = ObjectToQuery->GetFirstProperty();
while (CurrentProperty.IsValid())
{
FbxAnimCurve* AnimCurve = nullptr;
TArray<TArray<int32>> KeyFrameNumber;
TArray<TArray<float>> AnimationTimeSecond;
TArray<TArray<FString>> CurveName;
TArray<TArray<uint64>> CurveUniqueId;
TArray<TArray<FbxAnimCurve*>> CurveData;
bool PropertyHasCurve = false;
FbxAnimCurveNode* CurveNode = CurrentProperty.GetCurveNode(AnimLayer);
if (CurveNode != nullptr)
{
FbxDataType DataType = CurrentProperty.GetPropertyDataType();
if (DataType.GetType() == eFbxBool || DataType.GetType() == eFbxDouble || DataType.GetType() == eFbxFloat || DataType.GetType() == eFbxInt || DataType.GetType() == eFbxEnum)
{
TArray<int32> CompositeKeyFrameNumber;
TArray<float> CompositeAnimationTimeSecond;
TArray<FString> CompositeCurveName;
TArray<uint64> CompositeCurveUniqueId;
TArray<FbxAnimCurve*> CompositeCurveData;
for (int32 c = 0; c < CurveNode->GetCurveCount(0U); c++)
{
AnimCurve = CurveNode->GetCurve(0U, c);
if (AnimCurve != nullptr)
{
int32 KeyNumber = AnimCurve->KeyGetCount();
CompositeKeyFrameNumber.Add(KeyNumber);
FbxTime frameTime = AnimCurve->KeyGetTime(KeyNumber - 1);
CompositeAnimationTimeSecond.Add((float)frameTime.GetSecondDouble());
PropertyHasCurve = true;
CompositeCurveName.Add(UTF8_TO_TCHAR(AnimCurve->GetName()));
CompositeCurveUniqueId.Add(AnimCurve->GetUniqueID());
CompositeCurveData.Add(AnimCurve);
}
}
KeyFrameNumber.Add(CompositeKeyFrameNumber);
AnimationTimeSecond.Add(CompositeAnimationTimeSecond);
CurveName.Add(CompositeCurveName);
CurveUniqueId.Add(CompositeCurveUniqueId);
CurveData.Add(CompositeCurveData);
}
else if (DataType.GetType() == eFbxDouble3 || DataType.GetType() == eFbxDouble4 || DataType.Is(FbxColor3DT) || DataType.Is(FbxColor4DT))
{
// Set the channel number to 3 or 4
uint32 ChannelNumber = (DataType.GetType() == eFbxDouble3 || DataType.Is(FbxColor3DT)) ? 3 : 4;
for (uint32 ChannelIndex = 0; ChannelIndex < ChannelNumber; ++ChannelIndex)
{
TArray<int32> CompositeKeyFrameNumber;
TArray<float> CompositeAnimationTimeSecond;
TArray<FString> CompositeCurveName;
TArray<uint64> CompositeCurveUniqueId;
TArray<FbxAnimCurve*> CompositeCurveData;
TArray<EFbxType> CompositeCurveType;
for (int32 c = 0; c < CurveNode->GetCurveCount(ChannelIndex); c++)
{
AnimCurve = CurveNode->GetCurve(ChannelIndex, c);
if (AnimCurve)
{
int32 KeyNumber = AnimCurve->KeyGetCount();
CompositeKeyFrameNumber.Add(KeyNumber);
FbxTime frameTime = AnimCurve->KeyGetTime(KeyNumber - 1);
CompositeAnimationTimeSecond.Add((float)frameTime.GetSecondDouble());
PropertyHasCurve = true;
CompositeCurveName.Add(UTF8_TO_TCHAR(AnimCurve->GetName()));
CompositeCurveUniqueId.Add(AnimCurve->GetUniqueID());
CompositeCurveData.Add(AnimCurve);
}
}
KeyFrameNumber.Add(CompositeKeyFrameNumber);
AnimationTimeSecond.Add(CompositeAnimationTimeSecond);
CurveName.Add(CompositeCurveName);
CurveUniqueId.Add(CompositeCurveUniqueId);
CurveData.Add(CompositeCurveData);
}
}
if (PropertyHasCurve == true)
{
IsNodeAnimated = true;
FFbxAnimPropertyHandle PropertyHandle;
PropertyHandle.DataType = DataType.GetType();
PropertyHandle.Name = UTF8_TO_TCHAR(CurrentProperty.GetName());
for (int32 ChannelIndex = 0; ChannelIndex < KeyFrameNumber.Num(); ++ChannelIndex)
{
for (int32 CompositeIndex = 0; CompositeIndex < KeyFrameNumber[ChannelIndex].Num(); ++CompositeIndex)
{
FFbxAnimCurveHandle CurveHandle;
CurveHandle.Name = CurveName[ChannelIndex][CompositeIndex];
CurveHandle.UniqueId = CurveUniqueId[ChannelIndex][CompositeIndex];
CurveHandle.ChannelIndex = ChannelIndex;
CurveHandle.CompositeIndex = CompositeIndex;
CurveHandle.KeyNumber = KeyFrameNumber[ChannelIndex][CompositeIndex];
CurveHandle.AnimationTimeSecond = AnimationTimeSecond[ChannelIndex][CompositeIndex];
CurveHandle.AnimCurve = CurveData[ChannelIndex][CompositeIndex];
for (int TransformIndex = 0; TransformIndex < 9; ++TransformIndex)
{
if (TransformCurves[TransformIndex] != nullptr && TransformCurves[TransformIndex]->GetUniqueID() == CurveHandle.AnimCurve->GetUniqueID())
{
CurveHandle.CurveType = (FFbxAnimCurveHandle::CurveTypeDescription)(TransformIndex);
break;
}
}
PropertyHandle.CurveHandles.Add(CurveHandle);
}
}
if (IsNodeProperty == true)
{
AnimNodeHandle.NodeProperties.Add(PropertyHandle.Name, PropertyHandle);
}
else
{
AnimNodeHandle.AttributeProperties.Add(PropertyHandle.Name, PropertyHandle);
}
}
}
CurrentProperty = ObjectToQuery->GetNextProperty(CurrentProperty);
if (!CurrentProperty.IsValid() && ObjectToQuery->GetUniqueID() == NodeToQuery->GetUniqueID())
{
if (NodeAttribute != nullptr)
{
switch (NodeAttribute->GetAttributeType())
{
case FbxNodeAttribute::eCamera: {
FbxCamera* CameraAttribute = (FbxCamera*)NodeAttribute;
CurrentProperty = CameraAttribute->GetFirstProperty();
}
break;
case FbxNodeAttribute::eLight: {
FbxLight* LightAttribute = (FbxLight*)NodeAttribute;
CurrentProperty = LightAttribute->GetFirstProperty();
}
break;
}
ObjectToQuery = NodeAttribute;
IsNodeProperty = false;
}
}
} // while
}
}
if (IsNodeAnimated)
{
CurvesAPI.CurvesData.Add(AnimNodeHandle.UniqueId, AnimNodeHandle);
}
// Store default transform values in TransformData
bool bIsCamera = AnimNodeHandle.AttributeType == FbxNodeAttribute::eCamera;
bool bIsLight = AnimNodeHandle.AttributeType == FbxNodeAttribute::eLight;
FTransform Transform;
FbxVector4 LclTranslation = NodeToQuery->LclTranslation.EvaluateValue(0.f);
FbxVector4 LclRotation = NodeToQuery->LclRotation.EvaluateValue(0.f);
FbxVector4 LclScaling = NodeToQuery->LclScaling.EvaluateValue(0.f);
float EulerRotationX = LclRotation[0];
float EulerRotationY = -LclRotation[1];
float EulerRotationZ = -LclRotation[2];
float Pitch = EulerRotationY;
float Yaw = EulerRotationZ;
float Roll = EulerRotationX;
ConvertRotationToUnreal(Roll, Pitch, Yaw, bIsCamera, bIsLight);
Transform.SetLocation(FVector(LclTranslation[0], -LclTranslation[1], LclTranslation[2]));
Transform.SetRotation(FRotator(Pitch, Yaw, Roll).Quaternion());
Transform.SetScale3D(FVector(LclScaling[0], LclScaling[1], LclScaling[2]));
CurvesAPI.TransformData.Add(AnimNodeHandle.UniqueId, Transform);
}
// 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
void FFbxImporter::SetupTransformForNode(FbxNode* Node)
{
// 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->ResetPivotSetAndConvertAnimation(FbxTime::GetFrameRate(Scene->GetGlobalSettings().GetTimeMode()));
}
} // namespace UnFbx