2023-12-11 12:14:14 +08:00
|
|
|
|
---
|
|
|
|
|
title: FBXAnimation导入逻辑
|
|
|
|
|
date: 2023-12-11 11:28:36
|
|
|
|
|
excerpt:
|
|
|
|
|
tags:
|
|
|
|
|
rating: ⭐
|
|
|
|
|
---
|
2023-12-11 16:16:50 +08:00
|
|
|
|
# BVHImport插件中的案例
|
|
|
|
|
```c++
|
|
|
|
|
UObject* UBVHImportFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& FileName, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled)
|
|
|
|
|
{
|
|
|
|
|
if (!shouldImport) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
bool fileSuccess = BVHFile.ImportData(TCHAR_TO_ANSI(*FileName));
|
|
|
|
|
|
|
|
|
|
const bool bIsUnattended = (IsAutomatedImport()
|
|
|
|
|
|| FApp::IsUnattended()
|
|
|
|
|
|| IsRunningCommandlet()
|
|
|
|
|
|| GIsRunningUnattendedScript);
|
|
|
|
|
|
|
|
|
|
// Check if it's a re-import
|
|
|
|
|
if (InParent != nullptr)
|
|
|
|
|
{
|
|
|
|
|
UObject* ExistingObject = StaticFindObject(UObject::StaticClass(), InParent, *InName.ToString());
|
|
|
|
|
if (ExistingObject)
|
|
|
|
|
{
|
|
|
|
|
//reimport逻辑略
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fileSuccess)
|
|
|
|
|
{
|
|
|
|
|
UAnimSequence* AnimSequence = NewObject<UAnimSequence>(InParent, InName, Flags & ~EObjectFlags::RF_Transactional);
|
|
|
|
|
if (Skeleton == NULL) {
|
|
|
|
|
//创建骨骼资产略
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
AnimSequence->SetSkeleton(Skeleton);//Skeleton bvhSkel
|
|
|
|
|
AnimSequence->SetPreviewMesh(PreviewMesh);
|
|
|
|
|
}
|
|
|
|
|
ExtractAnimDataFromBVHFile(AnimSequence);
|
|
|
|
|
return AnimSequence;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```c++
|
|
|
|
|
void UBVHImportFactory::ExtractAnimDataFromBVHFile(UAnimSequence* AnimSequence) {
|
|
|
|
|
if (AnimSequence)
|
|
|
|
|
{
|
|
|
|
|
// create animation
|
|
|
|
|
IAnimationDataController& Controller = AnimSequence->GetController();
|
|
|
|
|
Controller.OpenBracket(LOCTEXT("ImportBVHAnimation", "Importing BVH Animation"));
|
|
|
|
|
Controller.ResetModel();
|
|
|
|
|
|
|
|
|
|
// Write animation data into animation sequence.
|
|
|
|
|
// Extract transform of hip to create root motion.
|
|
|
|
|
const FReferenceSkeleton& RefSkeleton = AnimSequence->GetSkeleton()->GetReferenceSkeleton();
|
|
|
|
|
const FName RootName = RefSkeleton.GetBoneName(0);
|
|
|
|
|
const int32 NumOfKeys = BVHFile.Root->FrameOffset.Num();
|
|
|
|
|
|
|
|
|
|
AnimSequence->ImportFileFramerate = (float)BVHFile.Header.DataRate;
|
|
|
|
|
AnimSequence->ImportResampleFramerate = BVHFile.Header.DataRate;
|
|
|
|
|
|
|
|
|
|
Controller.SetPlayLength(float(NumOfKeys - 1) / (float)BVHFile.Header.DataRate);
|
|
|
|
|
Controller.SetFrameRate(FFrameRate(BVHFile.Header.DataRate, 1));
|
|
|
|
|
|
|
|
|
|
RecursiveReadKeysFromNode(Controller, BVHFile.Root.Get());
|
|
|
|
|
|
|
|
|
|
Controller.NotifyPopulated();
|
|
|
|
|
Controller.CloseBracket();
|
|
|
|
|
|
|
|
|
|
AnimSequence->AdditiveAnimType = EAdditiveAnimationType::AAT_None;
|
|
|
|
|
AnimSequence->PostEditChange();
|
|
|
|
|
FAssetRegistryModule::AssetCreated(AnimSequence);
|
|
|
|
|
AnimSequence->MarkPackageDirty();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```c++
|
|
|
|
|
void UBVHImportFactory::RecursiveReadKeysFromNode(IAnimationDataController& Controller, FNode* Node)
|
|
|
|
|
{
|
|
|
|
|
if (Node)
|
|
|
|
|
{
|
|
|
|
|
Controller.AddBoneTrack(Node->Name,false);
|
|
|
|
|
Controller.SetBoneTrackKeys(Node->Name, Node->FrameOffset, Node->FrameQuat, Node->FrameScale,false);
|
|
|
|
|
if (Node->Children.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
for (NodePtr Child : Node->Children)
|
|
|
|
|
{
|
|
|
|
|
RecursiveReadKeysFromNode(Controller, Child.Get());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2023-12-11 16:56:38 +08:00
|
|
|
|
# UE5中使用FBXSDK导入动画逻辑
|
|
|
|
|
- UAnimSequence * UEditorEngine::ImportFbxAnimation
|
2023-12-11 19:15:43 +08:00
|
|
|
|
- UAnimSequence * UnFbx::FFbxImporter::ImportAnimations
|
|
|
|
|
|
|
|
|
|
UE5中FBXSDK相关函数调用方式:
|
|
|
|
|
- bool FFbxImporter::OpenFile(FString Filename)
|
|
|
|
|
- bool FFbxImporter::ImportFile(FString Filename, bool bPreventMaterialNameClash /*=false*/)
|
|
|
|
|
|
|
|
|
|
# 参考
|
|
|
|
|
1. Interchange\\Runtime\\Source\\Parsers
|
|
|
|
|
1. InterchangeFbxParser.Build.cs
|
2023-12-20 17:12:32 +08:00
|
|
|
|
2. FbxInclude.h:FBXSDK头文件包含问题。
|
|
|
|
|
|
|
|
|
|
# UE5中使用FBXSDK导出动画逻辑
|
|
|
|
|
1. FFbxExporter::ExportSkeletalMeshToFbx => FFbxExporter::ExportAnimSequence => FFbxExporter::ExportAnimSequenceToFbx
|
|
|
|
|
2. FFbxExporter::CorrectAnimTrackInterpolation
|
|
|
|
|
|
|
|
|
|
直接导出会有问题,所以UE在这里做了一步Correct:
|
|
|
|
|
```c++
|
|
|
|
|
// The curve code doesn't differentiate between angles and other data, so an interpolation from 179 to -179
|
|
|
|
|
// will cause the bone to rotate all the way around through 0 degrees. So here we make a second pass over the
|
|
|
|
|
// rotation tracks to convert the angles into a more interpolation-friendly format.
|
|
|
|
|
FFbxExporter::CorrectAnimTrackInterpolation()
|
|
|
|
|
{
|
|
|
|
|
void FFbxExporter::CorrectAnimTrackInterpolation( TArray<FbxNode*>& BoneNodes, FbxAnimLayer* InAnimLayer )
|
|
|
|
|
{
|
|
|
|
|
// Add the animation data to the bone nodes
|
|
|
|
|
for(int32 BoneIndex = 0; BoneIndex < BoneNodes.Num(); ++BoneIndex)
|
|
|
|
|
{
|
|
|
|
|
FbxNode* CurrentBoneNode = BoneNodes[BoneIndex];
|
|
|
|
|
|
|
|
|
|
// Fetch the AnimCurves
|
|
|
|
|
FbxAnimCurve* Curves[3];
|
|
|
|
|
Curves[0] = CurrentBoneNode->LclRotation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true);
|
|
|
|
|
Curves[1] = CurrentBoneNode->LclRotation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true);
|
|
|
|
|
Curves[2] = CurrentBoneNode->LclRotation.GetCurve(InAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true);
|
|
|
|
|
|
|
|
|
|
for(int32 CurveIndex = 0; CurveIndex < 3; ++CurveIndex)
|
|
|
|
|
{
|
|
|
|
|
FbxAnimCurve* CurrentCurve = Curves[CurveIndex];
|
|
|
|
|
CurrentCurve->KeyModifyBegin();
|
|
|
|
|
|
|
|
|
|
float CurrentAngleOffset = 0.f;
|
|
|
|
|
for(int32 KeyIndex = 1; KeyIndex < CurrentCurve->KeyGetCount(); ++KeyIndex)
|
|
|
|
|
{
|
|
|
|
|
float PreviousOutVal = CurrentCurve->KeyGetValue( KeyIndex-1 );
|
|
|
|
|
float CurrentOutVal = CurrentCurve->KeyGetValue( KeyIndex );
|
|
|
|
|
|
|
|
|
|
float DeltaAngle = (CurrentOutVal + CurrentAngleOffset) - PreviousOutVal;
|
|
|
|
|
|
|
|
|
|
if(DeltaAngle >= 180)
|
|
|
|
|
{
|
|
|
|
|
CurrentAngleOffset -= 360;
|
|
|
|
|
}
|
|
|
|
|
else if(DeltaAngle <= -180)
|
|
|
|
|
{
|
|
|
|
|
CurrentAngleOffset += 360;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CurrentOutVal += CurrentAngleOffset;
|
|
|
|
|
|
|
|
|
|
CurrentCurve->KeySetValue(KeyIndex, CurrentOutVal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CurrentCurve->KeyModifyEnd();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2024-01-11 17:58:35 +08:00
|
|
|
|
|
|
|
|
|
## 导入Curve相关逻辑
|
2024-01-11 18:35:22 +08:00
|
|
|
|
UnFbx::FFbxImporter::ImportCurveToAnimSequence() => UnFbx::FFbxImporter::ImportCurve()
|