/** 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;
}
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());
}
}
}
}
//.........这里部分代码省略.........
bool FoundMatch = false;
for( int32 NewTriIdx=0;NewTriIdx<NewStripIndices.Num();NewTriIdx+=3 )
{
if( (SavedVertices[OldStripIndices[OldTriIdx+0]] - NewVertices[NewStripIndices[NewTriIdx+0]].Position).SizeSquared() < KINDA_SMALL_NUMBER &&
(SavedVertices[OldStripIndices[OldTriIdx+1]] - NewVertices[NewStripIndices[NewTriIdx+1]].Position).SizeSquared() < KINDA_SMALL_NUMBER &&
(SavedVertices[OldStripIndices[OldTriIdx+2]] - NewVertices[NewStripIndices[NewTriIdx+2]].Position).SizeSquared() < KINDA_SMALL_NUMBER )
{
// Found a triangle match. Remove the triangle from the new list and try to match the next old triangle.
NewStripIndices.RemoveAt(NewTriIdx,3);
FoundMatch = true;
break;
}
}
// If we didn't find a match for this old triangle, the whole strip doesn't match.
if( !FoundMatch )
{
break;
}
}
if( NewStripIndices.Num() == 0 )
{
// strip completely matched
MatchingNewStrip = NsIdx;
}
}
if( MatchingNewStrip != INDEX_NONE )
{
NewSortedStrips.Add( NewStrips[MatchingNewStrip] );
NewStrips.RemoveAt(MatchingNewStrip);
}
else
{
NumMismatchedStrips++;
}
}
if( IndexCopy == 0 )
{
if( 100 * NumMismatchedStrips / OldStrips[0].Num() > 50 )
{
// If less than 50% of this section's strips match, we assume this is not the correct section.
break;
}
// This section matches!
bFoundMatchingSection = true;
// Warn the user if we couldn't match things up.
if( NumMismatchedStrips )
{
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("RestoreSortingMismatchedStripsForSection", "While restoring \"{0}\" sort order for section {1}, {2} of {3} strips could not be matched to the new data."),
FText::FromString(TriangleSortOptionToString((ETriangleSortOption)SavedSortOption)),
FText::AsNumber(SavedSectionIdx),
FText::AsNumber(NumMismatchedStrips),
FText::AsNumber(OldStrips[0].Num()))));
}
// Restore the settings saved in the LODInfo (used for the UI)
FTriangleSortSettings& TriangleSortSettings = LODInfo.TriangleSortSettings[SectionIndex];
TriangleSortSettings.TriangleSorting = SavedSortOption;
TriangleSortSettings.CustomLeftRightAxis = SavedCustomLeftRightAxis;
TriangleSortSettings.CustomLeftRightBoneName = SavedCustomLeftRightBoneName;
// Restore the sorting mode. For TRISORT_CustomLeftRight, this will also make the second copy of the index data.
FVector SortCenter;
bool bHaveSortCenter = NewSkelMesh->GetSortCenterPoint(SortCenter);
LODModel.SortTriangles(SortCenter, bHaveSortCenter, SectionIndex, (ETriangleSortOption)SavedSortOption);
}
// Append any strips we couldn't match to the end
NewSortedStrips += NewStrips;
// Export the strips out to the index buffer in order
TArray<uint32> Indexes;
LODModel.MultiSizeIndexContainer.GetIndexBuffer( Indexes );
uint32* NewIndices = Indexes.GetData() + (Section.BaseIndex + Section.NumTriangles*3*IndexCopy);
for( int32 StripIdx=0;StripIdx<NewSortedStrips.Num();StripIdx++ )
{
FMemory::Memcpy( NewIndices, &NewSortedStrips[StripIdx][0], NewSortedStrips[StripIdx].Num() * sizeof(uint32) );
// Cache-optimize the triangle order inside the final strip
CacheOptimizeSortStrip( NewIndices, NewSortedStrips[StripIdx].Num() );
NewIndices += NewSortedStrips[StripIdx].Num();
}
LODModel.MultiSizeIndexContainer.CopyIndexBuffer( Indexes );
}
}
if( !bFoundMatchingSection )
{
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("FailedRestoreSortingNoSectionMatch", "Unable to restore triangle sort setting \"{0}\" for section number {1} in the old mesh, as a matching section could not be found in the new mesh. The custom sorting information has been lost."),
FText::FromString(TriangleSortOptionToString((ETriangleSortOption)SavedSortOption)), FText::AsNumber(SavedSectionIdx))));
}
}
/**
* Process and update the vertex Influences using the raw binary import data
*
* @param ImportData - raw binary import data to process
*/
void ProcessImportMeshInfluences(FSkeletalMeshImportData& ImportData)
{
TArray <FVector>& Points = ImportData.Points;
TArray <VVertex>& Wedges = ImportData.Wedges;
TArray <VRawBoneInfluence>& Influences = ImportData.Influences;
// Sort influences by vertex index.
struct FCompareVertexIndex
{
bool operator()( const VRawBoneInfluence& A, const VRawBoneInfluence& B ) const
{
if ( A.VertexIndex > B.VertexIndex ) return false;
else if ( A.VertexIndex < B.VertexIndex ) return true;
else if ( A.Weight < B.Weight ) return false;
else if ( A.Weight > B.Weight ) return true;
else if ( A.BoneIndex > B.BoneIndex ) return false;
else if ( A.BoneIndex < B.BoneIndex ) return true;
else return false;
}
};
Influences.Sort( FCompareVertexIndex() );
TArray <VRawBoneInfluence> NewInfluences;
int32 LastNewInfluenceIndex=0;
int32 LastVertexIndex = INDEX_NONE;
int32 InfluenceCount = 0;
float TotalWeight = 0.f;
const float MINWEIGHT = 0.01f;
for( int32 i=0; i<Influences.Num(); i++ )
{
// we found next verts, normalize it now
if (LastVertexIndex != Influences[i].VertexIndex )
{
// Normalize the last set of influences.
if (InfluenceCount && (TotalWeight != 1.0f))
{
float OneOverTotalWeight = 1.f / TotalWeight;
for (int r = 0; r < InfluenceCount; r++)
{
NewInfluences[LastNewInfluenceIndex - r].Weight *= OneOverTotalWeight;
}
}
// now we insert missing verts
if (LastVertexIndex != INDEX_NONE)
{
int32 CurrentVertexIndex = Influences[i].VertexIndex;
for(int32 j=LastVertexIndex+1; j<CurrentVertexIndex; j++)
{
// Add a 0-bone weight if none other present (known to happen with certain MAX skeletal setups).
LastNewInfluenceIndex = NewInfluences.AddUninitialized();
NewInfluences[LastNewInfluenceIndex].VertexIndex = j;
NewInfluences[LastNewInfluenceIndex].BoneIndex = 0;
NewInfluences[LastNewInfluenceIndex].Weight = 1.f;
}
}
// clear to count next one
InfluenceCount = 0;
TotalWeight = 0.f;
LastVertexIndex = Influences[i].VertexIndex;
}
// if less than min weight, or it's more than 8, then we clear it to use weight
if (Influences[i].Weight > MINWEIGHT && InfluenceCount < MAX_TOTAL_INFLUENCES)
{
LastNewInfluenceIndex = NewInfluences.Add(Influences[i]);
InfluenceCount++;
TotalWeight += Influences[i].Weight;
}
}
Influences = NewInfluences;
// Ensure that each vertex has at least one influence as e.g. CreateSkinningStream relies on it.
// The below code relies on influences being sorted by vertex index.
if( Influences.Num() == 0 )
{
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
// warn about no influences
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, LOCTEXT("WarningNoSkelInfluences", "Warning skeletal mesh is has no vertex influences")), FFbxErrors::SkeletalMesh_NoInfluences);
// add one for each wedge entry
Influences.AddUninitialized(Wedges.Num());
for( int32 WedgeIdx=0; WedgeIdx<Wedges.Num(); WedgeIdx++ )
{
Influences[WedgeIdx].VertexIndex = WedgeIdx;
Influences[WedgeIdx].BoneIndex = 0;
Influences[WedgeIdx].Weight = 1.0f;
}
for(int32 i=0; i<Influences.Num(); i++)
{
int32 CurrentVertexIndex = Influences[i].VertexIndex;
//.........这里部分代码省略.........
bool UEditorEngine::ReimportFbxAnimation( USkeleton* Skeleton, UAnimSequence* AnimSequence, UFbxAnimSequenceImportData* ImportData, const TCHAR* InFilename)
{
check(Skeleton);
GWarn->BeginSlowTask( LOCTEXT("ImportingFbxAnimations", "Importing FBX animations"), true );
UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance();
// logger for all error/warnings
// this one prints all messages that are stored in FFbxImporter
UnFbx::FFbxLoggerSetter Logger(FbxImporter);
const bool bPrevImportMorph = (AnimSequence->RawCurveData.FloatCurves.Num() > 0) ;
if ( ImportData )
{
// Prepare the import options
UFbxImportUI* ReimportUI = NewObject<UFbxImportUI>();
ReimportUI->MeshTypeToImport = FBXIT_Animation;
ReimportUI->bOverrideFullName = false;
ReimportUI->AnimSequenceImportData = ImportData;
ApplyImportUIToImportOptions(ReimportUI, *FbxImporter->ImportOptions);
}
else
{
FbxImporter->ImportOptions->ResetForReimportAnimation();
}
if ( !FbxImporter->ImportFromFile( InFilename, FPaths::GetExtension( InFilename ) ) )
{
// Log the error message and fail the import.
FbxImporter->FlushToTokenizedErrorMessage(EMessageSeverity::Error);
}
else
{
// Log the import message and import the mesh.
FbxImporter->FlushToTokenizedErrorMessage(EMessageSeverity::Warning);
const FString Filename( InFilename );
// Get Mesh nodes array that bind to the skeleton system, then morph animation is imported.
TArray<FbxNode*> FBXMeshNodeArray;
FbxNode* SkeletonRoot = FbxImporter->FindFBXMeshesByBone(Skeleton->GetReferenceSkeleton().GetBoneName(0), true, FBXMeshNodeArray);
if (!SkeletonRoot)
{
FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_CouldNotFindFbxTrack", "Mesh contains {0} bone as root but animation doesn't contain the root track.\nImport failed."), FText::FromName(Skeleton->GetReferenceSkeleton().GetBoneName(0)))), FFbxErrors::Animation_CouldNotFindTrack);
FbxImporter->ReleaseScene();
GWarn->EndSlowTask();
return false;
}
// for now import all the time?
bool bImportMorphTracks = true;
// Check for blend shape curves that are not skinned. Unskinned geometry can still contain morph curves
if( bImportMorphTracks )
{
TArray<FbxNode*> MeshNodes;
FbxImporter->FillFbxMeshArray( FbxImporter->Scene->GetRootNode(), MeshNodes, FbxImporter );
for( int32 NodeIndex = 0; NodeIndex < MeshNodes.Num(); ++NodeIndex )
{
// Its possible the nodes already exist so make sure they are only added once
FBXMeshNodeArray.AddUnique( MeshNodes[NodeIndex] );
}
}
TArray<FbxNode*> SortedLinks;
FbxImporter->RecursiveBuildSkeleton(SkeletonRoot, SortedLinks);
if(SortedLinks.Num() == 0)
{
FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, LOCTEXT("Error_CouldNotBuildValidSkeleton", "Could not create a valid skeleton from the import data that matches the given Skeletal Mesh. Check the bone names of both the Skeletal Mesh for this AnimSet and the animation data you are trying to import.")), FFbxErrors::Animation_CouldNotBuildSkeleton);
}
else
{
// find the correct animation based on import data
FbxAnimStack* CurAnimStack = nullptr;
//ignore the source animation name if there's only one animation in the file.
//this is to make it easier for people who use content creation programs that only export one animation and/or ones that don't allow naming animations
if (FbxImporter->Scene->GetSrcObjectCount(FbxCriteria::ObjectType(FbxAnimStack::ClassId)) > 1 && !ImportData->SourceAnimationName.IsEmpty())
{
CurAnimStack = FbxCast<FbxAnimStack>(FbxImporter->Scene->FindSrcObject(FbxCriteria::ObjectType(FbxAnimStack::ClassId), TCHAR_TO_UTF8(*ImportData->SourceAnimationName), 0));
}
else
{
CurAnimStack = FbxCast<FbxAnimStack>(FbxImporter->Scene->GetSrcObject(FbxCriteria::ObjectType(FbxAnimStack::ClassId), 0));
}
if (CurAnimStack)
{
// set current anim stack
int32 ResampleRate = DEFAULT_SAMPLERATE;
if (FbxImporter->ImportOptions->bResample)
{
ResampleRate = FbxImporter->GetMaxSampleRate(SortedLinks, FBXMeshNodeArray);
}
//.........这里部分代码省略.........
请发表评论