ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
FBXFilter.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "FBXFilter.h"
9 
10 // qCC_db
11 #include <ecvMaterialSet.h>
12 #include <ecvMesh.h>
13 #include <ecvNormalVectors.h>
14 #include <ecvPointCloud.h>
15 
16 // FBX SDK
17 #include <fbxsdk.h>
18 
19 // Qt
20 #include <QDir>
21 #include <QFileInfo>
22 #include <QMap>
23 #include <QMessageBox>
24 #include <QPushButton>
25 
26 // System
27 #include <assert.h>
28 
29 #include <vector>
30 
31 static const char FBX_SCALE_METADATA_KEY[] = "FBX:ScaleToCM";
32 
34  : FileIOFilter({"_FBX Filter",
35  12.0f, // priority
36  QStringList{"fbx"}, "fbx", QStringList{"FBX mesh (*.fbx)"},
37  QStringList{"FBX mesh (*.fbx)"}, Import | Export}) {}
38 
40  bool& multiple,
41  bool& exclusive) const {
42  if (type == CV_TYPES::MESH) {
43  multiple = true;
44  exclusive = true;
45  return true;
46  }
47  return false;
48 }
49 
50 // Converts a CC mesh to an FBX mesh
51 static FbxNode* ToFbxMesh(ccGenericMesh* mesh,
52  FbxScene* pScene,
53  QString filename,
54  size_t meshIndex) {
55  if (!mesh) return 0;
56 
57  FbxNode* lNode = FbxNode::Create(pScene, qPrintable(mesh->getName()));
58  FbxMesh* lMesh = FbxMesh::Create(pScene, qPrintable(mesh->getName()));
59  lNode->SetNodeAttribute(lMesh);
60 
61  ccGenericPointCloud* cloud = mesh->getAssociatedCloud();
62  if (!cloud) return 0;
63  unsigned vertCount = cloud->size();
64  unsigned faceCount = mesh->size();
65 
66  // Create control points.
67  {
68  lMesh->InitControlPoints(vertCount);
69  FbxVector4* lControlPoints = lMesh->GetControlPoints();
70 
71  for (unsigned i = 0; i < vertCount; ++i) {
72  const CCVector3* P = cloud->getPoint(i);
73  lControlPoints[i] = FbxVector4(P->x, P->y, P->z);
74  // lControlPoints[i] = FbxVector4(P->x,P->z,-P->y); //DGM: see
75  // loadFile (Y and Z are inverted)
76  }
77  }
78 
79  ccMesh* asCCMesh = 0;
80  if (mesh->isA(CV_TYPES::MESH)) {
81  asCCMesh = static_cast<ccMesh*>(mesh);
82  }
83 
84  // normals
85  if (mesh->hasNormals()) {
86  FbxGeometryElementNormal* lGeometryElementNormal =
87  lMesh->CreateElementNormal();
88  if (mesh->hasTriNormals()) {
89  // We want to have one normal per vertex of each polygon,
90  // so we set the mapping mode to eByPolygonVertex.
91  lGeometryElementNormal->SetMappingMode(
92  FbxGeometryElement::eByPolygonVertex);
93  lGeometryElementNormal->SetReferenceMode(
94  FbxGeometryElement::eIndexToDirect);
95  lGeometryElementNormal->GetIndexArray().SetCount(faceCount * 3);
96 
97  if (asCCMesh) {
98  NormsIndexesTableType* triNorms = asCCMesh->getTriNormsTable();
99  assert(triNorms);
100  for (unsigned i = 0; i < triNorms->currentSize(); ++i) {
101  const CCVector3& N =
102  ccNormalVectors::GetNormal(triNorms->getValue(i));
103  FbxVector4 Nfbx(N.x, N.y, N.z);
104  lGeometryElementNormal->GetDirectArray().Add(Nfbx);
105  }
106  for (unsigned j = 0; j < faceCount; ++j) {
107  int i1, i2, i3;
108  asCCMesh->getTriangleNormalIndexes(j, i1, i2, i3);
109  lGeometryElementNormal->GetIndexArray().SetAt(
110  static_cast<int>(j) * 3 + 0, i1);
111  lGeometryElementNormal->GetIndexArray().SetAt(
112  static_cast<int>(j) * 3 + 1, i2);
113  lGeometryElementNormal->GetIndexArray().SetAt(
114  static_cast<int>(j) * 3 + 2, i3);
115  }
116  } else {
117  for (unsigned j = 0; j < faceCount; ++j) {
118  // we can't use the 'NormsIndexesTable' so we save all the
119  // normals of all the vertices
120  CCVector3 Na, Nb, Nc;
121  lGeometryElementNormal->GetDirectArray().Add(
122  FbxVector4(Na.x, Na.y, Na.z));
123  lGeometryElementNormal->GetDirectArray().Add(
124  FbxVector4(Nb.x, Nb.y, Nb.z));
125  lGeometryElementNormal->GetDirectArray().Add(
126  FbxVector4(Nc.x, Nc.y, Nc.z));
127 
128  mesh->getTriangleNormals(j, Na, Nb, Nc);
129  lGeometryElementNormal->GetIndexArray().SetAt(
130  static_cast<int>(j) * 3 + 0,
131  static_cast<int>(j) * 3 + 0);
132  lGeometryElementNormal->GetIndexArray().SetAt(
133  static_cast<int>(j) * 3 + 1,
134  static_cast<int>(j) * 3 + 1);
135  lGeometryElementNormal->GetIndexArray().SetAt(
136  static_cast<int>(j) * 3 + 2,
137  static_cast<int>(j) * 3 + 2);
138  }
139  }
140  } else {
141  // We want to have one normal for each vertex (or control point),
142  // so we set the mapping mode to eByControlPoint.
143  lGeometryElementNormal->SetMappingMode(
144  FbxGeometryElement::eByControlPoint);
145  // The first method is to set the actual normal value
146  // for every control point.
147  lGeometryElementNormal->SetReferenceMode(
148  FbxGeometryElement::eDirect);
149  for (unsigned i = 0; i < vertCount; ++i) {
150  const CCVector3& N = cloud->getPointNormal(i);
151  FbxVector4 Nfbx(N.x, N.y, N.z);
152  lGeometryElementNormal->GetDirectArray().Add(Nfbx);
153  }
154  }
155  } else {
157  "[FBX] Mesh has no normal! You can manually compute them "
158  "(select it then call \"Edit > Normals > Compute\")");
159  }
160 
161  // Set material mapping.
162  bool hasMaterial = false;
163  if (asCCMesh && asCCMesh->hasMaterials()) {
164  const ccMaterialSet* matSet = asCCMesh->getMaterialSet();
165  size_t matCount = matSet->size();
166 
167  // check if we have textures
168  bool hasTextures = asCCMesh->hasTextures();
169  if (hasTextures) {
170  // check that we actually have materials with textures as well!
171  hasTextures = false;
172  for (size_t i = 0; i < matCount; ++i) {
173  ccMaterial::CShared mat = matSet->at(i);
174  if (mat->hasTexture()) {
175  hasTextures = true;
176  break;
177  }
178  }
179  }
180 
181  static const char gDiffuseElementName[] = "DiffuseUV";
182 
183  // Create UV for Diffuse channel
184  if (hasTextures) {
185  FbxGeometryElementUV* lUVDiffuseElement =
186  lMesh->CreateElementUV(gDiffuseElementName);
187  assert(lUVDiffuseElement != 0);
188  lUVDiffuseElement->SetMappingMode(
189  FbxGeometryElement::eByPolygonVertex);
190  lUVDiffuseElement->SetReferenceMode(
191  FbxGeometryElement::eIndexToDirect);
192 
193  // fill Direct Array
194  const TextureCoordsContainer* texCoords =
195  asCCMesh->getTexCoordinatesTable();
196  assert(texCoords);
197  if (texCoords) {
198  unsigned count = texCoords->currentSize();
199  lUVDiffuseElement->GetDirectArray().SetCount(
200  static_cast<int>(count));
201  for (unsigned i = 0; i < count; ++i) {
202  const TexCoords2D& uv = texCoords->getValue(i);
203  lUVDiffuseElement->GetDirectArray().SetAt(
204  i, FbxVector2(uv.tx, uv.ty));
205  }
206  }
207 
208  // fill Indexes Array
209  assert(asCCMesh->hasPerTriangleTexCoordIndexes());
210  if (asCCMesh->hasPerTriangleTexCoordIndexes()) {
211  unsigned triCount = asCCMesh->size();
212  lUVDiffuseElement->GetIndexArray().SetCount(
213  static_cast<int>(3 * triCount));
214  for (unsigned j = 0; j < triCount; ++j) {
215  int t1 = 0, t2 = 0, t3 = 0;
216  asCCMesh->getTriangleTexCoordinatesIndexes(j, t1, t2, t3);
217 
218  lUVDiffuseElement->GetIndexArray().SetAt(j * 3 + 0, t1);
219  lUVDiffuseElement->GetIndexArray().SetAt(j * 3 + 1, t2);
220  lUVDiffuseElement->GetIndexArray().SetAt(j * 3 + 2, t3);
221  }
222  }
223  }
224 
225  // Textures used in this file
226  QMap<QString, QString> texFilenames;
227  // directory to save textures (if any)
228  QFileInfo info(filename);
229  QString textDirName = info.baseName() + QString(".fbm");
230  QDir baseDir = info.absoluteDir();
231  QDir texDir = QDir(baseDir.absolutePath() + QString("/") + textDirName);
232 
233  for (size_t i = 0; i < matCount; ++i) {
234  ccMaterial::CShared mat = matSet->at(i);
235  FbxSurfacePhong* lMaterial =
236  FbxSurfacePhong::Create(pScene, qPrintable(mat->getName()));
237 
238  const ecvColor::Rgbaf& emission = mat->getEmission();
239  const ecvColor::Rgbaf& ambient = mat->getAmbient();
240  const ecvColor::Rgbaf& diffuse = mat->getDiffuseFront();
241  const ecvColor::Rgbaf& specular = mat->getSpecular();
242  lMaterial->Emissive.Set(
243  FbxDouble3(emission.r, emission.g, emission.b));
244  lMaterial->Ambient.Set(FbxDouble3(ambient.r, ambient.g, ambient.b));
245  lMaterial->Diffuse.Set(FbxDouble3(diffuse.r, diffuse.g, diffuse.b));
246  lMaterial->Specular.Set(
247  FbxDouble3(specular.r, specular.g, specular.b));
248  lMaterial->Shininess = mat->getShininessFront();
249  lMaterial->ShadingModel.Set("Phong");
250 
251  if (hasTextures && mat->hasTexture()) {
252  QString texFilename = mat->getTextureFilename();
253 
254  // texture has not already been processed
255  if (!texFilenames.contains(texFilename)) {
256  // if necessary, we (try to) create a subfolder to store
257  // textures
258  if (!texDir.exists()) {
259  texDir = baseDir;
260  if (texDir.mkdir(textDirName)) {
261  texDir.cd(textDirName);
262  } else {
263  textDirName = QString();
265  "[FBX] Failed to create subfolder '%1' to "
266  "store texture files (files will be stored "
267  "next to the .fbx file)");
268  }
269  }
270 
271  QFileInfo fileInfo(texFilename);
272  QString baseTexName = fileInfo.fileName();
273  // add extension
274  QString extension = QFileInfo(texFilename).suffix();
275  if (fileInfo.suffix().isEmpty())
276  baseTexName += QString(".png");
277 
278  QString absoluteFilename =
279  texDir.absolutePath() + QString("/") + baseTexName;
280  CVLog::PrintDebug(QString("[FBX] Material '%1' texture: %2")
281  .arg(mat->getName())
282  .arg(absoluteFilename));
283 
284  texFilenames[texFilename] = absoluteFilename;
285  }
286  // mat.texture.save(absoluteFilename);
287 
288  // Set texture properties.
289  FbxFileTexture* lTexture =
290  FbxFileTexture::Create(pScene, "DiffuseTexture");
291  assert(!texFilenames[texFilename].isEmpty());
292  lTexture->SetFileName(qPrintable(texFilenames[texFilename]));
293  lTexture->SetTextureUse(FbxTexture::eStandard);
294  lTexture->SetMappingType(FbxTexture::eUV);
295  lTexture->SetMaterialUse(FbxFileTexture::eModelMaterial);
296  lTexture->SetSwapUV(false);
297  lTexture->SetTranslation(0.0, 0.0);
298  lTexture->SetScale(1.0, 1.0);
299  lTexture->SetRotation(0.0, 0.0);
300  lTexture->UVSet.Set(
301  FbxString(gDiffuseElementName)); // Connect texture to
302  // the proper UV
303 
304  // don't forget to connect the texture to the corresponding
305  // property of the material
306  lMaterial->Diffuse.ConnectSrcObject(lTexture);
307  }
308 
309  int matIndex = lNode->AddMaterial(lMaterial);
310  assert(matIndex == static_cast<int>(i));
311  }
312 
313  // don't forget to save the texture files
314  {
315  for (QMap<QString, QString>::ConstIterator it =
316  texFilenames.begin();
317  it != texFilenames.end(); ++it) {
318  const QImage image = ccMaterial::GetTexture(it.key());
319  image.save(it.value());
320  }
321 
322  texFilenames.clear(); // don't need this anymore!
323  }
324 
325  // Create 'triangle to material index' mapping
326  {
327  FbxGeometryElementMaterial* lMaterialElement =
328  lMesh->CreateElementMaterial();
329  lMaterialElement->SetMappingMode(FbxGeometryElement::eByPolygon);
330  lMaterialElement->SetReferenceMode(
331  FbxGeometryElement::eIndexToDirect);
332  }
333 
334  hasMaterial = true;
335  }
336 
337  // colors
338  if (cloud->hasColors()) {
339  FbxGeometryElementVertexColor* lGeometryElementVertexColor =
340  lMesh->CreateElementVertexColor();
341  lGeometryElementVertexColor->SetMappingMode(
342  FbxGeometryElement::eByControlPoint);
343  lGeometryElementVertexColor->SetReferenceMode(
344  FbxGeometryElement::eDirect);
345  lGeometryElementVertexColor->GetDirectArray().SetCount(vertCount);
346  for (unsigned i = 0; i < vertCount; ++i) {
347  const ecvColor::Rgb& C = cloud->getPointColor(i);
348  FbxColor col(static_cast<double>(C.r) / ecvColor::MAX,
349  static_cast<double>(C.g) / ecvColor::MAX,
350  static_cast<double>(C.b) / ecvColor::MAX);
351  lGeometryElementVertexColor->GetDirectArray().SetAt(i, col);
352  }
353 
354  if (!hasMaterial) {
355  // it seems that we have to create a fake material in order for the
356  // colors to be displayed (in Unity and FBX Review at least)!
357  FbxSurfacePhong* lMaterial =
358  FbxSurfacePhong::Create(pScene, "ColorMaterial");
359 
360  lMaterial->Emissive.Set(FbxDouble3(0, 0, 0));
361  lMaterial->Ambient.Set(FbxDouble3(0, 0, 0));
362  lMaterial->Diffuse.Set(FbxDouble3(1, 1, 1));
363  lMaterial->Specular.Set(FbxDouble3(0, 0, 0));
364  lMaterial->Shininess = 0;
365  lMaterial->ShadingModel.Set("Phong");
366 
367  FbxGeometryElementMaterial* lMaterialElement =
368  lMesh->CreateElementMaterial();
369  lMaterialElement->SetMappingMode(FbxGeometryElement::eAllSame);
370  lMaterialElement->SetReferenceMode(FbxGeometryElement::eDirect);
371  lNode->AddMaterial(lMaterial);
372  }
373  }
374 
375  // Create polygons
376  {
377  for (unsigned j = 0; j < faceCount; ++j) {
378  const cloudViewer::VerticesIndexes* tsi =
379  mesh->getTriangleVertIndexes(j);
380 
381  int matIndex = hasMaterial ? asCCMesh->getTriangleMtlIndex(j) : -1;
382  lMesh->BeginPolygon(matIndex);
383  lMesh->AddPolygon(tsi->i1);
384  lMesh->AddPolygon(tsi->i2);
385  lMesh->AddPolygon(tsi->i3);
386  lMesh->EndPolygon();
387  }
388  }
389 
390  return lNode;
391 }
392 
393 static bool SaveScene(FbxManager* pManager,
394  FbxDocument* pScene,
395  const char* pFilename,
396  int pFileFormat = -1,
397  bool pEmbedMedia = false) {
398  // Create an exporter
399  FbxExporter* lExporter = FbxExporter::Create(pManager, "");
400 
401  if (pFileFormat < 0 ||
402  pFileFormat >=
403  pManager->GetIOPluginRegistry()->GetWriterFormatCount()) {
404  // Write in fall back format in less no ASCII format found
405  pFileFormat = pManager->GetIOPluginRegistry()->GetNativeWriterFormat();
406 
407  // Try to export in ASCII if possible
408  int lFormatIndex,
409  lFormatCount =
410  pManager->GetIOPluginRegistry()->GetWriterFormatCount();
411 
412  for (lFormatIndex = 0; lFormatIndex < lFormatCount; lFormatIndex++) {
413  if (pManager->GetIOPluginRegistry()->WriterIsFBX(lFormatIndex)) {
414  FbxString lDesc =
415  pManager->GetIOPluginRegistry()
416  ->GetWriterFormatDescription(lFormatIndex);
417  const char* lASCII = "ascii";
418  if (lDesc.Find(lASCII) >= 0) {
419  pFileFormat = lFormatIndex;
420  break;
421  }
422  }
423  }
424  }
425 
426  // Set the export states. By default, the export states are always set to
427  // true except for the option eEXPORT_TEXTURE_AS_EMBEDDED. The code below
428  // shows how to change these states
429  (*(pManager->GetIOSettings())).SetBoolProp(EXP_FBX_MATERIAL, true);
430  (*(pManager->GetIOSettings())).SetBoolProp(EXP_FBX_TEXTURE, true);
431  (*(pManager->GetIOSettings())).SetBoolProp(EXP_FBX_EMBEDDED, pEmbedMedia);
432  (*(pManager->GetIOSettings())).SetBoolProp(EXP_FBX_SHAPE, true);
433  (*(pManager->GetIOSettings())).SetBoolProp(EXP_FBX_GOBO, true);
434  (*(pManager->GetIOSettings())).SetBoolProp(EXP_FBX_ANIMATION, true);
435  (*(pManager->GetIOSettings())).SetBoolProp(EXP_FBX_GLOBAL_SETTINGS, true);
436 
437  // Initialize the exporter by providing a filename
438  if (lExporter->Initialize(pFilename, pFileFormat,
439  pManager->GetIOSettings()) == false) {
440  CVLog::Warning("[FBX] Call to FbxExporter::Initialize() failed");
441  CVLog::Warning("[FBX] Error returned: %s",
442  lExporter->GetStatus().GetErrorString());
443  return false;
444  }
445 
446  // Export the scene
447  bool lStatus = lExporter->Export(pScene);
448 
449  // Destroy the exporter
450  lExporter->Destroy();
451 
452  return lStatus;
453 }
454 
455 static QString s_defaultOutputFormat;
456 
459 }
460 
461 QString SanitizeFBXFormatString(QString format) {
462  format.replace("(*.fbx)", "");
463  format = format.trimmed();
464  format.replace(" ", "_");
465 
466  return format;
467 }
468 
470  const QString& filename,
471  const SaveParameters& parameters) {
472  if (!entity) return CC_FERR_BAD_ARGUMENT;
473 
474  double scaleFactor = 0.0;
475 
476  std::vector<ccGenericMesh*> meshes;
477  if (entity->isKindOf(CV_TYPES::MESH)) {
478  meshes.push_back(static_cast<ccGenericMesh*>(entity));
479  } else if (entity->isA(CV_TYPES::HIERARCHY_OBJECT)) {
480  for (unsigned i = 0; i < entity->getChildrenNumber(); ++i) {
481  ccHObject* child = entity->getChild(i);
482  if (child->isKindOf(CV_TYPES::MESH)) {
483  meshes.push_back(static_cast<ccGenericMesh*>(child));
484 
485  // manage custom units (if any)
486  if (child->hasMetaData(FBX_SCALE_METADATA_KEY)) {
487  bool ok = false;
488  double sf = child->getMetaData(FBX_SCALE_METADATA_KEY)
489  .toDouble(&ok);
490  if (ok) {
491  if (scaleFactor == 0.0) {
492  // first time: remember it
493  scaleFactor = sf;
494  } else if (scaleFactor != sf) {
496  "[FBX] Attempt to save mutliple meshes "
497  "with different units!");
498  }
499  } else {
501  "[FBX] Internal error: invalid FBX scale "
502  "meta-data?!");
503  scaleFactor = 1.0;
504  }
505  } else {
506  // default FBX units = cm
508  "[FBX] Reminder: default FBX units are 'cm'");
509  scaleFactor = 1.0;
510  }
511  }
512  }
513  }
514 
515  if (meshes.empty()) {
516  return CC_FERR_NO_SAVE;
517  }
518 
519  // The first thing to do is to create the FBX Manager which is the object
520  // allocator for almost all the classes in the SDK
521  FbxManager* lSdkManager = FbxManager::Create();
522  if (!lSdkManager) {
523  CVLog::Warning("[FBX] Error: Unable to create FBX Manager!");
524  return CC_FERR_CONSOLE_ERROR;
525  } else {
526  CVLog::Print("[FBX] Autodesk FBX SDK version %s",
527  lSdkManager->GetVersion());
528  }
529 
530  try {
531  // Create an IOSettings object. This object holds all import/export
532  // settings.
533  FbxIOSettings* ios = FbxIOSettings::Create(lSdkManager, IOSROOT);
534  lSdkManager->SetIOSettings(ios);
535 
536  // Load plugins from the executable directory (optional)
537  // FbxString lPath = FbxGetApplicationDirectory();
538  // lSdkManager->LoadPluginsDirectory(lPath.Buffer());
539 
540  // Create an FBX scene. This object holds most objects imported/exported
541  // from/to files.
542  FbxScene* lScene = FbxScene::Create(lSdkManager, "My Scene");
543  if (!lScene) {
544  CVLog::Warning("[FBX] Error: Unable to create FBX scene!");
545  return CC_FERR_CONSOLE_ERROR;
546  }
547 
548  // create scene info
549  {
550  FbxDocumentInfo* sceneInfo =
551  FbxDocumentInfo::Create(lSdkManager, "SceneInfo");
552  sceneInfo->mTitle = qPrintable(
553  QString("Mesh: ") + (meshes.size() == 1
554  ? meshes[0]->getName()
555  : QString("Multiple meshes")));
556  sceneInfo->mAuthor = "ACloudViewer";
557  sceneInfo->mRevision = "rev. 1.0";
558  sceneInfo->mKeywords = "CloudViewer mesh";
559 
560  // we need to add the sceneInfo before calling AddThumbNailToScene
561  // because that function is asking the scene for the sceneInfo.
562  lScene->SetSceneInfo(sceneInfo);
563  }
564 
565  // scale
566  if (scaleFactor != 1.0 && scaleFactor > 0.0) {
567  lScene->GetGlobalSettings().SetSystemUnit(
568  FbxSystemUnit(scaleFactor));
569  }
570 
571  // create thumbnail
572  //{
573  // FbxThumbnail* lThumbnail = FbxThumbnail::Create(lScene,"");
574 
575  // lThumbnail->SetDataFormat(FbxThumbnail::eRGB_24);
576  // lThumbnail->SetSize(FbxThumbnail::e64x64);
577  // lThumbnail->SetThumbnailImage(cSceneThumbnail);
578 
579  // if (lScene->GetSceneInfo())
580  // {
581  // lScene->GetSceneInfo()->SetSceneThumbnail(lThumbnail);
582  // }
583  //}
584 
585  // Build the node tree.
586  FbxNode* lRootNode = lScene->GetRootNode();
587  {
588  for (size_t i = 0; i < meshes.size(); ++i) {
589  FbxNode* meshNode = ToFbxMesh(meshes[i], lScene, filename, i);
590  if (meshNode)
591  lRootNode->AddChild(meshNode);
592  else
593  CVLog::Warning(QString("[FBX] Failed to convert mesh '%1' "
594  "to FBX mesh/node!")
595  .arg(meshes[i]->getName()));
596  }
597  }
598 
599  int fileFormat = -1;
600 
601  // Display a combox box to let the user choose the export file format
602  {
603  FbxManager* pSdkManager = FbxManager::GetDefaultManager();
604  int lFormatCount = pSdkManager ? pSdkManager->GetIOPluginRegistry()
605  ->GetWriterFormatCount()
606  : 0;
607 
608  if (lFormatCount > 0) {
609  if (s_defaultOutputFormat.isEmpty()) {
610  try {
611  QMessageBox msgBox(QMessageBox::Question, "FBX format",
612  "Choose output format:");
613  QMap<QAbstractButton*, int> buttons;
614  for (int lFormatIndex = 0; lFormatIndex < lFormatCount;
615  lFormatIndex++) {
616  if (pSdkManager->GetIOPluginRegistry()->WriterIsFBX(
617  lFormatIndex)) {
618  FbxString lDesc =
619  pSdkManager->GetIOPluginRegistry()
620  ->GetWriterFormatDescription(
621  lFormatIndex);
622  QPushButton* button = msgBox.addButton(
623  lDesc.Buffer(),
624  QMessageBox::AcceptRole);
625  buttons[button] = lFormatIndex;
626  }
627  }
628  msgBox.exec();
629  // get the right format
630  fileFormat = buttons[msgBox.clickedButton()];
631  } catch (...) {
632  }
633  } else {
634  // try to find the default output format as set by the user
635  for (int lFormatIndex = 0; lFormatIndex < lFormatCount;
636  lFormatIndex++) {
637  if (pSdkManager->GetIOPluginRegistry()->WriterIsFBX(
638  lFormatIndex)) {
639  FbxString lDesc =
640  pSdkManager->GetIOPluginRegistry()
641  ->GetWriterFormatDescription(
642  lFormatIndex);
643  QString sanitizedDesc =
644  SanitizeFBXFormatString(lDesc.Buffer());
645  if (s_defaultOutputFormat == sanitizedDesc) {
646  CVLog::Print(QString("[FBX] Default output "
647  "file format: %1")
648  .arg(sanitizedDesc));
649  fileFormat = lFormatIndex;
650  break;
651  }
652  }
653  }
654 
655  // if we failed to find the specified file format, warn the
656  // user and display the list of supported formats
657  if (fileFormat < 0) {
659  QString("[FBX] File format '%1' not supported")
660  .arg(s_defaultOutputFormat));
661  CVLog::Print("[FBX] Supported output formats:");
662  for (int lFormatIndex = 0; lFormatIndex < lFormatCount;
663  lFormatIndex++) {
664  if (pSdkManager->GetIOPluginRegistry()->WriterIsFBX(
665  lFormatIndex)) {
666  FbxString lDesc =
667  pSdkManager->GetIOPluginRegistry()
668  ->GetWriterFormatDescription(
669  lFormatIndex);
670  CVLog::Print(QString("\t- %1").arg(
672  lDesc.Buffer())));
673  }
674  }
675  }
676  }
677  }
678  }
679 
681  CVLog::Warning(QString(
682  "[FBX] Output filename contains special characters. It "
683  "might be rejected by the third party library..."));
684  }
685 
686  // Save the scene.
687  bool lResult = SaveScene(lSdkManager, lScene, qPrintable(filename),
688  fileFormat);
689 
690  // Destroy all objects created by the FBX SDK.
691  if (lSdkManager) lSdkManager->Destroy();
692 
693  return lResult ? CC_FERR_NO_ERROR : CC_FERR_CONSOLE_ERROR;
694  } catch (...) {
695  CVLog::Warning("[FBX] FBX SDK has thrown an unknown exception!");
697  }
698 }
699 
700 QString GetAttributeTypeName(FbxNodeAttribute::EType type) {
701  switch (type) {
702  case FbxNodeAttribute::eUnknown:
703  return ("unidentified");
704  case FbxNodeAttribute::eNull:
705  return ("null");
706  case FbxNodeAttribute::eMarker:
707  return ("marker");
708  case FbxNodeAttribute::eSkeleton:
709  return ("skeleton");
710  case FbxNodeAttribute::eMesh:
711  return ("mesh");
712  case FbxNodeAttribute::eNurbs:
713  return ("nurbs");
714  case FbxNodeAttribute::ePatch:
715  return ("patch");
716  case FbxNodeAttribute::eCamera:
717  return ("camera");
718  case FbxNodeAttribute::eCameraStereo:
719  return ("stereo");
720  case FbxNodeAttribute::eCameraSwitcher:
721  return ("camera switcher");
722  case FbxNodeAttribute::eLight:
723  return ("light");
724  case FbxNodeAttribute::eOpticalReference:
725  return ("optical reference");
726  case FbxNodeAttribute::eOpticalMarker:
727  return ("marker");
728  case FbxNodeAttribute::eNurbsCurve:
729  return ("nurbs curve");
730  case FbxNodeAttribute::eTrimNurbsSurface:
731  return ("trim nurbs surface");
732  case FbxNodeAttribute::eBoundary:
733  return ("boundary");
734  case FbxNodeAttribute::eNurbsSurface:
735  return ("nurbs surface");
736  case FbxNodeAttribute::eShape:
737  return ("shape");
738  case FbxNodeAttribute::eLODGroup:
739  return ("lodgroup");
740  case FbxNodeAttribute::eSubDiv:
741  return ("subdiv");
742  default:
743  break;
744  }
745 
746  return ("unknown");
747 }
748 
749 // converts a FBX mesh to a CC mesh
750 static ccMesh* FromFbxMesh(FbxMesh* fbxMesh,
751  FileIOFilter::LoadParameters& parameters) {
752  if (!fbxMesh) return 0;
753 
754  int polyCount = fbxMesh->GetPolygonCount();
755  // fbxMesh->GetLayer(
756  unsigned triCount = 0;
757  unsigned polyVertCount = 0; // different from vertCount (vertices can be
758  // counted multiple times here!)
759  // as we can't load all polygons (yet ;) we already look if we can load any!
760  {
761  unsigned skipped = 0;
762  for (int i = 0; i < polyCount; ++i) {
763  int pSize = fbxMesh->GetPolygonSize(i);
764 
765  if (pSize == 3) {
766  ++triCount;
767  polyVertCount += 3;
768  } else if (pSize == 4) {
769  triCount += 2;
770  polyVertCount += 4;
771  } else {
772  ++skipped;
773  }
774  }
775 
776  if (triCount == 0) {
777  CVLog::Warning(QString("[FBX] No triangle or quad found in mesh "
778  "'%1'! (polygons with more than 4 vertices "
779  "are not supported for the moment)")
780  .arg(fbxMesh->GetName()));
781  return 0;
782  } else if (skipped != 0) {
783  CVLog::Warning(QString("[FBX] Some polygons in mesh '%1' were "
784  "ignored (%2): polygons with more than 4 "
785  "vertices are not supported for the moment)")
786  .arg(fbxMesh->GetName())
787  .arg(skipped));
788  return 0;
789  }
790  }
791 
792  int vertCount = fbxMesh->GetControlPointsCount();
793  if (vertCount <= 0) {
794  CVLog::Warning(QString("[FBX] Mesh '%1' has no vetex or no polygon?!")
795  .arg(fbxMesh->GetName()));
796  return 0;
797  }
798 
799  ccPointCloud* vertices = new ccPointCloud("vertices");
800  ccMesh* mesh = new ccMesh(vertices);
801  mesh->setName(fbxMesh->GetName());
802  mesh->addChild(vertices);
803  vertices->setEnabled(false);
804 
805  if (!mesh->reserve(static_cast<unsigned>(triCount)) ||
806  !vertices->reserve(vertCount)) {
807  CVLog::Warning(QString("[FBX] Not enough memory to load mesh '%1'!")
808  .arg(fbxMesh->GetName()));
809  delete mesh;
810  return 0;
811  }
812 
813  // colors
814  {
815  for (int l = 0; l < fbxMesh->GetElementVertexColorCount(); l++) {
816  FbxGeometryElementVertexColor* vertColor =
817  fbxMesh->GetElementVertexColor(l);
818  // CC can only handle per-vertex colors
819  if (vertColor->GetMappingMode() ==
820  FbxGeometryElement::eByControlPoint) {
821  if (vertColor->GetReferenceMode() ==
822  FbxGeometryElement::eDirect ||
823  vertColor->GetReferenceMode() ==
824  FbxGeometryElement::eIndexToDirect) {
825  if (vertices->reserveTheRGBTable()) {
826  switch (vertColor->GetReferenceMode()) {
827  case FbxGeometryElement::eDirect: {
828  for (int i = 0; i < vertCount; ++i) {
829  FbxColor c =
830  vertColor->GetDirectArray().GetAt(
831  i);
832  vertices->addRGBColor(
833  static_cast<ColorCompType>(
834  c.mRed * ecvColor::MAX),
835  static_cast<ColorCompType>(
836  c.mGreen * ecvColor::MAX),
837  static_cast<ColorCompType>(
838  c.mBlue * ecvColor::MAX));
839  }
840  } break;
841  case FbxGeometryElement::eIndexToDirect: {
842  for (int i = 0; i < vertCount; ++i) {
843  int id =
844  vertColor->GetIndexArray().GetAt(i);
845  FbxColor c =
846  vertColor->GetDirectArray().GetAt(
847  id);
848  vertices->addRGBColor(
849  static_cast<ColorCompType>(
850  c.mRed * ecvColor::MAX),
851  static_cast<ColorCompType>(
852  c.mGreen * ecvColor::MAX),
853  static_cast<ColorCompType>(
854  c.mBlue * ecvColor::MAX));
855  }
856  } break;
857  default:
858  assert(false);
859  break;
860  }
861 
862  vertices->showColors(true);
863  mesh->showColors(true);
864  break; // no need to look for other color fields (we
865  // won't be able to handle them!
866  } else {
867  CVLog::Warning(QString("[FBX] Not enough memory to "
868  "load mesh '%1' colors!")
869  .arg(fbxMesh->GetName()));
870  }
871  } else {
872  CVLog::Warning(QString("[FBX] Color field #%i of mesh '%1' "
873  "will be ignored (unhandled type)")
874  .arg(l)
875  .arg(fbxMesh->GetName()));
876  }
877  } else {
878  CVLog::Warning(QString("[FBX] Color field #%i of mesh '%1' "
879  "will be ignored (unhandled type)")
880  .arg(l)
881  .arg(fbxMesh->GetName()));
882  }
883  }
884  }
885 
886  // normals can be per vertices or per-triangle
887  int perPointNormals = -1;
888  int perVertexNormals = -1;
889  int perPolygonNormals = -1;
890  {
891  for (int j = 0; j < fbxMesh->GetElementNormalCount(); j++) {
892  FbxGeometryElementNormal* leNormals = fbxMesh->GetElementNormal(j);
893  switch (leNormals->GetMappingMode()) {
894  case FbxGeometryElement::eByControlPoint:
895  perPointNormals = j;
896  break;
897  case FbxGeometryElement::eByPolygonVertex:
898  perVertexNormals = j;
899  break;
900  case FbxGeometryElement::eByPolygon:
901  perPolygonNormals = j;
902  break;
903  default:
904  // not handled
905  break;
906  }
907  }
908  }
909 
910  // per-point normals
911  if (perPointNormals >= 0) {
912  FbxGeometryElementNormal* leNormals =
913  fbxMesh->GetElementNormal(perPointNormals);
914  FbxLayerElement::EReferenceMode refMode = leNormals->GetReferenceMode();
915  const FbxLayerElementArrayTemplate<FbxVector4>& normals =
916  leNormals->GetDirectArray();
917  assert(normals.GetCount() == vertCount);
918  if (normals.GetCount() != vertCount) {
920  QString("[FBX] Wrong number of normals on mesh '%1'!")
921  .arg(fbxMesh->GetName()));
922  perPointNormals = -1;
923  } else if (!vertices->reserveTheNormsTable()) {
924  CVLog::Warning(QString("[FBX] Not enough memory to load mesh '%1' "
925  "normals!")
926  .arg(fbxMesh->GetName()));
927  perPointNormals = -1;
928  } else {
929  // import normals
930  for (int i = 0; i < vertCount; ++i) {
931  int id = refMode != FbxGeometryElement::eDirect
932  ? leNormals->GetIndexArray().GetAt(i)
933  : i;
934  FbxVector4 N = normals.GetAt(id);
935  // convert to CC-structure
936  CCVector3 Npc(static_cast<PointCoordinateType>(N.Buffer()[0]),
937  static_cast<PointCoordinateType>(N.Buffer()[1]),
938  static_cast<PointCoordinateType>(N.Buffer()[2]));
939  vertices->addNorm(Npc);
940  }
941  vertices->showNormals(true);
942  mesh->showNormals(true);
943  // no need to import the other normals (if any)
944  perVertexNormals = -1;
945  perPolygonNormals = -1;
946  }
947  }
948 
949  // per-triangle normals
950  NormsIndexesTableType* normsTable = 0;
951  if (perVertexNormals >= 0 || perPolygonNormals >= 0) {
952  normsTable = new NormsIndexesTableType();
953  if (!normsTable->reserveSafe(polyVertCount) ||
955  CVLog::Warning(QString("[FBX] Not enough memory to load mesh '%1' "
956  "normals!")
957  .arg(fbxMesh->GetName()));
958  normsTable->release();
959  normsTable = 0;
960  } else {
961  mesh->setTriNormsTable(normsTable);
962  vertices->showNormals(true);
963  mesh->showNormals(true);
964  }
965  }
966 
967  // materials
968  ccMaterialSet* materials = 0;
969  {
970  FbxNode* lNode = fbxMesh->GetNode();
971  int lMaterialCount = lNode ? lNode->GetMaterialCount() : 0;
972  for (int i = 0; i < lMaterialCount; i++) {
973  FbxSurfaceMaterial* lBaseMaterial = lNode->GetMaterial(i);
974 
975  bool isLambert =
976  lBaseMaterial->GetClassId().Is(FbxSurfaceLambert::ClassId);
977  bool isPhong =
978  lBaseMaterial->GetClassId().Is(FbxSurfacePhong::ClassId);
979  if (isLambert || isPhong) {
980  ccMaterial::Shared mat(
981  new ccMaterial(lBaseMaterial->GetName()));
982 
983  FbxSurfaceLambert* lLambertMat =
984  static_cast<FbxSurfaceLambert*>(lBaseMaterial);
985 
986  ecvColor::Rgbaf ambient(0, 0, 0, 1);
987  ecvColor::Rgbaf diffuse(0, 0, 0, 1);
988  ecvColor::Rgbaf emission(0, 0, 0, 1);
989  ecvColor::Rgbaf specular(0, 0, 0, 1);
990 
991  FbxSurfacePhong* lPhongMat =
992  isPhong ? static_cast<FbxSurfacePhong*>(lBaseMaterial)
993  : 0;
994 
995  for (int k = 0; k < 3; ++k) {
996  ambient.rgba[k] =
997  static_cast<float>(lLambertMat->Ambient.Get()[k]);
998  diffuse.rgba[k] =
999  static_cast<float>(lLambertMat->Diffuse.Get()[k]);
1000  emission.rgba[k] =
1001  static_cast<float>(lLambertMat->Emissive.Get()[k]);
1002 
1003  if (lPhongMat) {
1004  specular.rgba[k] = static_cast<float>(
1005  lPhongMat->Specular.Get()[k]);
1006  }
1007  }
1008 
1009  mat->setAmbient(ambient);
1010  mat->setDiffuse(diffuse);
1011  mat->setEmission(emission);
1012  if (isPhong) {
1013  mat->setSpecular(specular);
1014  assert(lPhongMat);
1015  mat->setShininess(static_cast<float>(lPhongMat->Shininess));
1016  }
1017 
1018  // import associated texture (if any)
1019  {
1020  int lTextureIndex;
1021  FBXSDK_FOR_EACH_TEXTURE(lTextureIndex) {
1022  FbxProperty lProperty = lBaseMaterial->FindProperty(
1023  FbxLayerElement::sTextureChannelNames
1024  [lTextureIndex]);
1025  if (lProperty.IsValid()) {
1026  int lTextureCount =
1027  lProperty.GetSrcObjectCount<FbxTexture>();
1028  FbxTexture* texture =
1029  0; // we can handle only one texture per
1030  // material! We'll take the non layered
1031  // one by default (if any)
1032  for (int j = 0; j < lTextureCount; ++j) {
1033  // Here we have to check if it's
1034  // layeredtextures, or just textures:
1035  FbxLayeredTexture* lLayeredTexture =
1036  lProperty.GetSrcObject<
1037  FbxLayeredTexture>(j);
1038  if (lLayeredTexture) {
1039  // we don't handle layered textures!
1040  /*int lNbTextures =
1041  lLayeredTexture->GetSrcObjectCount<FbxTexture>();
1042  for (int k=0; k<lNbTextures; ++k)
1043  {
1044  FbxTexture* lTexture =
1045  lLayeredTexture->GetSrcObject<FbxTexture>(k);
1046  if(lTexture)
1047  {
1048  }
1049  }
1050  //*/
1051  } else {
1052  // non-layered texture
1053  FbxTexture* lTexture =
1054  lProperty.GetSrcObject<FbxTexture>(
1055  j);
1056  if (lTexture) {
1057  // we take the first non layered texture
1058  // by default
1059  texture = lTexture;
1060  break;
1061  }
1062  }
1063  }
1064 
1065  if (texture) {
1066  FbxFileTexture* lFileTexture =
1067  FbxCast<FbxFileTexture>(texture);
1068  if (lFileTexture) {
1069  const char* texAbsoluteFilename =
1070  lFileTexture->GetFileName();
1072  QString("[FBX] Texture absolue "
1073  "filename: %1")
1074  .arg(texAbsoluteFilename));
1075  if (texAbsoluteFilename != 0 &&
1076  texAbsoluteFilename[0] != 0) {
1077  if (!mat->loadAndSetTexture(
1078  texAbsoluteFilename)) {
1080  QString("[FBX] Failed to "
1081  "load texture "
1082  "file: %1")
1083  .arg(texAbsoluteFilename));
1084  }
1085  }
1086  }
1087  }
1088  }
1089  }
1090  }
1091 
1092  if (!materials) {
1093  materials = new ccMaterialSet("materials");
1094  mesh->addChild(materials);
1095  }
1096  materials->addMaterial(mat);
1097  } else {
1099  QString("[FBX] Material '%1' has an unhandled type")
1100  .arg(lBaseMaterial->GetName()));
1101  }
1102  }
1103  }
1104 
1105  // import textures UV
1106  TextureCoordsContainer* vertTexUVTable = 0;
1107  bool hasTexUVIndexes = false;
1108  {
1109  for (int l = 0; l < fbxMesh->GetElementUVCount(); ++l) {
1110  FbxGeometryElementUV* leUV = fbxMesh->GetElementUV(l);
1111  // per-point UV coordinates
1112  if (leUV->GetMappingMode() ==
1113  FbxGeometryElement::eByPolygonVertex) {
1114  vertTexUVTable = new TextureCoordsContainer();
1115  int uvCount = leUV->GetDirectArray().GetCount();
1116 
1117  if (!vertTexUVTable->reserveSafe(uvCount) ||
1119  vertTexUVTable->release();
1120  vertTexUVTable = 0;
1121  CVLog::Warning(QString("[FBX] Not enough memory to load "
1122  "mesh '%1' UV coordinates!")
1123  .arg(fbxMesh->GetName()));
1124  } else {
1125  FbxLayerElement::EReferenceMode refMode =
1126  leUV->GetReferenceMode();
1127  for (int i = 0; i < uvCount; ++i) {
1128  FbxVector2 uv = leUV->GetDirectArray().GetAt(i);
1129  // convert to CC-structure
1130  TexCoords2D uvf(static_cast<float>(uv.Buffer()[0]),
1131  static_cast<float>(uv.Buffer()[1]));
1132  vertTexUVTable->addElement(uvf);
1133  }
1134 
1135  if (refMode == FbxGeometryElement::eIndexToDirect) {
1136  hasTexUVIndexes = true;
1137  // for (int i=0; i<polyCount; ++i)
1138  //{
1139  // mesh->addTriangleTexCoordIndexes(leUV->GetIndexArray().GetAt(3*i),leUV->GetIndexArray().GetAt(3*i+1),leUV->GetIndexArray().GetAt(3*i+2));
1140  // }
1141  } else if (refMode == FbxGeometryElement::eDirect) {
1142  // for (int i=0; i<polyCount; ++i)
1143  //{
1144  // mesh->addTriangleTexCoordIndexes(3*i,3*i+1,3*i+2);
1145  // }
1146  } else {
1148  QString("[FBX] UV coordinates for mesh '%1' "
1149  "are encoded in an unhandled mode!")
1150  .arg(fbxMesh->GetName()));
1151  vertTexUVTable->release();
1152  vertTexUVTable = 0;
1153  }
1154  }
1155 
1156  if (vertTexUVTable)
1157  break; // no need to look to the other UV fields (can't
1158  // handle them!)
1159  }
1160  }
1161  }
1162 
1163  // import polygons
1164  {
1165  int uvIndex = 0;
1166  for (int i = 0; i < polyCount; ++i) {
1167  int pSize = fbxMesh->GetPolygonSize(i);
1168 
1169  if (pSize > 4) {
1170  // not handled for the moment
1171  if (!hasTexUVIndexes) uvIndex += pSize;
1172  continue;
1173  }
1174  // we split quads into two triangles
1175 
1176  // vertex indices
1177  int i1 = fbxMesh->GetPolygonVertex(i, 0);
1178  int i2 = fbxMesh->GetPolygonVertex(i, 1);
1179  int i3 = fbxMesh->GetPolygonVertex(i, 2);
1180  mesh->addTriangle(i1, i2, i3);
1181 
1182  int i4 = -1;
1183  if (pSize == 4) {
1184  i4 = fbxMesh->GetPolygonVertex(i, 3);
1185  mesh->addTriangle(i1, i3, i4);
1186  }
1187 
1188  if (vertTexUVTable) {
1189  if (hasTexUVIndexes) {
1190  i1 = fbxMesh->GetTextureUVIndex(i, 0);
1191  if (i1 > uvIndex) uvIndex = i1;
1192  i2 = fbxMesh->GetTextureUVIndex(i, 1);
1193  if (i2 > uvIndex) uvIndex = i2;
1194  i3 = fbxMesh->GetTextureUVIndex(i, 2);
1195  if (i3 > uvIndex) uvIndex = i3;
1196  } else {
1197  i1 = uvIndex++;
1198  i2 = uvIndex++;
1199  i3 = uvIndex++;
1200  }
1201  mesh->addTriangleTexCoordIndexes(i1, i2, i3);
1202  if (pSize == 4) {
1203  if (hasTexUVIndexes) {
1204  i4 = fbxMesh->GetTextureUVIndex(i, 3);
1205  if (i4 > uvIndex) uvIndex = i4;
1206  } else {
1207  i4 = uvIndex++;
1208  }
1209  mesh->addTriangleTexCoordIndexes(i1, i3, i4);
1210  }
1211 
1212  if (uvIndex >=
1213  static_cast<int>(vertTexUVTable->currentSize())) {
1214  CVLog::Warning(QString("[FBX] Mesh '%1': UV coordinates "
1215  "indexes mismatch!")
1216  .arg(fbxMesh->GetName()));
1217  vertTexUVTable->release();
1218  vertTexUVTable = 0;
1219  }
1220  }
1221 
1222  // per-triangle normals
1223  if (normsTable) {
1224  int nIndex = static_cast<int>(normsTable->currentSize());
1225  for (int j = 0; j < pSize; ++j) {
1226  FbxVector4 N;
1227  fbxMesh->GetPolygonVertexNormal(i, j, N);
1228  CCVector3 Npc(
1229  static_cast<PointCoordinateType>(N.Buffer()[0]),
1230  static_cast<PointCoordinateType>(N.Buffer()[1]),
1231  static_cast<PointCoordinateType>(N.Buffer()[2]));
1232  normsTable->addElement(
1234  }
1235 
1236  mesh->addTriangleNormalIndexes(nIndex, nIndex + 1, nIndex + 2);
1237  if (pSize == 4)
1238  mesh->addTriangleNormalIndexes(nIndex, nIndex + 2,
1239  nIndex + 3);
1240  }
1241  }
1242 
1243  if (vertTexUVTable) {
1244  mesh->setTexCoordinatesTable(vertTexUVTable);
1245  }
1246 
1247  if (mesh->size() == 0) {
1249  QString("[FBX] No triangle found in mesh '%1'! (only "
1250  "triangles are supported for the moment)")
1251  .arg(fbxMesh->GetName()));
1252  delete mesh;
1253  return 0;
1254  }
1255  }
1256 
1257  // import vertices
1258  {
1259  const FbxVector4* fbxVertices = fbxMesh->GetControlPoints();
1260  assert(vertices && fbxVertices);
1261  CCVector3d Pshift(0, 0, 0);
1262  for (int i = 0; i < vertCount; ++i, ++fbxVertices) {
1263  CCVector3d P(fbxVertices->Buffer());
1264 
1265  // coordinate shift management
1266  if (i == 0) {
1267  bool preserveCoordinateShift = true;
1269  P, Pshift, preserveCoordinateShift, parameters)) {
1270  if (preserveCoordinateShift) {
1271  vertices->setGlobalShift(Pshift);
1272  }
1274  "[FBX] Mesh has been recentered! Translation: "
1275  "(%.2f ; %.2f ; %.2f)",
1276  Pshift.x, Pshift.y, Pshift.z);
1277  }
1278  }
1279 
1280  CCVector3 PV = CCVector3::fromArray((P + Pshift).u);
1281  vertices->addPoint(PV);
1282  }
1283  }
1284 
1285  // import material mapping (AFTER LOADING THE POLYGONS!)
1286  if (materials) {
1287  int fbxMatCount = fbxMesh->GetElementMaterialCount();
1288  for (int i = 0; i < fbxMatCount; ++i) {
1289  FbxGeometryElementMaterial* lMaterialElement =
1290  fbxMesh->GetElementMaterial(i);
1291  if (lMaterialElement->GetMappingMode() ==
1292  FbxGeometryElement::eByPolygon &&
1293  lMaterialElement->GetReferenceMode() ==
1294  FbxGeometryElement::eIndexToDirect &&
1295  lMaterialElement->GetIndexArray().GetCount() ==
1296  fbxMesh->GetPolygonCount()) {
1297  if (mesh->reservePerTriangleMtlIndexes()) {
1298  int maxMaterialIndex = static_cast<int>(materials->size());
1299  int matElemCount =
1300  lMaterialElement->GetIndexArray().GetCount();
1301  for (int j = 0; j < matElemCount; ++j) {
1302  int mtlIndex =
1303  lMaterialElement->GetIndexArray().GetAt(j);
1304  mesh->addTriangleMtlIndex(
1305  mtlIndex < maxMaterialIndex ? mtlIndex : -1);
1306  }
1307  } else {
1309  "[FBX] Not enough memory to load materials!");
1310  }
1311  break;
1312  }
1313  else if (lMaterialElement->GetMappingMode() == FbxGeometryElement::eAllSame
1314  /*&& lMaterialElement->GetReferenceMode() == FbxGeometryElement::eIndexToDirect*/)
1315  {
1316  int mtlIndex = 0;
1317  if (lMaterialElement->GetReferenceMode() ==
1318  FbxGeometryElement::eIndexToDirect) {
1319  assert(lMaterialElement->GetIndexArray().GetCount() > 0);
1320  mtlIndex = lMaterialElement->GetIndexArray().GetAt(0);
1321  }
1322 
1323  if (mesh->reservePerTriangleMtlIndexes()) {
1324  for (unsigned j = 0; j < mesh->size(); ++j) {
1325  mesh->addTriangleMtlIndex(mtlIndex);
1326  }
1327  } else {
1329  "[FBX] Not enough memory to load materials!");
1330  }
1331  }
1332  }
1333 
1334  if (mesh->hasPerTriangleMtlIndexes()) {
1335  mesh->setMaterialSet(materials);
1336  mesh->showMaterials(true);
1337  } else {
1338  // we failed to load material mapping! No need to kepp the
1339  // materials...
1340  mesh->removeChild(materials);
1341  // materials->release();
1342  materials = 0;
1343  }
1344  }
1345 
1346  return mesh;
1347 }
1348 
1350  ccHObject& container,
1351  LoadParameters& parameters) {
1353 
1354  try {
1355  // Initialize the SDK manager. This object handles memory management.
1356  FbxManager* lSdkManager = FbxManager::Create();
1357 
1358  // Create the IO settings object.
1359  FbxIOSettings* ios = FbxIOSettings::Create(lSdkManager, IOSROOT);
1360  lSdkManager->SetIOSettings(ios);
1361 
1362  // Import options determine what kind of data is to be imported.
1363  // True is the default, but here we'll set some to true explicitly, and
1364  // others to false.
1365  //(*(lSdkManager->GetIOSettings())).SetBoolProp(IMP_FBX_MATERIAL,
1366  // true);
1367  //(*(lSdkManager->GetIOSettings())).SetBoolProp(IMP_FBX_TEXTURE,
1368  // true);
1369 
1370  // Create an importer using the SDK manager.
1371  FbxImporter* lImporter = FbxImporter::Create(lSdkManager, "");
1372 
1374  CVLog::Warning(QString(
1375  "[FBX] Input filename contains special characters. It "
1376  "might be rejected by the third party library..."));
1377  }
1378 
1379  // Use the first argument as the filename for the importer.
1380  if (!lImporter->Initialize(qPrintable(filename), -1,
1381  lSdkManager->GetIOSettings())) {
1383  QString("[FBX] Error: %1")
1384  .arg(lImporter->GetStatus().GetErrorString()));
1386  } else {
1387  // Create a new scene so that it can be populated by the imported
1388  // file.
1389  FbxScene* lScene = FbxScene::Create(lSdkManager, "myScene");
1390 
1391  // Import the contents of the file into the scene.
1392  if (lImporter->Import(lScene)) {
1393  // Print the nodes of the scene and their attributes
1394  // recursively. Note that we are not printing the root node
1395  // because it should not contain any attributes.
1396  FbxNode* lRootNode = lScene->GetRootNode();
1397  std::vector<FbxNode*> nodes;
1398  nodes.push_back(lRootNode);
1399 
1400  // handle units
1401  FbxSystemUnit unitSystem =
1402  lScene->GetGlobalSettings().GetSystemUnit();
1403  double scaleFactor = unitSystem.GetScaleFactor();
1404 
1405  while (!nodes.empty()) {
1406  FbxNode* lNode = nodes.back();
1407  nodes.pop_back();
1408 
1409  const char* nodeName = lNode->GetName();
1410 #ifdef QT_DEBUG
1411  CVLog::Print(QString("Node: %1 - %2 properties")
1412  .arg(nodeName)
1413  .arg(lNode->GetNodeAttributeCount()));
1414 #endif
1415  // scan the node's attributes.
1416  for (int i = 0; i < lNode->GetNodeAttributeCount(); i++) {
1417  FbxNodeAttribute* pAttribute =
1418  lNode->GetNodeAttributeByIndex(i);
1419  FbxNodeAttribute::EType type =
1420  pAttribute->GetAttributeType();
1421 #ifdef QT_DEBUG
1422  CVLog::Print(QString("\tProp. #%1")
1423  .arg(GetAttributeTypeName(type)));
1424 #endif
1425 
1426  switch (type) {
1427  case FbxNodeAttribute::eMesh: {
1428  ccMesh* mesh = FromFbxMesh(
1429  static_cast<FbxMesh*>(pAttribute),
1430  parameters);
1431  if (mesh) {
1432  // apply transformation
1433  FbxAMatrix& transform =
1434  lNode->EvaluateGlobalTransform();
1435  ccGLMatrix mat;
1436  float* data = mat.data();
1437  for (int c = 0; c < 4; ++c, data++) {
1438  FbxVector4 C = transform.GetColumn(c);
1439  data[0] = static_cast<float>(C[0]);
1440  data[4] = static_cast<float>(C[1]);
1441  data[8] = static_cast<float>(C[2]);
1442  data[12] = static_cast<float>(C[3]);
1443  }
1444  // ccGLMatrix invYZ;
1445  // invYZ.toZero();
1446  // invYZ.data()[0] = 1.0;
1447  // invYZ.data()[6] = 1.0;
1448  // invYZ.data()[9] = -1.0;
1449  // invYZ.data()[15] = 1.0;
1450  // mat = invYZ * mat;
1451  mesh->applyGLTransformation_recursive(&mat);
1452  // this transformation is of no interest for
1453  // the user
1455 
1456  if (mesh->getName().isEmpty()) {
1457  mesh->setName(nodeName);
1458  }
1459 
1460  if (scaleFactor != 1.0) {
1461  // save this info for later (in case the
1462  // user exports the mesh as FBX later)
1463  mesh->setMetaData(
1465  scaleFactor);
1466  }
1467 
1468  container.addChild(mesh);
1469  }
1470  } break;
1471 
1472  case FbxNodeAttribute::eUnknown:
1473  case FbxNodeAttribute::eNull:
1474  case FbxNodeAttribute::eMarker:
1475  case FbxNodeAttribute::eSkeleton:
1476  case FbxNodeAttribute::eNurbs:
1477  case FbxNodeAttribute::ePatch:
1478  case FbxNodeAttribute::eCamera:
1479  case FbxNodeAttribute::eCameraStereo:
1480  case FbxNodeAttribute::eCameraSwitcher:
1481  case FbxNodeAttribute::eLight:
1482  case FbxNodeAttribute::eOpticalReference:
1483  case FbxNodeAttribute::eOpticalMarker:
1484  case FbxNodeAttribute::eNurbsCurve:
1485  case FbxNodeAttribute::eTrimNurbsSurface:
1486  case FbxNodeAttribute::eBoundary:
1487  case FbxNodeAttribute::eNurbsSurface:
1488  case FbxNodeAttribute::eShape:
1489  case FbxNodeAttribute::eLODGroup:
1490  case FbxNodeAttribute::eSubDiv:
1491  default:
1492  // not handled yet
1493  break;
1494  }
1495  }
1496 
1497  // Recursively add the children.
1498  for (int j = 0; j < lNode->GetChildCount(); j++) {
1499  nodes.push_back(lNode->GetChild(j));
1500  }
1501  }
1502  }
1503 
1504  if (container.getChildrenNumber() == 0) {
1506  }
1507  }
1508 
1509  // The file is imported, so get rid of the importer.
1510  lImporter->Destroy();
1511  // Destroy the SDK manager and all the other objects it was handling.
1512  lSdkManager->Destroy();
1513  } catch (...) {
1514  CVLog::Warning("[FBX] FBX SDK has thrown an unknown exception!");
1516  }
1517 
1518  return result;
1519 }
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int64_t CV_CLASS_ENUM
Type of object type flags (64 bits)
Definition: CVTypes.h:97
std::string filename
QString GetAttributeTypeName(FbxNodeAttribute::EType type)
Definition: FBXFilter.cpp:700
static bool SaveScene(FbxManager *pManager, FbxDocument *pScene, const char *pFilename, int pFileFormat=-1, bool pEmbedMedia=false)
Definition: FBXFilter.cpp:393
static QString s_defaultOutputFormat
Definition: FBXFilter.cpp:455
static FbxNode * ToFbxMesh(ccGenericMesh *mesh, FbxScene *pScene, QString filename, size_t meshIndex)
Definition: FBXFilter.cpp:51
QString SanitizeFBXFormatString(QString format)
Definition: FBXFilter.cpp:461
static const char FBX_SCALE_METADATA_KEY[]
Definition: FBXFilter.cpp:31
static ccMesh * FromFbxMesh(FbxMesh *fbxMesh, FileIOFilter::LoadParameters &parameters)
Definition: FBXFilter.cpp:750
std::shared_ptr< core::Tensor > image
filament::Texture::InternalFormat format
int count
char type
CC_FILE_ERROR
Typical I/O filter errors.
Definition: FileIOFilter.h:20
@ CC_FERR_CONSOLE_ERROR
Definition: FileIOFilter.h:33
@ CC_FERR_NO_LOAD
Definition: FileIOFilter.h:28
@ CC_FERR_THIRD_PARTY_LIB_EXCEPTION
Definition: FileIOFilter.h:37
@ CC_FERR_BAD_ARGUMENT
Definition: FileIOFilter.h:22
@ CC_FERR_NO_SAVE
Definition: FileIOFilter.h:27
@ CC_FERR_NO_ERROR
Definition: FileIOFilter.h:21
math::float2 uv
core::Tensor result
Definition: VtkUtils.cpp:76
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
Definition: CVLog.cpp:153
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
CC_FILE_ERROR saveToFile(ccHObject *entity, const QString &filename, const SaveParameters &parameters) override
Saves an entity (or a group of) to a file.
Definition: FBXFilter.cpp:469
bool canSave(CV_CLASS_ENUM type, bool &multiple, bool &exclusive) const override
Returns whether this I/O filter can save the specified type of entity.
Definition: FBXFilter.cpp:39
static void SetDefaultOutputFormat(QString format)
Definition: FBXFilter.cpp:457
CC_FILE_ERROR loadFile(const QString &filename, ccHObject &container, LoadParameters &parameters) override
Loads one or more entities from a file.
Definition: FBXFilter.cpp:1349
Generic file I/O filter.
Definition: FileIOFilter.h:46
static bool CheckForSpecialChars(const QString &filename)
Returns whether special characters are present in the input string.
static bool HandleGlobalShift(const CCVector3d &P, CCVector3d &Pshift, bool &preserveCoordinateShift, LoadParameters &loadParameters, bool useInputCoordinatesShiftIfPossible=false)
Shortcut to the ecvGlobalShiftManager mechanism specific for files.
Array of compressed 3D normals (single index)
Array of 2D texture coordinates.
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
Type & getValue(size_t index)
Definition: ecvArray.h:100
bool reserveSafe(size_t count)
Reserves memory (no exception thrown)
Definition: ecvArray.h:56
unsigned currentSize() const
Definition: ecvArray.h:112
void addElement(const Type &value)
Definition: ecvArray.h:105
virtual bool hasColors() const
Returns whether colors are enabled or not.
virtual bool hasNormals() const
Returns whether normals are enabled or not.
virtual void showNormals(bool state)
Sets normals visibility.
virtual void showColors(bool state)
Sets colors visibility.
T * data()
Returns a pointer to internal data.
Float version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:19
Generic mesh interface.
void showNormals(bool state) override
Sets normals visibility.
virtual bool hasTriNormals() const =0
Returns whether the mesh has per-triangle normals.
virtual bool getTriangleNormals(unsigned triangleIndex, CCVector3 &Na, CCVector3 &Nb, CCVector3 &Nc) const =0
Returns a given triangle normal.
virtual ccGenericPointCloud * getAssociatedCloud() const =0
Returns the vertices cloud.
virtual void showMaterials(bool state)
Sets whether textures should be displayed or not.
A 3D cloud interface with associated features (color, normals, octree, etc.)
virtual const CCVector3 & getPointNormal(unsigned pointIndex) const =0
Returns normal corresponding to a given point.
virtual const ecvColor::Rgb & getPointColor(unsigned pointIndex) const =0
Returns color corresponding to a given point.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual void resetGLTransformationHistory_recursive()
Definition: ecvHObject.h:550
void applyGLTransformation_recursive(const ccGLMatrix *trans=nullptr)
Applies the active OpenGL transformation to the entity (recursive)
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
void removeChild(ccHObject *child)
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
Mesh (triangle) material.
int addMaterial(ccMaterial::CShared mat, bool allowDuplicateNames=false)
Adds a material.
Mesh (triangle) material.
Definition: ecvMaterial.h:28
static QImage GetTexture(const QString &absoluteFilename)
Returns the texture image associated to a given name.
QSharedPointer< const ccMaterial > CShared
Const + Shared type.
Definition: ecvMaterial.h:31
QSharedPointer< ccMaterial > Shared
Shared type.
Definition: ecvMaterial.h:33
Triangular mesh.
Definition: ecvMesh.h:35
void getTriangleTexCoordinatesIndexes(unsigned triangleIndex, int &i1, int &i2, int &i3) const override
Returns the triplet of tex coords indexes for a given triangle.
const ccMaterialSet * getMaterialSet() const override
Definition: ecvMesh.h:412
TextureCoordsContainer * getTexCoordinatesTable() const override
Returns per-triangle texture coordinates array.
Definition: ecvMesh.h:476
NormsIndexesTableType * getTriNormsTable() const override
Returns per-triangle normals shared array.
Definition: ecvMesh.h:344
void getTriangleNormalIndexes(unsigned triangleIndex, int &i1, int &i2, int &i3) const override
Returns a triplet of normal indexes for a given triangle (if any)
bool hasMaterials() const override
bool reservePerTriangleMtlIndexes()
Reserves memory to store per-triangle material index.
void setTriNormsTable(NormsIndexesTableType *triNormsTable, bool autoReleaseOldTable=true)
Sets per-triangle normals array (may be shared)
void addTriangleMtlIndex(int mtlIndex)
Adds triangle material index for next triangle.
void setMaterialSet(ccMaterialSet *materialSet, bool autoReleaseOldMaterialSet=true)
Sets associated material set (may be shared)
bool reserve(std::size_t n)
Reserves the memory to store the vertex indexes (3 per triangle)
void addTriangleTexCoordIndexes(int i1, int i2, int i3)
Adds a triplet of tex coords indexes for next triangle.
bool hasTextures() const override
Returns whether textures are available for this mesh.
void addTriangle(unsigned i1, unsigned i2, unsigned i3)
Adds a triangle to the mesh.
bool reservePerTriangleNormalIndexes()
Reserves memory to store per-triangle triplets of normal indexes.
bool hasPerTriangleTexCoordIndexes() const override
Returns whether this mesh as per-triangle triplets of tex coords indexes.
Definition: ecvMesh.h:484
void setTexCoordinatesTable(TextureCoordsContainer *texCoordsTable, bool autoReleaseOldTable=true)
Sets per-triangle texture coordinates array (may be shared)
void addTriangleNormalIndexes(int i1, int i2, int i3)
Adds a triplet of normal indexes for next triangle.
bool reservePerTriangleTexCoordIndexes()
Reserves memory to store per-triangle triplets of tex coords indexes.
virtual unsigned size() const override
Returns the number of triangles.
int getTriangleMtlIndex(unsigned triangleIndex) const override
Returns a given triangle material indexes.
bool hasPerTriangleMtlIndexes() const
Returns whether this mesh as per-triangle material index.
Definition: ecvMesh.h:421
static const CCVector3 & GetNormal(unsigned normIndex)
Static access to ccNormalVectors::getNormal.
static CompressedNormType GetNormIndex(const PointCoordinateType N[])
Returns the compressed index corresponding to a normal vector.
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
QVariant getMetaData(const QString &key) const
Returns a given associated meta data.
bool hasMetaData(const QString &key) const
Returns whether a meta-data element with the given key exists or not.
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void addNorm(const CCVector3 &N)
Pushes a normal vector on stack (shortcut)
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool reserveTheNormsTable()
Reserves memory to store the compressed normals.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
void addRGBColor(const ecvColor::Rgb &C)
Pushes an RGB color on stack.
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
virtual unsigned size() const =0
Returns the number of points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
virtual VerticesIndexes * getTriangleVertIndexes(unsigned triangleIndex)=0
Returns the indexes of the vertices of a given triangle.
virtual unsigned size() const =0
Returns the number of triangles.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
RGB color structure.
Definition: ecvColorTypes.h:49
RGBA color structure.
double normals[3]
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
@ HIERARCHY_OBJECT
Definition: CVTypes.h:103
@ MESH
Definition: CVTypes.h:105
constexpr ColorCompType MAX
Max value of a single color component (default type)
Definition: ecvColorTypes.h:34
Generic loading parameters.
Definition: FileIOFilter.h:51
Generic saving parameters.
Definition: FileIOFilter.h:84
2D texture coordinates
Triangle described by the indexes of its 3 vertices.