1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
4 
5 Copyright (c) 2006-2021, assimp team
6 
7 All rights reserved.
8 
9 Redistribution and use of this software in source and binary forms,
10 with or without modification, are permitted provided that the
11 following conditions are met:
12 
13 * Redistributions of source code must retain the above
14   copyright notice, this list of conditions and the
15   following disclaimer.
16 
17 * Redistributions in binary form must reproduce the above
18   copyright notice, this list of conditions and the
19   following disclaimer in the documentation and/or other
20   materials provided with the distribution.
21 
22 * Neither the name of the assimp team, nor the names of its
23   contributors may be used to endorse or promote products
24   derived from this software without specific prior
25   written permission of the assimp team.
26 
27 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 
39 ----------------------------------------------------------------------
40 */
41 
42 #include "OgreXmlSerializer.h"
43 #include "OgreBinarySerializer.h"
44 #include "OgreParsingUtils.h"
45 
46 #include <assimp/TinyFormatter.h>
47 #include <assimp/DefaultLogger.hpp>
48 
49 #include <memory>
50 
51 #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER
52 
53 // Define as 1 to get verbose logging.
54 #define OGRE_XML_SERIALIZER_DEBUG 0
55 
56 namespace Assimp {
57 namespace Ogre {
58 
59 //AI_WONT_RETURN void ThrowAttibuteError(const XmlParser *reader, const std::string &name, const std::string &error = "") AI_WONT_RETURN_SUFFIX;
60 
ThrowAttibuteError(const std::string & nodeName,const std::string & name,const std::string & error)61 AI_WONT_RETURN void ThrowAttibuteError(const std::string &nodeName, const std::string &name, const std::string &error) {
62     if (!error.empty()) {
63         throw DeadlyImportError(error, " in node '", nodeName, "' and attribute '", name, "'");
64     } else {
65         throw DeadlyImportError("Attribute '", name, "' does not exist in node '", nodeName, "'");
66     }
67 }
68 
69 template <>
ReadAttribute(XmlNode & xmlNode,const char * name) const70 int32_t OgreXmlSerializer::ReadAttribute<int32_t>(XmlNode &xmlNode, const char *name) const {
71     if (!XmlParser::hasAttribute(xmlNode, name)) {
72         ThrowAttibuteError(xmlNode.name(), name, "Not found");
73     }
74     pugi::xml_attribute attr = xmlNode.attribute(name);
75     return static_cast<int32_t>(attr.as_int());
76 }
77 
78 template <>
ReadAttribute(XmlNode & xmlNode,const char * name) const79 uint32_t OgreXmlSerializer::ReadAttribute<uint32_t>(XmlNode &xmlNode, const char *name) const {
80     if (!XmlParser::hasAttribute(xmlNode, name)) {
81         ThrowAttibuteError(xmlNode.name(), name, "Not found");
82     }
83 
84     // @note This is hackish. But we are never expecting unsigned values that go outside the
85     //       int32_t range. Just monitor for negative numbers and kill the import.
86     int32_t temp = ReadAttribute<int32_t>(xmlNode, name);
87     if (temp < 0) {
88         ThrowAttibuteError(xmlNode.name(), name, "Found a negative number value where expecting a uint32_t value");
89     }
90 
91     return static_cast<uint32_t>(temp);
92 }
93 
94 template <>
ReadAttribute(XmlNode & xmlNode,const char * name) const95 uint16_t OgreXmlSerializer::ReadAttribute<uint16_t>(XmlNode &xmlNode, const char *name) const {
96     if (!XmlParser::hasAttribute(xmlNode, name)) {
97         ThrowAttibuteError(xmlNode.name(), name, "Not found");
98     }
99 
100     return static_cast<uint16_t>(xmlNode.attribute(name).as_int());
101 }
102 
103 template <>
ReadAttribute(XmlNode & xmlNode,const char * name) const104 float OgreXmlSerializer::ReadAttribute<float>(XmlNode &xmlNode, const char *name) const {
105     if (!XmlParser::hasAttribute(xmlNode, name)) {
106         ThrowAttibuteError(xmlNode.name(), name, "Not found");
107     }
108 
109     return xmlNode.attribute(name).as_float();
110 }
111 
112 template <>
ReadAttribute(XmlNode & xmlNode,const char * name) const113 std::string OgreXmlSerializer::ReadAttribute<std::string>(XmlNode &xmlNode, const char *name) const {
114     if (!XmlParser::hasAttribute(xmlNode, name)) {
115         ThrowAttibuteError(xmlNode.name(), name, "Not found");
116     }
117 
118     return xmlNode.attribute(name).as_string();
119 }
120 
121 template <>
ReadAttribute(XmlNode & xmlNode,const char * name) const122 bool OgreXmlSerializer::ReadAttribute<bool>(XmlNode &xmlNode, const char *name) const {
123     std::string value = ai_tolower(ReadAttribute<std::string>(xmlNode, name));
124     if (ASSIMP_stricmp(value, "true") == 0) {
125         return true;
126     } else if (ASSIMP_stricmp(value, "false") == 0) {
127         return false;
128     }
129 
130     ThrowAttibuteError(xmlNode.name(), name, "Boolean value is expected to be 'true' or 'false', encountered '" + value + "'");
131     return false;
132 }
133 
134 // Mesh XML constants
135 
136 // <mesh>
137 static const char *nnMesh = "mesh";
138 static const char *nnSharedGeometry = "sharedgeometry";
139 static const char *nnSubMeshes = "submeshes";
140 static const char *nnSubMesh = "submesh";
141 //static const char *nnSubMeshNames = "submeshnames";
142 static const char *nnSkeletonLink = "skeletonlink";
143 //static const char *nnLOD = "levelofdetail";
144 //static const char *nnExtremes = "extremes";
145 //static const char *nnPoses = "poses";
146 static const char *nnAnimations = "animations";
147 
148 // <submesh>
149 static const char *nnFaces = "faces";
150 static const char *nnFace = "face";
151 static const char *nnGeometry = "geometry";
152 //static const char *nnTextures = "textures";
153 
154 // <mesh/submesh>
155 static const char *nnBoneAssignments = "boneassignments";
156 
157 // <sharedgeometry/geometry>
158 static const char *nnVertexBuffer = "vertexbuffer";
159 
160 // <vertexbuffer>
161 //static const char *nnVertex = "vertex";
162 static const char *nnPosition = "position";
163 static const char *nnNormal = "normal";
164 static const char *nnTangent = "tangent";
165 //static const char *nnBinormal = "binormal";
166 static const char *nnTexCoord = "texcoord";
167 //static const char *nnColorDiffuse = "colour_diffuse";
168 //static const char *nnColorSpecular = "colour_specular";
169 
170 // <boneassignments>
171 static const char *nnVertexBoneAssignment = "vertexboneassignment";
172 
173 // Skeleton XML constants
174 
175 // <skeleton>
176 static const char *nnSkeleton = "skeleton";
177 static const char *nnBones = "bones";
178 static const char *nnBoneHierarchy = "bonehierarchy";
179 //static const char *nnAnimationLinks = "animationlinks";
180 
181 // <bones>
182 static const char *nnBone = "bone";
183 static const char *nnRotation = "rotation";
184 static const char *nnAxis = "axis";
185 static const char *nnScale = "scale";
186 
187 // <bonehierarchy>
188 static const char *nnBoneParent = "boneparent";
189 
190 // <animations>
191 static const char *nnAnimation = "animation";
192 static const char *nnTracks = "tracks";
193 
194 // <tracks>
195 static const char *nnTrack = "track";
196 static const char *nnKeyFrames = "keyframes";
197 static const char *nnKeyFrame = "keyframe";
198 static const char *nnTranslate = "translate";
199 static const char *nnRotate = "rotate";
200 
201 // Common XML constants
202 static const char *anX = "x";
203 static const char *anY = "y";
204 static const char *anZ = "z";
205 
206 // Mesh
207 
OgreXmlSerializer(XmlParser * parser)208 OgreXmlSerializer::OgreXmlSerializer(XmlParser *parser) :
209         mParser(parser) {
210     // empty
211 }
212 
ImportMesh(XmlParser * parser)213 MeshXml *OgreXmlSerializer::ImportMesh(XmlParser *parser) {
214     if (nullptr == parser) {
215         return nullptr;
216     }
217 
218     OgreXmlSerializer serializer(parser);
219 
220     MeshXml *mesh = new MeshXml();
221     serializer.ReadMesh(mesh);
222 
223     return mesh;
224 }
225 
ReadMesh(MeshXml * mesh)226 void OgreXmlSerializer::ReadMesh(MeshXml *mesh) {
227     XmlNode root = mParser->getRootNode();
228     if (nullptr == root) {
229         throw DeadlyImportError("Root node is <" + std::string(root.name()) + "> expecting <mesh>");
230     }
231 
232     XmlNode startNode = root.child(nnMesh);
233     if (startNode.empty()) {
234         throw DeadlyImportError("Root node is <" + std::string(root.name()) + "> expecting <mesh>");
235     }
236     for (XmlNode currentNode : startNode.children()) {
237         const std::string currentName = currentNode.name();
238         if (currentName == nnSharedGeometry) {
239             mesh->sharedVertexData = new VertexDataXml();
240             ReadGeometry(currentNode, mesh->sharedVertexData);
241         } else if (currentName == nnSubMeshes) {
242             for (XmlNode &subMeshesNode : currentNode.children()) {
243                 const std::string &currentSMName = subMeshesNode.name();
244                 if (currentSMName == nnSubMesh) {
245                     ReadSubMesh(subMeshesNode, mesh);
246                 }
247             }
248         } else if (currentName == nnBoneAssignments) {
249             ReadBoneAssignments(currentNode, mesh->sharedVertexData);
250         } else if (currentName == nnSkeletonLink) {
251         }
252     }
253 
254     ASSIMP_LOG_VERBOSE_DEBUG("Reading Mesh");
255 }
256 
ReadGeometry(XmlNode & node,VertexDataXml * dest)257 void OgreXmlSerializer::ReadGeometry(XmlNode &node, VertexDataXml *dest) {
258     dest->count = ReadAttribute<uint32_t>(node, "vertexcount");
259     ASSIMP_LOG_VERBOSE_DEBUG("  - Reading geometry of ", dest->count, " vertices");
260 
261     for (XmlNode currentNode : node.children()) {
262         const std::string &currentName = currentNode.name();
263         if (currentName == nnVertexBuffer) {
264             ReadGeometryVertexBuffer(currentNode, dest);
265         }
266     }
267 }
268 
ReadGeometryVertexBuffer(XmlNode & node,VertexDataXml * dest)269 void OgreXmlSerializer::ReadGeometryVertexBuffer(XmlNode &node, VertexDataXml *dest) {
270     bool positions = (XmlParser::hasAttribute(node, "positions") && ReadAttribute<bool>(node, "positions"));
271     bool normals = (XmlParser::hasAttribute(node, "normals") && ReadAttribute<bool>(node, "normals"));
272     bool tangents = (XmlParser::hasAttribute(node, "tangents") && ReadAttribute<bool>(node, "tangents"));
273     uint32_t uvs = (XmlParser::hasAttribute(node, "texture_coords") ? ReadAttribute<uint32_t>(node, "texture_coords") : 0);
274 
275     // Not having positions is a error only if a previous vertex buffer did not have them.
276     if (!positions && !dest->HasPositions()) {
277         throw DeadlyImportError("Vertex buffer does not contain positions!");
278     }
279 
280     if (positions) {
281         ASSIMP_LOG_VERBOSE_DEBUG("    - Contains positions");
282         dest->positions.reserve(dest->count);
283     }
284     if (normals) {
285         ASSIMP_LOG_VERBOSE_DEBUG("    - Contains normals");
286         dest->normals.reserve(dest->count);
287     }
288     if (tangents) {
289         ASSIMP_LOG_VERBOSE_DEBUG("    - Contains tangents");
290         dest->tangents.reserve(dest->count);
291     }
292     if (uvs > 0) {
293         ASSIMP_LOG_VERBOSE_DEBUG("    - Contains ", uvs, " texture coords");
294         dest->uvs.resize(uvs);
295         for (size_t i = 0, len = dest->uvs.size(); i < len; ++i) {
296             dest->uvs[i].reserve(dest->count);
297         }
298     }
299 
300     for (XmlNode currentNode : node.children("vertex")) {
301         for (XmlNode vertexNode : currentNode.children()) {
302             const std::string &currentName = vertexNode.name();
303             if (positions && currentName == nnPosition) {
304                 aiVector3D pos;
305                 pos.x = ReadAttribute<float>(vertexNode, anX);
306                 pos.y = ReadAttribute<float>(vertexNode, anY);
307                 pos.z = ReadAttribute<float>(vertexNode, anZ);
308                 dest->positions.push_back(pos);
309             } else if (normals && currentName == nnNormal) {
310                 aiVector3D normal;
311                 normal.x = ReadAttribute<float>(vertexNode, anX);
312                 normal.y = ReadAttribute<float>(vertexNode, anY);
313                 normal.z = ReadAttribute<float>(vertexNode, anZ);
314                 dest->normals.push_back(normal);
315             } else if (tangents && currentName == nnTangent) {
316                 aiVector3D tangent;
317                 tangent.x = ReadAttribute<float>(vertexNode, anX);
318                 tangent.y = ReadAttribute<float>(vertexNode, anY);
319                 tangent.z = ReadAttribute<float>(vertexNode, anZ);
320                 dest->tangents.push_back(tangent);
321             } else if (uvs > 0 && currentName == nnTexCoord) {
322                 for (auto &curUvs : dest->uvs) {
323                     aiVector3D uv;
324                     uv.x = ReadAttribute<float>(vertexNode, "u");
325                     uv.y = (ReadAttribute<float>(vertexNode, "v") * -1) + 1; // Flip UV from Ogre to Assimp form
326                     curUvs.push_back(uv);
327                 }
328             }
329         }
330     }
331 
332     // Sanity checks
333     if (dest->positions.size() != dest->count) {
334         throw DeadlyImportError("Read only ", dest->positions.size(), " positions when should have read ", dest->count);
335     }
336     if (normals && dest->normals.size() != dest->count) {
337         throw DeadlyImportError("Read only ", dest->normals.size(), " normals when should have read ", dest->count);
338     }
339     if (tangents && dest->tangents.size() != dest->count) {
340         throw DeadlyImportError("Read only ", dest->tangents.size(), " tangents when should have read ", dest->count);
341     }
342     for (unsigned int i = 0; i < dest->uvs.size(); ++i) {
343         if (dest->uvs[i].size() != dest->count) {
344             throw DeadlyImportError("Read only ", dest->uvs[i].size(),
345                     " uvs for uv index ", i, " when should have read ", dest->count);
346         }
347     }
348 }
349 
ReadSubMesh(XmlNode & node,MeshXml * mesh)350 void OgreXmlSerializer::ReadSubMesh(XmlNode &node, MeshXml *mesh) {
351     static const char *anMaterial = "material";
352     static const char *anUseSharedVertices = "usesharedvertices";
353     static const char *anCount = "count";
354     static const char *anV1 = "v1";
355     static const char *anV2 = "v2";
356     static const char *anV3 = "v3";
357     static const char *anV4 = "v4";
358 
359     SubMeshXml *submesh = new SubMeshXml();
360 
361     if (XmlParser::hasAttribute(node, anMaterial)) {
362         submesh->materialRef = ReadAttribute<std::string>(node, anMaterial);
363     }
364     if (XmlParser::hasAttribute(node, anUseSharedVertices)) {
365         submesh->usesSharedVertexData = ReadAttribute<bool>(node, anUseSharedVertices);
366     }
367 
368     ASSIMP_LOG_VERBOSE_DEBUG("Reading SubMesh ", mesh->subMeshes.size());
369     ASSIMP_LOG_VERBOSE_DEBUG("  - Material: '", submesh->materialRef, "'");
370     ASSIMP_LOG_VERBOSE_DEBUG("  - Uses shared geometry: ", (submesh->usesSharedVertexData ? "true" : "false"));
371 
372     // TODO: maybe we have always just 1 faces and 1 geometry and always in this order. this loop will only work correct, when the order
373     // of faces and geometry changed, and not if we have more than one of one
374     /// @todo Fix above comment with better read logic below
375 
376     bool quadWarned = false;
377 
378     for (XmlNode &currentNode : node.children()) {
379         const std::string &currentName = currentNode.name();
380         if (currentName == nnFaces) {
381             submesh->indexData->faceCount = ReadAttribute<uint32_t>(currentNode, anCount);
382             submesh->indexData->faces.reserve(submesh->indexData->faceCount);
383             for (XmlNode currentChildNode : currentNode.children()) {
384                 const std::string &currentChildName = currentChildNode.name();
385                 if (currentChildName == nnFace) {
386                     aiFace face;
387                     face.mNumIndices = 3;
388                     face.mIndices = new unsigned int[3];
389                     face.mIndices[0] = ReadAttribute<uint32_t>(currentChildNode, anV1);
390                     face.mIndices[1] = ReadAttribute<uint32_t>(currentChildNode, anV2);
391                     face.mIndices[2] = ReadAttribute<uint32_t>(currentChildNode, anV3);
392                     /// @todo Support quads if Ogre even supports them in XML (I'm not sure but I doubt it)
393                     if (!quadWarned && XmlParser::hasAttribute(currentChildNode, anV4)) {
394                         ASSIMP_LOG_WARN("Submesh <face> has quads with <v4>, only triangles are supported at the moment!");
395                         quadWarned = true;
396                     }
397                     submesh->indexData->faces.push_back(face);
398                 }
399             }
400             if (submesh->indexData->faces.size() == submesh->indexData->faceCount) {
401                 ASSIMP_LOG_VERBOSE_DEBUG("  - Faces ", submesh->indexData->faceCount);
402             } else {
403                 throw DeadlyImportError("Read only ", submesh->indexData->faces.size(), " faces when should have read ", submesh->indexData->faceCount);
404             }
405         } else if (currentName == nnGeometry) {
406             if (submesh->usesSharedVertexData) {
407                 throw DeadlyImportError("Found <geometry> in <submesh> when use shared geometry is true. Invalid mesh file.");
408             }
409 
410             submesh->vertexData = new VertexDataXml();
411             ReadGeometry(currentNode, submesh->vertexData);
412         } else if (currentName == nnBoneAssignments) {
413             ReadBoneAssignments(currentNode, submesh->vertexData);
414         }
415     }
416 
417     submesh->index = static_cast<unsigned int>(mesh->subMeshes.size());
418     mesh->subMeshes.push_back(submesh);
419 }
420 
ReadBoneAssignments(XmlNode & node,VertexDataXml * dest)421 void OgreXmlSerializer::ReadBoneAssignments(XmlNode &node, VertexDataXml *dest) {
422     if (!dest) {
423         throw DeadlyImportError("Cannot read bone assignments, vertex data is null.");
424     }
425 
426     static const char *anVertexIndex = "vertexindex";
427     static const char *anBoneIndex = "boneindex";
428     static const char *anWeight = "weight";
429 
430     std::set<uint32_t> influencedVertices;
431     for (XmlNode &currentNode : node.children()) {
432         const std::string &currentName = currentNode.name();
433         if (currentName == nnVertexBoneAssignment) {
434             VertexBoneAssignment ba;
435             ba.vertexIndex = ReadAttribute<uint32_t>(currentNode, anVertexIndex);
436             ba.boneIndex = ReadAttribute<uint16_t>(currentNode, anBoneIndex);
437             ba.weight = ReadAttribute<float>(currentNode, anWeight);
438 
439             dest->boneAssignments.push_back(ba);
440             influencedVertices.insert(ba.vertexIndex);
441         }
442     }
443 
444     /** Normalize bone weights.
445         Some exporters won't care if the sum of all bone weights
446         for a single vertex equals 1 or not, so validate here. */
447     const float epsilon = 0.05f;
448     for (const uint32_t vertexIndex : influencedVertices) {
449         float sum = 0.0f;
450         for (VertexBoneAssignmentList::const_iterator baIter = dest->boneAssignments.begin(), baEnd = dest->boneAssignments.end(); baIter != baEnd; ++baIter) {
451             if (baIter->vertexIndex == vertexIndex)
452                 sum += baIter->weight;
453         }
454         if ((sum < (1.0f - epsilon)) || (sum > (1.0f + epsilon))) {
455             for (auto &boneAssign : dest->boneAssignments) {
456                 if (boneAssign.vertexIndex == vertexIndex)
457                     boneAssign.weight /= sum;
458             }
459         }
460     }
461 
462     ASSIMP_LOG_VERBOSE_DEBUG("  - ", dest->boneAssignments.size(), " bone assignments");
463 }
464 
465 // Skeleton
466 
ImportSkeleton(Assimp::IOSystem * pIOHandler,MeshXml * mesh)467 bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh) {
468     if (!mesh || mesh->skeletonRef.empty())
469         return false;
470 
471     // Highly unusual to see in read world cases but support
472     // XML mesh referencing a binary skeleton file.
473     if (EndsWith(mesh->skeletonRef, ".skeleton", false)) {
474         if (OgreBinarySerializer::ImportSkeleton(pIOHandler, mesh))
475             return true;
476 
477         /** Last fallback if .skeleton failed to be read. Try reading from
478             .skeleton.xml even if the XML file referenced a binary skeleton.
479             @note This logic was in the previous version and I don't want to break
480             old code that might depends on it. */
481         mesh->skeletonRef = mesh->skeletonRef + ".xml";
482     }
483 
484     XmlParserPtr xmlParser = OpenXmlParser(pIOHandler, mesh->skeletonRef);
485     if (!xmlParser.get())
486         return false;
487 
488     Skeleton *skeleton = new Skeleton();
489     OgreXmlSerializer serializer(xmlParser.get());
490     XmlNode root = xmlParser->getRootNode();
491     serializer.ReadSkeleton(root, skeleton);
492     mesh->skeleton = skeleton;
493     return true;
494 }
495 
ImportSkeleton(Assimp::IOSystem * pIOHandler,Mesh * mesh)496 bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh) {
497     if (!mesh || mesh->skeletonRef.empty()) {
498         return false;
499     }
500 
501     XmlParserPtr xmlParser = OpenXmlParser(pIOHandler, mesh->skeletonRef);
502     if (!xmlParser.get()) {
503         return false;
504     }
505 
506     Skeleton *skeleton = new Skeleton();
507     OgreXmlSerializer serializer(xmlParser.get());
508     XmlNode root = xmlParser->getRootNode();
509 
510     serializer.ReadSkeleton(root, skeleton);
511     mesh->skeleton = skeleton;
512 
513     return true;
514 }
515 
OpenXmlParser(Assimp::IOSystem * pIOHandler,const std::string & filename)516 XmlParserPtr OgreXmlSerializer::OpenXmlParser(Assimp::IOSystem *pIOHandler, const std::string &filename) {
517     if (!EndsWith(filename, ".skeleton.xml", false)) {
518         ASSIMP_LOG_ERROR("Imported Mesh is referencing to unsupported '", filename, "' skeleton file.");
519         return XmlParserPtr();
520     }
521 
522     if (!pIOHandler->Exists(filename)) {
523         ASSIMP_LOG_ERROR("Failed to find skeleton file '", filename, "' that is referenced by imported Mesh.");
524         return XmlParserPtr();
525     }
526 
527     std::unique_ptr<IOStream> file(pIOHandler->Open(filename));
528     if (!file.get()) {
529         throw DeadlyImportError("Failed to open skeleton file ", filename);
530     }
531 
532     XmlParserPtr xmlParser = std::make_shared<XmlParser>();
533     if (!xmlParser->parse(file.get())) {
534         throw DeadlyImportError("Failed to create XML reader for skeleton file " + filename);
535     }
536     return xmlParser;
537 }
538 
ReadSkeleton(XmlNode & node,Skeleton * skeleton)539 void OgreXmlSerializer::ReadSkeleton(XmlNode &node, Skeleton *skeleton) {
540     if (node.name() != nnSkeleton) {
541         throw DeadlyImportError("Root node is <" + std::string(node.name()) + "> expecting <skeleton>");
542     }
543 
544     ASSIMP_LOG_VERBOSE_DEBUG("Reading Skeleton");
545 
546     // Optional blend mode from root node
547     if (XmlParser::hasAttribute(node, "blendmode")) {
548         skeleton->blendMode = (ai_tolower(ReadAttribute<std::string>(node, "blendmode")) == "cumulative" ? Skeleton::ANIMBLEND_CUMULATIVE : Skeleton::ANIMBLEND_AVERAGE);
549     }
550 
551     for (XmlNode &currentNode : node.children()) {
552         const std::string currentName = currentNode.name();
553         if (currentName == nnBones) {
554             ReadBones(currentNode, skeleton);
555         } else if (currentName == nnBoneHierarchy) {
556             ReadBoneHierarchy(currentNode, skeleton);
557         } else if (currentName == nnAnimations) {
558             ReadAnimations(currentNode, skeleton);
559         }
560     }
561 }
562 
ReadAnimations(XmlNode & node,Skeleton * skeleton)563 void OgreXmlSerializer::ReadAnimations(XmlNode &node, Skeleton *skeleton) {
564     if (skeleton->bones.empty()) {
565         throw DeadlyImportError("Cannot read <animations> for a Skeleton without bones");
566     }
567 
568     ASSIMP_LOG_VERBOSE_DEBUG("  - Animations");
569 
570     for (XmlNode &currentNode : node.children()) {
571         const std::string currentName = currentNode.name();
572         if (currentName == nnAnimation) {
573             Animation *anim = new Animation(skeleton);
574             anim->name = ReadAttribute<std::string>(currentNode, "name");
575             anim->length = ReadAttribute<float>(currentNode, "length");
576             for (XmlNode &currentChildNode : currentNode.children()) {
577                 const std::string currentChildName = currentNode.name();
578                 if (currentChildName == nnTracks) {
579                     ReadAnimationTracks(currentChildNode, anim);
580                     skeleton->animations.push_back(anim);
581                 } else {
582                     throw DeadlyImportError("No <tracks> found in <animation> ", anim->name);
583                 }
584             }
585         }
586     }
587 }
588 
ReadAnimationTracks(XmlNode & node,Animation * dest)589 void OgreXmlSerializer::ReadAnimationTracks(XmlNode &node, Animation *dest) {
590     for (XmlNode &currentNode : node.children()) {
591         const std::string currentName = currentNode.name();
592         if (currentName == nnTrack) {
593             VertexAnimationTrack track;
594             track.type = VertexAnimationTrack::VAT_TRANSFORM;
595             track.boneName = ReadAttribute<std::string>(currentNode, "bone");
596             for (XmlNode &currentChildNode : currentNode.children()) {
597                 const std::string currentChildName = currentNode.name();
598                 if (currentChildName == nnKeyFrames) {
599                     ReadAnimationKeyFrames(currentChildNode, dest, &track);
600                     dest->tracks.push_back(track);
601                 } else {
602                     throw DeadlyImportError("No <keyframes> found in <track> ", dest->name);
603                 }
604             }
605         }
606     }
607 }
608 
ReadAnimationKeyFrames(XmlNode & node,Animation * anim,VertexAnimationTrack * dest)609 void OgreXmlSerializer::ReadAnimationKeyFrames(XmlNode &node, Animation *anim, VertexAnimationTrack *dest) {
610     const aiVector3D zeroVec(0.f, 0.f, 0.f);
611     for (XmlNode &currentNode : node.children()) {
612         TransformKeyFrame keyframe;
613         const std::string currentName = currentNode.name();
614         if (currentName == nnKeyFrame) {
615             keyframe.timePos = ReadAttribute<float>(currentNode, "time");
616             for (XmlNode &currentChildNode : currentNode.children()) {
617                 const std::string currentChildName = currentNode.name();
618                 if (currentChildName == nnTranslate) {
619                     keyframe.position.x = ReadAttribute<float>(currentChildNode, anX);
620                     keyframe.position.y = ReadAttribute<float>(currentChildNode, anY);
621                     keyframe.position.z = ReadAttribute<float>(currentChildNode, anZ);
622                 } else if (currentChildName == nnRotate) {
623                     float angle = ReadAttribute<float>(currentChildNode, "angle");
624                     for (XmlNode &currentChildChildNode : currentNode.children()) {
625                         const std::string currentChildChildName = currentNode.name();
626                         if (currentChildChildName == nnAxis) {
627                             aiVector3D axis;
628                             axis.x = ReadAttribute<float>(currentChildChildNode, anX);
629                             axis.y = ReadAttribute<float>(currentChildChildNode, anY);
630                             axis.z = ReadAttribute<float>(currentChildChildNode, anZ);
631                             if (axis.Equal(zeroVec)) {
632                                 axis.x = 1.0f;
633                                 if (angle != 0) {
634                                     ASSIMP_LOG_WARN("Found invalid a key frame with a zero rotation axis in animation: ", anim->name);
635                                 }
636                             }
637                             keyframe.rotation = aiQuaternion(axis, angle);
638                         }
639                     }
640                 } else if (currentChildName == nnScale) {
641                     keyframe.scale.x = ReadAttribute<float>(currentChildNode, anX);
642                     keyframe.scale.y = ReadAttribute<float>(currentChildNode, anY);
643                     keyframe.scale.z = ReadAttribute<float>(currentChildNode, anZ);
644                 }
645             }
646         }
647         dest->transformKeyFrames.push_back(keyframe);
648     }
649 }
650 
ReadBoneHierarchy(XmlNode & node,Skeleton * skeleton)651 void OgreXmlSerializer::ReadBoneHierarchy(XmlNode &node, Skeleton *skeleton) {
652     if (skeleton->bones.empty()) {
653         throw DeadlyImportError("Cannot read <bonehierarchy> for a Skeleton without bones");
654     }
655 
656     for (XmlNode &currentNode : node.children()) {
657         const std::string currentName = currentNode.name();
658         if (currentName == nnBoneParent) {
659             const std::string name = ReadAttribute<std::string>(currentNode, "bone");
660             const std::string parentName = ReadAttribute<std::string>(currentNode, "parent");
661 
662             Bone *bone = skeleton->BoneByName(name);
663             Bone *parent = skeleton->BoneByName(parentName);
664 
665             if (bone && parent) {
666                 parent->AddChild(bone);
667             } else {
668                 throw DeadlyImportError("Failed to find bones for parenting: Child ", name, " for parent ", parentName);
669             }
670         }
671     }
672 
673     // Calculate bone matrices for root bones. Recursively calculates their children.
674     for (size_t i = 0, len = skeleton->bones.size(); i < len; ++i) {
675         Bone *bone = skeleton->bones[i];
676         if (!bone->IsParented())
677             bone->CalculateWorldMatrixAndDefaultPose(skeleton);
678     }
679 }
680 
BoneCompare(Bone * a,Bone * b)681 static bool BoneCompare(Bone *a, Bone *b) {
682     ai_assert(nullptr != a);
683     ai_assert(nullptr != b);
684 
685     return (a->id < b->id);
686 }
687 
ReadBones(XmlNode & node,Skeleton * skeleton)688 void OgreXmlSerializer::ReadBones(XmlNode &node, Skeleton *skeleton) {
689     ASSIMP_LOG_VERBOSE_DEBUG("  - Bones");
690 
691     for (XmlNode &currentNode : node.children()) {
692         const std::string currentName = currentNode.name();
693         if (currentName == nnBone) {
694             Bone *bone = new Bone();
695             bone->id = ReadAttribute<uint16_t>(currentNode, "id");
696             bone->name = ReadAttribute<std::string>(currentNode, "name");
697             for (XmlNode &currentChildNode : currentNode.children()) {
698                 const std::string currentChildName = currentNode.name();
699                 if (currentChildName == nnRotation) {
700                     bone->position.x = ReadAttribute<float>(currentChildNode, anX);
701                     bone->position.y = ReadAttribute<float>(currentChildNode, anY);
702                     bone->position.z = ReadAttribute<float>(currentChildNode, anZ);
703                 } else if (currentChildName == nnScale) {
704                     float angle = ReadAttribute<float>(currentChildNode, "angle");
705                     for (XmlNode currentChildChildNode : currentChildNode.children()) {
706                         const std::string &currentChildChildName = currentChildChildNode.name();
707                         if (currentChildChildName == nnAxis) {
708                             aiVector3D axis;
709                             axis.x = ReadAttribute<float>(currentChildChildNode, anX);
710                             axis.y = ReadAttribute<float>(currentChildChildNode, anY);
711                             axis.z = ReadAttribute<float>(currentChildChildNode, anZ);
712 
713                             bone->rotation = aiQuaternion(axis, angle);
714                         } else {
715                             throw DeadlyImportError("No axis specified for bone rotation in bone ", bone->id);
716                         }
717                     }
718                 } else if (currentChildName == nnScale) {
719                     if (XmlParser::hasAttribute(currentChildNode, "factor")) {
720                         float factor = ReadAttribute<float>(currentChildNode, "factor");
721                         bone->scale.Set(factor, factor, factor);
722                     } else {
723                         if (XmlParser::hasAttribute(currentChildNode, anX))
724                             bone->scale.x = ReadAttribute<float>(currentChildNode, anX);
725                         if (XmlParser::hasAttribute(currentChildNode, anY))
726                             bone->scale.y = ReadAttribute<float>(currentChildNode, anY);
727                         if (XmlParser::hasAttribute(currentChildNode, anZ))
728                             bone->scale.z = ReadAttribute<float>(currentChildNode, anZ);
729                     }
730                 }
731             }
732             skeleton->bones.push_back(bone);
733         }
734     }
735 
736     // Order bones by Id
737     std::sort(skeleton->bones.begin(), skeleton->bones.end(), BoneCompare);
738 
739     // Validate that bone indexes are not skipped.
740     /** @note Left this from original authors code, but not sure if this is strictly necessary
741         as per the Ogre skeleton spec. It might be more that other (later) code in this imported does not break. */
742     for (size_t i = 0, len = skeleton->bones.size(); i < len; ++i) {
743         Bone *b = skeleton->bones[i];
744         ASSIMP_LOG_VERBOSE_DEBUG("    ", b->id, " ", b->name);
745 
746         if (b->id != static_cast<uint16_t>(i)) {
747             throw DeadlyImportError("Bone ids are not in sequence starting from 0. Missing index ", i);
748         }
749     }
750 }
751 
752 } // namespace Ogre
753 } // namespace Assimp
754 
755 #endif // ASSIMP_BUILD_NO_OGRE_IMPORTER
756