From 1129ae5a6e72caf2c6c61a69983d9d2cbdfd7bbb Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Wed, 21 May 2014 04:00:11 +0300 Subject: [PATCH] OgreImporter: Implement binary skeleton serialization. Fix bone/animation matrix stuff to be simpler (aka read as Quats to internal structures). Cleanup code for pull request. --- code/OgreBinarySerializer.cpp | 301 ++++++++++++++++- code/OgreBinarySerializer.h | 594 ++++++++++++++++++++-------------- code/OgreImporter.cpp | 3 + code/OgreStructs.cpp | 82 +++-- code/OgreStructs.h | 21 +- code/OgreXmlSerializer.cpp | 93 ++++-- code/OgreXmlSerializer.h | 8 +- 7 files changed, 789 insertions(+), 313 deletions(-) diff --git a/code/OgreBinarySerializer.cpp b/code/OgreBinarySerializer.cpp index 90e0d8c9d..6674f4cee 100644 --- a/code/OgreBinarySerializer.cpp +++ b/code/OgreBinarySerializer.cpp @@ -39,6 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "OgreBinarySerializer.h" +#include "OgreXmlSerializer.h" +#include "OgreParsingUtils.h" + #include "TinyFormatter.h" #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER @@ -51,9 +54,15 @@ namespace Assimp namespace Ogre { -const std::string VERSION_1_8 = "[MeshSerializer_v1.8]"; -const unsigned short HEADER_CHUNK_ID = 0x1000; -const long MSTREAM_OVERHEAD_SIZE = sizeof(uint16_t) + sizeof(uint32_t); +const std::string MESH_VERSION_1_8 = "[MeshSerializer_v1.8]"; +const std::string SKELETON_VERSION_1_8 = "[Serializer_v1.80]"; +const std::string SKELETON_VERSION_1_1 = "[Serializer_v1.10]"; + +const unsigned short HEADER_CHUNK_ID = 0x1000; + +const long MSTREAM_OVERHEAD_SIZE = sizeof(uint16_t) + sizeof(uint32_t); +const long MSTREAM_BONE_SIZE_WITHOUT_SCALE = MSTREAM_OVERHEAD_SIZE + sizeof(unsigned short) + (sizeof(float) * 7); +const long MSTREAM_KEYFRAME_SIZE_WITHOUT_SCALE = MSTREAM_OVERHEAD_SIZE + (sizeof(float) * 8); template<> inline bool OgreBinarySerializer::Read() @@ -118,6 +127,16 @@ void OgreBinarySerializer::ReadVector(aiVector3D &vec) m_reader->CopyAndAdvance(&vec.x, sizeof(float)*3); } +void OgreBinarySerializer::ReadQuaternion(aiQuaternion &quat) +{ + float temp[4]; + m_reader->CopyAndAdvance(temp, sizeof(float)*4); + quat.x = temp[0]; + quat.y = temp[1]; + quat.z = temp[2]; + quat.w = temp[3]; +} + bool OgreBinarySerializer::AtEnd() const { return (m_reader->GetRemainingSize() == 0); @@ -152,7 +171,10 @@ uint16_t OgreBinarySerializer::ReadHeader(bool readLen) #if (OGRE_BINARY_SERIALIZER_DEBUG == 1) if (id != HEADER_CHUNK_ID) - DefaultLogger::get()->debug(Formatter::format() << MeshHeaderToString(static_cast(id))); + { + DefaultLogger::get()->debug(Formatter::format() << (assetMode == AM_Mesh + ? MeshHeaderToString(static_cast(id)) : SkeletonHeaderToString(static_cast(id)))); + } #endif return id; @@ -172,9 +194,11 @@ void OgreBinarySerializer::SkipBytes(size_t numBytes) m_reader->IncPtr(numBytes); } +// Mesh + Mesh *OgreBinarySerializer::ImportMesh(MemoryStreamReader *stream) { - OgreBinarySerializer serializer(stream); + OgreBinarySerializer serializer(stream, OgreBinarySerializer::AM_Mesh); uint16_t id = serializer.ReadHeader(false); if (id != HEADER_CHUNK_ID) { @@ -183,8 +207,11 @@ Mesh *OgreBinarySerializer::ImportMesh(MemoryStreamReader *stream) /// @todo Check what we can actually support. std::string version = serializer.ReadLine(); - if (version != VERSION_1_8) - throw DeadlyExportError("Mesh version " + version + " not supported by this importer. Run OgreMeshUpgrader tool on the file and try again."); + if (version != MESH_VERSION_1_8) + { + throw DeadlyExportError(Formatter::format() << "Mesh version " << version << " not supported by this importer. Run OgreMeshUpgrader tool on the file and try again." + << " Supported versions: " << MESH_VERSION_1_8); + } Mesh *mesh = new Mesh(); while (!serializer.AtEnd()) @@ -732,8 +759,7 @@ void OgreBinarySerializer::ReadAnimations(Mesh *mesh) } void OgreBinarySerializer::ReadAnimation(Animation *anim) -{ - +{ if (!AtEnd()) { uint16_t id = ReadHeader(); @@ -821,6 +847,263 @@ void OgreBinarySerializer::ReadAnimationKeyFrames(Animation *anim, VertexAnimati } } +// Skeleton + +bool OgreBinarySerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh) +{ + if (!mesh || mesh->skeletonRef.empty()) + return false; + + // Highly unusual to see in read world cases but support + // binary mesh referencing a XML skeleton file. + if (EndsWith(mesh->skeletonRef, ".skeleton.xml", false)) + { + OgreXmlSerializer::ImportSkeleton(pIOHandler, mesh); + return false; + } + + MemoryStreamReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef); + + Skeleton *skeleton = new Skeleton(); + OgreBinarySerializer serializer(reader.get(), OgreBinarySerializer::AM_Skeleton); + serializer.ReadSkeleton(skeleton); + mesh->skeleton = skeleton; + return true; +} + +bool OgreBinarySerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh) +{ + if (!mesh || mesh->skeletonRef.empty()) + return false; + + MemoryStreamReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef); + if (!reader.get()) + return false; + + Skeleton *skeleton = new Skeleton(); + OgreBinarySerializer serializer(reader.get(), OgreBinarySerializer::AM_Skeleton); + serializer.ReadSkeleton(skeleton); + mesh->skeleton = skeleton; + return true; +} + +MemoryStreamReaderPtr OgreBinarySerializer::OpenReader(Assimp::IOSystem *pIOHandler, const std::string &filename) +{ + if (!EndsWith(filename, ".skeleton", false)) + { + DefaultLogger::get()->error("Imported Mesh is referencing to unsupported '" + filename + "' skeleton file."); + return MemoryStreamReaderPtr(); + } + + if (!pIOHandler->Exists(filename)) + { + DefaultLogger::get()->error("Failed to find skeleton file '" + filename + "' that is referenced by imported Mesh."); + return MemoryStreamReaderPtr(); + } + + IOStream *f = pIOHandler->Open(filename, "rb"); + if (!f) { + throw DeadlyImportError("Failed to open skeleton file " + filename); + } + + return MemoryStreamReaderPtr(new MemoryStreamReader(f)); +} + +void OgreBinarySerializer::ReadSkeleton(Skeleton *skeleton) +{ + uint16_t id = ReadHeader(false); + if (id != HEADER_CHUNK_ID) { + throw DeadlyExportError("Invalid Ogre Skeleton file header."); + } + + // This deserialization supports both versions of the skeleton spec + std::string version = ReadLine(); + if (version != SKELETON_VERSION_1_8 && version != SKELETON_VERSION_1_1) + { + throw DeadlyExportError(Formatter::format() << "Skeleton version " << version << " not supported by this importer." + << " Supported versions: " << SKELETON_VERSION_1_8 << " and " << SKELETON_VERSION_1_1); + } + + DefaultLogger::get()->debug("Reading Skeleton"); + + bool firstBone = true; + bool firstAnim = true; + + while (!AtEnd()) + { + id = ReadHeader(); + switch(id) + { + case SKELETON_BLENDMODE: + { + skeleton->blendMode = static_cast(Read()); + break; + } + case SKELETON_BONE: + { + if (firstBone) + { + DefaultLogger::get()->debug(" - Bones"); + firstBone = false; + } + + ReadBone(skeleton); + break; + } + case SKELETON_BONE_PARENT: + { + ReadBoneParent(skeleton); + break; + } + case SKELETON_ANIMATION: + { + if (firstAnim) + { + DefaultLogger::get()->debug(" - Animations"); + firstAnim = false; + } + + ReadSkeletonAnimation(skeleton); + break; + } + case SKELETON_ANIMATION_LINK: + { + ReadSkeletonAnimationLink(skeleton); + break; + } + } + } + + // Calculate bone matrices for root bones. Recursively calculates their children. + for (size_t i=0, len=skeleton->bones.size(); ibones[i]; + if (!bone->IsParented()) + bone->CalculateWorldMatrixAndDefaultPose(skeleton); + } +} + +void OgreBinarySerializer::ReadBone(Skeleton *skeleton) +{ + Bone *bone = new Bone(); + bone->name = ReadLine(); + bone->id = Read(); + + // Pos and rot + ReadVector(bone->position); + ReadQuaternion(bone->rotation); + + // Scale (optional) + if (m_currentLen > MSTREAM_BONE_SIZE_WITHOUT_SCALE) + ReadVector(bone->scale); + + // Bone indexes need to start from 0 and be contiguous + if (bone->id != skeleton->bones.size()) { + throw DeadlyImportError(Formatter::format() << "Ogre Skeleton bone indexes not contiguous. Error at bone index " << bone->id); + } + + DefaultLogger::get()->debug(Formatter::format() << " " << bone->id << " " << bone->name); + + skeleton->bones.push_back(bone); +} + +void OgreBinarySerializer::ReadBoneParent(Skeleton *skeleton) +{ + uint16_t childId = Read(); + uint16_t parentId = Read(); + + Bone *child = skeleton->BoneById(childId); + Bone *parent = skeleton->BoneById(parentId); + + if (child && parent) + parent->AddChild(child); + else + throw DeadlyImportError(Formatter::format() << "Failed to find bones for parenting: Child id " << childId << " for parent id " << parentId); +} + +void OgreBinarySerializer::ReadSkeletonAnimation(Skeleton *skeleton) +{ + Animation *anim = new Animation(skeleton); + anim->name = ReadLine(); + anim->length = Read(); + + if (!AtEnd()) + { + uint16_t id = ReadHeader(); + if (id == SKELETON_ANIMATION_BASEINFO) + { + anim->baseName = ReadLine(); + anim->baseTime = Read(); + + // Advance to first track + id = ReadHeader(); + } + + while (!AtEnd() && id == SKELETON_ANIMATION_TRACK) + { + ReadSkeletonAnimationTrack(skeleton, anim); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + } + + skeleton->animations.push_back(anim); + + DefaultLogger::get()->debug(Formatter::format() << " " << anim->name << " (" << anim->length << " sec, " << anim->tracks.size() << " tracks)"); +} + +void OgreBinarySerializer::ReadSkeletonAnimationTrack(Skeleton *skeleton, Animation *dest) +{ + uint16_t boneId = Read(); + Bone *bone = dest->parentSkeleton->BoneById(boneId); + if (!bone) { + throw DeadlyImportError(Formatter::format() << "Cannot read animation track, target bone " << boneId << " not in target Skeleton"); + } + + VertexAnimationTrack track; + track.type = VertexAnimationTrack::VAT_TRANSFORM; + track.boneName = bone->name; + + uint16_t id = ReadHeader(); + while (!AtEnd() && id == SKELETON_ANIMATION_TRACK_KEYFRAME) + { + ReadSkeletonAnimationKeyFrame(&track); + + if (!AtEnd()) + id = ReadHeader(); + } + if (!AtEnd()) + RollbackHeader(); + + dest->tracks.push_back(track); +} + +void OgreBinarySerializer::ReadSkeletonAnimationKeyFrame(VertexAnimationTrack *dest) +{ + TransformKeyFrame keyframe; + keyframe.timePos = Read(); + + // Rot and pos + ReadQuaternion(keyframe.rotation); + ReadVector(keyframe.position); + + // Scale (optional) + if (m_currentLen > MSTREAM_KEYFRAME_SIZE_WITHOUT_SCALE) + ReadVector(keyframe.scale); + + dest->transformKeyFrames.push_back(keyframe); +} + +void OgreBinarySerializer::ReadSkeletonAnimationLink(Skeleton *skeleton) +{ + // Skip bounds, not compatible with Assimp. + ReadLine(); // skeleton name + SkipBytes(sizeof(float) * 3); // scale +} + } // Ogre } // Assimp diff --git a/code/OgreBinarySerializer.h b/code/OgreBinarySerializer.h index e430e487a..30d4c9d7a 100644 --- a/code/OgreBinarySerializer.h +++ b/code/OgreBinarySerializer.h @@ -49,263 +49,365 @@ namespace Assimp { namespace Ogre { - class OgreBinarySerializer + +typedef Assimp::StreamReaderLE MemoryStreamReader; +typedef boost::shared_ptr MemoryStreamReaderPtr; + +class OgreBinarySerializer +{ +public: + /// Imports mesh and returns the result. + /** @note Fatal unrecoverable errors will throw a DeadlyImportError. */ + static Mesh *ImportMesh(MemoryStreamReader *reader); + + /// Imports skeleton to @c mesh into Mesh::skeleton. + /** If mesh does not have a skeleton reference or the skeleton file + cannot be found it is not a fatal DeadlyImportError. */ + static bool ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh); + static bool ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh); + +private: + enum AssetMode { - public: - static Mesh *ImportMesh(MemoryStreamReader *reader); - - private: - OgreBinarySerializer(MemoryStreamReader *reader) : - m_reader(reader), - m_currentLen(0) - { - } - - bool AtEnd() const; - - void ReadMesh(Mesh *mesh); - void ReadMeshLodInfo(Mesh *mesh); - void ReadMeshSkeletonLink(Mesh *mesh); - void ReadMeshBounds(Mesh *mesh); - void ReadMeshExtremes(Mesh *mesh); - - void ReadSubMesh(Mesh *mesh); - void ReadSubMeshNames(Mesh *mesh); - void ReadSubMeshOperation(SubMesh *submesh); - void ReadSubMeshTextureAlias(SubMesh *submesh); - - void ReadBoneAssignment(VertexData *dest); - - void ReadGeometry(VertexData *dest); - void ReadGeometryVertexDeclaration(VertexData *dest); - void ReadGeometryVertexElement(VertexData *dest); - void ReadGeometryVertexBuffer(VertexData *dest); - - void ReadEdgeList(Mesh *mesh); - void ReadPoses(Mesh *mesh); - void ReadPoseVertices(Pose *pose); - - void ReadAnimations(Mesh *mesh); - void ReadAnimation(Animation *anim); - void ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *track); - - void NormalizeBoneWeights(VertexData *vertexData) const; - - uint16_t ReadHeader(bool readLen = true); - void RollbackHeader(); - - template - inline T Read(); - - void ReadBytes(char *dest, size_t numBytes); - void ReadBytes(uint8_t *dest, size_t numBytes); - void ReadBytes(void *dest, size_t numBytes); - uint8_t *ReadBytes(size_t numBytes); - - void ReadVector(aiVector3D &vec); - - std::string ReadString(size_t len); - std::string ReadLine(); - - void SkipBytes(size_t numBytes); - - uint32_t m_currentLen; - MemoryStreamReader *m_reader; + AM_Mesh, + AM_Skeleton }; - - enum MeshChunkId + + OgreBinarySerializer(MemoryStreamReader *reader, AssetMode mode) : + m_reader(reader), + m_currentLen(0), + assetMode(mode) { - M_HEADER = 0x1000, - // char* version : Version number check - M_MESH = 0x3000, - // bool skeletallyAnimated // important flag which affects h/w buffer policies - // Optional M_GEOMETRY chunk - M_SUBMESH = 0x4000, - // char* materialName - // bool useSharedVertices - // unsigned int indexCount - // bool indexes32Bit - // unsigned int* faceVertexIndices (indexCount) - // OR - // unsigned short* faceVertexIndices (indexCount) - // M_GEOMETRY chunk (Optional: present only if useSharedVertices = false) - M_SUBMESH_OPERATION = 0x4010, // optional, trilist assumed if missing - // unsigned short operationType - M_SUBMESH_BONE_ASSIGNMENT = 0x4100, - // Optional bone weights (repeating section) - // unsigned int vertexIndex; - // unsigned short boneIndex; - // float weight; - // Optional chunk that matches a texture name to an alias - // a texture alias is sent to the submesh material to use this texture name - // instead of the one in the texture unit with a matching alias name - M_SUBMESH_TEXTURE_ALIAS = 0x4200, // Repeating section - // char* aliasName; - // char* textureName; + } + + static MemoryStreamReaderPtr OpenReader(Assimp::IOSystem *pIOHandler, const std::string &filename); - M_GEOMETRY = 0x5000, // NB this chunk is embedded within M_MESH and M_SUBMESH - // unsigned int vertexCount - M_GEOMETRY_VERTEX_DECLARATION = 0x5100, - M_GEOMETRY_VERTEX_ELEMENT = 0x5110, // Repeating section - // unsigned short source; // buffer bind source - // unsigned short type; // VertexElementType - // unsigned short semantic; // VertexElementSemantic - // unsigned short offset; // start offset in buffer in bytes - // unsigned short index; // index of the semantic (for colours and texture coords) - M_GEOMETRY_VERTEX_BUFFER = 0x5200, // Repeating section - // unsigned short bindIndex; // Index to bind this buffer to - // unsigned short vertexSize; // Per-vertex size, must agree with declaration at this index - M_GEOMETRY_VERTEX_BUFFER_DATA = 0x5210, - // raw buffer data - M_MESH_SKELETON_LINK = 0x6000, - // Optional link to skeleton - // char* skeletonName : name of .skeleton to use - M_MESH_BONE_ASSIGNMENT = 0x7000, + // Header + + uint16_t ReadHeader(bool readLen = true); + void RollbackHeader(); + + // Mesh + + void ReadMesh(Mesh *mesh); + void ReadMeshLodInfo(Mesh *mesh); + void ReadMeshSkeletonLink(Mesh *mesh); + void ReadMeshBounds(Mesh *mesh); + void ReadMeshExtremes(Mesh *mesh); + + void ReadSubMesh(Mesh *mesh); + void ReadSubMeshNames(Mesh *mesh); + void ReadSubMeshOperation(SubMesh *submesh); + void ReadSubMeshTextureAlias(SubMesh *submesh); + + void ReadBoneAssignment(VertexData *dest); + + void ReadGeometry(VertexData *dest); + void ReadGeometryVertexDeclaration(VertexData *dest); + void ReadGeometryVertexElement(VertexData *dest); + void ReadGeometryVertexBuffer(VertexData *dest); + + void ReadEdgeList(Mesh *mesh); + void ReadPoses(Mesh *mesh); + void ReadPoseVertices(Pose *pose); + + void ReadAnimations(Mesh *mesh); + void ReadAnimation(Animation *anim); + void ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *track); + + void NormalizeBoneWeights(VertexData *vertexData) const; + + // Skeleton + + void ReadSkeleton(Skeleton *skeleton); + + void ReadBone(Skeleton *skeleton); + void ReadBoneParent(Skeleton *skeleton); + + void ReadSkeletonAnimation(Skeleton *skeleton); + void ReadSkeletonAnimationTrack(Skeleton *skeleton, Animation *dest); + void ReadSkeletonAnimationKeyFrame(VertexAnimationTrack *dest); + void ReadSkeletonAnimationLink(Skeleton *skeleton); + + // Reader utils + bool AtEnd() const; + + template + inline T Read(); + + void ReadBytes(char *dest, size_t numBytes); + void ReadBytes(uint8_t *dest, size_t numBytes); + void ReadBytes(void *dest, size_t numBytes); + uint8_t *ReadBytes(size_t numBytes); + + void ReadVector(aiVector3D &vec); + void ReadQuaternion(aiQuaternion &quat); + + std::string ReadString(size_t len); + std::string ReadLine(); + + void SkipBytes(size_t numBytes); + + uint32_t m_currentLen; + MemoryStreamReader *m_reader; + + AssetMode assetMode; +}; + +enum MeshChunkId +{ + M_HEADER = 0x1000, + // char* version : Version number check + M_MESH = 0x3000, + // bool skeletallyAnimated // important flag which affects h/w buffer policies + // Optional M_GEOMETRY chunk + M_SUBMESH = 0x4000, + // char* materialName + // bool useSharedVertices + // unsigned int indexCount + // bool indexes32Bit + // unsigned int* faceVertexIndices (indexCount) + // OR + // unsigned short* faceVertexIndices (indexCount) + // M_GEOMETRY chunk (Optional: present only if useSharedVertices = false) + M_SUBMESH_OPERATION = 0x4010, // optional, trilist assumed if missing + // unsigned short operationType + M_SUBMESH_BONE_ASSIGNMENT = 0x4100, // Optional bone weights (repeating section) // unsigned int vertexIndex; // unsigned short boneIndex; // float weight; - M_MESH_LOD = 0x8000, - // Optional LOD information - // string strategyName; - // unsigned short numLevels; - // bool manual; (true for manual alternate meshes, false for generated) - M_MESH_LOD_USAGE = 0x8100, - // Repeating section, ordered in increasing depth - // NB LOD 0 (full detail from 0 depth) is omitted - // LOD value - this is a distance, a pixel count etc, based on strategy - // float lodValue; - M_MESH_LOD_MANUAL = 0x8110, - // Required if M_MESH_LOD section manual = true - // String manualMeshName; - M_MESH_LOD_GENERATED = 0x8120, - // Required if M_MESH_LOD section manual = false - // Repeating section (1 per submesh) - // unsigned int indexCount; - // bool indexes32Bit - // unsigned short* faceIndexes; (indexCount) - // OR - // unsigned int* faceIndexes; (indexCount) - M_MESH_BOUNDS = 0x9000, - // float minx, miny, minz - // float maxx, maxy, maxz - // float radius - - // Added By DrEvil - // optional chunk that contains a table of submesh indexes and the names of - // the sub-meshes. - M_SUBMESH_NAME_TABLE = 0xA000, - // Subchunks of the name table. Each chunk contains an index & string - M_SUBMESH_NAME_TABLE_ELEMENT = 0xA100, - // short index - // char* name - // Optional chunk which stores precomputed edge data - M_EDGE_LISTS = 0xB000, - // Each LOD has a separate edge list - M_EDGE_LIST_LOD = 0xB100, - // unsigned short lodIndex - // bool isManual // If manual, no edge data here, loaded from manual mesh - // bool isClosed - // unsigned long numTriangles - // unsigned long numEdgeGroups - // Triangle* triangleList - // unsigned long indexSet - // unsigned long vertexSet - // unsigned long vertIndex[3] - // unsigned long sharedVertIndex[3] - // float normal[4] + // Optional chunk that matches a texture name to an alias + // a texture alias is sent to the submesh material to use this texture name + // instead of the one in the texture unit with a matching alias name + M_SUBMESH_TEXTURE_ALIAS = 0x4200, // Repeating section + // char* aliasName; + // char* textureName; - M_EDGE_GROUP = 0xB110, - // unsigned long vertexSet - // unsigned long triStart - // unsigned long triCount - // unsigned long numEdges - // Edge* edgeList - // unsigned long triIndex[2] - // unsigned long vertIndex[2] - // unsigned long sharedVertIndex[2] - // bool degenerate - // Optional poses section, referred to by pose keyframes - M_POSES = 0xC000, - M_POSE = 0xC100, - // char* name (may be blank) - // unsigned short target // 0 for shared geometry, - // 1+ for submesh index + 1 - // bool includesNormals [1.8+] - M_POSE_VERTEX = 0xC111, - // unsigned long vertexIndex - // float xoffset, yoffset, zoffset - // float xnormal, ynormal, znormal (optional, 1.8+) - // Optional vertex animation chunk - M_ANIMATIONS = 0xD000, - M_ANIMATION = 0xD100, + M_GEOMETRY = 0x5000, // NB this chunk is embedded within M_MESH and M_SUBMESH + // unsigned int vertexCount + M_GEOMETRY_VERTEX_DECLARATION = 0x5100, + M_GEOMETRY_VERTEX_ELEMENT = 0x5110, // Repeating section + // unsigned short source; // buffer bind source + // unsigned short type; // VertexElementType + // unsigned short semantic; // VertexElementSemantic + // unsigned short offset; // start offset in buffer in bytes + // unsigned short index; // index of the semantic (for colours and texture coords) + M_GEOMETRY_VERTEX_BUFFER = 0x5200, // Repeating section + // unsigned short bindIndex; // Index to bind this buffer to + // unsigned short vertexSize; // Per-vertex size, must agree with declaration at this index + M_GEOMETRY_VERTEX_BUFFER_DATA = 0x5210, + // raw buffer data + M_MESH_SKELETON_LINK = 0x6000, + // Optional link to skeleton + // char* skeletonName : name of .skeleton to use + M_MESH_BONE_ASSIGNMENT = 0x7000, + // Optional bone weights (repeating section) + // unsigned int vertexIndex; + // unsigned short boneIndex; + // float weight; + M_MESH_LOD = 0x8000, + // Optional LOD information + // string strategyName; + // unsigned short numLevels; + // bool manual; (true for manual alternate meshes, false for generated) + M_MESH_LOD_USAGE = 0x8100, + // Repeating section, ordered in increasing depth + // NB LOD 0 (full detail from 0 depth) is omitted + // LOD value - this is a distance, a pixel count etc, based on strategy + // float lodValue; + M_MESH_LOD_MANUAL = 0x8110, + // Required if M_MESH_LOD section manual = true + // String manualMeshName; + M_MESH_LOD_GENERATED = 0x8120, + // Required if M_MESH_LOD section manual = false + // Repeating section (1 per submesh) + // unsigned int indexCount; + // bool indexes32Bit + // unsigned short* faceIndexes; (indexCount) + // OR + // unsigned int* faceIndexes; (indexCount) + M_MESH_BOUNDS = 0x9000, + // float minx, miny, minz + // float maxx, maxy, maxz + // float radius + + // Added By DrEvil + // optional chunk that contains a table of submesh indexes and the names of + // the sub-meshes. + M_SUBMESH_NAME_TABLE = 0xA000, + // Subchunks of the name table. Each chunk contains an index & string + M_SUBMESH_NAME_TABLE_ELEMENT = 0xA100, + // short index // char* name - // float length - M_ANIMATION_BASEINFO = 0xD105, - // [Optional] base keyframe information (pose animation only) - // char* baseAnimationName (blank for self) - // float baseKeyFrameTime - M_ANIMATION_TRACK = 0xD110, - // unsigned short type // 1 == morph, 2 == pose - // unsigned short target // 0 for shared geometry, - // 1+ for submesh index + 1 - M_ANIMATION_MORPH_KEYFRAME = 0xD111, - // float time - // bool includesNormals [1.8+] - // float x,y,z // repeat by number of vertices in original geometry - M_ANIMATION_POSE_KEYFRAME = 0xD112, - // float time - M_ANIMATION_POSE_REF = 0xD113, // repeat for number of referenced poses - // unsigned short poseIndex - // float influence - // Optional submesh extreme vertex list chink - M_TABLE_EXTREMES = 0xE000, - // unsigned short submesh_index; - // float extremes [n_extremes][3]; - }; - - static std::string MeshHeaderToString(MeshChunkId id) - { - switch(id) - { - case M_HEADER: return "HEADER"; - case M_MESH: return "MESH"; - case M_SUBMESH: return "SUBMESH"; - case M_SUBMESH_OPERATION: return "SUBMESH_OPERATION"; - case M_SUBMESH_BONE_ASSIGNMENT: return "SUBMESH_BONE_ASSIGNMENT"; - case M_SUBMESH_TEXTURE_ALIAS: return "SUBMESH_TEXTURE_ALIAS"; - case M_GEOMETRY: return "GEOMETRY"; - case M_GEOMETRY_VERTEX_DECLARATION: return "GEOMETRY_VERTEX_DECLARATION"; - case M_GEOMETRY_VERTEX_ELEMENT: return "GEOMETRY_VERTEX_ELEMENT"; - case M_GEOMETRY_VERTEX_BUFFER: return "GEOMETRY_VERTEX_BUFFER"; - case M_GEOMETRY_VERTEX_BUFFER_DATA: return "GEOMETRY_VERTEX_BUFFER_DATA"; - case M_MESH_SKELETON_LINK: return "MESH_SKELETON_LINK"; - case M_MESH_BONE_ASSIGNMENT: return "MESH_BONE_ASSIGNMENT"; - case M_MESH_LOD: return "MESH_LOD"; - case M_MESH_LOD_USAGE: return "MESH_LOD_USAGE"; - case M_MESH_LOD_MANUAL: return "MESH_LOD_MANUAL"; - case M_MESH_LOD_GENERATED: return "MESH_LOD_GENERATED"; - case M_MESH_BOUNDS: return "MESH_BOUNDS"; - case M_SUBMESH_NAME_TABLE: return "SUBMESH_NAME_TABLE"; - case M_SUBMESH_NAME_TABLE_ELEMENT: return "SUBMESH_NAME_TABLE_ELEMENT"; - case M_EDGE_LISTS: return "EDGE_LISTS"; - case M_EDGE_LIST_LOD: return "EDGE_LIST_LOD"; - case M_EDGE_GROUP: return "EDGE_GROUP"; - case M_POSES: return "POSES"; - case M_POSE: return "POSE"; - case M_POSE_VERTEX: return "POSE_VERTEX"; - case M_ANIMATIONS: return "ANIMATIONS"; - case M_ANIMATION: return "ANIMATION"; - case M_ANIMATION_BASEINFO: return "ANIMATION_BASEINFO"; - case M_ANIMATION_TRACK: return "ANIMATION_TRACK"; - case M_ANIMATION_MORPH_KEYFRAME: return "ANIMATION_MORPH_KEYFRAME"; - case M_ANIMATION_POSE_KEYFRAME: return "ANIMATION_POSE_KEYFRAME"; - case M_ANIMATION_POSE_REF: return "ANIMATION_POSE_REF"; - case M_TABLE_EXTREMES: return "TABLE_EXTREMES"; - } - return "Uknown_MeshChunkId"; - } + // Optional chunk which stores precomputed edge data + M_EDGE_LISTS = 0xB000, + // Each LOD has a separate edge list + M_EDGE_LIST_LOD = 0xB100, + // unsigned short lodIndex + // bool isManual // If manual, no edge data here, loaded from manual mesh + // bool isClosed + // unsigned long numTriangles + // unsigned long numEdgeGroups + // Triangle* triangleList + // unsigned long indexSet + // unsigned long vertexSet + // unsigned long vertIndex[3] + // unsigned long sharedVertIndex[3] + // float normal[4] + M_EDGE_GROUP = 0xB110, + // unsigned long vertexSet + // unsigned long triStart + // unsigned long triCount + // unsigned long numEdges + // Edge* edgeList + // unsigned long triIndex[2] + // unsigned long vertIndex[2] + // unsigned long sharedVertIndex[2] + // bool degenerate + // Optional poses section, referred to by pose keyframes + M_POSES = 0xC000, + M_POSE = 0xC100, + // char* name (may be blank) + // unsigned short target // 0 for shared geometry, + // 1+ for submesh index + 1 + // bool includesNormals [1.8+] + M_POSE_VERTEX = 0xC111, + // unsigned long vertexIndex + // float xoffset, yoffset, zoffset + // float xnormal, ynormal, znormal (optional, 1.8+) + // Optional vertex animation chunk + M_ANIMATIONS = 0xD000, + M_ANIMATION = 0xD100, + // char* name + // float length + M_ANIMATION_BASEINFO = 0xD105, + // [Optional] base keyframe information (pose animation only) + // char* baseAnimationName (blank for self) + // float baseKeyFrameTime + M_ANIMATION_TRACK = 0xD110, + // unsigned short type // 1 == morph, 2 == pose + // unsigned short target // 0 for shared geometry, + // 1+ for submesh index + 1 + M_ANIMATION_MORPH_KEYFRAME = 0xD111, + // float time + // bool includesNormals [1.8+] + // float x,y,z // repeat by number of vertices in original geometry + M_ANIMATION_POSE_KEYFRAME = 0xD112, + // float time + M_ANIMATION_POSE_REF = 0xD113, // repeat for number of referenced poses + // unsigned short poseIndex + // float influence + // Optional submesh extreme vertex list chink + M_TABLE_EXTREMES = 0xE000, + // unsigned short submesh_index; + // float extremes [n_extremes][3]; +}; + +static std::string MeshHeaderToString(MeshChunkId id) +{ + switch(id) + { + case M_HEADER: return "HEADER"; + case M_MESH: return "MESH"; + case M_SUBMESH: return "SUBMESH"; + case M_SUBMESH_OPERATION: return "SUBMESH_OPERATION"; + case M_SUBMESH_BONE_ASSIGNMENT: return "SUBMESH_BONE_ASSIGNMENT"; + case M_SUBMESH_TEXTURE_ALIAS: return "SUBMESH_TEXTURE_ALIAS"; + case M_GEOMETRY: return "GEOMETRY"; + case M_GEOMETRY_VERTEX_DECLARATION: return "GEOMETRY_VERTEX_DECLARATION"; + case M_GEOMETRY_VERTEX_ELEMENT: return "GEOMETRY_VERTEX_ELEMENT"; + case M_GEOMETRY_VERTEX_BUFFER: return "GEOMETRY_VERTEX_BUFFER"; + case M_GEOMETRY_VERTEX_BUFFER_DATA: return "GEOMETRY_VERTEX_BUFFER_DATA"; + case M_MESH_SKELETON_LINK: return "MESH_SKELETON_LINK"; + case M_MESH_BONE_ASSIGNMENT: return "MESH_BONE_ASSIGNMENT"; + case M_MESH_LOD: return "MESH_LOD"; + case M_MESH_LOD_USAGE: return "MESH_LOD_USAGE"; + case M_MESH_LOD_MANUAL: return "MESH_LOD_MANUAL"; + case M_MESH_LOD_GENERATED: return "MESH_LOD_GENERATED"; + case M_MESH_BOUNDS: return "MESH_BOUNDS"; + case M_SUBMESH_NAME_TABLE: return "SUBMESH_NAME_TABLE"; + case M_SUBMESH_NAME_TABLE_ELEMENT: return "SUBMESH_NAME_TABLE_ELEMENT"; + case M_EDGE_LISTS: return "EDGE_LISTS"; + case M_EDGE_LIST_LOD: return "EDGE_LIST_LOD"; + case M_EDGE_GROUP: return "EDGE_GROUP"; + case M_POSES: return "POSES"; + case M_POSE: return "POSE"; + case M_POSE_VERTEX: return "POSE_VERTEX"; + case M_ANIMATIONS: return "ANIMATIONS"; + case M_ANIMATION: return "ANIMATION"; + case M_ANIMATION_BASEINFO: return "ANIMATION_BASEINFO"; + case M_ANIMATION_TRACK: return "ANIMATION_TRACK"; + case M_ANIMATION_MORPH_KEYFRAME: return "ANIMATION_MORPH_KEYFRAME"; + case M_ANIMATION_POSE_KEYFRAME: return "ANIMATION_POSE_KEYFRAME"; + case M_ANIMATION_POSE_REF: return "ANIMATION_POSE_REF"; + case M_TABLE_EXTREMES: return "TABLE_EXTREMES"; + } + return "Unknown_MeshChunkId"; +} + +enum SkeletonChunkId +{ + SKELETON_HEADER = 0x1000, + // char* version : Version number check + SKELETON_BLENDMODE = 0x1010, // optional + // unsigned short blendmode : SkeletonAnimationBlendMode + SKELETON_BONE = 0x2000, + // Repeating section defining each bone in the system. + // Bones are assigned indexes automatically based on their order of declaration + // starting with 0. + // char* name : name of the bone + // unsigned short handle : handle of the bone, should be contiguous & start at 0 + // Vector3 position : position of this bone relative to parent + // Quaternion orientation : orientation of this bone relative to parent + // Vector3 scale : scale of this bone relative to parent + SKELETON_BONE_PARENT = 0x3000, + // Record of the parent of a single bone, used to build the node tree + // Repeating section, listed in Bone Index order, one per Bone + // unsigned short handle : child bone + // unsigned short parentHandle : parent bone + SKELETON_ANIMATION = 0x4000, + // A single animation for this skeleton + // char* name : Name of the animation + // float length : Length of the animation in seconds + SKELETON_ANIMATION_BASEINFO = 0x4010, + // [Optional] base keyframe information + // char* baseAnimationName (blank for self) + // float baseKeyFrameTime + SKELETON_ANIMATION_TRACK = 0x4100, + // A single animation track (relates to a single bone) + // Repeating section (within SKELETON_ANIMATION) + // unsigned short boneIndex : Index of bone to apply to + SKELETON_ANIMATION_TRACK_KEYFRAME = 0x4110, + // A single keyframe within the track + // Repeating section + // float time : The time position (seconds) + // Quaternion rotate : Rotation to apply at this keyframe + // Vector3 translate : Translation to apply at this keyframe + // Vector3 scale : Scale to apply at this keyframe + SKELETON_ANIMATION_LINK = 0x5000 + // Link to another skeleton, to re-use its animations + // char* skeletonName : name of skeleton to get animations from + // float scale : scale to apply to trans/scale keys +}; + +static std::string SkeletonHeaderToString(SkeletonChunkId id) +{ + switch(id) + { + case SKELETON_HEADER: return "HEADER"; + case SKELETON_BLENDMODE: return "BLENDMODE"; + case SKELETON_BONE: return "BONE"; + case SKELETON_BONE_PARENT: return "BONE_PARENT"; + case SKELETON_ANIMATION: return "ANIMATION"; + case SKELETON_ANIMATION_BASEINFO: return "ANIMATION_BASEINFO"; + case SKELETON_ANIMATION_TRACK: return "ANIMATION_TRACK"; + case SKELETON_ANIMATION_TRACK_KEYFRAME: return "ANIMATION_TRACK_KEYFRAME"; + case SKELETON_ANIMATION_LINK: return "ANIMATION_LINK"; + } + return "Unknown_SkeletonChunkId"; +} } // Ogre } // Assimp diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index 0faeb6520..d28b2cf90 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -110,6 +110,9 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass // Import mesh boost::scoped_ptr mesh = OgreBinarySerializer::ImportMesh(&reader); + // Import skeleton + OgreBinarySerializer::ImportSkeleton(pIOHandler, mesh); + // Import mesh referenced materials ReadMaterials(pFile, pIOHandler, pScene, mesh.get()); diff --git a/code/OgreStructs.cpp b/code/OgreStructs.cpp index 154ee343b..f651e96eb 100644 --- a/code/OgreStructs.cpp +++ b/code/OgreStructs.cpp @@ -459,6 +459,35 @@ void Mesh::ConvertToAssimpScene(aiScene* dest) dest->mMeshes[i] = subMeshes[i]->ConvertToAssimpMesh(this); dest->mRootNode->mMeshes[i] = i; } + + // Export skeleton + if (skeleton) + { + // Bones + if (!skeleton->bones.empty()) + { + BoneList rootBones = skeleton->RootBones(); + dest->mRootNode->mNumChildren = rootBones.size(); + dest->mRootNode->mChildren = new aiNode*[dest->mRootNode->mNumChildren]; + + for(size_t i=0, len=rootBones.size(); imRootNode->mChildren[i] = rootBones[i]->ConvertToAssimpNode(skeleton, dest->mRootNode); + } + } + + // Animations + if (!skeleton->animations.empty()) + { + dest->mNumAnimations = skeleton->animations.size(); + dest->mAnimations = new aiAnimation*[dest->mNumAnimations]; + + for(size_t i=0, len=skeleton->animations.size(); imAnimations[i] = skeleton->animations[i]->ConvertToAssimpAnimation(); + } + } + } } // ISubMesh @@ -652,7 +681,7 @@ aiMesh *SubMesh::ConvertToAssimpMesh(Mesh *parent) } } } - + // Bones and bone weights if (parent->skeleton && boneAssignments) { @@ -926,7 +955,8 @@ aiAnimation *Animation::ConvertToAssimpAnimation() // Skeleton -Skeleton::Skeleton() +Skeleton::Skeleton() : + blendMode(ANIMBLEND_AVERAGE) { } @@ -995,7 +1025,6 @@ Bone::Bone() : id(0), parent(0), parentId(-1), - rotationAngle(0.0f), scale(1.0f, 1.0f, 1.0f) { } @@ -1024,16 +1053,12 @@ void Bone::AddChild(Bone *bone) void Bone::CalculateWorldMatrixAndDefaultPose(Skeleton *skeleton) { - aiMatrix4x4 t0, t1; - aiMatrix4x4 transform = aiMatrix4x4::Rotation(-rotationAngle, rotation, t1) * aiMatrix4x4::Translation(-position, t0); - if (!IsParented()) - worldMatrix = transform; + worldMatrix = aiMatrix4x4(scale, rotation, position).Inverse(); else - worldMatrix = transform * parent->worldMatrix; + worldMatrix = aiMatrix4x4(scale, rotation, position).Inverse() * parent->worldMatrix; - aiMatrix4x4 t2, t3; /// @todo t0 and t1 could probably be reused here? - defaultPose = aiMatrix4x4::Translation(position, t2) * aiMatrix4x4::Rotation(rotationAngle, rotation, t3); + defaultPose = aiMatrix4x4(scale, rotation, position); // Recursively for all children now that the parent matrix has been calculated. for (size_t i=0, len=children.size(); imParent = parentNode; @@ -1123,35 +1146,40 @@ aiNodeAnim *VertexAnimationTrack::ConvertToAssimpAnimationNode(Skeleton *skeleto for(size_t kfi=0; kfidefaultPose * keyBonePose; + aiVector3D pos; aiQuaternion rot; aiVector3D scale; - aiVector3D kfPos; aiQuaternion kfRot; aiVector3D kfScale; - finalTransform.Decompose(kfScale, kfRot, kfPos); + aiMatrix4x4 finalTransform = bone->defaultPose * kfSource.Transform(); + finalTransform.Decompose(scale, rot, pos); double t = static_cast(kfSource.timePos); nodeAnim->mPositionKeys[kfi].mTime = t; nodeAnim->mRotationKeys[kfi].mTime = t; nodeAnim->mScalingKeys[kfi].mTime = t; - nodeAnim->mPositionKeys[kfi].mValue = kfPos; - nodeAnim->mRotationKeys[kfi].mValue = kfRot; - nodeAnim->mScalingKeys[kfi].mValue = kfScale; + nodeAnim->mPositionKeys[kfi].mValue = pos; + nodeAnim->mRotationKeys[kfi].mValue = rot; + nodeAnim->mScalingKeys[kfi].mValue = scale; } return nodeAnim; } +// TransformKeyFrame + +TransformKeyFrame::TransformKeyFrame() : + timePos(0.0f), + scale(1.0f, 1.0f, 1.0f) +{ +} + +aiMatrix4x4 TransformKeyFrame::Transform() +{ + return aiMatrix4x4(scale, rotation, position); +} + } // Ogre } // Assimp diff --git a/code/OgreStructs.h b/code/OgreStructs.h index 93d1d61c8..81431b65d 100644 --- a/code/OgreStructs.h +++ b/code/OgreStructs.h @@ -65,7 +65,6 @@ class Skeleton; #define OGRE_SAFE_DELETE(p) delete p; p=0; // Typedefs -typedef Assimp::StreamReaderLE MemoryStreamReader; typedef Assimp::MemoryIOStream MemoryStream; typedef boost::shared_ptr MemoryStreamPtr; typedef std::map VertexBufferBindings; @@ -318,6 +317,10 @@ typedef std::vector MorphKeyFrameList; /// Ogre animation key frame struct TransformKeyFrame { + TransformKeyFrame(); + + aiMatrix4x4 Transform(); + float timePos; aiQuaternion rotation; @@ -435,9 +438,8 @@ public: std::vector children; aiVector3D position; - aiVector3D rotation; - aiVector3D scale; ///< @todo Implement taking scale into account in matrix/pose calculations! - float rotationAngle; + aiQuaternion rotation; + aiVector3D scale; aiMatrix4x4 worldMatrix; aiMatrix4x4 defaultPose; @@ -448,6 +450,14 @@ typedef std::vector BoneList; class Skeleton { public: + enum BlendMode + { + /// Animations are applied by calculating a weighted average of all animations + ANIMBLEND_AVERAGE = 0, + /// Animations are applied by calculating a weighted cumulative total + ANIMBLEND_CUMULATIVE = 1 + }; + Skeleton(); ~Skeleton(); @@ -468,6 +478,9 @@ public: BoneList bones; AnimationList animations; + + /// @todo Take blend mode into account, but where? + BlendMode blendMode; }; /// Ogre Sub Mesh interface, inherited by the binary and XML implementations. diff --git a/code/OgreXmlSerializer.cpp b/code/OgreXmlSerializer.cpp index 9b8a4834e..73802aca6 100644 --- a/code/OgreXmlSerializer.cpp +++ b/code/OgreXmlSerializer.cpp @@ -39,8 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "OgreXmlSerializer.h" +#include "OgreBinarySerializer.h" +#include "OgreParsingUtils.h" -#include "irrXMLWrapper.h" #include "TinyFormatter.h" #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER @@ -638,9 +639,10 @@ void OgreXmlSerializer::ReadBoneAssignments(VertexDataXml *dest) ba.vertexIndex = ReadAttribute(anVertexIndex); ba.boneIndex = ReadAttribute(anBoneIndex); ba.weight = ReadAttribute(anWeight); + dest->boneAssignments.push_back(ba); - influencedVertices.insert(ba.vertexIndex); + NextNode(); } @@ -675,23 +677,61 @@ void OgreXmlSerializer::ReadBoneAssignments(VertexDataXml *dest) void OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh) { - if (mesh->skeletonRef.empty()) + if (!mesh || 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")) + // Highly unusual to see in read world cases but support + // XML mesh referencing a binary skeleton file. + if (EndsWith(mesh->skeletonRef, ".skeleton", false)) { - 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 (OgreBinarySerializer::ImportSkeleton(pIOHandler, mesh)) + return; + + /** Last fallback if .skeleton failed to be read. + Try reading from .skeleton.xml even if the XML file + referenced a binary skeleton. + @note This logic was in the previous version and + I don't want to break old code that depends on it. */ + mesh->skeletonRef = mesh->skeletonRef + ".xml"; + } + + XmlReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef); + if (!reader.get()) + return; + + Skeleton *skeleton = new Skeleton(); + OgreXmlSerializer serializer(reader.get()); + serializer.ReadSkeleton(skeleton); + mesh->skeleton = skeleton; +} + +void OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh) +{ + if (!mesh || mesh->skeletonRef.empty()) + return; + + XmlReaderPtr reader = OpenReader(pIOHandler, mesh->skeletonRef); + if (!reader.get()) + return; + + Skeleton *skeleton = new Skeleton(); + OgreXmlSerializer serializer(reader.get()); + serializer.ReadSkeleton(skeleton); + mesh->skeleton = skeleton; +} + +XmlReaderPtr OgreXmlSerializer::OpenReader(Assimp::IOSystem *pIOHandler, const std::string &filename) +{ + if (!EndsWith(filename, ".skeleton.xml", false)) + { + DefaultLogger::get()->error("Imported Mesh is referencing to unsupported '" + filename + "' skeleton file."); + return XmlReaderPtr(); } if (!pIOHandler->Exists(filename)) { DefaultLogger::get()->error("Failed to find skeleton file '" + filename + "' that is referenced by imported Mesh."); - return; + return XmlReaderPtr(); } boost::scoped_ptr file(pIOHandler->Open(filename)); @@ -700,15 +740,11 @@ void OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *me } boost::scoped_ptr stream(new CIrrXML_IOStreamReader(file.get())); - XmlReader* reader = irr::io::createIrrXMLReader(stream.get()); - if (!reader) { + XmlReaderPtr reader = XmlReaderPtr(irr::io::createIrrXMLReader(stream.get())); + if (!reader.get()) { throw DeadlyImportError("Failed to create XML reader for skeleton file " + filename); } - - Skeleton *skeleton = new Skeleton(); - OgreXmlSerializer serializer(reader); - serializer.ReadSkeleton(skeleton); - mesh->skeleton = skeleton; + return reader; } void OgreXmlSerializer::ReadSkeleton(Skeleton *skeleton) @@ -718,6 +754,12 @@ void OgreXmlSerializer::ReadSkeleton(Skeleton *skeleton) } DefaultLogger::get()->debug("Reading Skeleton"); + + // Optional blend mode from root node + if (HasAttribute("blendmode")) { + skeleton->blendMode = (ToLower(ReadAttribute("blendmode")) == "cumulative" + ? Skeleton::ANIMBLEND_CUMULATIVE : Skeleton::ANIMBLEND_AVERAGE); + } NextNode(); @@ -854,10 +896,10 @@ void OgreXmlSerializer::ReadBoneHierarchy(Skeleton *skeleton) if (bone && parent) parent->AddChild(bone); else - DefaultLogger::get()->warn("Failed to find bones for parenting: Child " + name + " for parent " + parentName); + throw DeadlyImportError("Failed to find bones for parenting: Child " + name + " for parent " + parentName); } - // Calculate bone matrices for root bones. Recursively calcutes their children. + // Calculate bone matrices for root bones. Recursively calculates their children. for (size_t i=0, len=skeleton->bones.size(); ibones[i]; @@ -895,15 +937,18 @@ void OgreXmlSerializer::ReadBones(Skeleton *skeleton) } else if (m_currentNodeName == nnRotation) { - bone->rotationAngle = ReadAttribute("angle"); + float angle = 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); + aiVector3D axis; + axis.x = ReadAttribute(anX); + axis.y = ReadAttribute(anY); + axis.z = ReadAttribute(anZ); + + bone->rotation = aiQuaternion(axis, angle); } else if (m_currentNodeName == nnScale) { diff --git a/code/OgreXmlSerializer.h b/code/OgreXmlSerializer.h index 561a715d1..1813a2949 100644 --- a/code/OgreXmlSerializer.h +++ b/code/OgreXmlSerializer.h @@ -44,8 +44,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER #include "OgreStructs.h" -#include "OgreParsingUtils.h" - #include "irrXMLWrapper.h" namespace Assimp @@ -54,6 +52,7 @@ namespace Ogre { typedef irr::io::IrrXMLReader XmlReader; +typedef boost::shared_ptr XmlReaderPtr; class OgreXmlSerializer { @@ -62,16 +61,19 @@ public: /** @note Fatal unrecoverable errors will throw a DeadlyImportError. */ static MeshXml *ImportMesh(XmlReader *reader); - /// Imports skeleton to @c mesh into MeshXML::skeleton. + /// Imports skeleton to @c mesh. /** 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); + static void ImportSkeleton(Assimp::IOSystem *pIOHandler, Mesh *mesh); private: OgreXmlSerializer(XmlReader *reader) : m_reader(reader) { } + + static XmlReaderPtr OpenReader(Assimp::IOSystem *pIOHandler, const std::string &filename); // Mesh void ReadMesh(MeshXml *mesh);