1 /*
2 ---------------------------------------------------------------------------
3 Open Asset Import Library (assimp)
4 ---------------------------------------------------------------------------
5 
6 Copyright (c) 2006-2021, 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         static const char * const 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 
219     auto file = pIOHandler->Open(pFile, "rb");
220     if (!file)
221         throw DeadlyImportError("MS3D: Could not open ", pFile);
222 
223     StreamReaderLE stream(file);
224 
225     // CanRead() should have done this already
226     char head[10];
227     int32_t version;
228 
229     mScene = pScene;
230 
231 
232     // 1 ------------ read into temporary data structures mirroring the original file
233 
234     stream.CopyAndAdvance(head,10);
235     stream >> version;
236     if (strncmp(head,"MS3D000000",10)) {
237         throw DeadlyImportError("Not a MS3D file, magic string MS3D000000 not found: ", pFile);
238     }
239 
240     if (version != 4) {
241         throw DeadlyImportError("MS3D: Unsupported file format version, 4 was expected");
242     }
243 
244     uint16_t verts;
245     stream >> verts;
246 
247     std::vector<TempVertex> vertices(verts);
248     for (unsigned int i = 0; i < verts; ++i) {
249         TempVertex& v = vertices[i];
250 
251         stream.IncPtr(1);
252         ReadVector(stream,v.pos);
253         v.bone_id[0] = stream.GetI1();
254         v.ref_cnt = stream.GetI1();
255 
256         v.bone_id[1] = v.bone_id[2] = v.bone_id[3] = UINT_MAX;
257         v.weights[1] = v.weights[2] = v.weights[3] = 0.f;
258         v.weights[0] = 1.f;
259     }
260 
261     uint16_t tris;
262     stream >> tris;
263 
264     std::vector<TempTriangle> triangles(tris);
265     for (unsigned int i = 0;i < tris; ++i) {
266         TempTriangle& t = triangles[i];
267 
268         stream.IncPtr(2);
269         for (unsigned int j = 0; j < 3; ++j) {
270             t.indices[j] = stream.GetI2();
271         }
272 
273         for (unsigned int j = 0; j < 3; ++j) {
274             ReadVector(stream,t.normals[j]);
275         }
276 
277         for (unsigned int j = 0; j < 3; ++j) {
278             stream >> (float&)(t.uv[j].x); // see note in ReadColor()
279         }
280         for (unsigned int j = 0; j < 3; ++j) {
281             stream >> (float&)(t.uv[j].y);
282         }
283 
284         t.sg    = stream.GetI1();
285         t.group = stream.GetI1();
286     }
287 
288     uint16_t grp;
289     stream >> grp;
290 
291     bool need_default = false;
292     std::vector<TempGroup> groups(grp);
293     for (unsigned int i = 0;i < grp; ++i) {
294         TempGroup& t = groups[i];
295 
296         stream.IncPtr(1);
297         stream.CopyAndAdvance(t.name,32);
298 
299         t.name[32] = '\0';
300         uint16_t num;
301         stream >> num;
302 
303         t.triangles.resize(num);
304         for (unsigned int j = 0; j < num; ++j) {
305             t.triangles[j] = stream.GetI2();
306         }
307         t.mat = stream.GetI1();
308         if (t.mat == UINT_MAX) {
309             need_default = true;
310         }
311     }
312 
313     uint16_t mat;
314     stream >> mat;
315 
316     std::vector<TempMaterial> materials(mat);
317     for (unsigned int j = 0;j < mat; ++j) {
318         TempMaterial& t = materials[j];
319 
320         stream.CopyAndAdvance(t.name,32);
321         t.name[32] = '\0';
322 
323         ReadColor(stream,t.ambient);
324         ReadColor(stream,t.diffuse);
325         ReadColor(stream,t.specular);
326         ReadColor(stream,t.emissive);
327         stream >> t.shininess  >> t.transparency;
328 
329         stream.IncPtr(1);
330 
331         stream.CopyAndAdvance(t.texture,128);
332         t.texture[128] = '\0';
333 
334         stream.CopyAndAdvance(t.alphamap,128);
335         t.alphamap[128] = '\0';
336     }
337 
338     float animfps, currenttime;
339     uint32_t totalframes;
340     stream >> animfps >> currenttime >> totalframes;
341 
342     uint16_t joint;
343     stream >> joint;
344 
345     std::vector<TempJoint> joints(joint);
346     for(unsigned int ii = 0; ii < joint; ++ii) {
347         TempJoint& j = joints[ii];
348 
349         stream.IncPtr(1);
350         stream.CopyAndAdvance(j.name,32);
351         j.name[32] = '\0';
352 
353         stream.CopyAndAdvance(j.parentName,32);
354         j.parentName[32] = '\0';
355 
356         ReadVector(stream,j.rotation);
357         ReadVector(stream,j.position);
358 
359         j.rotFrames.resize(stream.GetI2());
360         j.posFrames.resize(stream.GetI2());
361 
362         for(unsigned int a = 0; a < j.rotFrames.size(); ++a) {
363             TempKeyFrame& kf = j.rotFrames[a];
364             stream >> kf.time;
365             ReadVector(stream,kf.value);
366         }
367         for(unsigned int a = 0; a < j.posFrames.size(); ++a) {
368             TempKeyFrame& kf = j.posFrames[a];
369             stream >> kf.time;
370             ReadVector(stream,kf.value);
371         }
372     }
373 
374     if(stream.GetRemainingSize() > 4) {
375         uint32_t subversion;
376         stream >> subversion;
377         if (subversion == 1) {
378             ReadComments<TempGroup>(stream,groups);
379             ReadComments<TempMaterial>(stream,materials);
380             ReadComments<TempJoint>(stream,joints);
381 
382             // model comment - print it for we have such a nice log.
383             if (stream.GetI4()) {
384                 const size_t len = static_cast<size_t>(stream.GetI4());
385                 if (len > stream.GetRemainingSize()) {
386                     throw DeadlyImportError("MS3D: Model comment is too long");
387                 }
388 
389                 const std::string& s = std::string(reinterpret_cast<char*>(stream.GetPtr()),len);
390                 ASSIMP_LOG_DEBUG("MS3D: Model comment: ", s);
391             }
392 
393             if(stream.GetRemainingSize() > 4 && inrange((stream >> subversion,subversion),1u,3u)) {
394                 for(unsigned int i = 0; i < verts; ++i) {
395                     TempVertex& v = vertices[i];
396                     v.weights[3]=1.f;
397                     for(unsigned int n = 0; n < 3; v.weights[3]-=v.weights[n++]) {
398                         v.bone_id[n+1] = stream.GetI1();
399                         v.weights[n] = static_cast<float>(static_cast<unsigned int>(stream.GetI1()))/255.f;
400                     }
401                     stream.IncPtr((subversion-1)<<2u);
402                 }
403 
404                 // even further extra data is not of interest for us, at least now now.
405             }
406         }
407     }
408 
409     // 2 ------------ convert to proper aiXX data structures -----------------------------------
410 
411     if (need_default && materials.size()) {
412         ASSIMP_LOG_WARN("MS3D: Found group with no material assigned, spawning default material");
413         // if one of the groups has no material assigned, but there are other
414         // groups with materials, a default material needs to be added (
415         // scenepreprocessor adds a default material only if nummat==0).
416         materials.push_back(TempMaterial());
417         TempMaterial& m = materials.back();
418 
419         strcpy(m.name,"<MS3D_DefaultMat>");
420         m.diffuse = aiColor4D(0.6f,0.6f,0.6f,1.0);
421         m.transparency = 1.f;
422         m.shininess = 0.f;
423 
424         // this is because these TempXXX struct's have no c'tors.
425         m.texture[0] = m.alphamap[0] = '\0';
426 
427         for (unsigned int i = 0; i < groups.size(); ++i) {
428             TempGroup& g = groups[i];
429             if (g.mat == UINT_MAX) {
430                 g.mat = static_cast<unsigned int>(materials.size()-1);
431             }
432         }
433     }
434 
435     // convert materials to our generic key-value dict-alike
436     if (materials.size()) {
437         pScene->mMaterials = new aiMaterial*[materials.size()];
438         for (size_t i = 0; i < materials.size(); ++i) {
439 
440             aiMaterial* mo = new aiMaterial();
441             pScene->mMaterials[pScene->mNumMaterials++] = mo;
442 
443             const TempMaterial& mi = materials[i];
444 
445             aiString tmp;
446             if (0[mi.alphamap]) {
447                 tmp = aiString(mi.alphamap);
448                 mo->AddProperty(&tmp,AI_MATKEY_TEXTURE_OPACITY(0));
449             }
450             if (0[mi.texture]) {
451                 tmp = aiString(mi.texture);
452                 mo->AddProperty(&tmp,AI_MATKEY_TEXTURE_DIFFUSE(0));
453             }
454             if (0[mi.name]) {
455                 tmp = aiString(mi.name);
456                 mo->AddProperty(&tmp,AI_MATKEY_NAME);
457             }
458 
459             mo->AddProperty(&mi.ambient,1,AI_MATKEY_COLOR_AMBIENT);
460             mo->AddProperty(&mi.diffuse,1,AI_MATKEY_COLOR_DIFFUSE);
461             mo->AddProperty(&mi.specular,1,AI_MATKEY_COLOR_SPECULAR);
462             mo->AddProperty(&mi.emissive,1,AI_MATKEY_COLOR_EMISSIVE);
463 
464             mo->AddProperty(&mi.shininess,1,AI_MATKEY_SHININESS);
465             mo->AddProperty(&mi.transparency,1,AI_MATKEY_OPACITY);
466 
467             const int sm = mi.shininess>0.f?aiShadingMode_Phong:aiShadingMode_Gouraud;
468             mo->AddProperty(&sm,1,AI_MATKEY_SHADING_MODEL);
469         }
470     }
471 
472     // convert groups to meshes
473     if (groups.empty()) {
474         throw DeadlyImportError("MS3D: Didn't get any group records, file is malformed");
475     }
476 
477     pScene->mMeshes = new aiMesh*[pScene->mNumMeshes=static_cast<unsigned int>(groups.size())]();
478     for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
479 
480         aiMesh* m = pScene->mMeshes[i] = new aiMesh();
481         const TempGroup& g = groups[i];
482 
483         if (pScene->mNumMaterials && g.mat > pScene->mNumMaterials) {
484             throw DeadlyImportError("MS3D: Encountered invalid material index, file is malformed");
485         } // no error if no materials at all - scenepreprocessor adds one then
486 
487         m->mMaterialIndex  = g.mat;
488         m->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
489 
490         m->mFaces = new aiFace[m->mNumFaces = static_cast<unsigned int>(g.triangles.size())];
491         m->mNumVertices = m->mNumFaces*3;
492 
493         // storage for vertices - verbose format, as requested by the postprocessing pipeline
494         m->mVertices = new aiVector3D[m->mNumVertices];
495         m->mNormals  = new aiVector3D[m->mNumVertices];
496         m->mTextureCoords[0] = new aiVector3D[m->mNumVertices];
497         m->mNumUVComponents[0] = 2;
498 
499         typedef std::map<unsigned int,unsigned int> BoneSet;
500         BoneSet mybones;
501 
502         for (unsigned int j = 0,n = 0; j < m->mNumFaces; ++j) {
503             aiFace& f = m->mFaces[j];
504             if (g.triangles[j]>triangles.size()) {
505                 throw DeadlyImportError("MS3D: Encountered invalid triangle index, file is malformed");
506             }
507 
508             TempTriangle& t = triangles[g.triangles[j]];
509             f.mIndices = new unsigned int[f.mNumIndices=3];
510 
511             for (unsigned int k = 0; k < 3; ++k,++n) {
512                 if (t.indices[k]>vertices.size()) {
513                     throw DeadlyImportError("MS3D: Encountered invalid vertex index, file is malformed");
514                 }
515 
516                 const TempVertex& v = vertices[t.indices[k]];
517                 for(unsigned int a = 0; a < 4; ++a) {
518                     if (v.bone_id[a] != UINT_MAX) {
519                         if (v.bone_id[a] >= joints.size()) {
520                             throw DeadlyImportError("MS3D: Encountered invalid bone index, file is malformed");
521                         }
522                         if (mybones.find(v.bone_id[a]) == mybones.end()) {
523                              mybones[v.bone_id[a]] = 1;
524                         }
525                         else ++mybones[v.bone_id[a]];
526                     }
527                 }
528 
529                 // collect vertex components
530                 m->mVertices[n] = v.pos;
531 
532                 m->mNormals[n] = t.normals[k];
533                 m->mTextureCoords[0][n] = aiVector3D(t.uv[k].x,1.f-t.uv[k].y,0.0);
534                 f.mIndices[k] = n;
535             }
536         }
537 
538         // allocate storage for bones
539         if(!mybones.empty()) {
540             std::vector<unsigned int> bmap(joints.size());
541             m->mBones = new aiBone*[mybones.size()]();
542             for(BoneSet::const_iterator it = mybones.begin(); it != mybones.end(); ++it) {
543                 aiBone* const bn = m->mBones[m->mNumBones] = new aiBone();
544                 const TempJoint& jnt = joints[(*it).first];
545 
546                 bn->mName.Set(jnt.name);
547                 bn->mWeights = new aiVertexWeight[(*it).second];
548 
549                 bmap[(*it).first] = m->mNumBones++;
550             }
551 
552             // .. and collect bone weights
553             for (unsigned int j = 0,n = 0; j < m->mNumFaces; ++j) {
554                 TempTriangle& t = triangles[g.triangles[j]];
555 
556                 for (unsigned int k = 0; k < 3; ++k,++n) {
557                     const TempVertex& v = vertices[t.indices[k]];
558                     for(unsigned int a = 0; a < 4; ++a) {
559                         const unsigned int bone = v.bone_id[a];
560                         if(bone==UINT_MAX){
561                             continue;
562                         }
563 
564                         aiBone* const outbone = m->mBones[bmap[bone]];
565                         aiVertexWeight& outwght = outbone->mWeights[outbone->mNumWeights++];
566 
567                         outwght.mVertexId = n;
568                         outwght.mWeight = v.weights[a];
569                     }
570                 }
571             }
572         }
573     }
574 
575     // ... add dummy nodes under a single root, each holding a reference to one
576     // mesh. If we didn't do this, we'd lose the group name.
577     aiNode* rt = pScene->mRootNode = new aiNode("<MS3DRoot>");
578 
579 #ifdef ASSIMP_BUILD_MS3D_ONE_NODE_PER_MESH
580     rt->mChildren = new aiNode*[rt->mNumChildren=pScene->mNumMeshes+(joints.size()?1:0)]();
581 
582     for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
583         aiNode* nd = rt->mChildren[i] = new aiNode();
584 
585         const TempGroup& g = groups[i];
586 
587         // we need to generate an unique name for all mesh nodes.
588         // since we want to keep the group name, a prefix is
589         // prepended.
590         nd->mName = aiString("<MS3DMesh>_");
591         nd->mName.Append(g.name);
592         nd->mParent = rt;
593 
594         nd->mMeshes = new unsigned int[nd->mNumMeshes = 1];
595         nd->mMeshes[0] = i;
596     }
597 #else
598     rt->mMeshes = new unsigned int[pScene->mNumMeshes];
599     for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
600         rt->mMeshes[rt->mNumMeshes++] = i;
601     }
602 #endif
603 
604     // convert animations as well
605     if(joints.size()) {
606 #ifndef ASSIMP_BUILD_MS3D_ONE_NODE_PER_MESH
607         rt->mChildren = new aiNode*[1]();
608         rt->mNumChildren = 1;
609 
610         aiNode* jt = rt->mChildren[0] = new aiNode();
611 #else
612         aiNode* jt = rt->mChildren[pScene->mNumMeshes] = new aiNode();
613 #endif
614         jt->mParent = rt;
615         CollectChildJoints(joints,jt);
616         jt->mName.Set("<MS3DJointRoot>");
617 
618         pScene->mAnimations = new aiAnimation*[ pScene->mNumAnimations = 1 ];
619         aiAnimation* const anim = pScene->mAnimations[0] = new aiAnimation();
620 
621         anim->mName.Set("<MS3DMasterAnim>");
622 
623         // carry the fps info to the user by scaling all times with it
624         anim->mTicksPerSecond = animfps;
625 
626         // leave duration at its default, so ScenePreprocessor will fill an appropriate
627         // value (the values taken from some MS3D files seem to be too unreliable
628         // to pass the validation)
629         // anim->mDuration = totalframes/animfps;
630 
631         anim->mChannels = new aiNodeAnim*[joints.size()]();
632         for(std::vector<TempJoint>::const_iterator it = joints.begin(); it != joints.end(); ++it) {
633             if ((*it).rotFrames.empty() && (*it).posFrames.empty()) {
634                 continue;
635             }
636 
637             aiNodeAnim* nd = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim();
638             nd->mNodeName.Set((*it).name);
639 
640             if ((*it).rotFrames.size()) {
641                 nd->mRotationKeys = new aiQuatKey[(*it).rotFrames.size()];
642                 for(std::vector<TempKeyFrame>::const_iterator rot = (*it).rotFrames.begin(); rot != (*it).rotFrames.end(); ++rot) {
643                     aiQuatKey& q = nd->mRotationKeys[nd->mNumRotationKeys++];
644 
645                     q.mTime = (*rot).time*animfps;
646                     q.mValue = aiQuaternion(aiMatrix3x3(aiMatrix4x4().FromEulerAnglesXYZ((*it).rotation)*
647                         aiMatrix4x4().FromEulerAnglesXYZ((*rot).value)));
648                 }
649             }
650 
651             if ((*it).posFrames.size()) {
652                 nd->mPositionKeys = new aiVectorKey[(*it).posFrames.size()];
653 
654                 aiQuatKey* qu = nd->mRotationKeys;
655                 for(std::vector<TempKeyFrame>::const_iterator pos = (*it).posFrames.begin(); pos != (*it).posFrames.end(); ++pos,++qu) {
656                     aiVectorKey& v = nd->mPositionKeys[nd->mNumPositionKeys++];
657 
658                     v.mTime = (*pos).time*animfps;
659                     v.mValue = (*it).position + (*pos).value;
660                 }
661             }
662         }
663         // fixup to pass the validation if not a single animation channel is non-trivial
664         if (!anim->mNumChannels) {
665             anim->mChannels = nullptr;
666         }
667     }
668 }
669 
670 #endif
671