diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 5ab4437b1..8dbb0962f 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -331,8 +331,6 @@ SET( Ogre_SRCS OgreBinarySerializer.cpp OgreXmlSerializer.cpp OgreMaterial.cpp - OgreMesh.cpp - OgreSkeleton.cpp ) SOURCE_GROUP( Ogre FILES ${Ogre_SRCS}) diff --git a/code/OgreBinarySerializer.cpp b/code/OgreBinarySerializer.cpp index 96977e175..90e0d8c9d 100644 --- a/code/OgreBinarySerializer.cpp +++ b/code/OgreBinarySerializer.cpp @@ -206,7 +206,7 @@ void OgreBinarySerializer::ReadMesh(Mesh *mesh) { mesh->hasSkeletalAnimations = Read(); - DefaultLogger::get()->debug(Formatter::format() << "Reading Mesh"); + DefaultLogger::get()->debug("Reading Mesh"); DefaultLogger::get()->debug(Formatter::format() << " - Skeletal animations: " << (mesh->hasSkeletalAnimations ? "true" : "false")); if (!AtEnd()) @@ -230,7 +230,7 @@ void OgreBinarySerializer::ReadMesh(Mesh *mesh) case M_GEOMETRY: { mesh->sharedVertexData = new VertexData(); - ReadGeometry(mesh, mesh->sharedVertexData); + ReadGeometry(mesh->sharedVertexData); break; } case M_SUBMESH: @@ -245,7 +245,7 @@ void OgreBinarySerializer::ReadMesh(Mesh *mesh) } case M_MESH_BONE_ASSIGNMENT: { - ReadBoneAssignment(mesh); + ReadBoneAssignment(mesh->sharedVertexData); break; } case M_MESH_LOD: @@ -291,6 +291,8 @@ void OgreBinarySerializer::ReadMesh(Mesh *mesh) if (!AtEnd()) RollbackHeader(); } + + NormalizeBoneWeights(mesh->sharedVertexData); } void OgreBinarySerializer::ReadMeshLodInfo(Mesh *mesh) @@ -361,18 +363,12 @@ void OgreBinarySerializer::ReadMeshExtremes(Mesh *mesh) SkipBytes(numBytes); } -void OgreBinarySerializer::ReadBoneAssignment(Mesh *dest) -{ - VertexBoneAssignment ba; - ba.vertexIndex = Read(); - ba.boneIndex = Read(); - ba.weight = Read(); - - dest->boneAssignments.push_back(ba); -} - -void OgreBinarySerializer::ReadBoneAssignment(SubMesh2 *dest) +void OgreBinarySerializer::ReadBoneAssignment(VertexData *dest) { + if (!dest) { + throw DeadlyImportError("Cannot read bone assignments, vertex data is null."); + } + VertexBoneAssignment ba; ba.vertexIndex = Read(); ba.boneIndex = Read(); @@ -385,7 +381,7 @@ void OgreBinarySerializer::ReadSubMesh(Mesh *mesh) { uint16_t id = 0; - SubMesh2 *submesh = new SubMesh2(); + SubMesh *submesh = new SubMesh(); submesh->materialRef = ReadLine(); submesh->usesSharedVertexData = Read(); @@ -418,7 +414,7 @@ void OgreBinarySerializer::ReadSubMesh(Mesh *mesh) } submesh->vertexData = new VertexData(); - ReadGeometry(mesh, submesh->vertexData); + ReadGeometry(submesh->vertexData); } // Bone assignment, submesh operation and texture aliases @@ -439,7 +435,7 @@ void OgreBinarySerializer::ReadSubMesh(Mesh *mesh) } case M_SUBMESH_BONE_ASSIGNMENT: { - ReadBoneAssignment(submesh); + ReadBoneAssignment(submesh->vertexData); break; } case M_SUBMESH_TEXTURE_ALIAS: @@ -455,17 +451,54 @@ void OgreBinarySerializer::ReadSubMesh(Mesh *mesh) if (!AtEnd()) RollbackHeader(); } - + + NormalizeBoneWeights(submesh->vertexData); + submesh->index = mesh->subMeshes.size(); mesh->subMeshes.push_back(submesh); } -void OgreBinarySerializer::ReadSubMeshOperation(SubMesh2 *submesh) +void OgreBinarySerializer::NormalizeBoneWeights(VertexData *vertexData) const { - submesh->operationType = static_cast(Read()); + if (!vertexData || vertexData->boneAssignments.empty()) + return; + + std::set influencedVertices; + for (VertexBoneAssignmentList::const_iterator baIter=vertexData->boneAssignments.begin(), baEnd=vertexData->boneAssignments.end(); baIter != baEnd; ++baIter) { + influencedVertices.insert(baIter->vertexIndex); + } + + /** Normalize bone weights. + Some exporters wont care if the sum of all bone weights + for a single vertex equals 1 or not, so validate here. */ + const float epsilon = 0.05f; + for(std::set::const_iterator iter=influencedVertices.begin(), end=influencedVertices.end(); iter != end; ++iter) + { + const uint32_t vertexIndex = (*iter); + + float sum = 0.0f; + for (VertexBoneAssignmentList::const_iterator baIter=vertexData->boneAssignments.begin(), baEnd=vertexData->boneAssignments.end(); baIter != baEnd; ++baIter) + { + if (baIter->vertexIndex == vertexIndex) + sum += baIter->weight; + } + if ((sum < (1.0f - epsilon)) || (sum > (1.0f + epsilon))) + { + for (VertexBoneAssignmentList::iterator baIter=vertexData->boneAssignments.begin(), baEnd=vertexData->boneAssignments.end(); baIter != baEnd; ++baIter) + { + if (baIter->vertexIndex == vertexIndex) + baIter->weight /= sum; + } + } + } } -void OgreBinarySerializer::ReadSubMeshTextureAlias(SubMesh2 *submesh) +void OgreBinarySerializer::ReadSubMeshOperation(SubMesh *submesh) +{ + submesh->operationType = static_cast(Read()); +} + +void OgreBinarySerializer::ReadSubMeshTextureAlias(SubMesh *submesh) { submesh->textureAliasName = ReadLine(); submesh->textureAliasRef = ReadLine(); @@ -482,7 +515,7 @@ void OgreBinarySerializer::ReadSubMeshNames(Mesh *mesh) while (!AtEnd() && id == M_SUBMESH_NAME_TABLE_ELEMENT) { uint16_t submeshIndex = Read(); - SubMesh2 *submesh = mesh->SubMesh(submeshIndex); + SubMesh *submesh = mesh->GetSubMesh(submeshIndex); if (!submesh) { throw DeadlyImportError(Formatter::format() << "Ogre Mesh does not include submesh " << submeshIndex << " referenced in M_SUBMESH_NAME_TABLE_ELEMENT. Invalid mesh file."); } @@ -498,7 +531,7 @@ void OgreBinarySerializer::ReadSubMeshNames(Mesh *mesh) } } -void OgreBinarySerializer::ReadGeometry(Mesh *mesh, VertexData *dest) +void OgreBinarySerializer::ReadGeometry(VertexData *dest) { dest->count = Read(); @@ -515,12 +548,12 @@ void OgreBinarySerializer::ReadGeometry(Mesh *mesh, VertexData *dest) { case M_GEOMETRY_VERTEX_DECLARATION: { - ReadGeometryVertexDeclaration(mesh, dest); + ReadGeometryVertexDeclaration(dest); break; } case M_GEOMETRY_VERTEX_BUFFER: { - ReadGeometryVertexBuffer(mesh, dest); + ReadGeometryVertexBuffer(dest); break; } } @@ -533,14 +566,14 @@ void OgreBinarySerializer::ReadGeometry(Mesh *mesh, VertexData *dest) } } -void OgreBinarySerializer::ReadGeometryVertexDeclaration(Mesh *mesh, VertexData *dest) +void OgreBinarySerializer::ReadGeometryVertexDeclaration(VertexData *dest) { if (!AtEnd()) { uint16_t id = ReadHeader(); while (!AtEnd() && id == M_GEOMETRY_VERTEX_ELEMENT) { - ReadGeometryVertexElement(mesh, dest); + ReadGeometryVertexElement(dest); if (!AtEnd()) id = ReadHeader(); @@ -550,7 +583,7 @@ void OgreBinarySerializer::ReadGeometryVertexDeclaration(Mesh *mesh, VertexData } } -void OgreBinarySerializer::ReadGeometryVertexElement(Mesh *mesh, VertexData *dest) +void OgreBinarySerializer::ReadGeometryVertexElement(VertexData *dest) { VertexElement element; element.source = Read(); @@ -565,7 +598,7 @@ void OgreBinarySerializer::ReadGeometryVertexElement(Mesh *mesh, VertexData *des dest->vertexElements.push_back(element); } -void OgreBinarySerializer::ReadGeometryVertexBuffer(Mesh *mesh, VertexData *dest) +void OgreBinarySerializer::ReadGeometryVertexBuffer(VertexData *dest) { uint16_t bindIndex = Read(); uint16_t vertexSize = Read(); @@ -682,7 +715,7 @@ void OgreBinarySerializer::ReadAnimations(Mesh *mesh) uint16_t id = ReadHeader(); while (!AtEnd() && id == M_ANIMATION) { - Animation2 *anim = new Animation2(mesh); + Animation *anim = new Animation(mesh); anim->name = ReadLine(); anim->length = Read(); @@ -698,7 +731,7 @@ void OgreBinarySerializer::ReadAnimations(Mesh *mesh) } } -void OgreBinarySerializer::ReadAnimation(Animation2 *anim) +void OgreBinarySerializer::ReadAnimation(Animation *anim) { if (!AtEnd()) @@ -731,7 +764,7 @@ void OgreBinarySerializer::ReadAnimation(Animation2 *anim) } } -void OgreBinarySerializer::ReadAnimationKeyFrames(Animation2 *anim, VertexAnimationTrack *track) +void OgreBinarySerializer::ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *track) { if (!AtEnd()) { diff --git a/code/OgreBinarySerializer.h b/code/OgreBinarySerializer.h index 1eede65f9..e430e487a 100644 --- a/code/OgreBinarySerializer.h +++ b/code/OgreBinarySerializer.h @@ -51,7 +51,7 @@ namespace Ogre { class OgreBinarySerializer { - public: + public: static Mesh *ImportMesh(MemoryStreamReader *reader); private: @@ -68,27 +68,28 @@ namespace Ogre void ReadMeshSkeletonLink(Mesh *mesh); void ReadMeshBounds(Mesh *mesh); void ReadMeshExtremes(Mesh *mesh); - + void ReadSubMesh(Mesh *mesh); void ReadSubMeshNames(Mesh *mesh); - void ReadSubMeshOperation(SubMesh2 *submesh); - void ReadSubMeshTextureAlias(SubMesh2 *submesh); - - void ReadBoneAssignment(Mesh *dest); - void ReadBoneAssignment(SubMesh2 *dest); + void ReadSubMeshOperation(SubMesh *submesh); + void ReadSubMeshTextureAlias(SubMesh *submesh); - void ReadGeometry(Mesh *mesh, VertexData *dest); - void ReadGeometryVertexDeclaration(Mesh *mesh, VertexData *dest); - void ReadGeometryVertexElement(Mesh *mesh, VertexData *dest); - void ReadGeometryVertexBuffer(Mesh *mesh, VertexData *dest); + 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(Animation2 *anim); - void ReadAnimationKeyFrames(Animation2 *anim, VertexAnimationTrack *track); + void ReadAnimation(Animation *anim); + void ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *track); + + void NormalizeBoneWeights(VertexData *vertexData) const; uint16_t ReadHeader(bool readLen = true); void RollbackHeader(); diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index cc2f8fccb..0faeb6520 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -42,15 +42,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AssimpPCH.h" -#include -#include -#include - #include "OgreImporter.h" #include "OgreBinarySerializer.h" - -#include "TinyFormatter.h" -#include "irrXMLWrapper.h" +#include "OgreXmlSerializer.h" static const aiImporterDesc desc = { "Ogre3D Mesh Importer", @@ -65,8 +59,6 @@ static const aiImporterDesc desc = { "mesh mesh.xml" }; -using namespace std; - namespace Assimp { namespace Ogre @@ -96,192 +88,54 @@ bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandle } else { + /// @todo Read and validate first header chunk? return EndsWith(pFile, ".mesh", false); } } void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Assimp::IOSystem *pIOHandler) { - // -------------------- Initial file and XML operations -------------------- - - // Open + // Open source file IOStream *f = pIOHandler->Open(pFile, "rb"); if (!f) { throw DeadlyImportError("Failed to open file " + pFile); } // Binary .mesh import - if (EndsWith(pFile, ".mesh", false)) { - // Read full data from file + if (EndsWith(pFile, ".mesh", false)) + { /// @note MemoryStreamReader takes ownership of f. MemoryStreamReader reader(f); - + // Import mesh boost::scoped_ptr mesh = OgreBinarySerializer::ImportMesh(&reader); - + // Import mesh referenced materials ReadMaterials(pFile, pIOHandler, pScene, mesh.get()); - - // Convert to Assimp. + + // Convert to Assimp mesh->ConvertToAssimpScene(pScene); - return; - } - - // Read - boost::scoped_ptr file(f); - boost::scoped_ptr xmlStream(new CIrrXML_IOStreamReader(file.get())); - boost::scoped_ptr reader(irr::io::createIrrXMLReader(xmlStream.get())); - if (!reader) { - throw DeadlyImportError("Failed to create XML Reader for " + pFile); - } - - DefaultLogger::get()->debug("Opened a XML reader for " + pFile); - - // Read root node - NextNode(reader.get()); - if (!CurrentNodeNameEquals(reader.get(), "mesh")) { - throw DeadlyImportError("Root node is not but <" + string(reader->getNodeName()) + "> in " + pFile); - } - - // Node names - const string nnSharedGeometry = "sharedgeometry"; - const string nnVertexBuffer = "vertexbuffer"; - const string nnSubMeshes = "submeshes"; - const string nnSubMesh = "submesh"; - const string nnSubMeshNames = "submeshnames"; - const string nnSkeletonLink = "skeletonlink"; - - // -------------------- Shared Geometry -------------------- - // This can be used to share geometry between submeshes - - NextNode(reader.get()); - if (CurrentNodeNameEquals(reader.get(), nnSharedGeometry)) - { - DefaultLogger::get()->debug("Reading shared geometry"); - unsigned int NumVertices = GetAttribute(reader.get(), "vertexcount"); - - NextNode(reader.get()); - while(CurrentNodeNameEquals(reader.get(), nnVertexBuffer)) { - ReadVertexBuffer(m_SharedGeometry, reader.get(), NumVertices); - } - } - - // -------------------- Sub Meshes -------------------- - - if (!CurrentNodeNameEquals(reader.get(), nnSubMeshes)) { - throw DeadlyImportError("Could not find node inside root node"); - } - - vector > subMeshes; - vector materials; - - NextNode(reader.get()); - while(CurrentNodeNameEquals(reader.get(), nnSubMesh)) - { - SubMesh* submesh = new SubMesh(); - ReadSubMesh(subMeshes.size(), *submesh, reader.get()); - - /** @todo What is the correct way of handling empty ref here. - Does Assimp require there to be a valid material index for each mesh, - even if its a dummy material. */ - aiMaterial* material = ReadMaterial(pFile, pIOHandler, submesh->MaterialName); - if (!material) - material = new aiMaterial(); - if (material) - { - submesh->MaterialIndex = materials.size(); - materials.push_back(material); - } - - subMeshes.push_back(boost::shared_ptr(submesh)); - } - - if (subMeshes.empty()) { - throw DeadlyImportError("Could not find node inside root node"); - } - - // This is really a internal error if we failed to create dummy materials. - if (subMeshes.size() != materials.size()) { - throw DeadlyImportError("Internal Error: Material count does not match the submesh count"); - } - - // Skip submesh names. - /// @todo Should these be read to scene etc. metadata? - if (CurrentNodeNameEquals(reader.get(), nnSubMeshNames)) - { - NextNode(reader.get()); - while(CurrentNodeNameEquals(reader.get(), nnSubMesh)) { - NextNode(reader.get()); - } - } - - // -------------------- Skeleton -------------------- - - vector Bones; - vector Animations; - - if (CurrentNodeNameEquals(reader.get(), nnSkeletonLink)) - { - string skeletonFile = GetAttribute(reader.get(), "name"); - if (!skeletonFile.empty()) - { - ReadSkeleton(pFile, pIOHandler, pScene, skeletonFile, Bones, Animations); - } - else - { - DefaultLogger::get()->debug("Found a unusual <" + nnSkeletonLink + "> with a empty file reference"); - } - NextNode(reader.get()); } + // XML .mesh.xml import else { - DefaultLogger::get()->debug("Mesh has no assigned skeleton with <" + nnSkeletonLink + ">"); + /// @note XmlReader does not take ownership of f, hence the scoped ptr. + boost::scoped_ptr scopedFile(f); + boost::scoped_ptr xmlStream(new CIrrXML_IOStreamReader(scopedFile.get())); + boost::scoped_ptr reader(irr::io::createIrrXMLReader(xmlStream.get())); + + // Import mesh + boost::scoped_ptr mesh = OgreXmlSerializer::ImportMesh(reader.get()); + + // Import skeleton + OgreXmlSerializer::ImportSkeleton(pIOHandler, mesh); + + // Import mesh referenced materials + ReadMaterials(pFile, pIOHandler, pScene, mesh.get()); + + // Convert to Assimp + mesh->ConvertToAssimpScene(pScene); } - - // Now there might be for the shared geometry - if (CurrentNodeNameEquals(reader.get(), "boneassignments")) { - ReadBoneWeights(m_SharedGeometry, reader.get()); - } - - // -------------------- Process Results -------------------- - BOOST_FOREACH(boost::shared_ptr submesh, subMeshes) - { - ProcessSubMesh(*submesh.get(), m_SharedGeometry); - } - - // -------------------- Apply to aiScene -------------------- - - // Materials - pScene->mNumMaterials = materials.size(); - if (pScene->mNumMaterials > 0) { - pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials]; - } - - for(size_t i=0; imNumMaterials; ++i) { - pScene->mMaterials[i] = materials[i]; - } - - // Meshes - pScene->mMeshes = new aiMesh*[subMeshes.size()]; - pScene->mNumMeshes = subMeshes.size(); - - for(size_t i=0, len=subMeshes.size(); i submesh = subMeshes[i]; - pScene->mMeshes[i] = CreateAssimpSubMesh(pScene, *(submesh.get()), Bones); - } - - // Create the root node - pScene->mRootNode = new aiNode(); - pScene->mRootNode->mMeshes = new unsigned int[subMeshes.size()]; - pScene->mRootNode->mNumMeshes = subMeshes.size(); - - for(size_t i=0, len=subMeshes.size(); imRootNode->mMeshes[i] = static_cast(i); - } - - // Skeleton and animations - CreateAssimpSkeleton(pScene, Bones, Animations); } } // Ogre diff --git a/code/OgreImporter.h b/code/OgreImporter.h index 538ea83f5..892696407 100644 --- a/code/OgreImporter.h +++ b/code/OgreImporter.h @@ -53,58 +53,10 @@ namespace Assimp namespace Ogre { -struct Face; -struct BoneWeight; -struct Bone; -struct Animation; - -/// Ogre SubMesh -struct SubMesh -{ - bool UseSharedGeometry; - bool Use32bitIndexes; - - std::string Name; - std::string MaterialName; - - bool HasGeometry; - bool HasPositions; - bool HasNormals; - bool HasTangents; - - std::vector Faces; - std::vector Positions; - std::vector Normals; - std::vector Tangents; - - /// Arbitrary number of texcoords, they are nearly always 2d, but Assimp has always 3d texcoords, n vectors(outer) with texcoords for each vertex(inner). - std::vector > Uvs; - - /// A list(inner) of bones for each vertex(outer). - std::vector > Weights; - - /// The Index in the Assimp material array from the material witch is attached to this submesh. - int MaterialIndex; - - // The highest index of a bone from a bone weight, this is needed to create the Assimp bone struct. Converting from vertex-bones to bone-vertices. - unsigned int BonesUsed; - - SubMesh() : - UseSharedGeometry(false), - Use32bitIndexes(false), - HasGeometry(false), - HasPositions(false), - HasNormals(false), - HasTangents(false), - MaterialIndex(-1), - BonesUsed(0) - { - } -}; - /** Importer for Ogre mesh, skeleton and material formats. - @todo Support vertex colors - @todo Support multiple TexCoords (this is already done??) */ + @todo Support vertex colors. + @todo Support poses/animations from the mesh file. + Currently only skeleton file animations are supported. */ class OgreImporter : public BaseImporter { public: @@ -121,42 +73,10 @@ public: virtual void SetupProperties(const Importer *pImp); private: - //-------------------------------- OgreMesh.cpp ------------------------------- - - /// Helper Functions to read parts of the XML File. - void ReadSubMesh(const unsigned int submeshIndex, SubMesh &submesh, XmlReader *reader); - - /// Reads a single Vertexbuffer and writes its data in the Submesh. - static void ReadVertexBuffer(SubMesh &submesh, XmlReader *reader, const unsigned int numVertices); - - /// Reads bone weights are stores them into the given submesh. - static void ReadBoneWeights(SubMesh &submesh, XmlReader *reader); - - /// After Loading a SubMehs some work needs to be done (make all Vertexes unique, normalize weights). - static void ProcessSubMesh(SubMesh &submesh, SubMesh &sharedGeometry); - - /// Uses the bone data to convert a SubMesh into a aiMesh which will be created and returned. - aiMesh *CreateAssimpSubMesh(aiScene *pScene, const SubMesh &submesh, const std::vector &bones) const; - - //-------------------------------- OgreSkeleton.cpp ------------------------------- - - /// Writes the results in Bones and Animations, Filename is not const, because its call-by-value and the function will change it! - void ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOHandler, const aiScene *pScene, - const std::string &skeletonFile, std::vector &Bones, std::vector &Animations) const; - - /// Converts the animations in aiAnimations and puts them into the scene. - void PutAnimationsInScene(aiScene *pScene, const std::vector &Bones, const std::vector &Animations); - - /// Creates the aiSkeleton in current scene. - void CreateAssimpSkeleton(aiScene *pScene, const std::vector &bones, const std::vector &animations); - - /// Recursively creates a filled aiNode from a given root bone. - static aiNode* CreateNodeFromBone(int boneId, const std::vector &bones, aiNode *parent); - - //-------------------------------- OgreMaterial.cpp ------------------------------- - /// Read materials referenced by the @c mesh to @c pScene. void ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, Mesh *mesh); + void ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, MeshXml *mesh); + void AssignMaterials(aiScene *pScene, std::vector &materials); /// Reads material aiMaterial* ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string MaterialName); @@ -169,104 +89,8 @@ private: std::string m_userDefinedMaterialLibFile; bool m_detectTextureTypeFromFilename; - /// VertexBuffer for the sub meshes that use shader geometry. - SubMesh m_SharedGeometry; - std::map m_textures; }; - -/// Simplified face. -/** @todo Support other polygon types than just just triangles. Move to using aiFace. */ -struct Face -{ - unsigned int VertexIndices[3]; -}; - -/// Ogre Bone assignment -struct BoneAssignment -{ - /// Bone ID from Ogre. - unsigned int BoneId; - // Bone name for Assimp. - std::string BoneName; -}; - -/// Ogre Bone weight -struct BoneWeight -{ - /// Bone Id - unsigned int Id; - /// BoneWeight - float Value; -}; - - -/// Ogre Bone -struct Bone -{ - std::string Name; - - int Id; - int ParentId; - - aiVector3D Position; - aiVector3D RotationAxis; - float RotationAngle; - - aiMatrix4x4 BoneToWorldSpace; - - std::vector Children; - - Bone() : - Id(-1), - ParentId(-1), - RotationAngle(0.0f) - { - } - - /// Returns if this bone is parented. - bool IsParented() const { return (ParentId != -1); } - - /// This operator is needed to sort the bones by Id in a vector. - bool operator<(const Bone &other) const { return (Id < other.Id); } - - /// This operator is needed to find a bone by its name in a vector - bool operator==(const std::string& other) const { return Name == other; } - bool operator==(const aiString& other) const { return Name == std::string(other.data); } - - /// @note Implemented in OgreSkeleton.cpp - void CalculateBoneToWorldSpaceMatrix(std::vector& Bones); -}; - -/// Ogre animation key frame -/** Transformations for a frame. */ -struct KeyFrame -{ - float Time; - aiVector3D Position; - aiQuaternion Rotation; - aiVector3D Scaling; -}; - -/// Ogre animation track -/** Keyframes for one bone. */ -struct Track -{ - std::string BoneName; - std::vector Keyframes; -}; - -/// Ogre animation -struct Animation -{ - /// Name - std::string Name; - /// Length - float Length; - /// Tracks - std::vector Tracks; -}; - } // Ogre } // Assimp diff --git a/code/OgreMaterial.cpp b/code/OgreMaterial.cpp index 63ff53485..d4ee97366 100644 --- a/code/OgreMaterial.cpp +++ b/code/OgreMaterial.cpp @@ -42,12 +42,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER -#include -#include - #include "OgreImporter.h" #include "TinyFormatter.h" +#include "fast_atof.h" + +#include +#include + using namespace std; namespace Assimp @@ -66,7 +68,7 @@ void OgreImporter::ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIO // Create materials that can be found and parsed via the IOSystem. for (size_t i=0, len=mesh->NumSubMeshes(); iSubMesh(i); + SubMesh *submesh = mesh->GetSubMesh(i); if (submesh && !submesh->materialRef.empty()) { aiMaterial *material = ReadMaterial(pFile, pIOHandler, submesh->materialRef); @@ -78,7 +80,33 @@ void OgreImporter::ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIO } } - // Assign material to scene + AssignMaterials(pScene, materials); +} + +void OgreImporter::ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, MeshXml *mesh) +{ + std::vector materials; + + // Create materials that can be found and parsed via the IOSystem. + for (size_t i=0, len=mesh->NumSubMeshes(); iGetSubMesh(i); + if (submesh && !submesh->materialRef.empty()) + { + aiMaterial *material = ReadMaterial(pFile, pIOHandler, submesh->materialRef); + if (material) + { + submesh->materialIndex = materials.size(); + materials.push_back(material); + } + } + } + + AssignMaterials(pScene, materials); +} + +void OgreImporter::AssignMaterials(aiScene *pScene, std::vector &materials) +{ pScene->mNumMaterials = materials.size(); if (pScene->mNumMaterials > 0) { diff --git a/code/OgreMesh.cpp b/code/OgreMesh.cpp deleted file mode 100644 index a1b213127..000000000 --- a/code/OgreMesh.cpp +++ /dev/null @@ -1,570 +0,0 @@ -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2012, assimp team -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -#include "AssimpPCH.h" - -#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER - -#include "OgreImporter.h" -#include "TinyFormatter.h" - -using namespace std; - -namespace Assimp -{ -namespace Ogre -{ - -void OgreImporter::ReadSubMesh(const unsigned int submeshIndex, SubMesh &submesh, XmlReader *reader) -{ - if (reader->getAttributeValue("material")) { - submesh.MaterialName = GetAttribute(reader, "material"); - } - if (reader->getAttributeValue("use32bitindexes")) { - submesh.Use32bitIndexes = GetAttribute(reader, "use32bitindexes"); - } - if (reader->getAttributeValue("usesharedvertices")) { - submesh.UseSharedGeometry = GetAttribute(reader, "usesharedvertices"); - } - - DefaultLogger::get()->debug(Formatter::format() << "Reading submesh " << submeshIndex); - DefaultLogger::get()->debug(Formatter::format() << " - Material '" << submesh.MaterialName << "'"); - DefaultLogger::get()->debug(Formatter::format() << " - Shader geometry = " << (submesh.UseSharedGeometry ? "true" : "false") << - ", 32bit indexes = " << (submesh.Use32bitIndexes ? "true" : "false")); - - //TODO: maybe we have alsways just 1 faces and 1 geometry and always in this order. this loop will only work correct, when the order - //of faces and geometry changed, and not if we have more than one of one - /// @todo Fix above comment with better read logic below - - NextNode(reader); - string currentNodeName = reader->getNodeName(); - - const string nnFaces = "faces"; - const string nnFace = "face"; - const string nnGeometry = "geometry"; - const string nnBoneAssignments = "boneassignments"; - const string nnVertexBuffer = "vertexbuffer"; - - bool quadWarned = false; - - while(currentNodeName == nnFaces || - currentNodeName == nnGeometry || - currentNodeName == nnBoneAssignments) - { - if (currentNodeName == nnFaces) - { - unsigned int numFaces = GetAttribute(reader, "count"); - - NextNode(reader); - currentNodeName = reader->getNodeName(); - - while(currentNodeName == nnFace) - { - Face NewFace; - NewFace.VertexIndices[0] = GetAttribute(reader, "v1"); - NewFace.VertexIndices[1] = GetAttribute(reader, "v2"); - NewFace.VertexIndices[2] = GetAttribute(reader, "v3"); - - /// @todo Support quads - if (!quadWarned && reader->getAttributeValue("v4")) { - DefaultLogger::get()->warn("Submesh has quads, only triangles are supported at the moment!"); - } - - submesh.Faces.push_back(NewFace); - - // Advance - NextNode(reader); - currentNodeName = reader->getNodeName(); - } - - if (submesh.Faces.size() == numFaces) - { - DefaultLogger::get()->debug(Formatter::format() << " - Faces " << numFaces); - } - else - { - throw DeadlyImportError(Formatter::format() << "Read only " << submesh.Faces.size() << " faces when should have read " << numFaces); - } - } - else if (currentNodeName == nnGeometry) - { - unsigned int numVertices = GetAttribute(reader, "vertexcount"); - - NextNode(reader); - while(string(reader->getNodeName()) == nnVertexBuffer) { - ReadVertexBuffer(submesh, reader, numVertices); - } - } - else if (reader->getNodeName() == nnBoneAssignments) - { - ReadBoneWeights(submesh, reader); - } - - currentNodeName = reader->getNodeName(); - } -} - -void OgreImporter::ReadVertexBuffer(SubMesh &submesh, XmlReader *reader, const unsigned int numVertices) -{ - DefaultLogger::get()->debug(Formatter::format() << "Reading vertex buffer with " << numVertices << " vertices"); - - submesh.HasGeometry = true; - - if (reader->getAttributeValue("positions") && GetAttribute(reader, "positions")) - { - submesh.HasPositions = true; - submesh.Positions.reserve(numVertices); - DefaultLogger::get()->debug(" - Has positions"); - } - if (reader->getAttributeValue("normals") && GetAttribute(reader, "normals")) - { - submesh.HasNormals = true; - submesh.Normals.reserve(numVertices); - DefaultLogger::get()->debug(" - Has normals"); - } - if (reader->getAttributeValue("tangents") && GetAttribute(reader, "tangents")) - { - submesh.HasTangents = true; - submesh.Tangents.reserve(numVertices); - DefaultLogger::get()->debug(" - Has tangents"); - } - if (reader->getAttributeValue("texture_coords")) - { - submesh.Uvs.resize(GetAttribute(reader, "texture_coords")); - for(size_t i=0, len=submesh.Uvs.size(); idebug(Formatter::format() << " - Has " << submesh.Uvs.size() << " texture coords"); - } - - if (!submesh.HasPositions) { - throw DeadlyImportError("Vertex buffer does not contain positions!"); - } - - const string nnVertex = "vertex"; - const string nnPosition = "position"; - const string nnNormal = "normal"; - const string nnTangent = "tangent"; - const string nnBinormal = "binormal"; - const string nnTexCoord = "texcoord"; - const string nnColorDiffuse = "colour_diffuse"; - const string nnColorSpecular = "colour_specular"; - - bool warnBinormal = true; - bool warnColorDiffuse = true; - bool warnColorSpecular = true; - - NextNode(reader); - string currentNodeName = reader->getNodeName(); - - /// @todo Make this loop nicer. - while(currentNodeName == nnVertex || - currentNodeName == nnPosition || - currentNodeName == nnNormal || - currentNodeName == nnTangent || - currentNodeName == nnBinormal || - currentNodeName == nnTexCoord || - currentNodeName == nnColorDiffuse || - currentNodeName == nnColorSpecular) - { - if (currentNodeName == nnVertex) - { - NextNode(reader); - currentNodeName = reader->getNodeName(); - } - - /// @todo Implement nnBinormal, nnColorDiffuse and nnColorSpecular - - if (submesh.HasPositions && currentNodeName == nnPosition) - { - aiVector3D NewPos; - NewPos.x = GetAttribute(reader, "x"); - NewPos.y = GetAttribute(reader, "y"); - NewPos.z = GetAttribute(reader, "z"); - submesh.Positions.push_back(NewPos); - } - else if (submesh.HasNormals && currentNodeName == nnNormal) - { - aiVector3D NewNormal; - NewNormal.x = GetAttribute(reader, "x"); - NewNormal.y = GetAttribute(reader, "y"); - NewNormal.z = GetAttribute(reader, "z"); - submesh.Normals.push_back(NewNormal); - } - else if (submesh.HasTangents && currentNodeName == nnTangent) - { - aiVector3D NewTangent; - NewTangent.x = GetAttribute(reader, "x"); - NewTangent.y = GetAttribute(reader, "y"); - NewTangent.z = GetAttribute(reader, "z"); - submesh.Tangents.push_back(NewTangent); - } - else if (submesh.Uvs.size() > 0 && currentNodeName == nnTexCoord) - { - for(size_t i=0, len=submesh.Uvs.size(); i(reader, "u"); - NewUv.y = GetAttribute(reader, "v") * (-1)+1; //flip the uv vertikal, blender exports them so! (ahem... @todo ????) - submesh.Uvs[i].push_back(NewUv); - - NextNode(reader); - currentNodeName = reader->getNodeName(); - } - // Continue main loop as above already read next node - continue; - } - else - { - /// @todo Remove this stuff once implemented. We only want to log warnings once per element. - bool warn = true; - if (currentNodeName == nnBinormal) - { - if (warnBinormal) - { - warnBinormal = false; - } - else - { - warn = false; - } - } - else if (currentNodeName == nnColorDiffuse) - { - if (warnColorDiffuse) - { - warnColorDiffuse = false; - } - else - { - warn = false; - } - } - else if (currentNodeName == nnColorSpecular) - { - if (warnColorSpecular) - { - warnColorSpecular = false; - } - else - { - warn = false; - } - } - if (warn) { - DefaultLogger::get()->warn(string("Vertex buffer attribute read not implemented for element: ") + currentNodeName); - } - } - - // Advance - NextNode(reader); - currentNodeName = reader->getNodeName(); - } - - DefaultLogger::get()->debug(Formatter::format() << - " - Positions " << submesh.Positions.size() << - " Normals " << submesh.Normals.size() << - " TexCoords " << submesh.Uvs.size() << - " Tangents " << submesh.Tangents.size()); - - // Sanity checks - if (submesh.HasNormals && submesh.Normals.size() != numVertices) { - throw DeadlyImportError(Formatter::format() << "Read only " << submesh.Normals.size() << " normals when should have read " << numVertices); - } - if (submesh.HasTangents && submesh.Tangents.size() != numVertices) { - throw DeadlyImportError(Formatter::format() << "Read only " << submesh.Tangents.size() << " tangents when should have read " << numVertices); - } - for(unsigned int i=0; i(reader, "boneindex"); - weight.Value = GetAttribute(reader, "weight"); - - //calculate the number of bones used (this is the highest id +1 becuase bone ids start at 0) - /// @todo This can probably be refactored to something else. - submesh.BonesUsed = max(submesh.BonesUsed, weight.Id+1); - - const unsigned int vertexId = GetAttribute(reader, "vertexindex"); - submesh.Weights[vertexId].push_back(weight); - - NextNode(reader); - } - DefaultLogger::get()->debug(Formatter::format() << " - Bone weights " << numRead); -} - -void OgreImporter::ProcessSubMesh(SubMesh &submesh, SubMesh &sharedGeometry) -{ - // Make all vertexes unique. Required by Assimp. - vector uniqueFaceList(submesh.Faces.size()); - unsigned int uniqueVertexCount = submesh.Faces.size() * 3; - - vector uniquePositions(uniqueVertexCount); - vector uniqueNormals(uniqueVertexCount); - vector uniqueTangents(uniqueVertexCount); - - vector > uniqueWeights(uniqueVertexCount); - vector > uniqueUvs(submesh.UseSharedGeometry ? sharedGeometry.Uvs.size() : submesh.Uvs.size()); - - for(size_t uvi=0; uvi &uv = vertexSource.Uvs[uvi]; - uniqueUvs[uvi][pos] = uv[v1]; - uniqueUvs[uvi][pos+1] = uv[v2]; - uniqueUvs[uvi][pos+2] = uv[v3]; - } - - if (!vertexSource.Weights.empty()) - { - uniqueWeights[pos] = vertexSource.Weights[v1]; - uniqueWeights[pos+1] = vertexSource.Weights[v2]; - uniqueWeights[pos+2] = vertexSource.Weights[v3]; - } - } - - // Now we have the unique data, but want them in the SubMesh, so we swap all the containers. - // If we don't have one of them, we just swap empty containers, so everything is ok. - submesh.Faces.swap(uniqueFaceList); - submesh.Positions.swap(uniquePositions); - submesh.Normals.swap(uniqueNormals); - submesh.Tangents.swap(uniqueTangents); - submesh.Uvs.swap(uniqueUvs); - submesh.Weights.swap(uniqueWeights); - - // Normalize bone weights - // For example the Blender exporter doesn't care about whether the sum of all bone - // weights for a single vertex equals 1 or not, so validate here. - for(size_t vertexId=0, wlen=submesh.Weights.size(); vertexId &weights = submesh.Weights[vertexId]; - - float sum = 0.0f; - for(size_t boneId=0, blen=weights.size(); boneId (1.0f + 0.05f))) - { - for(size_t boneId=0, blen=weights.size(); boneId& bones) const -{ - const size_t sizeVector3D = sizeof(aiVector3D); - - aiMesh *dest = new aiMesh(); - - // Material - if (submesh.MaterialIndex != -1) - dest->mMaterialIndex = submesh.MaterialIndex; - - // Positions - dest->mVertices = new aiVector3D[submesh.Positions.size()]; - dest->mNumVertices = submesh.Positions.size(); - memcpy(dest->mVertices, &submesh.Positions[0], submesh.Positions.size() * sizeVector3D); - - // Normals - if (submesh.HasNormals) - { - dest->mNormals = new aiVector3D[submesh.Normals.size()]; - memcpy(dest->mNormals, &submesh.Normals[0], submesh.Normals.size() * sizeVector3D); - } - - // Tangents - // Until we have support for bitangents, no tangents will be written - /// @todo Investigate why the above? - if (submesh.HasTangents) - { - DefaultLogger::get()->warn("Tangents found from Ogre mesh but writing to Assimp mesh not yet supported!"); - //dest->mTangents = new aiVector3D[submesh.Tangents.size()]; - //memcpy(dest->mTangents, &submesh.Tangents[0], submesh.Tangents.size() * sizeVector3D); - } - - // UVs - for (size_t i=0, len=submesh.Uvs.size(); imNumUVComponents[i] = 2; - dest->mTextureCoords[i] = new aiVector3D[submesh.Uvs[i].size()]; - memcpy(dest->mTextureCoords[i], &(submesh.Uvs[i][0]), submesh.Uvs[i].size() * sizeVector3D); - } - - // Bone weights. Convert internal vertex-to-bone mapping to bone-to-vertex. - vector > assimpWeights(submesh.BonesUsed); - for(size_t vertexId=0, len=submesh.Weights.size(); vertexId &vertexWeights = submesh.Weights[vertexId]; - for (size_t boneId=0, len=vertexWeights.size(); boneId assimpBones; - assimpBones.reserve(submesh.BonesUsed); - - for(size_t boneId=0, len=submesh.BonesUsed; boneId &boneWeights = assimpWeights[boneId]; - if (boneWeights.size() == 0) { - continue; - } - - // @note The bones list is sorted by id's, this was done in LoadSkeleton. - aiBone *assimpBone = new aiBone(); - assimpBone->mName = bones[boneId].Name; - assimpBone->mOffsetMatrix = bones[boneId].BoneToWorldSpace; - assimpBone->mNumWeights = boneWeights.size(); - assimpBone->mWeights = new aiVertexWeight[boneWeights.size()]; - memcpy(assimpBone->mWeights, &boneWeights[0], boneWeights.size() * sizeof(aiVertexWeight)); - - assimpBones.push_back(assimpBone); - } - - if (!assimpBones.empty()) - { - dest->mBones = new aiBone*[assimpBones.size()]; - dest->mNumBones = assimpBones.size(); - - for(size_t i=0, len=assimpBones.size(); imBones[i] = assimpBones[i]; - } - } - - // Faces - dest->mFaces = new aiFace[submesh.Faces.size()]; - dest->mNumFaces = submesh.Faces.size(); - - for(size_t i=0, len=submesh.Faces.size(); imFaces[i].mNumIndices = 3; - dest->mFaces[i].mIndices = new unsigned int[3]; - - const Face &f = submesh.Faces[i]; - dest->mFaces[i].mIndices[0] = f.VertexIndices[0]; - dest->mFaces[i].mIndices[1] = f.VertexIndices[1]; - dest->mFaces[i].mIndices[2] = f.VertexIndices[2]; - } - - return dest; -} - -} // Ogre -} // Assimp - -#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/code/OgreParsingUtils.h b/code/OgreParsingUtils.h index 0771a0d30..d3a7aa8bf 100644 --- a/code/OgreParsingUtils.h +++ b/code/OgreParsingUtils.h @@ -44,9 +44,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER #include "ParsingUtils.h" -#include "irrXMLWrapper.h" -#include "fast_atof.h" #include + namespace Assimp { namespace Ogre @@ -116,119 +115,6 @@ static inline std::string &Trim(std::string &s, bool newlines = true) return TrimLeft(TrimRight(s, newlines), newlines); } -typedef irr::io::IrrXMLReader XmlReader; - -static void ThrowAttibuteError(const XmlReader* reader, const std::string &name, const std::string &error = "") -{ - if (!error.empty()) - { - throw DeadlyImportError(error + " in node '" + std::string(reader->getNodeName()) + "' and attribute '" + name + "'"); - } - else - { - throw DeadlyImportError("Attribute '" + name + "' does not exist in node '" + std::string(reader->getNodeName()) + "'"); - } -} - -template -inline T GetAttribute(const XmlReader* reader, const std::string &name); - -template<> -inline int GetAttribute(const XmlReader* reader, const std::string &name) -{ - const char* value = reader->getAttributeValue(name.c_str()); - if (value) - { - return atoi(value); - } - else - { - ThrowAttibuteError(reader, name); - return 0; - } -} - -template<> -inline unsigned int GetAttribute(const XmlReader* reader, const std::string &name) -{ - const char* value = reader->getAttributeValue(name.c_str()); - if (value) - { - return static_cast(atoi(value)); ///< @todo Find a better way... - } - else - { - ThrowAttibuteError(reader, name); - return 0; - } -} - -template<> -inline float GetAttribute(const XmlReader* reader, const std::string &name) -{ - const char* value = reader->getAttributeValue(name.c_str()); - if (value) - { - return fast_atof(value); - } - else - { - ThrowAttibuteError(reader, name); - return 0.f; - } -} - -template<> -inline std::string GetAttribute(const XmlReader* reader, const std::string &name) -{ - const char* value = reader->getAttributeValue(name.c_str()); - if (value) - { - return std::string(value); - } - else - { - ThrowAttibuteError(reader, name); - return ""; - } -} - -template<> -inline bool GetAttribute(const XmlReader* reader, const std::string &name) -{ - std::string value = Ogre::ToLower(GetAttribute(reader, name)); - if (ASSIMP_stricmp(value, "true") == 0) - { - return true; - } - else if (ASSIMP_stricmp(value, "false") == 0) - { - return false; - } - else - { - ThrowAttibuteError(reader, name, "Boolean value is expected to be 'true' or 'false', encountered '" + value + "'"); - return false; - } -} - -inline bool NextNode(XmlReader* reader) -{ - do - { - if (!reader->read()) { - return false; - } - } - while(reader->getNodeType() != irr::io::EXN_ELEMENT); - return true; -} - -inline bool CurrentNodeNameEquals(const XmlReader* reader, const std::string &name) -{ - return (ASSIMP_stricmp(std::string(reader->getNodeName()), name) == 0); -} - /// Skips a line from current @ss position until a newline. Returns the skipped part. static inline std::string SkipLine(std::stringstream &ss) { diff --git a/code/OgreSkeleton.cpp b/code/OgreSkeleton.cpp deleted file mode 100644 index b8ac4e43b..000000000 --- a/code/OgreSkeleton.cpp +++ /dev/null @@ -1,446 +0,0 @@ -/* -Open Asset Import Library (assimp) ----------------------------------------------------------------------- - -Copyright (c) 2006-2012, assimp team -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the -following conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ----------------------------------------------------------------------- -*/ - -#include "AssimpPCH.h" - -#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER - -#include "OgreImporter.h" -#include "TinyFormatter.h" - -using namespace std; - -namespace Assimp -{ -namespace Ogre -{ - -void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOHandler, const aiScene *pScene, - const std::string &skeletonFile, vector &Bones, vector &Animations) const -{ - string filename = skeletonFile; - 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); - } - - DefaultLogger::get()->debug("Reading skeleton '" + filename + "'"); - - // Root - NextNode(reader); - if (!CurrentNodeNameEquals(reader, "skeleton")) { - throw DeadlyImportError("Root node is not but <" + string(reader->getNodeName()) + "> in " + filename); - } - - // Bones - NextNode(reader); - if (!CurrentNodeNameEquals(reader, "bones")) { - throw DeadlyImportError("No node in skeleton " + skeletonFile); - } - - NextNode(reader); - while(CurrentNodeNameEquals(reader, "bone")) - { - /** @todo Fix this mandatory ordering. Some exporters might just write rotation first etc. - There is no technical reason this has to be so strict. */ - - Bone bone; - bone.Id = GetAttribute(reader, "id"); - bone.Name = GetAttribute(reader, "name"); - - NextNode(reader); - if (!CurrentNodeNameEquals(reader, "position")) { - throw DeadlyImportError("Position is not first node in Bone!"); - } - - bone.Position.x = GetAttribute(reader, "x"); - bone.Position.y = GetAttribute(reader, "y"); - bone.Position.z = GetAttribute(reader, "z"); - - NextNode(reader); - if (!CurrentNodeNameEquals(reader, "rotation")) { - throw DeadlyImportError("Rotation is not the second node in Bone!"); - } - - bone.RotationAngle = GetAttribute(reader, "angle"); - - NextNode(reader); - if (!CurrentNodeNameEquals(reader, "axis")) { - throw DeadlyImportError("No axis specified for bone rotation!"); - } - - bone.RotationAxis.x = GetAttribute(reader, "x"); - bone.RotationAxis.y = GetAttribute(reader, "y"); - bone.RotationAxis.z = GetAttribute(reader, "z"); - - Bones.push_back(bone); - - NextNode(reader); - } - - // Order bones by Id - std::sort(Bones.begin(), Bones.end()); - - // 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=Bones.size(); i(Bones[i].Id) != static_cast(i)) { - throw DeadlyImportError("Bone Ids are not in sequence in " + skeletonFile); - } - } - - DefaultLogger::get()->debug(Formatter::format() << " - Bones " << Bones.size()); - - // Bone hierarchy - if (!CurrentNodeNameEquals(reader, "bonehierarchy")) { - throw DeadlyImportError("No node found after in " + skeletonFile); - } - - NextNode(reader); - while(CurrentNodeNameEquals(reader, "boneparent")) - { - string childName = GetAttribute(reader, "bone"); - string parentName = GetAttribute(reader, "parent"); - - vector::iterator iterChild = find(Bones.begin(), Bones.end(), childName); - vector::iterator iterParent = find(Bones.begin(), Bones.end(), parentName); - - if (iterChild != Bones.end() && iterParent != Bones.end()) - { - iterChild->ParentId = iterParent->Id; - iterParent->Children.push_back(iterChild->Id); - } - else - { - DefaultLogger::get()->warn("Failed to find bones for parenting: Child " + childName + " Parent " + parentName); - } - - NextNode(reader); - } - - // Calculate bone matrices for root bones. Recursively does their children. - BOOST_FOREACH(Bone &theBone, Bones) - { - if (!theBone.IsParented()) { - theBone.CalculateBoneToWorldSpaceMatrix(Bones); - } - } - - aiVector3D zeroVec(0.f, 0.f, 0.f); - - // Animations - if (CurrentNodeNameEquals(reader, "animations")) - { - DefaultLogger::get()->debug(" - Animations"); - - NextNode(reader); - while(CurrentNodeNameEquals(reader, "animation")) - { - Animation animation; - animation.Name = GetAttribute(reader, "name"); - animation.Length = GetAttribute(reader, "length"); - - // Tracks - NextNode(reader); - if (!CurrentNodeNameEquals(reader, "tracks")) { - throw DeadlyImportError("No node found in animation '" + animation.Name + "' in " + skeletonFile); - } - - NextNode(reader); - while(CurrentNodeNameEquals(reader, "track")) - { - Track track; - track.BoneName = GetAttribute(reader, "bone"); - - // Keyframes - NextNode(reader); - if (!CurrentNodeNameEquals(reader, "keyframes")) { - throw DeadlyImportError("No node found in a track in animation '" + animation.Name + "' in " + skeletonFile); - } - - NextNode(reader); - while(CurrentNodeNameEquals(reader, "keyframe")) - { - KeyFrame keyFrame; - keyFrame.Time = GetAttribute(reader, "time"); - - NextNode(reader); - while(CurrentNodeNameEquals(reader, "translate") || CurrentNodeNameEquals(reader, "rotate") || CurrentNodeNameEquals(reader, "scale")) - { - if (CurrentNodeNameEquals(reader, "translate")) - { - keyFrame.Position.x = GetAttribute(reader, "x"); - keyFrame.Position.y = GetAttribute(reader, "y"); - keyFrame.Position.z = GetAttribute(reader, "z"); - } - else if (CurrentNodeNameEquals(reader, "rotate")) - { - float angle = GetAttribute(reader, "angle"); - - NextNode(reader); - if (!CurrentNodeNameEquals(reader, "axis")) { - throw DeadlyImportError("No axis for keyframe rotation in animation '" + animation.Name + "'"); - } - - aiVector3D axis; - axis.x = GetAttribute(reader, "x"); - axis.y = GetAttribute(reader, "y"); - axis.z = GetAttribute(reader, "z"); - - 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 '" + animation.Name + "'"); - } - } - keyFrame.Rotation = aiQuaternion(axis, angle); - } - else if (CurrentNodeNameEquals(reader, "scale")) - { - keyFrame.Scaling.x = GetAttribute(reader, "x"); - keyFrame.Scaling.y = GetAttribute(reader, "y"); - keyFrame.Scaling.z = GetAttribute(reader, "z"); - } - NextNode(reader); - } - track.Keyframes.push_back(keyFrame); - } - animation.Tracks.push_back(track); - } - Animations.push_back(animation); - - DefaultLogger::get()->debug(Formatter::format() << " " << animation.Name << " (" << animation.Length << " sec, " << animation.Tracks.size() << " tracks)"); - } - } -} - -void OgreImporter::CreateAssimpSkeleton(aiScene *pScene, const std::vector &bones, const std::vector &animations) -{ - if (bones.empty()) { - return; - } - - if (!pScene->mRootNode) { - throw DeadlyImportError("Creating Assimp skeleton: No root node created!"); - } - if (pScene->mRootNode->mNumChildren > 0) { - throw DeadlyImportError("Creating Assimp skeleton: Root node already has children!"); - } - - // Bones - vector rootBones; - BOOST_FOREACH(const Bone &bone, bones) - { - if (!bone.IsParented()) { - rootBones.push_back(CreateNodeFromBone(bone.Id, bones, pScene->mRootNode)); - } - } - - if (!rootBones.empty()) - { - pScene->mRootNode->mChildren = new aiNode*[rootBones.size()]; - pScene->mRootNode->mNumChildren = rootBones.size(); - - for(size_t i=0, len=rootBones.size(); imRootNode->mChildren[i] = rootBones[i]; - } - } - - // TODO: Auf nicht vorhandene Animationskeys achten! - // @todo Pay attention to non-existing animation Keys (google translated from above german comment) - - // Animations - if (!animations.empty()) - { - pScene->mAnimations = new aiAnimation*[animations.size()]; - pScene->mNumAnimations = animations.size(); - - for(size_t ai=0, alen=animations.size(); aimName = aSource.Name; - animation->mDuration = aSource.Length; - animation->mTicksPerSecond = 1.0f; - - // Tracks - animation->mChannels = new aiNodeAnim*[aSource.Tracks.size()]; - animation->mNumChannels = aSource.Tracks.size(); - - for(size_t ti=0, tlen=aSource.Tracks.size(); timNodeName = tSource.BoneName; - - // We need this, to access the bones default pose. - // Which we need to make keys absolute to the default bone pose. - vector::const_iterator boneIter = find(bones.begin(), bones.end(), tSource.BoneName); - if (boneIter == bones.end()) - { - for(size_t createdAnimationIndex=0; createdAnimationIndexmAnimations[createdAnimationIndex]; - } - delete [] pScene->mAnimations; - pScene->mAnimations = NULL; - pScene->mNumAnimations = 0; - - DefaultLogger::get()->error("Failed to find bone for name " + tSource.BoneName + " when creating animation " + aSource.Name + - ". This is a serious error, animations wont be imported."); - return; - } - - aiMatrix4x4 t0, t1; - aiMatrix4x4 defaultBonePose = aiMatrix4x4::Translation(boneIter->Position, t1) * aiMatrix4x4::Rotation(boneIter->RotationAngle, boneIter->RotationAxis, t0); - - // Keyframes - unsigned int numKeyframes = tSource.Keyframes.size(); - - animationNode->mPositionKeys = new aiVectorKey[numKeyframes]; - animationNode->mRotationKeys = new aiQuatKey[numKeyframes]; - animationNode->mScalingKeys = new aiVectorKey[numKeyframes]; - animationNode->mNumPositionKeys = numKeyframes; - animationNode->mNumRotationKeys = numKeyframes; - animationNode->mNumScalingKeys = numKeyframes; - - //...and fill them - for(size_t kfi=0; kfimPositionKeys[kfi].mTime = static_cast(kfSource.Time); - animationNode->mRotationKeys[kfi].mTime = static_cast(kfSource.Time); - animationNode->mScalingKeys[kfi].mTime = static_cast(kfSource.Time); - - animationNode->mPositionKeys[kfi].mValue = kfPos; - animationNode->mRotationKeys[kfi].mValue = kfRot; - animationNode->mScalingKeys[kfi].mValue = kfScale; - } - animation->mChannels[ti] = animationNode; - } - pScene->mAnimations[ai] = animation; - } - } -} - -aiNode* OgreImporter::CreateNodeFromBone(int boneId, const std::vector &bones, aiNode* parent) -{ - aiMatrix4x4 t0,t1; - const Bone &source = bones[boneId]; - - aiNode* boneNode = new aiNode(source.Name); - boneNode->mParent = parent; - boneNode->mTransformation = aiMatrix4x4::Translation(source.Position, t0) * aiMatrix4x4::Rotation(source.RotationAngle, source.RotationAxis, t1); - - if (!source.Children.empty()) - { - boneNode->mChildren = new aiNode*[source.Children.size()]; - boneNode->mNumChildren = source.Children.size(); - - for(size_t i=0, len=source.Children.size(); imChildren[i] = CreateNodeFromBone(source.Children[i], bones, boneNode); - } - } - - return boneNode; -} - -void Bone::CalculateBoneToWorldSpaceMatrix(vector &Bones) -{ - aiMatrix4x4 t0, t1; - aiMatrix4x4 transform = aiMatrix4x4::Rotation(-RotationAngle, RotationAxis, t1) * aiMatrix4x4::Translation(-Position, t0); - - if (!IsParented()) - { - BoneToWorldSpace = transform; - } - else - { - BoneToWorldSpace = transform * Bones[ParentId].BoneToWorldSpace; - } - - // Recursively for all children now that the parent matrix has been calculated. - BOOST_FOREACH(int childId, Children) - { - Bones[childId].CalculateBoneToWorldSpaceMatrix(Bones); - } -} - -} // Ogre -} // Assimp - -#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/code/OgreStructs.cpp b/code/OgreStructs.cpp index 1ba269ef4..e81b0f0a6 100644 --- a/code/OgreStructs.cpp +++ b/code/OgreStructs.cpp @@ -234,10 +234,68 @@ std::string VertexElement::SemanticToString(Semantic semantic) return "Uknown_VertexElement::Semantic"; } +// IVertexData + +IVertexData::IVertexData() : + count(0) +{ +} + +bool IVertexData::HasBoneAssignments() const +{ + return !boneAssignments.empty(); +} + +void IVertexData::AddVertexMapping(uint32_t oldIndex, uint32_t newIndex) +{ + BoneAssignmentsForVertex(oldIndex, newIndex, boneAssignmentsMap[newIndex]); + vertexIndexMapping[oldIndex].push_back(newIndex); +} + +void IVertexData::BoneAssignmentsForVertex(uint32_t currentIndex, uint32_t newIndex, VertexBoneAssignmentList &dest) const +{ + for (VertexBoneAssignmentList::const_iterator iter=boneAssignments.begin(), end=boneAssignments.end(); + iter!=end; ++iter) + { + if (iter->vertexIndex == currentIndex) + { + VertexBoneAssignment a = (*iter); + a.vertexIndex = newIndex; + dest.push_back(a); + } + } +} + +AssimpVertexBoneWeightList IVertexData::AssimpBoneWeights(size_t vertices) +{ + AssimpVertexBoneWeightList weights; + for(size_t vi=0; vi &boneWeights = weights[iter->boneIndex]; + boneWeights.push_back(aiVertexWeight(vi, iter->weight)); + } + } + return weights; +} + +std::set IVertexData::ReferencedBonesByWeights() const +{ + std::set referenced; + for (VertexBoneAssignmentList::const_iterator iter=boneAssignments.begin(), end=boneAssignments.end(); + iter!=end; ++iter) + { + referenced.insert(iter->boneIndex); + } + return referenced; +} + // VertexData -VertexData::VertexData() : - count(0) +VertexData::VertexData() { } @@ -282,6 +340,32 @@ VertexElement *VertexData::GetVertexElement(VertexElement::Semantic semantic, ui return 0; } +// VertexDataXml + +VertexDataXml::VertexDataXml() +{ +} + +bool VertexDataXml::HasNormals() const +{ + return !normals.empty(); +} + +bool VertexDataXml::HasTangents() const +{ + return !tangents.empty(); +} + +bool VertexDataXml::HasUvs() const +{ + return !uvs.empty(); +} + +size_t VertexDataXml::NumUvs() const +{ + return uvs.size(); +} + // IndexData IndexData::IndexData() : @@ -316,6 +400,7 @@ size_t IndexData::FaceSize() const Mesh::Mesh() : sharedVertexData(0), + skeleton(0), hasSkeletalAnimations(false) { } @@ -327,6 +412,7 @@ Mesh::~Mesh() void Mesh::Reset() { + OGRE_SAFE_DELETE(skeleton) OGRE_SAFE_DELETE(sharedVertexData) for(size_t i=0, len=subMeshes.size(); iindex == index) @@ -358,7 +444,7 @@ SubMesh2 *Mesh::SubMesh(uint16_t index) const void Mesh::ConvertToAssimpScene(aiScene* dest) { - // Export meshes + // Setup dest->mNumMeshes = NumSubMeshes(); dest->mMeshes = new aiMesh*[dest->mNumMeshes]; @@ -367,36 +453,44 @@ void Mesh::ConvertToAssimpScene(aiScene* dest) dest->mRootNode->mNumMeshes = dest->mNumMeshes; dest->mRootNode->mMeshes = new unsigned int[dest->mRootNode->mNumMeshes]; - for(size_t i=0; imNumMeshes; ++i) { + // Export meshes + for(size_t i=0; imNumMeshes; ++i) + { dest->mMeshes[i] = subMeshes[i]->ConvertToAssimpMesh(this); dest->mRootNode->mMeshes[i] = i; } } -// SubMesh2 +// ISubMesh -SubMesh2::SubMesh2() : +ISubMesh::ISubMesh() : index(0), - vertexData(0), - indexData(new IndexData()), + materialIndex(-1), usesSharedVertexData(false), - operationType(OT_POINT_LIST), - materialIndex(-1) + operationType(OT_POINT_LIST) { } -SubMesh2::~SubMesh2() +// SubMesh + +SubMesh::SubMesh() : + vertexData(0), + indexData(new IndexData()) +{ +} + +SubMesh::~SubMesh() { Reset(); } -void SubMesh2::Reset() +void SubMesh::Reset() { OGRE_SAFE_DELETE(vertexData) OGRE_SAFE_DELETE(indexData) } -aiMesh *SubMesh2::ConvertToAssimpMesh(Mesh *parent) +aiMesh *SubMesh::ConvertToAssimpMesh(Mesh *parent) { if (operationType != OT_TRIANGLE_LIST) { throw DeadlyImportError(Formatter::format() << "Only mesh operation type OT_TRIANGLE_LIST is supported. Found " << operationType); @@ -456,6 +550,8 @@ aiMesh *SubMesh2::ConvertToAssimpMesh(Mesh *parent) const size_t vWidthUv1 = (uv1Element ? src->VertexSize(uv1Element->source) : 0); const size_t vWidthUv2 = (uv2Element ? src->VertexSize(uv2Element->source) : 0); + bool boneAssignments = src->HasBoneAssignments(); + // Prepare normals if (normals) dest->mNormals = new aiVector3D[dest->mNumVertices]; @@ -530,6 +626,7 @@ aiMesh *SubMesh2::ConvertToAssimpMesh(Mesh *parent) // Ogres vertex index to ref into the source buffers. const size_t ogreVertexIndex = ogreFace.mIndices[v]; + src->AddVertexMapping(ogreVertexIndex, newIndex); // Position positions->Seek((vWidthPosition * ogreVertexIndex) + positionsElement->offset, aiOrigin_SET); @@ -553,29 +650,505 @@ aiMesh *SubMesh2::ConvertToAssimpMesh(Mesh *parent) uv2->Seek((vWidthUv2 * ogreVertexIndex) + uv2Element->offset, aiOrigin_SET); uv2->Read(&uv2Dest[newIndex], sizeUv2, 1); } - - /// @todo Bones and bone weights. } - } + } + + // Bones and bone weights + if (parent->skeleton && boneAssignments) + { + AssimpVertexBoneWeightList weights = src->AssimpBoneWeights(dest->mNumVertices); + std::set referencedBones = src->ReferencedBonesByWeights(); + + dest->mNumBones = referencedBones.size(); + dest->mBones = new aiBone*[dest->mNumBones]; + + size_t assimpBoneIndex = 0; + for(std::set::const_iterator rbIter=referencedBones.begin(), rbEnd=referencedBones.end(); rbIter != rbEnd; ++rbIter, ++assimpBoneIndex) + { + Bone *bone = parent->skeleton->BoneById((*rbIter)); + dest->mBones[assimpBoneIndex] = bone->ConvertToAssimpBone(parent->skeleton, weights[bone->id]); + } + } + return dest; } -// Animation2 +// MeshXml -Animation2::Animation2(Mesh *_parentMesh) : - parentMesh(_parentMesh), +MeshXml::MeshXml() : + sharedVertexData(0), + skeleton(0) +{ +} + +MeshXml::~MeshXml() +{ + Reset(); +} + +void MeshXml::Reset() +{ + OGRE_SAFE_DELETE(skeleton) + OGRE_SAFE_DELETE(sharedVertexData) + + for(size_t i=0, len=subMeshes.size(); iindex == index) + return subMeshes[i]; + return 0; +} + +void MeshXml::ConvertToAssimpScene(aiScene* dest) +{ + // Setup + dest->mNumMeshes = NumSubMeshes(); + dest->mMeshes = new aiMesh*[dest->mNumMeshes]; + + // Create root node + dest->mRootNode = new aiNode(); + dest->mRootNode->mNumMeshes = dest->mNumMeshes; + dest->mRootNode->mMeshes = new unsigned int[dest->mRootNode->mNumMeshes]; + + // Export meshes + for(size_t i=0; imNumMeshes; ++i) + { + 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(); + } + } + } +} + +// SubMeshXml + +SubMeshXml::SubMeshXml() : + vertexData(0), + indexData(new IndexDataXml()) +{ +} + +SubMeshXml::~SubMeshXml() +{ + Reset(); +} + +void SubMeshXml::Reset() +{ + OGRE_SAFE_DELETE(indexData) + OGRE_SAFE_DELETE(vertexData) +} + +aiMesh *SubMeshXml::ConvertToAssimpMesh(MeshXml *parent) +{ + aiMesh *dest = new aiMesh(); + dest->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + if (!name.empty()) + dest->mName = name; + + // Material index + if (materialIndex != -1) + dest->mMaterialIndex = materialIndex; + + // Faces + dest->mNumFaces = indexData->faceCount; + dest->mFaces = new aiFace[dest->mNumFaces]; + + // Assimp required unique vertices, we need to convert from Ogres shared indexing. + size_t uniqueVertexCount = dest->mNumFaces * 3; + dest->mNumVertices = uniqueVertexCount; + dest->mVertices = new aiVector3D[dest->mNumVertices]; + + VertexDataXml *src = (!usesSharedVertexData ? vertexData : parent->sharedVertexData); + bool boneAssignments = src->HasBoneAssignments(); + bool normals = src->HasNormals(); + size_t uvs = src->NumUvs(); + + // Prepare normals + if (normals) + dest->mNormals = new aiVector3D[dest->mNumVertices]; + + // Prepare UVs + for(size_t uvi=0; uvimNumUVComponents[uvi] = 2; + dest->mTextureCoords[uvi] = new aiVector3D[dest->mNumVertices]; + } + + for (size_t fi=0; fimNumFaces; ++fi) + { + // Source Ogre face + aiFace &ogreFace = indexData->faces[fi]; + + // Destination Assimp face + aiFace &face = dest->mFaces[fi]; + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + + const size_t pos = fi * 3; + for (size_t v=0; v<3; ++v) + { + const size_t newIndex = pos + v; + + // Write face index + face.mIndices[v] = newIndex; + + // Ogres vertex index to ref into the source buffers. + const size_t ogreVertexIndex = ogreFace.mIndices[v]; + src->AddVertexMapping(ogreVertexIndex, newIndex); + + // Position + dest->mVertices[newIndex] = src->positions[ogreVertexIndex]; + + // Normal + if (normals) + dest->mNormals[newIndex] = src->normals[ogreVertexIndex]; + + // UVs + for(size_t uvi=0; uvimTextureCoords[uvi]; + std::vector &uvSrc = src->uvs[uvi]; + uvDest[newIndex] = uvSrc[ogreVertexIndex]; + } + } + } + + // Bones and bone weights + if (parent->skeleton && boneAssignments) + { + AssimpVertexBoneWeightList weights = src->AssimpBoneWeights(dest->mNumVertices); + std::set referencedBones = src->ReferencedBonesByWeights(); + + dest->mNumBones = referencedBones.size(); + dest->mBones = new aiBone*[dest->mNumBones]; + + size_t assimpBoneIndex = 0; + for(std::set::const_iterator rbIter=referencedBones.begin(), rbEnd=referencedBones.end(); rbIter != rbEnd; ++rbIter, ++assimpBoneIndex) + { + Bone *bone = parent->skeleton->BoneById((*rbIter)); + dest->mBones[assimpBoneIndex] = bone->ConvertToAssimpBone(parent->skeleton, weights[bone->id]); + } + } + + return dest; +} + +// Animation + +Animation::Animation(Skeleton *parent) : + parentSkeleton(parent), + parentMesh(0), length(0.0f), baseTime(-1.0f) { } -VertexData *Animation2::AssociatedVertexData(VertexAnimationTrack *track) const +Animation::Animation(Mesh *parent) : + parentMesh(parent), + parentSkeleton(0), + length(0.0f), + baseTime(-1.0f) { +} + +VertexData *Animation::AssociatedVertexData(VertexAnimationTrack *track) const +{ + if (!parentMesh) + return 0; + bool sharedGeom = (track->target == 0); if (sharedGeom) return parentMesh->sharedVertexData; else - return parentMesh->SubMesh(track->target-1)->vertexData; + return parentMesh->GetSubMesh(track->target-1)->vertexData; +} + +aiAnimation *Animation::ConvertToAssimpAnimation() +{ + aiAnimation *anim = new aiAnimation(); + anim->mName = name; + anim->mDuration = static_cast(length); + anim->mTicksPerSecond = 1.0; + + // Tracks + if (!tracks.empty()) + { + anim->mNumChannels = tracks.size(); + anim->mChannels = new aiNodeAnim*[anim->mNumChannels]; + + for(size_t i=0, len=tracks.size(); imChannels[i] = tracks[i].ConvertToAssimpAnimationNode(parentSkeleton); + } + } + return anim; +} + +// Skeleton + +Skeleton::Skeleton() +{ +} + +Skeleton::~Skeleton() +{ + Reset(); +} + +void Skeleton::Reset() +{ + for(size_t i=0, len=bones.size(); iIsParented()) + rootBones.push_back((*iter)); + } + return rootBones; +} + +size_t Skeleton::NumRootBones() const +{ + size_t num = 0; + for(BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) + { + if (!(*iter)->IsParented()) + num++; + } + return num; +} + +Bone *Skeleton::BoneByName(const std::string &name) const +{ + for(BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) + { + if ((*iter)->name == name) + return (*iter); + } + return 0; +} + +Bone *Skeleton::BoneById(uint16_t id) const +{ + for(BoneList::const_iterator iter = bones.begin(); iter != bones.end(); ++iter) + { + if ((*iter)->id == id) + return (*iter); + } + return 0; +} + +// Bone + +Bone::Bone() : + id(0), + parent(0), + parentId(-1), + rotationAngle(0.0f) +{ +} + +bool Bone::IsParented() const +{ + return (parentId != -1 && parent != 0); +} + +uint16_t Bone::ParentId() const +{ + return static_cast(parentId); +} + +void Bone::AddChild(Bone *bone) +{ + if (!bone) + return; + if (bone->IsParented()) + throw DeadlyImportError("Attaching child Bone that is already parented: " + bone->name); + + bone->parent = this; + bone->parentId = id; + children.push_back(bone->id); +} + +void Bone::CalculateWorldMatrixAndDefaultPose(Skeleton *skeleton) +{ + aiMatrix4x4 t0, t1; + aiMatrix4x4 transform = aiMatrix4x4::Rotation(-rotationAngle, rotation, t1) * aiMatrix4x4::Translation(-position, t0); + + if (!IsParented()) + worldMatrix = transform; + else + worldMatrix = transform * parent->worldMatrix; + + aiMatrix4x4 t2, t3; /// @todo t0 and t1 could probably be reused here? + defaultPose = aiMatrix4x4::Translation(position, t2) * aiMatrix4x4::Rotation(rotationAngle, rotation, t3); + + // Recursively for all children now that the parent matrix has been calculated. + for (size_t i=0, len=children.size(); iBoneById(children[i]); + if (!child) { + throw DeadlyImportError(Formatter::format() << "CalculateWorldMatrixAndDefaultPose: Failed to find child bone " << children[i] << " for parent " << id << " " << name); + } + child->CalculateWorldMatrixAndDefaultPose(skeleton); + } +} + +aiNode *Bone::ConvertToAssimpNode(Skeleton *skeleton, aiNode *parentNode) +{ + aiMatrix4x4 t0,t1; + + // Bone node + aiNode* node = new aiNode(name); + node->mParent = parentNode; + node->mTransformation = defaultPose; + + // Children + if (!children.empty()) + { + node->mNumChildren = children.size(); + node->mChildren = new aiNode*[node->mNumChildren]; + + for(size_t i=0, len=children.size(); iBoneById(children[i]); + if (!child) { + throw DeadlyImportError(Formatter::format() << "ConvertToAssimpNode: Failed to find child bone " << children[i] << " for parent " << id << " " << name); + } + node->mChildren[i] = child->ConvertToAssimpNode(skeleton, node); + } + } + return node; +} + +aiBone *Bone::ConvertToAssimpBone(Skeleton *parent, const std::vector &boneWeights) +{ + aiBone *bone = new aiBone(); + bone->mName = name; + bone->mOffsetMatrix = worldMatrix; + + if (!boneWeights.empty()) + { + bone->mNumWeights = boneWeights.size(); + bone->mWeights = new aiVertexWeight[boneWeights.size()]; + memcpy(bone->mWeights, &boneWeights[0], boneWeights.size() * sizeof(aiVertexWeight)); + } + + return bone; +} + +// VertexAnimationTrack + +VertexAnimationTrack::VertexAnimationTrack() : + target(0), + type(VAT_NONE) +{ +} + +aiNodeAnim *VertexAnimationTrack::ConvertToAssimpAnimationNode(Skeleton *skeleton) +{ + if (boneName.empty() || type != VAT_TRANSFORM) { + throw DeadlyImportError("VertexAnimationTrack::ConvertToAssimpAnimationNode: Cannot convert track that has no target bone name or is not type of VAT_TRANSFORM"); + } + + aiNodeAnim *nodeAnim = new aiNodeAnim(); + nodeAnim->mNodeName = boneName; + + Bone *bone = skeleton->BoneByName(boneName); + if (!bone) { + throw DeadlyImportError("VertexAnimationTrack::ConvertToAssimpAnimationNode: Failed to find bone " + boneName + " from parent Skeleton"); + } + + // Keyframes + size_t numKeyframes = transformKeyFrames.size(); + + nodeAnim->mPositionKeys = new aiVectorKey[numKeyframes]; + nodeAnim->mRotationKeys = new aiQuatKey[numKeyframes]; + nodeAnim->mScalingKeys = new aiVectorKey[numKeyframes]; + nodeAnim->mNumPositionKeys = numKeyframes; + nodeAnim->mNumRotationKeys = numKeyframes; + nodeAnim->mNumScalingKeys = numKeyframes; + + for(size_t kfi=0; kfidefaultPose * keyBonePose; + + aiVector3D kfPos; aiQuaternion kfRot; aiVector3D kfScale; + finalTransform.Decompose(kfScale, kfRot, kfPos); + + 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; + } + + return nodeAnim; } } // Ogre diff --git a/code/OgreStructs.h b/code/OgreStructs.h index 72cdd3e91..8a2e40729 100644 --- a/code/OgreStructs.h +++ b/code/OgreStructs.h @@ -57,7 +57,10 @@ namespace Ogre // Forward decl class Mesh; -class SubMesh2; +class MeshXml; +class SubMesh; +class SubMeshXml; +class Skeleton; #define OGRE_SAFE_DELETE(p) delete p; p=0; @@ -156,11 +159,53 @@ public: Type type; Semantic semantic; }; - typedef std::vector VertexElementList; +/// Ogre Vertex Bone Assignment +struct VertexBoneAssignment +{ + uint32_t vertexIndex; + uint16_t boneIndex; + float weight; +}; +typedef std::vector VertexBoneAssignmentList; +typedef std::map VertexBoneAssignmentsMap; +typedef std::map > AssimpVertexBoneWeightList; + +// Ogre Vertex Data interface, inherited by the binary and XML implementations. +class IVertexData +{ +public: + IVertexData(); + + /// Returns if bone assignments are available. + bool HasBoneAssignments() const; + + /// Add vertex mapping from old to new index. + void AddVertexMapping(uint32_t oldIndex, uint32_t newIndex); + + /// Returns re-mapped bone assignments. + /** @note Uses mappings added via AddVertexMapping. */ + AssimpVertexBoneWeightList AssimpBoneWeights(size_t vertices); + + /// Returns a set of bone indexes that are referenced by bone assignments (weights). + std::set ReferencedBonesByWeights() const; + + /// Vertex count. + uint32_t count; + + /// Bone assignments. + VertexBoneAssignmentList boneAssignments; + +private: + void BoneAssignmentsForVertex(uint32_t currentIndex, uint32_t newIndex, VertexBoneAssignmentList &dest) const; + + std::map > vertexIndexMapping; + VertexBoneAssignmentsMap boneAssignmentsMap; +}; + // Ogre Vertex Data -class VertexData +class VertexData : public IVertexData { public: VertexData(); @@ -178,9 +223,6 @@ public: /// Get vertex element for @c semantic for @c index. VertexElement *GetVertexElement(VertexElement::Semantic semantic, uint16_t index = 0); - /// Vertex count. - uint32_t count; - /// Vertex elements. VertexElementList vertexElements; @@ -243,6 +285,7 @@ public: /// Vertex offset and normals. PoseVertexMap vertices; }; +typedef std::vector PoseList; /// Ogre Pose Key Frame Ref struct PoseRef @@ -250,6 +293,7 @@ struct PoseRef uint16_t index; float influence; }; +typedef std::vector PoseRefList; /// Ogre Pose Key Frame struct PoseKeyFrame @@ -257,8 +301,9 @@ struct PoseKeyFrame /// Time position in the animation. float timePos; - std::vector references; + PoseRefList references; }; +typedef std::vector PoseKeyFrameList; /// Ogre Morph Key Frame struct MorphKeyFrame @@ -268,6 +313,18 @@ struct MorphKeyFrame MemoryStreamPtr buffer; }; +typedef std::vector MorphKeyFrameList; + +/// Ogre animation key frame +struct TransformKeyFrame +{ + float timePos; + + aiQuaternion rotation; + aiVector3D position; + aiVector3D scale; +}; +typedef std::vector TransformKeyFrameList; /// Ogre Animation Track struct VertexAnimationTrack @@ -279,59 +336,141 @@ struct VertexAnimationTrack /// Morph animation is made up of many interpolated snapshot keyframes VAT_MORPH = 1, /// Pose animation is made up of a single delta pose keyframe - VAT_POSE = 2 + VAT_POSE = 2, + /// Keyframe that has its on pos, rot and scale for a time position + VAT_TRANSFORM = 3 }; + VertexAnimationTrack(); + + /// Convert to Assimp node animation. + aiNodeAnim *ConvertToAssimpAnimationNode(Skeleton *skeleton); + + // Animation type. + Type type; + /// Vertex data target. /** 0 == shared geometry >0 == submesh index + 1 */ uint16_t target; - Type type; - std::vector poseKeyFrames; - std::vector morphKeyFrames; + /// Only valid for VAT_TRANSFORM. + std::string boneName; + + /// Only one of these will contain key frames, depending on the type enum. + PoseKeyFrameList poseKeyFrames; + MorphKeyFrameList morphKeyFrames; + TransformKeyFrameList transformKeyFrames; }; +typedef std::vector VertexAnimationTrackList; /// Ogre Animation -/** @todo Port OgreImporter::Animation to this and rename this to Animation! */ -class Animation2 +class Animation { public: - Animation2(Mesh *_parentMesh); + Animation(Skeleton *parent); + Animation(Mesh *parent); /// Returns the associated vertex data for a track in this animation. + /** @note Only valid to call when parent Mesh is set. */ VertexData *AssociatedVertexData(VertexAnimationTrack *track) const; - + + /// Convert to Assimp animation. + aiAnimation *ConvertToAssimpAnimation(); + /// Parent mesh. + /** @note Set only when animation is read from a mesh. */ Mesh *parentMesh; - + + /// Parent skeleton. + /** @note Set only when animation is read from a skeleton. */ + Skeleton *parentSkeleton; + /// Animation name. std::string name; - + /// Base animation name. std::string baseName; - + /// Length in seconds. float length; - + /// Base animation key time. float baseTime; /// Animation tracks. - std::vector tracks; + VertexAnimationTrackList tracks; }; +typedef std::vector AnimationList; -/// Ogre Vertex Bone Assignment -struct VertexBoneAssignment +/// Ogre Bone +class Bone { - uint32_t vertexIndex; - uint16_t boneIndex; - float weight; +public: + Bone(); + + /// Returns if this bone is parented. + bool IsParented() const; + + /// Parent index as uint16_t. Internally int32_t as -1 means unparented. + uint16_t ParentId() const; + + /// Add child bone. + void AddChild(Bone *bone); + + /// Calculates the world matrix for bone and its children. + void CalculateWorldMatrixAndDefaultPose(Skeleton *skeleton); + + /// Convert to Assimp node (animation nodes). + aiNode *ConvertToAssimpNode(Skeleton *parent, aiNode *parentNode = 0); + + /// Convert to Assimp bone (mesh bones). + aiBone *ConvertToAssimpBone(Skeleton *parent, const std::vector &boneWeights); + + uint16_t id; + std::string name; + + Bone *parent; + int32_t parentId; + std::vector children; + + aiVector3D position; + aiVector3D rotation; + float rotationAngle; + + aiMatrix4x4 worldMatrix; + aiMatrix4x4 defaultPose; +}; +typedef std::vector BoneList; + +/// Ogre Skeleton +class Skeleton +{ +public: + Skeleton(); + ~Skeleton(); + + /// Releases all memory that this data structure owns. + void Reset(); + + /// Returns unparented root bones. + BoneList RootBones() const; + + /// Returns number of unparented root bones. + size_t NumRootBones() const; + + /// Get bone by name. + Bone *BoneByName(const std::string &name) const; + + /// Get bone by id. + Bone *BoneById(uint16_t id) const; + + BoneList bones; + AnimationList animations; }; -/// Ogre SubMesh -/** @todo Port OgreImporter::SubMesh to this and rename this to SubMesh! */ -class SubMesh2 +/// Ogre Sub Mesh interface, inherited by the binary and XML implementations. +class ISubMesh { public: /// @note Full list of Ogre types, not all of them are supported and exposed to Assimp. @@ -351,17 +490,7 @@ public: OT_TRIANGLE_FAN = 6 }; - SubMesh2(); - ~SubMesh2(); - - /// Releases all memory that this data structure owns. - /** @note Vertex and index data contains shared ptrs - that are freed automatically. In practice the ref count - should be 0 after this reset. */ - void Reset(); - - /// Covert to Assimp mesh. - aiMesh *ConvertToAssimpMesh(Mesh *parent); + ISubMesh(); /// SubMesh index. unsigned int index; @@ -371,7 +500,7 @@ public: /// Material used by this submesh. std::string materialRef; - + /// Texture alias information. std::string textureAliasName; std::string textureAliasRef; @@ -379,22 +508,37 @@ public: /// Assimp scene material index used by this submesh. /** -1 if no material or material could not be imported. */ int materialIndex; + + /// If submesh uses shared geometry from parent mesh. + bool usesSharedVertexData; + + /// Operation type. + OperationType operationType; +}; + +/// Ogre SubMesh +class SubMesh : public ISubMesh +{ +public: + SubMesh(); + ~SubMesh(); + + /// Releases all memory that this data structure owns. + /** @note Vertex and index data contains shared ptrs + that are freed automatically. In practice the ref count + should be 0 after this reset. */ + void Reset(); + + /// Covert to Assimp mesh. + aiMesh *ConvertToAssimpMesh(Mesh *parent); /// Vertex data. VertexData *vertexData; /// Index data. IndexData *indexData; - - /// If submesh uses shared geometry from parent mesh. - bool usesSharedVertexData; - - /// Operation type. - OperationType operationType; - - /// Bone assignments. - std::vector boneAssignments; }; +typedef std::vector SubMeshList; /// Ogre Mesh class Mesh @@ -410,7 +554,7 @@ public: size_t NumSubMeshes() const; /// Returns submesh for @c index. - SubMesh2 *SubMesh(uint16_t index) const; + SubMesh *GetSubMesh(uint16_t index) const; /// Convert mesh to Assimp scene. void ConvertToAssimpScene(aiScene* dest); @@ -421,20 +565,98 @@ public: /// Skeleton reference. std::string skeletonRef; + /// Skeleton. + Skeleton *skeleton; + /// Vertex data VertexData *sharedVertexData; /// Sub meshes. - std::vector subMeshes; + SubMeshList subMeshes; /// Animations - std::vector animations; - - /// Bone assignments. - std::vector boneAssignments; + AnimationList animations; /// Poses - std::vector poses; + PoseList poses; +}; + +/// Ogre XML Vertex Data +class VertexDataXml : public IVertexData +{ +public: + VertexDataXml(); + + bool HasNormals() const; + bool HasTangents() const; + bool HasUvs() const; + size_t NumUvs() const; + + std::vector positions; + std::vector normals; + std::vector tangents; + std::vector > uvs; +}; + +/// Ogre XML Index Data +class IndexDataXml +{ +public: + IndexDataXml() : faceCount(0) {} + + /// Face count. + uint32_t faceCount; + + std::vector faces; +}; + +/// Ogre XML SubMesh +class SubMeshXml : public ISubMesh +{ +public: + SubMeshXml(); + ~SubMeshXml(); + + /// Releases all memory that this data structure owns. + void Reset(); + + aiMesh *ConvertToAssimpMesh(MeshXml *parent); + + IndexDataXml *indexData; + VertexDataXml *vertexData; +}; +typedef std::vector SubMeshXmlList; + +/// Ogre XML Mesh +class MeshXml +{ +public: + MeshXml(); + ~MeshXml(); + + /// Releases all memory that this data structure owns. + void Reset(); + + /// Returns number of subMeshes. + size_t NumSubMeshes() const; + + /// Returns submesh for @c index. + SubMeshXml *GetSubMesh(uint16_t index) const; + + /// Convert mesh to Assimp scene. + void ConvertToAssimpScene(aiScene* dest); + + /// Skeleton reference. + std::string skeletonRef; + + /// Skeleton. + Skeleton *skeleton; + + /// Vertex data + VertexDataXml *sharedVertexData; + + /// Sub meshes. + SubMeshXmlList subMeshes; }; } // Ogre diff --git a/code/OgreXmlSerializer.cpp b/code/OgreXmlSerializer.cpp index b2f44290a..76da89c8f 100644 --- a/code/OgreXmlSerializer.cpp +++ b/code/OgreXmlSerializer.cpp @@ -38,5 +38,880 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @todo Move XML related serialization from OgreImporter.cpp - here in a similar fashion as OgreBinarySerializer. */ +#include "OgreXmlSerializer.h" + +#include "irrXMLWrapper.h" +#include "TinyFormatter.h" + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +// Define as 1 to get verbose logging. +#define OGRE_XML_SERIALIZER_DEBUG 0 + +namespace Assimp +{ +namespace Ogre +{ + +void ThrowAttibuteError(const XmlReader* reader, const std::string &name, const std::string &error = "") +{ + if (!error.empty()) + { + throw DeadlyImportError(error + " in node '" + std::string(reader->getNodeName()) + "' and attribute '" + name + "'"); + } + else + { + throw DeadlyImportError("Attribute '" + name + "' does not exist in node '" + std::string(reader->getNodeName()) + "'"); + } +} + +template<> +int32_t OgreXmlSerializer::ReadAttribute(const std::string &name) const +{ + if (HasAttribute(name.c_str())) + { + return static_cast(m_reader->getAttributeValueAsInt(name.c_str())); + } + else + { + ThrowAttibuteError(m_reader, name); + return 0; + } +} + +template<> +uint32_t OgreXmlSerializer::ReadAttribute(const std::string &name) const +{ + if (HasAttribute(name.c_str())) + { + /** @note This is hackish. But we are never expecting unsigned values that go outside the + int32_t range. Just monitor for negative numbers and kill the import. */ + int32_t temp = ReadAttribute(name); + if (temp >= 0) + { + return static_cast(temp); + } + else + { + ThrowAttibuteError(m_reader, name, "Found a negative number value where expecting a uint32_t value"); + } + } + else + { + ThrowAttibuteError(m_reader, name); + } + return 0; +} + +template<> +uint16_t OgreXmlSerializer::ReadAttribute(const std::string &name) const +{ + if (HasAttribute(name.c_str())) + { + return static_cast(ReadAttribute(name)); + } + else + { + ThrowAttibuteError(m_reader, name); + } + return 0; +} + +template<> +float OgreXmlSerializer::ReadAttribute(const std::string &name) const +{ + if (HasAttribute(name.c_str())) + { + return m_reader->getAttributeValueAsFloat(name.c_str()); + } + else + { + ThrowAttibuteError(m_reader, name); + return 0; + } +} + +template<> +std::string OgreXmlSerializer::ReadAttribute(const std::string &name) const +{ + const char* value = m_reader->getAttributeValue(name.c_str()); + if (value) + { + return std::string(value); + } + else + { + ThrowAttibuteError(m_reader, name); + return ""; + } +} + +template<> +bool OgreXmlSerializer::ReadAttribute(const std::string &name) const +{ + std::string value = Ogre::ToLower(ReadAttribute(name)); + if (ASSIMP_stricmp(value, "true") == 0) + { + return true; + } + else if (ASSIMP_stricmp(value, "false") == 0) + { + return false; + } + else + { + ThrowAttibuteError(m_reader, name, "Boolean value is expected to be 'true' or 'false', encountered '" + value + "'"); + return false; + } +} + +bool OgreXmlSerializer::HasAttribute(const std::string &name) const +{ + return (m_reader->getAttributeValue(name.c_str()) != 0); +} + +std::string &OgreXmlSerializer::NextNode() +{ + do + { + if (!m_reader->read()) + { + m_currentNodeName = ""; + return m_currentNodeName; + } + } + while(m_reader->getNodeType() != irr::io::EXN_ELEMENT); + + CurrentNodeName(true); +#if (OGRE_XML_SERIALIZER_DEBUG == 1) + DefaultLogger::get()->debug("<" + m_currentNodeName + ">"); +#endif + return m_currentNodeName; +} + +bool OgreXmlSerializer::CurrentNodeNameEquals(const std::string &name) const +{ + return (ASSIMP_stricmp(m_currentNodeName, name) == 0); +} + +std::string OgreXmlSerializer::CurrentNodeName(bool forceRead) +{ + if (forceRead) + m_currentNodeName = std::string(m_reader->getNodeName()); + return m_currentNodeName; +} + +std::string &OgreXmlSerializer::SkipCurrentNode() +{ +#if (OGRE_XML_SERIALIZER_DEBUG == 1) + DefaultLogger::get()->debug("Skipping node <" + m_currentNodeName + ">"); +#endif + + for(;;) + { + if (!m_reader->read()) + { + m_currentNodeName = ""; + return m_currentNodeName; + } + if (m_reader->getNodeType() != irr::io::EXN_ELEMENT_END) + continue; + else if (std::string(m_reader->getNodeName()) == m_currentNodeName) + break; + } + return NextNode(); +} + +// +const std::string nnMesh = "mesh"; +const std::string nnSharedGeometry = "sharedgeometry"; +const std::string nnSubMeshes = "submeshes"; +const std::string nnSubMesh = "submesh"; +const std::string nnSubMeshNames = "submeshnames"; +const std::string nnSkeletonLink = "skeletonlink"; +const std::string nnLOD = "levelofdetail"; +const std::string nnExtremes = "extremes"; +const std::string nnPoses = "poses"; +const std::string nnAnimations = "animations"; + +// +const std::string nnFaces = "faces"; +const std::string nnFace = "face"; +const std::string nnGeometry = "geometry"; +const std::string nnTextures = "textures"; + +// +const std::string nnBoneAssignments = "boneassignments"; + +// +const std::string nnVertexBuffer = "vertexbuffer"; + +// +const std::string nnVertex = "vertex"; +const std::string nnPosition = "position"; +const std::string nnNormal = "normal"; +const std::string nnTangent = "tangent"; +const std::string nnBinormal = "binormal"; +const std::string nnTexCoord = "texcoord"; +const std::string nnColorDiffuse = "colour_diffuse"; +const std::string nnColorSpecular = "colour_specular"; + +// +const std::string nnVertexBoneAssignment = "vertexboneassignment"; + +MeshXml *OgreXmlSerializer::ImportMesh(XmlReader *reader) +{ + OgreXmlSerializer serializer(reader); + + MeshXml *mesh = new MeshXml(); + serializer.ReadMesh(mesh); + 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) { + throw DeadlyImportError("Root node is <" + m_currentNodeName + "> expecting "); + } + + DefaultLogger::get()->debug("Reading Mesh"); + + NextNode(); + + // Root level nodes + while(m_currentNodeName == nnSharedGeometry || + m_currentNodeName == nnSubMeshes || + m_currentNodeName == nnSkeletonLink || + m_currentNodeName == nnBoneAssignments || + m_currentNodeName == nnLOD || + m_currentNodeName == nnSubMeshNames || + m_currentNodeName == nnExtremes || + m_currentNodeName == nnPoses || + m_currentNodeName == nnAnimations) + { + if (m_currentNodeName == nnSharedGeometry) + { + mesh->sharedVertexData = new VertexDataXml(); + ReadGeometry(mesh->sharedVertexData); + } + else if (m_currentNodeName == nnSubMeshes) + { + NextNode(); + while(m_currentNodeName == nnSubMesh) { + ReadSubMesh(mesh); + } + } + else if (m_currentNodeName == nnBoneAssignments) + { + ReadBoneAssignments(mesh->sharedVertexData); + } + else if (m_currentNodeName == nnSkeletonLink) + { + mesh->skeletonRef = ReadAttribute("name"); + DefaultLogger::get()->debug("Read skeleton link " + mesh->skeletonRef); + NextNode(); + } + // Assimp incompatible/ignored nodes + else + SkipCurrentNode(); + } +} + +void OgreXmlSerializer::ReadGeometry(VertexDataXml *dest) +{ + dest->count = ReadAttribute("vertexcount"); + DefaultLogger::get()->debug(Formatter::format() << " - Reading geometry of " << dest->count << " vertices"); + + NextNode(); + while(m_currentNodeName == nnVertexBuffer) { + ReadGeometryVertexBuffer(dest); + } +} + +void OgreXmlSerializer::ReadGeometryVertexBuffer(VertexDataXml *dest) +{ + bool positions = (HasAttribute("positions") && ReadAttribute("positions")); + bool normals = (HasAttribute("normals") && ReadAttribute("normals")); + bool tangents = (HasAttribute("tangents") && ReadAttribute("tangents")); + uint32_t uvs = (HasAttribute("texture_coords") ? ReadAttribute("texture_coords") : 0); + + if (!positions) { + throw DeadlyImportError("Vertex buffer does not contain positions!"); + } + DefaultLogger::get()->debug(" - Contains positions"); + dest->positions.reserve(dest->count); + + if (normals) + { + DefaultLogger::get()->debug(" - Contains normals"); + dest->normals.reserve(dest->count); + } + if (tangents) + { + DefaultLogger::get()->debug(" - Contains tangents"); + dest->tangents.reserve(dest->count); + } + if (uvs > 0) + { + DefaultLogger::get()->debug(Formatter::format() << " - Contains " << uvs << " texture coords"); + dest->uvs.resize(uvs); + for(size_t i=0, len=dest->uvs.size(); iuvs[i].reserve(dest->count); + } + } + + bool warnBinormal = true; + bool warnColorDiffuse = true; + bool warnColorSpecular = true; + + NextNode(); + + while(m_currentNodeName == nnVertex || + m_currentNodeName == nnPosition || + m_currentNodeName == nnNormal || + m_currentNodeName == nnTangent || + m_currentNodeName == nnBinormal || + m_currentNodeName == nnTexCoord || + m_currentNodeName == nnColorDiffuse || + m_currentNodeName == nnColorSpecular) + { + if (m_currentNodeName == nnVertex) { + NextNode(); + } + + /// @todo Implement nnBinormal, nnColorDiffuse and nnColorSpecular + + if (positions && m_currentNodeName == nnPosition) + { + aiVector3D pos; + pos.x = ReadAttribute(anX); + pos.y = ReadAttribute(anY); + pos.z = ReadAttribute(anZ); + dest->positions.push_back(pos); + } + else if (normals && m_currentNodeName == nnNormal) + { + aiVector3D normal; + normal.x = ReadAttribute(anX); + normal.y = ReadAttribute(anY); + normal.z = ReadAttribute(anZ); + dest->normals.push_back(normal); + } + else if (tangents && m_currentNodeName == nnTangent) + { + aiVector3D tangent; + tangent.x = ReadAttribute(anX); + tangent.y = ReadAttribute(anY); + tangent.z = ReadAttribute(anZ); + dest->tangents.push_back(tangent); + } + else if (uvs > 0 && m_currentNodeName == nnTexCoord) + { + for(size_t i=0, len=dest->uvs.size(); i("u"); + uv.y = ReadAttribute("v") * (-1)+1; //flip the uv vertikal, blender exports them so! (ahem... @todo ????) + dest->uvs[i].push_back(uv); + + NextNode(); + } + // Continue main loop as above already read next node + continue; + } + else + { + /// @todo Remove this stuff once implemented. We only want to log warnings once per element. + bool warn = true; + if (m_currentNodeName == nnBinormal) + { + if (warnBinormal) + { + warnBinormal = false; + } + else + { + warn = false; + } + } + else if (m_currentNodeName == nnColorDiffuse) + { + if (warnColorDiffuse) + { + warnColorDiffuse = false; + } + else + { + warn = false; + } + } + else if (m_currentNodeName == nnColorSpecular) + { + if (warnColorSpecular) + { + warnColorSpecular = false; + } + else + { + warn = false; + } + } + if (warn) { + DefaultLogger::get()->warn("Vertex buffer attribute read not implemented for element: " + m_currentNodeName); + } + } + + // Advance + NextNode(); + } + + // Sanity checks + if (dest->positions.size() != dest->count) { + throw DeadlyImportError(Formatter::format() << "Read only " << dest->positions.size() << " positions when should have read " << dest->count); + } + if (normals && dest->normals.size() != dest->count) { + throw DeadlyImportError(Formatter::format() << "Read only " << dest->normals.size() << " normals when should have read " << dest->count); + } + if (tangents && dest->tangents.size() != dest->count) { + throw DeadlyImportError(Formatter::format() << "Read only " << dest->tangents.size() << " tangents when should have read " << dest->count); + } + for(unsigned int i=0; iuvs.size(); ++i) + { + if (dest->uvs[i].size() != dest->count) { + throw DeadlyImportError(Formatter::format() << "Read only " << dest->uvs[i].size() + << " uvs for uv index " << i << " when should have read " << dest->count); + } + } +} + +void OgreXmlSerializer::ReadSubMesh(MeshXml *mesh) +{ + static const std::string anMaterial = "material"; + static const std::string anUseSharedVertices = "usesharedvertices"; + static const std::string anCount = "count"; + static const std::string anV1 = "v1"; + static const std::string anV2 = "v2"; + static const std::string anV3 = "v3"; + static const std::string anV4 = "v4"; + + SubMeshXml* submesh = new SubMeshXml(); + + if (HasAttribute(anMaterial)) { + submesh->materialRef = ReadAttribute(anMaterial); + } + if (HasAttribute(anUseSharedVertices)) { + submesh->usesSharedVertexData = ReadAttribute(anUseSharedVertices); + } + + DefaultLogger::get()->debug(Formatter::format() << "Reading SubMesh " << mesh->subMeshes.size()); + DefaultLogger::get()->debug(Formatter::format() << " - Material: '" << submesh->materialRef << "'"); + DefaultLogger::get()->debug(Formatter::format() << " - Uses shared geometry: " << (submesh->usesSharedVertexData ? "true" : "false")); + + // 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 + // of faces and geometry changed, and not if we have more than one of one + /// @todo Fix above comment with better read logic below + + bool quadWarned = false; + + NextNode(); + while(m_currentNodeName == nnFaces || + m_currentNodeName == nnGeometry || + m_currentNodeName == nnTextures || + m_currentNodeName == nnBoneAssignments) + { + if (m_currentNodeName == nnFaces) + { + submesh->indexData->faceCount = ReadAttribute(anCount); + submesh->indexData->faces.reserve(submesh->indexData->faceCount); + + NextNode(); + while(m_currentNodeName == nnFace) + { + aiFace face; + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + face.mIndices[0] = ReadAttribute(anV1); + face.mIndices[1] = ReadAttribute(anV2); + face.mIndices[2] = ReadAttribute(anV3); + + /// @todo Support quads if Ogre even supports them in XML (I'm not sure but I doubt it) + if (!quadWarned && HasAttribute(anV4)) { + DefaultLogger::get()->warn("Submesh has quads with , only triangles are supported at the moment!"); + quadWarned = true; + } + + submesh->indexData->faces.push_back(face); + + // Advance + NextNode(); + } + + if (submesh->indexData->faces.size() == submesh->indexData->faceCount) + { + DefaultLogger::get()->debug(Formatter::format() << " - Faces " << submesh->indexData->faceCount); + } + else + { + throw DeadlyImportError(Formatter::format() << "Read only " << submesh->indexData->faces.size() << " faces when should have read " << submesh->indexData->faceCount); + } + } + else if (m_currentNodeName == nnGeometry) + { + if (submesh->usesSharedVertexData) { + throw DeadlyImportError("Found in when use shared geometry is true. Invalid mesh file."); + } + + submesh->vertexData = new VertexDataXml(); + ReadGeometry(submesh->vertexData); + } + else if (m_currentNodeName == nnBoneAssignments) + { + ReadBoneAssignments(submesh->vertexData); + } + // Assimp incompatible/ignored nodes + else + SkipCurrentNode(); + } + + submesh->index = mesh->subMeshes.size(); + mesh->subMeshes.push_back(submesh); +} + +void OgreXmlSerializer::ReadBoneAssignments(VertexDataXml *dest) +{ + if (!dest) { + throw DeadlyImportError("Cannot read bone assignments, vertex data is null."); + } + + static const std::string anVertexIndex = "vertexindex"; + static const std::string anBoneIndex = "boneindex"; + static const std::string anWeight = "weight"; + + std::set influencedVertices; + + NextNode(); + while(m_currentNodeName == nnVertexBoneAssignment) + { + VertexBoneAssignment ba; + ba.vertexIndex = ReadAttribute(anVertexIndex); + ba.boneIndex = ReadAttribute(anBoneIndex); + ba.weight = ReadAttribute(anWeight); + dest->boneAssignments.push_back(ba); + + influencedVertices.insert(ba.vertexIndex); + NextNode(); + } + + /** Normalize bone weights. + Some exporters wont care if the sum of all bone weights + for a single vertex equals 1 or not, so validate here. */ + const float epsilon = 0.05f; + for(std::set::const_iterator iter=influencedVertices.begin(), end=influencedVertices.end(); iter != end; ++iter) + { + const uint32_t vertexIndex = (*iter); + + float sum = 0.0f; + for (VertexBoneAssignmentList::const_iterator baIter=dest->boneAssignments.begin(), baEnd=dest->boneAssignments.end(); baIter != baEnd; ++baIter) + { + if (baIter->vertexIndex == vertexIndex) + sum += baIter->weight; + } + if ((sum < (1.0f - epsilon)) || (sum > (1.0f + epsilon))) + { + for (VertexBoneAssignmentList::iterator baIter=dest->boneAssignments.begin(), baEnd=dest->boneAssignments.end(); baIter != baEnd; ++baIter) + { + if (baIter->vertexIndex == vertexIndex) + baIter->weight /= sum; + } + } + } + + DefaultLogger::get()->debug(Formatter::format() << " - " << dest->boneAssignments.size() << " bone assignments"); +} + +} // Ogre +} // Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/code/OgreXmlSerializer.h b/code/OgreXmlSerializer.h index b2f44290a..35c03d388 100644 --- a/code/OgreXmlSerializer.h +++ b/code/OgreXmlSerializer.h @@ -38,5 +38,70 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @todo Move XML related serialization from OgreImporter.cpp - here in a similar fashion as OgreBinarySerializer. */ +#ifndef AI_OGREXMLSERIALIZER_H_INC +#define AI_OGREXMLSERIALIZER_H_INC + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + +#include "OgreStructs.h" +#include "OgreParsingUtils.h" + +#include "irrXMLWrapper.h" + +namespace Assimp +{ +namespace Ogre +{ + +typedef irr::io::IrrXMLReader XmlReader; + +class OgreXmlSerializer +{ +public: + static MeshXml *ImportMesh(XmlReader *reader); + static void ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *mesh); + +private: + OgreXmlSerializer(XmlReader *reader) : + m_reader(reader) + { + } + + // Mesh + void ReadMesh(MeshXml *mesh); + void ReadSubMesh(MeshXml *mesh); + + void ReadGeometry(VertexDataXml *dest); + void ReadGeometryVertexBuffer(VertexDataXml *dest); + + void ReadBoneAssignments(VertexDataXml *dest); + + // Skeleton + void ReadSkeleton(Skeleton *skeleton); + + void ReadBones(Skeleton *skeleton); + void ReadBoneHierarchy(Skeleton *skeleton); + + void ReadAnimations(Skeleton *skeleton); + void ReadAnimationTracks(Animation *dest); + void ReadAnimationKeyFrames(Animation *anim, VertexAnimationTrack *dest); + + template + T ReadAttribute(const std::string &name) const; + bool HasAttribute(const std::string &name) const; + + std::string &NextNode(); + std::string &SkipCurrentNode(); + + bool CurrentNodeNameEquals(const std::string &name) const; + std::string CurrentNodeName(bool forceRead = false); + + XmlReader *m_reader; + std::string m_currentNodeName; +}; + +} // Ogre +} // Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // AI_OGREXMLSERIALIZER_H_INC