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