1 //
2 // Copyright (c) 2008-2017 the Urho3D 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 
23 #include <Urho3D/Core/Context.h>
24 #include <Urho3D/Core/ProcessUtils.h>
25 #include <Urho3D/Core/StringUtils.h>
26 #include <Urho3D/Core/WorkQueue.h>
27 #include <Urho3D/Graphics/AnimatedModel.h>
28 #include <Urho3D/Graphics/Animation.h>
29 #include <Urho3D/Graphics/DebugRenderer.h>
30 #include <Urho3D/Graphics/Geometry.h>
31 #include <Urho3D/Graphics/Graphics.h>
32 #include <Urho3D/Graphics/IndexBuffer.h>
33 #include <Urho3D/Graphics/Light.h>
34 #include <Urho3D/Graphics/Material.h>
35 #include <Urho3D/Graphics/Octree.h>
36 #include <Urho3D/Graphics/VertexBuffer.h>
37 #include <Urho3D/Graphics/Zone.h>
38 #include <Urho3D/IO/File.h>
39 #include <Urho3D/IO/FileSystem.h>
40 #ifdef URHO3D_PHYSICS
41 #include <Urho3D/Physics/PhysicsWorld.h>
42 #endif
43 #include <Urho3D/Resource/ResourceCache.h>
44 #include <Urho3D/Resource/XMLFile.h>
45 #include <Urho3D/Scene/Scene.h>
46 
47 #ifdef WIN32
48 #include <windows.h>
49 #endif
50 
51 #include <assimp/config.h>
52 #include <assimp/cimport.h>
53 #include <assimp/scene.h>
54 #include <assimp/postprocess.h>
55 #include <assimp/DefaultLogger.hpp>
56 
57 #include <Urho3D/DebugNew.h>
58 
59 using namespace Urho3D;
60 
61 struct OutModel
62 {
OutModelOutModel63     OutModel() :
64         rootBone_(0),
65         totalVertices_(0),
66         totalIndices_(0)
67     {
68     }
69 
70     String outName_;
71     aiNode* rootNode_;
72     HashSet<unsigned> meshIndices_;
73     PODVector<aiMesh*> meshes_;
74     PODVector<aiNode*> meshNodes_;
75     PODVector<aiNode*> bones_;
76     PODVector<aiNode*> pivotlessBones_;
77     PODVector<aiAnimation*> animations_;
78     PODVector<float> boneRadii_;
79     PODVector<BoundingBox> boneHitboxes_;
80     aiNode* rootBone_;
81     unsigned totalVertices_;
82     unsigned totalIndices_;
83 };
84 
85 struct OutScene
86 {
87     String outName_;
88     aiNode* rootNode_;
89     Vector<OutModel> models_;
90     PODVector<aiNode*> nodes_;
91     PODVector<unsigned> nodeModelIndices_;
92 };
93 
94 // FBX transform chain
95 enum TransformationComp
96 {
97     TransformationComp_Translation = 0,
98     TransformationComp_RotationOffset,
99     TransformationComp_RotationPivot,
100     TransformationComp_PreRotation,
101     TransformationComp_Rotation,
102     TransformationComp_PostRotation,
103     TransformationComp_RotationPivotInverse,
104 
105     TransformationComp_ScalingOffset,
106     TransformationComp_ScalingPivot,
107     TransformationComp_Scaling,
108 
109     // Not checking these
110     // They are typically flushed out in the fbxconverter, but there
111     // might be cases where they're not, hence, leaving them.
112     #ifdef EXT_TRANSFORMATION_CHECK
113     TransformationComp_ScalingPivotInverse,
114     TransformationComp_GeometricTranslation,
115     TransformationComp_GeometricRotation,
116     TransformationComp_GeometricScaling,
117     #endif
118 
119     TransformationComp_MAXIMUM
120 };
121 
122 const char *transformSuffix[TransformationComp_MAXIMUM] =
123 {
124     "Translation",          // TransformationComp_Translation = 0,
125     "RotationOffset",       // TransformationComp_RotationOffset,
126     "RotationPivot",        // TransformationComp_RotationPivot,
127     "PreRotation",          // TransformationComp_PreRotation,
128     "Rotation",             // TransformationComp_Rotation,
129     "PostRotation",         // TransformationComp_PostRotation,
130     "RotationPivotInverse", // TransformationComp_RotationPivotInverse,
131 
132     "ScalingOffset",        // TransformationComp_ScalingOffset,
133     "ScalingPivot",         // TransformationComp_ScalingPivot,
134     "Scaling",              // TransformationComp_Scaling,
135 
136     #ifdef EXT_TRANSFORMATION_CHECK
137     "ScalingPivotInverse",  // TransformationComp_ScalingPivotInverse,
138     "GeometricTranslation", // TransformationComp_GeometricTranslation,
139     "GeometricRotation",    // TransformationComp_GeometricRotation,
140     "GeometricScaling",     // TransformationComp_GeometricScaling,
141     #endif
142 };
143 
144 static const unsigned MAX_CHANNELS = 4;
145 
146 SharedPtr<Context> context_(new Context());
147 const aiScene* scene_ = 0;
148 aiNode* rootNode_ = 0;
149 String inputName_;
150 String resourcePath_;
151 String outPath_;
152 String outName_;
153 bool useSubdirs_ = true;
154 bool localIDs_ = false;
155 bool saveBinary_ = false;
156 bool saveJson_ = false;
157 bool createZone_ = true;
158 bool noAnimations_ = false;
159 bool noHierarchy_ = false;
160 bool noMaterials_ = false;
161 bool noTextures_ = false;
162 bool noMaterialDiffuseColor_ = false;
163 bool noEmptyNodes_ = false;
164 bool saveMaterialList_ = false;
165 bool includeNonSkinningBones_ = false;
166 bool verboseLog_ = false;
167 bool emissiveAO_ = false;
168 bool noOverwriteMaterial_ = false;
169 bool noOverwriteTexture_ = false;
170 bool noOverwriteNewerTexture_ = false;
171 bool checkUniqueModel_ = true;
172 bool moveToBindPose_ = false;
173 unsigned maxBones_ = 64;
174 Vector<String> nonSkinningBoneIncludes_;
175 Vector<String> nonSkinningBoneExcludes_;
176 
177 HashSet<aiAnimation*> allAnimations_;
178 PODVector<aiAnimation*> sceneAnimations_;
179 
180 float defaultTicksPerSecond_ = 4800.0f;
181 // For subset animation import usage
182 float importStartTime_ = 0.0f;
183 float importEndTime_ = 0.0f;
184 bool suppressFbxPivotNodes_ = true;
185 
186 int main(int argc, char** argv);
187 void Run(const Vector<String>& arguments);
188 void DumpNodes(aiNode* rootNode, unsigned level);
189 
190 void ExportModel(const String& outName, bool animationOnly);
191 void ExportAnimation(const String& outName, bool animationOnly);
192 void CollectMeshes(OutModel& model, aiNode* node);
193 void CollectBones(OutModel& model, bool animationOnly = false);
194 void CollectBonesFinal(PODVector<aiNode*>& dest, const HashSet<aiNode*>& necessary, aiNode* node);
195 void MoveToBindPose(OutModel& model, aiNode* current);
196 void CollectAnimations(OutModel* model = 0);
197 void BuildBoneCollisionInfo(OutModel& model);
198 void BuildAndSaveModel(OutModel& model);
199 void BuildAndSaveAnimations(OutModel* model = 0);
200 
201 void ExportScene(const String& outName, bool asPrefab);
202 void CollectSceneModels(OutScene& scene, aiNode* node);
203 void CreateHierarchy(Scene* scene, aiNode* srcNode, HashMap<aiNode*, Node*>& nodeMapping);
204 Node* CreateSceneNode(Scene* scene, aiNode* srcNode, HashMap<aiNode*, Node*>& nodeMapping);
205 void BuildAndSaveScene(OutScene& scene, bool asPrefab);
206 
207 void ExportMaterials(HashSet<String>& usedTextures);
208 void BuildAndSaveMaterial(aiMaterial* material, HashSet<String>& usedTextures);
209 void CopyTextures(const HashSet<String>& usedTextures, const String& sourcePath);
210 
211 void CombineLods(const PODVector<float>& lodDistances, const Vector<String>& modelNames, const String& outName);
212 
213 void GetMeshesUnderNode(Vector<Pair<aiNode*, aiMesh*> >& meshes, aiNode* node);
214 unsigned GetMeshIndex(aiMesh* mesh);
215 unsigned GetBoneIndex(OutModel& model, const String& boneName);
216 aiBone* GetMeshBone(OutModel& model, const String& boneName);
217 Matrix3x4 GetOffsetMatrix(OutModel& model, const String& boneName);
218 void GetBlendData(OutModel& model, aiMesh* mesh, aiNode* meshNode, PODVector<unsigned>& boneMappings, Vector<PODVector<unsigned char> >&
219     blendIndices, Vector<PODVector<float> >& blendWeights);
220 String GetMeshMaterialName(aiMesh* mesh);
221 String GetMaterialTextureName(const String& nameIn);
222 String GenerateMaterialName(aiMaterial* material);
223 String GenerateTextureName(unsigned texIndex);
224 unsigned GetNumValidFaces(aiMesh* mesh);
225 
226 void WriteShortIndices(unsigned short*& dest, aiMesh* mesh, unsigned index, unsigned offset);
227 void WriteLargeIndices(unsigned*& dest, aiMesh* mesh, unsigned index, unsigned offset);
228 void WriteVertex(float*& dest, aiMesh* mesh, unsigned index, bool isSkinned, BoundingBox& box,
229     const Matrix3x4& vertexTransform, const Matrix3& normalTransform, Vector<PODVector<unsigned char> >& blendIndices,
230     Vector<PODVector<float> >& blendWeights);
231 PODVector<VertexElement> GetVertexElements(aiMesh* mesh, bool isSkinned);
232 
233 aiNode* GetNode(const String& name, aiNode* rootNode, bool caseSensitive = true);
234 aiMatrix4x4 GetDerivedTransform(aiNode* node, aiNode* rootNode, bool rootInclusive = true);
235 aiMatrix4x4 GetDerivedTransform(aiMatrix4x4 transform, aiNode* node, aiNode* rootNode, bool rootInclusive = true);
236 aiMatrix4x4 GetMeshBakingTransform(aiNode* meshNode, aiNode* modelRootNode);
237 void GetPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot, Vector3& scale);
238 
239 String FromAIString(const aiString& str);
240 Vector3 ToVector3(const aiVector3D& vec);
241 Vector2 ToVector2(const aiVector2D& vec);
242 Quaternion ToQuaternion(const aiQuaternion& quat);
243 Matrix3x4 ToMatrix3x4(const aiMatrix4x4& mat);
244 aiMatrix4x4 ToAIMatrix4x4(const Matrix3x4& mat);
245 String SanitateAssetName(const String& name);
246 
247 unsigned GetPivotlessBoneIndex(OutModel& model, const String& boneName);
248 void ExtrapolatePivotlessAnimation(OutModel* model);
249 void CollectSceneNodesAsBones(OutModel &model, aiNode* rootNode);
250 
main(int argc,char ** argv)251 int main(int argc, char** argv)
252 {
253     Vector<String> arguments;
254 
255     #ifdef WIN32
256     arguments = ParseArguments(GetCommandLineW());
257     #else
258     arguments = ParseArguments(argc, argv);
259     #endif
260 
261     Run(arguments);
262     return 0;
263 }
264 
Run(const Vector<String> & arguments)265 void Run(const Vector<String>& arguments)
266 {
267     if (arguments.Size() < 2)
268     {
269         ErrorExit(
270             "Usage: AssetImporter <command> <input file> <output file> [options]\n"
271             "See http://assimp.sourceforge.net/main_features_formats.html for input formats\n\n"
272             "Commands:\n"
273             "model       Output a model\n"
274             "anim        Output animation(s)\n"
275             "scene       Output a scene\n"
276             "node        Output a node and its children (prefab)\n"
277             "dump        Dump scene node structure. No output file is generated\n"
278             "lod         Combine several Urho3D models as LOD levels of the output model\n"
279             "            Syntax: lod <dist0> <mdl0> <dist1 <mdl1> ... <output file>\n"
280             "\n"
281             "Options:\n"
282             "-b          Save scene in binary format, default format is XML\n"
283             "-j          Save scene in JSON format, default format is XML\n"
284             "-h          Generate hard instead of smooth normals if input has no normals\n"
285             "-i          Use local ID's for scene nodes\n"
286             "-l          Output a material list file for models\n"
287             "-na         Do not output animations\n"
288             "-nm         Do not output materials\n"
289             "-nt         Do not output material textures\n"
290             "-nc         Do not use material diffuse color value, instead output white\n"
291             "-nh         Do not save full node hierarchy (scene mode only)\n"
292             "-ns         Do not create subdirectories for resources\n"
293             "-nz         Do not create a zone and a directional light (scene mode only)\n"
294             "-nf         Do not fix infacing normals\n"
295             "-ne         Do not save empty nodes (scene mode only)\n"
296             "-mb <x>     Maximum number of bones per submesh. Default 64\n"
297             "-p <path>   Set path for scene resources. Default is output file path\n"
298             "-r <name>   Use the named scene node as root node\n"
299             "-f <freq>   Animation tick frequency to use if unspecified. Default 4800\n"
300             "-o          Optimize redundant submeshes. Loses scene hierarchy and animations\n"
301             "-s <filter> Include non-skinning bones in the model's skeleton. Can be given a\n"
302             "            case-insensitive semicolon separated filter list. Bone is included\n"
303             "            if its name contains any of the filters. Prefix filter with minus\n"
304             "            sign to use as an exclude. For example -s \"Bip01;-Dummy;-Helper\"\n"
305             "-t          Generate tangents\n"
306             "-v          Enable verbose Assimp library logging\n"
307             "-eao        Interpret material emissive texture as ambient occlusion\n"
308             "-cm         Check and do not overwrite if material exists\n"
309             "-ct         Check and do not overwrite if texture exists\n"
310             "-ctn        Check and do not overwrite if texture has newer timestamp\n"
311             "-am         Export all meshes even if identical (scene mode only)\n"
312             "-bp         Move bones to bind pose before saving model\n"
313             "-split <start> <end> (animation model only)\n"
314             "            Split animation, will only import from start frame to end frame\n"
315             "-np         Do not suppress $fbx pivot nodes (FBX files only)\n"
316         );
317     }
318 
319     context_->RegisterSubsystem(new FileSystem(context_));
320     context_->RegisterSubsystem(new ResourceCache(context_));
321     context_->RegisterSubsystem(new WorkQueue(context_));
322     RegisterSceneLibrary(context_);
323     RegisterGraphicsLibrary(context_);
324 #ifdef URHO3D_PHYSICS
325     RegisterPhysicsLibrary(context_);
326 #endif
327 
328     String command = arguments[0].ToLower();
329     String rootNodeName;
330 
331     unsigned flags =
332         aiProcess_ConvertToLeftHanded |
333         aiProcess_JoinIdenticalVertices |
334         aiProcess_Triangulate |
335         aiProcess_GenSmoothNormals |
336         aiProcess_LimitBoneWeights |
337         aiProcess_ImproveCacheLocality |
338         aiProcess_RemoveRedundantMaterials |
339         aiProcess_FixInfacingNormals |
340         aiProcess_FindInvalidData |
341         aiProcess_GenUVCoords |
342         aiProcess_FindInstances |
343         aiProcess_OptimizeMeshes;
344 
345     for (unsigned i = 2; i < arguments.Size(); ++i)
346     {
347         if (arguments[i].Length() > 1 && arguments[i][0] == '-')
348         {
349             String argument = arguments[i].Substring(1).ToLower();
350             String value = i + 1 < arguments.Size() ? arguments[i + 1] : String::EMPTY;
351 
352             if (argument == "b")
353                 saveBinary_ = true;
354             else if(argument == "j")
355                 saveJson_ = true;
356             else if (argument == "h")
357             {
358                 flags &= ~aiProcess_GenSmoothNormals;
359                 flags |= aiProcess_GenNormals;
360             }
361             else if (argument == "i")
362                 localIDs_ = true;
363             else if (argument == "l")
364                 saveMaterialList_ = true;
365             else if (argument == "t")
366                 flags |= aiProcess_CalcTangentSpace;
367             else if (argument == "o")
368                 flags |= aiProcess_PreTransformVertices;
369             else if (argument.Length() == 2 && argument[0] == 'n')
370             {
371                 switch (tolower(argument[1]))
372                 {
373                 case 'a':
374                     noAnimations_ = true;
375                     break;
376 
377                 case 'c':
378                     noMaterialDiffuseColor_ = true;
379                     break;
380 
381                 case 'm':
382                     noMaterials_ = true;
383                     break;
384 
385                 case 'h':
386                     noHierarchy_ = true;
387                     break;
388 
389                 case 'e':
390                     noEmptyNodes_ = true;
391                     break;
392 
393                 case 's':
394                     useSubdirs_ = false;
395                     break;
396 
397                 case 't':
398                     noTextures_ = true;
399                     break;
400 
401                 case 'z':
402                     createZone_ = false;
403                     break;
404 
405                 case 'f':
406                     flags &= ~aiProcess_FixInfacingNormals;
407                     break;
408 
409                 case 'p':
410                         suppressFbxPivotNodes_ = false;
411                     break;
412 
413                 }
414             }
415             else if (argument == "mb" && !value.Empty())
416             {
417                 maxBones_ = ToUInt(value);
418                 if (maxBones_ < 1)
419                     maxBones_ = 1;
420                 ++i;
421             }
422             else if (argument == "p" && !value.Empty())
423             {
424                 resourcePath_ = AddTrailingSlash(value);
425                 ++i;
426             }
427             else if (argument == "r" && !value.Empty())
428             {
429                 rootNodeName = value;
430                 ++i;
431             }
432             else if (argument == "f" && !value.Empty())
433             {
434                 defaultTicksPerSecond_ = ToFloat(value);
435                 ++i;
436             }
437             else if (argument == "s")
438             {
439                 includeNonSkinningBones_ = true;
440                 if (value.Length() && (value[0] != '-' || value.Length() > 3))
441                 {
442                     Vector<String> filters = value.Split(';');
443                     for (unsigned i = 0; i < filters.Size(); ++i)
444                     {
445                         if (filters[i][0] == '-')
446                             nonSkinningBoneExcludes_.Push(filters[i].Substring(1));
447                         else
448                             nonSkinningBoneIncludes_.Push(filters[i]);
449                     }
450                 }
451             }
452             else if (argument == "v")
453                 verboseLog_ = true;
454             else if (argument == "eao")
455                 emissiveAO_ = true;
456             else if (argument == "cm")
457                 noOverwriteMaterial_ = true;
458             else if (argument == "ct")
459                 noOverwriteTexture_ = true;
460             else if (argument == "ctn")
461                 noOverwriteNewerTexture_ = true;
462             else if (argument == "am")
463                 checkUniqueModel_ = false;
464             else if (argument == "bp")
465                 moveToBindPose_ = true;
466             else if (argument == "split")
467             {
468                 String value2 = i + 2 < arguments.Size() ? arguments[i + 2] : String::EMPTY;
469                 if (value.Length() && value2.Length() && (value[0] != '-') && (value2[0] != '-'))
470                 {
471                     importStartTime_ = ToFloat(value);
472                     importEndTime_ = ToFloat(value2);
473                 }
474             }
475         }
476     }
477 
478     if (command == "model" || command == "scene" || command == "anim" || command == "node" || command == "dump")
479     {
480         String inFile = arguments[1];
481         String outFile;
482         if (arguments.Size() > 2 && arguments[2][0] != '-')
483             outFile = GetInternalPath(arguments[2]);
484 
485         inputName_ = GetFileName(inFile);
486         outName_ = outFile;
487         outPath_ = GetPath(outFile);
488 
489         if (resourcePath_.Empty())
490         {
491             resourcePath_ = outPath_;
492             // If output file already has the Models/ path (model mode), do not take it into the resource path
493             if (command == "model")
494             {
495                 if (resourcePath_.EndsWith("Models/", false))
496                     resourcePath_ = resourcePath_.Substring(0, resourcePath_.Length() - 7);
497             }
498             if (resourcePath_.Empty())
499                 resourcePath_ = "./";
500         }
501 
502         resourcePath_ = AddTrailingSlash(resourcePath_);
503 
504         if (command != "dump" && outFile.Empty())
505             ErrorExit("No output file defined");
506 
507         if (verboseLog_)
508             Assimp::DefaultLogger::create("", Assimp::Logger::VERBOSE, aiDefaultLogStream_STDOUT);
509 
510         PrintLine("Reading file " + inFile);
511 
512         if (!inFile.EndsWith(".fbx", false))
513             suppressFbxPivotNodes_ = false;
514 
515         // Only do this for the "model" command. "anim" command extrapolates animation from the original bone definition
516         if (suppressFbxPivotNodes_ && command == "model")
517         {
518             PrintLine("Suppressing $fbx nodes");
519             aiPropertyStore *aiprops = aiCreatePropertyStore();
520             aiSetImportPropertyInteger(aiprops, AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, 1);       //default = true;
521             aiSetImportPropertyInteger(aiprops, AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, 0);             //default = false;
522             aiSetImportPropertyInteger(aiprops, AI_CONFIG_IMPORT_FBX_READ_MATERIALS, 1);                 //default = true;
523             aiSetImportPropertyInteger(aiprops, AI_CONFIG_IMPORT_FBX_READ_CAMERAS, 1);                   //default = true;
524             aiSetImportPropertyInteger(aiprops, AI_CONFIG_IMPORT_FBX_READ_LIGHTS, 1);                    //default = true;
525             aiSetImportPropertyInteger(aiprops, AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, 1);                //default = true;
526             aiSetImportPropertyInteger(aiprops, AI_CONFIG_IMPORT_FBX_STRICT_MODE, 0);                    //default = false;
527             aiSetImportPropertyInteger(aiprops, AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, 0);                //**false, default = true;
528             aiSetImportPropertyInteger(aiprops, AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, 1);//default = true;
529 
530             scene_ = aiImportFileExWithProperties(GetNativePath(inFile).CString(), flags, NULL, aiprops);
531 
532             // prevent processing animation suppression, both cannot work simultaneously
533             suppressFbxPivotNodes_ = false;
534         }
535         else
536             scene_ = aiImportFile(GetNativePath(inFile).CString(), flags);
537 
538         if (!scene_)
539             ErrorExit("Could not open or parse input file " + inFile + ": " + String(aiGetErrorString()));
540 
541         if (verboseLog_)
542             Assimp::DefaultLogger::kill();
543 
544         rootNode_ = scene_->mRootNode;
545         if (!rootNodeName.Empty())
546         {
547             rootNode_ = GetNode(rootNodeName, rootNode_, false);
548             if (!rootNode_)
549                 ErrorExit("Could not find scene node " + rootNodeName);
550         }
551 
552         if (command == "dump")
553         {
554             DumpNodes(rootNode_, 0);
555             return;
556         }
557 
558         if (command == "model")
559             ExportModel(outFile, scene_->mFlags & AI_SCENE_FLAGS_INCOMPLETE);
560 
561         if (command == "anim")
562         {
563             noMaterials_ = true;
564             ExportAnimation(outFile, scene_->mFlags & AI_SCENE_FLAGS_INCOMPLETE);
565         }
566         if (command == "scene" || command == "node")
567         {
568             bool asPrefab = command == "node";
569             // Saving as prefab requires the hierarchy, especially the root node
570             if (asPrefab)
571                 noHierarchy_ = false;
572             ExportScene(outFile, asPrefab);
573         }
574 
575         if (!noMaterials_)
576         {
577             HashSet<String> usedTextures;
578             ExportMaterials(usedTextures);
579             if (!noTextures_)
580                 CopyTextures(usedTextures, GetPath(inFile));
581         }
582     }
583     else if (command == "lod")
584     {
585         PODVector<float> lodDistances;
586         Vector<String> modelNames;
587         String outFile;
588 
589         unsigned numLodArguments = 0;
590         for (unsigned i = 1; i < arguments.Size(); ++i)
591         {
592             if (arguments[i][0] == '-')
593                 break;
594             ++numLodArguments;
595         }
596         if (numLodArguments < 4)
597             ErrorExit("Must define at least 2 LOD levels");
598         if (!(numLodArguments & 1))
599             ErrorExit("No output file defined");
600 
601         for (unsigned i = 1; i < numLodArguments + 1; ++i)
602         {
603             if (i == numLodArguments)
604                 outFile = GetInternalPath(arguments[i]);
605             else
606             {
607                 if (i & 1)
608                     lodDistances.Push(Max(ToFloat(arguments[i]), 0.0f));
609                 else
610                     modelNames.Push(GetInternalPath(arguments[i]));
611             }
612         }
613 
614         if (lodDistances[0] != 0.0f)
615         {
616             PrintLine("Warning: first LOD distance forced to 0");
617             lodDistances[0] = 0.0f;
618         }
619 
620         CombineLods(lodDistances, modelNames, outFile);
621     }
622     else
623         ErrorExit("Unrecognized command " + command);
624 }
625 
DumpNodes(aiNode * rootNode,unsigned level)626 void DumpNodes(aiNode* rootNode, unsigned level)
627 {
628     if (!rootNode)
629         return;
630 
631     String indent(' ', level * 2);
632     Vector3 pos, scale;
633     Quaternion rot;
634     aiMatrix4x4 transform = GetDerivedTransform(rootNode, rootNode_);
635     GetPosRotScale(transform, pos, rot, scale);
636 
637     PrintLine(indent + "Node " + FromAIString(rootNode->mName) + " pos " + String(pos));
638 
639     if (rootNode->mNumMeshes == 1)
640         PrintLine(indent + "  " + String(rootNode->mNumMeshes) + " geometry");
641     if (rootNode->mNumMeshes > 1)
642         PrintLine(indent + "  " + String(rootNode->mNumMeshes) + " geometries");
643 
644     for (unsigned i = 0; i < rootNode->mNumChildren; ++i)
645         DumpNodes(rootNode->mChildren[i], level + 1);
646 }
647 
ExportModel(const String & outName,bool animationOnly)648 void ExportModel(const String& outName, bool animationOnly)
649 {
650     if (outName.Empty())
651         ErrorExit("No output file defined");
652 
653     OutModel model;
654     model.rootNode_ = rootNode_;
655     model.outName_ = outName;
656 
657     CollectMeshes(model, model.rootNode_);
658     CollectBones(model, animationOnly);
659     BuildBoneCollisionInfo(model);
660     BuildAndSaveModel(model);
661     if (!noAnimations_)
662     {
663         CollectAnimations(&model);
664         BuildAndSaveAnimations(&model);
665 
666         // Save scene-global animations
667         CollectAnimations();
668         BuildAndSaveAnimations();
669     }
670 }
671 
ExportAnimation(const String & outName,bool animationOnly)672 void ExportAnimation(const String& outName, bool animationOnly)
673 {
674     if (outName.Empty())
675         ErrorExit("No output file defined");
676 
677     OutModel model;
678     model.rootNode_ = rootNode_;
679     model.outName_ = outName;
680 
681     CollectMeshes(model, model.rootNode_);
682     CollectBones(model, animationOnly);
683     BuildBoneCollisionInfo(model);
684     //    BuildAndSaveModel(model);
685     if (!noAnimations_)
686     {
687         // Most fbx animation files contain only a skeleton and no skinned mesh.
688         // Assume the scene node contains the model's bone definition and,
689         // transfer the info to the model.
690         if (suppressFbxPivotNodes_ && model.bones_.Size() == 0)
691             CollectSceneNodesAsBones(model, rootNode_);
692 
693         CollectAnimations(&model);
694         BuildAndSaveAnimations(&model);
695 
696         // Save scene-global animations
697         CollectAnimations();
698         BuildAndSaveAnimations();
699     }
700 }
701 
CollectMeshes(OutModel & model,aiNode * node)702 void CollectMeshes(OutModel& model, aiNode* node)
703 {
704     for (unsigned i = 0; i < node->mNumMeshes; ++i)
705     {
706         aiMesh* mesh = scene_->mMeshes[node->mMeshes[i]];
707         for (unsigned j = 0; j < model.meshes_.Size(); ++j)
708         {
709             if (mesh == model.meshes_[j])
710             {
711                 PrintLine("Warning: same mesh found multiple times");
712                 break;
713             }
714         }
715 
716         model.meshIndices_.Insert(node->mMeshes[i]);
717         model.meshes_.Push(mesh);
718         model.meshNodes_.Push(node);
719         model.totalVertices_ += mesh->mNumVertices;
720         model.totalIndices_ += GetNumValidFaces(mesh) * 3;
721     }
722 
723     for (unsigned i = 0; i < node->mNumChildren; ++i)
724         CollectMeshes(model, node->mChildren[i]);
725 }
726 
CollectBones(OutModel & model,bool animationOnly)727 void CollectBones(OutModel& model, bool animationOnly)
728 {
729     HashSet<aiNode*> necessary;
730     HashSet<aiNode*> rootNodes;
731 
732     bool haveSkinnedMeshes = false;
733     for (unsigned i = 0; i < model.meshes_.Size(); ++i)
734     {
735         if (model.meshes_[i]->HasBones())
736         {
737             haveSkinnedMeshes = true;
738             break;
739         }
740     }
741 
742     for (unsigned i = 0; i < model.meshes_.Size(); ++i)
743     {
744         aiMesh* mesh = model.meshes_[i];
745         aiNode* meshNode = model.meshNodes_[i];
746         aiNode* meshParentNode = meshNode->mParent;
747         aiNode* rootNode = 0;
748 
749         for (unsigned j = 0; j < mesh->mNumBones; ++j)
750         {
751             aiBone* bone = mesh->mBones[j];
752             String boneName(FromAIString(bone->mName));
753             aiNode* boneNode = GetNode(boneName, scene_->mRootNode, true);
754             if (!boneNode)
755                 ErrorExit("Could not find scene node for bone " + boneName);
756             necessary.Insert(boneNode);
757             rootNode = boneNode;
758 
759             for (;;)
760             {
761                 boneNode = boneNode->mParent;
762                 if (!boneNode || ((boneNode == meshNode || boneNode == meshParentNode) && !animationOnly))
763                     break;
764                 rootNode = boneNode;
765                 necessary.Insert(boneNode);
766             }
767 
768             if (rootNodes.Find(rootNode) == rootNodes.End())
769                 rootNodes.Insert(rootNode);
770         }
771 
772         // When model is partially skinned, include the attachment nodes of the rigid meshes in the skeleton
773         if (haveSkinnedMeshes && !mesh->mNumBones)
774         {
775             aiNode* boneNode = meshNode;
776             necessary.Insert(boneNode);
777             rootNode = boneNode;
778 
779             for (;;)
780             {
781                 boneNode = boneNode->mParent;
782                 if (!boneNode || ((boneNode == meshNode || boneNode == meshParentNode) && !animationOnly))
783                     break;
784                 rootNode = boneNode;
785                 necessary.Insert(boneNode);
786             }
787 
788             if (rootNodes.Find(rootNode) == rootNodes.End())
789                 rootNodes.Insert(rootNode);
790         }
791     }
792 
793 
794     // If we find multiple root nodes, try to remedy by going back in the parent chain and finding a common parent
795     if (rootNodes.Size() > 1)
796     {
797         for (HashSet<aiNode*>::Iterator i = rootNodes.Begin(); i != rootNodes.End(); ++i)
798         {
799             aiNode* commonParent = (*i);
800 
801             while (commonParent)
802             {
803                 unsigned found = 0;
804                 for (HashSet<aiNode*>::Iterator j = rootNodes.Begin(); j != rootNodes.End(); ++j)
805                 {
806                     if (i == j)
807                         continue;
808                     aiNode* parent = *j;
809                     while (parent)
810                     {
811                         if (parent == commonParent)
812                         {
813                             ++found;
814                             break;
815                         }
816                         parent = parent->mParent;
817                     }
818                 }
819 
820                 if (found >= rootNodes.Size() - 1)
821                 {
822                     PrintLine("Multiple roots initially found, using new root node " + FromAIString(commonParent->mName));
823                     rootNodes.Clear();
824                     rootNodes.Insert(commonParent);
825                     necessary.Insert(commonParent);
826                     break;
827                 }
828 
829                 commonParent = commonParent->mParent;
830             }
831 
832             if (rootNodes.Size() == 1)
833                 break; // Succeeded
834         }
835         if (rootNodes.Size() > 1)
836             ErrorExit("Skeleton with multiple root nodes found, not supported");
837     }
838 
839     if (rootNodes.Empty())
840         return;
841 
842     model.rootBone_ = *rootNodes.Begin();
843 
844     // Move the model to bind pose now if requested
845     if (moveToBindPose_)
846     {
847         PrintLine("Moving bones to bind pose");
848         MoveToBindPose(model, model.rootBone_);
849     }
850 
851     CollectBonesFinal(model.bones_, necessary, model.rootBone_);
852     // Initialize the bone collision info
853     model.boneRadii_.Resize(model.bones_.Size());
854     model.boneHitboxes_.Resize(model.bones_.Size());
855     for (unsigned i = 0; i < model.bones_.Size(); ++i)
856     {
857         model.boneRadii_[i] = 0.0f;
858         model.boneHitboxes_[i] = BoundingBox(0.0f, 0.0f);
859     }
860 }
861 
MoveToBindPose(OutModel & model,aiNode * current)862 void MoveToBindPose(OutModel& model, aiNode* current)
863 {
864     String nodeName(FromAIString(current->mName));
865     Matrix3x4 bindWorldTransform = GetOffsetMatrix(model, nodeName).Inverse();
866     // Skip if we get an identity offset matrix (bone lookup failed)
867     if (!bindWorldTransform.Equals(Matrix3x4::IDENTITY))
868     {
869         if (current->mParent && current != model.rootNode_)
870         {
871             aiMatrix4x4 parentWorldTransform = GetDerivedTransform(current->mParent, model.rootNode_, true);
872             Matrix3x4 parentInverse = ToMatrix3x4(parentWorldTransform).Inverse();
873             current->mTransformation = ToAIMatrix4x4(parentInverse * bindWorldTransform);
874         }
875         else
876             current->mTransformation = ToAIMatrix4x4(bindWorldTransform);
877     }
878 
879     for (unsigned i = 0; i < current->mNumChildren; ++i)
880         MoveToBindPose(model, current->mChildren[i]);
881 }
882 
CollectBonesFinal(PODVector<aiNode * > & dest,const HashSet<aiNode * > & necessary,aiNode * node)883 void CollectBonesFinal(PODVector<aiNode*>& dest, const HashSet<aiNode*>& necessary, aiNode* node)
884 {
885     bool includeBone = necessary.Find(node) != necessary.End();
886     String boneName = FromAIString(node->mName);
887 
888     // Check include/exclude filters for non-skinned bones
889     if (!includeBone && includeNonSkinningBones_)
890     {
891         // If no includes specified, include by default but check for excludes
892         if (nonSkinningBoneIncludes_.Empty())
893             includeBone = true;
894 
895         // Check against includes/excludes
896         for (unsigned i = 0; i < nonSkinningBoneIncludes_.Size(); ++i)
897         {
898             if (boneName.Contains(nonSkinningBoneIncludes_[i], false))
899             {
900                 includeBone = true;
901                 break;
902             }
903         }
904         for (unsigned i = 0; i < nonSkinningBoneExcludes_.Size(); ++i)
905         {
906             if (boneName.Contains(nonSkinningBoneExcludes_[i], false))
907             {
908                 includeBone = false;
909                 break;
910             }
911         }
912 
913         if (includeBone)
914             PrintLine("Including non-skinning bone " + boneName);
915     }
916 
917     if (includeBone)
918         dest.Push(node);
919 
920     for (unsigned i = 0; i < node->mNumChildren; ++i)
921         CollectBonesFinal(dest, necessary, node->mChildren[i]);
922 }
923 
CollectAnimations(OutModel * model)924 void CollectAnimations(OutModel* model)
925 {
926     const aiScene* scene = scene_;
927     for (unsigned i = 0; i < scene->mNumAnimations; ++i)
928     {
929         aiAnimation* anim = scene->mAnimations[i];
930         if (allAnimations_.Contains(anim))
931             continue;
932 
933         if (model)
934         {
935             bool modelBoneFound = false;
936             for (unsigned j = 0; j < anim->mNumChannels; ++j)
937             {
938                 aiNodeAnim* channel = anim->mChannels[j];
939                 String channelName = FromAIString(channel->mNodeName);
940                 if (GetBoneIndex(*model, channelName) != M_MAX_UNSIGNED)
941                 {
942                     modelBoneFound = true;
943                     break;
944                 }
945             }
946             if (modelBoneFound)
947             {
948                 model->animations_.Push(anim);
949                 allAnimations_.Insert(anim);
950             }
951         }
952         else
953         {
954             sceneAnimations_.Push(anim);
955             allAnimations_.Insert(anim);
956         }
957     }
958 
959     /// \todo Vertex morphs are ignored for now
960 }
961 
BuildBoneCollisionInfo(OutModel & model)962 void BuildBoneCollisionInfo(OutModel& model)
963 {
964     for (unsigned i = 0; i < model.meshes_.Size(); ++i)
965     {
966         aiMesh* mesh = model.meshes_[i];
967         for (unsigned j = 0; j < mesh->mNumBones; ++j)
968         {
969             aiBone* bone = mesh->mBones[j];
970             String boneName = FromAIString(bone->mName);
971             unsigned boneIndex = GetBoneIndex(model, boneName);
972             if (boneIndex == M_MAX_UNSIGNED)
973                 continue;
974             for (unsigned k = 0; k < bone->mNumWeights; ++k)
975             {
976                 float weight = bone->mWeights[k].mWeight;
977                 // Require skinning weight to be sufficiently large before vertex contributes to bone hitbox
978                 if (weight > 0.33f)
979                 {
980                     aiVector3D vertexBoneSpace = bone->mOffsetMatrix * mesh->mVertices[bone->mWeights[k].mVertexId];
981                     Vector3 vertex = ToVector3(vertexBoneSpace);
982                     float radius = vertex.Length();
983                     if (radius > model.boneRadii_[boneIndex])
984                         model.boneRadii_[boneIndex] = radius;
985                     model.boneHitboxes_[boneIndex].Merge(vertex);
986                 }
987             }
988         }
989     }
990 }
991 
BuildAndSaveModel(OutModel & model)992 void BuildAndSaveModel(OutModel& model)
993 {
994     if (!model.rootNode_)
995     {
996         PrintLine("Null root node for model, skipping model save");
997         return;
998     }
999 
1000     String rootNodeName = FromAIString(model.rootNode_->mName);
1001     if (!model.meshes_.Size())
1002     {
1003         PrintLine("No geometries found starting from node " + rootNodeName + ", skipping model save");
1004         return;
1005     }
1006 
1007     PrintLine("Writing model " + rootNodeName);
1008 
1009     SharedPtr<Model> outModel(new Model(context_));
1010     Vector<PODVector<unsigned> > allBoneMappings;
1011     BoundingBox box;
1012 
1013     unsigned numValidGeometries = 0;
1014 
1015     bool combineBuffers = true;
1016     // Check if buffers can be combined (same vertex elements, under 65535 vertices)
1017     PODVector<VertexElement> elements = GetVertexElements(model.meshes_[0], model.bones_.Size() > 0);
1018     for (unsigned i = 0; i < model.meshes_.Size(); ++i)
1019     {
1020         if (GetNumValidFaces(model.meshes_[i]))
1021         {
1022             ++numValidGeometries;
1023             if (i > 0 && GetVertexElements(model.meshes_[i], model.bones_.Size() > 0) != elements)
1024                 combineBuffers = false;
1025         }
1026     }
1027 
1028     // Check if keeping separate buffers allows to avoid 32-bit indices
1029     if (combineBuffers && model.totalVertices_ > 65535)
1030     {
1031         bool allUnder65k = true;
1032         for (unsigned i = 0; i < model.meshes_.Size(); ++i)
1033         {
1034             if (GetNumValidFaces(model.meshes_[i]))
1035             {
1036                 if (model.meshes_[i]->mNumVertices > 65535)
1037                     allUnder65k = false;
1038             }
1039         }
1040         if (allUnder65k == true)
1041             combineBuffers = false;
1042     }
1043 
1044     SharedPtr<IndexBuffer> ib;
1045     SharedPtr<VertexBuffer> vb;
1046     Vector<SharedPtr<VertexBuffer> > vbVector;
1047     Vector<SharedPtr<IndexBuffer> > ibVector;
1048     unsigned startVertexOffset = 0;
1049     unsigned startIndexOffset = 0;
1050     unsigned destGeomIndex = 0;
1051     bool isSkinned = model.bones_.Size() > 0;
1052 
1053     outModel->SetNumGeometries(numValidGeometries);
1054 
1055     for (unsigned i = 0; i < model.meshes_.Size(); ++i)
1056     {
1057         aiMesh* mesh = model.meshes_[i];
1058         PODVector<VertexElement> elements = GetVertexElements(mesh, isSkinned);
1059         unsigned validFaces = GetNumValidFaces(mesh);
1060         if (!validFaces)
1061             continue;
1062 
1063         bool largeIndices;
1064         if (combineBuffers)
1065             largeIndices = model.totalIndices_ > 65535;
1066         else
1067             largeIndices = mesh->mNumVertices > 65535;
1068 
1069         // Create new buffers if necessary
1070         if (!combineBuffers || vbVector.Empty())
1071         {
1072             vb = new VertexBuffer(context_);
1073             ib = new IndexBuffer(context_);
1074 
1075             if (combineBuffers)
1076             {
1077                 ib->SetSize(model.totalIndices_, largeIndices);
1078                 vb->SetSize(model.totalVertices_, elements);
1079             }
1080             else
1081             {
1082                 ib->SetSize(validFaces * 3, largeIndices);
1083                 vb->SetSize(mesh->mNumVertices, elements);
1084             }
1085 
1086             vbVector.Push(vb);
1087             ibVector.Push(ib);
1088             startVertexOffset = 0;
1089             startIndexOffset = 0;
1090         }
1091 
1092         // Get the world transform of the mesh for baking into the vertices
1093         Matrix3x4 vertexTransform;
1094         Matrix3 normalTransform;
1095         Vector3 pos, scale;
1096         Quaternion rot;
1097         GetPosRotScale(GetMeshBakingTransform(model.meshNodes_[i], model.rootNode_), pos, rot, scale);
1098         vertexTransform = Matrix3x4(pos, rot, scale);
1099         normalTransform = rot.RotationMatrix();
1100 
1101         SharedPtr<Geometry> geom(new Geometry(context_));
1102 
1103         PrintLine("Writing geometry " + String(i) + " with " + String(mesh->mNumVertices) + " vertices " +
1104             String(validFaces * 3) + " indices");
1105 
1106         if (model.bones_.Size() > 0 && !mesh->HasBones())
1107             PrintLine("Warning: model has bones but geometry " + String(i) + " has no skinning information");
1108 
1109         unsigned char* vertexData = vb->GetShadowData();
1110         unsigned char* indexData = ib->GetShadowData();
1111 
1112         // Build the index data
1113         if (!largeIndices)
1114         {
1115             unsigned short* dest = (unsigned short*)indexData + startIndexOffset;
1116             for (unsigned j = 0; j < mesh->mNumFaces; ++j)
1117                 WriteShortIndices(dest, mesh, j, startVertexOffset);
1118         }
1119         else
1120         {
1121             unsigned* dest = (unsigned*)indexData + startIndexOffset;
1122             for (unsigned j = 0; j < mesh->mNumFaces; ++j)
1123                 WriteLargeIndices(dest, mesh, j, startVertexOffset);
1124         }
1125 
1126         // Build the vertex data
1127         // If there are bones, get blend data
1128         Vector<PODVector<unsigned char> > blendIndices;
1129         Vector<PODVector<float> > blendWeights;
1130         PODVector<unsigned> boneMappings;
1131         if (model.bones_.Size())
1132             GetBlendData(model, mesh, model.meshNodes_[i], boneMappings, blendIndices, blendWeights);
1133 
1134         float* dest = (float*)((unsigned char*)vertexData + startVertexOffset * vb->GetVertexSize());
1135         for (unsigned j = 0; j < mesh->mNumVertices; ++j)
1136             WriteVertex(dest, mesh, j, isSkinned, box, vertexTransform, normalTransform, blendIndices, blendWeights);
1137 
1138         // Calculate the geometry center
1139         Vector3 center = Vector3::ZERO;
1140         if (validFaces)
1141         {
1142             for (unsigned j = 0; j < mesh->mNumFaces; ++j)
1143             {
1144                 if (mesh->mFaces[j].mNumIndices == 3)
1145                 {
1146                     center += vertexTransform * ToVector3(mesh->mVertices[mesh->mFaces[j].mIndices[0]]);
1147                     center += vertexTransform * ToVector3(mesh->mVertices[mesh->mFaces[j].mIndices[1]]);
1148                     center += vertexTransform * ToVector3(mesh->mVertices[mesh->mFaces[j].mIndices[2]]);
1149                 }
1150             }
1151 
1152             center /= (float)validFaces * 3;
1153         }
1154 
1155         // Define the geometry
1156         geom->SetIndexBuffer(ib);
1157         geom->SetVertexBuffer(0, vb);
1158         geom->SetDrawRange(TRIANGLE_LIST, startIndexOffset, validFaces * 3, true);
1159         outModel->SetNumGeometryLodLevels(destGeomIndex, 1);
1160         outModel->SetGeometry(destGeomIndex, 0, geom);
1161         outModel->SetGeometryCenter(destGeomIndex, center);
1162         if (model.bones_.Size() > maxBones_)
1163             allBoneMappings.Push(boneMappings);
1164 
1165         startVertexOffset += mesh->mNumVertices;
1166         startIndexOffset += validFaces * 3;
1167         ++destGeomIndex;
1168     }
1169 
1170     // Define the model buffers and bounding box
1171     PODVector<unsigned> emptyMorphRange;
1172     outModel->SetVertexBuffers(vbVector, emptyMorphRange, emptyMorphRange);
1173     outModel->SetIndexBuffers(ibVector);
1174     outModel->SetBoundingBox(box);
1175 
1176     // Build skeleton if necessary
1177     if (model.bones_.Size() && model.rootBone_)
1178     {
1179         PrintLine("Writing skeleton with " + String(model.bones_.Size()) + " bones, rootbone " +
1180             FromAIString(model.rootBone_->mName));
1181 
1182         Skeleton skeleton;
1183         Vector<Bone>& bones = skeleton.GetModifiableBones();
1184 
1185         for (unsigned i = 0; i < model.bones_.Size(); ++i)
1186         {
1187             aiNode* boneNode = model.bones_[i];
1188             String boneName(FromAIString(boneNode->mName));
1189 
1190             Bone newBone;
1191             newBone.name_ = boneName;
1192 
1193             aiMatrix4x4 transform = boneNode->mTransformation;
1194             // Make the root bone transform relative to the model's root node, if it is not already
1195             // (in case there are nodes between that are not accounted for otherwise)
1196             if (boneNode == model.rootBone_)
1197                 transform = GetDerivedTransform(boneNode, model.rootNode_, false);
1198 
1199             GetPosRotScale(transform, newBone.initialPosition_, newBone.initialRotation_, newBone.initialScale_);
1200 
1201             // Get offset information if exists
1202             newBone.offsetMatrix_ = GetOffsetMatrix(model, boneName);
1203             newBone.radius_ = model.boneRadii_[i];
1204             newBone.boundingBox_ = model.boneHitboxes_[i];
1205             newBone.collisionMask_ = BONECOLLISION_SPHERE | BONECOLLISION_BOX;
1206             newBone.parentIndex_ = i;
1207             bones.Push(newBone);
1208         }
1209         // Set the bone hierarchy
1210         for (unsigned i = 1; i < model.bones_.Size(); ++i)
1211         {
1212             String parentName = FromAIString(model.bones_[i]->mParent->mName);
1213             for (unsigned j = 0; j < bones.Size(); ++j)
1214             {
1215                 if (bones[j].name_ == parentName)
1216                 {
1217                     bones[i].parentIndex_ = j;
1218                     break;
1219                 }
1220             }
1221         }
1222 
1223         outModel->SetSkeleton(skeleton);
1224         if (model.bones_.Size() > maxBones_)
1225             outModel->SetGeometryBoneMappings(allBoneMappings);
1226     }
1227 
1228     File outFile(context_);
1229     if (!outFile.Open(model.outName_, FILE_WRITE))
1230         ErrorExit("Could not open output file " + model.outName_);
1231     outModel->Save(outFile);
1232 
1233     // If exporting materials, also save material list for use by the editor
1234     if (!noMaterials_ && saveMaterialList_)
1235     {
1236         String materialListName = ReplaceExtension(model.outName_, ".txt");
1237         File listFile(context_);
1238         if (listFile.Open(materialListName, FILE_WRITE))
1239         {
1240             for (unsigned i = 0; i < model.meshes_.Size(); ++i)
1241                 listFile.WriteLine(GetMeshMaterialName(model.meshes_[i]));
1242         }
1243         else
1244             PrintLine("Warning: could not write material list file " + materialListName);
1245     }
1246 }
1247 
BuildAndSaveAnimations(OutModel * model)1248 void BuildAndSaveAnimations(OutModel* model)
1249 {
1250     // extrapolate anim
1251     ExtrapolatePivotlessAnimation(model);
1252 
1253     // build and save anim
1254     const PODVector<aiAnimation*>& animations = model ? model->animations_ : sceneAnimations_;
1255 
1256     for (unsigned i = 0; i < animations.Size(); ++i)
1257     {
1258         aiAnimation* anim = animations[i];
1259 
1260         float duration = (float)anim->mDuration;
1261         String animName = FromAIString(anim->mName);
1262         String animOutName;
1263 
1264         float thisImportEndTime = importEndTime_;
1265         float thisImportStartTime = importStartTime_;
1266 
1267         // If no animation split specified, set the end time to duration
1268         if (thisImportEndTime == 0.0f)
1269             thisImportEndTime = duration;
1270 
1271         if (animName.Empty())
1272             animName = "Anim" + String(i + 1);
1273         if (model)
1274             animOutName = GetPath(model->outName_) + GetFileName(model->outName_) + "_" + SanitateAssetName(animName) + ".ani";
1275         else
1276             animOutName = outPath_ + GetFileName(outName_) + "_" + SanitateAssetName(animName) + ".ani";
1277 
1278         float ticksPerSecond = (float)anim->mTicksPerSecond;
1279         // If ticks per second not specified, it's probably a .X file. In this case use the default tick rate
1280         if (ticksPerSecond < M_EPSILON)
1281             ticksPerSecond = defaultTicksPerSecond_;
1282         float tickConversion = 1.0f / ticksPerSecond;
1283 
1284         // Find out the start time of animation from each channel's first keyframe for adjusting the keyframe times
1285         // to start from zero
1286         float startTime = duration;
1287         for (unsigned j = 0; j < anim->mNumChannels; ++j)
1288         {
1289             aiNodeAnim* channel = anim->mChannels[j];
1290             if (channel->mNumPositionKeys > 0)
1291                 startTime = Min(startTime, (float)channel->mPositionKeys[0].mTime);
1292             if (channel->mNumRotationKeys > 0)
1293                 startTime = Min(startTime, (float)channel->mRotationKeys[0].mTime);
1294             if (channel->mNumScalingKeys > 0)
1295                 startTime = Min(startTime, (float)channel->mScalingKeys[0].mTime);
1296         }
1297         if (startTime > thisImportStartTime)
1298             thisImportStartTime = startTime;
1299         duration = thisImportEndTime - thisImportStartTime;
1300 
1301         SharedPtr<Animation> outAnim(new Animation(context_));
1302         outAnim->SetAnimationName(animName);
1303         outAnim->SetLength(duration * tickConversion);
1304 
1305         PrintLine("Writing animation " + animName + " length " + String(outAnim->GetLength()));
1306         for (unsigned j = 0; j < anim->mNumChannels; ++j)
1307         {
1308             aiNodeAnim* channel = anim->mChannels[j];
1309             String channelName = FromAIString(channel->mNodeName);
1310             aiNode* boneNode = 0;
1311 
1312             if (model)
1313             {
1314                 unsigned boneIndex;
1315                 unsigned pos = channelName.Find("_$AssimpFbx$");
1316 
1317                 if (!suppressFbxPivotNodes_ || pos == String::NPOS)
1318                 {
1319                     boneIndex = GetBoneIndex(*model, channelName);
1320                     if (boneIndex == M_MAX_UNSIGNED)
1321                     {
1322                         PrintLine("Warning: skipping animation track " + channelName + " not found in model skeleton");
1323                         outAnim->RemoveTrack(channelName);
1324                         continue;
1325                     }
1326                     boneNode = model->bones_[boneIndex];
1327                 }
1328                 else
1329                 {
1330                     channelName = channelName.Substring(0, pos);
1331 
1332                     // every first $fbx animation channel for a bone will consolidate other $fbx animation to a single channel
1333                     // skip subsequent $fbx animation channel for the same bone
1334                     if (outAnim->GetTrack(channelName) != NULL)
1335                         continue;
1336 
1337                     boneIndex = GetPivotlessBoneIndex(*model, channelName);
1338                     if (boneIndex == M_MAX_UNSIGNED)
1339                     {
1340                         PrintLine("Warning: skipping animation track " + channelName + " not found in model skeleton");
1341                         outAnim->RemoveTrack(channelName);
1342                         continue;
1343                     }
1344 
1345                     boneNode = model->pivotlessBones_[boneIndex];
1346                 }
1347             }
1348             else
1349             {
1350                 boneNode = GetNode(channelName, scene_->mRootNode);
1351                 if (!boneNode)
1352                 {
1353                     PrintLine("Warning: skipping animation track " + channelName + " whose scene node was not found");
1354                     outAnim->RemoveTrack(channelName);
1355                     continue;
1356                 }
1357             }
1358 
1359             // To export single frame animation, check if first key frame is identical to bone transformation
1360             aiVector3D bonePos, boneScale;
1361             aiQuaternion boneRot;
1362             boneNode->mTransformation.Decompose(boneScale, boneRot, bonePos);
1363 
1364             bool posEqual = true;
1365             bool scaleEqual = true;
1366             bool rotEqual = true;
1367 
1368             if (channel->mNumPositionKeys > 0 && !ToVector3(bonePos).Equals(ToVector3(channel->mPositionKeys[0].mValue)))
1369                 posEqual = false;
1370             if (channel->mNumScalingKeys > 0 && !ToVector3(boneScale).Equals(ToVector3(channel->mScalingKeys[0].mValue)))
1371                 scaleEqual = false;
1372             if (channel->mNumRotationKeys > 0 && !ToQuaternion(boneRot).Equals(ToQuaternion(channel->mRotationKeys[0].mValue)))
1373                 rotEqual = false;
1374 
1375             AnimationTrack* track = outAnim->CreateTrack(channelName);
1376 
1377             // Check which channels are used
1378             track->channelMask_ = 0;
1379             if (channel->mNumPositionKeys > 1 || !posEqual)
1380                 track->channelMask_ |= CHANNEL_POSITION;
1381             if (channel->mNumRotationKeys > 1 || !rotEqual)
1382                 track->channelMask_ |= CHANNEL_ROTATION;
1383             if (channel->mNumScalingKeys > 1 || !scaleEqual)
1384                 track->channelMask_ |= CHANNEL_SCALE;
1385             // Check for redundant identity scale in all keyframes and remove in that case
1386             if (track->channelMask_ & CHANNEL_SCALE)
1387             {
1388                 bool redundantScale = true;
1389                 for (unsigned k = 0; k < channel->mNumScalingKeys; ++k)
1390                 {
1391                     float SCALE_EPSILON = 0.000001f;
1392                     Vector3 scaleVec = ToVector3(channel->mScalingKeys[k].mValue);
1393                     if (fabsf(scaleVec.x_ - 1.0f) >= SCALE_EPSILON || fabsf(scaleVec.y_ - 1.0f) >= SCALE_EPSILON ||
1394                         fabsf(scaleVec.z_ - 1.0f) >= SCALE_EPSILON)
1395                     {
1396                         redundantScale = false;
1397                         break;
1398                     }
1399                 }
1400                 if (redundantScale)
1401                     track->channelMask_ &= ~CHANNEL_SCALE;
1402             }
1403 
1404             if (!track->channelMask_)
1405             {
1406                 PrintLine("Warning: skipping animation track " + channelName + " with no keyframes");
1407                 outAnim->RemoveTrack(channelName);
1408                 continue;
1409             }
1410 
1411             // Currently only same amount of keyframes is supported
1412             // Note: should also check the times of individual keyframes for match
1413             if ((channel->mNumPositionKeys > 1 && channel->mNumRotationKeys > 1 && channel->mNumPositionKeys != channel->mNumRotationKeys) ||
1414                 (channel->mNumPositionKeys > 1 && channel->mNumScalingKeys > 1 && channel->mNumPositionKeys != channel->mNumScalingKeys) ||
1415                 (channel->mNumRotationKeys > 1 && channel->mNumScalingKeys > 1 && channel->mNumRotationKeys != channel->mNumScalingKeys))
1416             {
1417                 PrintLine("Warning: differing amounts of channel keyframes, skipping animation track " + channelName);
1418                 outAnim->RemoveTrack(channelName);
1419                 continue;
1420             }
1421 
1422             unsigned keyFrames = channel->mNumPositionKeys;
1423             if (channel->mNumRotationKeys > keyFrames)
1424                 keyFrames = channel->mNumRotationKeys;
1425             if (channel->mNumScalingKeys > keyFrames)
1426                 keyFrames = channel->mNumScalingKeys;
1427 
1428             for (unsigned k = 0; k < keyFrames; ++k)
1429             {
1430                 AnimationKeyFrame kf;
1431                 kf.time_ = 0.0f;
1432                 kf.position_ = Vector3::ZERO;
1433                 kf.rotation_ = Quaternion::IDENTITY;
1434                 kf.scale_ = Vector3::ONE;
1435 
1436                 // Get time for the keyframe. Adjust with animation's start time
1437                 if (track->channelMask_ & CHANNEL_POSITION && k < channel->mNumPositionKeys)
1438                     kf.time_ = ((float)channel->mPositionKeys[k].mTime - startTime);
1439                 else if (track->channelMask_ & CHANNEL_ROTATION && k < channel->mNumRotationKeys)
1440                     kf.time_ = ((float)channel->mRotationKeys[k].mTime - startTime);
1441                 else if (track->channelMask_ & CHANNEL_SCALE && k < channel->mNumScalingKeys)
1442                     kf.time_ = ((float)channel->mScalingKeys[k].mTime - startTime);
1443 
1444                 // Make sure time stays positive
1445                 kf.time_ = Max(kf.time_, 0.0f);
1446 
1447                 // Start with the bone's base transform
1448                 aiMatrix4x4 boneTransform = boneNode->mTransformation;
1449                 aiVector3D pos, scale;
1450                 aiQuaternion rot;
1451                 boneTransform.Decompose(scale, rot, pos);
1452                 // Then apply the active channels
1453                 if (track->channelMask_ & CHANNEL_POSITION && k < channel->mNumPositionKeys)
1454                     pos = channel->mPositionKeys[k].mValue;
1455                 if (track->channelMask_ & CHANNEL_ROTATION && k < channel->mNumRotationKeys)
1456                     rot = channel->mRotationKeys[k].mValue;
1457                 if (track->channelMask_ & CHANNEL_SCALE && k < channel->mNumScalingKeys)
1458                     scale = channel->mScalingKeys[k].mValue;
1459 
1460                 // If root bone, transform with nodes in between model root node (if any)
1461                 if (model && boneNode == model->rootBone_)
1462                 {
1463                     aiMatrix4x4 transMat, scaleMat, rotMat;
1464                     aiMatrix4x4::Translation(pos, transMat);
1465                     aiMatrix4x4::Scaling(scale, scaleMat);
1466                     rotMat = aiMatrix4x4(rot.GetMatrix());
1467                     aiMatrix4x4 tform = transMat * rotMat * scaleMat;
1468                     aiMatrix4x4 tformOld = tform;
1469                     tform = GetDerivedTransform(tform, boneNode, model->rootNode_, false);
1470                     // Do not decompose if did not actually change
1471                     if (tform != tformOld)
1472                         tform.Decompose(scale, rot, pos);
1473                 }
1474 
1475                 if (track->channelMask_ & CHANNEL_POSITION)
1476                     kf.position_ = ToVector3(pos);
1477                 if (track->channelMask_ & CHANNEL_ROTATION)
1478                     kf.rotation_ = ToQuaternion(rot);
1479                 if (track->channelMask_ & CHANNEL_SCALE)
1480                     kf.scale_ = ToVector3(scale);
1481                 if (kf.time_ >= thisImportStartTime && kf.time_ <= thisImportEndTime)
1482                 {
1483                     kf.time_ = (kf.time_ - thisImportStartTime) * tickConversion;
1484                     track->keyFrames_.Push(kf);
1485                 }
1486             }
1487         }
1488 
1489         File outFile(context_);
1490         if (!outFile.Open(animOutName, FILE_WRITE))
1491             ErrorExit("Could not open output file " + animOutName);
1492         outAnim->Save(outFile);
1493     }
1494 }
1495 
ExportScene(const String & outName,bool asPrefab)1496 void ExportScene(const String& outName, bool asPrefab)
1497 {
1498     OutScene outScene;
1499     outScene.outName_ = outName;
1500     outScene.rootNode_ = rootNode_;
1501 
1502     if (useSubdirs_)
1503         context_->GetSubsystem<FileSystem>()->CreateDir(resourcePath_ + "Models");
1504 
1505     CollectSceneModels(outScene, rootNode_);
1506 
1507     // Save models, their material lists and animations
1508     for (unsigned i = 0; i < outScene.models_.Size(); ++i)
1509         BuildAndSaveModel(outScene.models_[i]);
1510 
1511     // Save scene-global animations
1512     if (!noAnimations_)
1513     {
1514         CollectAnimations();
1515         BuildAndSaveAnimations();
1516     }
1517 
1518     // Save scene
1519     BuildAndSaveScene(outScene, asPrefab);
1520 }
1521 
CollectSceneModels(OutScene & scene,aiNode * node)1522 void CollectSceneModels(OutScene& scene, aiNode* node)
1523 {
1524     Vector<Pair<aiNode*, aiMesh*> > meshes;
1525     GetMeshesUnderNode(meshes, node);
1526 
1527     if (meshes.Size())
1528     {
1529         OutModel model;
1530         model.rootNode_ = node;
1531         model.outName_ = resourcePath_ + (useSubdirs_ ? "Models/" : "") + SanitateAssetName(FromAIString(node->mName)) + ".mdl";
1532         for (unsigned i = 0; i < meshes.Size(); ++i)
1533         {
1534             aiMesh* mesh = meshes[i].second_;
1535             unsigned meshIndex = GetMeshIndex(mesh);
1536             model.meshIndices_.Insert(meshIndex);
1537             model.meshes_.Push(mesh);
1538             model.meshNodes_.Push(meshes[i].first_);
1539             model.totalVertices_ += mesh->mNumVertices;
1540             model.totalIndices_ += GetNumValidFaces(mesh) * 3;
1541         }
1542 
1543         // Check if a model with identical mesh indices already exists. If yes, do not export twice
1544         bool unique = true;
1545         if (checkUniqueModel_)
1546         {
1547             for (unsigned i = 0; i < scene.models_.Size(); ++i)
1548             {
1549                 if (scene.models_[i].meshIndices_ == model.meshIndices_)
1550                 {
1551                     PrintLine("Added node " + FromAIString(node->mName));
1552                     scene.nodes_.Push(node);
1553                     scene.nodeModelIndices_.Push(i);
1554                     unique = false;
1555                     break;
1556                 }
1557             }
1558         }
1559         if (unique)
1560         {
1561             PrintLine("Added model " + model.outName_);
1562             PrintLine("Added node " + FromAIString(node->mName));
1563             CollectBones(model);
1564             BuildBoneCollisionInfo(model);
1565             if (!noAnimations_)
1566             {
1567                 CollectAnimations(&model);
1568                 BuildAndSaveAnimations(&model);
1569             }
1570 
1571             scene.models_.Push(model);
1572             scene.nodes_.Push(node);
1573             scene.nodeModelIndices_.Push(scene.models_.Size() - 1);
1574         }
1575     }
1576 
1577     for (unsigned i = 0; i < node->mNumChildren; ++i)
1578         CollectSceneModels(scene, node->mChildren[i]);
1579 }
1580 
CreateHierarchy(Scene * scene,aiNode * srcNode,HashMap<aiNode *,Node * > & nodeMapping)1581 void CreateHierarchy(Scene* scene, aiNode* srcNode, HashMap<aiNode*, Node*>& nodeMapping)
1582 {
1583     CreateSceneNode(scene, srcNode, nodeMapping);
1584     for (unsigned i = 0; i < srcNode->mNumChildren; ++i)
1585         CreateHierarchy(scene, srcNode->mChildren[i], nodeMapping);
1586 }
1587 
CreateSceneNode(Scene * scene,aiNode * srcNode,HashMap<aiNode *,Node * > & nodeMapping)1588 Node* CreateSceneNode(Scene* scene, aiNode* srcNode, HashMap<aiNode*, Node*>& nodeMapping)
1589 {
1590     if (nodeMapping.Contains(srcNode))
1591         return nodeMapping[srcNode];
1592     // Flatten hierarchy if requested
1593     if (noHierarchy_)
1594     {
1595         Node* outNode = scene->CreateChild(FromAIString(srcNode->mName), localIDs_ ? LOCAL : REPLICATED);
1596         Vector3 pos, scale;
1597         Quaternion rot;
1598         GetPosRotScale(GetDerivedTransform(srcNode, rootNode_), pos, rot, scale);
1599         outNode->SetTransform(pos, rot, scale);
1600         nodeMapping[srcNode] = outNode;
1601 
1602         return outNode;
1603     }
1604 
1605     if (srcNode == rootNode_ || !srcNode->mParent)
1606     {
1607         Node* outNode = scene->CreateChild(FromAIString(srcNode->mName), localIDs_ ? LOCAL : REPLICATED);
1608         Vector3 pos, scale;
1609         Quaternion rot;
1610         GetPosRotScale(srcNode->mTransformation, pos, rot, scale);
1611         outNode->SetTransform(pos, rot, scale);
1612         nodeMapping[srcNode] = outNode;
1613 
1614         return outNode;
1615     }
1616     else
1617     {
1618         // Ensure the existence of the parent chain as in the original file
1619         if (!nodeMapping.Contains(srcNode->mParent))
1620             CreateSceneNode(scene, srcNode->mParent, nodeMapping);
1621 
1622         Node* parent = nodeMapping[srcNode->mParent];
1623         Node* outNode = parent->CreateChild(FromAIString(srcNode->mName), localIDs_ ? LOCAL : REPLICATED);
1624         Vector3 pos, scale;
1625         Quaternion rot;
1626         GetPosRotScale(srcNode->mTransformation, pos, rot, scale);
1627         outNode->SetTransform(pos, rot, scale);
1628         nodeMapping[srcNode] = outNode;
1629 
1630         return outNode;
1631     }
1632 }
1633 
BuildAndSaveScene(OutScene & scene,bool asPrefab)1634 void BuildAndSaveScene(OutScene& scene, bool asPrefab)
1635 {
1636     if (!asPrefab)
1637         PrintLine("Writing scene");
1638     else
1639         PrintLine("Writing node hierarchy");
1640 
1641     SharedPtr<Scene> outScene(new Scene(context_));
1642 
1643     if (!asPrefab)
1644     {
1645         #ifdef URHO3D_PHYSICS
1646         /// \todo Make the physics properties configurable
1647         outScene->CreateComponent<PhysicsWorld>();
1648         #endif
1649 
1650         /// \todo Make the octree properties configurable, or detect from the scene contents
1651         outScene->CreateComponent<Octree>();
1652 
1653         outScene->CreateComponent<DebugRenderer>();
1654 
1655         if (createZone_)
1656         {
1657             Node* zoneNode = outScene->CreateChild("Zone", localIDs_ ? LOCAL : REPLICATED);
1658             Zone* zone = zoneNode->CreateComponent<Zone>();
1659             zone->SetBoundingBox(BoundingBox(-1000.0f, 1000.f));
1660             zone->SetAmbientColor(Color(0.25f, 0.25f, 0.25f));
1661 
1662             // Create default light only if scene does not define them
1663             if (!scene_->HasLights())
1664             {
1665                 Node* lightNode = outScene->CreateChild("GlobalLight", localIDs_ ? LOCAL : REPLICATED);
1666                 Light* light = lightNode->CreateComponent<Light>();
1667                 light->SetLightType(LIGHT_DIRECTIONAL);
1668                 lightNode->SetRotation(Quaternion(60.0f, 30.0f, 0.0f));
1669             }
1670         }
1671     }
1672 
1673     ResourceCache* cache = context_->GetSubsystem<ResourceCache>();
1674 
1675     HashMap<aiNode*, Node*> nodeMapping;
1676 
1677     Node* outRootNode = 0;
1678     if (asPrefab)
1679         outRootNode = CreateSceneNode(outScene, rootNode_, nodeMapping);
1680     else
1681     {
1682         // If not saving as a prefab, associate the root node with the scene first to prevent unnecessary creation of a root
1683         // However do not do that if the root node does not have an identity matrix, or itself contains a model
1684         // (models at the Urho scene root are not preferable)
1685         if (ToMatrix3x4(rootNode_->mTransformation).Equals(Matrix3x4::IDENTITY) && !scene.nodes_.Contains(rootNode_))
1686            nodeMapping[rootNode_] = outScene;
1687     }
1688 
1689     // If is allowed to export empty nodes, export the full Assimp node hierarchy first
1690     if (!noHierarchy_ && !noEmptyNodes_)
1691         CreateHierarchy(outScene, rootNode_, nodeMapping);
1692 
1693     // Create geometry nodes
1694     for (unsigned i = 0; i < scene.nodes_.Size(); ++i)
1695     {
1696         const OutModel& model = scene.models_[scene.nodeModelIndices_[i]];
1697         Node* modelNode = CreateSceneNode(outScene, scene.nodes_[i], nodeMapping);
1698         StaticModel* staticModel = model.bones_.Empty() ? modelNode->CreateComponent<StaticModel>() : modelNode->CreateComponent<AnimatedModel>();
1699 
1700         // Create a dummy model so that the reference can be stored
1701         String modelName = (useSubdirs_ ? "Models/" : "") + GetFileNameAndExtension(model.outName_);
1702         if (!cache->Exists(modelName))
1703         {
1704             Model* dummyModel = new Model(context_);
1705             dummyModel->SetName(modelName);
1706             dummyModel->SetNumGeometries(model.meshes_.Size());
1707             cache->AddManualResource(dummyModel);
1708         }
1709         staticModel->SetModel(cache->GetResource<Model>(modelName));
1710 
1711         // Set materials if they are known
1712         for (unsigned j = 0; j < model.meshes_.Size(); ++j)
1713         {
1714             String matName = GetMeshMaterialName(model.meshes_[j]);
1715             // Create a dummy material so that the reference can be stored
1716             if (!cache->Exists(matName))
1717             {
1718                 Material* dummyMat = new Material(context_);
1719                 dummyMat->SetName(matName);
1720                 cache->AddManualResource(dummyMat);
1721             }
1722             staticModel->SetMaterial(j, cache->GetResource<Material>(matName));
1723         }
1724     }
1725 
1726     // Create lights
1727     if (!asPrefab)
1728     {
1729         for (unsigned i = 0; i < scene_->mNumLights; ++i)
1730         {
1731             aiLight* light = scene_->mLights[i];
1732             aiNode* lightNode = GetNode(FromAIString(light->mName), rootNode_, true);
1733             if (!lightNode)
1734                 continue;
1735             Node* outNode = CreateSceneNode(outScene, lightNode, nodeMapping);
1736 
1737             Vector3 lightAdjustPosition = ToVector3(light->mPosition);
1738             Vector3 lightAdjustDirection = ToVector3(light->mDirection);
1739             // If light is not aligned at the scene node, an adjustment node needs to be created
1740             if (!lightAdjustPosition.Equals(Vector3::ZERO) || (light->mType != aiLightSource_POINT &&
1741                 !lightAdjustDirection.Equals(Vector3::FORWARD)))
1742             {
1743                 outNode = outNode->CreateChild("LightAdjust");
1744                 outNode->SetPosition(lightAdjustPosition);
1745                 outNode->SetDirection(lightAdjustDirection);
1746             }
1747 
1748             Light* outLight = outNode->CreateComponent<Light>();
1749             outLight->SetColor(Color(light->mColorDiffuse.r, light->mColorDiffuse.g, light->mColorDiffuse.b));
1750 
1751             switch (light->mType)
1752             {
1753             case aiLightSource_DIRECTIONAL:
1754                 outLight->SetLightType(LIGHT_DIRECTIONAL);
1755                 break;
1756             case aiLightSource_SPOT:
1757                 outLight->SetLightType(LIGHT_SPOT);
1758                 outLight->SetFov(light->mAngleOuterCone * 0.5f * M_RADTODEG);
1759                 break;
1760             case aiLightSource_POINT:
1761                 outLight->SetLightType(LIGHT_POINT);
1762                 break;
1763             default:
1764                 break;
1765             }
1766 
1767             // Calculate range from attenuation parameters so that light intensity has been reduced to 10% at that distance
1768             if (light->mType != aiLightSource_DIRECTIONAL)
1769             {
1770                 float a = light->mAttenuationQuadratic;
1771                 float b = light->mAttenuationLinear;
1772                 float c = -10.0f;
1773                 if (!Equals(a, 0.0f))
1774                 {
1775                     float root1 = (-b + sqrtf(b * b - 4.0f * a * c)) / (2.0f * a);
1776                     float root2 = (-b - sqrtf(b * b - 4.0f * a * c)) / (2.0f * a);
1777                     outLight->SetRange(Max(root1, root2));
1778                 }
1779                 else if (!Equals(b, 0.0f))
1780                     outLight->SetRange(-c / b);
1781             }
1782         }
1783     }
1784 
1785     File file(context_);
1786     if (!file.Open(scene.outName_, FILE_WRITE))
1787         ErrorExit("Could not open output file " + scene.outName_);
1788     if (!asPrefab)
1789     {
1790         if (saveBinary_)
1791             outScene->Save(file);
1792         else if (saveJson_)
1793             outScene->SaveJSON(file);
1794         else
1795             outScene->SaveXML(file);
1796     }
1797     else
1798     {
1799         if (saveBinary_)
1800             outRootNode->Save(file);
1801         else if (saveJson_)
1802             outRootNode->SaveJSON(file);
1803         else
1804             outRootNode->SaveXML(file);
1805     }
1806 }
1807 
ExportMaterials(HashSet<String> & usedTextures)1808 void ExportMaterials(HashSet<String>& usedTextures)
1809 {
1810     if (useSubdirs_)
1811         context_->GetSubsystem<FileSystem>()->CreateDir(resourcePath_ + "Materials");
1812 
1813     for (unsigned i = 0; i < scene_->mNumMaterials; ++i)
1814         BuildAndSaveMaterial(scene_->mMaterials[i], usedTextures);
1815 }
1816 
BuildAndSaveMaterial(aiMaterial * material,HashSet<String> & usedTextures)1817 void BuildAndSaveMaterial(aiMaterial* material, HashSet<String>& usedTextures)
1818 {
1819     aiString matNameStr;
1820     material->Get(AI_MATKEY_NAME, matNameStr);
1821     String matName = SanitateAssetName(FromAIString(matNameStr));
1822     if (matName.Trimmed().Empty())
1823         matName = GenerateMaterialName(material);
1824 
1825     // Do not actually create a material instance, but instead craft an xml file manually
1826     XMLFile outMaterial(context_);
1827     XMLElement materialElem = outMaterial.CreateRoot("material");
1828 
1829     String diffuseTexName;
1830     String normalTexName;
1831     String specularTexName;
1832     String lightmapTexName;
1833     String emissiveTexName;
1834     Color diffuseColor = Color::WHITE;
1835     Color specularColor;
1836     Color emissiveColor = Color::BLACK;
1837     bool hasAlpha = false;
1838     bool twoSided = false;
1839     float specPower = 1.0f;
1840 
1841     aiString stringVal;
1842     float floatVal;
1843     int intVal;
1844     aiColor3D colorVal;
1845 
1846     if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0), stringVal) == AI_SUCCESS)
1847         diffuseTexName = GetFileNameAndExtension(FromAIString(stringVal));
1848     if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0), stringVal) == AI_SUCCESS)
1849         normalTexName = GetFileNameAndExtension(FromAIString(stringVal));
1850     if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_SPECULAR, 0), stringVal) == AI_SUCCESS)
1851         specularTexName = GetFileNameAndExtension(FromAIString(stringVal));
1852     if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, 0), stringVal) == AI_SUCCESS)
1853         lightmapTexName = GetFileNameAndExtension(FromAIString(stringVal));
1854     if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_EMISSIVE, 0), stringVal) == AI_SUCCESS)
1855         emissiveTexName = GetFileNameAndExtension(FromAIString(stringVal));
1856     if (!noMaterialDiffuseColor_)
1857     {
1858         if (material->Get(AI_MATKEY_COLOR_DIFFUSE, colorVal) == AI_SUCCESS)
1859             diffuseColor = Color(colorVal.r, colorVal.g, colorVal.b);
1860     }
1861     if (material->Get(AI_MATKEY_COLOR_SPECULAR, colorVal) == AI_SUCCESS)
1862         specularColor = Color(colorVal.r, colorVal.g, colorVal.b);
1863     if (!emissiveAO_)
1864     {
1865         if (material->Get(AI_MATKEY_COLOR_EMISSIVE, colorVal) == AI_SUCCESS)
1866             emissiveColor = Color(colorVal.r, colorVal.g, colorVal.b);
1867     }
1868     if (material->Get(AI_MATKEY_OPACITY, floatVal) == AI_SUCCESS)
1869     {
1870         /// \hack New Assimp behavior - some materials may return 0 opacity, which is invisible.
1871         /// Revert to full opacity in that case
1872         if (floatVal < M_EPSILON)
1873             floatVal = 1.0f;
1874 
1875         if (floatVal < 1.0f)
1876             hasAlpha = true;
1877         diffuseColor.a_ = floatVal;
1878     }
1879     if (material->Get(AI_MATKEY_SHININESS, floatVal) == AI_SUCCESS)
1880         specPower = floatVal;
1881     if (material->Get(AI_MATKEY_TWOSIDED, intVal) == AI_SUCCESS)
1882         twoSided = (intVal != 0);
1883 
1884     String techniqueName = "Techniques/NoTexture";
1885     if (!diffuseTexName.Empty())
1886     {
1887         techniqueName = "Techniques/Diff";
1888         if (!normalTexName.Empty())
1889             techniqueName += "Normal";
1890         if (!specularTexName.Empty())
1891             techniqueName += "Spec";
1892         // For now lightmap does not coexist with normal & specular
1893         if (normalTexName.Empty() && specularTexName.Empty() && !lightmapTexName.Empty())
1894             techniqueName += "LightMap";
1895         if (lightmapTexName.Empty() && !emissiveTexName.Empty())
1896             techniqueName += emissiveAO_ ? "AO" : "Emissive";
1897     }
1898     if (hasAlpha)
1899         techniqueName += "Alpha";
1900 
1901     XMLElement techniqueElem = materialElem.CreateChild("technique");
1902     techniqueElem.SetString("name", techniqueName + ".xml");
1903 
1904     if (!diffuseTexName.Empty())
1905     {
1906         XMLElement diffuseElem = materialElem.CreateChild("texture");
1907         diffuseElem.SetString("unit", "diffuse");
1908         diffuseElem.SetString("name", GetMaterialTextureName(diffuseTexName));
1909         usedTextures.Insert(diffuseTexName);
1910     }
1911     if (!normalTexName.Empty())
1912     {
1913         XMLElement normalElem = materialElem.CreateChild("texture");
1914         normalElem.SetString("unit", "normal");
1915         normalElem.SetString("name", GetMaterialTextureName(normalTexName));
1916         usedTextures.Insert(normalTexName);
1917     }
1918     if (!specularTexName.Empty())
1919     {
1920         XMLElement specularElem = materialElem.CreateChild("texture");
1921         specularElem.SetString("unit", "specular");
1922         specularElem.SetString("name", GetMaterialTextureName(specularTexName));
1923         usedTextures.Insert(specularTexName);
1924     }
1925     if (!lightmapTexName.Empty())
1926     {
1927         XMLElement lightmapElem = materialElem.CreateChild("texture");
1928         lightmapElem.SetString("unit", "emissive");
1929         lightmapElem.SetString("name", GetMaterialTextureName(lightmapTexName));
1930         usedTextures.Insert(lightmapTexName);
1931     }
1932     if (!emissiveTexName.Empty())
1933     {
1934         XMLElement emissiveElem = materialElem.CreateChild("texture");
1935         emissiveElem.SetString("unit", "emissive");
1936         emissiveElem.SetString("name", GetMaterialTextureName(emissiveTexName));
1937         usedTextures.Insert(emissiveTexName);
1938     }
1939 
1940     XMLElement diffuseColorElem = materialElem.CreateChild("parameter");
1941     diffuseColorElem.SetString("name", "MatDiffColor");
1942     diffuseColorElem.SetColor("value", diffuseColor);
1943     XMLElement specularElem = materialElem.CreateChild("parameter");
1944     specularElem.SetString("name", "MatSpecColor");
1945     specularElem.SetVector4("value", Vector4(specularColor.r_, specularColor.g_, specularColor.b_, specPower));
1946     XMLElement emissiveColorElem = materialElem.CreateChild("parameter");
1947     emissiveColorElem.SetString("name", "MatEmissiveColor");
1948     emissiveColorElem.SetColor("value", emissiveColor);
1949 
1950     if (twoSided)
1951     {
1952         XMLElement cullElem = materialElem.CreateChild("cull");
1953         XMLElement shadowCullElem = materialElem.CreateChild("shadowcull");
1954         cullElem.SetString("value", "none");
1955         shadowCullElem.SetString("value", "none");
1956     }
1957 
1958     FileSystem* fileSystem = context_->GetSubsystem<FileSystem>();
1959 
1960     String outFileName = resourcePath_ + (useSubdirs_ ? "Materials/" : "" ) + matName + ".xml";
1961     if (noOverwriteMaterial_ && fileSystem->FileExists(outFileName))
1962     {
1963         PrintLine("Skipping save of existing material " + matName);
1964         return;
1965     }
1966 
1967     PrintLine("Writing material " + matName);
1968 
1969     File outFile(context_);
1970     if (!outFile.Open(outFileName, FILE_WRITE))
1971         ErrorExit("Could not open output file " + outFileName);
1972     outMaterial.Save(outFile);
1973 }
1974 
CopyTextures(const HashSet<String> & usedTextures,const String & sourcePath)1975 void CopyTextures(const HashSet<String>& usedTextures, const String& sourcePath)
1976 {
1977     FileSystem* fileSystem = context_->GetSubsystem<FileSystem>();
1978 
1979     if (useSubdirs_)
1980         fileSystem->CreateDir(resourcePath_ + "Textures");
1981 
1982     for (HashSet<String>::ConstIterator i = usedTextures.Begin(); i != usedTextures.End(); ++i)
1983     {
1984         // Handle assimp embedded textures
1985         if (i->Length() && i->At(0) == '*')
1986         {
1987             unsigned texIndex = ToInt(i->Substring(1));
1988             if (texIndex >= scene_->mNumTextures)
1989                 PrintLine("Skipping out of range texture index " + String(texIndex));
1990             else
1991             {
1992                 aiTexture* tex = scene_->mTextures[texIndex];
1993                 String fullDestName = resourcePath_ + GenerateTextureName(texIndex);
1994                 bool destExists = fileSystem->FileExists(fullDestName);
1995                 if (destExists && noOverwriteTexture_)
1996                 {
1997                     PrintLine("Skipping copy of existing embedded texture " + GetFileNameAndExtension(fullDestName));
1998                     continue;
1999                 }
2000                 // Encoded texture
2001                 if (!tex->mHeight)
2002                 {
2003                     PrintLine("Saving embedded texture " + GetFileNameAndExtension(fullDestName));
2004                     File dest(context_, fullDestName, FILE_WRITE);
2005                     dest.Write((const void*)tex->pcData, tex->mWidth);
2006                 }
2007                 // RGBA8 texture
2008                 else
2009                 {
2010                     PrintLine("Saving embedded RGBA texture " + GetFileNameAndExtension(fullDestName));
2011                     Image image(context_);
2012                     image.SetSize(tex->mWidth, tex->mHeight, 4);
2013                     memcpy(image.GetData(), (const void*)tex->pcData, tex->mWidth * tex->mHeight * 4);
2014                     image.SavePNG(fullDestName);
2015                 }
2016             }
2017         }
2018         else
2019         {
2020             String fullSourceName = sourcePath + *i;
2021             String fullDestName = resourcePath_ + (useSubdirs_ ? "Textures/" : "") + *i;
2022 
2023             if (!fileSystem->FileExists(fullSourceName))
2024             {
2025                 PrintLine("Skipping copy of nonexisting material texture " + *i);
2026                 continue;
2027             }
2028             {
2029                 File test(context_, fullSourceName);
2030                 if (!test.GetSize())
2031                 {
2032                     PrintLine("Skipping copy of zero-size material texture " + *i);
2033                     continue;
2034                 }
2035             }
2036 
2037             bool destExists = fileSystem->FileExists(fullDestName);
2038             if (destExists && noOverwriteTexture_)
2039             {
2040                 PrintLine("Skipping copy of existing texture " + *i);
2041                 continue;
2042             }
2043             if (destExists && noOverwriteNewerTexture_ && fileSystem->GetLastModifiedTime(fullDestName) >
2044                 fileSystem->GetLastModifiedTime(fullSourceName))
2045             {
2046                 PrintLine("Skipping copying of material texture " + *i + ", destination is newer");
2047                 continue;
2048             }
2049 
2050             PrintLine("Copying material texture " + *i);
2051             fileSystem->Copy(fullSourceName, fullDestName);
2052         }
2053     }
2054 }
2055 
CombineLods(const PODVector<float> & lodDistances,const Vector<String> & modelNames,const String & outName)2056 void CombineLods(const PODVector<float>& lodDistances, const Vector<String>& modelNames, const String& outName)
2057 {
2058     // Load models
2059     Vector<SharedPtr<Model> > srcModels;
2060     for (unsigned i = 0; i < modelNames.Size(); ++i)
2061     {
2062         PrintLine("Reading LOD level " + String(i) + ": model " + modelNames[i] + " distance " + String(lodDistances[i]));
2063         File srcFile(context_);
2064         srcFile.Open(modelNames[i]);
2065         SharedPtr<Model> srcModel(new Model(context_));
2066         if (!srcModel->Load(srcFile))
2067             ErrorExit("Could not load input model " + modelNames[i]);
2068         srcModels.Push(srcModel);
2069     }
2070 
2071     // Check that none of the models already has LOD levels
2072     for (unsigned i = 0; i < srcModels.Size(); ++i)
2073     {
2074         for (unsigned j = 0; j < srcModels[i]->GetNumGeometries(); ++j)
2075         {
2076             if (srcModels[i]->GetNumGeometryLodLevels(j) > 1)
2077                 ErrorExit(modelNames[i] + " already has multiple LOD levels defined");
2078         }
2079     }
2080 
2081     // Check for number of geometries (need to have same amount for now)
2082     for (unsigned i = 1; i < srcModels.Size(); ++i)
2083     {
2084         if (srcModels[i]->GetNumGeometries() != srcModels[0]->GetNumGeometries())
2085             ErrorExit(modelNames[i] + " has different amount of geometries than " + modelNames[0]);
2086     }
2087 
2088     // If there are bones, check for compatibility (need to have exact match for now)
2089     for (unsigned i = 1; i < srcModels.Size(); ++i)
2090     {
2091         if (srcModels[i]->GetSkeleton().GetNumBones() != srcModels[0]->GetSkeleton().GetNumBones())
2092             ErrorExit(modelNames[i] + " has different amount of bones than " + modelNames[0]);
2093         for (unsigned j = 0; j < srcModels[0]->GetSkeleton().GetNumBones(); ++j)
2094         {
2095             if (srcModels[i]->GetSkeleton().GetBone(j)->name_ != srcModels[0]->GetSkeleton().GetBone(j)->name_)
2096                 ErrorExit(modelNames[i] + " has different bones than " + modelNames[0]);
2097         }
2098         if (srcModels[i]->GetGeometryBoneMappings() != srcModels[0]->GetGeometryBoneMappings())
2099             ErrorExit(modelNames[i] + " has different per-geometry bone mappings than " + modelNames[0]);
2100     }
2101 
2102     Vector<SharedPtr<VertexBuffer> > vbVector;
2103     Vector<SharedPtr<IndexBuffer> > ibVector;
2104     PODVector<unsigned> emptyMorphRange;
2105 
2106     // Create the final model
2107     SharedPtr<Model> outModel(new Model(context_));
2108     outModel->SetNumGeometries(srcModels[0]->GetNumGeometries());
2109     for (unsigned i = 0; i < srcModels[0]->GetNumGeometries(); ++i)
2110     {
2111         outModel->SetNumGeometryLodLevels(i, srcModels.Size());
2112         for (unsigned j = 0; j < srcModels.Size(); ++j)
2113         {
2114             Geometry* geometry = srcModels[j]->GetGeometry(i, 0);
2115             geometry->SetLodDistance(lodDistances[j]);
2116             outModel->SetGeometry(i, j, geometry);
2117 
2118             for (unsigned k = 0; k < geometry->GetNumVertexBuffers(); ++k)
2119             {
2120                 SharedPtr<VertexBuffer> vb(geometry->GetVertexBuffer(k));
2121                 if (!vbVector.Contains(vb))
2122                     vbVector.Push(vb);
2123             }
2124 
2125             SharedPtr<IndexBuffer> ib(geometry->GetIndexBuffer());
2126             if (!ibVector.Contains(ib))
2127                 ibVector.Push(ib);
2128         }
2129     }
2130 
2131     outModel->SetVertexBuffers(vbVector, emptyMorphRange, emptyMorphRange);
2132     outModel->SetIndexBuffers(ibVector);
2133     outModel->SetSkeleton(srcModels[0]->GetSkeleton());
2134     outModel->SetGeometryBoneMappings(srcModels[0]->GetGeometryBoneMappings());
2135     outModel->SetBoundingBox(srcModels[0]->GetBoundingBox());
2136     /// \todo Vertex morphs are ignored for now
2137 
2138     // Save the final model
2139     PrintLine("Writing output model");
2140     File outFile(context_);
2141     if (!outFile.Open(outName, FILE_WRITE))
2142         ErrorExit("Could not open output file " + outName);
2143     outModel->Save(outFile);
2144 }
2145 
GetMeshesUnderNode(Vector<Pair<aiNode *,aiMesh * >> & dest,aiNode * node)2146 void GetMeshesUnderNode(Vector<Pair<aiNode*, aiMesh*> >& dest, aiNode* node)
2147 {
2148     for (unsigned i = 0; i < node->mNumMeshes; ++i)
2149         dest.Push(MakePair(node, scene_->mMeshes[node->mMeshes[i]]));
2150 }
2151 
GetMeshIndex(aiMesh * mesh)2152 unsigned GetMeshIndex(aiMesh* mesh)
2153 {
2154     for (unsigned i = 0; i < scene_->mNumMeshes; ++i)
2155     {
2156         if (scene_->mMeshes[i] == mesh)
2157             return i;
2158     }
2159     return M_MAX_UNSIGNED;
2160 }
2161 
GetBoneIndex(OutModel & model,const String & boneName)2162 unsigned GetBoneIndex(OutModel& model, const String& boneName)
2163 {
2164     for (unsigned i = 0; i < model.bones_.Size(); ++i)
2165     {
2166         if (boneName == model.bones_[i]->mName.data)
2167             return i;
2168     }
2169     return M_MAX_UNSIGNED;
2170 }
2171 
GetMeshBone(OutModel & model,const String & boneName)2172 aiBone* GetMeshBone(OutModel& model, const String& boneName)
2173 {
2174     for (unsigned i = 0; i < model.meshes_.Size(); ++i)
2175     {
2176         aiMesh* mesh = model.meshes_[i];
2177         for (unsigned j = 0; j < mesh->mNumBones; ++j)
2178         {
2179             aiBone* bone = mesh->mBones[j];
2180             if (boneName == bone->mName.data)
2181                 return bone;
2182         }
2183     }
2184     return 0;
2185 }
2186 
GetOffsetMatrix(OutModel & model,const String & boneName)2187 Matrix3x4 GetOffsetMatrix(OutModel& model, const String& boneName)
2188 {
2189     for (unsigned i = 0; i < model.meshes_.Size(); ++i)
2190     {
2191         aiMesh* mesh = model.meshes_[i];
2192         aiNode* node = model.meshNodes_[i];
2193         for (unsigned j = 0; j < mesh->mNumBones; ++j)
2194         {
2195             aiBone* bone = mesh->mBones[j];
2196             if (boneName == bone->mName.data)
2197             {
2198                 aiMatrix4x4 offset = bone->mOffsetMatrix;
2199                 aiMatrix4x4 nodeDerivedInverse = GetMeshBakingTransform(node, model.rootNode_);
2200                 nodeDerivedInverse.Inverse();
2201                 offset *= nodeDerivedInverse;
2202                 return ToMatrix3x4(offset);
2203             }
2204         }
2205     }
2206 
2207     // Fallback for rigid skinning for which actual offset matrix information doesn't exist
2208     for (unsigned i = 0; i < model.meshes_.Size(); ++i)
2209     {
2210         aiMesh* mesh = model.meshes_[i];
2211         aiNode* node = model.meshNodes_[i];
2212         if (!mesh->HasBones() && boneName == node->mName.data)
2213         {
2214             aiMatrix4x4 nodeDerivedInverse = GetMeshBakingTransform(node, model.rootNode_);
2215             nodeDerivedInverse.Inverse();
2216             return ToMatrix3x4(nodeDerivedInverse);
2217         }
2218     }
2219 
2220     return Matrix3x4::IDENTITY;
2221 }
2222 
GetBlendData(OutModel & model,aiMesh * mesh,aiNode * meshNode,PODVector<unsigned> & boneMappings,Vector<PODVector<unsigned char>> & blendIndices,Vector<PODVector<float>> & blendWeights)2223 void GetBlendData(OutModel& model, aiMesh* mesh, aiNode* meshNode, PODVector<unsigned>& boneMappings, Vector<PODVector<unsigned char> >&
2224     blendIndices, Vector<PODVector<float> >& blendWeights)
2225 {
2226     blendIndices.Resize(mesh->mNumVertices);
2227     blendWeights.Resize(mesh->mNumVertices);
2228     boneMappings.Clear();
2229 
2230     // If model has more bones than can fit vertex shader parameters, write the per-geometry mappings
2231     if (model.bones_.Size() > maxBones_)
2232     {
2233         if (mesh->mNumBones > maxBones_)
2234         {
2235             ErrorExit(
2236                 "Geometry (submesh) has over " + String(maxBones_) + " bone influences. Try splitting to more submeshes\n"
2237                 "that each stay at " + String(maxBones_) + " bones or below."
2238             );
2239         }
2240         if (mesh->mNumBones > 0)
2241         {
2242             boneMappings.Resize(mesh->mNumBones);
2243             for (unsigned i = 0; i < mesh->mNumBones; ++i)
2244             {
2245                 aiBone* bone = mesh->mBones[i];
2246                 String boneName = FromAIString(bone->mName);
2247                 unsigned globalIndex = GetBoneIndex(model, boneName);
2248                 if (globalIndex == M_MAX_UNSIGNED)
2249                     ErrorExit("Bone " + boneName + " not found");
2250                 boneMappings[i] = globalIndex;
2251                 for (unsigned j = 0; j < bone->mNumWeights; ++j)
2252                 {
2253                     unsigned vertex = bone->mWeights[j].mVertexId;
2254                     blendIndices[vertex].Push(i);
2255                     blendWeights[vertex].Push(bone->mWeights[j].mWeight);
2256                 }
2257             }
2258         }
2259         else
2260         {
2261             // If mesh does not have skinning information, implement rigid skinning so that it stays compatible with AnimatedModel
2262             String boneName = FromAIString(meshNode->mName);
2263             unsigned globalIndex = GetBoneIndex(model, boneName);
2264             if (globalIndex == M_MAX_UNSIGNED)
2265                 PrintLine("Warning: bone " + boneName + " not found, skipping rigid skinning");
2266             else
2267             {
2268                 boneMappings.Push(globalIndex);
2269                 for (unsigned i = 0; i < mesh->mNumVertices; ++i)
2270                 {
2271                     blendIndices[i].Push(0);
2272                     blendWeights[i].Push(1.0f);
2273                 }
2274             }
2275         }
2276     }
2277     else
2278     {
2279         if (mesh->mNumBones > 0)
2280         {
2281             for (unsigned i = 0; i < mesh->mNumBones; ++i)
2282             {
2283                 aiBone* bone = mesh->mBones[i];
2284                 String boneName = FromAIString(bone->mName);
2285                 unsigned globalIndex = GetBoneIndex(model, boneName);
2286                 if (globalIndex == M_MAX_UNSIGNED)
2287                     ErrorExit("Bone " + boneName + " not found");
2288                 for (unsigned j = 0; j < bone->mNumWeights; ++j)
2289                 {
2290                     unsigned vertex = bone->mWeights[j].mVertexId;
2291                     blendIndices[vertex].Push(globalIndex);
2292                     blendWeights[vertex].Push(bone->mWeights[j].mWeight);
2293                 }
2294             }
2295         }
2296         else
2297         {
2298             String boneName = FromAIString(meshNode->mName);
2299             unsigned globalIndex = GetBoneIndex(model, boneName);
2300             if (globalIndex == M_MAX_UNSIGNED)
2301                 PrintLine("Warning: bone " + boneName + " not found, skipping rigid skinning");
2302             else
2303             {
2304                 for (unsigned i = 0; i < mesh->mNumVertices; ++i)
2305                 {
2306                     blendIndices[i].Push(globalIndex);
2307                     blendWeights[i].Push(1.0f);
2308                 }
2309             }
2310         }
2311     }
2312 
2313     // Normalize weights now if necessary, also remove too many influences
2314     for (unsigned i = 0; i < blendWeights.Size(); ++i)
2315     {
2316         if (blendWeights[i].Size() > 4)
2317         {
2318             PrintLine("Warning: more than 4 bone influences in vertex " + String(i));
2319 
2320             while (blendWeights[i].Size() > 4)
2321             {
2322                 unsigned lowestIndex = 0;
2323                 float lowest = M_INFINITY;
2324                 for (unsigned j = 0; j < blendWeights[i].Size(); ++j)
2325                 {
2326                     if (blendWeights[i][j] < lowest)
2327                     {
2328                         lowest = blendWeights[i][j];
2329                         lowestIndex = j;
2330                     }
2331                 }
2332                 blendWeights[i].Erase(lowestIndex);
2333                 blendIndices[i].Erase(lowestIndex);
2334             }
2335         }
2336 
2337         float sum = 0.0f;
2338         for (unsigned j = 0; j < blendWeights[i].Size(); ++j)
2339             sum += blendWeights[i][j];
2340         if (sum != 1.0f && sum != 0.0f)
2341         {
2342             for (unsigned j = 0; j < blendWeights[i].Size(); ++j)
2343                 blendWeights[i][j] /= sum;
2344         }
2345     }
2346 }
2347 
GetMeshMaterialName(aiMesh * mesh)2348 String GetMeshMaterialName(aiMesh* mesh)
2349 {
2350     aiMaterial* material = scene_->mMaterials[mesh->mMaterialIndex];
2351     aiString matNameStr;
2352     material->Get(AI_MATKEY_NAME, matNameStr);
2353     String matName = SanitateAssetName(FromAIString(matNameStr));
2354     if (matName.Trimmed().Empty())
2355         matName = GenerateMaterialName(material);
2356 
2357     return (useSubdirs_ ? "Materials/" : "") + matName + ".xml";
2358 }
2359 
GenerateMaterialName(aiMaterial * material)2360 String GenerateMaterialName(aiMaterial* material)
2361 {
2362     for (unsigned i = 0; i < scene_->mNumMaterials; ++i)
2363     {
2364         if (scene_->mMaterials[i] == material)
2365             return inputName_ + "_Material" + String(i);
2366     }
2367 
2368     // Should not go here
2369     return String::EMPTY;
2370 }
2371 
GetMaterialTextureName(const String & nameIn)2372 String GetMaterialTextureName(const String& nameIn)
2373 {
2374     // Detect assimp embedded texture
2375     if (nameIn.Length() && nameIn[0] == '*')
2376         return GenerateTextureName(ToInt(nameIn.Substring(1)));
2377     else
2378         return (useSubdirs_ ? "Textures/" : "") + nameIn;
2379 }
2380 
GenerateTextureName(unsigned texIndex)2381 String GenerateTextureName(unsigned texIndex)
2382 {
2383     if (texIndex < scene_->mNumTextures)
2384     {
2385         // If embedded texture contains encoded data, use the format hint for file extension. Else save RGBA8 data as PNG
2386         aiTexture* tex = scene_->mTextures[texIndex];
2387         if (!tex->mHeight)
2388             return (useSubdirs_ ? "Textures/" : "") + inputName_ + "_Texture" + String(texIndex) + "." + tex->achFormatHint;
2389         else
2390             return (useSubdirs_ ? "Textures/" : "") + inputName_ + "_Texture" + String(texIndex) + ".png";
2391     }
2392 
2393     // Should not go here
2394     return String::EMPTY;
2395 }
2396 
GetNumValidFaces(aiMesh * mesh)2397 unsigned GetNumValidFaces(aiMesh* mesh)
2398 {
2399     unsigned ret = 0;
2400 
2401     for (unsigned j = 0; j < mesh->mNumFaces; ++j)
2402     {
2403         if (mesh->mFaces[j].mNumIndices == 3)
2404             ++ret;
2405     }
2406 
2407     return ret;
2408 }
2409 
WriteShortIndices(unsigned short * & dest,aiMesh * mesh,unsigned index,unsigned offset)2410 void WriteShortIndices(unsigned short*& dest, aiMesh* mesh, unsigned index, unsigned offset)
2411 {
2412     if (mesh->mFaces[index].mNumIndices == 3)
2413     {
2414         *dest++ = mesh->mFaces[index].mIndices[0] + offset;
2415         *dest++ = mesh->mFaces[index].mIndices[1] + offset;
2416         *dest++ = mesh->mFaces[index].mIndices[2] + offset;
2417     }
2418 }
2419 
WriteLargeIndices(unsigned * & dest,aiMesh * mesh,unsigned index,unsigned offset)2420 void WriteLargeIndices(unsigned*& dest, aiMesh* mesh, unsigned index, unsigned offset)
2421 {
2422     if (mesh->mFaces[index].mNumIndices == 3)
2423     {
2424         *dest++ = mesh->mFaces[index].mIndices[0] + offset;
2425         *dest++ = mesh->mFaces[index].mIndices[1] + offset;
2426         *dest++ = mesh->mFaces[index].mIndices[2] + offset;
2427     }
2428 }
2429 
WriteVertex(float * & dest,aiMesh * mesh,unsigned index,bool isSkinned,BoundingBox & box,const Matrix3x4 & vertexTransform,const Matrix3 & normalTransform,Vector<PODVector<unsigned char>> & blendIndices,Vector<PODVector<float>> & blendWeights)2430 void WriteVertex(float*& dest, aiMesh* mesh, unsigned index, bool isSkinned, BoundingBox& box,
2431     const Matrix3x4& vertexTransform, const Matrix3& normalTransform, Vector<PODVector<unsigned char> >& blendIndices,
2432     Vector<PODVector<float> >& blendWeights)
2433 {
2434     Vector3 vertex = vertexTransform * ToVector3(mesh->mVertices[index]);
2435     box.Merge(vertex);
2436     *dest++ = vertex.x_;
2437     *dest++ = vertex.y_;
2438     *dest++ = vertex.z_;
2439 
2440     if (mesh->HasNormals())
2441     {
2442         Vector3 normal = normalTransform * ToVector3(mesh->mNormals[index]);
2443         *dest++ = normal.x_;
2444         *dest++ = normal.y_;
2445         *dest++ = normal.z_;
2446     }
2447 
2448     for (unsigned i = 0; i < mesh->GetNumColorChannels() && i < MAX_CHANNELS; ++i)
2449     {
2450         *((unsigned*)dest) = Color(mesh->mColors[i][index].r, mesh->mColors[i][index].g, mesh->mColors[i][index].b,
2451             mesh->mColors[i][index].a).ToUInt();
2452         ++dest;
2453     }
2454 
2455     for (unsigned i = 0; i < mesh->GetNumUVChannels() && i < MAX_CHANNELS; ++i)
2456     {
2457         Vector3 texCoord = ToVector3(mesh->mTextureCoords[i][index]);
2458         *dest++ = texCoord.x_;
2459         *dest++ = texCoord.y_;
2460     }
2461 
2462     if (mesh->HasTangentsAndBitangents())
2463     {
2464         Vector3 tangent = normalTransform * ToVector3(mesh->mTangents[index]);
2465         Vector3 normal = normalTransform * ToVector3(mesh->mNormals[index]);
2466         Vector3 bitangent = normalTransform * ToVector3(mesh->mBitangents[index]);
2467         // Check handedness
2468         float w = 1.0f;
2469         if ((tangent.CrossProduct(normal)).DotProduct(bitangent) < 0.5f)
2470             w = -1.0f;
2471 
2472         *dest++ = tangent.x_;
2473         *dest++ = tangent.y_;
2474         *dest++ = tangent.z_;
2475         *dest++ = w;
2476     }
2477 
2478     if (isSkinned)
2479     {
2480         for (unsigned i = 0; i < 4; ++i)
2481         {
2482             if (i < blendWeights[index].Size())
2483                 *dest++ = blendWeights[index][i];
2484             else
2485                 *dest++ = 0.0f;
2486         }
2487 
2488         unsigned char* destBytes = (unsigned char*)dest;
2489         ++dest;
2490         for (unsigned i = 0; i < 4; ++i)
2491         {
2492             if (i < blendIndices[index].Size())
2493                 *destBytes++ = blendIndices[index][i];
2494             else
2495                 *destBytes++ = 0;
2496         }
2497     }
2498 }
2499 
GetVertexElements(aiMesh * mesh,bool isSkinned)2500 PODVector<VertexElement> GetVertexElements(aiMesh* mesh, bool isSkinned)
2501 {
2502     PODVector<VertexElement> ret;
2503 
2504     // Position must always be first and of type Vector3 for raycasts to work
2505     ret.Push(VertexElement(TYPE_VECTOR3, SEM_POSITION));
2506 
2507     if (mesh->HasNormals())
2508         ret.Push(VertexElement(TYPE_VECTOR3, SEM_NORMAL));
2509 
2510     for (unsigned i = 0; i < mesh->GetNumColorChannels() && i < MAX_CHANNELS; ++i)
2511         ret.Push(VertexElement(TYPE_UBYTE4_NORM, SEM_COLOR, i));
2512 
2513     /// \todo Assimp mesh structure can specify 3D UV-coords. How to determine the difference? For now always treated as 2D.
2514     for (unsigned i = 0; i < mesh->GetNumUVChannels() && i < MAX_CHANNELS; ++i)
2515         ret.Push(VertexElement(TYPE_VECTOR2, SEM_TEXCOORD, i));
2516 
2517     if (mesh->HasTangentsAndBitangents())
2518         ret.Push(VertexElement(TYPE_VECTOR4, SEM_TANGENT));
2519 
2520     if (isSkinned)
2521     {
2522         ret.Push(VertexElement(TYPE_VECTOR4, SEM_BLENDWEIGHTS));
2523         ret.Push(VertexElement(TYPE_UBYTE4, SEM_BLENDINDICES));
2524     }
2525 
2526     return ret;
2527 }
2528 
GetNode(const String & name,aiNode * rootNode,bool caseSensitive)2529 aiNode* GetNode(const String& name, aiNode* rootNode, bool caseSensitive)
2530 {
2531     if (!rootNode)
2532         return 0;
2533     if (!name.Compare(rootNode->mName.data, caseSensitive))
2534         return rootNode;
2535     for (unsigned i = 0; i < rootNode->mNumChildren; ++i)
2536     {
2537         aiNode* found = GetNode(name, rootNode->mChildren[i], caseSensitive);
2538         if (found)
2539             return found;
2540     }
2541     return 0;
2542 }
2543 
GetDerivedTransform(aiNode * node,aiNode * rootNode,bool rootInclusive)2544 aiMatrix4x4 GetDerivedTransform(aiNode* node, aiNode* rootNode, bool rootInclusive)
2545 {
2546     return GetDerivedTransform(node->mTransformation, node, rootNode, rootInclusive);
2547 }
2548 
GetDerivedTransform(aiMatrix4x4 transform,aiNode * node,aiNode * rootNode,bool rootInclusive)2549 aiMatrix4x4 GetDerivedTransform(aiMatrix4x4 transform, aiNode* node, aiNode* rootNode, bool rootInclusive)
2550 {
2551     // If basenode is defined, go only up to it in the parent chain
2552     while (node && node != rootNode)
2553     {
2554         node = node->mParent;
2555         if (!rootInclusive && node == rootNode)
2556             break;
2557         if (node)
2558             transform = node->mTransformation * transform;
2559     }
2560     return transform;
2561 }
2562 
GetMeshBakingTransform(aiNode * meshNode,aiNode * modelRootNode)2563 aiMatrix4x4 GetMeshBakingTransform(aiNode* meshNode, aiNode* modelRootNode)
2564 {
2565     if (meshNode == modelRootNode)
2566         return aiMatrix4x4();
2567     else
2568         return GetDerivedTransform(meshNode, modelRootNode);
2569 }
2570 
GetPosRotScale(const aiMatrix4x4 & transform,Vector3 & pos,Quaternion & rot,Vector3 & scale)2571 void GetPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot, Vector3& scale)
2572 {
2573     aiVector3D aiPos;
2574     aiQuaternion aiRot;
2575     aiVector3D aiScale;
2576     transform.Decompose(aiScale, aiRot, aiPos);
2577     pos = ToVector3(aiPos);
2578     rot = ToQuaternion(aiRot);
2579     scale = ToVector3(aiScale);
2580 }
2581 
2582 
FromAIString(const aiString & str)2583 String FromAIString(const aiString& str)
2584 {
2585     return String(str.data);
2586 }
2587 
ToVector3(const aiVector3D & vec)2588 Vector3 ToVector3(const aiVector3D& vec)
2589 {
2590     return Vector3(vec.x, vec.y, vec.z);
2591 }
2592 
ToVector2(const aiVector2D & vec)2593 Vector2 ToVector2(const aiVector2D& vec)
2594 {
2595     return Vector2(vec.x, vec.y);
2596 }
2597 
ToQuaternion(const aiQuaternion & quat)2598 Quaternion ToQuaternion(const aiQuaternion& quat)
2599 {
2600     return Quaternion(quat.w, quat.x, quat.y, quat.z);
2601 }
2602 
ToMatrix3x4(const aiMatrix4x4 & mat)2603 Matrix3x4 ToMatrix3x4(const aiMatrix4x4& mat)
2604 {
2605     Matrix3x4 ret;
2606     memcpy(&ret.m00_, &mat.a1, sizeof(Matrix3x4));
2607     return ret;
2608 }
2609 
ToAIMatrix4x4(const Matrix3x4 & mat)2610 aiMatrix4x4 ToAIMatrix4x4(const Matrix3x4& mat)
2611 {
2612     aiMatrix4x4 ret;
2613     memcpy(&ret.a1, &mat.m00_, sizeof(Matrix3x4));
2614     return ret;
2615 }
2616 
SanitateAssetName(const String & name)2617 String SanitateAssetName(const String& name)
2618 {
2619     String fixedName = name;
2620     fixedName.Replace("<", "");
2621     fixedName.Replace(">", "");
2622     fixedName.Replace("?", "");
2623     fixedName.Replace("*", "");
2624     fixedName.Replace(":", "");
2625     fixedName.Replace("\"", "");
2626     fixedName.Replace("/", "");
2627     fixedName.Replace("\\", "");
2628     fixedName.Replace("|", "");
2629 
2630     return fixedName;
2631 }
2632 
GetPivotlessBoneIndex(OutModel & model,const String & boneName)2633 unsigned GetPivotlessBoneIndex(OutModel& model, const String& boneName)
2634 {
2635     for (unsigned i = 0; i < model.pivotlessBones_.Size(); ++i)
2636     {
2637         if (boneName == model.pivotlessBones_[i]->mName.data)
2638             return i;
2639     }
2640     return M_MAX_UNSIGNED;
2641 }
2642 
FillChainTransforms(OutModel & model,aiMatrix4x4 * chain,const String & mainBoneName)2643 void FillChainTransforms(OutModel &model, aiMatrix4x4 *chain, const String& mainBoneName)
2644 {
2645     for (unsigned j = 0; j < TransformationComp_MAXIMUM; ++j)
2646     {
2647         String transfBoneName = mainBoneName + "_$AssimpFbx$_" + String(transformSuffix[j]);
2648 
2649         for (unsigned k = 0; k < model.bones_.Size(); ++k)
2650         {
2651             String boneName = String(model.bones_[k]->mName.data);
2652 
2653             if (boneName == transfBoneName)
2654             {
2655                 chain[j] = model.bones_[k]->mTransformation;
2656                 break;
2657             }
2658         }
2659     }
2660 }
2661 
ExpandAnimatedChannelKeys(aiAnimation * anim,unsigned mainChannel,int * channelIndices)2662 void ExpandAnimatedChannelKeys(aiAnimation* anim, unsigned mainChannel, int *channelIndices)
2663 {
2664     aiNodeAnim* channel = anim->mChannels[mainChannel];
2665     unsigned int poskeyFrames = channel->mNumPositionKeys;
2666     unsigned int rotkeyFrames = channel->mNumRotationKeys;
2667     unsigned int scalekeyFrames = channel->mNumScalingKeys;
2668 
2669     // Get max key frames
2670     for (unsigned i = 0; i < TransformationComp_MAXIMUM; ++i)
2671     {
2672         if (channelIndices[i] != -1 && channelIndices[i] != mainChannel)
2673         {
2674             aiNodeAnim* channel2 = anim->mChannels[channelIndices[i]];
2675 
2676             if (channel2->mNumPositionKeys > poskeyFrames)
2677                 poskeyFrames = channel2->mNumPositionKeys;
2678             if (channel2->mNumRotationKeys > rotkeyFrames)
2679                 rotkeyFrames = channel2->mNumRotationKeys;
2680             if (channel2->mNumScalingKeys  > scalekeyFrames)
2681                 scalekeyFrames = channel2->mNumScalingKeys;
2682         }
2683     }
2684 
2685     // Resize and init vector key array
2686     if (poskeyFrames > channel->mNumPositionKeys)
2687     {
2688         aiVectorKey* newKeys  = new aiVectorKey[poskeyFrames];
2689         for (unsigned i = 0; i < poskeyFrames; ++i)
2690         {
2691             if (i < channel->mNumPositionKeys )
2692                 newKeys[i] = aiVectorKey(channel->mPositionKeys[i].mTime, channel->mPositionKeys[i].mValue);
2693             else
2694                 newKeys[i].mValue = aiVector3D(0.0f, 0.0f, 0.0f);
2695         }
2696         delete[] channel->mPositionKeys;
2697         channel->mPositionKeys = newKeys;
2698         channel->mNumPositionKeys = poskeyFrames;
2699     }
2700     if (rotkeyFrames > channel->mNumRotationKeys)
2701     {
2702         aiQuatKey* newKeys  = new aiQuatKey[rotkeyFrames];
2703         for (unsigned i = 0; i < rotkeyFrames; ++i)
2704         {
2705             if (i < channel->mNumRotationKeys)
2706                 newKeys[i] = aiQuatKey(channel->mRotationKeys[i].mTime, channel->mRotationKeys[i].mValue);
2707             else
2708                 newKeys[i].mValue = aiQuaternion();
2709         }
2710         delete[] channel->mRotationKeys;
2711         channel->mRotationKeys = newKeys;
2712         channel->mNumRotationKeys = rotkeyFrames;
2713     }
2714     if (scalekeyFrames > channel->mNumScalingKeys)
2715     {
2716         aiVectorKey* newKeys  = new aiVectorKey[scalekeyFrames];
2717         for (unsigned i = 0; i < scalekeyFrames; ++i)
2718         {
2719             if ( i < channel->mNumScalingKeys)
2720                 newKeys[i] = aiVectorKey(channel->mScalingKeys[i].mTime, channel->mScalingKeys[i].mValue);
2721             else
2722                 newKeys[i].mValue = aiVector3D(1.0f, 1.0f, 1.0f);
2723         }
2724         delete[] channel->mScalingKeys;
2725         channel->mScalingKeys = newKeys;
2726         channel->mNumScalingKeys = scalekeyFrames;
2727     }
2728 }
2729 
InitAnimatedChainTransformIndices(aiAnimation * anim,unsigned mainChannel,const String & mainBoneName,int * channelIndices)2730 void InitAnimatedChainTransformIndices(aiAnimation* anim, unsigned mainChannel, const String& mainBoneName, int *channelIndices)
2731 {
2732     int numTransforms = 0;
2733 
2734     for (unsigned j = 0; j < TransformationComp_MAXIMUM; ++j)
2735     {
2736         String transfBoneName = mainBoneName + "_$AssimpFbx$_" + String(transformSuffix[j]);
2737         channelIndices[j] = -1;
2738 
2739         for (unsigned k = 0; k < anim->mNumChannels; ++k)
2740         {
2741             aiNodeAnim* channel = anim->mChannels[k];
2742             String channelName = FromAIString(channel->mNodeName);
2743 
2744             if (channelName == transfBoneName)
2745             {
2746                 ++numTransforms;
2747                 channelIndices[j] = k;
2748                 break;
2749             }
2750         }
2751     }
2752 
2753     // resize animated channel key size
2754     if (numTransforms > 1)
2755         ExpandAnimatedChannelKeys(anim, mainChannel, channelIndices);
2756 }
2757 
CreatePivotlessFbxBoneStruct(OutModel & model)2758 void CreatePivotlessFbxBoneStruct(OutModel &model)
2759 {
2760     // Init
2761     model.pivotlessBones_.Clear();
2762     aiMatrix4x4 chain[TransformationComp_MAXIMUM];
2763 
2764     for (unsigned i = 0; i < model.bones_.Size(); ++i)
2765     {
2766         String mainBoneName = String(model.bones_[i]->mName.data);
2767 
2768         // Skip $fbx nodes
2769         if (mainBoneName.Find("$AssimpFbx$") != String::NPOS)
2770             continue;
2771 
2772         std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
2773         FillChainTransforms(model, &chain[0], mainBoneName);
2774 
2775         // Calculate chained transform
2776         aiMatrix4x4 finalTransform;
2777         for (unsigned j = 0; j < TransformationComp_MAXIMUM; ++j)
2778             finalTransform = finalTransform * chain[j];
2779 
2780         // New bone node
2781         aiNode *pnode = new aiNode;
2782         pnode->mName = model.bones_[i]->mName;
2783         pnode->mTransformation = finalTransform * model.bones_[i]->mTransformation;
2784 
2785         model.pivotlessBones_.Push(pnode);
2786     }
2787 }
2788 
ExtrapolatePivotlessAnimation(OutModel * model)2789 void ExtrapolatePivotlessAnimation(OutModel* model)
2790 {
2791     if (suppressFbxPivotNodes_ && model)
2792     {
2793         PrintLine("Suppressing $fbx nodes");
2794 
2795         // Construct new bone structure from suppressed $fbx pivot nodes
2796         CreatePivotlessFbxBoneStruct(*model);
2797 
2798         // Extrapolate anim
2799         const PODVector<aiAnimation *> &animations = model->animations_;
2800         for (unsigned i = 0; i < animations.Size(); ++i)
2801         {
2802             aiAnimation* anim = animations[i];
2803             Vector<String> mainBoneCompleteList;
2804             mainBoneCompleteList.Clear();
2805 
2806             for (unsigned j = 0; j < anim->mNumChannels; ++j)
2807             {
2808                 aiNodeAnim* channel = anim->mChannels[j];
2809                 String channelName = FromAIString(channel->mNodeName);
2810                 unsigned pos = channelName.Find("_$AssimpFbx$");
2811 
2812                 if (pos != String::NPOS)
2813                 {
2814                     // Every first $fbx animation channel for a bone will consolidate other $fbx animation to a single channel
2815                     // skip subsequent $fbx animation channel for the same bone
2816                     String mainBoneName = channelName.Substring(0, pos);
2817 
2818                     if (mainBoneCompleteList.Find(mainBoneName) != mainBoneCompleteList.End())
2819                         continue;
2820 
2821                     mainBoneCompleteList.Push(mainBoneName);
2822                     unsigned boneIdx = GetBoneIndex(*model, mainBoneName);
2823 
2824                     // This condition exists if a geometry, not a bone, has a key animation
2825                     if (boneIdx == M_MAX_UNSIGNED)
2826                         continue;
2827 
2828                     // Init chain indices and fill transforms
2829                     aiMatrix4x4 mainboneTransform = model->bones_[boneIdx]->mTransformation;
2830                     aiMatrix4x4 chain[TransformationComp_MAXIMUM];
2831                     int channelIndices[TransformationComp_MAXIMUM];
2832 
2833                     InitAnimatedChainTransformIndices(anim, j, mainBoneName, &channelIndices[0]);
2834                     std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
2835                     FillChainTransforms(*model, &chain[0], mainBoneName);
2836 
2837                     unsigned keyFrames = channel->mNumPositionKeys;
2838                     if (channel->mNumRotationKeys > keyFrames)
2839                         keyFrames = channel->mNumRotationKeys;
2840                     if (channel->mNumScalingKeys  > keyFrames)
2841                         keyFrames = channel->mNumScalingKeys;
2842 
2843                     for (unsigned k = 0; k < keyFrames; ++k)
2844                     {
2845                         double frameTime = 0.0;
2846                         aiMatrix4x4 finalTransform;
2847 
2848                         // Chain transform animated values
2849                         for (unsigned l = 0; l < TransformationComp_MAXIMUM; ++l)
2850                         {
2851                             // It's either the chain transform or animation channel transform
2852                             if (channelIndices[l] != -1)
2853                             {
2854                                 aiMatrix4x4 animtform, tempMat;
2855                                 aiNodeAnim* animchannel = anim->mChannels[channelIndices[l]];
2856 
2857                                 if (k < animchannel->mNumPositionKeys)
2858                                 {
2859                                     aiMatrix4x4::Translation(animchannel->mPositionKeys[k].mValue, tempMat);
2860                                     animtform = animtform * tempMat;
2861                                     frameTime = Max(animchannel->mPositionKeys[k].mTime, frameTime);
2862                                 }
2863                                 if (k < animchannel->mNumRotationKeys)
2864                                 {
2865                                     tempMat = aiMatrix4x4(animchannel->mRotationKeys[k].mValue.GetMatrix());
2866                                     animtform = animtform * tempMat;
2867                                     frameTime = Max(animchannel->mRotationKeys[k].mTime, frameTime);
2868                                 }
2869                                 if (k < animchannel->mNumScalingKeys)
2870                                 {
2871                                     aiMatrix4x4::Scaling(animchannel->mScalingKeys[k].mValue, tempMat);
2872                                     animtform = animtform * tempMat;
2873                                     frameTime = Max(animchannel->mScalingKeys[k].mTime, frameTime);
2874                                 }
2875 
2876                                 finalTransform = finalTransform * animtform;
2877                             }
2878                             else
2879                                 finalTransform = finalTransform * chain[l];
2880                         }
2881 
2882                         aiVector3D animPos, animScale;
2883                         aiQuaternion animRot;
2884                         finalTransform = finalTransform * mainboneTransform;
2885                         finalTransform.Decompose(animScale, animRot, animPos);
2886 
2887                         // New values
2888                         if (k < channel->mNumPositionKeys)
2889                         {
2890                             channel->mPositionKeys[k].mValue = animPos;
2891                             channel->mPositionKeys[k].mTime = frameTime;
2892                         }
2893 
2894                         if (k < channel->mNumRotationKeys)
2895                         {
2896                             channel->mRotationKeys[k].mValue = animRot;
2897                             channel->mRotationKeys[k].mTime = frameTime;
2898                         }
2899 
2900                         if (k < channel->mNumScalingKeys)
2901                         {
2902                             channel->mScalingKeys[k].mValue = animScale;
2903                             channel->mScalingKeys[k].mTime = frameTime;
2904                         }
2905                     }
2906                 }
2907             }
2908         }
2909     }
2910 }
2911 
CollectSceneNodesAsBones(OutModel & model,aiNode * rootNode)2912 void CollectSceneNodesAsBones(OutModel &model, aiNode* rootNode)
2913 {
2914     if (!rootNode)
2915         return;
2916 
2917     model.bones_.Push(rootNode);
2918 
2919     for (unsigned i = 0; i < rootNode->mNumChildren; ++i)
2920     {
2921         CollectSceneNodesAsBones(model, rootNode->mChildren[i]);
2922     }
2923 }
2924 
2925