// This method is templated on the implementation of hctMayaSceneExporter/hctMaxSceneExporter::createScene() bool FbxToHkxConverter::createScenes(FbxScene* fbxScene) { clear(); m_curFbxScene = fbxScene; m_rootNode = m_curFbxScene->GetRootNode(); m_modeller = "FBX"; hkStringBuf application = fbxScene->GetSceneInfo()->Original_ApplicationName.Get(); if (application.getLength() > 0) { m_modeller += " ["; m_modeller += application; m_modeller += "]"; } printf("Modeller: %s\n", m_modeller.cString()); if (m_options.m_selectedOnly) { printf("Exporting Selected Only\n"); } if (m_options.m_visibleOnly) { printf("Exporting Visible Only\n"); } hkArray<FbxNode*> boneNodes; findChildren(m_rootNode, boneNodes, FbxNodeAttribute::eSkeleton); m_numBones = boneNodes.getSize(); printf("Bones: %d\n", m_numBones); const int poseCount = m_curFbxScene->GetPoseCount(); if (poseCount > 0) { m_pose = m_curFbxScene->GetPose(0); printf("Pose Elements: %d\n", m_pose->GetCount()); } m_numAnimStacks = m_curFbxScene->GetSrcObjectCount<FbxAnimStack>(); if (m_numAnimStacks > 0) { const FbxAnimStack* lAnimStack = m_curFbxScene->GetSrcObject<FbxAnimStack>(0); const FbxTimeSpan animTimeSpan = lAnimStack->GetLocalTimeSpan(); FbxTime timePerFrame; timePerFrame.SetTime(0, 0, 0, 1, 0, m_curFbxScene->GetGlobalSettings().GetTimeMode()); m_startTime = animTimeSpan.GetStart(); } printf("Animation stacks: %d\n", m_numAnimStacks); createSceneStack(-1); for (int animStackIndex = 0; animStackIndex < m_numAnimStacks && m_numBones > 0; animStackIndex++) { createSceneStack(animStackIndex); } return true; }
void findMinMaxTime(FbxAnimCurve* animCurve, float* startTime, float* stopTime, float* frameRate) { FbxTime start, stop; FbxTimeSpan timeSpan; animCurve->GetTimeInterval(timeSpan); start = timeSpan.GetStart(); stop = timeSpan.GetStop(); *startTime = std::min(*startTime, (float)start.GetMilliSeconds()); *stopTime = std::max(*stopTime, (float)stop.GetMilliSeconds()); *frameRate = std::max(*frameRate, (float)stop.GetFrameRate(FbxTime::eDefaultMode)); }
bool SceneContext::SetCurrentAnimStack(int nIndex) { const int nAnimStackCount = mAnimStackNameArray.GetCount(); if (!nAnimStackCount || nIndex >= nAnimStackCount) { return false; } // select the base layer from the animation stack FbxAnimStack * lCurrentAnimationStack = mScene->FindMember<FbxAnimStack>(mAnimStackNameArray[nIndex]->Buffer()); if (lCurrentAnimationStack == NULL) { // this is a problem. The anim stack should be found in the scene! return false; } // we assume that the first animation layer connected to the animation stack is the base layer // (this is the assumption made in the FBXSDK) mCurrentAnimLayer = lCurrentAnimationStack->GetMember<FbxAnimLayer>(); mScene->GetEvaluator()->SetContext(lCurrentAnimationStack); FbxTakeInfo* lCurrentTakeInfo = mScene->GetTakeInfo(*(mAnimStackNameArray[nIndex])); if (lCurrentTakeInfo) { mStart = lCurrentTakeInfo->mLocalTimeSpan.GetStart(); mStop = lCurrentTakeInfo->mLocalTimeSpan.GetStop(); } else { // Take the time line value FbxTimeSpan lTimeLineTimeSpan; mScene->GetGlobalSettings().GetTimelineDefaultTimeSpan(lTimeLineTimeSpan); mStart = lTimeLineTimeSpan.GetStart(); mStop = lTimeLineTimeSpan.GetStop(); } // check for smallest start with cache start if(mCache_Start < mStart) mStart = mCache_Start; // check for biggest stop with cache stop if(mCache_Stop > mStop) mStop = mCache_Stop; // move to beginning mCurrentTime = mStart; // Set the scene status flag to refresh // the scene in the next timer callback. mStatus = MUST_BE_REFRESHED; return true; }
void UnFbx::FFbxImporter::MergeAllLayerAnimation(FbxAnimStack* AnimStack, int32 ResampleRate) { FbxTime lFramePeriod; lFramePeriod.SetSecondDouble(1.0 / ResampleRate); FbxTimeSpan lTimeSpan = AnimStack->GetLocalTimeSpan(); AnimStack->BakeLayers(Scene->GetAnimationEvaluator(), lTimeSpan.GetStart(), lTimeSpan.GetStop(), lFramePeriod); // always apply unroll filter FbxAnimCurveFilterUnroll UnrollFilter; FbxAnimLayer* lLayer = AnimStack->GetMember<FbxAnimLayer>(0); UnrollFilter.Reset(); ApplyUnroll(Scene->GetRootNode(), lLayer, &UnrollFilter); }
void fbxLoader2::readAnimationTakeData(FbxNode* node) { FbxAnimStack* pAnimStack = FbxCast<FbxAnimStack>(scene->GetSrcObject(FBX_TYPE(FbxAnimStack))); FbxAnimLayer* pAnimLayer = pAnimStack->GetMember(FBX_TYPE(FbxAnimLayer)); FbxAnimCurve* animCv = node->LclTranslation.GetCurve(pAnimLayer, FBXSDK_CURVENODE_COMPONENT_X); FbxTimeSpan length = FbxTimeSpan(); int p = animCv->KeyGetCount(); const char* nameAnim = animCv->GetName(); const size_t len = strlen(nameAnim); char * new_name = new char[len + 1]; strncpy(new_name, nameAnim, len); animCv->GetTimeInterval(length); FbxTime duration = length.GetDuration(); FbxTime::EMode mode = duration.GetGlobalTimeMode(); double frameRate = duration.GetFrameRate(mode); double startt = length.GetStart().GetMilliSeconds(); double endt = length.GetStop().GetMilliSeconds(); int frames = animCv->KeyGetCount(); animationStructure = new AnimationData(new_name, startt, endt, (int)frameRate, frames); for (int i = 0; i< frames; i++) { SkeletalData *sk = new SkeletalData(); for(int j = 0; j<skeleton->GetBonesCount(); j++) { BoneData *bonecopy = new BoneData(); bonecopy->SetID(skeleton->GetBone(j)->GetID()); bonecopy->SetName(skeleton->GetBone(j)->GetName()); bonecopy->SetParent(skeleton->GetBone(j)->GetParent()); sk->SetBones(bonecopy); } animationStructure->SetSkeleton(sk, i); } }
void FBXScene::ProcessAnimations(FbxScene* pScene) { m_pAnimationController = new AnimationController(); FbxNode* pRootNode = pScene->GetRootNode(); if(!pRootNode) return; float fFrameRate = (float)FbxTime::GetFrameRate(pScene->GetGlobalSettings().GetTimeMode()); FbxArray<FbxString*> takeArray; FbxDocument* pDocument = FbxCast<FbxDocument>(pScene); // dynamic_cast<FbxDocument*>(pScene); if( pDocument ) pDocument->FillAnimStackNameArray(takeArray); for( int i = 0; i < takeArray.GetCount(); ++i ) { FbxString* takeName = takeArray.GetAt(i); if( std::string(takeName->Buffer()) != "Default" ) { /// ARRRGHHH SÄTTER ALLTID FÖRSTA HÄR!!!!!!!!!!!!!!!!!! FbxTakeInfo* lCurrentTakeInfo = pScene->GetTakeInfo(takeName->Buffer()); FbxAnimStack* lAnimStack = FbxCast<FbxAnimStack>(pScene->GetSrcObject<FbxAnimStack>(i)); pScene->GetEvaluator()->SetContext(lAnimStack); FbxTime KStart; FbxTime KStop; if(lCurrentTakeInfo) { KStart = lCurrentTakeInfo->mLocalTimeSpan.GetStart(); KStop = lCurrentTakeInfo->mLocalTimeSpan.GetStop(); } else { // Take the time line value FbxTimeSpan lTimeLineTimeSpan; pScene->GetGlobalSettings().GetTimelineDefaultTimeSpan(lTimeLineTimeSpan); KStart = lTimeLineTimeSpan.GetStart(); KStop = lTimeLineTimeSpan.GetStop(); } float fStart = (float)KStart.GetSecondDouble(); float fStop = (float)KStop.GetSecondDouble(); if( fStart < fStop ) { int nKeyFrames = int((fStop-fStart)*fFrameRate); Animation* pAnimation = new Animation(takeName->Buffer(), nKeyFrames, fFrameRate); m_pAnimationController->AddAnimation(pAnimation); ProcessAnimation(pRootNode, takeName->Buffer(), fFrameRate, fStart, fStop); } } delete takeName; } takeArray.Clear(); }
int main(int argc, char **argv) { #ifndef _DEBUG if (argc != 2) { printf("invalid arg"); return 0; } const char* filename = argv[1]; #else const char* filename = "*****@*****.**"; #endif output.open("output.txt", ios::out | ios::trunc); output2.open("output2.txt", ios::out | ios::trunc); output3.open("output3.txt", ios::out | ios::trunc); if (!output.is_open()) { exit(1); } FbxManager* fm = FbxManager::Create(); FbxIOSettings *ios = FbxIOSettings::Create(fm, IOSROOT); //ios->SetBoolProp(EXP_FBX_ANIMATION, false); ios->SetIntProp(EXP_FBX_COMPRESS_LEVEL, 9); ios->SetAllObjectFlags(true); fm->SetIOSettings(ios); FbxImporter* importer = FbxImporter::Create(fm, ""); if (!importer->Initialize(filename, -1, fm->GetIOSettings())) { printf("error returned : %s\n", importer->GetStatus().GetErrorString()); exit(-1); } FbxScene* scene = FbxScene::Create(fm, "myscene"); importer->Import(scene); importer->Destroy(); output << "some\n"; output << "charcnt : " << scene->GetCharacterCount() << endl << "node cnt : " << scene->GetNodeCount() << endl; int animstackcnt = scene->GetSrcObjectCount<FbxAnimStack>(); output << "animstackcnt : " << animstackcnt << endl; output << "------------" << endl; vector<FbxNode*> removableNodes; for (int i = 0; i < scene->GetNodeCount(); i++) { FbxNode* node = scene->GetNode(i); output << "scene's node " << i << " : " << node->GetName() << ", childcnt : " << node->GetChildCount(); if (node->GetNodeAttribute()) { output <<", att type : " << node->GetNodeAttribute()->GetAttributeType(); if (node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::EType::eMesh) { FbxMesh* mesh = node->GetMesh(); output << ", mem usage : " << mesh->MemoryUsage() << ", deformer cnt : " << mesh->GetDeformerCount(FbxDeformer::EDeformerType::eSkin) << endl; collapseMesh(mesh); FbxSkin* skin = (FbxSkin*) (mesh->GetDeformer(0, FbxDeformer::EDeformerType::eSkin)); if (skin) { for (int cli = 0; cli < skin->GetClusterCount(); cli++) { FbxCluster* cluster = skin->GetCluster(cli); output << "\tcluster no." << cli << " has " << cluster->GetControlPointIndicesCount() << " connected verts" << endl; if (cluster->GetControlPointIndicesCount() == 0) removableNodes.push_back( cluster->GetLink() ); //cluster-> //skin->RemoveCluster(cluster);효과없음 } } if (mesh->IsTriangleMesh()) { output << "\tit's triangle mesh" << endl; } //mesh->RemoveDeformer(0);효과없음 } else output << endl; } else { output << ", att type : none" << endl; } } for (int rni = 0; rni < removableNodes.size(); rni++) { FbxNode* rnd = removableNodes[rni]; if (rnd && rnd->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::EType::eSkeleton) { output3 << rnd->GetName() << " node with no vert attached's curve : " << rnd->GetSrcObjectCount<FbxAnimCurve>() << "," << rnd->GetSrcObjectCount<FbxAnimCurveNode>() << endl; } } output << "-----------animinfo" << endl; int cubic = 0, linear = 0, cons = 0; for (int si = 0; si < animstackcnt; si++) { FbxAnimStack* stack = scene->GetSrcObject<FbxAnimStack>(si); for (int i = 0; i < stack->GetMemberCount<FbxAnimLayer>(); i++) { FbxAnimLayer* layer = stack->GetMember<FbxAnimLayer>(i); int curvenodecnt = layer->GetMemberCount<FbxAnimCurveNode>(); int compositcnt = 0; for (int j = 0; j < curvenodecnt; j++) { FbxAnimCurveNode* cnode = layer->GetMember<FbxAnimCurveNode>(j); compositcnt += (cnode->IsComposite() ? 1 : 0); } output << "\tanimstack's layer " << i << " : " << layer->GetName() << ", curve node cnt : " << curvenodecnt << ", composit node cnt : " << compositcnt << endl; vector<FbxAnimCurveNode*> nodes2del; for (int j = 0; j < curvenodecnt; j++) { FbxAnimCurveNode* cnode = layer->GetMember<FbxAnimCurveNode>(j); output << "\t\tcurvenode " << j << " channel cnt : " << cnode->GetChannelsCount() << ", dst obj cnt " << cnode->GetDstObjectCount() << "("; for (int dsti = 0; dsti < cnode->GetDstObjectCount(); dsti++) { output << "," << cnode->GetDstObject(dsti)->GetName(); if (cnode->GetDstObject(dsti)->GetSrcObjectCount() > 0) output << "<" << cnode->GetDstObject(dsti)->GetSrcObjectCount<FbxSkeleton>() << ">"; } output << ")"; FbxTimeSpan interval; if (cnode->GetAnimationInterval(interval)) { output << ", start : " << interval.GetStart().GetTimeString() << ", end : " << interval.GetStop().GetTimeString() << endl; } else { nodes2del.push_back(cnode); output << ", no interval" << endl; } for (int chi = 0; chi < cnode->GetChannelsCount(); chi++) { int curvecnt = cnode->GetCurveCount(chi); output << "\t\t\tchannel." << chi << " curvecnt : " << curvecnt << endl; for (int ci = 0; ci < curvecnt; ci++) { FbxAnimCurve* curve = cnode->GetCurve(chi, ci); int keycnt = curve->KeyGetCount(); output << "\t\t\t\tcurve no." << ci << " : key count : " << keycnt; output2 << "curve " << ci << endl; vector<int> keys2Remove; for (int cki = 0; cki < keycnt; cki++) { FbxAnimCurveKey prevkey, currkey, nextkey; if (cki == 0 || cki == keycnt - 1) continue; currkey = curve->KeyGet(cki); prevkey = curve->KeyGet(cki-1); nextkey = curve->KeyGet(cki + 1); bool keepit = true; output2 << ci << "-" << cki; // keepit = keepTestHorizon(curve, prevkey, currkey, nextkey); // if (keepit) // keepit = slopkeepTest(curve, prevkey, currkey, nextkey); if (!keepit) { if (!(currkey.GetInterpolation() == FbxAnimCurveDef::EInterpolationType::eInterpolationConstant && nextkey.GetInterpolation() != FbxAnimCurveDef::EInterpolationType::eInterpolationConstant)) keys2Remove.push_back(cki); } } for (int kri = keys2Remove.size() - 1; kri >= 0; kri--) { //curve->KeyRemove(keys2Remove[kri]); } output2 << endl; //output << ", cubic:linear:const : " << cubic << ":" << linear << ":" << cons << endl; if (keys2Remove.size() > 0) output << ", " << keys2Remove.size() << " keys removed"; keycnt = curve->KeyGetCount(); } } } //이부분은 별로 효과없음 /* for (int di = 0; di < nodes2del.size(); di++) { layer->RemoveMember(nodes2del[di]); } */ } } output << "cubic:linear:const " << cubic << ":" << linear << ":" << cons << endl; FbxExporter* exporter = FbxExporter::Create(fm, ""); const char* outFBXName = "after.fbx"; bool exportstatus = exporter->Initialize(outFBXName, -1, fm->GetIOSettings()); if (exportstatus == false) { puts("err export fail"); } exporter->Export(scene); exporter->Destroy(); scene->Destroy(); ios->Destroy(); fm->Destroy(); output.close(); output2.close(); output3.close(); return 0; }
bool UnFbx::FFbxImporter::ImportAnimation(USkeleton* Skeleton, UAnimSequence * DestSeq, const FString& FileName, TArray<FbxNode*>& SortedLinks, TArray<FbxNode*>& NodeArray, FbxAnimStack* CurAnimStack, const int32 ResampleRate, const FbxTimeSpan AnimTimeSpan) { // @todo : the length might need to change w.r.t. sampling keys FbxTime SequenceLength = AnimTimeSpan.GetDuration(); float PreviousSequenceLength = DestSeq->SequenceLength; // if you have one pose(thus 0.f duration), it still contains animation, so we'll need to consider that as MINIMUM_ANIMATION_LENGTH time length DestSeq->SequenceLength = FGenericPlatformMath::Max<float>(SequenceLength.GetSecondDouble(), MINIMUM_ANIMATION_LENGTH); if(PreviousSequenceLength > MINIMUM_ANIMATION_LENGTH && DestSeq->RawCurveData.FloatCurves.Num() > 0) { // The sequence already existed when we began the import. We need to scale the key times for all curves to match the new // duration before importing over them. This is to catch any user-added curves float ScaleFactor = DestSeq->SequenceLength / PreviousSequenceLength; for(FFloatCurve& Curve : DestSeq->RawCurveData.FloatCurves) { Curve.FloatCurve.ScaleCurve(0.0f, ScaleFactor); } } if (ImportOptions->bDeleteExistingMorphTargetCurves) { for (int32 CurveIdx=0; CurveIdx<DestSeq->RawCurveData.FloatCurves.Num(); ++CurveIdx) { auto& Curve = DestSeq->RawCurveData.FloatCurves[CurveIdx]; if (Curve.GetCurveTypeFlag(ACF_DrivesMorphTarget)) { DestSeq->RawCurveData.FloatCurves.RemoveAt(CurveIdx, 1, false); --CurveIdx; } } DestSeq->RawCurveData.FloatCurves.Shrink(); } // // import blend shape curves // { GWarn->BeginSlowTask( LOCTEXT("BeginImportMorphTargetCurves", "Importing Morph Target Curves"), true); for ( int32 NodeIndex = 0; NodeIndex < NodeArray.Num(); NodeIndex++ ) { // consider blendshape animation curve FbxGeometry* Geometry = (FbxGeometry*)NodeArray[NodeIndex]->GetNodeAttribute(); if (Geometry) { int32 BlendShapeDeformerCount = Geometry->GetDeformerCount(FbxDeformer::eBlendShape); for(int32 BlendShapeIndex = 0; BlendShapeIndex<BlendShapeDeformerCount; ++BlendShapeIndex) { FbxBlendShape* BlendShape = (FbxBlendShape*)Geometry->GetDeformer(BlendShapeIndex, FbxDeformer::eBlendShape); const int32 BlendShapeChannelCount = BlendShape->GetBlendShapeChannelCount(); FString BlendShapeName = UTF8_TO_TCHAR(MakeName(BlendShape->GetName())); for(int32 ChannelIndex = 0; ChannelIndex<BlendShapeChannelCount; ++ChannelIndex) { FbxBlendShapeChannel* Channel = BlendShape->GetBlendShapeChannel(ChannelIndex); if(Channel) { FString ChannelName = UTF8_TO_TCHAR(MakeName(Channel->GetName())); // Maya adds the name of the blendshape and an underscore to the front of the channel name, so remove it if(ChannelName.StartsWith(BlendShapeName)) { ChannelName = ChannelName.Right(ChannelName.Len() - (BlendShapeName.Len()+1)); } FbxAnimCurve* Curve = Geometry->GetShapeChannel(BlendShapeIndex, ChannelIndex, (FbxAnimLayer*)CurAnimStack->GetMember(0)); if (Curve && Curve->KeyGetCount() > 0) { FFormatNamedArguments Args; Args.Add(TEXT("BlendShape"), FText::FromString(ChannelName)); const FText StatusUpate = FText::Format(LOCTEXT("ImportingMorphTargetCurvesDetail", "Importing Morph Target Curves [{BlendShape}]"), Args); GWarn->StatusUpdate(NodeIndex + 1, NodeArray.Num(), StatusUpate); // now see if we have one already exists. If so, just overwrite that. if not, add new one. ImportCurveToAnimSequence(DestSeq, *ChannelName, Curve, ACF_DrivesMorphTarget | ACF_TriggerEvent, AnimTimeSpan, 0.01f /** for some reason blend shape values are coming as 100 scaled **/); } } } } } } GWarn->EndSlowTask(); } // // importing custom attribute START // if (ImportOptions->bImportCustomAttribute) { GWarn->BeginSlowTask( LOCTEXT("BeginImportMorphTargetCurves", "Importing Custom Attirubte Curves"), true); const int32 TotalLinks = SortedLinks.Num(); int32 CurLinkIndex=0; for(auto Node: SortedLinks) { FbxProperty Property = Node->GetFirstProperty(); while (Property.IsValid()) { FbxAnimCurveNode* CurveNode = Property.GetCurveNode(); // do this if user defined and animated and leaf node if( CurveNode && Property.GetFlag(FbxPropertyAttr::eUserDefined) && CurveNode->IsAnimated() && IsSupportedCurveDataType(Property.GetPropertyDataType().GetType()) ) { FString CurveName = UTF8_TO_TCHAR(CurveNode->GetName()); UE_LOG(LogFbx, Log, TEXT("CurveName : %s"), *CurveName ); int32 TotalCount = CurveNode->GetChannelsCount(); for (int32 ChannelIndex=0; ChannelIndex<TotalCount; ++ChannelIndex) { FbxAnimCurve * AnimCurve = CurveNode->GetCurve(ChannelIndex); FString ChannelName = CurveNode->GetChannelName(ChannelIndex).Buffer(); if (AnimCurve) { FString FinalCurveName; if (TotalCount == 1) { FinalCurveName = CurveName; } else { FinalCurveName = CurveName + "_" + ChannelName; } FFormatNamedArguments Args; Args.Add(TEXT("CurveName"), FText::FromString(FinalCurveName)); const FText StatusUpate = FText::Format(LOCTEXT("ImportingCustomAttributeCurvesDetail", "Importing Custom Attribute [{CurveName}]"), Args); GWarn->StatusUpdate(CurLinkIndex + 1, TotalLinks, StatusUpate); ImportCurveToAnimSequence(DestSeq, FinalCurveName, AnimCurve, ACF_DefaultCurve, AnimTimeSpan); } } } Property = Node->GetNextProperty(Property); } CurLinkIndex++; } GWarn->EndSlowTask(); } // importing custom attribute END const bool bSourceDataExists = (DestSeq->SourceRawAnimationData.Num() > 0); TArray<AnimationTransformDebug::FAnimationTransformDebugData> TransformDebugData; int32 TotalNumKeys = 0; const FReferenceSkeleton& RefSkeleton = Skeleton->GetReferenceSkeleton(); // import animation { GWarn->BeginSlowTask( LOCTEXT("BeginImportAnimation", "Importing Animation"), true); TArray<struct FRawAnimSequenceTrack>& RawAnimationData = bSourceDataExists? DestSeq->SourceRawAnimationData : DestSeq->RawAnimationData; DestSeq->TrackToSkeletonMapTable.Empty(); DestSeq->AnimationTrackNames.Empty(); RawAnimationData.Empty(); TArray<FName> FbxRawBoneNames; FillAndVerifyBoneNames(Skeleton, SortedLinks, FbxRawBoneNames, FileName); UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); const bool bPreserveLocalTransform = FbxImporter->GetImportOptions()->bPreserveLocalTransform; // Build additional transform matrix UFbxAnimSequenceImportData* TemplateData = Cast<UFbxAnimSequenceImportData>(DestSeq->AssetImportData); FbxAMatrix FbxAddedMatrix; BuildFbxMatrixForImportTransform(FbxAddedMatrix, TemplateData); FMatrix AddedMatrix = Converter.ConvertMatrix(FbxAddedMatrix); const int32 NumSamplingKeys = FMath::FloorToInt(AnimTimeSpan.GetDuration().GetSecondDouble() * ResampleRate); const FbxTime TimeIncrement = (NumSamplingKeys > 1)? AnimTimeSpan.GetDuration() / (NumSamplingKeys - 1) : AnimTimeSpan.GetDuration(); for(int32 SourceTrackIdx = 0; SourceTrackIdx < FbxRawBoneNames.Num(); ++SourceTrackIdx) { int32 NumKeysForTrack = 0; // see if it's found in Skeleton FName BoneName = FbxRawBoneNames[SourceTrackIdx]; int32 BoneTreeIndex = RefSkeleton.FindBoneIndex(BoneName); // update status FFormatNamedArguments Args; Args.Add(TEXT("TrackName"), FText::FromName(BoneName)); Args.Add(TEXT("TotalKey"), FText::AsNumber(NumSamplingKeys)); Args.Add(TEXT("TrackIndex"), FText::AsNumber(SourceTrackIdx+1)); Args.Add(TEXT("TotalTracks"), FText::AsNumber(FbxRawBoneNames.Num())); const FText StatusUpate = FText::Format(LOCTEXT("ImportingAnimTrackDetail", "Importing Animation Track [{TrackName}] ({TrackIndex}/{TotalTracks}) - TotalKey {TotalKey}"), Args); GWarn->StatusForceUpdate(SourceTrackIdx + 1, FbxRawBoneNames.Num(), StatusUpate); if (BoneTreeIndex!=INDEX_NONE) { bool bSuccess = true; FRawAnimSequenceTrack RawTrack; RawTrack.PosKeys.Empty(); RawTrack.RotKeys.Empty(); RawTrack.ScaleKeys.Empty(); AnimationTransformDebug::FAnimationTransformDebugData NewDebugData; FbxNode* Link = SortedLinks[SourceTrackIdx]; FbxNode * LinkParent = Link->GetParent(); for(FbxTime CurTime = AnimTimeSpan.GetStart(); CurTime <= AnimTimeSpan.GetStop(); CurTime += TimeIncrement) { // save global trasnform FbxAMatrix GlobalMatrix = Link->EvaluateGlobalTransform(CurTime); // we'd like to verify this before going to Transform. // currently transform has tons of NaN check, so it will crash there FMatrix GlobalUEMatrix = Converter.ConvertMatrix(GlobalMatrix); if (GlobalUEMatrix.ContainsNaN()) { bSuccess = false; AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidTransform", "Track {0} contains invalid transform. Could not import the track."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError); break; } FTransform GlobalTransform = Converter.ConvertTransform(GlobalMatrix); if (GlobalTransform.ContainsNaN()) { bSuccess = false; AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidUnrealTransform", "Track {0} did not yeild valid transform. Please report this to animation team."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError); break; } // debug data, including import transformation FTransform AddedTransform(AddedMatrix); NewDebugData.SourceGlobalTransform.Add(GlobalTransform * AddedTransform); FTransform LocalTransform; if( !bPreserveLocalTransform && LinkParent) { // I can't rely on LocalMatrix. I need to recalculate quaternion/scale based on global transform if Parent exists FbxAMatrix ParentGlobalMatrix = Link->GetParent()->EvaluateGlobalTransform(CurTime); FTransform ParentGlobalTransform = Converter.ConvertTransform(ParentGlobalMatrix); LocalTransform = GlobalTransform.GetRelativeTransform(ParentGlobalTransform); NewDebugData.SourceParentGlobalTransform.Add(ParentGlobalTransform); } else { FbxAMatrix& LocalMatrix = Link->EvaluateLocalTransform(CurTime); FbxVector4 NewLocalT = LocalMatrix.GetT(); FbxVector4 NewLocalS = LocalMatrix.GetS(); FbxQuaternion NewLocalQ = LocalMatrix.GetQ(); LocalTransform.SetTranslation(Converter.ConvertPos(NewLocalT)); LocalTransform.SetScale3D(Converter.ConvertScale(NewLocalS)); LocalTransform.SetRotation(Converter.ConvertRotToQuat(NewLocalQ)); NewDebugData.SourceParentGlobalTransform.Add(FTransform::Identity); } if(TemplateData && BoneTreeIndex == 0) { // If we found template data earlier, apply the import transform matrix to // the root track. LocalTransform.SetFromMatrix(LocalTransform.ToMatrixWithScale() * AddedMatrix); } if (LocalTransform.ContainsNaN()) { bSuccess = false; AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("Error_InvalidUnrealLocalTransform", "Track {0} did not yeild valid local transform. Please report this to animation team."), FText::FromName(BoneName))), FFbxErrors::Animation_TransformError); break; } RawTrack.ScaleKeys.Add(LocalTransform.GetScale3D()); RawTrack.PosKeys.Add(LocalTransform.GetTranslation()); RawTrack.RotKeys.Add(LocalTransform.GetRotation()); NewDebugData.RecalculatedLocalTransform.Add(LocalTransform); ++NumKeysForTrack; } if (bSuccess) { //add new track int32 NewTrackIdx = RawAnimationData.Add(RawTrack); DestSeq->AnimationTrackNames.Add(BoneName); NewDebugData.SetTrackData(NewTrackIdx, BoneTreeIndex, BoneName); // add mapping to skeleton bone track DestSeq->TrackToSkeletonMapTable.Add(FTrackToSkeletonMap(BoneTreeIndex)); TransformDebugData.Add(NewDebugData); } } TotalNumKeys = FMath::Max( TotalNumKeys, NumKeysForTrack ); } DestSeq->NumFrames = TotalNumKeys; GWarn->EndSlowTask(); } // compress animation { GWarn->BeginSlowTask( LOCTEXT("BeginCompressAnimation", "Compress Animation"), true); GWarn->StatusForceUpdate(1, 1, LOCTEXT("CompressAnimation", "Compressing Animation")); // if source data exists, you should bake it to Raw to apply if(bSourceDataExists) { DestSeq->BakeTrackCurvesToRawAnimation(); } else { // otherwise just compress DestSeq->PostProcessSequence(); } // run debug mode AnimationTransformDebug::OutputAnimationTransformDebugData(TransformDebugData, TotalNumKeys, RefSkeleton); GWarn->EndSlowTask(); } return true; }
bool UnFbx::FFbxImporter::ImportCurve(const FbxAnimCurve* FbxCurve, FFloatCurve * Curve, const FbxTimeSpan &AnimTimeSpan, const float ValueScale/*=1.f*/) const { static float DefaultCurveWeight = FbxAnimCurveDef::sDEFAULT_WEIGHT; if ( FbxCurve && Curve ) { for ( int32 KeyIndex=0; KeyIndex<FbxCurve->KeyGetCount(); ++KeyIndex ) { FbxAnimCurveKey Key = FbxCurve->KeyGet(KeyIndex); FbxTime KeyTime = Key.GetTime() - AnimTimeSpan.GetStart(); float Value = Key.GetValue() * ValueScale; FKeyHandle NewKeyHandle = Curve->FloatCurve.AddKey(KeyTime.GetSecondDouble(), Value, true); 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 = 0.f; float ArriveTangentWeight = 0.f; 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 { LeaveTangent = Key.GetDataFloat(FbxAnimCurveDef::eRightSlope); if ( KeyIndex > 0 ) { FbxAnimCurveKey PrevKey = FbxCurve->KeyGet(KeyIndex-1); ArriveTangent = PrevKey.GetDataFloat(FbxAnimCurveDef::eNextLeftSlope); } else { ArriveTangent = 0.f; } } break; } // when we import tangent, we only support break or user // since it's modified by DCC and we only assume these two are valid // auto does our own stuff, which doesn't work with what you see in DCC if (KeyTangentMode & FbxAnimCurveDef::eTangentBreak) { NewTangentMode = RCTM_Break; } else { NewTangentMode = RCTM_User; } // @fix me : weight of tangent is not used, but we'll just save this for future where we might use it. 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; } Curve->FloatCurve.SetKeyInterpMode(NewKeyHandle, NewInterpMode); Curve->FloatCurve.SetKeyTangentMode(NewKeyHandle, NewTangentMode); Curve->FloatCurve.SetKeyTangentWeightMode(NewKeyHandle, NewTangentWeightMode); FRichCurveKey& NewKey = Curve->FloatCurve.GetKey(NewKeyHandle); // apply 1/100 - that seems like the tangent unit difference with FBX NewKey.ArriveTangent = ArriveTangent * 0.01f; NewKey.LeaveTangent = LeaveTangent * 0.01f; NewKey.ArriveTangentWeight = ArriveTangentWeight; NewKey.LeaveTangentWeight = LeaveTangentWeight; } return true; } return false; }
int32 UnFbx::FFbxImporter::GetMaxSampleRate(TArray<FbxNode*>& SortedLinks, TArray<FbxNode*>& NodeArray) { int32 MaxStackResampleRate = 0; int32 AnimStackCount = Scene->GetSrcObjectCount<FbxAnimStack>(); for( int32 AnimStackIndex = 0; AnimStackIndex < AnimStackCount; AnimStackIndex++) { FbxAnimStack* CurAnimStack = Scene->GetSrcObject<FbxAnimStack>(AnimStackIndex); FbxTimeSpan AnimStackTimeSpan = GetAnimationTimeSpan(SortedLinks[0], CurAnimStack); double AnimStackStart = AnimStackTimeSpan.GetStart().GetSecondDouble(); double AnimStackStop = AnimStackTimeSpan.GetStop().GetSecondDouble(); FbxAnimLayer* AnimLayer = (FbxAnimLayer*)CurAnimStack->GetMember(0); for(int32 LinkIndex = 0; LinkIndex < SortedLinks.Num(); ++LinkIndex) { FbxNode* CurrentLink = SortedLinks[LinkIndex]; FbxAnimCurve* Curves[6]; Curves[0] = CurrentLink->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, false); Curves[1] = CurrentLink->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, false); Curves[2] = CurrentLink->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, false); Curves[3] = CurrentLink->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, false); Curves[4] = CurrentLink->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, false); Curves[5] = CurrentLink->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, false); for(int32 CurveIndex = 0; CurveIndex < 6; ++CurveIndex) { FbxAnimCurve* CurrentCurve = Curves[CurveIndex]; if(CurrentCurve) { int32 KeyCount = CurrentCurve->KeyGetCount(); FbxTimeSpan TimeInterval(FBXSDK_TIME_INFINITE,FBXSDK_TIME_MINUS_INFINITE); bool bValidTimeInterval = CurrentCurve->GetTimeInterval(TimeInterval); if(KeyCount > 1 && bValidTimeInterval) { double KeyAnimLength = TimeInterval.GetDuration().GetSecondDouble(); double KeyAnimStart = TimeInterval.GetStart().GetSecondDouble(); double KeyAnimStop = TimeInterval.GetStop().GetSecondDouble(); if(KeyAnimLength != 0.0) { // 30 fps animation has 31 keys because it includes index 0 key for 0.0 second int32 NewRate = FPlatformMath::RoundToInt((KeyCount-1) / KeyAnimLength); MaxStackResampleRate = FMath::Max(NewRate, MaxStackResampleRate); } } } } } } // Make sure we're not hitting 0 for samplerate if ( MaxStackResampleRate != 0 ) { return MaxStackResampleRate; } return DEFAULT_SAMPLERATE; }
void FbxToHkxConverter::extractKeyFramesAndAnnotations(hkxScene *scene, FbxNode* fbxChildNode, hkxNode* newChildNode, int animStackIndex) { FbxAMatrix bindPoseMatrix; FbxAnimStack* lAnimStack = NULL; int numAnimLayers = 0; FbxTimeSpan animTimeSpan; if (animStackIndex >= 0) { lAnimStack = m_curFbxScene->GetSrcObject<FbxAnimStack>(animStackIndex); numAnimLayers = lAnimStack->GetMemberCount<FbxAnimLayer>(); animTimeSpan = lAnimStack->GetLocalTimeSpan(); } // Find the time offset (in the "time space" of the FBX file) of the first animation frame FbxTime timePerFrame; timePerFrame.SetTime(0, 0, 0, 1, 0, m_curFbxScene->GetGlobalSettings().GetTimeMode()); const FbxTime startTime = animTimeSpan.GetStart(); const FbxTime endTime = animTimeSpan.GetStop(); const hkReal startTimeSeconds = static_cast<hkReal>(startTime.GetSecondDouble()); const hkReal endTimeSeconds = static_cast<hkReal>(endTime.GetSecondDouble()); int numFrames = 0; bool staticNode = true; if (scene->m_sceneLength == 0) { bindPoseMatrix = fbxChildNode->EvaluateLocalTransform(startTime); } else { hkArray<hkStringOld> annotationStrings; hkArray<hkReal> annotationTimes; HK_ASSERT(0x0, newChildNode->m_keyFrames.getSize() == 0); // Sample each animation frame for (FbxTime time = startTime, priorSampleTime = endTime; time < endTime; priorSampleTime = time, time += timePerFrame, ++numFrames) { FbxAMatrix frameMatrix = fbxChildNode->EvaluateLocalTransform(time); staticNode = staticNode && (frameMatrix == bindPoseMatrix); hkMatrix4 mat; // Extract this frame's transform convertFbxXMatrixToMatrix4(frameMatrix, mat); newChildNode->m_keyFrames.pushBack(mat); // Extract all annotation strings for this frame using the deprecated // pipeline (new annotations are extracted when sampling attributes) if (m_options.m_exportAnnotations && numAnimLayers > 0) { FbxProperty prop = fbxChildNode->GetFirstProperty(); while(prop.IsValid()) { FbxString propName = prop.GetName(); FbxDataType lDataType = prop.GetPropertyDataType(); hkStringOld name(propName.Buffer(), (int) propName.GetLen()); if (name.asUpperCase().beginsWith("HK") && lDataType.GetType() == eFbxEnum) { FbxAnimLayer* lAnimLayer = lAnimStack->GetMember<FbxAnimLayer>(0); FbxAnimCurve* lAnimCurve = prop.GetCurve(lAnimLayer); int currentKeyIndex; const int keyIndex = (int) lAnimCurve->KeyFind(time, ¤tKeyIndex); const int priorKeyIndex = (int) lAnimCurve->KeyFind(priorSampleTime); // Only store annotations on frames where they're explicitly keyframed, or if this is the first keyframe if (priorKeyIndex != keyIndex) { const int currentEnumValueIndex = keyIndex < 0 ? (int) lAnimCurve->Evaluate(priorSampleTime) : (int) lAnimCurve->Evaluate(time); HK_ASSERT(0x0, currentEnumValueIndex < prop.GetEnumCount()); const char* enumValue = prop.GetEnumValue(currentEnumValueIndex); hkxNode::AnnotationData& annotation = newChildNode->m_annotations.expandOne(); annotation.m_time = (hkReal) (time - startTime).GetSecondDouble(); annotation.m_description = (name + hkStringOld(enumValue, hkString::strLen(enumValue))).cString(); } } prop = fbxChildNode->GetNextProperty(prop); } } } } // Replace animation key data for static nodes with just 1 or 2 frames of bind pose data if (staticNode) { // Static nodes in animated scene data are exported with two keys const bool exportTwoFramesForStaticNodes = (numFrames > 1); // replace transform newChildNode->m_keyFrames.setSize(exportTwoFramesForStaticNodes ? 2: 1); newChildNode->m_keyFrames.optimizeCapacity(0, true); // convert the bind pose transform to Havok format convertFbxXMatrixToMatrix4(bindPoseMatrix, newChildNode->m_keyFrames[0]); if (exportTwoFramesForStaticNodes) { newChildNode->m_keyFrames[1] = newChildNode->m_keyFrames[0]; } } // Extract all times of actual keyframes for the current node... this can be used by Vision if ( m_options.m_storeKeyframeSamplePoints && newChildNode->m_keyFrames.getSize() > 2 && numAnimLayers > 0 ) { FbxAnimLayer* lAnimLayer = lAnimStack->GetMember<FbxAnimLayer>(0); extractKeyTimes(fbxChildNode, lAnimLayer, FBXSDK_CURVENODE_TRANSLATION, newChildNode, startTimeSeconds, endTimeSeconds); extractKeyTimes(fbxChildNode, lAnimLayer, FBXSDK_CURVENODE_ROTATION, newChildNode, startTimeSeconds, endTimeSeconds); extractKeyTimes(fbxChildNode, lAnimLayer, FBXSDK_CURVENODE_SCALING, newChildNode, startTimeSeconds, endTimeSeconds); extractKeyTimes(fbxChildNode, lAnimLayer, FBXSDK_CURVENODE_COMPONENT_X, newChildNode, startTimeSeconds, endTimeSeconds); extractKeyTimes(fbxChildNode, lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, newChildNode, startTimeSeconds, endTimeSeconds); extractKeyTimes(fbxChildNode, lAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, newChildNode, startTimeSeconds, endTimeSeconds); if (newChildNode->m_linearKeyFrameHints.getSize() > 1) { hkSort(newChildNode->m_linearKeyFrameHints.begin(), newChildNode->m_linearKeyFrameHints.getSize()); } } }
void LoadNodeRecursive(FbxModelData* aModel,AnimationData& aAnimation,FbxNode* aNode,FbxAMatrix& aParentOrientation, FbxPose* aPose, FbxAnimLayer* aCurrentAnimLayer, int parentBone) { parentBone; FbxAMatrix lGlobalPosition = GetGlobalPosition(aNode, static_cast<FbxTime>(0.0f), aPose, &aParentOrientation); FbxNodeAttribute* lNodeAttribute = aNode->GetNodeAttribute(); if (lNodeAttribute && lNodeAttribute->GetAttributeType() == FbxNodeAttribute::eSkeleton) { return; } CU::Matrix44f fixMatrix; fixMatrix = CU::Matrix44<float>::CreateReflectionMatrixAboutAxis(CU::Vector3f(1, 0, 0)); FbxAMatrix lGeometryOffset = GetGeometry(aNode); FbxAMatrix lGlobalOffPosition = lGlobalPosition * lGeometryOffset; FbxAMatrix lRotationOffset = GetRotaionPivot(aNode); aModel->myRotationPivot = fixMatrix * CreateMatrix(lRotationOffset) * fixMatrix; aModel->myOrientation = fixMatrix * CreateMatrix(lGlobalOffPosition) * fixMatrix; int boneId = -1; if (lNodeAttribute) { if(lNodeAttribute->GetAttributeType() == FbxNodeAttribute::eMesh) { aModel->myData = new ModelData(); aModel->myData->myLayout.Init(8); // Geometry offset. // it is not inherited by the children. FillData(aModel->myData, aNode, &aAnimation); } else if (lNodeAttribute->GetAttributeType() == FbxNodeAttribute::eLight) { FBXLight* newLight = new FBXLight(); FbxLight* light = aNode->GetLight(); newLight->myIntensity = static_cast<float>(light->Intensity); auto color = light->Color.Get(); newLight->myColor = CU::Vector3<float>(static_cast<float>(color.mData[0]), static_cast<float>(color.mData[1]), static_cast<float>(color.mData[2])); auto type = light->LightType.Get(); if (type == FbxLight::eDirectional) { newLight->myType = EDirectionalLight; } else if (type == FbxLight::ePoint) { newLight->myType = EPointLight; } else if (type == FbxLight::eSpot) { newLight->myInnerAngle = static_cast<float>(light->InnerAngle); newLight->myOuterAngle = static_cast<float>(light->OuterAngle); } aModel->myLight = newLight; } else if (lNodeAttribute->GetAttributeType() == FbxNodeAttribute::eCamera) { aModel->myCamera = new Camera(); auto orgCamera = aNode->GetCamera(); aModel->myCamera->myFov = static_cast<float>(orgCamera->FieldOfViewY); } FbxTimeSpan animationInterval; if (aNode->GetAnimationInterval(animationInterval)) { aModel->myAnimationCurves = new AnimationCurves(); aModel->myAnimationCurves->myRotationCurve[0] = aNode->LclRotation.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_COMPONENT_X); aModel->myAnimationCurves->myRotationCurve[1] = aNode->LclRotation.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y); aModel->myAnimationCurves->myRotationCurve[2] = aNode->LclRotation.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z); aModel->myAnimationCurves->myRotationCurve[3] = aNode->LclRotation.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_ROTATION); aModel->myAnimationCurves->myScalingCurve[0] = aNode->LclScaling.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_COMPONENT_X); aModel->myAnimationCurves->myScalingCurve[1] = aNode->LclScaling.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y); aModel->myAnimationCurves->myScalingCurve[2] = aNode->LclScaling.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z); aModel->myAnimationCurves->myTtranslationCurve[0] = aNode->LclTranslation.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_COMPONENT_X); aModel->myAnimationCurves->myTtranslationCurve[1] = aNode->LclTranslation.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y); aModel->myAnimationCurves->myTtranslationCurve[2] = aNode->LclTranslation.GetCurve(aCurrentAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z); int nrOfKeys = 0; nrOfKeys; float startTime = (float)animationInterval.GetStart().GetSecondDouble(); float endTime = (float)animationInterval.GetStop().GetSecondDouble(); aModel->myAnimatedOrientation.Init((int)((endTime - startTime) / (1.0f / 24.0f))); for (float currentTime = startTime; currentTime < endTime; currentTime += 1.0f / 24.0f) { FbxTime time; time.SetSecondDouble(currentTime); KeyFrame animationFrame; animationFrame.myTime = currentTime; animationFrame.myMatrix = fixMatrix * CreateMatrix(aNode->EvaluateLocalTransform(time)) * fixMatrix; aModel->myAnimatedOrientation.Add(animationFrame); } } } const int lChildCount = aNode->GetChildCount(); if(lChildCount > 0) { aModel->myChilds.Init(lChildCount); for (int lChildIndex = 0; lChildIndex < lChildCount; ++lChildIndex) { aModel->myChilds.Add(new FbxModelData()); LoadNodeRecursive(aModel->myChilds.GetLast(), aAnimation, aNode->GetChild(lChildIndex), lGlobalPosition , aPose, aCurrentAnimLayer, boneId); } } }