!XT (BREAK-16) (Sandbox) Remove double-newlines at the end of files.
[CRYENGINE.git] / Code / Sandbox / Plugins / FacialEditorPlugin / FBX_Import.cpp
blob32ab40f5964d251301a32c1cd30990d4ec01b25b
1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
3 #include "StdAfx.h"
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.
30 int lFileFormat = -1;
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()*/);
41 if (!lImportStatus)
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());
45 return NULL;
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.
52 lImporter->Destroy();
54 return lScene;
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.");
67 return NULL;
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");
87 dlg.SetItems(items);
88 if (dlg.DoModal() != IDOK)
89 return NULL;
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)
97 break;
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
111 return pAnimStack;
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.");
135 return NULL;
138 CString meshToUse = items[0];
139 if (items.size() > 1)
141 CGenericSelectItemDialog dlg(parent);
142 dlg.SetTitle("Select Model to import animation from:");
143 dlg.SetItems(items);
144 if (dlg.DoModal() != IDOK)
145 return NULL;
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)
155 return pMeshNode;
159 return NULL;
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);
174 return;
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)")))
180 // Replace
181 m_pContext->SetSequence(newSequence);
183 else
185 // Merge
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);
197 if (pScene == NULL)
198 return false;
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)
205 return false;
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)
212 return false;
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");
220 return false;
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);
242 // Name the folder
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);
259 if (!pChannel)
260 continue;
262 FbxAnimCurve* pAnimCurve = pMesh->GetShapeChannel(deformerIdx, blendShapeChannelIdx, pAnimLayer);
263 if (!pAnimCurve)
264 continue;
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 = "";
288 string curKey = "";
289 double keyTime = 0;
290 float keyValue = 0;
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)
308 allZeroKeys = false;
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
319 if (!allZeroKeys)
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");
335 return false;
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");
348 return true;
350 #else //CRY_ENABLE_FBX_SDK
351 CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, "FBX SDK not supported.");
352 return false;
353 #endif //CRY_ENABLE_FBX_SDK
356 //////////////////////////////////////////////////////////////////////////