1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following 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
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF 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 "TinyFormatter.h"
47#include <assimp/DefaultLogger.hpp>
48#include <memory>
49
50#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER
51
52// Define as 1 to get verbose logging.
53#define OGRE_XML_SERIALIZER_DEBUG 0
54
55namespace Assimp
56{
57namespace Ogre
58{
59
60AI_WONT_RETURN void ThrowAttibuteError(const XmlReader* reader, const std::string &name, const std::string &error = "") AI_WONT_RETURN_SUFFIX;
61AI_WONT_RETURN void ThrowAttibuteError(const XmlReader* reader, const std::string &name, const std::string &error)
62{
63 if (!error.empty())
64 {
65 throw DeadlyImportError(error + " in node '" + std::string(reader->getNodeName()) + "' and attribute '" + name + "'");
66 }
67 else
68 {
69 throw DeadlyImportError("Attribute '" + name + "' does not exist in node '" + std::string(reader->getNodeName()) + "'");
70 }
71}
72
73template<>
74int32_t OgreXmlSerializer::ReadAttribute<int32_t>(const std::string &name) const
75{
76 if (HasAttribute(name.c_str()))
77 {
78 return static_cast<int32_t>(m_reader->getAttributeValueAsInt(name.c_str()));
79 }
80 else
81 {
82 ThrowAttibuteError(m_reader, name);
83 return 0;
84 }
85}
86
87template<>
88uint32_t OgreXmlSerializer::ReadAttribute<uint32_t>(const std::string &name) const
89{
90 if (HasAttribute(name.c_str()))
91 {
92 /** @note This is hackish. But we are never expecting unsigned values that go outside the
93 int32_t range. Just monitor for negative numbers and kill the import. */
94 int32_t temp = ReadAttribute<int32_t>(name);
95 if (temp >= 0)
96 {
97 return static_cast<uint32_t>(temp);
98 }
99 else
100 {
101 ThrowAttibuteError(m_reader, name, "Found a negative number value where expecting a uint32_t value");
102 }
103 }
104 else
105 {
106 ThrowAttibuteError(m_reader, name);
107 }
108 return 0;
109}
110
111template<>
112uint16_t OgreXmlSerializer::ReadAttribute<uint16_t>(const std::string &name) const
113{
114 if (HasAttribute(name.c_str()))
115 {
116 return static_cast<uint16_t>(ReadAttribute<uint32_t>(name));
117 }
118 else
119 {
120 ThrowAttibuteError(m_reader, name);
121 }
122 return 0;
123}
124
125template<>
126float OgreXmlSerializer::ReadAttribute<float>(const std::string &name) const
127{
128 if (HasAttribute(name.c_str()))
129 {
130 return m_reader->getAttributeValueAsFloat(name.c_str());
131 }
132 else
133 {
134 ThrowAttibuteError(m_reader, name);
135 return 0;
136 }
137}
138
139template<>
140std::string OgreXmlSerializer::ReadAttribute<std::string>(const std::string &name) const
141{
142 const char* value = m_reader->getAttributeValue(name.c_str());
143 if (value)
144 {
145 return std::string(value);
146 }
147 else
148 {
149 ThrowAttibuteError(m_reader, name);
150 return "";
151 }
152}
153
154template<>
155bool OgreXmlSerializer::ReadAttribute<bool>(const std::string &name) const
156{
157 std::string value = Ogre::ToLower(ReadAttribute<std::string>(name));
158 if (ASSIMP_stricmp(value, "true") == 0)
159 {
160 return true;
161 }
162 else if (ASSIMP_stricmp(value, "false") == 0)
163 {
164 return false;
165 }
166 else
167 {
168 ThrowAttibuteError(m_reader, name, "Boolean value is expected to be 'true' or 'false', encountered '" + value + "'");
169 return false;
170 }
171}
172
173bool OgreXmlSerializer::HasAttribute(const std::string &name) const
174{
175 return (m_reader->getAttributeValue(name.c_str()) != 0);
176}
177
178std::string &OgreXmlSerializer::NextNode()
179{
180 do
181 {
182 if (!m_reader->read())
183 {
184 m_currentNodeName = "";
185 return m_currentNodeName;
186 }
187 }
188 while(m_reader->getNodeType() != irr::io::EXN_ELEMENT);
189
190 CurrentNodeName(true);
191#if (OGRE_XML_SERIALIZER_DEBUG == 1)
192 DefaultLogger::get()->debug("<" + m_currentNodeName + ">");
193#endif
194 return m_currentNodeName;
195}
196
197bool OgreXmlSerializer::CurrentNodeNameEquals(const std::string &name) const
198{
199 return (ASSIMP_stricmp(m_currentNodeName, name) == 0);
200}
201
202std::string OgreXmlSerializer::CurrentNodeName(bool forceRead)
203{
204 if (forceRead)
205 m_currentNodeName = std::string(m_reader->getNodeName());
206 return m_currentNodeName;
207}
208
209std::string &OgreXmlSerializer::SkipCurrentNode()
210{
211#if (OGRE_XML_SERIALIZER_DEBUG == 1)
212 DefaultLogger::get()->debug("Skipping node <" + m_currentNodeName + ">");
213#endif
214
215 for(;;)
216 {
217 if (!m_reader->read())
218 {
219 m_currentNodeName = "";
220 return m_currentNodeName;
221 }
222 if (m_reader->getNodeType() != irr::io::EXN_ELEMENT_END)
223 continue;
224 else if (std::string(m_reader->getNodeName()) == m_currentNodeName)
225 break;
226 }
227 return NextNode();
228}
229
230// Mesh XML constants
231
232// <mesh>
233const std::string nnMesh = "mesh";
234const std::string nnSharedGeometry = "sharedgeometry";
235const std::string nnSubMeshes = "submeshes";
236const std::string nnSubMesh = "submesh";
237const std::string nnSubMeshNames = "submeshnames";
238const std::string nnSkeletonLink = "skeletonlink";
239const std::string nnLOD = "levelofdetail";
240const std::string nnExtremes = "extremes";
241const std::string nnPoses = "poses";
242const std::string nnAnimations = "animations";
243
244// <submesh>
245const std::string nnFaces = "faces";
246const std::string nnFace = "face";
247const std::string nnGeometry = "geometry";
248const std::string nnTextures = "textures";
249
250// <mesh/submesh>
251const std::string nnBoneAssignments = "boneassignments";
252
253// <sharedgeometry/geometry>
254const std::string nnVertexBuffer = "vertexbuffer";
255
256// <vertexbuffer>
257const std::string nnVertex = "vertex";
258const std::string nnPosition = "position";
259const std::string nnNormal = "normal";
260const std::string nnTangent = "tangent";
261const std::string nnBinormal = "binormal";
262const std::string nnTexCoord = "texcoord";
263const std::string nnColorDiffuse = "colour_diffuse";
264const std::string nnColorSpecular = "colour_specular";
265
266// <boneassignments>
267const std::string nnVertexBoneAssignment = "vertexboneassignment";
268
269// Skeleton XML constants
270
271// <skeleton>
272const std::string nnSkeleton = "skeleton";
273const std::string nnBones = "bones";
274const std::string nnBoneHierarchy = "bonehierarchy";
275const std::string nnAnimationLinks = "animationlinks";
276
277// <bones>
278const std::string nnBone = "bone";
279const std::string nnRotation = "rotation";
280const std::string nnAxis = "axis";
281const std::string nnScale = "scale";
282
283// <bonehierarchy>
284const std::string nnBoneParent = "boneparent";
285
286// <animations>
287const std::string nnAnimation = "animation";
288const std::string nnTracks = "tracks";
289
290// <tracks>
291const std::string nnTrack = "track";
292const std::string nnKeyFrames = "keyframes";
293const std::string nnKeyFrame = "keyframe";
294const std::string nnTranslate = "translate";
295const std::string nnRotate = "rotate";
296
297// Common XML constants
298
299const std::string anX = "x";
300const std::string anY = "y";
301const std::string anZ = "z";
302
303// Mesh
304
305MeshXml *OgreXmlSerializer::ImportMesh(XmlReader *reader)
306{
307 OgreXmlSerializer serializer(reader);
308
309 MeshXml *mesh = new MeshXml();
310 serializer.ReadMesh(mesh);
311 return mesh;
312}
313
314void OgreXmlSerializer::ReadMesh(MeshXml *mesh)
315{
316 if (NextNode() != nnMesh) {
317 throw DeadlyImportError("Root node is <" + m_currentNodeName + "> expecting <mesh>");
318 }
319
320 DefaultLogger::get()->debug("Reading Mesh");
321
322 NextNode();
323
324 // Root level nodes
325 while(m_currentNodeName == nnSharedGeometry ||
326 m_currentNodeName == nnSubMeshes ||
327 m_currentNodeName == nnSkeletonLink ||
328 m_currentNodeName == nnBoneAssignments ||
329 m_currentNodeName == nnLOD ||
330 m_currentNodeName == nnSubMeshNames ||
331 m_currentNodeName == nnExtremes ||
332 m_currentNodeName == nnPoses ||
333 m_currentNodeName == nnAnimations)
334 {
335 if (m_currentNodeName == nnSharedGeometry)
336 {
337 mesh->sharedVertexData = new VertexDataXml();
338 ReadGeometry(mesh->sharedVertexData);
339 }
340 else if (m_currentNodeName == nnSubMeshes)
341 {
342 NextNode();
343 while(m_currentNodeName == nnSubMesh) {
344 ReadSubMesh(mesh);
345 }
346 }
347 else if (m_currentNodeName == nnBoneAssignments)
348 {
349 ReadBoneAssignments(mesh->sharedVertexData);
350 }
351 else if (m_currentNodeName == nnSkeletonLink)
352 {
353 mesh->skeletonRef = ReadAttribute<std::string>("name");
354 DefaultLogger::get()->debug("Read skeleton link " + mesh->skeletonRef);
355 NextNode();
356 }
357 // Assimp incompatible/ignored nodes
358 else
359 SkipCurrentNode();
360 }
361}
362
363void OgreXmlSerializer::ReadGeometry(VertexDataXml *dest)
364{
365 dest->count = ReadAttribute<uint32_t>("vertexcount");
366 DefaultLogger::get()->debug(Formatter::format() << " - Reading geometry of " << dest->count << " vertices");
367
368 NextNode();
369 while(m_currentNodeName == nnVertexBuffer) {
370 ReadGeometryVertexBuffer(dest);
371 }
372}
373
374void OgreXmlSerializer::ReadGeometryVertexBuffer(VertexDataXml *dest)
375{
376 bool positions = (HasAttribute("positions") && ReadAttribute<bool>("positions"));
377 bool normals = (HasAttribute("normals") && ReadAttribute<bool>("normals"));
378 bool tangents = (HasAttribute("tangents") && ReadAttribute<bool>("tangents"));
379 uint32_t uvs = (HasAttribute("texture_coords") ? ReadAttribute<uint32_t>("texture_coords") : 0);
380
381 // Not having positions is a error only if a previous vertex buffer did not have them.
382 if (!positions && !dest->HasPositions()) {
383 throw DeadlyImportError("Vertex buffer does not contain positions!");
384 }
385
386 if (positions)
387 {
388 DefaultLogger::get()->debug(" - Contains positions");
389 dest->positions.reserve(dest->count);
390 }
391 if (normals)
392 {
393 DefaultLogger::get()->debug(" - Contains normals");
394 dest->normals.reserve(dest->count);
395 }
396 if (tangents)
397 {
398 DefaultLogger::get()->debug(" - Contains tangents");
399 dest->tangents.reserve(dest->count);
400 }
401 if (uvs > 0)
402 {
403 DefaultLogger::get()->debug(Formatter::format() << " - Contains " << uvs << " texture coords");
404 dest->uvs.resize(uvs);
405 for(size_t i=0, len=dest->uvs.size(); i<len; ++i) {
406 dest->uvs[i].reserve(dest->count);
407 }
408 }
409
410 bool warnBinormal = true;
411 bool warnColorDiffuse = true;
412 bool warnColorSpecular = true;
413
414 NextNode();
415
416 while(m_currentNodeName == nnVertex ||
417 m_currentNodeName == nnPosition ||
418 m_currentNodeName == nnNormal ||
419 m_currentNodeName == nnTangent ||
420 m_currentNodeName == nnBinormal ||
421 m_currentNodeName == nnTexCoord ||
422 m_currentNodeName == nnColorDiffuse ||
423 m_currentNodeName == nnColorSpecular)
424 {
425 if (m_currentNodeName == nnVertex) {
426 NextNode();
427 }
428
429 /// @todo Implement nnBinormal, nnColorDiffuse and nnColorSpecular
430
431 if (positions && m_currentNodeName == nnPosition)
432 {
433 aiVector3D pos;
434 pos.x = ReadAttribute<float>(anX);
435 pos.y = ReadAttribute<float>(anY);
436 pos.z = ReadAttribute<float>(anZ);
437 dest->positions.push_back(pos);
438 }
439 else if (normals && m_currentNodeName == nnNormal)
440 {
441 aiVector3D normal;
442 normal.x = ReadAttribute<float>(anX);
443 normal.y = ReadAttribute<float>(anY);
444 normal.z = ReadAttribute<float>(anZ);
445 dest->normals.push_back(normal);
446 }
447 else if (tangents && m_currentNodeName == nnTangent)
448 {
449 aiVector3D tangent;
450 tangent.x = ReadAttribute<float>(anX);
451 tangent.y = ReadAttribute<float>(anY);
452 tangent.z = ReadAttribute<float>(anZ);
453 dest->tangents.push_back(tangent);
454 }
455 else if (uvs > 0 && m_currentNodeName == nnTexCoord)
456 {
457 for(auto &uvs : dest->uvs)
458 {
459 if (m_currentNodeName != nnTexCoord) {
460 throw DeadlyImportError("Vertex buffer declared more UVs than can be found in a vertex");
461 }
462
463 aiVector3D uv;
464 uv.x = ReadAttribute<float>("u");
465 uv.y = (ReadAttribute<float>("v") * -1) + 1; // Flip UV from Ogre to Assimp form
466 uvs.push_back(uv);
467
468 NextNode();
469 }
470 // Continue main loop as above already read next node
471 continue;
472 }
473 else
474 {
475 /// @todo Remove this stuff once implemented. We only want to log warnings once per element.
476 bool warn = true;
477 if (m_currentNodeName == nnBinormal)
478 {
479 if (warnBinormal)
480 {
481 warnBinormal = false;
482 }
483 else
484 {
485 warn = false;
486 }
487 }
488 else if (m_currentNodeName == nnColorDiffuse)
489 {
490 if (warnColorDiffuse)
491 {
492 warnColorDiffuse = false;
493 }
494 else
495 {
496 warn = false;
497 }
498 }
499 else if (m_currentNodeName == nnColorSpecular)
500 {
501 if (warnColorSpecular)
502 {
503 warnColorSpecular = false;
504 }
505 else
506 {
507 warn = false;
508 }
509 }
510 if (warn) {
511 DefaultLogger::get()->warn("Vertex buffer attribute read not implemented for element: " + m_currentNodeName);
512 }
513 }
514
515 // Advance
516 NextNode();
517 }
518
519 // Sanity checks
520 if (dest->positions.size() != dest->count) {
521 throw DeadlyImportError(Formatter::format() << "Read only " << dest->positions.size() << " positions when should have read " << dest->count);
522 }
523 if (normals && dest->normals.size() != dest->count) {
524 throw DeadlyImportError(Formatter::format() << "Read only " << dest->normals.size() << " normals when should have read " << dest->count);
525 }
526 if (tangents && dest->tangents.size() != dest->count) {
527 throw DeadlyImportError(Formatter::format() << "Read only " << dest->tangents.size() << " tangents when should have read " << dest->count);
528 }
529 for(unsigned int i=0; i<dest->uvs.size(); ++i)
530 {
531 if (dest->uvs[i].size() != dest->count) {
532 throw DeadlyImportError(Formatter::format() << "Read only " << dest->uvs[i].size()
533 << " uvs for uv index " << i << " when should have read " << dest->count);
534 }
535 }
536}
537
538void OgreXmlSerializer::ReadSubMesh(MeshXml *mesh)
539{
540 static const std::string anMaterial = "material";
541 static const std::string anUseSharedVertices = "usesharedvertices";
542 static const std::string anCount = "count";
543 static const std::string anV1 = "v1";
544 static const std::string anV2 = "v2";
545 static const std::string anV3 = "v3";
546 static const std::string anV4 = "v4";
547
548 SubMeshXml* submesh = new SubMeshXml();
549
550 if (HasAttribute(anMaterial)) {
551 submesh->materialRef = ReadAttribute<std::string>(anMaterial);
552 }
553 if (HasAttribute(anUseSharedVertices)) {
554 submesh->usesSharedVertexData = ReadAttribute<bool>(anUseSharedVertices);
555 }
556
557 DefaultLogger::get()->debug(Formatter::format() << "Reading SubMesh " << mesh->subMeshes.size());
558 DefaultLogger::get()->debug(Formatter::format() << " - Material: '" << submesh->materialRef << "'");
559 DefaultLogger::get()->debug(Formatter::format() << " - Uses shared geometry: " << (submesh->usesSharedVertexData ? "true" : "false"));
560
561 // 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
562 // of faces and geometry changed, and not if we have more than one of one
563 /// @todo Fix above comment with better read logic below
564
565 bool quadWarned = false;
566
567 NextNode();
568 while(m_currentNodeName == nnFaces ||
569 m_currentNodeName == nnGeometry ||
570 m_currentNodeName == nnTextures ||
571 m_currentNodeName == nnBoneAssignments)
572 {
573 if (m_currentNodeName == nnFaces)
574 {
575 submesh->indexData->faceCount = ReadAttribute<uint32_t>(anCount);
576 submesh->indexData->faces.reserve(submesh->indexData->faceCount);
577
578 NextNode();
579 while(m_currentNodeName == nnFace)
580 {
581 aiFace face;
582 face.mNumIndices = 3;
583 face.mIndices = new unsigned int[3];
584 face.mIndices[0] = ReadAttribute<uint32_t>(anV1);
585 face.mIndices[1] = ReadAttribute<uint32_t>(anV2);
586 face.mIndices[2] = ReadAttribute<uint32_t>(anV3);
587
588 /// @todo Support quads if Ogre even supports them in XML (I'm not sure but I doubt it)
589 if (!quadWarned && HasAttribute(anV4)) {
590 DefaultLogger::get()->warn("Submesh <face> has quads with <v4>, only triangles are supported at the moment!");
591 quadWarned = true;
592 }
593
594 submesh->indexData->faces.push_back(face);
595
596 // Advance
597 NextNode();
598 }
599
600 if (submesh->indexData->faces.size() == submesh->indexData->faceCount)
601 {
602 DefaultLogger::get()->debug(Formatter::format() << " - Faces " << submesh->indexData->faceCount);
603 }
604 else
605 {
606 throw DeadlyImportError(Formatter::format() << "Read only " << submesh->indexData->faces.size() << " faces when should have read " << submesh->indexData->faceCount);
607 }
608 }
609 else if (m_currentNodeName == nnGeometry)
610 {
611 if (submesh->usesSharedVertexData) {
612 throw DeadlyImportError("Found <geometry> in <submesh> when use shared geometry is true. Invalid mesh file.");
613 }
614
615 submesh->vertexData = new VertexDataXml();
616 ReadGeometry(submesh->vertexData);
617 }
618 else if (m_currentNodeName == nnBoneAssignments)
619 {
620 ReadBoneAssignments(submesh->vertexData);
621 }
622 // Assimp incompatible/ignored nodes
623 else
624 SkipCurrentNode();
625 }
626
627 submesh->index = static_cast<unsigned int>(mesh->subMeshes.size());
628 mesh->subMeshes.push_back(submesh);
629}
630
631void OgreXmlSerializer::ReadBoneAssignments(VertexDataXml *dest)
632{
633 if (!dest) {
634 throw DeadlyImportError("Cannot read bone assignments, vertex data is null.");
635 }
636
637 static const std::string anVertexIndex = "vertexindex";
638 static const std::string anBoneIndex = "boneindex";
639 static const std::string anWeight = "weight";
640
641 std::set<uint32_t> influencedVertices;
642
643 NextNode();
644 while(m_currentNodeName == nnVertexBoneAssignment)
645 {
646 VertexBoneAssignment ba;
647 ba.vertexIndex = ReadAttribute<uint32_t>(anVertexIndex);
648 ba.boneIndex = ReadAttribute<uint16_t>(anBoneIndex);
649 ba.weight = ReadAttribute<float>(anWeight);
650
651 dest->boneAssignments.push_back(ba);
652 influencedVertices.insert(ba.vertexIndex);
653
654 NextNode();
655 }
656
657 /** Normalize bone weights.
658 Some exporters won't care if the sum of all bone weights
659 for a single vertex equals 1 or not, so validate here. */
660 const float epsilon = 0.05f;
661 for (const uint32_t vertexIndex : influencedVertices)
662 {
663 float sum = 0.0f;
664 for (VertexBoneAssignmentList::const_iterator baIter=dest->boneAssignments.begin(), baEnd=dest->boneAssignments.end(); baIter != baEnd; ++baIter)
665 {
666 if (baIter->vertexIndex == vertexIndex)
667 sum += baIter->weight;
668 }
669 if ((sum < (1.0f - epsilon)) || (sum > (1.0f + epsilon)))
670 {
671 for (auto &boneAssign : dest->boneAssignments)
672 {
673 if (boneAssign.vertexIndex == vertexIndex)
674 boneAssign.weight /= sum;
675 }
676 }
677 }
678
679 DefaultLogger::get()->debug(Formatter::format() << " - " << dest->boneAssignments.size() << " bone assignments");
680}
681
682// Skeleton
683
684bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh)
685{
686 if (!mesh || mesh->skeletonRef.empty())
687 return false;
688
689 // Highly unusual to see in read world cases but support
690 // XML mesh referencing a binary skeleton file.
691 if (EndsWith(mesh->skeletonRef, ".skeleton", false))
692 {
693 if (OgreBinarySerializer::ImportSkeleton(pIOHandler, mesh))
694 return true;
695
696 /** Last fallback if .skeleton failed to be read. Try reading from
697 .skeleton.xml even if the XML file referenced a binary skeleton.
698 @note This logic was in the previous version and I don't want to break
699 old code that might depends on it. */
700 mesh->skeletonRef = mesh->skeletonRef + ".xml";
701 }
702
703 XmlReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef);
704 if (!reader.get())
705 return false;
706
707 Skeleton *skeleton = new Skeleton();
708 OgreXmlSerializer serializer(reader.get());
709 serializer.ReadSkeleton(skeleton);
710 mesh->skeleton = skeleton;
711 return true;
712}
713
714bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh)
715{
716 if (!mesh || mesh->skeletonRef.empty())
717 return false;
718
719 XmlReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef);
720 if (!reader.get())
721 return false;
722
723 Skeleton *skeleton = new Skeleton();
724 OgreXmlSerializer serializer(reader.get());
725 serializer.ReadSkeleton(skeleton);
726 mesh->skeleton = skeleton;
727 return true;
728}
729
730XmlReaderPtr OgreXmlSerializer::OpenReader(Assimp::IOSystem *pIOHandler, const std::string &filename)
731{
732 if (!EndsWith(filename, ".skeleton.xml", false))
733 {
734 DefaultLogger::get()->error("Imported Mesh is referencing to unsupported '" + filename + "' skeleton file.");
735 return XmlReaderPtr();
736 }
737
738 if (!pIOHandler->Exists(filename))
739 {
740 DefaultLogger::get()->error("Failed to find skeleton file '" + filename + "' that is referenced by imported Mesh.");
741 return XmlReaderPtr();
742 }
743
744 std::unique_ptr<IOStream> file(pIOHandler->Open(filename));
745 if (!file.get()) {
746 throw DeadlyImportError("Failed to open skeleton file " + filename);
747 }
748
749 std::unique_ptr<CIrrXML_IOStreamReader> stream(new CIrrXML_IOStreamReader(file.get()));
750 XmlReaderPtr reader = XmlReaderPtr(irr::io::createIrrXMLReader(stream.get()));
751 if (!reader.get()) {
752 throw DeadlyImportError("Failed to create XML reader for skeleton file " + filename);
753 }
754 return reader;
755}
756
757void OgreXmlSerializer::ReadSkeleton(Skeleton *skeleton)
758{
759 if (NextNode() != nnSkeleton) {
760 throw DeadlyImportError("Root node is <" + m_currentNodeName + "> expecting <skeleton>");
761 }
762
763 DefaultLogger::get()->debug("Reading Skeleton");
764
765 // Optional blend mode from root node
766 if (HasAttribute("blendmode")) {
767 skeleton->blendMode = (ToLower(ReadAttribute<std::string>("blendmode")) == "cumulative"
768 ? Skeleton::ANIMBLEND_CUMULATIVE : Skeleton::ANIMBLEND_AVERAGE);
769 }
770
771 NextNode();
772
773 // Root level nodes
774 while(m_currentNodeName == nnBones ||
775 m_currentNodeName == nnBoneHierarchy ||
776 m_currentNodeName == nnAnimations ||
777 m_currentNodeName == nnAnimationLinks)
778 {
779 if (m_currentNodeName == nnBones)
780 ReadBones(skeleton);
781 else if (m_currentNodeName == nnBoneHierarchy)
782 ReadBoneHierarchy(skeleton);
783 else if (m_currentNodeName == nnAnimations)
784 ReadAnimations(skeleton);
785 else
786 SkipCurrentNode();
787 }
788}
789
790void OgreXmlSerializer::ReadAnimations(Skeleton *skeleton)
791{
792 if (skeleton->bones.empty()) {
793 throw DeadlyImportError("Cannot read <animations> for a Skeleton without bones");
794 }
795
796 DefaultLogger::get()->debug(" - Animations");
797
798 NextNode();
799 while(m_currentNodeName == nnAnimation)
800 {
801 Animation *anim = new Animation(skeleton);
802 anim->name = ReadAttribute<std::string>("name");
803 anim->length = ReadAttribute<float>("length");
804
805 if (NextNode() != nnTracks) {
806 throw DeadlyImportError(Formatter::format() << "No <tracks> found in <animation> " << anim->name);
807 }
808
809 ReadAnimationTracks(anim);
810 skeleton->animations.push_back(anim);
811
812 DefaultLogger::get()->debug(Formatter::format() << " " << anim->name << " (" << anim->length << " sec, " << anim->tracks.size() << " tracks)");
813 }
814}
815
816void OgreXmlSerializer::ReadAnimationTracks(Animation *dest)
817{
818 NextNode();
819 while(m_currentNodeName == nnTrack)
820 {
821 VertexAnimationTrack track;
822 track.type = VertexAnimationTrack::VAT_TRANSFORM;
823 track.boneName = ReadAttribute<std::string>("bone");
824
825 if (NextNode() != nnKeyFrames) {
826 throw DeadlyImportError(Formatter::format() << "No <keyframes> found in <track> " << dest->name);
827 }
828
829 ReadAnimationKeyFrames(dest, &track);
830
831 dest->tracks.push_back(track);
832 }
833}
834
835void OgreXmlSerializer::ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *dest)
836{
837 const aiVector3D zeroVec(0.f, 0.f, 0.f);
838
839 NextNode();
840 while(m_currentNodeName == nnKeyFrame)
841 {
842 TransformKeyFrame keyframe;
843 keyframe.timePos = ReadAttribute<float>("time");
844
845 NextNode();
846 while(m_currentNodeName == nnTranslate || m_currentNodeName == nnRotate || m_currentNodeName == nnScale)
847 {
848 if (m_currentNodeName == nnTranslate)
849 {
850 keyframe.position.x = ReadAttribute<float>(anX);
851 keyframe.position.y = ReadAttribute<float>(anY);
852 keyframe.position.z = ReadAttribute<float>(anZ);
853 }
854 else if (m_currentNodeName == nnRotate)
855 {
856 float angle = ReadAttribute<float>("angle");
857
858 if (NextNode() != nnAxis) {
859 throw DeadlyImportError("No axis specified for keyframe rotation in animation " + anim->name);
860 }
861
862 aiVector3D axis;
863 axis.x = ReadAttribute<float>(anX);
864 axis.y = ReadAttribute<float>(anY);
865 axis.z = ReadAttribute<float>(anZ);
866 if (axis.Equal(zeroVec))
867 {
868 axis.x = 1.0f;
869 if (angle != 0) {
870 DefaultLogger::get()->warn("Found invalid a key frame with a zero rotation axis in animation: " + anim->name);
871 }
872 }
873 keyframe.rotation = aiQuaternion(axis, angle);
874 }
875 else if (m_currentNodeName == nnScale)
876 {
877 keyframe.scale.x = ReadAttribute<float>(anX);
878 keyframe.scale.y = ReadAttribute<float>(anY);
879 keyframe.scale.z = ReadAttribute<float>(anZ);
880 }
881
882 NextNode();
883 }
884
885 dest->transformKeyFrames.push_back(keyframe);
886 }
887}
888
889void OgreXmlSerializer::ReadBoneHierarchy(Skeleton *skeleton)
890{
891 if (skeleton->bones.empty()) {
892 throw DeadlyImportError("Cannot read <bonehierarchy> for a Skeleton without bones");
893 }
894
895 while(NextNode() == nnBoneParent)
896 {
897 const std::string name = ReadAttribute<std::string>("bone");
898 const std::string parentName = ReadAttribute<std::string>("parent");
899
900 Bone *bone = skeleton->BoneByName(name);
901 Bone *parent = skeleton->BoneByName(parentName);
902
903 if (bone && parent)
904 parent->AddChild(bone);
905 else
906 throw DeadlyImportError("Failed to find bones for parenting: Child " + name + " for parent " + parentName);
907 }
908
909 // Calculate bone matrices for root bones. Recursively calculates their children.
910 for (size_t i=0, len=skeleton->bones.size(); i<len; ++i)
911 {
912 Bone *bone = skeleton->bones[i];
913 if (!bone->IsParented())
914 bone->CalculateWorldMatrixAndDefaultPose(skeleton);
915 }
916}
917
918bool BoneCompare(Bone *a, Bone *b)
919{
920 return (a->id < b->id);
921}
922
923void OgreXmlSerializer::ReadBones(Skeleton *skeleton)
924{
925 DefaultLogger::get()->debug(" - Bones");
926
927 NextNode();
928 while(m_currentNodeName == nnBone)
929 {
930 Bone *bone = new Bone();
931 bone->id = ReadAttribute<uint16_t>("id");
932 bone->name = ReadAttribute<std::string>("name");
933
934 NextNode();
935 while(m_currentNodeName == nnPosition ||
936 m_currentNodeName == nnRotation ||
937 m_currentNodeName == nnScale)
938 {
939 if (m_currentNodeName == nnPosition)
940 {
941 bone->position.x = ReadAttribute<float>(anX);
942 bone->position.y = ReadAttribute<float>(anY);
943 bone->position.z = ReadAttribute<float>(anZ);
944 }
945 else if (m_currentNodeName == nnRotation)
946 {
947 float angle = ReadAttribute<float>("angle");
948
949 if (NextNode() != nnAxis) {
950 throw DeadlyImportError(Formatter::format() << "No axis specified for bone rotation in bone " << bone->id);
951 }
952
953 aiVector3D axis;
954 axis.x = ReadAttribute<float>(anX);
955 axis.y = ReadAttribute<float>(anY);
956 axis.z = ReadAttribute<float>(anZ);
957
958 bone->rotation = aiQuaternion(axis, angle);
959 }
960 else if (m_currentNodeName == nnScale)
961 {
962 /// @todo Implement taking scale into account in matrix/pose calculations!
963 if (HasAttribute("factor"))
964 {
965 float factor = ReadAttribute<float>("factor");
966 bone->scale.Set(factor, factor, factor);
967 }
968 else
969 {
970 if (HasAttribute(anX))
971 bone->scale.x = ReadAttribute<float>(anX);
972 if (HasAttribute(anY))
973 bone->scale.y = ReadAttribute<float>(anY);
974 if (HasAttribute(anZ))
975 bone->scale.z = ReadAttribute<float>(anZ);
976 }
977 }
978
979 NextNode();
980 }
981
982 skeleton->bones.push_back(bone);
983 }
984
985 // Order bones by Id
986 std::sort(skeleton->bones.begin(), skeleton->bones.end(), BoneCompare);
987
988 // Validate that bone indexes are not skipped.
989 /** @note Left this from original authors code, but not sure if this is strictly necessary
990 as per the Ogre skeleton spec. It might be more that other (later) code in this imported does not break. */
991 for (size_t i=0, len=skeleton->bones.size(); i<len; ++i)
992 {
993 Bone *b = skeleton->bones[i];
994 DefaultLogger::get()->debug(Formatter::format() << " " << b->id << " " << b->name);
995
996 if (b->id != static_cast<uint16_t>(i)) {
997 throw DeadlyImportError(Formatter::format() << "Bone ids are not in sequence starting from 0. Missing index " << i);
998 }
999 }
1000}
1001
1002} // Ogre
1003} // Assimp
1004
1005#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER
1006