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