SLProject  4.2.000
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
SLAssimpImporter.cpp
Go to the documentation of this file.
1 /**
2  * \file sl/SLAssimpImporter.cpp
3  * \authors Marcus Hudritsch
4  * \date July 2014
5  * \authors Marcus Hudritsch
6  * \copyright http://opensource.org/licenses/GPL-3.0
7  * \remarks Please use clangformat to format the code. See more code style on
8  * https://github.com/cpvrlab/SLProject4/wiki/SLProject-Coding-Style
9  */
10 
11 #include "SL.h"
12 #include <assimp/material.h>
13 #ifdef SL_BUILD_WITH_ASSIMP
14 
15 #include <cstddef>
16 # include <Utils.h>
17 
18 # include <SLAnimation.h>
19 # include <SLAssimpImporter.h>
20 # include <SLGLTexture.h>
21 # include <SLMaterial.h>
22 # include <SLAnimSkeleton.h>
23 # include <SLAssetManager.h>
24 # include <SLAnimManager.h>
25 # include <Profiler.h>
26 # include <SLAssimpProgressHandler.h>
27 # include <SLAssimpIOSystem.h>
28 
29 // assimp is only included in the source file to not expose it to the rest of the framework
30 # include <assimp/Importer.hpp>
31 # include <assimp/scene.h>
32 # include <assimp/pbrmaterial.h>
33 
34 //-----------------------------------------------------------------------------
35 //! Temporary struct to hold keyframe data during assimp import.
36 struct SLImportKeyframe
37 {
38  SLImportKeyframe()
39  : translation(nullptr),
40  rotation(nullptr),
41  scaling(nullptr)
42  {
43  }
44 
45  SLImportKeyframe(aiVectorKey* trans, aiQuatKey* rot, aiVectorKey* scl)
46  {
47  translation = trans;
48  rotation = rot;
49  scaling = scl;
50  }
51 
52  aiVectorKey* translation;
53  aiQuatKey* rotation;
54  aiVectorKey* scaling;
55 };
56 typedef std::map<SLfloat, SLImportKeyframe> KeyframeMap;
57 
58 //-----------------------------------------------------------------------------
59 /* Get the correct translation out of the keyframes map for a given time
60 this function interpolates linearly if no value is present in the map.
61 @note this function does not wrap around to interpolate. If there is no
62  translation key to the right of the passed in time then this function
63  will take the last known value on the left!
64 */
65 SLVec3f getTranslation(SLfloat time, const KeyframeMap& keyframes)
66 {
67  KeyframeMap::const_iterator it = keyframes.find(time);
68  aiVector3D result(0, 0, 0); // return 0 position of nothing was found
69 
70  // If the timestamp passed in doesnt exist then something in the loading
71  // of the kfs went wrong.
72  // @todo this should throw an exception and not kill the app
73  assert(it != keyframes.end() && "A KeyframeMap was passed in with an illegal timestamp.");
74 
75  aiVectorKey* transKey = it->second.translation;
76 
77  // the timestamp has a valid translation value, just return the SL type
78  if (transKey)
79  result = transKey->mValue;
80  else
81  {
82  aiVectorKey* frontKey = nullptr;
83  aiVectorKey* backKey = nullptr;
84 
85  // no translation value present, we must interpolate
86  KeyframeMap::const_reverse_iterator revIt(it);
87 
88  // search to the right
89  for (; it != keyframes.end(); it++)
90  {
91  if (it->second.translation != nullptr)
92  {
93  backKey = it->second.translation;
94  break;
95  }
96  }
97 
98  // search to the left
99  for (; revIt != keyframes.rend(); revIt++)
100  {
101  if (revIt->second.translation != nullptr)
102  {
103  frontKey = revIt->second.translation;
104  break;
105  }
106  }
107 
108  if (frontKey && backKey)
109  {
110  SLfloat frontTime = revIt->first;
111  SLfloat backTime = it->first;
112  SLfloat t = (time - frontTime) / (backTime - frontTime);
113 
114  result = frontKey->mValue + (t * (backKey->mValue - frontKey->mValue));
115  }
116  else if (frontKey)
117  {
118  result = frontKey->mValue;
119  }
120  else if (backKey)
121  {
122  result = backKey->mValue;
123  }
124  }
125 
126  return SLVec3f(result.x, result.y, result.z);
127 }
128 //-----------------------------------------------------------------------------
129 /*! Get the correct scaling out of the keyframes map for a given time
130  this function interpolates linearly if no value is present in the map.
131 
132  @note this function does not wrap around to interpolate. if there is no
133  scaling key to the right of the passed in time then this function
134  will take the last known value on the left!
135 */
136 SLVec3f getScaling(SLfloat time, const KeyframeMap& keyframes)
137 {
138  KeyframeMap::const_iterator it = keyframes.find(time);
139  aiVector3D result(1, 1, 1); // return unit scale if no kf was found
140 
141  // If the timestamp passed in doesnt exist then something in the loading of the kfs went wrong
142  // @todo this should throw an exception and not kill the app
143  assert(it != keyframes.end() && "A KeyframeMap was passed in with an illegal timestamp.");
144 
145  aiVectorKey* scaleKey = it->second.scaling;
146 
147  // the timestamp has a valid translation value, just return the SL type
148  if (scaleKey)
149  result = scaleKey->mValue;
150  else
151  {
152  aiVectorKey* frontKey = nullptr;
153  aiVectorKey* backKey = nullptr;
154 
155  // no translation value present, we must interpolate
156  KeyframeMap::const_reverse_iterator revIt(it);
157 
158  // search to the right
159  for (; it != keyframes.end(); it++)
160  {
161  if (it->second.rotation != nullptr)
162  {
163  backKey = it->second.scaling;
164  break;
165  }
166  }
167 
168  // search to the left
169  for (; revIt != keyframes.rend(); revIt++)
170  {
171  if (revIt->second.rotation != nullptr)
172  {
173  frontKey = revIt->second.scaling;
174  break;
175  }
176  }
177 
178  if (frontKey && backKey)
179  {
180  SLfloat frontTime = revIt->first;
181  SLfloat backTime = it->first;
182  SLfloat t = (time - frontTime) / (backTime - frontTime);
183 
184  result = frontKey->mValue + (t * (backKey->mValue - frontKey->mValue));
185  }
186  else if (frontKey)
187  {
188  result = frontKey->mValue;
189  }
190  else if (backKey)
191  {
192  result = backKey->mValue;
193  }
194  }
195 
196  return SLVec3f(result.x, result.y, result.z);
197 }
198 //-----------------------------------------------------------------------------
199 /*! Get the correct rotation out of the keyframes map for a given time
200  this function interpolates linearly if no value is present in the
201 
202  @note this function does not wrap around to interpolate. if there is no
203  rotation key to the right of the passed in time then this function will take
204  the last known value on the left!
205 */
206 SLQuat4f getRotation(SLfloat time, const KeyframeMap& keyframes)
207 {
208  KeyframeMap::const_iterator it = keyframes.find(time);
209  aiQuaternion result(1, 0, 0, 0); // identity rotation
210 
211  // If the timesamp passed in doesnt exist then something in the loading of the kfs went wrong
212  // @todo this should throw an exception and not kill the app
213  assert(it != keyframes.end() && "A KeyframeMap was passed in with an illegal timestamp.");
214 
215  aiQuatKey* rotKey = it->second.rotation;
216 
217  // the timestamp has a valid translation value, just return the SL type
218  if (rotKey)
219  result = rotKey->mValue;
220  else
221  {
222  aiQuatKey* frontKey = nullptr;
223  aiQuatKey* backKey = nullptr;
224 
225  // no translation value present, we must interpolate
226  KeyframeMap::const_reverse_iterator revIt(it);
227 
228  // search to the right
229  for (; it != keyframes.end(); it++)
230  {
231  if (it->second.rotation != nullptr)
232  {
233  backKey = it->second.rotation;
234  break;
235  }
236  }
237 
238  // search to the left
239  for (; revIt != keyframes.rend(); revIt++)
240  {
241  if (revIt->second.rotation != nullptr)
242  {
243  frontKey = revIt->second.rotation;
244  break;
245  }
246  }
247 
248  if (frontKey && backKey)
249  {
250  SLfloat frontTime = revIt->first;
251  SLfloat backTime = it->first;
252  SLfloat t = (time - frontTime) / (backTime - frontTime);
253 
254  aiQuaternion::Interpolate(result, frontKey->mValue, backKey->mValue, t);
255  }
256  else if (frontKey)
257  {
258  result = frontKey->mValue;
259  }
260  else if (backKey)
261  {
262  result = backKey->mValue;
263  }
264  }
265 
266  return SLQuat4f(result.x, result.y, result.z, result.w);
267 }
268 
269 //-----------------------------------------------------------------------------
270 /*! Loads the scene from a file and creates materials with textures, the
271 meshes and the nodes for the scene graph. Materials, textures and meshes are
272 added to the according vectors of SLScene for later deallocation. If an
273 override material is provided it will be assigned to all meshes and all
274 materials within the file are ignored.
275 */
276 SLNode* SLAssimpImporter::load(SLAnimManager& aniMan, //!< Reference to the animation manager
277  SLAssetManager* assetMgr, //!< Pointer to the asset manager
278  SLstring pathAndFile, //!< File with path or on default path
279  SLstring texturePath, //!< Path to the texture images
280  SLSkybox* skybox, //!< Pointer to the skybox
281  SLbool deleteTexImgAfterBuild, //!< Default = false
282  SLbool loadMeshesOnly, //!< Default = true
283  SLMaterial* overrideMat, //!< Override material
284  float ambientFactor, //!< if ambientFactor > 0 ambient = diffuse * AmbientFactor
285  SLbool forceCookTorranceRM, //!< Forces Cook-Torrance reflection model
286  SLProgressHandler* progressHandler, //!< Pointer to progress handler
287  SLuint flags //!< Import flags (see postprocess.h)
288 )
289 {
291 
292  // clear the intermediate data
293  clear();
294 
295  // Check existence
296  if (!SLFileStorage::exists(pathAndFile, IOK_shader))
297  {
298  SLstring msg = "SLAssimpImporter: File not found: " + pathAndFile + "\n";
299  SL_EXIT_MSG(msg.c_str());
300  return nullptr;
301  }
302 
303  // Import file with assimp importer
304  Assimp::Importer ai;
305 
306  // Set progress handler
307  if (progressHandler)
308  ai.SetProgressHandler((Assimp::ProgressHandler*)progressHandler);
309 
310  ///////////////////////////////////////////////////////////////////////
311  ai.SetIOHandler(new SLAssimpIOSystem());
312  const aiScene* scene = ai.ReadFile(pathAndFile, (SLuint)flags);
313  ///////////////////////////////////////////////////////////////////////
314 
315  if (!scene)
316  {
317  SLstring msg = "Failed to load file: " + pathAndFile + "\n" +
318  ai.GetErrorString() + "\n";
319  SL_WARN_MSG(msg.c_str());
320  return nullptr;
321  }
322 
323  // initial scan of the scene
324  performInitialScan(scene);
325 
326  // load skeleton
327  loadSkeleton(aniMan, nullptr, _skeletonRoot);
328 
329  // load materials
330  SLstring modelPath = Utils::getPath(pathAndFile);
331  SLVMaterial materials;
332  if (!overrideMat)
333  {
334  for (SLint i = 0; i < (SLint)scene->mNumMaterials; i++)
335  materials.push_back(loadMaterial(assetMgr,
336  i,
337  scene->mMaterials[i],
338  modelPath,
339  texturePath,
340  skybox,
341  ambientFactor,
342  forceCookTorranceRM,
343  deleteTexImgAfterBuild));
344  }
345 
346  // load meshes & set their material
347  std::map<int, SLMesh*> meshMap; // map from the ai index to our mesh
348  for (SLint i = 0; i < (SLint)scene->mNumMeshes; i++)
349  {
350  SLMesh* mesh = loadMesh(assetMgr, scene->mMeshes[i]);
351  if (mesh != nullptr)
352  {
353  if (overrideMat)
354  mesh->mat(overrideMat);
355  else
356  mesh->mat(materials[scene->mMeshes[i]->mMaterialIndex]);
357  _meshes.push_back(mesh);
358  meshMap[i] = mesh;
359  }
360  else
361  SL_LOG("SLAsssimpImporter::load failed: %s\nin path: %s",
362  pathAndFile.c_str(),
363  modelPath.c_str());
364  }
365 
366  // load the scene nodes recursively
367  _sceneRoot = loadNodesRec(nullptr, scene->mRootNode, meshMap, loadMeshesOnly);
368 
369  // load animations
370  vector<SLAnimation*> animations;
371  for (SLint i = 0; i < (SLint)scene->mNumAnimations; i++)
372  animations.push_back(loadAnimation(aniMan, scene->mAnimations[i]));
373 
374  logMessage(LV_minimal, "\n---------------------------\n\n");
375 
376  // Rename root node to the more meaningfull filename
377  if (_sceneRoot)
378  _sceneRoot->name(Utils::getFileName(pathAndFile));
379 
380  return _sceneRoot;
381 }
382 //-----------------------------------------------------------------------------
383 //! Clears all helper containers
385 {
386  _nodeMap.clear();
387  _jointOffsets.clear();
388  _skeletonRoot = nullptr;
389  _skeleton = nullptr;
390  _skinnedMeshes.clear();
391 }
392 //-----------------------------------------------------------------------------
393 //! Return an aiNode ptr if name exists, or null if it doesn't
394 aiNode* SLAssimpImporter::getNodeByName(const SLstring& name)
395 {
396  if (_nodeMap.find(name) != _nodeMap.end())
397  return _nodeMap[name];
398 
399  return nullptr;
400 }
401 //-----------------------------------------------------------------------------
402 //! Returns an aiBone ptr if name exists, or null if it doesn't
403 SLMat4f SLAssimpImporter::getOffsetMat(const SLstring& name)
404 {
405  if (_jointOffsets.find(name) != _jointOffsets.end())
406  return _jointOffsets[name];
407 
408  return SLMat4f();
409 }
410 //-----------------------------------------------------------------------------
411 //! Populates nameToNode, nameToBone, jointGroups, skinnedMeshes
412 void SLAssimpImporter::performInitialScan(const aiScene* scene)
413 {
415 
416  // populate the _nameToNode map and print the assimp structure on detailed log verbosity.
417  logMessage(LV_detailed, "[Assimp scene]\n");
418  logMessage(LV_detailed, " Cameras: %d\n", scene->mNumCameras);
419  logMessage(LV_detailed, " Lights: %d\n", scene->mNumLights);
420  logMessage(LV_detailed, " Meshes: %d\n", scene->mNumMeshes);
421  logMessage(LV_detailed, " Materials: %d\n", scene->mNumMaterials);
422  logMessage(LV_detailed, " Textures: %d\n", scene->mNumTextures);
423  logMessage(LV_detailed, " Animations: %d\n", scene->mNumAnimations);
424 
425  logMessage(LV_detailed, "---------------------------------------------\n");
426  logMessage(LV_detailed, " Node node tree: \n");
427  findNodes(scene->mRootNode, " ", true);
428 
429  logMessage(LV_detailed, "---------------------------------------------\n");
430  logMessage(LV_detailed, " Searching for skinned meshes and scanning joint names.\n");
431 
432  findJoints(scene);
433  findSkeletonRoot();
434 }
435 //-----------------------------------------------------------------------------
436 //! Scans the assimp scene graph structure and populates nameToNode
437 void SLAssimpImporter::findNodes(aiNode* node, SLstring padding, SLbool lastChild)
438 {
439  SLstring name = node->mName.C_Str();
440  /*
441  /// @todo we can't allow for duplicate node names, ever at the moment. The 'solution' below
442  /// only hides the problem and moves it to a different part.
443  // rename duplicate node names
444  SLstring renamedString;
445  if (_nodeMap.find(name) != _nodeMap.end())
446  {
447  SLint index = 0;
448  std::ostringstream ss;
449  SLstring lastMatch = name;
450  while (_nodeMap.find(lastMatch) != _nodeMap.end())
451  {
452  ss.str(SLstring());
453  ss.clear();
454  ss << name << "_" << std::setw( 2 ) << std::setfill( '0' ) << index;
455  lastMatch = ss.str();
456  index++;
457  }
458  ss.str(SLstring());
459  ss.clear();
460  ss << "(renamed from '" << name << "')";
461  renamedString = ss.str();
462  name = lastMatch;
463  }*/
464 
465  // this should not happen
466  assert(_nodeMap.find(name) == _nodeMap.end() && "Duplicated node name found!");
467  _nodeMap[name] = node;
468 
469  // logMessage(LV_Detailed, "%s |\n", padding.c_str());
470  // logMessage(LV_detailed,"%s |-[%s] (%d children, %d meshes)", padding.c_str(), name.c_str(), node->mNumChildren, node->mNumMeshes);
471 
472  if (lastChild)
473  padding += " ";
474  else
475  padding += " |";
476 
477  for (SLuint i = 0; i < node->mNumChildren; i++)
478  {
479  findNodes(node->mChildren[i], padding, (i == node->mNumChildren - 1));
480  }
481 }
482 //-----------------------------------------------------------------------------
483 /*! Scans all meshes in the assimp scene and populates nameToBone and
484 jointGroups
485 */
486 void SLAssimpImporter::findJoints(const aiScene* scene)
487 {
488  for (SLuint i = 0; i < scene->mNumMeshes; i++)
489  {
490  aiMesh* mesh = scene->mMeshes[i];
491  if (!mesh->HasBones())
492  continue;
493 
494  logMessage(LV_normal,
495  " Mesh '%s' contains %d joints.\n",
496  mesh->mName.C_Str(),
497  mesh->mNumBones);
498 
499  for (SLuint j = 0; j < mesh->mNumBones; j++)
500  {
501  SLstring name = mesh->mBones[j]->mName.C_Str();
502  std::map<SLstring, SLMat4f>::iterator it = _jointOffsets.find(name);
503  if (it != _jointOffsets.end())
504  continue;
505 
506  // add the offset matrix to our offset matrix map
507  SLMat4f offsetMat;
508  memcpy(&offsetMat, &mesh->mBones[j]->mOffsetMatrix, sizeof(SLMat4f));
509  offsetMat.transpose();
510  _jointOffsets[name] = offsetMat;
511 
512  logMessage(LV_detailed, " Bone '%s' found.\n", name.c_str());
513  }
514  }
515 }
516 //-----------------------------------------------------------------------------
517 /*! Finds the common ancestor for each remaining group in jointGroups,
518 these are our final skeleton roots
519 */
520 void SLAssimpImporter::findSkeletonRoot()
521 {
522  _skeletonRoot = nullptr;
523  // early out if we don't have any joint bindings
524  if (_jointOffsets.empty()) return;
525 
526  vector<SLVaiNode> ancestorList(_jointOffsets.size());
527  SLint minDepth = INT_MAX;
528  SLuint index = 0;
529 
530  logMessage(LV_detailed, "Building joint ancestor lists.\n");
531 
532  auto it = _jointOffsets.begin();
533  for (; it != _jointOffsets.end(); it++, index++)
534  {
535  aiNode* node = getNodeByName(it->first);
536  SLVaiNode& list = ancestorList[index];
537 
538  while (node)
539  {
540  list.insert(list.begin(), node);
541  node = node->mParent;
542  }
543 
544  // log the gathered ancestor list if on diagnostic
545  if (LV_diagnostic)
546  {
547  logMessage(LV_diagnostic,
548  " '%s' ancestor list: ",
549  it->first.c_str());
550 
551  for (auto& i : list)
552  logMessage(LV_diagnostic,
553  "'%s' ",
554  i->mName.C_Str());
555 
556  logMessage(LV_diagnostic, "\n");
557  }
558  else
559  logMessage(LV_detailed,
560  " '%s' lies at a depth of %d\n",
561  it->first.c_str(),
562  list.size());
563 
564  minDepth = std::min(minDepth, (SLint)list.size());
565  }
566 
567  logMessage(LV_detailed,
568  "Bone ancestor lists completed, min depth: %d\n",
569  minDepth);
570 
571  logMessage(LV_detailed,
572  "Searching ancestor lists for common ancestor.\n");
573 
574  // now we have a ancestor list for each joint node beginning with the root node
575  for (SLuint i = 0; i < (SLuint)minDepth; i++)
576  {
577  SLbool failed = false;
578  aiNode* lastMatch = ancestorList[0][i];
579  for (SLuint j = 1; j < ancestorList.size(); j++)
580  {
581  if (ancestorList[j][i] != lastMatch)
582  failed = true;
583 
584  lastMatch = ancestorList[j][i];
585  }
586 
587  // all ancestors matched
588  if (!failed)
589  {
590  _skeletonRoot = lastMatch;
591  logMessage(LV_detailed,
592  "Found matching ancestor '%s'.\n",
593  _skeletonRoot->mName.C_Str());
594  }
595  else
596  {
597  break;
598  }
599  }
600 
601  // seems like the above can be wrong, we should just select the common
602  // ancestor that is one below the assimps root.
603  // @todo fix this function up and make sure there exists a second element
604  if (!_skeletonRoot)
605  _skeletonRoot = ancestorList[0][1];
606 
607  logMessage(LV_normal,
608  "Determined '%s' to be the skeleton's root node.\n",
609  _skeletonRoot->mName.C_Str());
610 }
611 //-----------------------------------------------------------------------------
612 //! Loads the skeleton
613 void SLAssimpImporter::loadSkeleton(SLAnimManager& animManager, SLJoint* parent, aiNode* node)
614 {
615  if (!node)
616  return;
617 
618  SLJoint* joint;
619  SLstring name = node->mName.C_Str();
620 
621  if (!parent)
622  {
623  logMessage(LV_normal, "Loading skeleton skeleton.\n");
624  _skeleton = new SLAnimSkeleton;
625  animManager.skeletons().push_back(_skeleton);
626  _jointIndex = 0;
627 
628  joint = _skeleton->createJoint(name, _jointIndex++);
629  _skeleton->rootJoint(joint);
630  }
631  else
632  {
633  joint = parent->createChild(name, _jointIndex++);
634  }
635 
636  joint->offsetMat(getOffsetMat(name));
637 
638  // set the initial state for the joints (in case we render the model
639  // without playing its animation) an other possibility is to set the joints
640  // to the inverse offset matrix so that the model remains in its bind pose
641  // some files will report the node transformation as the animation state
642  // transformation that the model had when exporting (in case of our astroboy
643  // its in the middle of the animation.
644  // It might be more desirable to have ZERO joint transformations in the initial
645  // pose to be able to see the model without any joint modifications applied
646  // exported state
647 
648  // set the current node transform as the initial state
649  /*
650  SLMat4f om;
651  memcpy(&om, &node->mTransformation, sizeof(SLMat4f));
652  om.transpose();
653  joint->om(om);
654  joint->setInitialState();
655  */
656  // set the binding pose as initial state
657  SLMat4f om;
658  om = joint->offsetMat().inverted();
659  if (parent)
660  om = parent->updateAndGetWM().inverted() * om;
661  joint->om(om);
662  joint->setInitialState();
663 
664  for (SLuint i = 0; i < node->mNumChildren; i++)
665  loadSkeleton(animManager, joint, node->mChildren[i]);
666 }
667 //-----------------------------------------------------------------------------
668 /*!
669 SLAssimpImporter::loadMaterial loads the AssImp aiMat an returns the SLMaterial.
670 The materials and textures are added to the SLScene aiMat and texture
671 vectors.
672 */
673 SLMaterial* SLAssimpImporter::loadMaterial(SLAssetManager* am,
674  SLint index,
675  aiMaterial* aiMat,
676  const SLstring& modelPath,
677  const SLstring& texturePath,
678  SLSkybox* skybox,
679  float ambientFactor,
680  SLbool forceCookTorranceRM,
681  SLbool deleteTexImgAfterBuild)
682 {
684 
685  // Get the materials name
686  aiString matName;
687  aiMat->Get(AI_MATKEY_NAME, matName);
688  SLstring name = matName.data;
689  if (name.empty()) name = "Import Material";
690 
691  // Create SLMaterial instance. It is also added to the SLScene::_materials vector
692  SLMaterial* slMat = new SLMaterial(am, name.c_str());
693 
694  // load all the textures for this aiMat and add it to the aiMat vector
695  for (int tt = aiTextureType_NONE; tt <= aiTextureType_UNKNOWN; ++tt)
696  {
697  aiTextureType aiTexType = (aiTextureType)tt;
698 
699  if (aiMat->GetTextureCount(aiTexType) > 0)
700  {
701  aiString aiPath("");
702  aiTextureMapping mappingType = aiTextureMapping_UV;
703  SLuint uvIndex = 0;
704 
705  aiMat->GetTexture(aiTexType,
706  0,
707  &aiPath,
708  &mappingType,
709  &uvIndex,
710  nullptr,
711  nullptr,
712  nullptr);
713 
714  SLTextureType slTexType = TT_unknown;
715 
716  switch (aiTexType)
717  {
718  case aiTextureType_DIFFUSE: slTexType = TT_diffuse; break;
719  case aiTextureType_NORMALS: slTexType = TT_normal; break;
720  case aiTextureType_SPECULAR: slTexType = TT_specular; break;
721  case aiTextureType_HEIGHT: slTexType = TT_height; break;
722  case aiTextureType_OPACITY: slTexType = TT_diffuse; break;
723  case aiTextureType_EMISSIVE: slTexType = TT_emissive; break;
724  case aiTextureType_LIGHTMAP:
725  {
726  // Check if the glTF occlusion texture is within a occlusionRoughnessMetallic texture
727  aiString fileRoughnessMetallic;
728  aiMat->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE,
729  &fileRoughnessMetallic);
730  SLstring occRghMtlTex = checkFilePath(modelPath,
731  texturePath,
732  fileRoughnessMetallic.data,
733  false);
734  SLstring occlusionTex = checkFilePath(modelPath,
735  texturePath,
736  aiPath.data,
737  false);
738  if (occRghMtlTex == occlusionTex)
739  slTexType = TT_occluRoughMetal;
740  else
741  slTexType = TT_occlusion;
742 
743  // Erleb-AR occulsion map use uvIndex 1
744  string filenameWOExt = Utils::getFileNameWOExt(aiPath.data);
745  if (Utils::startsWithString(filenameWOExt, "AO") ||
746  Utils::endsWithString(filenameWOExt, "AO"))
747  uvIndex = 1;
748 
749  break; // glTF stores AO maps as light maps
750  }
751  case aiTextureType_AMBIENT_OCCLUSION:
752  {
753  // Check if the glTF occlusion texture is within a occlusionRoughnessMetallic texture
754  aiString fileRoughnessMetallic;
755  aiMat->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE,
756  &fileRoughnessMetallic);
757  SLstring occRghMtlTex = checkFilePath(modelPath,
758  texturePath,
759  fileRoughnessMetallic.data,
760  false);
761  SLstring occlusionTex = checkFilePath(modelPath,
762  texturePath,
763  aiPath.data,
764  false);
765  if (occRghMtlTex == occlusionTex)
766  slTexType = TT_occluRoughMetal;
767  else
768  slTexType = TT_occlusion;
769  break; // glTF stores AO maps as light maps
770  }
771  case aiTextureType_UNKNOWN:
772  {
773  // Check if the unknown texture is a roughnessMetallic texture
774  aiString fileMetallicRoughness;
775  aiMat->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE,
776  &fileMetallicRoughness);
777  SLstring rghMtlTex = checkFilePath(modelPath,
778  texturePath,
779  fileMetallicRoughness.data,
780  false);
781  SLstring unknownTex = checkFilePath(modelPath,
782  texturePath,
783  aiPath.data,
784  false);
785  if (rghMtlTex == unknownTex)
786  {
787  // Check if the roughnessMetallic texture also is the occlusion texture
788  aiString fileOcclusion;
789  aiMat->GetTexture(aiTextureType_LIGHTMAP,
790  0,
791  &fileOcclusion);
792  SLstring occlusionTex = checkFilePath(modelPath,
793  texturePath,
794  fileOcclusion.data,
795  false);
796  if (rghMtlTex == occlusionTex)
797  slTexType = TT_unknown; // Don't load twice. The occlusionRoughnessMetallic texture will be loaded as aiTextureType_LIGHTMAP
798  else
799  slTexType = TT_roughMetal;
800  }
801  else
802  slTexType = TT_unknown;
803  break;
804  }
805  default: break;
806  }
807 
808  SLstring texFile = checkFilePath(modelPath, texturePath, aiPath.data);
809 
810  // Only color texture are loaded so far
811  // For normal maps we have to adjust first the normal and tangent generation
812  if (slTexType == TT_diffuse ||
813  slTexType == TT_normal ||
814  slTexType == TT_occlusion ||
815  slTexType == TT_emissive ||
816  slTexType == TT_roughMetal ||
817  slTexType == TT_occluRoughMetal)
818  {
819  SLGLTexture* slTex = loadTexture(am,
820  texFile,
821  slTexType,
822  uvIndex,
823  deleteTexImgAfterBuild);
824  slMat->addTexture(slTex);
825  }
826  }
827  }
828 
829  // get color data
830  aiColor3D ambient, diffuse, specular, emissive;
831  SLfloat shininess, refracti, reflectivity, transparencyFactor, opacity, roughness = -1, metalness = -1;
832  aiMat->Get(AI_MATKEY_COLOR_AMBIENT, ambient);
833  aiMat->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse);
834  aiMat->Get(AI_MATKEY_COLOR_SPECULAR, specular);
835  aiMat->Get(AI_MATKEY_COLOR_EMISSIVE, emissive);
836  aiMat->Get(AI_MATKEY_SHININESS, shininess);
837  aiMat->Get(AI_MATKEY_REFRACTI, refracti);
838  aiMat->Get(AI_MATKEY_REFLECTIVITY, reflectivity);
839  aiMat->Get(AI_MATKEY_OPACITY, opacity);
840  aiMat->Get(AI_MATKEY_TRANSPARENCYFACTOR, transparencyFactor);
841  aiMat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, metalness);
842  aiMat->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, roughness);
843 
844  // increase shininess if specular color is not low.
845  // The aiMat will otherwise be too bright
846  if (specular.r > 0.5f &&
847  specular.g > 0.5f &&
848  specular.b > 0.5f &&
849  shininess < 0.01f)
850  shininess = 10.0f;
851 
852  // set color data
853  if (ambientFactor > 0.0f)
854  slMat->ambient(SLCol4f(diffuse.r * ambientFactor,
855  diffuse.g * ambientFactor,
856  diffuse.b * ambientFactor));
857  else
858  slMat->ambient(SLCol4f(ambient.r, ambient.g, ambient.b));
859 
860  slMat->diffuse(SLCol4f(diffuse.r, diffuse.g, diffuse.b));
861  slMat->specular(SLCol4f(specular.r, specular.g, specular.b));
862  slMat->emissive(SLCol4f(emissive.r, emissive.g, emissive.b));
863  slMat->shininess(shininess);
864  slMat->roughness(roughness);
865  slMat->metalness(metalness);
866 
867  // Switch lighting model to PBR (RM_CookTorrance) only if PBR textures are used.
868  // PBR without must be set by additional setter call
869  if (slMat->hasTextureType(TT_roughness) ||
870  slMat->hasTextureType(TT_metallic) ||
871  slMat->hasTextureType(TT_roughMetal) ||
873  forceCookTorranceRM)
874  {
876  slMat->skybox(skybox);
877 
878  if (roughness == -1.0f)
879  slMat->roughness(1.0f);
880 
881  if (metalness == -1.0f)
882  slMat->metalness(0.0f);
883  }
884  else
885  {
887  }
888 
889  return slMat;
890 }
891 //-----------------------------------------------------------------------------
892 /*!
893 SLAssimpImporter::loadTexture loads the AssImp texture an returns the SLGLTexture
894 */
895 SLGLTexture* SLAssimpImporter::loadTexture(SLAssetManager* assetMgr,
896  SLstring& textureFile,
897  SLTextureType texType,
898  SLuint uvIndex,
899  SLbool deleteTexImgAfterBuild)
900 {
902 
903  SLVGLTexture& allLoadedTex = assetMgr->textures();
904 
905  // return if a texture with the same file already exists
906  for (auto& i : allLoadedTex)
907  if (i->url() == textureFile)
908  return i;
909 
910  SLint minificationFilter = texType == TT_occlusion ? GL_LINEAR : SL_ANISOTROPY_MAX;
911 
912  // Create the new texture. It is also push back to SLScene::_textures
913  SLGLTexture* texture = new SLGLTexture(assetMgr,
914  textureFile,
915  minificationFilter,
916  GL_LINEAR,
917  texType);
918  texture->uvIndex((SLbyte)uvIndex);
919 
920  // if texture images get deleted after build you can't do ray tracing
921  if (deleteTexImgAfterBuild)
922  texture->deleteImageAfterBuild(true);
923 
924  return texture;
925 }
926 //-----------------------------------------------------------------------------
927 /*!
928 SLAssimpImporter::loadMesh creates a new SLMesh an copies the meshs vertex data and
929 triangle face indices. Normals & tangents are not loaded. They are calculated
930 in SLMesh.
931 */
932 SLMesh* SLAssimpImporter::loadMesh(SLAssetManager* am, aiMesh* mesh)
933 {
935 
936  // Count first the NO. of triangles in the mesh
937  SLuint numPoints = 0;
938  SLuint numLines = 0;
939  SLuint numTriangles = 0;
940  SLuint numPolygons = 0;
941 
942  for (unsigned int i = 0; i < mesh->mNumFaces; ++i)
943  {
944  if (mesh->mFaces[i].mNumIndices == 1) numPoints++;
945  if (mesh->mFaces[i].mNumIndices == 2) numLines++;
946  if (mesh->mFaces[i].mNumIndices == 3) numTriangles++;
947  if (mesh->mFaces[i].mNumIndices > 3) numPolygons++;
948  }
949 
950  // A mesh can contain either point, lines or triangles
951  if ((numTriangles && (numLines || numPoints)) ||
952  (numLines && (numTriangles || numPoints)) ||
953  (numPoints && (numLines || numTriangles)))
954  {
955  // SL_LOG("SLAssimpImporter::loadMesh: Mesh contains multiple primitive types: %s, Lines: %d, Points: %d",
956  // mesh->mName.C_Str(),
957  // numLines,
958  // numPoints);
959 
960  // Prioritize triangles over lines over points
961  if (numTriangles && numLines) numLines = 0;
962  if (numTriangles && numPoints) numPoints = 0;
963  if (numLines && numPoints) numPoints = 0;
964  }
965 
966  if (numPolygons > 0)
967  {
968  SL_LOG("SLAssimpImporter::loadMesh: Mesh contains polygons: %s",
969  mesh->mName.C_Str());
970  return nullptr;
971  }
972 
973  // We only load meshes that contain triangles or lines
974  if (mesh->mNumVertices == 0)
975  {
976  SL_LOG("SLAssimpImporter::loadMesh: Mesh has no vertices: %s",
977  mesh->mName.C_Str());
978  return nullptr;
979  }
980 
981  // We only load meshes that contain triangles or lines
982  if (numTriangles == 0 && numLines == 0 && numPoints == 0)
983  {
984  SL_LOG("SLAssimpImporter::loadMesh: Mesh has has no triangles nor lines nor points: %s",
985  mesh->mName.C_Str());
986  return nullptr;
987  }
988 
989  // create a new mesh.
990  // The mesh pointer is added automatically to the SLScene::meshes vector.
991  SLstring name = mesh->mName.data;
992  SLMesh* m = new SLMesh(am, name.empty() ? "Imported Mesh" : name);
993 
994  // Set primitive type
995  if (numTriangles) m->primitive(SLGLPrimitiveType::PT_triangles);
996  if (numLines) m->primitive(SLGLPrimitiveType::PT_lines);
997  if (numPoints) m->primitive(SLGLPrimitiveType::PT_points);
998 
999  // Create position & normal vector
1000  m->P.clear();
1001  m->P.resize(mesh->mNumVertices);
1002 
1003  // Create normal vector for triangle primitive types
1004  if (mesh->HasNormals() && numTriangles)
1005  {
1006  m->N.clear();
1007  m->N.resize(m->P.size());
1008  }
1009 
1010  // Allocate 1st tex. coord. vector if needed
1011  if (mesh->HasTextureCoords(0) && numTriangles)
1012  {
1013  m->UV[0].clear();
1014  m->UV[0].resize(m->P.size());
1015  }
1016 
1017  // Allocate 2nd texture coordinate vector if needed
1018  // Some models use multiple textures with different uv's
1019  if (mesh->HasTextureCoords(1) && numTriangles)
1020  {
1021  m->UV[1].clear();
1022  m->UV[1].resize(m->P.size());
1023  }
1024 
1025  // copy vertex positions & tex. coord.
1026  for (SLuint i = 0; i < m->P.size(); ++i)
1027  {
1028  m->P[i].set(mesh->mVertices[i].x,
1029  mesh->mVertices[i].y,
1030  mesh->mVertices[i].z);
1031  if (!m->N.empty())
1032  m->N[i].set(mesh->mNormals[i].x,
1033  mesh->mNormals[i].y,
1034  mesh->mNormals[i].z);
1035  if (!m->UV[0].empty())
1036  m->UV[0][i].set(mesh->mTextureCoords[0][i].x,
1037  mesh->mTextureCoords[0][i].y);
1038  if (!m->UV[1].empty())
1039  m->UV[1][i].set(mesh->mTextureCoords[1][i].x,
1040  mesh->mTextureCoords[1][i].y);
1041  }
1042 
1043  // create primitive index vector
1044  SLuint j = 0;
1045  if (m->P.size() < 65536)
1046  {
1047  m->I16.clear();
1048  if (numTriangles)
1049  {
1050  m->I16.resize((static_cast<size_t>(numTriangles * 3)));
1051  for (SLuint i = 0; i < mesh->mNumFaces; ++i)
1052  {
1053  if (mesh->mFaces[i].mNumIndices == 3)
1054  {
1055  m->I16[j++] = (SLushort)mesh->mFaces[i].mIndices[0];
1056  m->I16[j++] = (SLushort)mesh->mFaces[i].mIndices[1];
1057  m->I16[j++] = (SLushort)mesh->mFaces[i].mIndices[2];
1058  }
1059  }
1060  }
1061  else if (numLines)
1062  {
1063  m->I16.resize((size_t)numLines * 2);
1064  for (SLuint i = 0; i < mesh->mNumFaces; ++i)
1065  {
1066  if (mesh->mFaces[i].mNumIndices == 2)
1067  {
1068  m->I16[j++] = (SLushort)mesh->mFaces[i].mIndices[0];
1069  m->I16[j++] = (SLushort)mesh->mFaces[i].mIndices[1];
1070  }
1071  }
1072  }
1073  else if (numPoints)
1074  {
1075  m->I16.resize(numPoints);
1076  for (SLuint i = 0; i < mesh->mNumFaces; ++i)
1077  {
1078  if (mesh->mFaces[i].mNumIndices == 1)
1079  m->I16[j++] = (SLushort)mesh->mFaces[i].mIndices[0];
1080  }
1081  }
1082 
1083  // check for invalid indices
1084  for (auto i : m->I16)
1085  assert(i < m->P.size() && "SLAssimpImporter::loadMesh: Invalid Index");
1086  }
1087  else
1088  {
1089  m->I32.clear();
1090  if (numTriangles)
1091  {
1092  m->I32.resize((size_t)numTriangles * 3);
1093  for (SLuint i = 0; i < mesh->mNumFaces; ++i)
1094  {
1095  if (mesh->mFaces[i].mNumIndices == 3)
1096  {
1097  m->I32[j++] = mesh->mFaces[i].mIndices[0];
1098  m->I32[j++] = mesh->mFaces[i].mIndices[1];
1099  m->I32[j++] = mesh->mFaces[i].mIndices[2];
1100  }
1101  }
1102  }
1103  else if (numLines)
1104  {
1105  m->I32.resize((size_t)numLines * 2);
1106  for (SLuint i = 0; i < mesh->mNumFaces; ++i)
1107  {
1108  if (mesh->mFaces[i].mNumIndices == 2)
1109  {
1110  m->I32[j++] = mesh->mFaces[i].mIndices[0];
1111  m->I32[j++] = mesh->mFaces[i].mIndices[1];
1112  }
1113  }
1114  }
1115  else if (numPoints)
1116  {
1117  m->I32.resize((size_t)numPoints * 1);
1118  for (SLuint i = 0; i < mesh->mNumFaces; ++i)
1119  {
1120  if (mesh->mFaces[i].mNumIndices == 1)
1121  m->I32[j++] = mesh->mFaces[i].mIndices[0];
1122  }
1123  }
1124 
1125  // check for invalid indices
1126  for (auto i : m->I32)
1127  assert(i < m->P.size() && "SLAssimpImporter::loadMesh: Invalid Index");
1128  }
1129 
1130  if (!mesh->HasNormals() && numTriangles)
1131  m->calcNormals();
1132 
1133  // load joints
1134  if (mesh->HasBones())
1135  {
1136  _skinnedMeshes.push_back(m);
1137  m->skeleton(_skeleton);
1138 
1139  m->Ji.resize(m->P.size());
1140  m->Jw.resize(m->P.size());
1141 
1142  for (SLuint i = 0; i < mesh->mNumBones; i++)
1143  {
1144  aiBone* joint = mesh->mBones[i];
1145  SLJoint* slJoint = _skeleton->getJoint(joint->mName.C_Str());
1146 
1147  // @todo On OSX it happens from time to time that slJoint is nullptr
1148  if (slJoint)
1149  {
1150  for (SLuint nW = 0; nW < joint->mNumWeights; nW++)
1151  {
1152  // add the weight
1153  SLuint vertId = joint->mWeights[nW].mVertexId;
1154  SLfloat weight = joint->mWeights[nW].mWeight;
1155 
1156  m->Ji[vertId].push_back((SLuchar)slJoint->id());
1157  m->Jw[vertId].push_back(weight);
1158 
1159  // check if the bones max radius changed
1160  // @todo this is very specific to this loaded mesh,
1161  // when we add a skeleton instances class this radius
1162  // calculation has to be done on the instance!
1163  slJoint->calcMaxRadius(SLVec3f(mesh->mVertices[vertId].x,
1164  mesh->mVertices[vertId].y,
1165  mesh->mVertices[vertId].z));
1166  }
1167  }
1168  else
1169  {
1170  SL_LOG("Failed to load joint of skeleton in SLAssimpImporter::loadMesh: %s",
1171  joint->mName.C_Str());
1172  // return nullptr;
1173  }
1174  }
1175  }
1176 
1177  return m;
1178 }
1179 //-----------------------------------------------------------------------------
1180 /*!
1181 SLAssimpImporter::loadNodesRec loads the scene graph node tree recursively.
1182 */
1183 SLNode* SLAssimpImporter::loadNodesRec(SLNode* curNode, //!< Pointer to the current node. Pass nullptr for root node
1184  aiNode* node, //!< The according assimp node. Pass nullptr for root node
1185  SLMeshMap& meshes, //!< Reference to the meshes vector
1186  SLbool loadMeshesOnly) //!< Only load nodes with meshes
1187 {
1188  PROFILE_FUNCTION();
1189 
1190  // we're at the root
1191  if (!curNode)
1192  curNode = new SLNode(node->mName.data);
1193 
1194  // load local transform
1195  aiMatrix4x4* M = &node->mTransformation;
1196 
1197  // clang-format off
1198  SLMat4f SLM(M->a1, M->a2, M->a3, M->a4,
1199  M->b1, M->b2, M->b3, M->b4,
1200  M->c1, M->c2, M->c3, M->c4,
1201  M->d1, M->d2, M->d3, M->d4);
1202  // clang-format on
1203 
1204  curNode->om(SLM);
1205 
1206  // New: Add only one mesh per node so that they can be sorted by material
1207  // If a mesh has multiple meshes add a sub-node for each mesh
1208  if (node->mNumMeshes > 1)
1209  {
1210  for (SLuint i = 0; i < node->mNumMeshes; ++i)
1211  {
1212  // Only add meshes that were added to the meshMap (triangle meshes)
1213  if (meshes.count((SLint)node->mMeshes[i]))
1214  {
1215  SLstring nodeMeshName = node->mName.data;
1216  nodeMeshName += "-";
1217  nodeMeshName += meshes[(SLint)node->mMeshes[i]]->name();
1218  SLNode* child = new SLNode(nodeMeshName);
1219  curNode->addChild(child);
1220  child->addMesh(meshes[(SLint)node->mMeshes[i]]);
1221  }
1222  }
1223  }
1224  else if (node->mNumMeshes == 1)
1225  {
1226  // Only add meshes that were added to the meshMap (triangle meshes)
1227  if (meshes.count((SLint)node->mMeshes[0]))
1228  curNode->addMesh(meshes[(SLint)node->mMeshes[0]]);
1229  }
1230 
1231  // load children recursively
1232  for (SLuint i = 0; i < node->mNumChildren; i++)
1233  {
1234  // skip the skeleton
1235  if (node->mChildren[i] == _skeletonRoot)
1236  continue;
1237 
1238  // only add subtrees that contain a mesh in one of their nodes
1239  if (!loadMeshesOnly || aiNodeHasMesh(node->mChildren[i]))
1240  {
1241  SLNode* child = new SLNode(node->mChildren[i]->mName.data);
1242  curNode->addChild(child);
1243  loadNodesRec(child, node->mChildren[i], meshes);
1244  }
1245  }
1246 
1247  return curNode;
1248 }
1249 //-----------------------------------------------------------------------------
1250 /*!
1251 SLAssimpImporter::loadAnimation loads the scene graph node tree recursively.
1252 */
1253 SLAnimation* SLAssimpImporter::loadAnimation(SLAnimManager& animManager, aiAnimation* anim)
1254 {
1255  ostringstream oss;
1256  oss << "unnamed_anim_" << animManager.animationNames().size();
1257  SLstring animName = oss.str();
1258  SLfloat animTicksPerSec = (anim->mTicksPerSecond < 0.0001f)
1259  ? 30.0f
1260  : (SLfloat)anim->mTicksPerSecond;
1261  SLfloat animDuration = (SLfloat)anim->mDuration / animTicksPerSec;
1262 
1263  if (anim->mName.length > 0)
1264  animName = anim->mName.C_Str();
1265 
1266  // log
1267  logMessage(LV_minimal, "\nLoading animation %s\n", animName.c_str());
1268  logMessage(LV_normal, " Duration(seconds): %f \n", animDuration);
1269  logMessage(LV_normal, " Duration(ticks): %f \n", anim->mDuration);
1270  logMessage(LV_normal, " Ticks per second: %f \n", animTicksPerSec);
1271  logMessage(LV_normal, " Num channels: %d\n", anim->mNumChannels);
1272 
1273  // exit if we didn't load a skeleton but have animations for one
1274  if (!_skinnedMeshes.empty())
1275  assert(_skeleton != nullptr && "The skeleton wasn't imported correctly.");
1276 
1277  // create the animation
1278  SLAnimation* result;
1279  if (_skeleton)
1280  result = _skeleton->createAnimation(animManager, animName, animDuration);
1281  else
1282  {
1283  result = animManager.createNodeAnimation(animName, animDuration);
1284  _animationNamesMap.push_back(result);
1285  }
1286 
1287  SLbool isSkeletonAnim = false;
1288  for (SLuint i = 0; i < anim->mNumChannels; i++)
1289  {
1290  aiNodeAnim* channel = anim->mChannels[i];
1291 
1292  // find the node that is animated by this channel
1293  SLstring nodeName = channel->mNodeName.C_Str();
1294  SLNode* affectedNode = _sceneRoot->find<SLNode>(nodeName);
1295  SLuint id = 0;
1296  SLbool isJointNode = (affectedNode == nullptr);
1297 
1298  // @todo: this is currently a work around but it can happen that we receive normal node animation tracks
1299  // and joint animation tracks we don't allow node animation tracks in a skeleton animation, so we
1300  // should split an animation in two separate animations if this happens. for now we just ignore node
1301  // animation tracks if we already have joint tracks ofc this will crash if the first track is a node
1302  // anim but its just temporary
1303  if (!isJointNode && isSkeletonAnim)
1304  continue;
1305 
1306  // is there a skeleton and is this animation channel not affecting a normal node?
1307  if (_skeletonRoot && !affectedNode)
1308  {
1309  isSkeletonAnim = true;
1310  SLJoint* affectedJoint = _skeleton->getJoint(nodeName);
1311  if (affectedJoint == nullptr)
1312  break;
1313 
1314  id = affectedJoint->id();
1315  // @todo warn if we find an animation with some node channels and some joint channels
1316  // this shouldn't happen!
1317 
1318  /// @todo [high priority!] Address the problem of some bones not containing an animation channel
1319  /// when importing. Current workaround is to set their reset position to their bind pose.
1320  /// This will however fail if we have multiple animations affecting a single model and fading
1321  /// some of them out or in. This will require us to provide animations that have a channel
1322  /// for all bones even if they're just positional.
1323  // What does this next line do?
1324  //
1325  // The testimportfile we used (Astroboy.dae) has the following properties:
1326  // > It has joints in the skeleton that aren't animated by any channel.
1327  // > The joints need a reset position of (0, 0, 0) to work properly
1328  // because the joint position is contained in a single keyframe for every joint
1329  //
1330  // Since some of the joints don't have a channel that animates them, they also lack
1331  // the joint position that the other joints get from their animation channel.
1332  // So we need to set the initial state for all joints that have a channel
1333  // to identity.
1334  // All joints that arent in a channel will receive their local joint bind pose as
1335  // reset position.
1336  //
1337  // The problem stems from the design desicion to reset a whole skeleton before applying
1338  // animations to it. If we were to reset each joint just before applying a channel to it
1339  // we wouldn't have this problem. But we coulnd't blend animations as easily.
1340  //
1341  SLMat4f prevOM = affectedJoint->om();
1342  affectedJoint->om(SLMat4f());
1343  affectedJoint->setInitialState();
1344  affectedJoint->om(prevOM);
1345  }
1346 
1347  // log
1348  logMessage(LV_normal, "\n Channel %d %s", i, (isJointNode) ? "(joint animation)\n" : "\n");
1349  logMessage(LV_normal, " Affected node: %s\n", channel->mNodeName.C_Str());
1350  logMessage(LV_detailed, " Num position keys: %d\n", channel->mNumPositionKeys);
1351  logMessage(LV_detailed, " Num rotation keys: %d\n", channel->mNumRotationKeys);
1352  logMessage(LV_detailed, " Num scaling keys: %d\n", channel->mNumScalingKeys);
1353 
1354  // joint animation channels should receive the correct node id, normal node animations just get 0
1355  SLNodeAnimTrack* track = result->createNodeAnimTrack(id);
1356 
1357  // this is a node animation only, so we add a reference to the affected node to the track
1358  if (affectedNode && !isSkeletonAnim)
1359  {
1360  track->animatedNode(affectedNode);
1361  }
1362 
1363  KeyframeMap keyframes;
1364 
1365  // add position keys
1366  for (SLuint iK = 0; iK < channel->mNumPositionKeys; iK++)
1367  {
1368  SLfloat time = (SLfloat)channel->mPositionKeys[iK].mTime / animTicksPerSec;
1369  keyframes[time] = SLImportKeyframe(&channel->mPositionKeys[iK], nullptr, nullptr);
1370  }
1371 
1372  // add rotation keys
1373  for (SLuint iK = 0; iK < channel->mNumRotationKeys; iK++)
1374  {
1375  SLfloat time = (SLfloat)channel->mRotationKeys[iK].mTime / animTicksPerSec;
1376 
1377  if (keyframes.find(time) == keyframes.end())
1378  keyframes[time] = SLImportKeyframe(nullptr, &channel->mRotationKeys[iK], nullptr);
1379  else
1380  {
1381  // @todo this shouldn't abort but just throw an exception
1382  assert(keyframes[time].rotation == nullptr && "There were two rotation keys assigned to the same timestamp.");
1383  keyframes[time].rotation = &channel->mRotationKeys[iK];
1384  }
1385  }
1386 
1387  // add scaling keys
1388  for (SLuint iK = 0; iK < channel->mNumScalingKeys; iK++)
1389  {
1390  SLfloat time = (SLfloat)channel->mScalingKeys[iK].mTime / animTicksPerSec;
1391 
1392  if (keyframes.find(time) == keyframes.end())
1393  keyframes[time] = SLImportKeyframe(nullptr, nullptr, &channel->mScalingKeys[iK]);
1394  else
1395  {
1396  // @todo this shouldn't abort but just throw an exception
1397  assert(keyframes[time].scaling == nullptr && "There were two scaling keys assigned to the same timestamp.");
1398  keyframes[time].scaling = &channel->mScalingKeys[iK];
1399  }
1400  }
1401 
1402  logMessage(LV_normal, " Found %d distinct keyframe timestamp(s).\n", keyframes.size());
1403 
1404  for (auto it : keyframes)
1405  {
1406  SLTransformKeyframe* kf = track->createNodeKeyframe(it.first);
1407  kf->translation(getTranslation(it.first, keyframes));
1408  kf->rotation(getRotation(it.first, keyframes));
1409  kf->scale(getScaling(it.first, keyframes));
1410 
1411  // log
1412  logMessage(LV_detailed,
1413  "\n Generating keyframe at time '%.2f'\n",
1414  it.first);
1415  logMessage(LV_detailed,
1416  " Translation: (%.2f, %.2f, %.2f) %s\n",
1417  kf->translation().x,
1418  kf->translation().y,
1419  kf->translation().z,
1420  (it.second.translation != nullptr) ? "imported" : "generated");
1421  logMessage(LV_detailed,
1422  " Rotation: (%.2f, %.2f, %.2f, %.2f) %s\n",
1423  kf->rotation().x(),
1424  kf->rotation().y(),
1425  kf->rotation().z(),
1426  kf->rotation().w(),
1427  (it.second.rotation != nullptr) ? "imported" : "generated");
1428  logMessage(LV_detailed,
1429  " Scale: (%.2f, %.2f, %.2f) %s\n",
1430  kf->scale().x,
1431  kf->scale().y,
1432  kf->scale().z,
1433  (it.second.scaling != nullptr) ? "imported" : "generated");
1434  }
1435  }
1436 
1437  return result;
1438 }
1439 //-----------------------------------------------------------------------------
1440 /*!
1441 SLAssimpImporter::aiNodeHasMesh returns true if the passed node or one of its
1442 children has a mesh. aiNode can contain only transform or joint nodes without
1443 any visuals.
1444 
1445 @todo this function doesn't look well optimized. It's currently used if the option to
1446  only load nodes containing meshes somewhere in their hierarchy is enabled.
1447  This means we call it on ancestor nodes first. This also means that we will
1448  redundantly traverse the same exact nodes multiple times. This isn't a pressing
1449  issue at the moment but should be tackled when this importer is being optimized
1450 */
1451 SLbool SLAssimpImporter::aiNodeHasMesh(aiNode* node)
1452 {
1453  if (node->mNumMeshes > 0) return true;
1454 
1455  for (SLuint i = 0; i < node->mNumChildren; i++)
1456  if (node->mChildren[i]->mNumMeshes > 0)
1457  return aiNodeHasMesh(node->mChildren[i]);
1458  return false;
1459 }
1460 //-----------------------------------------------------------------------------
1461 /*!
1462 SLAssimpImporter::checkFilePath tries to build the full absolut texture file path.
1463 Some file formats have absolute path stored, some have relative paths.
1464 1st attempt: modelPath + aiTexFile
1465 2nd attempt: aiTexFile
1466 3rd attempt: modelPath + getFileName(aiTexFile)
1467 If a model contains absolute path it is best to put all texture files beside the
1468 model file in the same folder.
1469 */
1470 SLstring SLAssimpImporter::checkFilePath(const SLstring& modelPath,
1471  const SLstring& texturePath,
1472  SLstring aiTexFile,
1473  bool showWarning)
1474 {
1475  // Check path & file combination
1476  SLstring pathFile = modelPath + aiTexFile;
1477  if (SLFileStorage::exists(pathFile, IOK_generic))
1478  return pathFile;
1479 
1480  // Check file alone
1481  if (SLFileStorage::exists(aiTexFile, IOK_generic))
1482  return aiTexFile;
1483 
1484  // Check path & file combination
1485  pathFile = modelPath + Utils::getFileName(aiTexFile);
1486  if (SLFileStorage::exists(pathFile, IOK_generic))
1487  return pathFile;
1488 
1489  if (showWarning)
1490  SL_LOG_DEBUG("**** WARNING ****: SLAssimpImporter: Texture file not found: %s from model %s",
1491  aiTexFile.c_str(),
1492  modelPath.c_str());
1493 
1494  // Return path for texture not found image;
1495  return texturePath + "TexNotFound.png";
1496 }
1497 //-----------------------------------------------------------------------------
1498 
1499 #endif // SL_BUILD_WITH_ASSIMP
#define PROFILE_FUNCTION()
Definition: Instrumentor.h:41
float SLfloat
Definition: SL.h:173
#define SL_LOG_DEBUG(...)
Definition: SL.h:237
#define SL_LOG(...)
Definition: SL.h:233
unsigned int SLuint
Definition: SL.h:171
#define SL_WARN_MSG(message)
Definition: SL.h:241
unsigned char SLuchar
Definition: SL.h:163
bool SLbool
Definition: SL.h:175
unsigned short SLushort
Definition: SL.h:169
#define SL_EXIT_MSG(message)
Definition: SL.h:240
signed char SLbyte
Definition: SL.h:166
string SLstring
Definition: SL.h:158
int SLint
Definition: SL.h:170
@ LV_diagnostic
Definition: SLEnums.h:249
@ LV_normal
Definition: SLEnums.h:247
@ LV_minimal
Definition: SLEnums.h:246
@ LV_detailed
Definition: SLEnums.h:248
@ RM_BlinnPhong
Definition: SLEnums.h:289
@ RM_CookTorrance
Definition: SLEnums.h:290
@ IOK_shader
Definition: SLFileStorage.h:42
@ IOK_generic
Definition: SLFileStorage.h:39
@ PT_points
Definition: SLGLEnums.h:31
@ PT_lines
Definition: SLGLEnums.h:32
@ PT_triangles
Definition: SLGLEnums.h:35
SLTextureType
Texture type enumeration & their filename appendix for auto type detection.
Definition: SLGLTexture.h:76
@ TT_occluRoughMetal
Definition: SLGLTexture.h:87
@ TT_height
Definition: SLGLTexture.h:80
@ TT_metallic
Definition: SLGLTexture.h:85
@ TT_unknown
Definition: SLGLTexture.h:77
@ TT_roughMetal
Definition: SLGLTexture.h:86
@ TT_roughness
Definition: SLGLTexture.h:84
@ TT_specular
Definition: SLGLTexture.h:81
@ TT_normal
Definition: SLGLTexture.h:79
@ TT_diffuse
Definition: SLGLTexture.h:78
@ TT_occlusion
Definition: SLGLTexture.h:83
@ TT_emissive
Definition: SLGLTexture.h:82
vector< SLGLTexture * > SLVGLTexture
STL vector of SLGLTexture pointers.
Definition: SLGLTexture.h:342
#define SL_ANISOTROPY_MAX
Definition: SLGLTexture.h:34
std::map< int, SLMesh * > SLMeshMap
Definition: SLImporter.h:62
SLMat4< SLfloat > SLMat4f
Definition: SLMat4.h:1581
vector< SLMaterial * > SLVMaterial
STL vector of material pointers.
Definition: SLMaterial.h:274
SLQuat4< SLfloat > SLQuat4f
Definition: SLQuat4.h:846
SLVec3< SLfloat > SLVec3f
Definition: SLVec3.h:318
SLVec4< SLfloat > SLCol4f
Definition: SLVec4.h:237
SLAnimManager is the central class for all animation handling.
Definition: SLAnimManager.h:27
SLVSkeleton & skeletons()
Definition: SLAnimManager.h:43
SLVstring & animationNames()
Definition: SLAnimManager.h:46
SLAnimation * createNodeAnimation(SLfloat duration)
SLAnimSkeleton keeps track of a skeletons joints and animations.
SLAnimation is the base container for all animation data.
Definition: SLAnimation.h:33
SLNodeAnimTrack * createNodeAnimTrack()
Definition: SLAnimation.cpp:94
Toplevel holder of the assets meshes, materials, textures and shaders.
SLVGLTexture & textures()
Texture object for OpenGL texturing.
Definition: SLGLTexture.h:110
void deleteImageAfterBuild(SLbool delImg)
If deleteImageAfterBuild is set to true you won't be able to ray trace the scene.
Definition: SLGLTexture.h:215
void uvIndex(SLbyte i)
Definition: SLGLTexture.h:201
Specialized SLNode that represents a single joint (or bone) in a skeleton.
Definition: SLJoint.h:27
SLuint id() const
Definition: SLJoint.h:47
void offsetMat(const SLMat4f &mat)
Definition: SLJoint.h:44
void calcMaxRadius(const SLVec3f &vec)
Definition: SLJoint.cpp:51
SLJoint * createChild(SLuint id)
Definition: SLJoint.cpp:33
void transpose()
Sets the transposed matrix by swaping around the main diagonal.
Definition: SLMat4.h:1341
SLMat4< T > inverted() const
Computes the inverse of a 4x4 non-singular matrix.
Definition: SLMat4.h:1371
Defines a standard CG material with textures and a shader program.
Definition: SLMaterial.h:56
void reflectionModel(SLReflectionModel rm)
Definition: SLMaterial.h:169
void specular(const SLCol4f &spec)
Definition: SLMaterial.h:173
void skybox(SLSkybox *sb)
Definition: SLMaterial.h:207
void diffuse(const SLCol4f &diff)
Definition: SLMaterial.h:171
void addTexture(SLGLTexture *texture)
Adds the passed texture to the equivalent texture type vector.
Definition: SLMaterial.cpp:348
void shininess(SLfloat shin)
Definition: SLMaterial.h:177
void ambient(const SLCol4f &ambi)
Definition: SLMaterial.h:170
void roughness(SLfloat r)
Definition: SLMaterial.h:182
SLbool hasTextureType(SLTextureType tt)
Definition: SLMaterial.h:149
void emissive(const SLCol4f &emis)
Definition: SLMaterial.h:174
void metalness(SLfloat m)
Definition: SLMaterial.h:183
An SLMesh object is a triangulated mesh, drawn with one draw call.
Definition: SLMesh.h:134
SLVuint I32
Vector of vertex indices 32 bit.
Definition: SLMesh.h:215
SLGLPrimitiveType primitive() const
Definition: SLMesh.h:179
SLVushort I16
Vector of vertex indices 16 bit.
Definition: SLMesh.h:214
virtual void calcNormals()
SLMesh::calcNormals recalculates vertex normals for triangle meshes.
Definition: SLMesh.cpp:1165
SLVVec3f N
Vector for vertex normals (opt.) layout (location = 1)
Definition: SLMesh.h:204
SLVVec2f UV[2]
Array of 2 Vectors for tex. coords. (opt.) layout (location = 2)
Definition: SLMesh.h:205
SLVVuchar Ji
2D Vector of per vertex joint ids (opt.) layout (location = 6)
Definition: SLMesh.h:208
SLVVfloat Jw
2D Vector of per vertex joint weights (opt.) layout (location = 7)
Definition: SLMesh.h:209
const SLAnimSkeleton * skeleton() const
Definition: SLMesh.h:180
SLVVec3f P
Vector for vertex positions layout (location = 0)
Definition: SLMesh.h:203
SLMaterial * mat() const
Definition: SLMesh.h:177
Specialized animation track for node animations.
Definition: SLAnimTrack.h:66
void animatedNode(SLNode *target)
Definition: SLAnimTrack.h:73
SLTransformKeyframe * createNodeKeyframe(SLfloat time)
SLNode represents a node in a hierarchical scene graph.
Definition: SLNode.h:148
T * find(const SLstring &name="", SLbool findRecursive=true)
Definition: SLNode.h:377
void addChild(SLNode *child)
Definition: SLNode.cpp:207
const SLMat4f & updateAndGetWM() const
Definition: SLNode.cpp:703
virtual void addMesh(SLMesh *mesh)
Definition: SLNode.cpp:157
void setInitialState()
Definition: SLNode.cpp:1084
void om(const SLMat4f &mat)
Definition: SLNode.h:277
Skybox node class with a SLBox mesh.
Definition: SLSkybox.h:29
SLTransformKeyframe is a specialized SLKeyframe for node transformations.
void translation(const SLVec3f &t)
void scale(const SLVec3f &s)
void rotation(const SLQuat4f &r)
bool exists(std::string path, SLIOStreamKind kind)
Checks whether a given file exists.
void clear(std::string path)
Definition: SLIOMemory.cpp:34
string getFileNameWOExt(const string &pathFilename)
Returns the filename without extension.
Definition: Utils.cpp:616
string getFileName(const string &pathFilename)
Returns the filename of path-filename string.
Definition: Utils.cpp:580
string getPath(const string &pathFilename)
Returns the path w. '\' of path-filename string.
Definition: Utils.cpp:392
bool startsWithString(const string &container, const string &startStr)
Return true if the container string starts with the startStr.
Definition: Utils.cpp:351
bool endsWithString(const string &container, const string &endStr)
Return true if the container string ends with the endStr.
Definition: Utils.cpp:357