From cf9b705829eb0d92f738b8b37c389fbec84a6add Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Wed, 21 May 2014 00:09:30 +0300 Subject: [PATCH] OgreImporter: Move skeleton functions to the end of file (after mesh functions). Put all constant to same place in the file. Implement support for 'animationlinks' (skipped) and bone 'scale' (read to Bone but not utilized atm). --- code/OgreStructs.cpp | 3 +- code/OgreStructs.h | 1 + code/OgreXmlSerializer.cpp | 592 ++++++++++++++++++++----------------- code/OgreXmlSerializer.h | 6 + 4 files changed, 323 insertions(+), 279 deletions(-) diff --git a/code/OgreStructs.cpp b/code/OgreStructs.cpp index e81b0f0a6..154ee343b 100644 --- a/code/OgreStructs.cpp +++ b/code/OgreStructs.cpp @@ -995,7 +995,8 @@ Bone::Bone() : id(0), parent(0), parentId(-1), - rotationAngle(0.0f) + rotationAngle(0.0f), + scale(1.0f, 1.0f, 1.0f) { } diff --git a/code/OgreStructs.h b/code/OgreStructs.h index 8a2e40729..93d1d61c8 100644 --- a/code/OgreStructs.h +++ b/code/OgreStructs.h @@ -436,6 +436,7 @@ public: aiVector3D position; aiVector3D rotation; + aiVector3D scale; ///< @todo Implement taking scale into account in matrix/pose calculations! float rotationAngle; aiMatrix4x4 worldMatrix; diff --git a/code/OgreXmlSerializer.cpp b/code/OgreXmlSerializer.cpp index 76da89c8f..9b8a4834e 100644 --- a/code/OgreXmlSerializer.cpp +++ b/code/OgreXmlSerializer.cpp @@ -222,6 +222,8 @@ std::string &OgreXmlSerializer::SkipCurrentNode() return NextNode(); } +// Mesh XML constants + // const std::string nnMesh = "mesh"; const std::string nnSharedGeometry = "sharedgeometry"; @@ -259,6 +261,42 @@ const std::string nnColorSpecular = "colour_specular"; // const std::string nnVertexBoneAssignment = "vertexboneassignment"; +// Skeleton XML constants + +// +const std::string nnSkeleton = "skeleton"; +const std::string nnBones = "bones"; +const std::string nnBoneHierarchy = "bonehierarchy"; +const std::string nnAnimationLinks = "animationlinks"; + +// +const std::string nnBone = "bone"; +const std::string nnRotation = "rotation"; +const std::string nnAxis = "axis"; +const std::string nnScale = "scale"; + +// +const std::string nnBoneParent = "boneparent"; + +// +const std::string nnAnimation = "animation"; +const std::string nnTracks = "tracks"; + +// +const std::string nnTrack = "track"; +const std::string nnKeyFrames = "keyframes"; +const std::string nnKeyFrame = "keyframe"; +const std::string nnTranslate = "translate"; +const std::string nnRotate = "rotate"; + +// Common XML constants + +const std::string anX = "x"; +const std::string anY = "y"; +const std::string anZ = "z"; + +// Mesh + MeshXml *OgreXmlSerializer::ImportMesh(XmlReader *reader) { OgreXmlSerializer serializer(reader); @@ -268,284 +306,6 @@ MeshXml *OgreXmlSerializer::ImportMesh(XmlReader *reader) return mesh; } -void OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh) -{ - if (mesh->skeletonRef.empty()) - return; - - /** @todo Also support referencing a binary skeleton from a XML mesh? - This will involves new interfacing to cross ref from MeshXml... */ - - std::string filename = mesh->skeletonRef; - if (EndsWith(filename, ".skeleton")) - { - DefaultLogger::get()->warn("Mesh is referencing a Ogre binary skeleton. Parsing binary Ogre assets is not supported at the moment. Trying to find .skeleton.xml file instead."); - filename += ".xml"; - } - - if (!pIOHandler->Exists(filename)) - { - DefaultLogger::get()->error("Failed to find skeleton file '" + filename + "', skeleton will be missing."); - return; - } - - boost::scoped_ptr file(pIOHandler->Open(filename)); - if (!file.get()) { - throw DeadlyImportError("Failed to open skeleton file " + filename); - } - - boost::scoped_ptr stream(new CIrrXML_IOStreamReader(file.get())); - XmlReader* reader = irr::io::createIrrXMLReader(stream.get()); - if (!reader) { - throw DeadlyImportError("Failed to create XML reader for skeleton file " + filename); - } - - Skeleton *skeleton = new Skeleton(); - - OgreXmlSerializer serializer(reader); - serializer.ReadSkeleton(skeleton); - - mesh->skeleton = skeleton; -} - -// -const std::string nnSkeleton = "skeleton"; -const std::string nnBones = "bones"; -const std::string nnBoneHierarchy = "bonehierarchy"; - -// -const std::string nnBone = "bone"; -const std::string nnRotation = "rotation"; -const std::string nnAxis = "axis"; - -// -const std::string nnBoneParent = "boneparent"; - -// -const std::string nnAnimation = "animation"; -const std::string nnTracks = "tracks"; - -// -const std::string nnTrack = "track"; -const std::string nnKeyFrames = "keyframes"; -const std::string nnKeyFrame = "keyframe"; -const std::string nnTranslate = "translate"; -const std::string nnRotate = "rotate"; -const std::string nnScale = "scale"; - -const std::string anX = "x"; -const std::string anY = "y"; -const std::string anZ = "z"; - -void OgreXmlSerializer::ReadSkeleton(Skeleton *skeleton) -{ - if (NextNode() != nnSkeleton) { - throw DeadlyImportError("Root node is <" + m_currentNodeName + "> expecting "); - } - - DefaultLogger::get()->debug("Reading Skeleton"); - - NextNode(); - - // Root level nodes - while(m_currentNodeName == nnBones || - m_currentNodeName == nnBoneHierarchy || - m_currentNodeName == nnAnimations) - { - if (m_currentNodeName == nnBones) - ReadBones(skeleton); - else if (m_currentNodeName == nnBoneHierarchy) - ReadBoneHierarchy(skeleton); - else if (m_currentNodeName == nnAnimations) - ReadAnimations(skeleton); - } -} - -void OgreXmlSerializer::ReadAnimations(Skeleton *skeleton) -{ - DefaultLogger::get()->debug(" - Animations"); - - NextNode(); - while(m_currentNodeName == nnAnimation) - { - Animation *anim = new Animation(skeleton); - anim->name = ReadAttribute("name"); - anim->length = ReadAttribute("length"); - - if (NextNode() != nnTracks) { - throw DeadlyImportError(Formatter::format() << "No found in " << anim->name); - } - - ReadAnimationTracks(anim); - skeleton->animations.push_back(anim); - - DefaultLogger::get()->debug(Formatter::format() << " " << anim->name << " (" << anim->length << " sec, " << anim->tracks.size() << " tracks)"); - } -} - -void OgreXmlSerializer::ReadAnimationTracks(Animation *dest) -{ - NextNode(); - while(m_currentNodeName == nnTrack) - { - VertexAnimationTrack track; - track.type = VertexAnimationTrack::VAT_TRANSFORM; - track.boneName = ReadAttribute("bone"); - - if (NextNode() != nnKeyFrames) { - throw DeadlyImportError(Formatter::format() << "No found in " << dest->name); - } - - ReadAnimationKeyFrames(dest, &track); - - dest->tracks.push_back(track); - } -} - -void OgreXmlSerializer::ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *dest) -{ - const aiVector3D zeroVec(0.f, 0.f, 0.f); - - NextNode(); - while(m_currentNodeName == nnKeyFrame) - { - TransformKeyFrame keyframe; - keyframe.timePos = ReadAttribute("time"); - - NextNode(); - while(m_currentNodeName == nnTranslate || m_currentNodeName == nnRotate || m_currentNodeName == nnScale) - { - if (m_currentNodeName == nnTranslate) - { - keyframe.position.x = ReadAttribute(anX); - keyframe.position.y = ReadAttribute(anY); - keyframe.position.z = ReadAttribute(anZ); - } - else if (m_currentNodeName == nnRotate) - { - float angle = ReadAttribute("angle"); - - if (NextNode() != nnAxis) { - throw DeadlyImportError("No axis specified for keyframe rotation in animation " + anim->name); - } - - aiVector3D axis; - axis.x = ReadAttribute(anX); - axis.y = ReadAttribute(anY); - axis.z = ReadAttribute(anZ); - if (axis.Equal(zeroVec)) - { - axis.x = 1.0f; - if (angle != 0) { - DefaultLogger::get()->warn("Found invalid a key frame with a zero rotation axis in animation: " + anim->name); - } - } - keyframe.rotation = aiQuaternion(axis, angle); - } - else if (m_currentNodeName == nnScale) - { - keyframe.scale.x = ReadAttribute(anX); - keyframe.scale.y = ReadAttribute(anY); - keyframe.scale.z = ReadAttribute(anZ); - } - - NextNode(); - } - - dest->transformKeyFrames.push_back(keyframe); - } -} - -void OgreXmlSerializer::ReadBoneHierarchy(Skeleton *skeleton) -{ - if (skeleton->bones.empty()) { - throw DeadlyImportError("Cannot read for Skeleton without bones"); - } - - while(NextNode() == nnBoneParent) - { - const std::string name = ReadAttribute("bone"); - const std::string parentName = ReadAttribute("parent"); - - Bone *bone = skeleton->BoneByName(name); - Bone *parent = skeleton->BoneByName(parentName); - - if (bone && parent) - parent->AddChild(bone); - else - DefaultLogger::get()->warn("Failed to find bones for parenting: Child " + name + " for parent " + parentName); - } - - // Calculate bone matrices for root bones. Recursively calcutes their children. - for (size_t i=0, len=skeleton->bones.size(); ibones[i]; - if (!bone->IsParented()) - bone->CalculateWorldMatrixAndDefaultPose(skeleton); - } -} - -bool BoneCompare(Bone *a, Bone *b) -{ - return (a->id < b->id); -} - -void OgreXmlSerializer::ReadBones(Skeleton *skeleton) -{ - DefaultLogger::get()->debug(" - Bones"); - - NextNode(); - while(m_currentNodeName == nnBone) - { - Bone *bone = new Bone(); - bone->id = ReadAttribute("id"); - bone->name = ReadAttribute("name"); - - NextNode(); - while(m_currentNodeName == nnPosition || m_currentNodeName == nnRotation) - { - if (m_currentNodeName == nnPosition) - { - bone->position.x = ReadAttribute(anX); - bone->position.y = ReadAttribute(anY); - bone->position.z = ReadAttribute(anZ); - } - else if (m_currentNodeName == nnRotation) - { - bone->rotationAngle = ReadAttribute("angle"); - - if (NextNode() != nnAxis) { - throw DeadlyImportError(Formatter::format() << "No axis specified for bone rotation in bone " << bone->id); - } - - bone->rotation.x = ReadAttribute(anX); - bone->rotation.y = ReadAttribute(anY); - bone->rotation.z = ReadAttribute(anZ); - } - - NextNode(); - } - - skeleton->bones.push_back(bone); - } - - // Order bones by Id - std::sort(skeleton->bones.begin(), skeleton->bones.end(), BoneCompare); - - // Validate that bone indexes are not skipped. - /** @note Left this from original authors code, but not sure if this is strictly necessary - as per the Ogre skeleton spec. It might be more that other (later) code in this imported does not break. */ - for (size_t i=0, len=skeleton->bones.size(); ibones[i]; - DefaultLogger::get()->debug(Formatter::format() << " " << b->id << " " << b->name); - - if (b->id != static_cast(i)) { - throw DeadlyImportError(Formatter::format() << "Bone ids are not in sequence starting from 0. Missing index " << i); - } - } -} - void OgreXmlSerializer::ReadMesh(MeshXml *mesh) { if (NextNode() != nnMesh) { @@ -911,6 +671,282 @@ void OgreXmlSerializer::ReadBoneAssignments(VertexDataXml *dest) DefaultLogger::get()->debug(Formatter::format() << " - " << dest->boneAssignments.size() << " bone assignments"); } +// Skeleton + +void OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh) +{ + if (mesh->skeletonRef.empty()) + return; + + /** @todo Also support referencing a binary skeleton from a XML mesh? + This will involves new interfacing to cross ref from MeshXml... */ + + std::string filename = mesh->skeletonRef; + if (EndsWith(filename, ".skeleton")) + { + DefaultLogger::get()->warn("Mesh is referencing a Ogre binary skeleton. Parsing binary Ogre assets is not supported at the moment. Trying to find .skeleton.xml file instead."); + filename += ".xml"; + } + + if (!pIOHandler->Exists(filename)) + { + DefaultLogger::get()->error("Failed to find skeleton file '" + filename + "' that is referenced by imported Mesh."); + return; + } + + boost::scoped_ptr file(pIOHandler->Open(filename)); + if (!file.get()) { + throw DeadlyImportError("Failed to open skeleton file " + filename); + } + + boost::scoped_ptr stream(new CIrrXML_IOStreamReader(file.get())); + XmlReader* reader = irr::io::createIrrXMLReader(stream.get()); + if (!reader) { + throw DeadlyImportError("Failed to create XML reader for skeleton file " + filename); + } + + Skeleton *skeleton = new Skeleton(); + OgreXmlSerializer serializer(reader); + serializer.ReadSkeleton(skeleton); + mesh->skeleton = skeleton; +} + +void OgreXmlSerializer::ReadSkeleton(Skeleton *skeleton) +{ + if (NextNode() != nnSkeleton) { + throw DeadlyImportError("Root node is <" + m_currentNodeName + "> expecting "); + } + + DefaultLogger::get()->debug("Reading Skeleton"); + + NextNode(); + + // Root level nodes + while(m_currentNodeName == nnBones || + m_currentNodeName == nnBoneHierarchy || + m_currentNodeName == nnAnimations || + m_currentNodeName == nnAnimationLinks) + { + if (m_currentNodeName == nnBones) + ReadBones(skeleton); + else if (m_currentNodeName == nnBoneHierarchy) + ReadBoneHierarchy(skeleton); + else if (m_currentNodeName == nnAnimations) + ReadAnimations(skeleton); + else + SkipCurrentNode(); + } +} + +void OgreXmlSerializer::ReadAnimations(Skeleton *skeleton) +{ + if (skeleton->bones.empty()) { + throw DeadlyImportError("Cannot read for a Skeleton without bones"); + } + + DefaultLogger::get()->debug(" - Animations"); + + NextNode(); + while(m_currentNodeName == nnAnimation) + { + Animation *anim = new Animation(skeleton); + anim->name = ReadAttribute("name"); + anim->length = ReadAttribute("length"); + + if (NextNode() != nnTracks) { + throw DeadlyImportError(Formatter::format() << "No found in " << anim->name); + } + + ReadAnimationTracks(anim); + skeleton->animations.push_back(anim); + + DefaultLogger::get()->debug(Formatter::format() << " " << anim->name << " (" << anim->length << " sec, " << anim->tracks.size() << " tracks)"); + } +} + +void OgreXmlSerializer::ReadAnimationTracks(Animation *dest) +{ + NextNode(); + while(m_currentNodeName == nnTrack) + { + VertexAnimationTrack track; + track.type = VertexAnimationTrack::VAT_TRANSFORM; + track.boneName = ReadAttribute("bone"); + + if (NextNode() != nnKeyFrames) { + throw DeadlyImportError(Formatter::format() << "No found in " << dest->name); + } + + ReadAnimationKeyFrames(dest, &track); + + dest->tracks.push_back(track); + } +} + +void OgreXmlSerializer::ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *dest) +{ + const aiVector3D zeroVec(0.f, 0.f, 0.f); + + NextNode(); + while(m_currentNodeName == nnKeyFrame) + { + TransformKeyFrame keyframe; + keyframe.timePos = ReadAttribute("time"); + + NextNode(); + while(m_currentNodeName == nnTranslate || m_currentNodeName == nnRotate || m_currentNodeName == nnScale) + { + if (m_currentNodeName == nnTranslate) + { + keyframe.position.x = ReadAttribute(anX); + keyframe.position.y = ReadAttribute(anY); + keyframe.position.z = ReadAttribute(anZ); + } + else if (m_currentNodeName == nnRotate) + { + float angle = ReadAttribute("angle"); + + if (NextNode() != nnAxis) { + throw DeadlyImportError("No axis specified for keyframe rotation in animation " + anim->name); + } + + aiVector3D axis; + axis.x = ReadAttribute(anX); + axis.y = ReadAttribute(anY); + axis.z = ReadAttribute(anZ); + if (axis.Equal(zeroVec)) + { + axis.x = 1.0f; + if (angle != 0) { + DefaultLogger::get()->warn("Found invalid a key frame with a zero rotation axis in animation: " + anim->name); + } + } + keyframe.rotation = aiQuaternion(axis, angle); + } + else if (m_currentNodeName == nnScale) + { + keyframe.scale.x = ReadAttribute(anX); + keyframe.scale.y = ReadAttribute(anY); + keyframe.scale.z = ReadAttribute(anZ); + } + + NextNode(); + } + + dest->transformKeyFrames.push_back(keyframe); + } +} + +void OgreXmlSerializer::ReadBoneHierarchy(Skeleton *skeleton) +{ + if (skeleton->bones.empty()) { + throw DeadlyImportError("Cannot read for a Skeleton without bones"); + } + + while(NextNode() == nnBoneParent) + { + const std::string name = ReadAttribute("bone"); + const std::string parentName = ReadAttribute("parent"); + + Bone *bone = skeleton->BoneByName(name); + Bone *parent = skeleton->BoneByName(parentName); + + if (bone && parent) + parent->AddChild(bone); + else + DefaultLogger::get()->warn("Failed to find bones for parenting: Child " + name + " for parent " + parentName); + } + + // Calculate bone matrices for root bones. Recursively calcutes their children. + for (size_t i=0, len=skeleton->bones.size(); ibones[i]; + if (!bone->IsParented()) + bone->CalculateWorldMatrixAndDefaultPose(skeleton); + } +} + +bool BoneCompare(Bone *a, Bone *b) +{ + return (a->id < b->id); +} + +void OgreXmlSerializer::ReadBones(Skeleton *skeleton) +{ + DefaultLogger::get()->debug(" - Bones"); + + NextNode(); + while(m_currentNodeName == nnBone) + { + Bone *bone = new Bone(); + bone->id = ReadAttribute("id"); + bone->name = ReadAttribute("name"); + + NextNode(); + while(m_currentNodeName == nnPosition || + m_currentNodeName == nnRotation || + m_currentNodeName == nnScale) + { + if (m_currentNodeName == nnPosition) + { + bone->position.x = ReadAttribute(anX); + bone->position.y = ReadAttribute(anY); + bone->position.z = ReadAttribute(anZ); + } + else if (m_currentNodeName == nnRotation) + { + bone->rotationAngle = ReadAttribute("angle"); + + if (NextNode() != nnAxis) { + throw DeadlyImportError(Formatter::format() << "No axis specified for bone rotation in bone " << bone->id); + } + + bone->rotation.x = ReadAttribute(anX); + bone->rotation.y = ReadAttribute(anY); + bone->rotation.z = ReadAttribute(anZ); + } + else if (m_currentNodeName == nnScale) + { + /// @todo Implement taking scale into account in matrix/pose calculations! + if (HasAttribute("factor")) + { + float factor = ReadAttribute("factor"); + bone->scale.Set(factor, factor, factor); + } + else + { + if (HasAttribute(anX)) + bone->scale.x = ReadAttribute(anX); + if (HasAttribute(anY)) + bone->scale.y = ReadAttribute(anY); + if (HasAttribute(anZ)) + bone->scale.z = ReadAttribute(anZ); + } + } + + NextNode(); + } + + skeleton->bones.push_back(bone); + } + + // Order bones by Id + std::sort(skeleton->bones.begin(), skeleton->bones.end(), BoneCompare); + + // Validate that bone indexes are not skipped. + /** @note Left this from original authors code, but not sure if this is strictly necessary + as per the Ogre skeleton spec. It might be more that other (later) code in this imported does not break. */ + for (size_t i=0, len=skeleton->bones.size(); ibones[i]; + DefaultLogger::get()->debug(Formatter::format() << " " << b->id << " " << b->name); + + if (b->id != static_cast(i)) { + throw DeadlyImportError(Formatter::format() << "Bone ids are not in sequence starting from 0. Missing index " << i); + } + } +} + } // Ogre } // Assimp diff --git a/code/OgreXmlSerializer.h b/code/OgreXmlSerializer.h index 35c03d388..561a715d1 100644 --- a/code/OgreXmlSerializer.h +++ b/code/OgreXmlSerializer.h @@ -58,7 +58,13 @@ typedef irr::io::IrrXMLReader XmlReader; class OgreXmlSerializer { public: + /// Imports mesh and returns the result. + /** @note Fatal unrecoverable errors will throw a DeadlyImportError. */ static MeshXml *ImportMesh(XmlReader *reader); + + /// Imports skeleton to @c mesh into MeshXML::skeleton. + /** If mesh does not have a skeleton reference or the skeleton file + cannot be found it is not a fatal DeadlyImportError. */ static void ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh); private: