WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
FBXExporter.cpp
Go to the documentation of this file.
1/*----------------------------------------------------------------------*\
2| This file is part of WoW Model Viewer |
3| |
4| WoW Model Viewer is free software: you can redistribute it and/or |
5| modify it under the terms of the GNU General Public License as |
6| published by the Free Software Foundation, either version 3 of the |
7| License, or (at your option) any later version. |
8| |
9| WoW Model Viewer is distributed in the hope that it will be useful, |
10| but WITHOUT ANY WARRANTY; without even the implied warranty of |
11| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12| GNU General Public License for more details. |
13| |
14| You should have received a copy of the GNU General Public License |
15| along with WoW Model Viewer. |
16| If not, see <http://www.gnu.org/licenses/>. |
17\*----------------------------------------------------------------------*/
18
19/*
20 * FBXExporter.cpp
21 *
22 * Created on: 13 june 2015
23 * Copyright: 2015 , WoW Model Viewer (http://wowmodelviewer.net)
24 */
25
26#include "FBXExporter.h"
27
28#ifdef _WIN32
29#include <windows.h>
30#endif
31#include <filesystem>
32#include <mutex>
33
34#include "FBXHeaders.h"
35#include "FBXAnimExporter.h"
36#include "ModelRenderPass.h"
37#include "WoWModel.h"
38
39#include "GlobalSettings.h"
40
41FBXExporter::FBXExporter() : m_p_manager(nullptr), m_p_scene(nullptr), m_p_model(nullptr),
42 m_p_meshNode(nullptr), m_p_skeletonNode(nullptr)
43{
45}
46
47std::wstring FBXExporter::menuLabel() const
48{
49 return L"FBX...";
50}
51
52std::wstring FBXExporter::fileSaveTitle() const
53{
54 return L"Save FBX file";
55}
56
57std::wstring FBXExporter::fileSaveFilter() const
58{
59 return L"FBX files (*.fbx)|*.fbx";
60}
61
62bool FBXExporter::exportModel(Model* model, std::wstring target)
63{
64 reset();
65
66 m_p_model = dynamic_cast<WoWModel*>(model);
67
68 if (!m_p_model)
69 return false;
70
71 m_filename = target;
72 m_p_manager = FbxManager::Create();
73 if (!m_p_manager)
74 {
75 LOG_ERROR << "Unable to create the FBX SDK manager";
76 return false;
77 }
78
79 FbxIOSettings* ios = FbxIOSettings::Create(m_p_manager, IOSROOT);
80 m_p_manager->SetIOSettings(ios);
81
82 // ensure that textures are embed in final fbx file
83 ios->SetBoolProp(EXP_FBX_MATERIAL, true);
84 ios->SetBoolProp(EXP_FBX_TEXTURE, true);
85 ios->SetBoolProp(EXP_FBX_EMBEDDED, true);
86 ios->SetBoolProp(EXP_FBX_SHAPE, true);
87 ios->SetBoolProp(EXP_FBX_GOBO, true);
88 ios->SetBoolProp(EXP_FBX_ANIMATION, false);
89 ios->SetBoolProp(EXP_FBX_GLOBAL_SETTINGS, true);
90
91 FbxExporter* exporter = nullptr;
92
93 m_fileVersion = FBX_2014_00_COMPATIBLE;
94
95 std::string filenameUtf8;
96 if (!m_filename.empty())
97 {
98 int n = WideCharToMultiByte(CP_UTF8, 0, m_filename.c_str(), static_cast<int>(m_filename.size()), nullptr, 0, nullptr, nullptr);
99 filenameUtf8.resize(n);
100 WideCharToMultiByte(CP_UTF8, 0, m_filename.c_str(), static_cast<int>(m_filename.size()), filenameUtf8.data(), n, nullptr, nullptr);
101 }
102 if (FBXHeaders::createFBXHeaders(m_fileVersion, filenameUtf8, m_p_manager, exporter,
103 m_p_scene) == false)
104 return false;
105
106 // add some info to exported scene
107 FbxDocumentInfo* sceneInfo = FbxDocumentInfo::Create(m_p_manager, "SceneInfo");
108 {
109 std::string modelName = m_p_model->name();
110 auto sp = modelName.rfind('/');
111 if (sp != std::string::npos)
112 modelName = modelName.substr(sp + 1);
113 sceneInfo->mTitle = modelName.c_str();
114 }
115 {
116 std::wstring appName = GLOBALSETTINGS.appName();
117 if (!appName.empty())
118 {
119 int n = WideCharToMultiByte(CP_UTF8, 0, appName.c_str(), static_cast<int>(appName.size()), nullptr, 0, nullptr, nullptr);
120 std::string s(n, '\0');
121 WideCharToMultiByte(CP_UTF8, 0, appName.c_str(), static_cast<int>(appName.size()), s.data(), n, nullptr, nullptr);
122 sceneInfo->mAuthor = s.c_str();
123 }
124 }
125 {
126 std::wstring appVer = GLOBALSETTINGS.appVersion();
127 if (!appVer.empty())
128 {
129 int n = WideCharToMultiByte(CP_UTF8, 0, appVer.c_str(), static_cast<int>(appVer.size()), nullptr, 0, nullptr, nullptr);
130 std::string s(n, '\0');
131 WideCharToMultiByte(CP_UTF8, 0, appVer.c_str(), static_cast<int>(appVer.size()), s.data(), n, nullptr, nullptr);
132 sceneInfo->mRevision = s.c_str();
133 }
134 }
135 m_p_scene->SetSceneInfo(sceneInfo);
136
137 // export main model mesh
138 // follow FBX SDK example (ExportScene01) for organization
139 try
140 {
141 createMeshes();
142 LOG_INFO << "Meshes successfully created";
143
145 LOG_INFO << "Materials successfully created";
146
148 LOG_INFO << "Skeleton successfully created";
149
151
153 //FBXHeaders::storeRestPose(m_p_scene, m_p_skeletonNode);
154
155 for (const auto it : *m_p_model)
156 {
157 std::map<POSITION_SLOTS, WoWModel*> itemModels = it->models();
158 if (!itemModels.empty())
159 {
160 for (const auto& itemModel : itemModels)
161 {
162 if (m_attachBoneClusters.find(itemModel.first) != m_attachBoneClusters.end() && m_attachMeshNodes.
163 find(itemModel.first) != m_attachMeshNodes.end())
165 m_attachMeshNodes[itemModel.first]);
166 //if (m_attachSkeletonNode.find(it->first) != m_attachSkeletonNode.end())
167 //FBXHeaders::storeRestPose(m_p_scene, m_attachSkeletonNode[it->first]);
168 }
169 }
170 }
171 }
172 catch (const std::exception& ex)
173 {
174 LOG_ERROR << "Error during export:" << ex.what();
175 return false;
176 }
177
178 if (!exporter->Export(m_p_scene))
179 {
180 LOG_ERROR << "Unable to export FBX scene";
181 return false;
182 }
183 LOG_INFO << "Model successfully created";
184
185 // delete texture files created during export
186 for (auto it : m_texturesToExport)
187 _wremove((it.first).c_str());
188
189 // Export bulk animations
190 if (m_animsToExport.size() > 0)
191 {
192 try
193 {
195 LOG_INFO << "Animations successfully created";
196 }
197 catch (const std::exception& ex)
198 {
199 LOG_ERROR << "Error during bulk animation export:" << ex.what();
200 return false;
201 }
202 }
203
204 LOG_INFO << "FBX scene successfully exported";
205 return true;
206}
207
209{
211
212 FbxNode* root_node = m_p_scene->GetRootNode();
213 root_node->AddChild(m_p_meshNode);
214
215 for (const auto it : *m_p_model)
216 {
217 std::map<POSITION_SLOTS, WoWModel*> itemModels = it->models();
218 if (!itemModels.empty())
219 {
220 for (const auto& It : itemModels)
221 {
222 WoWModel* itemModel = It.second;
223 LOG_INFO << "Found attached item:" << itemModel->modelname.c_str();
224
225 const int l = m_p_model->attLookup[It.first];
226 glm::mat4 m;
227 if (l > -1)
228 {
229 m = m_p_model->bones[m_p_model->atts[l].bone].mat;
230 }
231
232 FbxNode* itemMeshNode = FBXHeaders::createMesh(m_p_manager, m_p_scene, itemModel, m);
233 m_attachMeshNodes[It.first] = itemMeshNode;
234
235 root_node->AddChild(itemMeshNode);
236 }
237 }
238 }
239}
240
242{
244
245 FbxNode* root_node = m_p_scene->GetRootNode();
246 root_node->AddChild(m_p_skeletonNode);
247
248 for (const auto it : *m_p_model)
249 {
250 std::map<POSITION_SLOTS, WoWModel*> itemModels = it->models();
251 if (!itemModels.empty())
252 {
253 for (const auto& It : itemModels)
254 {
255 WoWModel* itemModel = It.second;
256 if (itemModel->animated == false || itemModel->bones.size() < 2)
257 continue;
258
259 FbxNode* itemSkeleton;
260 std::map<int, FbxNode*> itemboneNodes;
261
262 FBXHeaders::createSkeleton(itemModel, m_p_scene, itemSkeleton, itemboneNodes);
263
264 m_attachSkeletonNode[It.first] = itemSkeleton;
265 m_attachBoneNodes[It.first] = itemboneNodes;
266 root_node->AddChild(itemSkeleton);
267 }
268 }
269 }
270}
271
273{
274 // create clusters
275 for (const auto it : m_boneNodes)
276 {
277 FbxCluster* cluster = FbxCluster::Create(m_p_scene, "");
278 m_boneClusters.push_back(cluster);
279 cluster->SetLink(it.second);
280 cluster->SetLinkMode(FbxCluster::ELinkMode::eNormalize);
281 }
282
283 // define control points
284 int i = 0;
285 for (const auto& it : m_p_model->origVertices)
286 {
287 for (size_t j = 0; j < 4; j++)
288 {
289 if (it.weights[j] > 0)
290 m_boneClusters[it.bones[j]]->AddControlPointIndex((int)i, static_cast<double>(it.weights[j]) / 255.0);
291 }
292 i++;
293 }
294
295 // set initial matrices
296 FbxAMatrix matrix = m_p_meshNode->EvaluateGlobalTransform();
297 for (const auto it : m_boneClusters)
298 {
299 it->SetTransformMatrix(matrix);
300 }
301
302 // set link matrices
303 std::vector<FbxCluster*>::iterator clusterIt = m_boneClusters.begin();
304 for (const auto it : m_boneNodes)
305 {
306 matrix = it.second->EvaluateGlobalTransform();
307 (*clusterIt)->SetTransformLinkMatrix(matrix);
308 ++clusterIt;
309 }
310
311 // add cluster to skin
312 FbxGeometry* lMeshAttribute = static_cast<FbxGeometry*>(m_p_meshNode->GetNodeAttribute());
313 FbxSkin* skin = FbxSkin::Create(m_p_scene, "");
314
315 for (const auto it : m_boneClusters)
316 skin->AddCluster(it);
317
318 lMeshAttribute->AddDeformer(skin);
319}
320
322{
323 if (m_boneNodes.empty())
324 {
325 LOG_ERROR << "No bone in skeleton, so no animation will be exported";
326 return;
327 }
328
329 LOG_INFO << "Num animations to export:" << m_animsToExport.size();
330
332 {
333 LOG_ERROR << "An error occured while exporting Animation files.";
334 return;
335 }
336}
337
338// Creates separate FBX files for each animation, in a folder with the original FBX file's name.
340{
341 std::map<int, std::wstring> animsMap = m_p_model->getAnimsMap();
342
343 /**** NOTICE! ****//*
344
345 Animation export runs single-threaded on purpose: the FBX SDK itself is not thread-safe!
346
347 We will have to develop our own threaded wrapper for FBX, or copy the FBX data, or find some other method to make the SDK work in a thread-safe manner before we can re-enable multi-threaded animation exporting.
348 Without doing this, WMV will crash while exporting animations, as the data becomes cross-contaminated, and thus, invalidated.
349
350 */ /**** ****/
351
352 for (const auto it : m_animsToExport)
353 {
354 std::lock_guard<std::mutex> locker(m_mutex);
355 const ModelAnimation curAnimation = m_p_model->anims[it];
356 FBXAnimExporter* exporter = new FBXAnimExporter();
357 std::string fnUtf8, anUtf8;
358 if (!m_filename.empty())
359 {
360 int n = WideCharToMultiByte(CP_UTF8, 0, m_filename.c_str(), static_cast<int>(m_filename.size()), nullptr, 0, nullptr, nullptr);
361 fnUtf8.resize(n);
362 WideCharToMultiByte(CP_UTF8, 0, m_filename.c_str(), static_cast<int>(m_filename.size()), fnUtf8.data(), n, nullptr, nullptr);
363 }
364 {
365 const std::wstring& wAnim = animsMap[curAnimation.animID];
366 if (!wAnim.empty())
367 {
368 int n = WideCharToMultiByte(CP_UTF8, 0, wAnim.c_str(), static_cast<int>(wAnim.size()), nullptr, 0, nullptr, nullptr);
369 anUtf8.resize(n);
370 WideCharToMultiByte(CP_UTF8, 0, wAnim.c_str(), static_cast<int>(wAnim.size()), anUtf8.data(), n, nullptr, nullptr);
371 }
372 }
373 exporter->setValues(m_fileVersion, fnUtf8, anUtf8, m_p_model, m_boneClusters,
374 m_p_meshNode, it);
375 exporter->run();
376 delete exporter;
377 }
378
379 return true;
380}
381
382// Create materials.
384{
385 for (unsigned int i = 0; i < m_p_model->passes.size(); i++)
386 {
387 ModelRenderPass* pass = m_p_model->passes[i];
388 if (pass->init())
389 {
390 // Build material name.
391 std::string mn = m_p_model->name();
392 auto sp = mn.rfind('/');
393 if (sp != std::string::npos)
394 mn = mn.substr(sp + 1);
395 FbxString mtrl_name = mn.c_str();
396 mtrl_name.Append("_", 1);
397 char tmp[32];
398 _itoa(static_cast<int>(i), tmp, 10);
399 mtrl_name.Append(tmp, strlen(tmp));
400
401 // Create material.
402 FbxString shading_name = "Phong";
403 FbxSurfacePhong* material = FbxSurfacePhong::Create(m_p_manager, mtrl_name.Buffer());
404 material->Ambient.Set(FbxDouble3(0.7, 0.7, 0.7));
405
406 std::string tex = m_p_model->getNameForTex(pass->tex);
407
408 std::string tex_name = tex;
409 auto slashPos = tex_name.rfind('/');
410 if (slashPos != std::string::npos)
411 tex_name = tex_name.substr(slashPos + 1);
412 if (auto blpPos = tex_name.find(".blp"); blpPos != std::string::npos)
413 tex_name.replace(blpPos, 4, ".png");
414
415 std::string filenameNarrow;
416 if (!m_filename.empty())
417 {
418 int n = WideCharToMultiByte(CP_UTF8, 0, m_filename.c_str(), static_cast<int>(m_filename.size()), nullptr, 0, nullptr, nullptr);
419 filenameNarrow.resize(n);
420 WideCharToMultiByte(CP_UTF8, 0, m_filename.c_str(), static_cast<int>(m_filename.size()), filenameNarrow.data(), n, nullptr, nullptr);
421 }
422 std::string tex_fullpath = filenameNarrow;
423 auto bsPos = tex_fullpath.rfind('\\');
424 if (bsPos != std::string::npos)
425 tex_fullpath = tex_fullpath.substr(0, bsPos + 1) + tex_name;
426
427 m_texturesToExport[std::wstring(tex_fullpath.begin(), tex_fullpath.end())] = m_p_model->getGLTexture(pass->tex);
428
429 FbxFileTexture* texture = FbxFileTexture::Create(m_p_manager, tex_name.c_str());
430 texture->SetFileName(tex_fullpath.c_str());
431 texture->SetTextureUse(FbxTexture::eStandard);
432 texture->SetMappingType(FbxTexture::eUV);
433 texture->SetMaterialUse(FbxFileTexture::eModelMaterial);
434 texture->SetSwapUV(false);
435 texture->SetTranslation(0.0, 0.0);
436 texture->SetScale(1.0, 1.0);
437 texture->SetRotation(0.0, 0.0);
438 texture->UVSet.Set(FbxString("DiffuseUV"));
439 material->Diffuse.ConnectSrcObject(texture);
440
441 // Add material to the scene.
442 m_p_meshNode->AddMaterial(material);
443 }
444 }
445
446 for (const auto it : *m_p_model)
447 {
448 std::map<POSITION_SLOTS, WoWModel*> itemModels = it->models();
449 if (!itemModels.empty())
450 {
451 for (const auto& itemModel : itemModels)
452 {
453 WoWModel* model = itemModel.second;
454 for (unsigned int i = 0; i < model->passes.size(); i++)
455 {
456 ModelRenderPass* pass = model->passes[i];
457 if (pass->init())
458 {
459 // Build material name.
460 std::string mn2 = model->name();
461 auto sp2 = mn2.rfind('/');
462 if (sp2 != std::string::npos)
463 mn2 = mn2.substr(sp2 + 1);
464 FbxString mtrl_name = mn2.c_str();
465 mtrl_name.Append("_", 1);
466 char tmp[32];
467 _itoa(static_cast<int>(i), tmp, 10);
468 mtrl_name.Append(tmp, strlen(tmp));
469
470 // Create material.
471 FbxString shading_name = "Phong";
472 FbxSurfacePhong* material = FbxSurfacePhong::Create(m_p_manager, mtrl_name.Buffer());
473 material->Ambient.Set(FbxDouble3(0.7, 0.7, 0.7));
474
475 std::string tex = model->getNameForTex(pass->tex);
476
477 std::string tex_name = tex;
478 auto slashPos2 = tex_name.rfind('/');
479 if (slashPos2 != std::string::npos)
480 tex_name = tex_name.substr(slashPos2 + 1);
481 if (auto blpPos = tex_name.find(".blp"); blpPos != std::string::npos)
482 tex_name.replace(blpPos, 4, ".png");
483
484 std::string filenameNarrow2;
485 if (!m_filename.empty())
486 {
487 int n = WideCharToMultiByte(CP_UTF8, 0, m_filename.c_str(), static_cast<int>(m_filename.size()), nullptr, 0, nullptr, nullptr);
488 filenameNarrow2.resize(n);
489 WideCharToMultiByte(CP_UTF8, 0, m_filename.c_str(), static_cast<int>(m_filename.size()), filenameNarrow2.data(), n, nullptr, nullptr);
490 }
491 std::string tex_fullpath2 = filenameNarrow2;
492 auto bsPos2 = tex_fullpath2.rfind('\\');
493 if (bsPos2 != std::string::npos)
494 tex_fullpath2 = tex_fullpath2.substr(0, bsPos2 + 1) + tex_name;
495
496 m_texturesToExport[std::wstring(tex_fullpath2.begin(), tex_fullpath2.end())] = model->getGLTexture(pass->tex);
497
498 FbxFileTexture* texture = FbxFileTexture::Create(m_p_manager, tex_name.c_str());
499 texture->SetFileName(tex_fullpath2.c_str());
500 texture->SetTextureUse(FbxTexture::eStandard);
501 texture->SetMappingType(FbxTexture::eUV);
502 texture->SetMaterialUse(FbxFileTexture::eModelMaterial);
503 texture->SetSwapUV(false);
504 texture->SetTranslation(0.0, 0.0);
505 texture->SetScale(1.0, 1.0);
506 texture->SetRotation(0.0, 0.0);
507 texture->UVSet.Set(FbxString("DiffuseUV"));
508 material->Diffuse.ConnectSrcObject(texture);
509
510 // Add material to the scene.
511 m_attachMeshNodes[itemModel.first]->AddMaterial(material);
512 }
513 }
514 }
515 }
516 }
517
518 for (const auto it : m_texturesToExport)
519 exportGLTexture(it.second, it.first);
520}
521
523{
524 if (m_p_manager)
525 m_p_manager->Destroy();
526
527 m_p_manager = nullptr;
528
529 // scene is destroyed by manager's destroy call
530 m_p_scene = nullptr;
531
532 m_p_model = nullptr;
533 m_p_meshNode = nullptr;
534 m_p_skeletonNode = nullptr;
535
536 m_filename = L"";
537
538 m_boneNodes.clear();
539 m_texturesToExport.clear();
540 m_boneClusters.clear();
541}
#define GLOBALSETTINGS
#define LOG_ERROR
Definition Logger.h:11
#define LOG_INFO
Definition Logger.h:10
std::string name() const
Definition Component.cpp:52
void exportGLTexture(GLuint id, std::wstring filename) const
std::vector< int > m_animsToExport
Exports individual animation sequences from a WoW model into separate FBX files.
void run()
Execute the animation export.
void setValues(FbxString fileVersion, std::string fn, std::string an, WoWModel *m, std::vector< FbxCluster * > bc, FbxNode *&meshnode, int aID, bool uan=false)
Configure the exporter with source model, animation, and output settings.
FbxNode * m_p_skeletonNode
Definition FBXExporter.h:68
bool exportModel(Model *, std::wstring file)
FbxString m_fileVersion
Definition FBXExporter.h:73
std::wstring fileSaveFilter() const
void createAnimations()
void createMeshes()
WoWModel * m_p_model
Definition FBXExporter.h:66
std::map< int, FbxNode * > m_boneNodes
Definition FBXExporter.h:75
std::map< std::wstring, GLuint > m_texturesToExport
Definition FBXExporter.h:83
FbxNode * m_p_meshNode
Definition FBXExporter.h:67
std::wstring fileSaveTitle() const
std::map< int, std::vector< FbxCluster * > > m_attachBoneClusters
Definition FBXExporter.h:81
void linkMeshAndSkeleton()
std::wstring m_filename
Definition FBXExporter.h:74
std::mutex m_mutex
Definition FBXExporter.h:71
std::wstring menuLabel() const
FbxScene * m_p_scene
Definition FBXExporter.h:65
void createSkeletons()
std::map< int, FbxNode * > m_attachSkeletonNode
Definition FBXExporter.h:78
FbxManager * m_p_manager
Definition FBXExporter.h:64
std::vector< FbxCluster * > m_boneClusters
Definition FBXExporter.h:76
std::map< int, std::map< int, FbxNode * > > m_attachBoneNodes
Definition FBXExporter.h:80
bool createAnimationFiles()
std::map< int, FbxNode * > m_attachMeshNodes
Definition FBXExporter.h:79
void createMaterials()
Represents a single render pass (material + geometry) for an M2 model geoset.
uint16 tex
Texture index.
bool init()
Initialise render state from the model's material data.
Abstract base interface for all 3D model types.
Definition Model.h:5
Core WoW .m2 model: geometry, animation, textures, and character data.
Definition WoWModel.h:50
std::vector< ModelAnimation > anims
Definition WoWModel.h:178
GLuint getGLTexture(uint16 tex) const
int16 attLookup[ATT_MAX]
Definition WoWModel.h:209
std::map< int, std::wstring > getAnimsMap()
bool animated
Definition WoWModel.h:172
std::vector< Bone > bones
Definition WoWModel.h:181
std::string modelname
Definition WoWModel.h:145
std::vector< ModelRenderPass * > passes
Definition WoWModel.h:148
std::string getNameForTex(uint16 tex)
std::vector< ModelAttachment > atts
Definition WoWModel.h:207
std::vector< ModelVertex > origVertices
Definition WoWModel.h:132
FbxNode * createMesh(FbxManager *&l_manager, FbxScene *&l_scene, WoWModel *model, const glm::mat4 &matrix=glm::mat4(1.0f), const glm::vec3 &offset=glm::vec3(0.0f))
void createSkeleton(WoWModel *l_model, FbxScene *&l_scene, FbxNode *&l_skeletonNode, std::map< int, FbxNode * > &l_boneNodes)
void storeBindPose(FbxScene *&l_scene, std::vector< FbxCluster * > l_boneClusters, FbxNode *l_meshNode)
bool createFBXHeaders(FbxString fileVersion, std::string l_FileName, FbxManager *&l_Manager, FbxExporter *&l_Exporter, FbxScene *&l_Scene)
An animation sequence entry in the M2 model (block B).