OpenAssetImporter.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. //
  2. // Copyright (c) 2008-2015 the Atomic project.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
  23. // Please see LICENSE.md in repository root for license information
  24. // https://github.com/AtomicGameEngine/AtomicGameEngine
  25. #include <Atomic/Core/ProcessUtils.h>
  26. #include <Atomic/IO/Log.h>
  27. #include <Atomic/IO/File.h>
  28. #include <Atomic/IO/FileSystem.h>
  29. #include <Atomic/Atomic3D/AnimatedModel.h>
  30. #include <Atomic/Atomic3D/Animation.h>
  31. #include "OpenAssetImporter.h"
  32. namespace ToolCore
  33. {
  34. OpenAssetImporter::OpenAssetImporter(Context* context) : Object(context) ,
  35. scene_(0),
  36. rootNode_(0),
  37. useSubdirs_(true),
  38. localIDs_(false),
  39. saveBinary_(false),
  40. createZone_(true),
  41. noAnimations_(false),
  42. noHierarchy_(false),
  43. noMaterials_(false),
  44. noTextures_(false),
  45. noMaterialDiffuseColor_(false),
  46. noEmptyNodes_(false),
  47. saveMaterialList_(false),
  48. includeNonSkinningBones_(false),
  49. verboseLog_(false),
  50. emissiveAO_(false),
  51. noOverwriteMaterial_(false),
  52. noOverwriteTexture_(false),
  53. noOverwriteNewerTexture_(false),
  54. checkUniqueModel_(true),
  55. defaultTicksPerSecond_(4800.0f)
  56. {
  57. aiFlagsDefault_ =
  58. aiProcess_ConvertToLeftHanded |
  59. aiProcess_JoinIdenticalVertices |
  60. aiProcess_Triangulate |
  61. aiProcess_GenSmoothNormals |
  62. aiProcess_LimitBoneWeights |
  63. aiProcess_ImproveCacheLocality |
  64. aiProcess_RemoveRedundantMaterials |
  65. aiProcess_FixInfacingNormals |
  66. aiProcess_FindInvalidData |
  67. aiProcess_GenUVCoords |
  68. aiProcess_FindInstances |
  69. aiProcess_OptimizeMeshes;
  70. aiCurrentFlags_ = aiFlagsDefault_;
  71. }
  72. bool OpenAssetImporter::Load(const String &assetPath)
  73. {
  74. if (verboseLog_)
  75. Assimp::DefaultLogger::create("", Assimp::Logger::VERBOSE, aiDefaultLogStream_STDOUT);
  76. PrintLine("Reading file " + assetPath);
  77. scene_ = aiImportFile(GetNativePath(assetPath).CString(), aiCurrentFlags_);
  78. if (!scene_)
  79. ErrorExit("Could not open or parse input file " + assetPath + ": " + String(aiGetErrorString()));
  80. if (verboseLog_)
  81. Assimp::DefaultLogger::kill();
  82. return true;
  83. }
  84. String OpenAssetImporter::GetMeshMaterialName(aiMesh* mesh)
  85. {
  86. aiMaterial* material = scene_->mMaterials[mesh->mMaterialIndex];
  87. aiString matNameStr;
  88. material->Get(AI_MATKEY_NAME, matNameStr);
  89. String matName = SanitateAssetName(FromAIString(matNameStr));
  90. if (matName.Trimmed().Empty())
  91. matName = GenerateMaterialName(material);
  92. return (useSubdirs_ ? "Materials/" : "") + matName + ".xml";
  93. }
  94. String OpenAssetImporter::GenerateMaterialName(aiMaterial* material)
  95. {
  96. for (unsigned i = 0; i < scene_->mNumMaterials; ++i)
  97. {
  98. if (scene_->mMaterials[i] == material)
  99. return inputName_ + "_Material" + String(i);
  100. }
  101. // Should not go here
  102. return String::EMPTY;
  103. }
  104. String OpenAssetImporter::GetMaterialTextureName(const String& nameIn)
  105. {
  106. // Detect assimp embedded texture
  107. if (nameIn.Length() && nameIn[0] == '*')
  108. return GenerateTextureName(ToInt(nameIn.Substring(1)));
  109. else
  110. return (useSubdirs_ ? "Textures/" : "") + nameIn;
  111. }
  112. String OpenAssetImporter::GenerateTextureName(unsigned texIndex)
  113. {
  114. if (texIndex < scene_->mNumTextures)
  115. {
  116. // If embedded texture contains encoded data, use the format hint for file extension. Else save RGBA8 data as PNG
  117. aiTexture* tex = scene_->mTextures[texIndex];
  118. if (!tex->mHeight)
  119. return (useSubdirs_ ? "Textures/" : "") + inputName_ + "_Texture" + String(texIndex) + "." + tex->achFormatHint;
  120. else
  121. return (useSubdirs_ ? "Textures/" : "") + inputName_ + "_Texture" + String(texIndex) + ".png";
  122. }
  123. // Should not go here
  124. return String::EMPTY;
  125. }
  126. void OpenAssetImporter::CollectSceneModels(OutScene& scene, aiNode* node)
  127. {
  128. Vector<Pair<aiNode*, aiMesh*> > meshes;
  129. GetMeshesUnderNode(scene_, meshes, node);
  130. if (meshes.Size())
  131. {
  132. OutModel model;
  133. model.rootNode_ = node;
  134. model.outName_ = resourcePath_ + (useSubdirs_ ? "Models/" : "") + SanitateAssetName(FromAIString(node->mName)) + ".mdl";
  135. for (unsigned i = 0; i < meshes.Size(); ++i)
  136. {
  137. aiMesh* mesh = meshes[i].second_;
  138. unsigned meshIndex = GetMeshIndex(scene_, mesh);
  139. model.meshIndices_.Insert(meshIndex);
  140. model.meshes_.Push(mesh);
  141. model.meshNodes_.Push(meshes[i].first_);
  142. model.totalVertices_ += mesh->mNumVertices;
  143. model.totalIndices_ += GetNumValidFaces(mesh) * 3;
  144. }
  145. // Check if a model with identical mesh indices already exists. If yes, do not export twice
  146. bool unique = true;
  147. if (checkUniqueModel_)
  148. {
  149. for (unsigned i = 0; i < scene.models_.Size(); ++i)
  150. {
  151. if (scene.models_[i].meshIndices_ == model.meshIndices_)
  152. {
  153. PrintLine("Added node " + FromAIString(node->mName));
  154. scene.nodes_.Push(node);
  155. scene.nodeModelIndices_.Push(i);
  156. unique = false;
  157. break;
  158. }
  159. }
  160. }
  161. if (unique)
  162. {
  163. PrintLine("Added model " + model.outName_);
  164. PrintLine("Added node " + FromAIString(node->mName));
  165. CollectBones(model);
  166. BuildBoneCollisionInfo(model);
  167. if (!noAnimations_)
  168. {
  169. CollectAnimations(&model);
  170. BuildAndSaveAnimations(&model);
  171. }
  172. scene.models_.Push(model);
  173. scene.nodes_.Push(node);
  174. scene.nodeModelIndices_.Push(scene.models_.Size() - 1);
  175. }
  176. }
  177. for (unsigned i = 0; i < node->mNumChildren; ++i)
  178. CollectSceneModels(scene, node->mChildren[i]);
  179. }
  180. void OpenAssetImporter::CollectBones(OutModel& model, bool animationOnly)
  181. {
  182. HashSet<aiNode*> necessary;
  183. HashSet<aiNode*> rootNodes;
  184. for (unsigned i = 0; i < model.meshes_.Size(); ++i)
  185. {
  186. aiMesh* mesh = model.meshes_[i];
  187. aiNode* meshNode = model.meshNodes_[i];
  188. aiNode* meshParentNode = meshNode->mParent;
  189. aiNode* rootNode = 0;
  190. for (unsigned j = 0; j < mesh->mNumBones; ++j)
  191. {
  192. aiBone* bone = mesh->mBones[j];
  193. String boneName(FromAIString(bone->mName));
  194. aiNode* boneNode = GetNode(boneName, scene_->mRootNode, true);
  195. if (!boneNode)
  196. ErrorExit("Could not find scene node for bone " + boneName);
  197. necessary.Insert(boneNode);
  198. rootNode = boneNode;
  199. for (;;)
  200. {
  201. boneNode = boneNode->mParent;
  202. if (!boneNode || ((boneNode == meshNode || boneNode == meshParentNode) && !animationOnly))
  203. break;
  204. rootNode = boneNode;
  205. necessary.Insert(boneNode);
  206. }
  207. if (rootNodes.Find(rootNode) == rootNodes.End())
  208. rootNodes.Insert(rootNode);
  209. }
  210. }
  211. // If we find multiple root nodes, try to remedy by using their parent instead
  212. if (rootNodes.Size() > 1)
  213. {
  214. aiNode* commonParent = (*rootNodes.Begin())->mParent;
  215. for (HashSet<aiNode*>::Iterator i = rootNodes.Begin(); i != rootNodes.End(); ++i)
  216. {
  217. if (*i != commonParent)
  218. {
  219. if (!commonParent || (*i)->mParent != commonParent)
  220. ErrorExit("Skeleton with multiple root nodes found, not supported");
  221. }
  222. }
  223. rootNodes.Clear();
  224. rootNodes.Insert(commonParent);
  225. necessary.Insert(commonParent);
  226. }
  227. if (rootNodes.Empty())
  228. return;
  229. model.rootBone_ = *rootNodes.Begin();
  230. CollectBonesFinal(model.bones_, necessary, model.rootBone_);
  231. // Initialize the bone collision info
  232. model.boneRadii_.Resize(model.bones_.Size());
  233. model.boneHitboxes_.Resize(model.bones_.Size());
  234. for (unsigned i = 0; i < model.bones_.Size(); ++i)
  235. {
  236. model.boneRadii_[i] = 0.0f;
  237. model.boneHitboxes_[i] = BoundingBox(0.0f, 0.0f);
  238. }
  239. }
  240. void OpenAssetImporter::CollectBonesFinal(PODVector<aiNode*>& dest, const HashSet<aiNode*>& necessary, aiNode* node)
  241. {
  242. bool includeBone = necessary.Find(node) != necessary.End();
  243. String boneName = FromAIString(node->mName);
  244. // Check include/exclude filters for non-skinned bones
  245. if (!includeBone && includeNonSkinningBones_)
  246. {
  247. // If no includes specified, include by default but check for excludes
  248. if (nonSkinningBoneIncludes_.Empty())
  249. includeBone = true;
  250. // Check against includes/excludes
  251. for (unsigned i = 0; i < nonSkinningBoneIncludes_.Size(); ++i)
  252. {
  253. if (boneName.Contains(nonSkinningBoneIncludes_[i], false))
  254. {
  255. includeBone = true;
  256. break;
  257. }
  258. }
  259. for (unsigned i = 0; i < nonSkinningBoneExcludes_.Size(); ++i)
  260. {
  261. if (boneName.Contains(nonSkinningBoneExcludes_[i], false))
  262. {
  263. includeBone = false;
  264. break;
  265. }
  266. }
  267. if (includeBone)
  268. PrintLine("Including non-skinning bone " + boneName);
  269. }
  270. if (includeBone)
  271. dest.Push(node);
  272. for (unsigned i = 0; i < node->mNumChildren; ++i)
  273. CollectBonesFinal(dest, necessary, node->mChildren[i]);
  274. }
  275. void OpenAssetImporter::CollectAnimations(OutModel* model)
  276. {
  277. const aiScene* scene = scene_;
  278. for (unsigned i = 0; i < scene->mNumAnimations; ++i)
  279. {
  280. aiAnimation* anim = scene->mAnimations[i];
  281. if (allAnimations_.Contains(anim))
  282. continue;
  283. if (model)
  284. {
  285. bool modelBoneFound = false;
  286. for (unsigned j = 0; j < anim->mNumChannels; ++j)
  287. {
  288. aiNodeAnim* channel = anim->mChannels[j];
  289. String channelName = FromAIString(channel->mNodeName);
  290. if (GetBoneIndex(*model, channelName) != M_MAX_UNSIGNED)
  291. {
  292. modelBoneFound = true;
  293. break;
  294. }
  295. }
  296. if (modelBoneFound)
  297. {
  298. model->animations_.Push(anim);
  299. allAnimations_.Insert(anim);
  300. }
  301. }
  302. else
  303. {
  304. sceneAnimations_.Push(anim);
  305. allAnimations_.Insert(anim);
  306. }
  307. }
  308. /// \todo Vertex morphs are ignored for now
  309. }
  310. void OpenAssetImporter::BuildBoneCollisionInfo(OutModel& model)
  311. {
  312. for (unsigned i = 0; i < model.meshes_.Size(); ++i)
  313. {
  314. aiMesh* mesh = model.meshes_[i];
  315. for (unsigned j = 0; j < mesh->mNumBones; ++j)
  316. {
  317. aiBone* bone = mesh->mBones[j];
  318. String boneName = FromAIString(bone->mName);
  319. unsigned boneIndex = GetBoneIndex(model, boneName);
  320. if (boneIndex == M_MAX_UNSIGNED)
  321. continue;
  322. for (unsigned k = 0; k < bone->mNumWeights; ++k)
  323. {
  324. float weight = bone->mWeights[k].mWeight;
  325. // Require skinning weight to be sufficiently large before vertex contributes to bone hitbox
  326. if (weight > 0.33f)
  327. {
  328. aiVector3D vertexBoneSpace = bone->mOffsetMatrix * mesh->mVertices[bone->mWeights[k].mVertexId];
  329. Vector3 vertex = ToVector3(vertexBoneSpace);
  330. float radius = vertex.Length();
  331. if (radius > model.boneRadii_[boneIndex])
  332. model.boneRadii_[boneIndex] = radius;
  333. model.boneHitboxes_[boneIndex].Merge(vertex);
  334. }
  335. }
  336. }
  337. }
  338. }
  339. void OpenAssetImporter::BuildAndSaveAnimations(OutModel* model)
  340. {
  341. const PODVector<aiAnimation*>& animations = model ? model->animations_ : sceneAnimations_;
  342. for (unsigned i = 0; i < animations.Size(); ++i)
  343. {
  344. aiAnimation* anim = animations[i];
  345. float duration = (float)anim->mDuration;
  346. String animName = FromAIString(anim->mName);
  347. String animOutName;
  348. if (animName.Empty())
  349. animName = "Anim" + String(i + 1);
  350. if (model)
  351. animOutName = GetPath(model->outName_) + GetFileName(model->outName_) + "_" + SanitateAssetName(animName) + ".ani";
  352. else
  353. animOutName = outPath_ + SanitateAssetName(animName) + ".ani";
  354. float ticksPerSecond = (float)anim->mTicksPerSecond;
  355. // If ticks per second not specified, it's probably a .X file. In this case use the default tick rate
  356. if (ticksPerSecond < M_EPSILON)
  357. ticksPerSecond = defaultTicksPerSecond_;
  358. float tickConversion = 1.0f / ticksPerSecond;
  359. // Find out the start time of animation from each channel's first keyframe for adjusting the keyframe times
  360. // to start from zero
  361. float startTime = duration;
  362. for (unsigned j = 0; j < anim->mNumChannels; ++j)
  363. {
  364. aiNodeAnim* channel = anim->mChannels[j];
  365. if (channel->mNumPositionKeys > 0)
  366. startTime = Min(startTime, (float)channel->mPositionKeys[0].mTime);
  367. if (channel->mNumRotationKeys > 0)
  368. startTime = Min(startTime, (float)channel->mRotationKeys[0].mTime);
  369. if (channel->mScalingKeys > 0)
  370. startTime = Min(startTime, (float)channel->mScalingKeys[0].mTime);
  371. }
  372. duration -= startTime;
  373. SharedPtr<Animation> outAnim(new Animation(context_));
  374. outAnim->SetAnimationName(animName);
  375. outAnim->SetLength(duration * tickConversion);
  376. PrintLine("Writing animation " + animName + " length " + String(outAnim->GetLength()));
  377. Vector<AnimationTrack> tracks;
  378. for (unsigned j = 0; j < anim->mNumChannels; ++j)
  379. {
  380. aiNodeAnim* channel = anim->mChannels[j];
  381. String channelName = FromAIString(channel->mNodeName);
  382. aiNode* boneNode = 0;
  383. bool isRootBone = false;
  384. if (model)
  385. {
  386. unsigned boneIndex = GetBoneIndex(*model, channelName);
  387. if (boneIndex == M_MAX_UNSIGNED)
  388. {
  389. PrintLine("Warning: skipping animation track " + channelName + " not found in model skeleton");
  390. continue;
  391. }
  392. boneNode = model->bones_[boneIndex];
  393. isRootBone = boneIndex == 0;
  394. }
  395. else
  396. {
  397. boneNode = GetNode(channelName, scene_->mRootNode);
  398. if (!boneNode)
  399. {
  400. PrintLine("Warning: skipping animation track " + channelName + " whose scene node was not found");
  401. continue;
  402. }
  403. }
  404. // To export single frame animation, check if first key frame is identical to bone transformation
  405. aiVector3D bonePos, boneScale;
  406. aiQuaternion boneRot;
  407. boneNode->mTransformation.Decompose(boneScale, boneRot, bonePos);
  408. bool posEqual = true;
  409. bool scaleEqual = true;
  410. bool rotEqual = true;
  411. if (channel->mNumPositionKeys > 0 && !ToVector3(bonePos).Equals(ToVector3(channel->mPositionKeys[0].mValue)))
  412. posEqual = false;
  413. if (channel->mNumScalingKeys > 0 && !ToVector3(boneScale).Equals(ToVector3(channel->mScalingKeys[0].mValue)))
  414. scaleEqual = false;
  415. if (channel->mNumRotationKeys > 0 && !ToQuaternion(boneRot).Equals(ToQuaternion(channel->mRotationKeys[0].mValue)))
  416. rotEqual = false;
  417. AnimationTrack track;
  418. track.name_ = channelName;
  419. track.nameHash_ = channelName;
  420. // Check which channels are used
  421. track.channelMask_ = 0;
  422. if (channel->mNumPositionKeys > 1 || !posEqual)
  423. track.channelMask_ |= CHANNEL_POSITION;
  424. if (channel->mNumRotationKeys > 1 || !rotEqual)
  425. track.channelMask_ |= CHANNEL_ROTATION;
  426. if (channel->mNumScalingKeys > 1 || !scaleEqual)
  427. track.channelMask_ |= CHANNEL_SCALE;
  428. // Check for redundant identity scale in all keyframes and remove in that case
  429. if (track.channelMask_ & CHANNEL_SCALE)
  430. {
  431. bool redundantScale = true;
  432. for (unsigned k = 0; k < channel->mNumScalingKeys; ++k)
  433. {
  434. float SCALE_EPSILON = 0.000001f;
  435. Vector3 scaleVec = ToVector3(channel->mScalingKeys[k].mValue);
  436. if (fabsf(scaleVec.x_ - 1.0f) >= SCALE_EPSILON || fabsf(scaleVec.y_ - 1.0f) >= SCALE_EPSILON ||
  437. fabsf(scaleVec.z_ - 1.0f) >= SCALE_EPSILON)
  438. {
  439. redundantScale = false;
  440. break;
  441. }
  442. }
  443. if (redundantScale)
  444. track.channelMask_ &= ~CHANNEL_SCALE;
  445. }
  446. if (!track.channelMask_)
  447. PrintLine("Warning: skipping animation track " + channelName + " with no keyframes");
  448. // Currently only same amount of keyframes is supported
  449. // Note: should also check the times of individual keyframes for match
  450. if ((channel->mNumPositionKeys > 1 && channel->mNumRotationKeys > 1 && channel->mNumPositionKeys != channel->mNumRotationKeys) ||
  451. (channel->mNumPositionKeys > 1 && channel->mNumScalingKeys > 1 && channel->mNumPositionKeys != channel->mNumScalingKeys) ||
  452. (channel->mNumRotationKeys > 1 && channel->mNumScalingKeys > 1 && channel->mNumRotationKeys != channel->mNumScalingKeys))
  453. {
  454. PrintLine("Warning: differing amounts of channel keyframes, skipping animation track " + channelName);
  455. continue;
  456. }
  457. unsigned keyFrames = channel->mNumPositionKeys;
  458. if (channel->mNumRotationKeys > keyFrames)
  459. keyFrames = channel->mNumRotationKeys;
  460. if (channel->mNumScalingKeys > keyFrames)
  461. keyFrames = channel->mNumScalingKeys;
  462. for (unsigned k = 0; k < keyFrames; ++k)
  463. {
  464. AnimationKeyFrame kf;
  465. kf.time_ = 0.0f;
  466. kf.position_ = Vector3::ZERO;
  467. kf.rotation_ = Quaternion::IDENTITY;
  468. kf.scale_ = Vector3::ONE;
  469. // Get time for the keyframe. Adjust with animation's start time
  470. if (track.channelMask_ & CHANNEL_POSITION && k < channel->mNumPositionKeys)
  471. kf.time_ = ((float)channel->mPositionKeys[k].mTime - startTime) * tickConversion;
  472. else if (track.channelMask_ & CHANNEL_ROTATION && k < channel->mNumRotationKeys)
  473. kf.time_ = ((float)channel->mRotationKeys[k].mTime - startTime) * tickConversion;
  474. else if (track.channelMask_ & CHANNEL_SCALE && k < channel->mNumScalingKeys)
  475. kf.time_ = ((float)channel->mScalingKeys[k].mTime - startTime) * tickConversion;
  476. // Make sure time stays positive
  477. kf.time_ = Max(kf.time_, 0.0f);
  478. // Start with the bone's base transform
  479. aiMatrix4x4 boneTransform = boneNode->mTransformation;
  480. aiVector3D pos, scale;
  481. aiQuaternion rot;
  482. boneTransform.Decompose(scale, rot, pos);
  483. // Then apply the active channels
  484. if (track.channelMask_ & CHANNEL_POSITION && k < channel->mNumPositionKeys)
  485. pos = channel->mPositionKeys[k].mValue;
  486. if (track.channelMask_ & CHANNEL_ROTATION && k < channel->mNumRotationKeys)
  487. rot = channel->mRotationKeys[k].mValue;
  488. if (track.channelMask_ & CHANNEL_SCALE && k < channel->mNumScalingKeys)
  489. scale = channel->mScalingKeys[k].mValue;
  490. // If root bone, transform with the model root node transform
  491. if (model && isRootBone)
  492. {
  493. aiMatrix4x4 transMat, scaleMat, rotMat;
  494. aiMatrix4x4::Translation(pos, transMat);
  495. aiMatrix4x4::Scaling(scale, scaleMat);
  496. rotMat = aiMatrix4x4(rot.GetMatrix());
  497. aiMatrix4x4 tform = transMat * rotMat * scaleMat;
  498. tform = GetDerivedTransform(tform, boneNode, model->rootNode_);
  499. tform.Decompose(scale, rot, pos);
  500. }
  501. if (track.channelMask_ & CHANNEL_POSITION)
  502. kf.position_ = ToVector3(pos);
  503. if (track.channelMask_ & CHANNEL_ROTATION)
  504. kf.rotation_ = ToQuaternion(rot);
  505. if (track.channelMask_ & CHANNEL_SCALE)
  506. kf.scale_ = ToVector3(scale);
  507. track.keyFrames_.Push(kf);
  508. }
  509. tracks.Push(track);
  510. }
  511. outAnim->SetTracks(tracks);
  512. File outFile(context_);
  513. if (!outFile.Open(animOutName, FILE_WRITE))
  514. ErrorExit("Could not open output file " + animOutName);
  515. outAnim->Save(outFile);
  516. }
  517. }
  518. void OpenAssetImporter::DumpNodes(aiNode* rootNode, unsigned level)
  519. {
  520. if (!rootNode)
  521. return;
  522. String indent(' ', level * 2);
  523. Vector3 pos, scale;
  524. Quaternion rot;
  525. aiMatrix4x4 transform = GetDerivedTransform(rootNode, rootNode_);
  526. GetPosRotScale(transform, pos, rot, scale);
  527. PrintLine(indent + "Node " + FromAIString(rootNode->mName) + " pos " + String(pos));
  528. if (rootNode->mNumMeshes == 1)
  529. PrintLine(indent + " " + String(rootNode->mNumMeshes) + " geometry");
  530. if (rootNode->mNumMeshes > 1)
  531. PrintLine(indent + " " + String(rootNode->mNumMeshes) + " geometries");
  532. for (unsigned i = 0; i < rootNode->mNumChildren; ++i)
  533. DumpNodes(rootNode->mChildren[i], level + 1);
  534. }
  535. }