void FAnimationRuntime::ExcludeBonesWithNoParents(const TArray<int32> & BoneIndices, const FReferenceSkeleton& RefSkeleton, TArray<int32> & FilteredRequiredBones) { // Filter list, we only want bones that have their parents present in this array. FilteredRequiredBones.Empty(BoneIndices.Num()); for (int32 Index=0; Index<BoneIndices.Num(); Index++) { const int32& BoneIndex = BoneIndices[Index]; // Always add root bone. if( BoneIndex == 0 ) { FilteredRequiredBones.Add(BoneIndex); } else { const int32 ParentBoneIndex = RefSkeleton.GetParentIndex(BoneIndex); if( FilteredRequiredBones.Contains(ParentBoneIndex) ) { FilteredRequiredBones.Add(BoneIndex); } else { UE_LOG(LogAnimation, Warning, TEXT("ExcludeBonesWithNoParents: Filtering out bone (%s) since parent (%s) is missing"), *RefSkeleton.GetBoneName(BoneIndex).ToString(), *RefSkeleton.GetBoneName(ParentBoneIndex).ToString()); } } } }
/** * Process and fill in the mesh ref skeleton bone hierarchy using the raw binary import data * * @param RefSkeleton - [out] reference skeleton hierarchy to update * @param SkeletalDepth - [out] depth of the reference skeleton hierarchy * @param ImportData - raw binary import data to process * @return true if the operation completed successfully */ bool ProcessImportMeshSkeleton(FReferenceSkeleton& RefSkeleton, int32& SkeletalDepth, FSkeletalMeshImportData& ImportData) { TArray <VBone>& RefBonesBinary = ImportData.RefBonesBinary; // Setup skeletal hierarchy + names structure. RefSkeleton.Empty(); // Digest bones to the serializable format. for( int32 b=0; b<RefBonesBinary.Num(); b++ ) { const VBone & BinaryBone = RefBonesBinary[ b ]; const FString BoneName = FSkeletalMeshImportData::FixupBoneName( BinaryBone.Name ); const FMeshBoneInfo BoneInfo(FName(*BoneName, FNAME_Add, true), BinaryBone.ParentIndex); const FTransform BoneTransform(BinaryBone.BonePos.Orientation, BinaryBone.BonePos.Position, FVector(1.f)); if(RefSkeleton.FindBoneIndex(BoneInfo.Name) != INDEX_NONE) { UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("SkeletonHasDuplicateBones", "Skeleton has non-unique bone names.\nBone named '{0}' encountered more than once."), FText::FromName(BoneInfo.Name)))); return false; } RefSkeleton.Add(BoneInfo, BoneTransform); } // Add hierarchy index to each bone and detect max depth. SkeletalDepth = 0; TArray<int32> SkeletalDepths; SkeletalDepths.Empty( RefBonesBinary.Num() ); SkeletalDepths.AddZeroed( RefBonesBinary.Num() ); for( int32 b=0; b < RefSkeleton.GetNum(); b++ ) { int32 Parent = RefSkeleton.GetParentIndex(b); int32 Depth = 1.0f; SkeletalDepths[b] = 1.0f; if( Parent != INDEX_NONE ) { Depth += SkeletalDepths[Parent]; } if( SkeletalDepth < Depth ) { SkeletalDepth = Depth; } SkeletalDepths[b] = Depth; } return true; }
void OutputAnimationTransformDebugData(TArray<AnimationTransformDebug::FAnimationTransformDebugData> &TransformDebugData, int32 TotalNumKeys, const FReferenceSkeleton& RefSkeleton) { bool bShouldOutputToMessageLog = true; for(int32 Key=0; Key<TotalNumKeys; ++Key) { // go through all bones and find for(int32 BoneIndex=0; BoneIndex<TransformDebugData.Num(); ++BoneIndex) { FAnimationTransformDebugData& Data = TransformDebugData[BoneIndex]; int32 ParentIndex = RefSkeleton.GetParentIndex(Data.BoneIndex); int32 ParentTransformDebugDataIndex = 0; check(Data.RecalculatedLocalTransform.Num() == TotalNumKeys); check(Data.SourceGlobalTransform.Num() == TotalNumKeys); check(Data.SourceParentGlobalTransform.Num() == TotalNumKeys); for(; ParentTransformDebugDataIndex<BoneIndex; ++ParentTransformDebugDataIndex) { if(ParentIndex == TransformDebugData[ParentTransformDebugDataIndex].BoneIndex) { FTransform ParentTransform = TransformDebugData[ParentTransformDebugDataIndex].RecalculatedLocalTransform[Key] * TransformDebugData[ParentTransformDebugDataIndex].RecalculatedParentTransform[Key]; Data.RecalculatedParentTransform.Add(ParentTransform); break; } } // did not find Parent if(ParentTransformDebugDataIndex == BoneIndex) { Data.RecalculatedParentTransform.Add(FTransform::Identity); } check(Data.RecalculatedParentTransform.Num() == Key+1); FTransform GlobalTransform = Data.RecalculatedLocalTransform[Key] * Data.RecalculatedParentTransform[Key]; // makes more generous on the threshold. if(GlobalTransform.Equals(Data.SourceGlobalTransform[Key], 0.1f) == false) { // so that we don't spawm with this message if(bShouldOutputToMessageLog) { UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); // now print information - it doesn't match well, find out what it is FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("FBXImport_TransformError", "Imported bone transform is different from original. Please check Output Log to see detail of error. "), FText::FromName(Data.BoneName), FText::AsNumber(Data.BoneIndex), FText::FromString(Data.SourceGlobalTransform[Key].ToString()), FText::FromString(GlobalTransform.ToString()))), FFbxErrors::Animation_TransformError); bShouldOutputToMessageLog = false; } // now print information - it doesn't match well, find out what it is UE_LOG(LogFbx, Warning, TEXT("IMPORT TRANSFORM ERROR : Bone (%s:%d) \r\nSource Global Transform (%s), \r\nConverted Global Transform (%s)"), *Data.BoneName.ToString(), Data.BoneIndex, *Data.SourceGlobalTransform[Key].ToString(), *GlobalTransform.ToString()); } } } }
void FBoneHierarchyBuilder::CopyToRefSkeleton(FReferenceSkeleton& RefSkeleton) { for (int32 SourceBoneIndex = 0; SourceBoneIndex < AllBones.Num(); ++SourceBoneIndex) { const FName BoneName(AllBones[SourceBoneIndex]); const int32 ParentIndex(ParentIndices[SourceBoneIndex]); const FMeshBoneInfo BoneInfo(BoneName, BoneName.ToString(), ParentIndex); const FTransform& Transform = Transforms[SourceBoneIndex]; RefSkeleton.Add(BoneInfo, Transform); } }
/** Check that root bone is the same, and that any bones that are common have the correct parent. */ bool SkeletonsAreCompatible( const FReferenceSkeleton& NewSkel, const FReferenceSkeleton& ExistSkel ) { if(NewSkel.GetBoneName(0) != ExistSkel.GetBoneName(0)) { UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("MeshHasDifferentRoot", "Root Bone is '{0}' instead of '{1}'.\nDiscarding existing LODs."), FText::FromName(NewSkel.GetBoneName(0)), FText::FromName(ExistSkel.GetBoneName(0))))); return false; } for(int32 i=1; i<NewSkel.GetNum(); i++) { // See if bone is in both skeletons. int32 NewBoneIndex = i; FName NewBoneName = NewSkel.GetBoneName(NewBoneIndex); int32 BBoneIndex = ExistSkel.FindBoneIndex(NewBoneName); // If it is, check parents are the same. if(BBoneIndex != INDEX_NONE) { FName NewParentName = NewSkel.GetBoneName( NewSkel.GetParentIndex(NewBoneIndex) ); FName ExistParentName = ExistSkel.GetBoneName( ExistSkel.GetParentIndex(BBoneIndex) ); if(NewParentName != ExistParentName) { UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("MeshHasDifferentRoot", "Root Bone is '{0}' instead of '{1}'.\nDiscarding existing LODs."), FText::FromName(NewBoneName), FText::FromName(NewParentName)))); return false; } } } return true; }