1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
4 #include "FacialEditorDialog.h"
6 #include <CryAnimation/IFacialAnimation.h> // for handling the actual facial sequence and it's data
7 #include "Dialogs/GenericSelectItemDialog.h" // access to the listbox dialog class
9 #ifdef CRY_ENABLE_FBX_SDK
10 #include <fbxsdk.h> // for importing FBX files
11 #include <fbxsdk/fileio/fbxiosettings.h> // for importing FBX files
12 #include <fbxsdk/scene/geometry/fbxdeformer.h> // to handle the blendshapes
13 #include "Controls/QuestionDialog.h"
15 //////////////////////////////////////////////////////////////////////////
17 FbxScene
* LoadFBXFile(const char* filename
)
19 // Create the FBX SDK manager
20 FbxManager
* lSdkManager
= FbxManager::Create();
22 // Create an IOSettings object.
23 FbxIOSettings
* ios
= FbxIOSettings::Create(lSdkManager
, IOSROOT
);
24 lSdkManager
->SetIOSettings(ios
);
26 // Create a new scene so it can be populated by the imported file.
27 FbxScene
* lScene
= FbxScene::Create(lSdkManager
, "myScene");
29 // Create an importer.
31 FbxImporter
* lImporter
= FbxImporter::Create(lSdkManager
, "");
32 if (!lSdkManager
->GetIOPluginRegistry()->DetectReaderFileFormat(filename
, lFileFormat
))
34 // Unrecognizable file format. Try to fall back to FbxImporter::eFBX_BINARY
35 lFileFormat
= lSdkManager
->GetIOPluginRegistry()->FindReaderIDByDescription("FBX binary (*.fbx)");
39 // Initialize the importer.
40 bool lImportStatus
= lImporter
->Initialize(filename
, lFileFormat
/*, lSdkManager->GetIOSettings()*/);
43 CQuestionDialog::SWarning(QObject::tr(""), QObject::tr(lImporter
->GetStatus().GetErrorString()));
44 CryLog("[ERROR] Facial Editor: Could not import FBX file. Importer return error: '%s'", lImporter
->GetStatus().GetErrorString());
48 // Import the contents of the file into the scene.
49 lImporter
->Import(lScene
);
51 // The file has been imported; we can get rid of the importer.
57 //////////////////////////////////////////////////////////////////////////
59 FbxAnimStack
* GetAnimStackToUse(FbxScene
* lScene
, CWnd
* parent
)
61 // Is there at least one animation in the FBX file?
62 int numAnimStacks
= lScene
->GetSrcObjectCount(FbxCriteria::ObjectType(FbxAnimStack::ClassId
));
63 if (numAnimStacks
<= 0)
65 CQuestionDialog::SWarning(QObject::tr(""), QObject::tr("FBX file has no animation. Aborting import."));
66 CryLog("[ERROR] Facial Editor: FBX file has no animation. Aborting import.");
69 // CryLogAlways("Found %i AnimStacks", numAnimStacks);
71 FbxAnimStack
* pAnimStack
= FbxCast
<FbxAnimStack
>(lScene
->GetSrcObject(FbxCriteria::ObjectType(FbxAnimStack::ClassId
), 0));
73 // Is there more than one animation in the FBX file? Let the user choose which one to use
74 if (numAnimStacks
> 1)
76 std::vector
<CString
> items
;
77 for (int n
= 0; n
< numAnimStacks
; ++n
)
79 FbxAnimStack
* tempAnimStack
= FbxCast
<FbxAnimStack
>(lScene
->GetSrcObject(FbxCriteria::ObjectType(FbxAnimStack::ClassId
), n
));
80 CString animName
= tempAnimStack
->GetName();
81 items
.push_back(animName
);
82 // CryLogAlways("AnimStack #%i: %s", n, pAnimStack->GetName());
85 CGenericSelectItemDialog
dlg(parent
);
86 dlg
.SetTitle("Select Animation to Import");
88 if (dlg
.DoModal() != IDOK
)
90 CString selectStack
= dlg
.GetSelectedItem();
92 // Find the one with the right name (cannot access by index of selected item, since the listbox is auto-sorted)
93 for (int n
= 0; n
< numAnimStacks
; ++n
)
95 pAnimStack
= FbxCast
<FbxAnimStack
>(lScene
->GetSrcObject(FbxCriteria::ObjectType(FbxAnimStack::ClassId
), n
));
96 if (selectStack
.Compare(pAnimStack
->GetName()) == 0)
101 // we can only import one animation layer
102 if (pAnimStack
->GetMemberCount(FbxCriteria::ObjectType(FbxAnimLayer::ClassId
)) > 1)
104 CQuestionDialog::SWarning(QObject::tr(""), QObject::tr("Found more than one layer in Animation Stack.\nOnly the first layer will be imported."));
105 CryLog("[Warning] Facial Editor: Found more than one animation layer in FBX file. Only first layer will be imported!");
108 //CryLogAlways("Using AnimStack %s", pAnimStack->GetName());
110 // Get the animation to import
114 //////////////////////////////////////////////////////////////////////////
116 FbxNode
* GetMeshToUse(FbxNode
* lRootNode
, CWnd
* parent
)
118 // Traverse through all nodes and find the meshes
119 // If more than one is found, let the user pick the one he wants
120 std::vector
<CString
> items
;
121 int childCount
= lRootNode
->GetChildCount();
122 for (int i
= 0; i
< childCount
; ++i
)
124 FbxNode
* node
= lRootNode
->GetChild(i
);
125 if (node
->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eMesh
)
127 CString meshName
= node
->GetNameOnly();
128 items
.push_back(meshName
);
131 if (items
.size() < 0)
133 CQuestionDialog::SCritical(QObject::tr(""), QObject::tr("FBX file contains no meshes. Aborting import."));
134 CryLog("[ERROR] Facial Editor: FBX file contains no meshes. Aborting import.");
138 CString meshToUse
= items
[0];
139 if (items
.size() > 1)
141 CGenericSelectItemDialog
dlg(parent
);
142 dlg
.SetTitle("Select Model to import animation from:");
144 if (dlg
.DoModal() != IDOK
)
146 meshToUse
= dlg
.GetSelectedItem();
149 for (int i
= 0; i
< childCount
; ++i
)
151 FbxNode
* pMeshNode
= lRootNode
->GetChild(i
);
152 if (pMeshNode
->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eMesh
)
154 if (meshToUse
.Compare(pMeshNode
->GetNameOnly()) == 0)
162 #endif //CRY_ENABLE_FBX_SDK
164 //////////////////////////////////////////////////////////////////////////
165 void CFacialEditorDialog::LoadFBXToSequence(const char* filename
)
167 // Create a sequence and add a folder to it
168 IFacialAnimSequence
* newSequence
= GetISystem()->GetIAnimationSystem()->GetIFacialAnimation()->CreateSequence();
170 // Import FBX and convert to sequence
171 if (!ImportFBXAsSequence(newSequence
, filename
))
173 SAFE_DELETE(newSequence
);
177 // Prompt user whether he wants to merge with the current sequence or replace it
178 if (m_pContext
->GetSequence() == NULL
|| QDialogButtonBox::StandardButton::Yes
== CQuestionDialog::SQuestion(QObject::tr(""), QObject::tr("Do you want to replace the current sequence?\n(Select No to merge with current sequence)")))
181 m_pContext
->SetSequence(newSequence
);
186 m_pContext
->ImportSequence(newSequence
, this);
190 //////////////////////////////////////////////////////////////////////////
192 bool CFacialEditorDialog::ImportFBXAsSequence(IFacialAnimSequence
* newSequence
, const char* filename
)
194 #ifdef CRY_ENABLE_FBX_SDK
195 // Load the relevant data and put it into a temporary XML structure that the facial sequence can load from
196 FbxScene
* pScene
= LoadFBXFile(filename
);
199 // Get the root node of the FBX scene
200 FbxNode
* lRootNode
= pScene
->GetRootNode();
202 // Retrieve the anim stack to use and display a listbox dialog if there is more than one
203 FbxAnimStack
* pAnimStack
= GetAnimStackToUse(pScene
, this);
204 if (pAnimStack
== NULL
)
206 // only the first layer of animation will be imported
207 FbxAnimLayer
* pAnimLayer
= (FbxAnimLayer
*)pAnimStack
->GetMember(FbxCriteria::ObjectType(FbxAnimLayer::ClassId
), 0);
209 // Get the mesh model to import the animation from, display a listbox if there is more than one
210 FbxNode
* pMeshNode
= GetMeshToUse(lRootNode
, this);
211 if (pMeshNode
== NULL
)
214 // Safety check whether there actually ARE any morphs on this mesh
215 FbxMesh
* pMesh
= pMeshNode
->GetMesh();
216 if (pMesh
->GetDeformerCount(FbxDeformer::eBlendShape
) <= 0 || pMesh
->GetShapeCount() <= 0)
218 CQuestionDialog::SCritical(QObject::tr(""), QObject::tr("Found no deformer/morpher and/or no blendshapes in selected model. Aborting import."));
219 CryLog("[Error] Facial Editor FBX Importer: Found no deformer/morpher or no blendshapes in selected model. Aborting import");
223 // Create an FSQ format XML structure to load the data into
224 XmlNodeRef xmlRoot
= XmlHelpers::CreateXmlNode("FacialSequence");
225 // add all the nodes and attributes the Facial Editor requires as a minimum
226 xmlRoot
->setAttr("Flags", "0");
228 // Get the animation start and end times
229 double secondStartTime
= pAnimStack
->GetLocalTimeSpan().GetStart().GetSecondDouble();
230 double secondEndTime
= pAnimStack
->GetLocalTimeSpan().GetStop().GetSecondDouble();
231 xmlRoot
->setAttr("StartTime", secondStartTime
);
232 xmlRoot
->setAttr("EndTime", secondEndTime
);
233 double duration
= secondEndTime
- secondStartTime
;
234 //CryLogAlways("Found Anim Stack to contain animation from %.2f to %.2f seconds (duration %.4f seconds)", secondStartTime, secondEndTime, duration);
236 // Create a folder, so that all imported data goes into a subfolder
237 // This way things will stay organized even if the scene is merged with an existing one
238 // Create the folder (=channel/group) into which this data is loaded
239 XmlNodeRef channelNode
= xmlRoot
->createNode("Channel");
240 channelNode
->setAttr("Flags", "1"); // = IFacialAnimChannel::FLAG_GROUP
241 xmlRoot
->addChild(channelNode
);
243 string channelName
= "FBX_Import_";
244 channelName
.append(pMeshNode
->GetNameOnly());
245 channelName
.append("_");
246 channelName
.append(pScene
->GetName());
247 channelNode
->setAttr("Name", channelName
);
249 // Go through all the blendshape deformers, and through all their blendshapes, get their animation curves and put them into the XML structure
250 bool warningAboutMultiTargetsShowed
= false;
251 int deformerCount
= pMesh
->GetDeformerCount(FbxDeformer::eBlendShape
);
252 for (int deformerIdx
= 0; deformerIdx
< deformerCount
; ++deformerIdx
)
254 FbxBlendShape
* pDeformer
= (FbxBlendShape
*)pMesh
->GetDeformer(deformerIdx
, FbxDeformer::eBlendShape
);
255 int blendShapeChannelCount
= pDeformer
->GetBlendShapeChannelCount();
256 for (int blendShapeChannelIdx
= 0; blendShapeChannelIdx
< blendShapeChannelCount
; ++blendShapeChannelIdx
)
258 FbxBlendShapeChannel
* pChannel
= pDeformer
->GetBlendShapeChannel(blendShapeChannelIdx
);
262 FbxAnimCurve
* pAnimCurve
= pMesh
->GetShapeChannel(deformerIdx
, blendShapeChannelIdx
, pAnimLayer
);
266 // Only the first target shape in a blendchannel will be evaluated. The rare case of multiple target shapes in a channel is not supported.
267 FbxShape
* pShape
= pMesh
->GetShape(deformerIdx
, blendShapeChannelIdx
, 0);
268 string shapeName
= pShape
->GetNameOnly();
270 if (pChannel
->GetTargetShapeCount() > 1 && !warningAboutMultiTargetsShowed
)
272 CQuestionDialog::SWarning(QObject::tr(""), QObject::tr("Found one or more blend channels that contain more that one morph target shape.\nThis is not supported, only the first shape will be evaluated."));
273 CryLog("[WARNING] Facial Editor: Found one or more blend channels that contain more that one morph target shape.\nThis is not supported, only the first shape will be evaluated.");
274 // only show this warning once per import, to avoid making the user click a hundred times
275 warningAboutMultiTargetsShowed
= true;
278 int keyCount
= pAnimCurve
->KeyGetCount();
279 //CryLogAlways("Evaluating blendchannel %s. Found %i keys", pChannel->GetName(), keyCount);
281 // Create a channel in the xml for this blendshape
282 XmlNodeRef blendShapeChannel
= channelNode
->createNode("Channel");
283 blendShapeChannel
->setAttr("Flags", "0");
284 blendShapeChannel
->setAttr("Name", shapeName
);
286 // all keys are stored in one long string inside the xml
287 CString animKeys
= "";
292 // Optimization - don't create a channel if the morph has only 0 keys
293 bool allZeroKeys
= true;
295 for (int keyIdx
= 0; keyIdx
< keyCount
; ++keyIdx
)
297 FbxAnimCurveKey animKey
= pAnimCurve
->KeyGet(keyIdx
);
298 FbxAnimCurveDef::EInterpolationType keyInterpolType
= animKey
.GetInterpolation();
299 keyTime
= animKey
.GetTime().GetSecondDouble();
300 keyValue
= animKey
.GetValue() / 100.0f
; // Morphs go from 0..100, but Facial Editor from 0..1
302 // Convert the above data into an FSQ style key entry (still missing the tangent/interpolation type)
303 // There is no easy way to convert the interpolation type, so we will use zeroed tangents for now (Flags 18) - which matches the default in Max closest
304 curKey
.Format("%f:%f:18,", keyTime
, keyValue
);
305 animKeys
.Append(curKey
);
307 if (keyValue
!= 0.0f
)
311 // If the last key is not at the end of the time, add another key there with the same values
312 if (keyTime
< secondEndTime
)
314 curKey
.Format("%f:%f:18,", secondEndTime
, keyValue
);
315 animKeys
.Append(curKey
);
318 // Create a node for the actual keys on the timeline
321 channelNode
->addChild(blendShapeChannel
);
322 XmlNodeRef splineNode
= blendShapeChannel
->createNode("Spline");
323 splineNode
->setAttr("Keys", animKeys
);
324 blendShapeChannel
->addChild(splineNode
);
329 // Check whether any keys for anything have been loaded (if the channel group has any children at all)
330 // and give a warning that no blendshape animation could be found in TAKE or LAYER
331 if (channelNode
->getChildCount() == 0)
333 CQuestionDialog::SCritical(QObject::tr(""), QObject::tr("Animation Take contained no keyframes for any blendshapes (only layer 0 was evaluated). Aborting Import."));
334 CryLog("[WARNING] Facial Editor: Animation Take contained no keyframes for any blendshapes (only layer 0 was evaluated). Aborting Import");
338 // Now that the XML structure is prepared, use the internal serialize functionality to load the data
339 newSequence
->Serialize(xmlRoot
, true);
341 // Get the name of the scene and set it as the sequence name
342 newSequence
->SetName(pScene
->GetNameOnly());
344 // Save out the FSQ as a temp file. This helps with debugging later (if this is enabled in console)
345 if (gEnv
->pConsole
->GetCVar("fe_fbx_savetempfile")->GetIVal() != 0)
346 xmlRoot
->saveToFile("Animations/temp/Converted_FBX_Temp.fsq");
350 #else //CRY_ENABLE_FBX_SDK
351 CryWarning(VALIDATOR_MODULE_EDITOR
, VALIDATOR_WARNING
, "FBX SDK not supported.");
353 #endif //CRY_ENABLE_FBX_SDK
356 //////////////////////////////////////////////////////////////////////////