1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2019, assimp team
7 
8 
9 
10 All rights reserved.
11 
12 Redistribution and use of this software in source and binary forms,
13 with or without modification, are permitted provided that the following
14 conditions are met:
15 
16 * Redistributions of source code must retain the above
17   copyright notice, this list of conditions and the
18   following disclaimer.
19 
20 * Redistributions in binary form must reproduce the above
21   copyright notice, this list of conditions and the
22   following disclaimer in the documentation and/or other
23   materials provided with the distribution.
24 
25 * Neither the name of the assimp team, nor the names of its
26   contributors may be used to endorse or promote products
27   derived from this software without specific prior
28   written permission of the assimp team.
29 
30 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
31 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
32 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
33 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
34 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
36 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
37 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
38 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
40 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 ---------------------------------------------------------------------------
42 */
43 
44 /** @file  MS3DLoader.cpp
45  *  @brief Implementation of the Ms3D importer class.
46  *  Written against http://chumbalum.swissquake.ch/ms3d/ms3dspec.txt
47  */
48 
49 
50 #ifndef ASSIMP_BUILD_NO_MS3D_IMPORTER
51 
52 // internal headers
53 #include "MS3DLoader.h"
54 #include <assimp/StreamReader.h>
55 #include <assimp/DefaultLogger.hpp>
56 #include <assimp/scene.h>
57 #include <assimp/IOSystem.hpp>
58 #include <assimp/importerdesc.h>
59 #include <map>
60 
61 using namespace Assimp;
62 
63 static const aiImporterDesc desc = {
64     "Milkshape 3D Importer",
65     "",
66     "",
67     "http://chumbalum.swissquake.ch/",
68     aiImporterFlags_SupportBinaryFlavour,
69     0,
70     0,
71     0,
72     0,
73     "ms3d"
74 };
75 
76 // ASSIMP_BUILD_MS3D_ONE_NODE_PER_MESH
77 //   (enable old code path, which generates extra nodes per mesh while
78 //    the newer code uses aiMesh::mName to express the name of the
79 //    meshes (a.k.a. groups in MS3D))
80 
81 // ------------------------------------------------------------------------------------------------
82 // Constructor to be privately used by Importer
MS3DImporter()83 MS3DImporter::MS3DImporter()
84     : mScene()
85 {}
86 
87 // ------------------------------------------------------------------------------------------------
88 // Destructor, private as well
~MS3DImporter()89 MS3DImporter::~MS3DImporter()
90 {}
91 
92 // ------------------------------------------------------------------------------------------------
93 // Returns whether the class can handle the format of the given file.
CanRead(const std::string & pFile,IOSystem * pIOHandler,bool checkSig) const94 bool MS3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
95 {
96     // first call - simple extension check
97     const std::string extension = GetExtension(pFile);
98     if (extension == "ms3d") {
99         return true;
100     }
101 
102     // second call - check for magic identifiers
103     else if (!extension.length() || checkSig)   {
104         if (!pIOHandler) {
105             return true;
106         }
107         const char* tokens[] = {"MS3D000000"};
108         return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
109     }
110     return false;
111 }
112 
113 // ------------------------------------------------------------------------------------------------
GetInfo() const114 const aiImporterDesc* MS3DImporter::GetInfo () const
115 {
116     return &desc;
117 }
118 
119 // ------------------------------------------------------------------------------------------------
ReadColor(StreamReaderLE & stream,aiColor4D & ambient)120 void ReadColor(StreamReaderLE& stream, aiColor4D& ambient)
121 {
122     // aiColor4D is packed on gcc, implicit binding to float& fails therefore.
123     stream >> (float&)ambient.r >> (float&)ambient.g >> (float&)ambient.b >> (float&)ambient.a;
124 }
125 
126 // ------------------------------------------------------------------------------------------------
ReadVector(StreamReaderLE & stream,aiVector3D & pos)127 void ReadVector(StreamReaderLE& stream, aiVector3D& pos)
128 {
129     // See note in ReadColor()
130     stream >> (float&)pos.x >> (float&)pos.y >> (float&)pos.z;
131 }
132 
133 // ------------------------------------------------------------------------------------------------
134 template<typename T>
ReadComments(StreamReaderLE & stream,std::vector<T> & outp)135 void MS3DImporter :: ReadComments(StreamReaderLE& stream, std::vector<T>& outp)
136 {
137     uint16_t cnt;
138     stream >> cnt;
139 
140     for(unsigned int i = 0; i < cnt; ++i) {
141         uint32_t index, clength;
142         stream >> index >> clength;
143 
144         if(index >= outp.size()) {
145             ASSIMP_LOG_WARN("MS3D: Invalid index in comment section");
146         }
147         else if (clength > stream.GetRemainingSize()) {
148             throw DeadlyImportError("MS3D: Failure reading comment, length field is out of range");
149         }
150         else {
151             outp[index].comment = std::string(reinterpret_cast<char*>(stream.GetPtr()),clength);
152         }
153         stream.IncPtr(clength);
154     }
155 }
156 
157 // ------------------------------------------------------------------------------------------------
inrange(const T & in,const T2 & lower,const T3 & higher)158 template <typename T, typename T2, typename T3> bool inrange(const T& in, const T2& lower, const T3& higher)
159 {
160     return in > lower && in <= higher;
161 }
162 
163 // ------------------------------------------------------------------------------------------------
CollectChildJoints(const std::vector<TempJoint> & joints,std::vector<bool> & hadit,aiNode * nd,const aiMatrix4x4 & absTrafo)164 void MS3DImporter :: CollectChildJoints(const std::vector<TempJoint>& joints,
165     std::vector<bool>& hadit,
166     aiNode* nd,
167     const aiMatrix4x4& absTrafo)
168 {
169     unsigned int cnt = 0;
170     for(size_t i = 0; i < joints.size(); ++i) {
171         if (!hadit[i] && !strcmp(joints[i].parentName,nd->mName.data)) {
172             ++cnt;
173         }
174     }
175 
176     nd->mChildren = new aiNode*[nd->mNumChildren = cnt];
177     cnt = 0;
178     for(size_t i = 0; i < joints.size(); ++i) {
179         if (!hadit[i] && !strcmp(joints[i].parentName,nd->mName.data)) {
180             aiNode* ch = nd->mChildren[cnt++] = new aiNode(joints[i].name);
181             ch->mParent = nd;
182 
183             ch->mTransformation = aiMatrix4x4::Translation(joints[i].position,aiMatrix4x4()=aiMatrix4x4())*
184                 aiMatrix4x4().FromEulerAnglesXYZ(joints[i].rotation);
185 
186             const aiMatrix4x4 abs = absTrafo*ch->mTransformation;
187             for(unsigned int a = 0; a < mScene->mNumMeshes; ++a) {
188                 aiMesh* const msh = mScene->mMeshes[a];
189                 for(unsigned int n = 0; n < msh->mNumBones; ++n) {
190                     aiBone* const bone = msh->mBones[n];
191 
192                     if(bone->mName == ch->mName) {
193                         bone->mOffsetMatrix = aiMatrix4x4(abs).Inverse();
194                     }
195                 }
196             }
197 
198             hadit[i] = true;
199             CollectChildJoints(joints,hadit,ch,abs);
200         }
201     }
202 }
203 
204 // ------------------------------------------------------------------------------------------------
CollectChildJoints(const std::vector<TempJoint> & joints,aiNode * nd)205 void MS3DImporter :: CollectChildJoints(const std::vector<TempJoint>& joints, aiNode* nd)
206 {
207      std::vector<bool> hadit(joints.size(),false);
208      aiMatrix4x4 trafo;
209 
210      CollectChildJoints(joints,hadit,nd,trafo);
211 }
212 
213 // ------------------------------------------------------------------------------------------------
214 // Imports the given file into the given scene structure.
InternReadFile(const std::string & pFile,aiScene * pScene,IOSystem * pIOHandler)215 void MS3DImporter::InternReadFile( const std::string& pFile,
216     aiScene* pScene, IOSystem* pIOHandler)
217 {
218     StreamReaderLE stream(pIOHandler->Open(pFile,"rb"));
219 
220     // CanRead() should have done this already
221     char head[10];
222     int32_t version;
223 
224     mScene = pScene;
225 
226 
227     // 1 ------------ read into temporary data structures mirroring the original file
228 
229     stream.CopyAndAdvance(head,10);
230     stream >> version;
231     if (strncmp(head,"MS3D000000",10)) {
232         throw DeadlyImportError("Not a MS3D file, magic string MS3D000000 not found: "+pFile);
233     }
234 
235     if (version != 4) {
236         throw DeadlyImportError("MS3D: Unsupported file format version, 4 was expected");
237     }
238 
239     uint16_t verts;
240     stream >> verts;
241 
242     std::vector<TempVertex> vertices(verts);
243     for (unsigned int i = 0; i < verts; ++i) {
244         TempVertex& v = vertices[i];
245 
246         stream.IncPtr(1);
247         ReadVector(stream,v.pos);
248         v.bone_id[0] = stream.GetI1();
249         v.ref_cnt = stream.GetI1();
250 
251         v.bone_id[1] = v.bone_id[2] = v.bone_id[3] = UINT_MAX;
252         v.weights[1] = v.weights[2] = v.weights[3] = 0.f;
253         v.weights[0] = 1.f;
254     }
255 
256     uint16_t tris;
257     stream >> tris;
258 
259     std::vector<TempTriangle> triangles(tris);
260     for (unsigned int i = 0;i < tris; ++i) {
261         TempTriangle& t = triangles[i];
262 
263         stream.IncPtr(2);
264         for (unsigned int i = 0; i < 3; ++i) {
265             t.indices[i] = stream.GetI2();
266         }
267 
268         for (unsigned int i = 0; i < 3; ++i) {
269             ReadVector(stream,t.normals[i]);
270         }
271 
272         for (unsigned int i = 0; i < 3; ++i) {
273             stream >> (float&)(t.uv[i].x); // see note in ReadColor()
274         }
275         for (unsigned int i = 0; i < 3; ++i) {
276             stream >> (float&)(t.uv[i].y);
277         }
278 
279         t.sg    = stream.GetI1();
280         t.group = stream.GetI1();
281     }
282 
283     uint16_t grp;
284     stream >> grp;
285 
286     bool need_default = false;
287     std::vector<TempGroup> groups(grp);
288     for (unsigned int i = 0;i < grp; ++i) {
289         TempGroup& t = groups[i];
290 
291         stream.IncPtr(1);
292         stream.CopyAndAdvance(t.name,32);
293 
294         t.name[32] = '\0';
295         uint16_t num;
296         stream >> num;
297 
298         t.triangles.resize(num);
299         for (unsigned int i = 0; i < num; ++i) {
300             t.triangles[i] = stream.GetI2();
301         }
302         t.mat = stream.GetI1();
303         if (t.mat == UINT_MAX) {
304             need_default = true;
305         }
306     }
307 
308     uint16_t mat;
309     stream >> mat;
310 
311     std::vector<TempMaterial> materials(mat);
312     for (unsigned int i = 0;i < mat; ++i) {
313         TempMaterial& t = materials[i];
314 
315         stream.CopyAndAdvance(t.name,32);
316         t.name[32] = '\0';
317 
318         ReadColor(stream,t.ambient);
319         ReadColor(stream,t.diffuse);
320         ReadColor(stream,t.specular);
321         ReadColor(stream,t.emissive);
322         stream >> t.shininess  >> t.transparency;
323 
324         stream.IncPtr(1);
325 
326         stream.CopyAndAdvance(t.texture,128);
327         t.texture[128] = '\0';
328 
329         stream.CopyAndAdvance(t.alphamap,128);
330         t.alphamap[128] = '\0';
331     }
332 
333     float animfps, currenttime;
334     uint32_t totalframes;
335     stream >> animfps >> currenttime >> totalframes;
336 
337     uint16_t joint;
338     stream >> joint;
339 
340     std::vector<TempJoint> joints(joint);
341     for(unsigned int i = 0; i < joint; ++i) {
342         TempJoint& j = joints[i];
343 
344         stream.IncPtr(1);
345         stream.CopyAndAdvance(j.name,32);
346         j.name[32] = '\0';
347 
348         stream.CopyAndAdvance(j.parentName,32);
349         j.parentName[32] = '\0';
350 
351         ReadVector(stream,j.rotation);
352         ReadVector(stream,j.position);
353 
354         j.rotFrames.resize(stream.GetI2());
355         j.posFrames.resize(stream.GetI2());
356 
357         for(unsigned int a = 0; a < j.rotFrames.size(); ++a) {
358             TempKeyFrame& kf = j.rotFrames[a];
359             stream >> kf.time;
360             ReadVector(stream,kf.value);
361         }
362         for(unsigned int a = 0; a < j.posFrames.size(); ++a) {
363             TempKeyFrame& kf = j.posFrames[a];
364             stream >> kf.time;
365             ReadVector(stream,kf.value);
366         }
367     }
368 
369     if(stream.GetRemainingSize() > 4) {
370         uint32_t subversion;
371         stream >> subversion;
372         if (subversion == 1) {
373             ReadComments<TempGroup>(stream,groups);
374             ReadComments<TempMaterial>(stream,materials);
375             ReadComments<TempJoint>(stream,joints);
376 
377             // model comment - print it for we have such a nice log.
378             if (stream.GetI4()) {
379                 const size_t len = static_cast<size_t>(stream.GetI4());
380                 if (len > stream.GetRemainingSize()) {
381                     throw DeadlyImportError("MS3D: Model comment is too long");
382                 }
383 
384                 const std::string& s = std::string(reinterpret_cast<char*>(stream.GetPtr()),len);
385                 ASSIMP_LOG_DEBUG_F("MS3D: Model comment: ", s);
386             }
387 
388             if(stream.GetRemainingSize() > 4 && inrange((stream >> subversion,subversion),1u,3u)) {
389                 for(unsigned int i = 0; i < verts; ++i) {
390                     TempVertex& v = vertices[i];
391                     v.weights[3]=1.f;
392                     for(unsigned int n = 0; n < 3; v.weights[3]-=v.weights[n++]) {
393                         v.bone_id[n+1] = stream.GetI1();
394                         v.weights[n] = static_cast<float>(static_cast<unsigned int>(stream.GetI1()))/255.f;
395                     }
396                     stream.IncPtr((subversion-1)<<2u);
397                 }
398 
399                 // even further extra data is not of interest for us, at least now now.
400             }
401         }
402     }
403 
404     // 2 ------------ convert to proper aiXX data structures -----------------------------------
405 
406     if (need_default && materials.size()) {
407         ASSIMP_LOG_WARN("MS3D: Found group with no material assigned, spawning default material");
408         // if one of the groups has no material assigned, but there are other
409         // groups with materials, a default material needs to be added (
410         // scenepreprocessor adds a default material only if nummat==0).
411         materials.push_back(TempMaterial());
412         TempMaterial& m = materials.back();
413 
414         strcpy(m.name,"<MS3D_DefaultMat>");
415         m.diffuse = aiColor4D(0.6f,0.6f,0.6f,1.0);
416         m.transparency = 1.f;
417         m.shininess = 0.f;
418 
419         // this is because these TempXXX struct's have no c'tors.
420         m.texture[0] = m.alphamap[0] = '\0';
421 
422         for (unsigned int i = 0; i < groups.size(); ++i) {
423             TempGroup& g = groups[i];
424             if (g.mat == UINT_MAX) {
425                 g.mat = static_cast<unsigned int>(materials.size()-1);
426             }
427         }
428     }
429 
430     // convert materials to our generic key-value dict-alike
431     if (materials.size()) {
432         pScene->mMaterials = new aiMaterial*[materials.size()];
433         for (size_t i = 0; i < materials.size(); ++i) {
434 
435             aiMaterial* mo = new aiMaterial();
436             pScene->mMaterials[pScene->mNumMaterials++] = mo;
437 
438             const TempMaterial& mi = materials[i];
439 
440             aiString tmp;
441             if (0[mi.alphamap]) {
442                 tmp = aiString(mi.alphamap);
443                 mo->AddProperty(&tmp,AI_MATKEY_TEXTURE_OPACITY(0));
444             }
445             if (0[mi.texture]) {
446                 tmp = aiString(mi.texture);
447                 mo->AddProperty(&tmp,AI_MATKEY_TEXTURE_DIFFUSE(0));
448             }
449             if (0[mi.name]) {
450                 tmp = aiString(mi.name);
451                 mo->AddProperty(&tmp,AI_MATKEY_NAME);
452             }
453 
454             mo->AddProperty(&mi.ambient,1,AI_MATKEY_COLOR_AMBIENT);
455             mo->AddProperty(&mi.diffuse,1,AI_MATKEY_COLOR_DIFFUSE);
456             mo->AddProperty(&mi.specular,1,AI_MATKEY_COLOR_SPECULAR);
457             mo->AddProperty(&mi.emissive,1,AI_MATKEY_COLOR_EMISSIVE);
458 
459             mo->AddProperty(&mi.shininess,1,AI_MATKEY_SHININESS);
460             mo->AddProperty(&mi.transparency,1,AI_MATKEY_OPACITY);
461 
462             const int sm = mi.shininess>0.f?aiShadingMode_Phong:aiShadingMode_Gouraud;
463             mo->AddProperty(&sm,1,AI_MATKEY_SHADING_MODEL);
464         }
465     }
466 
467     // convert groups to meshes
468     if (groups.empty()) {
469         throw DeadlyImportError("MS3D: Didn't get any group records, file is malformed");
470     }
471 
472     pScene->mMeshes = new aiMesh*[pScene->mNumMeshes=static_cast<unsigned int>(groups.size())]();
473     for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
474 
475         aiMesh* m = pScene->mMeshes[i] = new aiMesh();
476         const TempGroup& g = groups[i];
477 
478         if (pScene->mNumMaterials && g.mat > pScene->mNumMaterials) {
479             throw DeadlyImportError("MS3D: Encountered invalid material index, file is malformed");
480         } // no error if no materials at all - scenepreprocessor adds one then
481 
482         m->mMaterialIndex  = g.mat;
483         m->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
484 
485         m->mFaces = new aiFace[m->mNumFaces = static_cast<unsigned int>(g.triangles.size())];
486         m->mNumVertices = m->mNumFaces*3;
487 
488         // storage for vertices - verbose format, as requested by the postprocessing pipeline
489         m->mVertices = new aiVector3D[m->mNumVertices];
490         m->mNormals  = new aiVector3D[m->mNumVertices];
491         m->mTextureCoords[0] = new aiVector3D[m->mNumVertices];
492         m->mNumUVComponents[0] = 2;
493 
494         typedef std::map<unsigned int,unsigned int> BoneSet;
495         BoneSet mybones;
496 
497         for (unsigned int i = 0,n = 0; i < m->mNumFaces; ++i) {
498             aiFace& f = m->mFaces[i];
499             if (g.triangles[i]>triangles.size()) {
500                 throw DeadlyImportError("MS3D: Encountered invalid triangle index, file is malformed");
501             }
502 
503             TempTriangle& t = triangles[g.triangles[i]];
504             f.mIndices = new unsigned int[f.mNumIndices=3];
505 
506             for (unsigned int i = 0; i < 3; ++i,++n) {
507                 if (t.indices[i]>vertices.size()) {
508                     throw DeadlyImportError("MS3D: Encountered invalid vertex index, file is malformed");
509                 }
510 
511                 const TempVertex& v = vertices[t.indices[i]];
512                 for(unsigned int a = 0; a < 4; ++a) {
513                     if (v.bone_id[a] != UINT_MAX) {
514                         if (v.bone_id[a] >= joints.size()) {
515                             throw DeadlyImportError("MS3D: Encountered invalid bone index, file is malformed");
516                         }
517                         if (mybones.find(v.bone_id[a]) == mybones.end()) {
518                              mybones[v.bone_id[a]] = 1;
519                         }
520                         else ++mybones[v.bone_id[a]];
521                     }
522                 }
523 
524                 // collect vertex components
525                 m->mVertices[n] = v.pos;
526 
527                 m->mNormals[n] = t.normals[i];
528                 m->mTextureCoords[0][n] = aiVector3D(t.uv[i].x,1.f-t.uv[i].y,0.0);
529                 f.mIndices[i] = n;
530             }
531         }
532 
533         // allocate storage for bones
534         if(!mybones.empty()) {
535             std::vector<unsigned int> bmap(joints.size());
536             m->mBones = new aiBone*[mybones.size()]();
537             for(BoneSet::const_iterator it = mybones.begin(); it != mybones.end(); ++it) {
538                 aiBone* const bn = m->mBones[m->mNumBones] = new aiBone();
539                 const TempJoint& jnt = joints[(*it).first];
540 
541                 bn->mName.Set(jnt.name);
542                 bn->mWeights = new aiVertexWeight[(*it).second];
543 
544                 bmap[(*it).first] = m->mNumBones++;
545             }
546 
547             // .. and collect bone weights
548             for (unsigned int i = 0,n = 0; i < m->mNumFaces; ++i) {
549                 TempTriangle& t = triangles[g.triangles[i]];
550 
551                 for (unsigned int i = 0; i < 3; ++i,++n) {
552                     const TempVertex& v = vertices[t.indices[i]];
553                     for(unsigned int a = 0; a < 4; ++a) {
554                         const unsigned int bone = v.bone_id[a];
555                         if(bone==UINT_MAX){
556                             continue;
557                         }
558 
559                         aiBone* const outbone = m->mBones[bmap[bone]];
560                         aiVertexWeight& outwght = outbone->mWeights[outbone->mNumWeights++];
561 
562                         outwght.mVertexId = n;
563                         outwght.mWeight = v.weights[a];
564                     }
565                 }
566             }
567         }
568     }
569 
570     // ... add dummy nodes under a single root, each holding a reference to one
571     // mesh. If we didn't do this, we'd lose the group name.
572     aiNode* rt = pScene->mRootNode = new aiNode("<MS3DRoot>");
573 
574 #ifdef ASSIMP_BUILD_MS3D_ONE_NODE_PER_MESH
575     rt->mChildren = new aiNode*[rt->mNumChildren=pScene->mNumMeshes+(joints.size()?1:0)]();
576 
577     for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
578         aiNode* nd = rt->mChildren[i] = new aiNode();
579 
580         const TempGroup& g = groups[i];
581 
582         // we need to generate an unique name for all mesh nodes.
583         // since we want to keep the group name, a prefix is
584         // prepended.
585         nd->mName = aiString("<MS3DMesh>_");
586         nd->mName.Append(g.name);
587         nd->mParent = rt;
588 
589         nd->mMeshes = new unsigned int[nd->mNumMeshes = 1];
590         nd->mMeshes[0] = i;
591     }
592 #else
593     rt->mMeshes = new unsigned int[pScene->mNumMeshes];
594     for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
595         rt->mMeshes[rt->mNumMeshes++] = i;
596     }
597 #endif
598 
599     // convert animations as well
600     if(joints.size()) {
601 #ifndef ASSIMP_BUILD_MS3D_ONE_NODE_PER_MESH
602         rt->mChildren = new aiNode*[1]();
603         rt->mNumChildren = 1;
604 
605         aiNode* jt = rt->mChildren[0] = new aiNode();
606 #else
607         aiNode* jt = rt->mChildren[pScene->mNumMeshes] = new aiNode();
608 #endif
609         jt->mParent = rt;
610         CollectChildJoints(joints,jt);
611         jt->mName.Set("<MS3DJointRoot>");
612 
613         pScene->mAnimations = new aiAnimation*[ pScene->mNumAnimations = 1 ];
614         aiAnimation* const anim = pScene->mAnimations[0] = new aiAnimation();
615 
616         anim->mName.Set("<MS3DMasterAnim>");
617 
618         // carry the fps info to the user by scaling all times with it
619         anim->mTicksPerSecond = animfps;
620 
621         // leave duration at its default, so ScenePreprocessor will fill an appropriate
622         // value (the values taken from some MS3D files seem to be too unreliable
623         // to pass the validation)
624         // anim->mDuration = totalframes/animfps;
625 
626         anim->mChannels = new aiNodeAnim*[joints.size()]();
627         for(std::vector<TempJoint>::const_iterator it = joints.begin(); it != joints.end(); ++it) {
628             if ((*it).rotFrames.empty() && (*it).posFrames.empty()) {
629                 continue;
630             }
631 
632             aiNodeAnim* nd = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
633             nd->mNodeName.Set((*it).name);
634 
635             if ((*it).rotFrames.size()) {
636                 nd->mRotationKeys = new aiQuatKey[(*it).rotFrames.size()];
637                 for(std::vector<TempKeyFrame>::const_iterator rot = (*it).rotFrames.begin(); rot != (*it).rotFrames.end(); ++rot) {
638                     aiQuatKey& q = nd->mRotationKeys[nd->mNumRotationKeys++];
639 
640                     q.mTime = (*rot).time*animfps;
641                     q.mValue = aiQuaternion(aiMatrix3x3(aiMatrix4x4().FromEulerAnglesXYZ((*it).rotation)*
642                         aiMatrix4x4().FromEulerAnglesXYZ((*rot).value)));
643                 }
644             }
645 
646             if ((*it).posFrames.size()) {
647                 nd->mPositionKeys = new aiVectorKey[(*it).posFrames.size()];
648 
649                 aiQuatKey* qu = nd->mRotationKeys;
650                 for(std::vector<TempKeyFrame>::const_iterator pos = (*it).posFrames.begin(); pos != (*it).posFrames.end(); ++pos,++qu) {
651                     aiVectorKey& v = nd->mPositionKeys[nd->mNumPositionKeys++];
652 
653                     v.mTime = (*pos).time*animfps;
654                     v.mValue = (*it).position + (*pos).value;
655                 }
656             }
657         }
658         // fixup to pass the validation if not a single animation channel is non-trivial
659         if (!anim->mNumChannels) {
660             anim->mChannels = NULL;
661         }
662     }
663 }
664 
665 #endif
666