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 ¤tSMName = 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 ¤tName = 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 ¤tName = 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 ¤tNode : node.children()) {
379 const std::string ¤tName = 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 ¤tChildName = 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 ¤tNode : node.children()) {
432 const std::string ¤tName = 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 ¤tNode : 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 ¤tNode : 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 ¤tChildNode : 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 ¤tNode : 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 ¤tChildNode : 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 ¤tNode : 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 ¤tChildNode : 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 ¤tChildChildNode : 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 ¤tNode : 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 ¤tNode : 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 ¤tChildNode : 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 ¤tChildChildName = 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