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