From 09517b342b620fd66f1078304e109ce531187c0f Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Wed, 30 Apr 2014 05:43:13 +0300 Subject: [PATCH 01/12] OgreImporter: Started cleanup and refactoring. Aim is to get this into a shape that its easy to read and understand before I start making any new features. --- code/OgreImporter.cpp | 218 ++++++++--------- code/OgreImporter.hpp | 235 ++++++++++-------- code/OgreMesh.cpp | 523 ++++++++++++++++++++++------------------- code/OgreSkeleton.cpp | 36 +-- code/OgreXmlHelper.hpp | 102 ++++---- 5 files changed, 596 insertions(+), 518 deletions(-) diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index 21b974969..1a51489d9 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -46,7 +46,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -using namespace std; #include "OgreImporter.hpp" #include "TinyFormatter.h" @@ -65,160 +64,153 @@ static const aiImporterDesc desc = { "mesh.xml" }; +using namespace std; + namespace Assimp { namespace Ogre { - bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool checkSig) const { - if(!checkSig)//Check File Extension + if (!checkSig) { - std::string extension("mesh.xml"); - int l=extension.length(); - return pFile.substr(pFile.length()-l, l)==extension; - } - else//Check file Header - { - const char* tokens[] = {""}; - return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); + string ext = "mesh.xml"; + int len = ext.length(); + return (ASSIMP_stricmp(pFile.substr(pFile.length()-len, len), ext) == 0); } + const char* tokens[] = {""}; + return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); } - void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Assimp::IOSystem *pIOHandler) { - m_CurrentFilename=pFile; - m_CurrentIOHandler=pIOHandler; - m_CurrentScene=pScene; + m_CurrentFilename = pFile; + m_CurrentIOHandler = pIOHandler; + m_CurrentScene = pScene; - //Open the File: - boost::scoped_ptr file(pIOHandler->Open(pFile)); - if( file.get() == NULL) - throw DeadlyImportError("Failed to open file "+pFile+"."); - - //Read the Mesh File: - boost::scoped_ptr mIOWrapper( new CIrrXML_IOStreamReader( file.get())); - boost::scoped_ptr MeshFile(irr::io::createIrrXMLReader(mIOWrapper.get())); - if(!MeshFile)//parse the xml file - throw DeadlyImportError("Failed to create XML Reader for "+pFile); - - - DefaultLogger::get()->debug("Mesh File opened"); + // -------------------- Initial file and XML operations -------------------- - //Read root Node: - if(!(XmlRead(MeshFile.get()) && string(MeshFile->getNodeName())=="mesh")) + // Open + boost::scoped_ptr file(pIOHandler->Open(pFile)); + if (file.get() == NULL) + throw DeadlyImportError("Failed to open file " + pFile); + + // Read + 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, "mesh")) + throw DeadlyImportError("Root node is not but <" + string(reader->getNodeName()) + ">"); + + // Node names + string nnSharedGeometry = "sharedgeometry"; + string nnVertexBuffer = "vertexbuffer"; + string nnSubMeshes = "submeshes"; + string nnSubMesh = "submesh"; + string nnSubMeshNames = "submeshnames"; + string nnSkeletonLink = "skeletonlink"; + + // -------------------- Shared Geometry -------------------- + // This can be used to share geometry between submeshes + + NextNode(reader.get()); + if (CurrentNodeNameEquals(reader, nnSharedGeometry)) { - throw DeadlyImportError("Root Node is not ! "+pFile+" "+MeshFile->getNodeName()); + DefaultLogger::get()->debug("Reading shader geometry"); + unsigned int NumVertices = GetAttribute(reader.get(), "vertexcount"); + + NextNode(reader.get()); + while(CurrentNodeNameEquals(reader, nnVertexBuffer)) + ReadVertexBuffer(m_SharedGeometry, reader.get(), NumVertices); } - //eventually load shared geometry - XmlRead(MeshFile.get());//shared geometry is optional, so we need a reed for the next two if's - if(MeshFile->getNodeName()==string("sharedgeometry")) + // -------------------- Sub Meshes -------------------- + + if (!CurrentNodeNameEquals(reader, nnSubMeshes)) + throw DeadlyImportError("Could not find node inside root node"); + + list > subMeshes; + vector materials; + + NextNode(reader.get()); + while(CurrentNodeNameEquals(reader, nnSubMesh)) { - unsigned int NumVertices=GetAttribute(MeshFile.get(), "vertexcount");; + SubMesh* submesh = new SubMesh(); + ReadSubMesh(subMeshes.size(), *submesh, reader.get()); - XmlRead(MeshFile.get()); - while(MeshFile->getNodeName()==string("vertexbuffer")) - { - ReadVertexBuffer(m_SharedGeometry, MeshFile.get(), NumVertices); - } - } + // Just a index in a array, we add a mesh in each loop cycle, so we get indicies like 0, 1, 2 ... n; + // so it is important to do this before pushing the mesh in the vector! + submesh->MaterialIndex = subMeshes.size(); - //Go to the submeshs: - if(MeshFile->getNodeName()!=string("submeshes")) - { - throw DeadlyImportError("No node in node! "+pFile); - } + subMeshes.push_back(boost::shared_ptr(submesh)); - - //-------------------Read the submeshs and materials:----------------------- - std::list > SubMeshes; - vector Materials; - XmlRead(MeshFile.get()); - while(MeshFile->getNodeName()==string("submesh")) - { - SubMesh* theSubMesh=new SubMesh(); - theSubMesh->MaterialName=GetAttribute(MeshFile.get(), "material"); - DefaultLogger::get()->debug("Loading Submehs with Material: "+theSubMesh->MaterialName); - ReadSubMesh(*theSubMesh, MeshFile.get()); - - //just a index in a array, we add a mesh in each loop cycle, so we get indicies like 0, 1, 2 ... n; - //so it is important to do this before pushing the mesh in the vector! - theSubMesh->MaterialIndex=SubMeshes.size(); - - SubMeshes.push_back(boost::shared_ptr(theSubMesh)); - - //Load the Material: - aiMaterial* MeshMat=LoadMaterial(theSubMesh->MaterialName); + // Load the Material: + aiMaterial* MeshMat = LoadMaterial(submesh->MaterialName); - //Set the Material: - Materials.push_back(MeshMat); + // Set the Material: + materials.push_back(MeshMat); } - if(SubMeshes.empty()) - throw DeadlyImportError("no submesh loaded!"); - if(SubMeshes.size()!=Materials.size()) - throw DeadlyImportError("materialcount doesn't match mesh count!"); + 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 submeshnames (stupid irrxml) - if(MeshFile->getNodeName()==string("submeshnames")) + // Skip submesh names. + /// @todo Should these be read to scene etc. metadata? + if (CurrentNodeNameEquals(reader, nnSubMeshNames)) { - XmlRead(MeshFile.get()); - while(MeshFile->getNodeName()==string("submesh")) - XmlRead(MeshFile.get()); + NextNode(reader.get()); + while(CurrentNodeNameEquals(reader, nnSubMesh)) + NextNode(reader.get()); } + // -------------------- Skeleton -------------------- - //----------------Load the skeleton: ------------------------------- vector Bones; vector Animations; - if(MeshFile->getNodeName()==string("skeletonlink")) + if (CurrentNodeNameEquals(reader, nnSkeletonLink)) { - string SkeletonFile=GetAttribute(MeshFile.get(), "name"); - LoadSkeleton(SkeletonFile, Bones, Animations); - XmlRead(MeshFile.get()); + string SkeletonFile = GetAttribute(reader.get(), "name"); + if (!SkeletonFile.empty()) + LoadSkeleton(SkeletonFile, Bones, Animations); + else + DefaultLogger::get()->debug("Found a unusual <" + nnSkeletonLink + "> with a empty reference"); + NextNode(reader.get()); } else - { - DefaultLogger::get()->debug("No skeleton file will be loaded"); - DefaultLogger::get()->debug(MeshFile->getNodeName()); - } - //__________________________________________________________________ + DefaultLogger::get()->debug("Mesh has no assigned skeleton with <" + nnSkeletonLink + ">"); + // Now there might be for the shared geometry + if (CurrentNodeNameEquals(reader, "boneassignments")) + ReadBoneWeights(m_SharedGeometry, reader.get()); - //now there might be boneassignments for the shared geometry: - if(MeshFile->getNodeName()==string("boneassignments")) - { - ReadBoneWeights(m_SharedGeometry, MeshFile.get()); - } - - - //----------------- Process Meshs ----------------------- - BOOST_FOREACH(boost::shared_ptr theSubMesh, SubMeshes) + // -------------------- Process Results -------------------- + BOOST_FOREACH(boost::shared_ptr theSubMesh, subMeshes) { ProcessSubMesh(*theSubMesh, m_SharedGeometry); } - //_______________________________________________________ + // -------------------- Apply to aiScene -------------------- - - - //----------------- Now fill the Assimp scene --------------------------- - //put the aiMaterials in the scene: - m_CurrentScene->mMaterials=new aiMaterial*[Materials.size()]; - m_CurrentScene->mNumMaterials=Materials.size(); - for(unsigned int i=0; imMaterials[i]=Materials[i]; + m_CurrentScene->mMaterials=new aiMaterial*[materials.size()]; + m_CurrentScene->mNumMaterials=materials.size(); + for(unsigned int i=0; imMaterials[i]=materials[i]; //create the aiMehs... vector aiMeshes; - BOOST_FOREACH(boost::shared_ptr theSubMesh, SubMeshes) + BOOST_FOREACH(boost::shared_ptr theSubMesh, subMeshes) { aiMeshes.push_back(CreateAssimpSubMesh(*theSubMesh, Bones)); } @@ -231,16 +223,14 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass m_CurrentScene->mRootNode=new aiNode("root"); //link the meshs with the root node: - m_CurrentScene->mRootNode->mMeshes=new unsigned int[SubMeshes.size()]; - m_CurrentScene->mRootNode->mNumMeshes=SubMeshes.size(); - for(unsigned int i=0; imRootNode->mMeshes[i]=i; - + m_CurrentScene->mRootNode->mMeshes=new unsigned int[subMeshes.size()]; + m_CurrentScene->mRootNode->mNumMeshes=subMeshes.size(); + for(unsigned int i=0; imRootNode->mMeshes[i]=i; CreateAssimpSkeleton(Bones, Animations); PutAnimationsInScene(Bones, Animations); - //___________________________________________________________ } diff --git a/code/OgreImporter.hpp b/code/OgreImporter.hpp index 20f3bc576..cf1d0bda2 100644 --- a/code/OgreImporter.hpp +++ b/code/OgreImporter.hpp @@ -1,104 +1,126 @@ + #include "BaseImporter.h" - -#include - #include "OgreXmlHelper.hpp" #include "irrXMLWrapper.h" -/// Ogre Importer TODO -/* - Read Vertex Colors - - Read multiple TexCoords +#include + +/** @todo Read Vertex Colors + @todo Read multiple TexCoords */ - - namespace Assimp { namespace Ogre { - -//Forward declarations: struct Face; -struct Weight; +struct BoneWeight; struct Bone; struct Animation; -struct Track; -struct Keyframe; -///A submesh from Ogre +/// Ogre SubMesh struct SubMesh -{ - bool SharedData; +{ + bool UseSharedGeometry; + bool Use32bitIndexes; std::string Name; std::string MaterialName; - std::vector FaceList; - std::vector Positions; bool HasPositions; - std::vector Normals; bool HasNormals; - std::vector Tangents; bool HasTangents; - std::vector > Uvs;//arbitrary number of texcoords, they are nearly always 2d, but assimp has always 3d texcoords, n vectors(outer) with texcoords for each vertex(inner) + bool HasGeometry; + bool HasPositions; + bool HasNormals; + bool HasTangents; - std::vector< std::vector > Weights;//a list(inner) of bones for each vertex(outer) - int MaterialIndex;///< The Index in the Assimp Materialarray from the material witch is attached to this submesh - unsigned int BonesUsed;//the highest index of a bone from a bone weight, this is needed to create the assimp bone structur (converting from Vertex-Bones to Bone-Vertices) + std::vector Faces; + std::vector Positions; + std::vector Normals; + std::vector Tangents; - SubMesh(): SharedData(false), HasPositions(false), HasNormals(false), HasTangents(false), - MaterialIndex(-1), BonesUsed(0) {}//initialize everything + /// 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) + { + } }; - -///The Main Ogre Importer Class +// ------------------------------------------------------------------------------------------------ +/// \class OgreImporter +/// \brief Importer for Ogre mesh, skeleton and material formats. +// ------------------------------------------------------------------------------------------------ class OgreImporter : public BaseImporter { public: - virtual bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; - virtual void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); - virtual const aiImporterDesc* GetInfo () const; - virtual void SetupProperties(const Importer* pImp); -private: - - - //-------------------------------- OgreMesh.cpp ------------------------------- - /// Helper Functions to read parts of the XML File - void ReadSubMesh(SubMesh& theSubMesh, XmlReader* Reader);//the submesh reference is the result value - - /// Reads a single Vertexbuffer and writes its data in the Submesh - static void ReadVertexBuffer(SubMesh &theSubMesh, XmlReader *Reader, unsigned int NumVertices); - - /// Reads bone weights are stores them into the given submesh - static void ReadBoneWeights(SubMesh &theSubMesh, XmlReader *Reader); - - /// After Loading a SubMehs some work needs to be done (make all Vertexes unique, normalize weights) - static void ProcessSubMesh(SubMesh &theSubMesh, SubMesh &theSharedGeometry); - - /// Uses the bone data to convert a SubMesh into a aiMesh which will be created and returned - aiMesh* CreateAssimpSubMesh(const SubMesh &theSubMesh, const std::vector& Bones) const; + /// BaseImporter override. + virtual bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; + /// BaseImporter override. + virtual void InternReadFile(const std::string &pFile, aiScene* pScene, IOSystem* pIOHandler); + + /// BaseImporter override. + virtual const aiImporterDesc* GetInfo () const; + + /// BaseImporter override. + 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(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 LoadSkeleton(std::string FileName, std::vector &Bones, std::vector &Animations) const; - /// Converts the animations in aiAnimations and puts them into the scene + /// Converts the animations in aiAnimations and puts them into the scene. void PutAnimationsInScene(const std::vector &Bones, const std::vector &Animations); - /// Creates the aiskeleton in current scene + /// Creates the aiSkeleton in current scene. void CreateAssimpSkeleton(const std::vector &Bones, const std::vector &Animations); - /// Recursivly creates a filled aiNode from a given root bone + /// Recursivly creates a filled aiNode from a given root bone. static aiNode* CreateAiNodeFromBone(int BoneId, const std::vector &Bones, aiNode* ParentNode); - //-------------------------------- OgreMaterial.cpp ------------------------------- + aiMaterial* LoadMaterial(const std::string MaterialName) const; void ReadTechnique(std::stringstream &ss, aiMaterial* NewMaterial) const; - - - - //Now we don't have to give theses parameters to all functions + // Now we don't have to give theses parameters to all functions + /// @todo Remove this m_Current* bookkeeping. std::string m_CurrentFilename; std::string m_MaterialLibFilename; bool m_TextureTypeFromFilename; @@ -107,73 +129,71 @@ private: SubMesh m_SharedGeometry;///< we will just use the vertexbuffers of the submesh }; -///For the moment just triangles, no other polygon types! +/// 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 { - unsigned int BoneId;//this is, what we get from ogre - std::string BoneName;//this is, what we need for assimp + /// Bone ID from Ogre. + unsigned int BoneId; + // Bone name for Assimp. + std::string BoneName; }; -///for a vertex->bone structur -struct Weight +/// Ogre Bone weight +struct BoneWeight { - unsigned int BoneId; + /// Bone ID + unsigned int Id; + /// BoneWeight float Value; }; /// Helper Class to describe an ogre-bone for the skeleton: -/** All Id's are signed ints, because than we have -1 as a simple INVALID_ID Value (we start from 0 so 0 is a valid bone ID!*/ +/** All Id's are signed ints, because than we have -1 as a simple INVALID_ID Value (we start from 0 so 0 is a valid bone ID! + @todo Cleanup if possible. Seems like this is overly complex for what we are reading. */ struct Bone { + std::string Name; + int Id; int ParentId; - std::string Name; + aiVector3D Position; - float RotationAngle; aiVector3D RotationAxis; - std::vector Children; + float RotationAngle; + aiMatrix4x4 BoneToWorldSpace; - ///ctor - Bone(): Id(-1), ParentId(-1), RotationAngle(0.0f) {} - ///this operator is needed to sort the bones after Id's - bool operator<(const Bone& rval) const - {return Id - bool operator==(const std::string& rval) const - {return Name==rval; } - bool operator==(const aiString& rval) const - {return Name==std::string(rval.data); } + std::vector Children; - // implemented in OgreSkeleton.cpp + Bone() : + Id(-1), + ParentId(-1), + RotationAngle(0.0f) + { + } + + /// This operator is needed to sort the bones after Id's + 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); }; - - -///Describes an Ogre Animation -struct Animation -{ - std::string Name; - float Length; - std::vector Tracks; -}; - -///a track (keyframes for one bone) from an animation -struct Track -{ - std::string BoneName; - std::vector Keyframes; -}; - -/// keyframe (bone transformation) from a track from a animation -struct Keyframe +/// Ogre animation key frame +/** Transformations for a frame. */ +struct KeyFrame { float Time; aiVector3D Position; @@ -181,5 +201,24 @@ struct Keyframe 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; +}; + }//namespace Ogre }//namespace Assimp diff --git a/code/OgreMesh.cpp b/code/OgreMesh.cpp index 4abdee635..e4fba43d3 100644 --- a/code/OgreMesh.cpp +++ b/code/OgreMesh.cpp @@ -52,236 +52,281 @@ namespace Assimp namespace Ogre { - -void OgreImporter::ReadSubMesh(SubMesh &theSubMesh, XmlReader *Reader) +void OgreImporter::ReadSubMesh(const unsigned int submeshIndex, SubMesh &submesh, XmlReader *reader) { - if(Reader->getAttributeValue("usesharedvertices")) - theSubMesh.SharedData=GetAttribute(Reader, "usesharedvertices"); + 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")); - XmlRead(Reader); //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 - while( Reader->getNodeName()==string("faces") - || Reader->getNodeName()==string("geometry") - || Reader->getNodeName()==string("boneassignments")) - { - if(string(Reader->getNodeName())=="faces")//Read the face list - { - //some info logging: - unsigned int NumFaces=GetAttribute(Reader, "count"); - ostringstream ss; ss <<"Submesh has " << NumFaces << " Faces."; - DefaultLogger::get()->debug(ss.str()); + /// @todo Fix above comment with better read logic below - while(XmlRead(Reader) && Reader->getNodeName()==string("face")) + NextNode(reader); + string currentNodeName = reader->getNodeName(); + + string nnFaces = "faces"; + string nnFace = "face"; + string nnGeometry = "geometry"; + string nnBoneAssignments = "boneassignments"; + 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"); - if(Reader->getAttributeValue("v4"))//this should be supported in the future - { - DefaultLogger::get()->warn("Submesh has quads, only traingles are supported!"); - //throw DeadlyImportError("Submesh has quads, only traingles are supported!"); - } - theSubMesh.FaceList.push_back(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(); } - }//end of faces - else if(string(Reader->getNodeName())=="geometry")//Read the vertexdata - { - //some info logging: - unsigned int NumVertices=GetAttribute(Reader, "vertexcount"); - ostringstream ss; ss<<"VertexCount: " << NumVertices; - DefaultLogger::get()->debug(ss.str()); - - //General Informations about vertices - XmlRead(Reader); - while(Reader->getNodeName()==string("vertexbuffer")) - { - ReadVertexBuffer(theSubMesh, Reader, NumVertices); - } - - //some error checking on the loaded data - if(!theSubMesh.HasPositions) - throw DeadlyImportError("No positions could be loaded!"); - - if(theSubMesh.HasNormals && theSubMesh.Normals.size() != NumVertices) - throw DeadlyImportError("Wrong Number of Normals loaded!"); - - if(theSubMesh.HasTangents && theSubMesh.Tangents.size() != NumVertices) - throw DeadlyImportError("Wrong Number of Tangents loaded!"); - - for(unsigned int i=0; igetNodeName()==string("boneassignments")) - { - ReadBoneWeights(theSubMesh, Reader); + 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(); } - DefaultLogger::get()->debug((Formatter::format(), - "Positionen: ",theSubMesh.Positions.size(), - " Normale: ",theSubMesh.Normals.size(), - " TexCoords: ",theSubMesh.Uvs.size(), - " Tantents: ",theSubMesh.Tangents.size() - )); } - -void OgreImporter::ReadVertexBuffer(SubMesh &theSubMesh, XmlReader *Reader, unsigned int NumVertices) +void OgreImporter::ReadVertexBuffer(SubMesh &submesh, XmlReader *reader, const unsigned int numVertices) { - DefaultLogger::get()->debug("new Vertex Buffer"); - - bool ReadPositions=false; - bool ReadNormals=false; - bool ReadTangents=false; - unsigned int NumUvs=0; - - //-------------------- check, what we need to read: -------------------------------- - if(Reader->getAttributeValue("positions") && GetAttribute(Reader, "positions")) - { - ReadPositions=theSubMesh.HasPositions=true; - theSubMesh.Positions.reserve(NumVertices); - DefaultLogger::get()->debug("reading positions"); - } - if(Reader->getAttributeValue("normals") && GetAttribute(Reader, "normals")) - { - ReadNormals=theSubMesh.HasNormals=true; - theSubMesh.Normals.reserve(NumVertices); - DefaultLogger::get()->debug("reading normals"); - } - if(Reader->getAttributeValue("tangents") && GetAttribute(Reader, "tangents")) - { - ReadTangents=theSubMesh.HasTangents=true; - theSubMesh.Tangents.reserve(NumVertices); - DefaultLogger::get()->debug("reading tangents"); - } - - if(Reader->getAttributeValue("texture_coords")) - { - NumUvs=GetAttribute(Reader, "texture_coords"); - theSubMesh.Uvs.resize(NumUvs); - for(unsigned int i=0; idebug("reading texture coords"); - } - //___________________________________________________________________ - - - //check if we will load anything - if(!( ReadPositions || ReadNormals || ReadTangents || (NumUvs>0) )) - DefaultLogger::get()->warn("vertexbuffer seams to be empty!"); + DefaultLogger::get()->debug(Formatter::format() << "Reading vertex buffer with " << numVertices << " vertices"); + submesh.HasGeometry = true; - //read all the vertices: - XmlRead(Reader); - - /*it might happen, that we have more than one attribute per vertex (they are not splitted to different buffers) - so the break condition is a bit tricky */ - while(Reader->getNodeName()==string("vertex") - ||Reader->getNodeName()==string("position") - ||Reader->getNodeName()==string("normal") - ||Reader->getNodeName()==string("tangent") - ||Reader->getNodeName()==string("texcoord") - ||Reader->getNodeName()==string("colour_diffuse")) + if (reader->getAttributeValue("positions") && GetAttribute(reader, "positions")) { - if(Reader->getNodeName()==string("vertex")) - XmlRead(Reader);//Read an attribute tag + 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"); + } - //Position - if(ReadPositions && Reader->getNodeName()==string("position")) + if (!submesh.HasPositions) + throw DeadlyImportError("Vertex buffer does not contain positions!"); + + string nnVertex = "vertex"; + string nnPosition = "position"; + string nnNormal = "normal"; + string nnTangent = "tangent"; + string nnBinormal = "binormal"; + string nnTexCoord = "texcoord"; + string nnColorDiffuse = "colour_diffuse"; + 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"); - theSubMesh.Positions.push_back(NewPos); + NewPos.x = GetAttribute(reader, "x"); + NewPos.y = GetAttribute(reader, "y"); + NewPos.z = GetAttribute(reader, "z"); + submesh.Positions.push_back(NewPos); } - - //Normal - else if(ReadNormals && Reader->getNodeName()==string("normal")) + else if (submesh.HasNormals && currentNodeName == nnNormal) { aiVector3D NewNormal; - NewNormal.x=GetAttribute(Reader, "x"); - NewNormal.y=GetAttribute(Reader, "y"); - NewNormal.z=GetAttribute(Reader, "z"); - theSubMesh.Normals.push_back(NewNormal); + NewNormal.x = GetAttribute(reader, "x"); + NewNormal.y = GetAttribute(reader, "y"); + NewNormal.z = GetAttribute(reader, "z"); + submesh.Normals.push_back(NewNormal); } - - //Tangent - else if(ReadTangents && Reader->getNodeName()==string("tangent")) + else if (submesh.HasTangents && currentNodeName == nnTangent) { aiVector3D NewTangent; - NewTangent.x=GetAttribute(Reader, "x"); - NewTangent.y=GetAttribute(Reader, "y"); - NewTangent.z=GetAttribute(Reader, "z"); - theSubMesh.Tangents.push_back(NewTangent); + NewTangent.x = GetAttribute(reader, "x"); + NewTangent.y = GetAttribute(reader, "y"); + NewTangent.z = GetAttribute(reader, "z"); + submesh.Tangents.push_back(NewTangent); } - - //Uv: - else if(NumUvs>0 && Reader->getNodeName()==string("texcoord")) + else if (submesh.Uvs.size() > 0 && currentNodeName == nnTexCoord) { - for(unsigned int i=0; igetNodeName()!=string("texcoord")) - { - DefaultLogger::get()->warn(string("Not enough UVs in Vertex: ")+Reader->getNodeName()); - } + if (currentNodeName != nnTexCoord) + throw DeadlyImportError("Vertex buffer declared more UVs than can be found in a vertex"); + aiVector3D NewUv; - NewUv.x=GetAttribute(Reader, "u"); - NewUv.y=GetAttribute(Reader, "v")*(-1)+1;//flip the uv vertikal, blender exports them so! - theSubMesh.Uvs[i].push_back(NewUv); - XmlRead(Reader); + NewUv.x = GetAttribute(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;//because we already read the next node... + // Continue main loop as above already read next node + continue; } - - //Color: - //TODO: actually save this data! - else if(Reader->getNodeName()==string("colour_diffuse")) - { - //do nothing, because we not yet support them - } - - //Attribute could not be read else { - DefaultLogger::get()->warn(string("Attribute was not read: ")+Reader->getNodeName()); + /// @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); } - XmlRead(Reader);//Read the Vertex tag + // 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()); -void OgreImporter::ReadBoneWeights(SubMesh &theSubMesh, XmlReader *Reader) -{ - theSubMesh.Weights.resize(theSubMesh.Positions.size()); - while(XmlRead(Reader) && Reader->getNodeName()==string("vertexboneassignment")) + // 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, "vertexindex"); - NewWeight.BoneId=GetAttribute(Reader, "boneindex"); - NewWeight.Value=GetAttribute(Reader, "weight"); - //calculate the number of bones used (this is the highest id +1 becuase bone ids start at 0) - theSubMesh.BonesUsed=max(theSubMesh.BonesUsed, NewWeight.BoneId+1); - - theSubMesh.Weights[VertexId].push_back(NewWeight); + if (submesh.Uvs[i].size() != numVertices) + throw DeadlyImportError(Formatter::format() << "Read only " << submesh.Uvs[i].size() + << " uvs for uv index " << i << " when should have read " << numVertices); } } +void OgreImporter::ReadBoneWeights(SubMesh &submesh, XmlReader *reader) +{ + submesh.Weights.resize(submesh.Positions.size()); + unsigned int numRead = 0; + string nnVertexBoneAssignment = "vertexboneassignment"; -void OgreImporter::ProcessSubMesh(SubMesh &theSubMesh, SubMesh &theSharedGeometry) + NextNode(reader); + while(CurrentNodeNameEquals(reader, nnVertexBoneAssignment)) + { + numRead++; + + BoneWeight weight; + weight.Id = GetAttribute(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: (this is required by assimp)----------------------- - vector UniqueFaceList(theSubMesh.FaceList.size()); - unsigned int UniqueVertexCount=theSubMesh.FaceList.size()*3;//*3 because each face consists of 3 vertexes, because we only support triangles^^ + vector UniqueFaceList(submesh.Faces.size()); + unsigned int UniqueVertexCount=submesh.Faces.size()*3;//*3 because each face consists of 3 vertexes, because we only support triangles^^ vector UniquePositions(UniqueVertexCount); @@ -289,9 +334,9 @@ void OgreImporter::ProcessSubMesh(SubMesh &theSubMesh, SubMesh &theSharedGeometr vector UniqueTangents(UniqueVertexCount); - vector< vector > UniqueWeights(UniqueVertexCount); + vector< vector > UniqueWeights(UniqueVertexCount); - vector< vector > UniqueUvs(theSubMesh.Uvs.size()); + vector< vector > UniqueUvs(submesh.Uvs.size()); for(unsigned int i=0; i1.0f+0.05f) { //normalize all weights: - for(unsigned int BoneId=0; BoneId& Bones) const +aiMesh* OgreImporter::CreateAssimpSubMesh(const SubMesh& submesh, const vector& bones) const { const aiScene* const m_CurrentScene=this->m_CurrentScene;//make sure, that we can access but not change the scene (void)m_CurrentScene; @@ -408,63 +453,63 @@ aiMesh* OgreImporter::CreateAssimpSubMesh(const SubMesh& theSubMesh, const vecto aiMesh* NewAiMesh=new aiMesh(); //Positions - NewAiMesh->mVertices=new aiVector3D[theSubMesh.Positions.size()]; - memcpy(NewAiMesh->mVertices, &theSubMesh.Positions[0], theSubMesh.Positions.size()*sizeof(aiVector3D)); - NewAiMesh->mNumVertices=theSubMesh.Positions.size(); + NewAiMesh->mVertices=new aiVector3D[submesh.Positions.size()]; + memcpy(NewAiMesh->mVertices, &submesh.Positions[0], submesh.Positions.size()*sizeof(aiVector3D)); + NewAiMesh->mNumVertices=submesh.Positions.size(); //Normals - if(theSubMesh.HasNormals) + if(submesh.HasNormals) { - NewAiMesh->mNormals=new aiVector3D[theSubMesh.Normals.size()]; - memcpy(NewAiMesh->mNormals, &theSubMesh.Normals[0], theSubMesh.Normals.size()*sizeof(aiVector3D)); + NewAiMesh->mNormals=new aiVector3D[submesh.Normals.size()]; + memcpy(NewAiMesh->mNormals, &submesh.Normals[0], submesh.Normals.size()*sizeof(aiVector3D)); } //until we have support for bitangents, no tangents will be written /* //Tangents - if(theSubMesh.HasTangents) + if(submesh.HasTangents) { - NewAiMesh->mTangents=new aiVector3D[theSubMesh.Tangents.size()]; - memcpy(NewAiMesh->mTangents, &theSubMesh.Tangents[0], theSubMesh.Tangents.size()*sizeof(aiVector3D)); + NewAiMesh->mTangents=new aiVector3D[submesh.Tangents.size()]; + memcpy(NewAiMesh->mTangents, &submesh.Tangents[0], submesh.Tangents.size()*sizeof(aiVector3D)); } */ //Uvs - if(theSubMesh.Uvs.size()>0) + if(submesh.Uvs.size()>0) { - for(unsigned int i=0; imNumUVComponents[i]=2; - NewAiMesh->mTextureCoords[i]=new aiVector3D[theSubMesh.Uvs[i].size()]; - memcpy(NewAiMesh->mTextureCoords[i], &(theSubMesh.Uvs[i][0]), theSubMesh.Uvs[i].size()*sizeof(aiVector3D)); + NewAiMesh->mTextureCoords[i]=new aiVector3D[submesh.Uvs[i].size()]; + memcpy(NewAiMesh->mTextureCoords[i], &(submesh.Uvs[i][0]), submesh.Uvs[i].size()*sizeof(aiVector3D)); } } - //---------------------------------------- Bones -------------------------------------------- + //---------------------------------------- bones -------------------------------------------- //Copy the weights in in Bone-Vertices Struktur - //(we have them in a Vertex-Bones Structur, this is much easier for making them unique, which is required by assimp - vector< vector > aiWeights(theSubMesh.BonesUsed);//now the outer list are the bones, and the inner vector the vertices - for(unsigned int VertexId=0; VertexId > aiWeights(submesh.BonesUsed);//now the outer list are the bones, and the inner vector the vertices + for(unsigned int VertexId=0; VertexId aiBones; - aiBones.reserve(theSubMesh.BonesUsed);//the vector might be smaller, because there might be empty bones (bones that are not attached to any vertex) + aiBones.reserve(submesh.BonesUsed);//the vector might be smaller, because there might be empty bones (bones that are not attached to any vertex) //create all the bones and fill them with informations - for(unsigned int i=0; i0) { @@ -472,8 +517,8 @@ aiMesh* OgreImporter::CreateAssimpSubMesh(const SubMesh& theSubMesh, const vecto NewBone->mNumWeights=aiWeights[i].size(); NewBone->mWeights=new aiVertexWeight[aiWeights[i].size()]; memcpy(NewBone->mWeights, &(aiWeights[i][0]), sizeof(aiVertexWeight)*aiWeights[i].size()); - NewBone->mName=Bones[i].Name;//The bone list should be sorted after its id's, this was done in LoadSkeleton - NewBone->mOffsetMatrix=Bones[i].BoneToWorldSpace; + NewBone->mName=bones[i].Name;//The bone list should be sorted after its id's, this was done in LoadSkeleton + NewBone->mOffsetMatrix=bones[i].BoneToWorldSpace; aiBones.push_back(NewBone); } @@ -491,20 +536,20 @@ aiMesh* OgreImporter::CreateAssimpSubMesh(const SubMesh& theSubMesh, const vecto //Faces - NewAiMesh->mFaces=new aiFace[theSubMesh.FaceList.size()]; - for(unsigned int i=0; imFaces=new aiFace[submesh.Faces.size()]; + for(unsigned int i=0; imFaces[i].mNumIndices=3; NewAiMesh->mFaces[i].mIndices=new unsigned int[3]; - NewAiMesh->mFaces[i].mIndices[0]=theSubMesh.FaceList[i].VertexIndices[0]; - NewAiMesh->mFaces[i].mIndices[1]=theSubMesh.FaceList[i].VertexIndices[1]; - NewAiMesh->mFaces[i].mIndices[2]=theSubMesh.FaceList[i].VertexIndices[2]; + NewAiMesh->mFaces[i].mIndices[0]=submesh.Faces[i].VertexIndices[0]; + NewAiMesh->mFaces[i].mIndices[1]=submesh.Faces[i].VertexIndices[1]; + NewAiMesh->mFaces[i].mIndices[2]=submesh.Faces[i].VertexIndices[2]; } - NewAiMesh->mNumFaces=theSubMesh.FaceList.size(); + NewAiMesh->mNumFaces=submesh.Faces.size(); //Link the material: - NewAiMesh->mMaterialIndex=theSubMesh.MaterialIndex;//the index is set by the function who called ReadSubMesh + NewAiMesh->mMaterialIndex=submesh.MaterialIndex;//the index is set by the function who called ReadSubMesh return NewAiMesh; } diff --git a/code/OgreSkeleton.cpp b/code/OgreSkeleton.cpp index 376829cfb..627c56ab8 100644 --- a/code/OgreSkeleton.cpp +++ b/code/OgreSkeleton.cpp @@ -77,18 +77,18 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto if(!SkeletonFile) throw DeadlyImportError(string("Failed to create XML Reader for ")+FileName); - XmlRead(SkeletonFile); + NextNode(SkeletonFile); if(string("skeleton")!=SkeletonFile->getNodeName()) throw DeadlyImportError("No node in SkeletonFile: "+FileName); //------------------------------------load bones----------------------------------------- - XmlRead(SkeletonFile); + NextNode(SkeletonFile); if(string("bones")!=SkeletonFile->getNodeName()) throw DeadlyImportError("No bones node in skeleton "+FileName); - XmlRead(SkeletonFile); + NextNode(SkeletonFile); while(string("bone")==SkeletonFile->getNodeName()) { @@ -100,7 +100,7 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto NewBone.Name=GetAttribute(SkeletonFile, "name"); //load the position: - XmlRead(SkeletonFile); + NextNode(SkeletonFile); if(string("position")!=SkeletonFile->getNodeName()) throw DeadlyImportError("Position is not first node in Bone!"); NewBone.Position.x=GetAttribute(SkeletonFile, "x"); @@ -108,11 +108,11 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto NewBone.Position.z=GetAttribute(SkeletonFile, "z"); //Rotation: - XmlRead(SkeletonFile); + NextNode(SkeletonFile); if(string("rotation")!=SkeletonFile->getNodeName()) throw DeadlyImportError("Rotation is not the second node in Bone!"); NewBone.RotationAngle=GetAttribute(SkeletonFile, "angle"); - XmlRead(SkeletonFile); + NextNode(SkeletonFile); if(string("axis")!=SkeletonFile->getNodeName()) throw DeadlyImportError("No axis specified for bone rotation!"); NewBone.RotationAxis.x=GetAttribute(SkeletonFile, "x"); @@ -123,7 +123,7 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto Bones.push_back(NewBone); //Proceed to the next bone: - XmlRead(SkeletonFile); + NextNode(SkeletonFile); } //The bones in the file a not neccesarly ordered by there id's so we do it now: std::sort(Bones.begin(), Bones.end()); @@ -153,7 +153,7 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto throw DeadlyImportError("no bonehierarchy node in "+FileName); DefaultLogger::get()->debug("loading bonehierarchy..."); - XmlRead(SkeletonFile); + NextNode(SkeletonFile); while(string("boneparent")==SkeletonFile->getNodeName()) { string Child, Parent; @@ -167,7 +167,7 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto Bones[ChildId].ParentId=ParentId; Bones[ParentId].Children.push_back(ChildId); - XmlRead(SkeletonFile); + NextNode(SkeletonFile); } //_____________________________________________________________________________ @@ -187,7 +187,7 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto if(string("animations")==SkeletonFile->getNodeName())//animations are optional values { DefaultLogger::get()->debug("Loading Animations"); - XmlRead(SkeletonFile); + NextNode(SkeletonFile); while(string("animation")==SkeletonFile->getNodeName()) { Animation NewAnimation; @@ -195,30 +195,30 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto NewAnimation.Length=GetAttribute(SkeletonFile, "length"); //Load all Tracks - XmlRead(SkeletonFile); + NextNode(SkeletonFile); if(string("tracks")!=SkeletonFile->getNodeName()) throw DeadlyImportError("no tracks node in animation"); - XmlRead(SkeletonFile); + NextNode(SkeletonFile); while(string("track")==SkeletonFile->getNodeName()) { Track NewTrack; NewTrack.BoneName=GetAttribute(SkeletonFile, "bone"); //Load all keyframes; - XmlRead(SkeletonFile); + NextNode(SkeletonFile); if(string("keyframes")!=SkeletonFile->getNodeName()) throw DeadlyImportError("no keyframes node!"); - XmlRead(SkeletonFile); + NextNode(SkeletonFile); while(string("keyframe")==SkeletonFile->getNodeName()) { - Keyframe NewKeyframe; + KeyFrame NewKeyframe; NewKeyframe.Time=GetAttribute(SkeletonFile, "time"); //loop over the attributes: while(true) //will quit, if a Node is not a animationkey { - XmlRead(SkeletonFile); + NextNode(SkeletonFile); //If any property doesn't show up, it will keep its initialization value @@ -235,7 +235,7 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto { float RotationAngle=GetAttribute(SkeletonFile, "angle"); aiVector3D RotationAxis; - XmlRead(SkeletonFile); + NextNode(SkeletonFile); if(string("axis")!=SkeletonFile->getNodeName()) throw DeadlyImportError("No axis for keyframe rotation!"); RotationAxis.x=GetAttribute(SkeletonFile, "x"); @@ -247,7 +247,7 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto RotationAxis.x=1.0f; if(0!=RotationAngle)//if we don't rotate at all, the axis does not matter { - DefaultLogger::get()->warn("Invalid Rotation Axis in Keyframe!"); + DefaultLogger::get()->warn("Invalid Rotation Axis in KeyFrame!"); } } NewKeyframe.Rotation=aiQuaternion(RotationAxis, RotationAngle); diff --git a/code/OgreXmlHelper.hpp b/code/OgreXmlHelper.hpp index 8a71a4be5..74445b86d 100644 --- a/code/OgreXmlHelper.hpp +++ b/code/OgreXmlHelper.hpp @@ -9,80 +9,84 @@ namespace Ogre typedef irr::io::IrrXMLReader XmlReader; - -//------------Helper Funktion to Get a Attribute Save--------------- -template inline t GetAttribute(XmlReader* Reader, std::string Name); - -/* +static void ThrowAttibuteError(const XmlReader* reader, const std::string &name, const std::string &error = "") { - BOOST_STATIC_ASSERT(false); - return t(); -} -*/ - -template<> inline int GetAttribute(XmlReader* Reader, std::string Name) -{ - const char* Value=Reader->getAttributeValue(Name.c_str()); - if(Value) - return atoi(Value); + if (!error.empty()) + throw DeadlyImportError(error + " in node '" + std::string(reader->getNodeName()) + "' and attribute '" + name + "'"); else - throw DeadlyImportError(std::string("Attribute "+Name+" does not exist in "+Reader->getNodeName()).c_str()); + throw DeadlyImportError("Attribute '" + name + "' does not exist in node '" + std::string(reader->getNodeName()) + "'"); } -template<> inline unsigned int GetAttribute(XmlReader* Reader, std::string Name) +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 static_cast(atoi(Value));//yes, ugly, but pfff + const char* value = reader->getAttributeValue(name.c_str()); + if (value) + return atoi(value); else - throw DeadlyImportError(std::string("Attribute "+Name+" does not exist in "+Reader->getNodeName()).c_str()); + ThrowAttibuteError(reader, name); } -template<> inline float GetAttribute(XmlReader* Reader, std::string Name) +template<> +inline unsigned int GetAttribute(const XmlReader* reader, const std::string &name) { - const char* Value=Reader->getAttributeValue(Name.c_str()); - if(Value) - return fast_atof(Value); + const char* value = reader->getAttributeValue(name.c_str()); + if (value) + return static_cast(atoi(value)); ///< @todo Find a better way... else - throw DeadlyImportError(std::string("Attribute "+Name+" does not exist in "+Reader->getNodeName()).c_str()); + ThrowAttibuteError(reader, name); } -template<> inline std::string GetAttribute(XmlReader* Reader, std::string Name) +template<> +inline float GetAttribute(const XmlReader* reader, const std::string &name) { - const char* Value=Reader->getAttributeValue(Name.c_str()); - if(Value) - return std::string(Value); + const char* value = reader->getAttributeValue(name.c_str()); + if (value) + return fast_atof(value); else - throw DeadlyImportError(std::string("Attribute "+Name+" does not exist in "+Reader->getNodeName()).c_str()); + ThrowAttibuteError(reader, name); } -template<> inline bool GetAttribute(XmlReader* Reader, std::string Name) +template<> +inline std::string GetAttribute(const XmlReader* reader, const std::string &name) { - const char* Value=Reader->getAttributeValue(Name.c_str()); - if(Value) - { - if(Value==std::string("true")) - return true; - else if(Value==std::string("false")) - return false; - else - throw DeadlyImportError(std::string("Bool value has invalid value: "+Name+" / "+Value+" / "+Reader->getNodeName())); - } + const char* value = reader->getAttributeValue(name.c_str()); + if (value) + return std::string(value); else - throw DeadlyImportError(std::string("Attribute "+Name+" does not exist in "+Reader->getNodeName()).c_str()); + ThrowAttibuteError(reader, name); } -//__________________________________________________________________ -inline bool XmlRead(XmlReader* Reader) +template<> +inline bool GetAttribute(const XmlReader* reader, const std::string &name) +{ + std::string value = 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 + "'"); +} + +inline bool NextNode(XmlReader* reader) { do { - if(!Reader->read()) + if (!reader->read()) return false; } - while(Reader->getNodeType()!=irr::io::EXN_ELEMENT); + while(reader->getNodeType() != irr::io::EXN_ELEMENT); return true; } -}//namespace Ogre -}//namespace Assimp +inline bool CurrentNodeNameEquals(const XmlReader* reader, const std::string &name) +{ + return (ASSIMP_stricmp(std::string(reader->getNodeName()), name) == 0); +} + +} // Ogre +} // Assimp From 6c51fa2072bc7bdafb0acf22dfc382e9b74e7ae1 Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Wed, 30 Apr 2014 05:52:59 +0300 Subject: [PATCH 02/12] OgreImporter: Continue cleanup. --- code/OgreImporter.cpp | 25 +++++++++++++------------ code/OgreImporter.hpp | 11 +++-------- code/OgreMesh.cpp | 28 ++++++++++++++-------------- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index 1a51489d9..eb3b569f5 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -110,12 +110,12 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass throw DeadlyImportError("Root node is not but <" + string(reader->getNodeName()) + ">"); // Node names - string nnSharedGeometry = "sharedgeometry"; - string nnVertexBuffer = "vertexbuffer"; - string nnSubMeshes = "submeshes"; - string nnSubMesh = "submesh"; - string nnSubMeshNames = "submeshnames"; - string nnSkeletonLink = "skeletonlink"; + 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 @@ -123,7 +123,7 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass NextNode(reader.get()); if (CurrentNodeNameEquals(reader, nnSharedGeometry)) { - DefaultLogger::get()->debug("Reading shader geometry"); + DefaultLogger::get()->debug("Reading shared geometry"); unsigned int NumVertices = GetAttribute(reader.get(), "vertexcount"); NextNode(reader.get()); @@ -147,15 +147,16 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass // Just a index in a array, we add a mesh in each loop cycle, so we get indicies like 0, 1, 2 ... n; // so it is important to do this before pushing the mesh in the vector! + /// @todo Not sure if this really is needed, refactor out if possible. submesh->MaterialIndex = subMeshes.size(); subMeshes.push_back(boost::shared_ptr(submesh)); - // Load the Material: - aiMaterial* MeshMat = LoadMaterial(submesh->MaterialName); - - // Set the Material: - materials.push_back(MeshMat); + /** @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 = LoadMaterial(submesh->MaterialName); + materials.push_back(material); } if (subMeshes.empty()) diff --git a/code/OgreImporter.hpp b/code/OgreImporter.hpp index cf1d0bda2..d946081da 100644 --- a/code/OgreImporter.hpp +++ b/code/OgreImporter.hpp @@ -5,10 +5,6 @@ #include -/** @todo Read Vertex Colors - @todo Read multiple TexCoords -*/ - namespace Assimp { namespace Ogre @@ -63,10 +59,9 @@ struct SubMesh } }; -// ------------------------------------------------------------------------------------------------ -/// \class OgreImporter -/// \brief Importer for Ogre mesh, skeleton and material formats. -// ------------------------------------------------------------------------------------------------ +/** Importer for Ogre mesh, skeleton and material formats. + @todo Support vertex colors + @todo Support multiple TexCoords (this is already done??) */ class OgreImporter : public BaseImporter { public: diff --git a/code/OgreMesh.cpp b/code/OgreMesh.cpp index e4fba43d3..5ca51e583 100644 --- a/code/OgreMesh.cpp +++ b/code/OgreMesh.cpp @@ -73,11 +73,11 @@ void OgreImporter::ReadSubMesh(const unsigned int submeshIndex, SubMesh &submesh NextNode(reader); string currentNodeName = reader->getNodeName(); - string nnFaces = "faces"; - string nnFace = "face"; - string nnGeometry = "geometry"; - string nnBoneAssignments = "boneassignments"; - string nnVertexBuffer = "vertexbuffer"; + const string nnFaces = "faces"; + const string nnFace = "face"; + const string nnGeometry = "geometry"; + const string nnBoneAssignments = "boneassignments"; + const string nnVertexBuffer = "vertexbuffer"; bool quadWarned = false; @@ -165,14 +165,14 @@ void OgreImporter::ReadVertexBuffer(SubMesh &submesh, XmlReader *reader, const u if (!submesh.HasPositions) throw DeadlyImportError("Vertex buffer does not contain positions!"); - string nnVertex = "vertex"; - string nnPosition = "position"; - string nnNormal = "normal"; - string nnTangent = "tangent"; - string nnBinormal = "binormal"; - string nnTexCoord = "texcoord"; - string nnColorDiffuse = "colour_diffuse"; - string nnColorSpecular = "colour_specular"; + 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; @@ -299,7 +299,7 @@ void OgreImporter::ReadBoneWeights(SubMesh &submesh, XmlReader *reader) submesh.Weights.resize(submesh.Positions.size()); unsigned int numRead = 0; - string nnVertexBoneAssignment = "vertexboneassignment"; + const string nnVertexBoneAssignment = "vertexboneassignment"; NextNode(reader); while(CurrentNodeNameEquals(reader, nnVertexBoneAssignment)) From f98584cdeaa128d10e218d618f2ca375fd9f7a6a Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Thu, 1 May 2014 16:33:15 +0300 Subject: [PATCH 03/12] OgreImporter: Remove unnecessary m_currentX state. Improve and clean OgreMaterial: split tech/pass/texture_unit to their own functions. Document missing features and potential bugs. Improve the original authors 'detection from texture filename' logic (enabled with AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME). Add generic detection from texture unit name, which is commonly used in Ogre materials. --- code/OgreImporter.cpp | 48 ++- code/OgreImporter.hpp | 29 +- code/OgreMaterial.cpp | 754 +++++++++++++++++++++++------------------ code/OgreMesh.cpp | 10 +- code/OgreSkeleton.cpp | 57 ++-- code/OgreXmlHelper.hpp | 38 ++- 6 files changed, 520 insertions(+), 416 deletions(-) diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index eb3b569f5..bcc49c9ea 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -38,9 +38,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** @file OgreImporter.cpp - * @brief Implementation of the Ogre XML (.mesh.xml) loader. - */ #include "AssimpPCH.h" #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER @@ -85,10 +82,6 @@ bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandle void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Assimp::IOSystem *pIOHandler) { - m_CurrentFilename = pFile; - m_CurrentIOHandler = pIOHandler; - m_CurrentScene = pScene; - // -------------------- Initial file and XML operations -------------------- // Open @@ -155,7 +148,7 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass /** @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 = LoadMaterial(submesh->MaterialName); + aiMaterial* material = ReadMaterial(pFile, pIOHandler, submesh->MaterialName); materials.push_back(material); } @@ -179,13 +172,14 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass vector Bones; vector Animations; + if (CurrentNodeNameEquals(reader, nnSkeletonLink)) { - string SkeletonFile = GetAttribute(reader.get(), "name"); - if (!SkeletonFile.empty()) - LoadSkeleton(SkeletonFile, Bones, Animations); + 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 reference"); + DefaultLogger::get()->debug("Found a unusual <" + nnSkeletonLink + "> with a empty file reference"); NextNode(reader.get()); } else @@ -204,34 +198,34 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass // -------------------- Apply to aiScene -------------------- //put the aiMaterials in the scene: - m_CurrentScene->mMaterials=new aiMaterial*[materials.size()]; - m_CurrentScene->mNumMaterials=materials.size(); + pScene->mMaterials=new aiMaterial*[materials.size()]; + pScene->mNumMaterials=materials.size(); for(unsigned int i=0; imMaterials[i]=materials[i]; + pScene->mMaterials[i]=materials[i]; //create the aiMehs... vector aiMeshes; BOOST_FOREACH(boost::shared_ptr theSubMesh, subMeshes) { - aiMeshes.push_back(CreateAssimpSubMesh(*theSubMesh, Bones)); + aiMeshes.push_back(CreateAssimpSubMesh(pScene, *theSubMesh, Bones)); } //... and put them in the scene: - m_CurrentScene->mNumMeshes=aiMeshes.size(); - m_CurrentScene->mMeshes=new aiMesh*[aiMeshes.size()]; - memcpy(m_CurrentScene->mMeshes, &(aiMeshes[0]), sizeof(aiMeshes[0])*aiMeshes.size()); + pScene->mNumMeshes=aiMeshes.size(); + pScene->mMeshes=new aiMesh*[aiMeshes.size()]; + memcpy(pScene->mMeshes, &(aiMeshes[0]), sizeof(aiMeshes[0])*aiMeshes.size()); //Create the root node - m_CurrentScene->mRootNode=new aiNode("root"); + pScene->mRootNode=new aiNode("root"); //link the meshs with the root node: - m_CurrentScene->mRootNode->mMeshes=new unsigned int[subMeshes.size()]; - m_CurrentScene->mRootNode->mNumMeshes=subMeshes.size(); + pScene->mRootNode->mMeshes=new unsigned int[subMeshes.size()]; + pScene->mRootNode->mNumMeshes=subMeshes.size(); for(unsigned int i=0; imRootNode->mMeshes[i]=i; + pScene->mRootNode->mMeshes[i]=i; - CreateAssimpSkeleton(Bones, Animations); - PutAnimationsInScene(Bones, Animations); + CreateAssimpSkeleton(pScene, Bones, Animations); + PutAnimationsInScene(pScene, Bones, Animations); } @@ -243,8 +237,8 @@ const aiImporterDesc* OgreImporter::GetInfo () const void OgreImporter::SetupProperties(const Importer* pImp) { - m_MaterialLibFilename=pImp->GetPropertyString(AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE, "Scene.material"); - m_TextureTypeFromFilename=pImp->GetPropertyBool(AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME, false); + m_userDefinedMaterialLibFile = pImp->GetPropertyString(AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE, "Scene.material"); + m_detectTextureTypeFromFilename = pImp->GetPropertyBool(AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME, false); } diff --git a/code/OgreImporter.hpp b/code/OgreImporter.hpp index d946081da..b41d85b43 100644 --- a/code/OgreImporter.hpp +++ b/code/OgreImporter.hpp @@ -93,35 +93,42 @@ private: 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(const SubMesh &submesh, const std::vector& bones) const; + 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 LoadSkeleton(std::string FileName, std::vector &Bones, std::vector &Animations) const; + 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(const std::vector &Bones, const std::vector &Animations); + void PutAnimationsInScene(aiScene *pScene, const std::vector &Bones, const std::vector &Animations); /// Creates the aiSkeleton in current scene. - void CreateAssimpSkeleton(const std::vector &Bones, const std::vector &Animations); + void CreateAssimpSkeleton(aiScene *pScene, const std::vector &Bones, const std::vector &Animations); /// Recursivly creates a filled aiNode from a given root bone. static aiNode* CreateAiNodeFromBone(int BoneId, const std::vector &Bones, aiNode* ParentNode); //-------------------------------- OgreMaterial.cpp ------------------------------- - aiMaterial* LoadMaterial(const std::string MaterialName) const; - void ReadTechnique(std::stringstream &ss, aiMaterial* NewMaterial) const; + /// Reads material + aiMaterial* ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string MaterialName); + + // These functions parse blocks from a material file from @c ss. Starting parsing from "{" and ending it to "}". + bool ReadTechnique(const std::string &techniqueName, std::stringstream &ss, aiMaterial *material); + bool ReadPass(const std::string &passName, std::stringstream &ss, aiMaterial *material); + bool ReadTextureUnit(const std::string &textureUnitName, std::stringstream &ss, aiMaterial *material); // Now we don't have to give theses parameters to all functions /// @todo Remove this m_Current* bookkeeping. - std::string m_CurrentFilename; - std::string m_MaterialLibFilename; - bool m_TextureTypeFromFilename; - IOSystem* m_CurrentIOHandler; - aiScene *m_CurrentScene; + + std::string m_userDefinedMaterialLibFile; + bool m_detectTextureTypeFromFilename; + SubMesh m_SharedGeometry;///< we will just use the vertexbuffers of the submesh + + std::map m_textures; }; /// Simplified face. diff --git a/code/OgreMaterial.cpp b/code/OgreMaterial.cpp index b9913648e..b8e0a05a0 100644 --- a/code/OgreMaterial.cpp +++ b/code/OgreMaterial.cpp @@ -60,34 +60,35 @@ namespace Assimp namespace Ogre { +static const string partComment = "//"; +static const string partBlockStart = "{"; +static const string partBlockEnd = "}"; - -aiMaterial* OgreImporter::LoadMaterial(const std::string MaterialName) const +/// Skips a line from current @ss position until a newline. Returns the skipped part. +std::string SkipLine(stringstream &ss) { - /*For better understanding of the material parser, here is a material example file: + string skipped; + getline(ss, skipped); + return skipped; +} - material Sarg - { - receive_shadows on - technique - { - pass - { - ambient 0.500000 0.500000 0.500000 1.000000 - diffuse 0.640000 0.640000 0.640000 1.000000 - specular 0.500000 0.500000 0.500000 1.000000 12.500000 - emissive 0.000000 0.000000 0.000000 1.000000 - texture_unit - { - texture SargTextur.tga - tex_address_mode wrap - filtering linear linear none - } - } - } - } +/// Skips a line and reads next element from @c ss to @c nextElement. +/** @return Skipped line content until newline. */ +std::string NextAfterNewLine(stringstream &ss, std::string &nextElement) +{ + string skipped = SkipLine(ss); + ss >> nextElement; + return skipped; +} - */ +aiMaterial* OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string materialName) +{ + /// @todo Should we return null ptr here or a empty material? + if (materialName.empty()) + return new aiMaterial(); + + // Full reference and examples of Ogre Material Script + // can be found from http://www.ogre3d.org/docs/manual/manual_14.html /*and here is another one: @@ -112,342 +113,421 @@ aiMaterial* OgreImporter::LoadMaterial(const std::string MaterialName) const } */ - //Read the file into memory and put it in a stringstream stringstream ss; - {// after this block, the temporarly loaded data will be released - /* - We have 3 guesses for the Material filename: - - the Material Name - - the Name of the mesh file - - the DefaultMaterialLib (which you can set before importing) - */ + // Scope for scopre_ptr auto release + { + /* There are three .material options in priority order: + 1) File with the material name (materialName) + 2) File with the mesh files base name (pFile) + 3) Optional user defined material library file (m_userDefinedMaterialLibFile) */ + std::vector potentialFiles; + potentialFiles.push_back(materialName + ".material"); + potentialFiles.push_back(pFile.substr(0, pFile.rfind(".mesh")) + ".material"); + if (!m_userDefinedMaterialLibFile.empty()) + potentialFiles.push_back(m_userDefinedMaterialLibFile); - IOStream* MatFilePtr=m_CurrentIOHandler->Open(MaterialName+".material"); - if(NULL==MatFilePtr) + IOStream *materialFile = 0; + for(size_t i=0; iOpen(potentialFiles[i]); + if (materialFile) + break; + DefaultLogger::get()->debug(Formatter::format() << "Source file for material '" << materialName << "' " << potentialFiles[i] << " does not exist"); + } + if (!materialFile) + { + /// @todo Should we return null ptr here or a empty material? + DefaultLogger::get()->error(Formatter::format() << "Failed to find source file for material '" << materialName << "'"); + return new aiMaterial(); + } - MatFilePtr=m_CurrentIOHandler->Open(MaterialFileName); - if(NULL==MatFilePtr) - { - //try the default mat Library - if(NULL==MatFilePtr) - { - - MatFilePtr=m_CurrentIOHandler->Open(m_MaterialLibFilename); - if(NULL==MatFilePtr) - { - DefaultLogger::get()->error(m_MaterialLibFilename+" and "+MaterialFileName + " could not be opened, Material will not be loaded!"); - return new aiMaterial(); - } - } - } - } - //Fill the stream - boost::scoped_ptr MaterialFile(MatFilePtr); - if(MaterialFile->FileSize()>0) + boost::scoped_ptr stream(materialFile); + if (stream->FileSize() == 0) { - vector FileData(MaterialFile->FileSize()); - MaterialFile->Read(&FileData[0], MaterialFile->FileSize(), 1); - BaseImporter::ConvertToUTF8(FileData); + /// @todo Should we return null ptr here or a empty material? + DefaultLogger::get()->warn(Formatter::format() << "Source file for material '" << materialName << "' is empty (size is 0 bytes)"); + return new aiMaterial(); + } - FileData.push_back('\0');//terminate the string with zero, so that the ss can parse it correctly - ss << &FileData[0]; - } - else - { - DefaultLogger::get()->warn("Material " + MaterialName + " seams to be empty"); - return NULL; - } + // Read bytes + vector data(stream->FileSize()); + stream->Read(&data[0], stream->FileSize(), 1); + + // Convert to UTF-8 and terminate the string for ss + BaseImporter::ConvertToUTF8(data); + data.push_back('\0'); + + ss << &data[0]; } + + DefaultLogger::get()->debug("Reading material '" + materialName + "'"); - //create the material - aiMaterial *NewMaterial=new aiMaterial(); + aiMaterial *material = new aiMaterial(); + m_textures.clear(); + + aiString ts(materialName); + material->AddProperty(&ts, AI_MATKEY_NAME); - aiString ts(MaterialName.c_str()); - NewMaterial->AddProperty(&ts, AI_MATKEY_NAME); + // The stringstream will push words from a line until newline. + // It will also trim whitespace from line start and between words. + string linePart; + ss >> linePart; + + const string partMaterial = "material"; + const string partTechnique = "technique"; - string Line; - ss >> Line; -// unsigned int Level=0;//Hierarchielevels in the material file, like { } blocks into another while(!ss.eof()) { - if(Line=="material") + // Skip commented lines + if (linePart == partComment) { - ss >> Line; - if(Line==MaterialName)//Load the next material - { - string RestOfLine; - getline(ss, RestOfLine);//ignore the rest of the line - ss >> Line; - - if(Line!="{") - { - DefaultLogger::get()->warn("empyt material!"); - return NULL; - } - - while(Line!="}")//read until the end of the material - { - //Proceed to the first technique - ss >> Line; - if(Line=="technique") - { - ReadTechnique(ss, NewMaterial); - } - - DefaultLogger::get()->info(Line); - //read informations from a custom material: - if(Line=="set") - { - ss >> Line; - if(Line=="$specular")//todo load this values: - { - } - if(Line=="$diffuse") - { - } - if(Line=="$ambient") - { - } - if(Line=="$colormap") - { - ss >> Line; - aiString ts(Line.c_str()); - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); - } - if(Line=="$normalmap") - { - ss >> Line; - aiString ts(Line.c_str()); - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0)); - } - - if(Line=="$shininess_strength") - { - ss >> Line; - float Shininess=fast_atof(Line.c_str()); - NewMaterial->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS_STRENGTH); - } - - if(Line=="$shininess_exponent") - { - ss >> Line; - float Shininess=fast_atof(Line.c_str()); - NewMaterial->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS); - } - - //Properties from Venetica: - if(Line=="$diffuse_map") - { - ss >> Line; - if(Line[0]=='"')// "file" -> file - Line=Line.substr(1, Line.size()-2); - aiString ts(Line.c_str()); - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); - } - if(Line=="$specular_map") - { - ss >> Line; - if(Line[0]=='"')// "file" -> file - Line=Line.substr(1, Line.size()-2); - aiString ts(Line.c_str()); - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_SHININESS, 0)); - } - if(Line=="$normal_map") - { - ss >> Line; - if(Line[0]=='"')// "file" -> file - Line=Line.substr(1, Line.size()-2); - aiString ts(Line.c_str()); - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0)); - } - if(Line=="$light_map") - { - ss >> Line; - if(Line[0]=='"')// "file" -> file - Line=Line.substr(1, Line.size()-2); - aiString ts(Line.c_str()); - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, 0)); - } - } - }//end of material - } - else {} //this is the wrong material, proceed the file until we reach the next material + string postComment = NextAfterNewLine(ss, linePart); + DefaultLogger::get()->debug("//" + postComment + " (comment line ignored)"); + continue; } - ss >> Line; - } - - return NewMaterial; -} - -void OgreImporter::ReadTechnique(stringstream &ss, aiMaterial* NewMaterial) const -{ - unsigned int CurrentDiffuseTextureId=0; - unsigned int CurrentSpecularTextureId=0; - unsigned int CurrentNormalTextureId=0; - unsigned int CurrentLightTextureId=0; - - - string RestOfLine; - getline(ss, RestOfLine);//ignore the rest of the line - - string Line; - ss >> Line; - if(Line!="{") - { - DefaultLogger::get()->warn("empty technique!"); - return; - } - while(Line!="}")//read until the end of the technique - { - ss >> Line; - if(Line=="pass") + if (linePart != partMaterial) { - getline(ss, RestOfLine);//ignore the rest of the line + ss >> linePart; + continue; + } - ss >> Line; - if(Line!="{") - { - DefaultLogger::get()->warn("empty pass!"); - return; - } - while(Line!="}")//read until the end of the pass - { - ss >> Line; - if(Line=="ambient") - { - float r,g,b; - ss >> r >> g >> b; - const aiColor3D Color(r,g,b); - NewMaterial->AddProperty(&Color, 1, AI_MATKEY_COLOR_AMBIENT); - } - else if(Line=="diffuse") - { - float r,g,b; - ss >> r >> g >> b; - const aiColor3D Color(r,g,b); - NewMaterial->AddProperty(&Color, 1, AI_MATKEY_COLOR_DIFFUSE); - } - else if(Line=="specular") - { - float r,g,b; - ss >> r >> g >> b; - const aiColor3D Color(r,g,b); - NewMaterial->AddProperty(&Color, 1, AI_MATKEY_COLOR_SPECULAR); - } - else if(Line=="emmisive") - { - float r,g,b; - ss >> r >> g >> b; - const aiColor3D Color(r,g,b); - NewMaterial->AddProperty(&Color, 1, AI_MATKEY_COLOR_EMISSIVE); - } - else if(Line=="texture_unit") - { - getline(ss, RestOfLine);//ignore the rest of the line + ss >> linePart; + if (linePart != materialName) + { + //DefaultLogger::get()->debug(Formatter::format() << "Found material '" << linePart << "' that does not match at index " << ss.tellg()); + ss >> linePart; + continue; + } - std::string TextureName; - int TextureType=-1; - int UvSet=0; + NextAfterNewLine(ss, linePart); + if (linePart != partBlockStart) + { + DefaultLogger::get()->error(Formatter::format() << "Invalid material: block start missing near index " << ss.tellg()); + return material; + } + + DefaultLogger::get()->debug("material '" + materialName + "'"); - ss >> Line; - if(Line!="{") - throw DeadlyImportError("empty texture unit!"); - while(Line!="}")//read until the end of the texture_unit - { - ss >> Line; - if(Line=="texture") - { - ss >> Line; - TextureName=Line; - - if(m_TextureTypeFromFilename) - { - if(Line.find("_n.")!=string::npos)// Normalmap - { - TextureType=aiTextureType_NORMALS; - } - else if(Line.find("_s.")!=string::npos)// Specularmap - { - TextureType=aiTextureType_SPECULAR; - } - else if(Line.find("_l.")!=string::npos)// Lightmap - { - TextureType=aiTextureType_LIGHTMAP; - } - else// colormap - { - TextureType=aiTextureType_DIFFUSE; - } - } - else - { - TextureType=aiTextureType_DIFFUSE; - } - } - else if(Line=="tex_coord_set") - { - ss >> UvSet; - } - else if(Line=="colour_op")//TODO implement this - { - /* - ss >> Line; - if("replace"==Line)//I don't think, assimp has something for this... - { - } - else if("modulate"==Line) - { - //TODO: set value - //NewMaterial->AddProperty(aiTextureOp_Multiply) - } - */ - } - - }//end of texture unit - Line="";//clear the } that would end the outer loop - - //give the texture to assimp: + while(linePart != partBlockEnd) + { + // Proceed to the first technique + ss >> linePart; - aiString ts(TextureName.c_str()); - switch(TextureType) - { - case aiTextureType_DIFFUSE: - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, CurrentDiffuseTextureId)); - NewMaterial->AddProperty(&UvSet, 1, AI_MATKEY_UVWSRC(0, CurrentDiffuseTextureId)); - CurrentDiffuseTextureId++; - break; - case aiTextureType_NORMALS: - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, CurrentNormalTextureId)); - NewMaterial->AddProperty(&UvSet, 1, AI_MATKEY_UVWSRC(0, CurrentNormalTextureId)); - CurrentNormalTextureId++; - break; - case aiTextureType_SPECULAR: - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_SPECULAR, CurrentSpecularTextureId)); - NewMaterial->AddProperty(&UvSet, 1, AI_MATKEY_UVWSRC(0, CurrentSpecularTextureId)); - CurrentSpecularTextureId++; - break; - case aiTextureType_LIGHTMAP: - NewMaterial->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, CurrentLightTextureId)); - NewMaterial->AddProperty(&UvSet, 1, AI_MATKEY_UVWSRC(0, CurrentLightTextureId)); - CurrentLightTextureId++; - break; - default: - DefaultLogger::get()->warn("Invalid Texture Type!"); - break; - } - } + if (linePart == partTechnique) + { + string techniqueName = trim(SkipLine(ss)); + ReadTechnique(techniqueName, ss, material); } - Line="";//clear the } that would end the outer loop + + // Read informations from a custom material + /** @todo This "set $x y" does not seem to be a official Ogre material system feature. + Materials can inherit other materials and override texture units by using the (unique) + parent texture unit name in your cloned material. + This is not yet supported and below code is probably some hack from the original + author of this Ogre importer. Should be removed? */ + if(linePart=="set") + { + ss >> linePart; + if(linePart=="$specular")//todo load this values: + { + } + if(linePart=="$diffuse") + { + } + if(linePart=="$ambient") + { + } + if(linePart=="$colormap") + { + ss >> linePart; + aiString ts(linePart.c_str()); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); + } + if(linePart=="$normalmap") + { + ss >> linePart; + aiString ts(linePart.c_str()); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0)); + } + + if(linePart=="$shininess_strength") + { + ss >> linePart; + float Shininess=fast_atof(linePart.c_str()); + material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS_STRENGTH); + } + + if(linePart=="$shininess_exponent") + { + ss >> linePart; + float Shininess=fast_atof(linePart.c_str()); + material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS); + } + + //Properties from Venetica: + if(linePart=="$diffuse_map") + { + ss >> linePart; + if(linePart[0]=='"')// "file" -> file + linePart=linePart.substr(1, linePart.size()-2); + aiString ts(linePart.c_str()); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); + } + if(linePart=="$specular_map") + { + ss >> linePart; + if(linePart[0]=='"')// "file" -> file + linePart=linePart.substr(1, linePart.size()-2); + aiString ts(linePart.c_str()); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_SHININESS, 0)); + } + if(linePart=="$normal_map") + { + ss >> linePart; + if(linePart[0]=='"')// "file" -> file + linePart=linePart.substr(1, linePart.size()-2); + aiString ts(linePart.c_str()); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0)); + } + if(linePart=="$light_map") + { + ss >> linePart; + if(linePart[0]=='"')// "file" -> file + linePart=linePart.substr(1, linePart.size()-2); + aiString ts(linePart.c_str()); + material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, 0)); + } + } } - }//end of technique + ss >> linePart; + } + + return material; } +bool OgreImporter::ReadTechnique(const std::string &techniqueName, stringstream &ss, aiMaterial *material) +{ + string linePart; + ss >> linePart; -}//namespace Ogre -}//namespace Assimp + if (linePart != partBlockStart) + { + DefaultLogger::get()->error(Formatter::format() << "Invalid material: Technique block start missing near index " << ss.tellg()); + return false; + } -#endif // !! ASSIMP_BUILD_NO_OGRE_IMPORTER + DefaultLogger::get()->debug(" technique '" + techniqueName + "'"); + + const string partPass = "pass"; + + while(linePart != partBlockEnd) + { + ss >> linePart; + + // Skip commented lines + if (linePart == partComment) + { + string postComment = SkipLine(ss); + DefaultLogger::get()->debug(" //" + postComment + " (comment line ignored)"); + continue; + } + + /// @todo Techniques have other attributes than just passes. + if (linePart == partPass) + { + string passName = trim(SkipLine(ss)); + ReadPass(passName, ss, material); + } + } + return true; +} + +bool OgreImporter::ReadPass(const std::string &passName, stringstream &ss, aiMaterial *material) +{ + string linePart; + ss >> linePart; + + if (linePart != partBlockStart) + { + DefaultLogger::get()->error(Formatter::format() << "Invalid material: Pass block start missing near index " << ss.tellg()); + return false; + } + + DefaultLogger::get()->debug(" pass '" + passName + "'"); + + const string partAmbient = "ambient"; + const string partDiffuse = "diffuse"; + const string partSpecular = "specular"; + const string partEmissive = "emissive"; + const string partTextureUnit = "texture_unit"; + + while(linePart != partBlockEnd) + { + ss >> linePart; + + // Skip commented lines + if (linePart == partComment) + { + string postComment = SkipLine(ss); + DefaultLogger::get()->debug(" //" + postComment + " (comment line ignored)"); + continue; + } + + // Colors + /// @todo Support alpha via aiColor4D. + if (linePart == partAmbient || linePart == partDiffuse || linePart == partSpecular || linePart == partEmissive) + { + float r, g, b; + ss >> r >> g >> b; + const aiColor3D color(r, g, b); + + DefaultLogger::get()->debug(Formatter::format() << " " << linePart << " " << r << " " << g << " " << b); + + if (linePart == partAmbient) + material->AddProperty(&color, 1, AI_MATKEY_COLOR_AMBIENT); + else if (linePart == partDiffuse) + material->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE); + else if (linePart == partSpecular) + material->AddProperty(&color, 1, AI_MATKEY_COLOR_SPECULAR); + else if (linePart == partEmissive) + material->AddProperty(&color, 1, AI_MATKEY_COLOR_EMISSIVE); + } + else if (linePart == partTextureUnit) + { + string textureUnitName = trim(SkipLine(ss)); + ReadTextureUnit(textureUnitName, ss, material); + } + } + return true; +} + +bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstream &ss, aiMaterial *material) +{ + string linePart; + ss >> linePart; + + if (linePart != partBlockStart) + { + DefaultLogger::get()->error(Formatter::format() << "Invalid material: Texture unit block start missing near index " << ss.tellg()); + return false; + } + + DefaultLogger::get()->debug(" texture_unit '" + textureUnitName + "'"); + + const string partTexture = "texture"; + const string partTextCoordSet = "tex_coord_set"; + const string partColorOp = "colour_op"; + + aiTextureType textureType = aiTextureType_NONE; + std::string textureRef; + int uvCoord = 0; + + while(linePart != partBlockEnd) + { + ss >> linePart; + + // Skip commented lines + if (linePart == partComment) + { + string postComment = SkipLine(ss); + DefaultLogger::get()->debug(" //" + postComment + " (comment line ignored)"); + continue; + } + + if (linePart == partTexture) + { + ss >> linePart; + textureRef = linePart; + + // User defined Assimp config property to detect texture type from filename. + if (m_detectTextureTypeFromFilename) + { + size_t posSuffix = textureRef.find_last_of("."); + size_t posUnderscore = textureRef.find_last_of("_"); + + if (posSuffix != string::npos && posUnderscore != string::npos && posSuffix > posUnderscore) + { + string identifier = Ogre::ToLower(textureRef.substr(posUnderscore, posSuffix - posUnderscore)); + DefaultLogger::get()->debug(Formatter::format() << "Detecting texture type from filename postfix '" << identifier << "'"); + + if (identifier == "_n" || identifier == "_nrm" || identifier == "_nrml" || identifier == "_normal" || identifier == "_normals" || identifier == "_normalmap") + textureType = aiTextureType_NORMALS; + else if (identifier == "_s" || identifier == "_spec" || identifier == "_specular" || identifier == "_specularmap") + textureType = aiTextureType_SPECULAR; + else if (identifier == "_l" || identifier == "_light" || identifier == "_lightmap" || identifier == "_occ" || identifier == "_occlusion") + textureType = aiTextureType_LIGHTMAP; + else if (identifier == "_disp" || identifier == "_displacement") + textureType = aiTextureType_DISPLACEMENT; + else + textureType = aiTextureType_DIFFUSE; + } + else + textureType = aiTextureType_DIFFUSE; + } + // Detect from texture unit name. This cannot be too broad as + // authors might give names like "LightSaber" or "NormalNinja". + else + { + string unitNameLower = Ogre::ToLower(textureUnitName); + if (unitNameLower.find("normalmap") != string::npos) + textureType = aiTextureType_NORMALS; + else if (unitNameLower.find("specularmap") != string::npos) + textureType = aiTextureType_SPECULAR; + else if (unitNameLower.find("lightmap") != string::npos) + textureType = aiTextureType_LIGHTMAP; + else if (unitNameLower.find("displacementmap") != string::npos) + textureType = aiTextureType_DISPLACEMENT; + else + textureType = aiTextureType_DIFFUSE; + } + } + else if (linePart == partTextCoordSet) + { + ss >> uvCoord; + } + /// @todo Implement + else if(linePart == partColorOp) + { + /* + ss >> linePart; + if("replace"==linePart)//I don't think, assimp has something for this... + { + } + else if("modulate"==linePart) + { + //TODO: set value + //material->AddProperty(aiTextureOp_Multiply) + } + */ + } + } + + if (textureRef.empty()) + { + DefaultLogger::get()->warn("Texture reference is empty, ignoring texture_unit."); + return false; + } + if (textureType == aiTextureType_NONE) + { + DefaultLogger::get()->warn("Failed to detect texture type for '" + textureRef + "', ignoring texture_unit."); + return false; + } + + unsigned int textureTypeIndex = m_textures[textureType]; + m_textures[textureType]++; + + DefaultLogger::get()->debug(Formatter::format() << " texture '" << textureRef << "' type " << textureType + << " index " << textureTypeIndex << " UV " << uvCoord); + + aiString assimpTextureRef(textureRef); + material->AddProperty(&assimpTextureRef, AI_MATKEY_TEXTURE(textureType, textureTypeIndex)); + material->AddProperty(&uvCoord, 1, AI_MATKEY_UVWSRC(textureType, textureTypeIndex)); + + return true; +} + +} // Ogre +} // Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/code/OgreMesh.cpp b/code/OgreMesh.cpp index 5ca51e583..bd67d3add 100644 --- a/code/OgreMesh.cpp +++ b/code/OgreMesh.cpp @@ -442,15 +442,9 @@ void OgreImporter::ProcessSubMesh(SubMesh &submesh, SubMesh &sharedGeometry) //_________________________________________________________ } - - - -aiMesh* OgreImporter::CreateAssimpSubMesh(const SubMesh& submesh, const vector& bones) const +aiMesh* OgreImporter::CreateAssimpSubMesh(aiScene *pScene, const SubMesh& submesh, const vector& bones) const { - const aiScene* const m_CurrentScene=this->m_CurrentScene;//make sure, that we can access but not change the scene - (void)m_CurrentScene; - - aiMesh* NewAiMesh=new aiMesh(); + aiMesh* NewAiMesh = new aiMesh(); //Positions NewAiMesh->mVertices=new aiVector3D[submesh.Positions.size()]; diff --git a/code/OgreSkeleton.cpp b/code/OgreSkeleton.cpp index 627c56ab8..f231cc465 100644 --- a/code/OgreSkeleton.cpp +++ b/code/OgreSkeleton.cpp @@ -52,41 +52,36 @@ namespace Assimp namespace Ogre { - - -void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vector &Animations) const +void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOHandler, const aiScene *pScene, + const std::string &skeletonFile, vector &Bones, vector &Animations) const { - const aiScene* const m_CurrentScene=this->m_CurrentScene;//make sure, that we can access but not change the scene - (void)m_CurrentScene; - - //most likely the skeleton file will only end with .skeleton //But this is a xml reader, so we need: .skeleton.xml - FileName+=".xml"; + string skeletonPath = skeletonFile + ".xml"; - DefaultLogger::get()->debug(string("Loading Skeleton: ")+FileName); + DefaultLogger::get()->debug(string("Loading Skeleton: ")+skeletonFile); //Open the File: - boost::scoped_ptr File(m_CurrentIOHandler->Open(FileName)); + boost::scoped_ptr File(pIOHandler->Open(skeletonFile)); if(NULL==File.get()) - throw DeadlyImportError("Failed to open skeleton file "+FileName+"."); + throw DeadlyImportError("Failed to open skeleton file "+skeletonFile+"."); //Read the Mesh File: boost::scoped_ptr mIOWrapper(new CIrrXML_IOStreamReader(File.get())); XmlReader* SkeletonFile = irr::io::createIrrXMLReader(mIOWrapper.get()); if(!SkeletonFile) - throw DeadlyImportError(string("Failed to create XML Reader for ")+FileName); + throw DeadlyImportError(string("Failed to create XML Reader for ")+skeletonFile); NextNode(SkeletonFile); if(string("skeleton")!=SkeletonFile->getNodeName()) - throw DeadlyImportError("No node in SkeletonFile: "+FileName); + throw DeadlyImportError("No node in SkeletonFile: "+skeletonFile); //------------------------------------load bones----------------------------------------- NextNode(SkeletonFile); if(string("bones")!=SkeletonFile->getNodeName()) - throw DeadlyImportError("No bones node in skeleton "+FileName); + throw DeadlyImportError("No bones node in skeleton "+skeletonFile); NextNode(SkeletonFile); @@ -138,7 +133,7 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto IdsOk=false; } if(!IdsOk) - throw DeadlyImportError("Bone Ids are not valid!"+FileName); + throw DeadlyImportError("Bone Ids are not valid!"+skeletonFile); } DefaultLogger::get()->debug((Formatter::format(),"Number of bones: ",Bones.size())); //________________________________________________________________________________ @@ -150,7 +145,7 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto //----------------------------load bonehierarchy-------------------------------- if(string("bonehierarchy")!=SkeletonFile->getNodeName()) - throw DeadlyImportError("no bonehierarchy node in "+FileName); + throw DeadlyImportError("no bonehierarchy node in "+skeletonFile); DefaultLogger::get()->debug("loading bonehierarchy..."); NextNode(SkeletonFile); @@ -280,11 +275,11 @@ void OgreImporter::LoadSkeleton(std::string FileName, vector &Bones, vecto } -void OgreImporter::CreateAssimpSkeleton(const std::vector &Bones, const std::vector &/*Animations*/) +void OgreImporter::CreateAssimpSkeleton(aiScene *pScene, const std::vector &Bones, const std::vector &/*Animations*/) { - if(!m_CurrentScene->mRootNode) + if(!pScene->mRootNode) throw DeadlyImportError("No root node exists!!"); - if(0!=m_CurrentScene->mRootNode->mNumChildren) + if(0!=pScene->mRootNode->mNumChildren) throw DeadlyImportError("Root Node already has childnodes!"); @@ -295,26 +290,27 @@ void OgreImporter::CreateAssimpSkeleton(const std::vector &Bones, const st if(-1==theBone.ParentId) //the bone is a root bone { //which will recursily add all other nodes - RootBoneNodes.push_back(CreateAiNodeFromBone(theBone.Id, Bones, m_CurrentScene->mRootNode)); + RootBoneNodes.push_back(CreateAiNodeFromBone(theBone.Id, Bones, pScene->mRootNode)); } } if(RootBoneNodes.size() > 0) { - m_CurrentScene->mRootNode->mNumChildren=RootBoneNodes.size(); - m_CurrentScene->mRootNode->mChildren=new aiNode*[RootBoneNodes.size()]; - memcpy(m_CurrentScene->mRootNode->mChildren, &RootBoneNodes[0], sizeof(aiNode*)*RootBoneNodes.size()); + pScene->mRootNode->mNumChildren=RootBoneNodes.size(); + pScene->mRootNode->mChildren=new aiNode*[RootBoneNodes.size()]; + memcpy(pScene->mRootNode->mChildren, &RootBoneNodes[0], sizeof(aiNode*)*RootBoneNodes.size()); } } - -void OgreImporter::PutAnimationsInScene(const std::vector &Bones, const std::vector &Animations) +void OgreImporter::PutAnimationsInScene(aiScene *pScene, const std::vector &Bones, const std::vector &Animations) { - //-----------------Create the Assimp Animations -------------------- + // TODO: Auf nicht vorhandene Animationskeys achten! + // @todo Pay attention to non-existing animation Keys (google translated from above german comment) + if(Animations.size()>0)//Maybe the model had only a skeleton and no animations. (If it also has no skeleton, this function would'nt have been called { - m_CurrentScene->mNumAnimations=Animations.size(); - m_CurrentScene->mAnimations=new aiAnimation*[Animations.size()]; + pScene->mNumAnimations=Animations.size(); + pScene->mAnimations=new aiAnimation*[Animations.size()]; for(unsigned int i=0; i &Bones, const st NewAnimation->mChannels[j]=NewNodeAnim; } - m_CurrentScene->mAnimations[i]=NewAnimation; + pScene->mAnimations[i]=NewAnimation; } } -//TODO: Auf nicht vorhandene Animationskeys achten! -//#pragma warning (s.o.) - //__________________________________________________________________ } diff --git a/code/OgreXmlHelper.hpp b/code/OgreXmlHelper.hpp index 74445b86d..79bcf0096 100644 --- a/code/OgreXmlHelper.hpp +++ b/code/OgreXmlHelper.hpp @@ -1,4 +1,5 @@ +#include "ParsingUtils.h" #include "irrXMLWrapper.h" #include "fast_atof.h" @@ -6,7 +7,7 @@ namespace Assimp { namespace Ogre { - + typedef irr::io::IrrXMLReader XmlReader; static void ThrowAttibuteError(const XmlReader* reader, const std::string &name, const std::string &error = "") @@ -88,5 +89,40 @@ inline bool CurrentNodeNameEquals(const XmlReader* reader, const std::string &na return (ASSIMP_stricmp(std::string(reader->getNodeName()), name) == 0); } +/// Returns a lower cased copy of @s. +static inline std::string ToLower(std::string s) +{ + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + return s; +} + +// ------------------------------------------------------------------------------------------------ +// From http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring + +// trim from start +static inline std::string <rim(std::string &s, bool newlines = true) +{ + if (!newlines) + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(Assimp::IsSpace)))); + else + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(Assimp::IsSpaceOrNewLine)))); + return s; +} + +// trim from end +static inline std::string &rtrim(std::string &s, bool newlines = true) +{ + if (!newlines) + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(Assimp::IsSpace))).base(),s.end()); + else + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(Assimp::IsSpaceOrNewLine)))); + return s; +} +// trim from both ends +static inline std::string &trim(std::string &s, bool newlines = true) +{ + return ltrim(rtrim(s, newlines), newlines); +} + } // Ogre } // Assimp From 6ea07f39e1e45e8c20abb7ccc9cb620641a0762f Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Thu, 1 May 2014 16:35:34 +0300 Subject: [PATCH 04/12] OgreImporter: Rewrote documentation for AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE and AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME to reflect the current logic. And to be honest the earlier one was very confusing english and was not very clear what is happening with these config options. --- include/assimp/config.h | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/include/assimp/config.h b/include/assimp/config.h index 034652fa8..96df293b8 100644 --- a/include/assimp/config.h +++ b/include/assimp/config.h @@ -801,29 +801,37 @@ enum aiComponent #define AI_CONFIG_IMPORT_IRR_ANIM_FPS \ "IMPORT_IRR_ANIM_FPS" - // --------------------------------------------------------------------------- -/** @brief Ogre Importer will try to load this Materialfile. +/** @brief Ogre Importer will try to find referenced materials from this file. * - * Ogre Meshes contain only the MaterialName, not the MaterialFile. If there - * is no material file with the same name as the material, Ogre Importer will - * try to load this file and search the material in it. + * Ogre meshes reference with material names, this does not tell Assimp the file + * where it is located in. Assimp will try to find the source file in the following + * order: .material, .material and + * lastly the material name defined by this config property. *
- * Property type: String. Default value: guessed. + * Property type: String. Default value: n/a. */ -#define AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE "IMPORT_OGRE_MATERIAL_FILE" - +#define AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE \ + "IMPORT_OGRE_MATERIAL_FILE" // --------------------------------------------------------------------------- -/** @brief Ogre Importer detect the texture usage from its filename +/** @brief Ogre Importer detect the texture usage from its filename. * - * Normally, a texture is loaded as a colormap, if no target is specified in the - * materialfile. Is this switch is enabled, texture names ending with _n, _l, _s - * are used as normalmaps, lightmaps or specularmaps. + * Ogre material texture units do not define texture type, the textures usage + * depends on the used shader or Ogres fixed pipeline. If this config property + * is true Assimp will try to detect the type from the textures filename postfix: + * _n, _nrm, _nrml, _normal, _normals and _normalmap for normal map, _s, _spec, + * _specular and _specularmap for specular map, _l, _light, _lightmap, _occ + * and _occlusion for light map, _disp and _displacement for displacement map. + * The matching is case insensitive. Post fix is taken between last "_" and last ".". + * Default behavior is to detect type from lower cased texture unit name by + * matching against: normalmap, specularmap, lightmap and displacementmap. + * For both cases if no match is found aiTextureType_DIFFUSE is used. *
* Property type: Bool. Default value: false. */ -#define AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME "IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME" +#define AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME \ + "IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME" /** @brief Specifies whether the IFC loader skips over IfcSpace elements. * From fcb97bb595bc5dfce46cc636797bc29b5444fcfb Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Thu, 1 May 2014 16:49:26 +0300 Subject: [PATCH 05/12] OgreImporter: Rename .hpp to .h. Shuffled parsing utils to a single file. --- code/CMakeLists.txt | 4 +-- code/{OgreImporter.hpp => OgreImporter.h} | 2 +- code/OgreMaterial.cpp | 28 ++++--------------- .../{OgreXmlHelper.hpp => OgreParsingUtils.h} | 25 ++++++++++++++--- 4 files changed, 30 insertions(+), 29 deletions(-) rename code/{OgreImporter.hpp => OgreImporter.h} (99%) rename code/{OgreXmlHelper.hpp => OgreParsingUtils.h} (81%) diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 561797942..9afbc6c3d 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -320,8 +320,8 @@ SET( Obj_SRCS SOURCE_GROUP( Obj FILES ${Obj_SRCS}) SET( Ogre_SRCS - OgreImporter.hpp - OgreXmlHelper.hpp + OgreImporter.h + OgreParsingUtils.h OgreImporter.cpp OgreMaterial.cpp OgreMesh.cpp diff --git a/code/OgreImporter.hpp b/code/OgreImporter.h similarity index 99% rename from code/OgreImporter.hpp rename to code/OgreImporter.h index b41d85b43..d41e77230 100644 --- a/code/OgreImporter.hpp +++ b/code/OgreImporter.h @@ -1,7 +1,7 @@ #include "BaseImporter.h" -#include "OgreXmlHelper.hpp" #include "irrXMLWrapper.h" +#include "OgreParsingUtils.h" #include diff --git a/code/OgreMaterial.cpp b/code/OgreMaterial.cpp index b8e0a05a0..f19e9ec88 100644 --- a/code/OgreMaterial.cpp +++ b/code/OgreMaterial.cpp @@ -49,12 +49,13 @@ to make it shorter easier to maintain. #include #include -using namespace std; -#include "OgreImporter.hpp" +#include "OgreImporter.h" #include "irrXMLWrapper.h" #include "TinyFormatter.h" +using namespace std; + namespace Assimp { namespace Ogre @@ -64,23 +65,6 @@ static const string partComment = "//"; static const string partBlockStart = "{"; static const string partBlockEnd = "}"; -/// Skips a line from current @ss position until a newline. Returns the skipped part. -std::string SkipLine(stringstream &ss) -{ - string skipped; - getline(ss, skipped); - return skipped; -} - -/// Skips a line and reads next element from @c ss to @c nextElement. -/** @return Skipped line content until newline. */ -std::string NextAfterNewLine(stringstream &ss, std::string &nextElement) -{ - string skipped = SkipLine(ss); - ss >> nextElement; - return skipped; -} - aiMaterial* OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string materialName) { /// @todo Should we return null ptr here or a empty material? @@ -216,7 +200,7 @@ aiMaterial* OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSyste if (linePart == partTechnique) { - string techniqueName = trim(SkipLine(ss)); + string techniqueName = Trim(SkipLine(ss)); ReadTechnique(techniqueName, ss, material); } @@ -336,7 +320,7 @@ bool OgreImporter::ReadTechnique(const std::string &techniqueName, stringstream /// @todo Techniques have other attributes than just passes. if (linePart == partPass) { - string passName = trim(SkipLine(ss)); + string passName = Trim(SkipLine(ss)); ReadPass(passName, ss, material); } } @@ -395,7 +379,7 @@ bool OgreImporter::ReadPass(const std::string &passName, stringstream &ss, aiMat } else if (linePart == partTextureUnit) { - string textureUnitName = trim(SkipLine(ss)); + string textureUnitName = Trim(SkipLine(ss)); ReadTextureUnit(textureUnitName, ss, material); } } diff --git a/code/OgreXmlHelper.hpp b/code/OgreParsingUtils.h similarity index 81% rename from code/OgreXmlHelper.hpp rename to code/OgreParsingUtils.h index 79bcf0096..d287f451b 100644 --- a/code/OgreXmlHelper.hpp +++ b/code/OgreParsingUtils.h @@ -89,6 +89,23 @@ inline bool CurrentNodeNameEquals(const XmlReader* reader, const std::string &na 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) +{ + std::string skipped; + getline(ss, skipped); + return skipped; +} + +/// Skips a line and reads next element from @c ss to @c nextElement. +/** @return Skipped line content until newline. */ +static inline std::string NextAfterNewLine(std::stringstream &ss, std::string &nextElement) +{ + std::string skipped = SkipLine(ss); + ss >> nextElement; + return skipped; +} + /// Returns a lower cased copy of @s. static inline std::string ToLower(std::string s) { @@ -100,7 +117,7 @@ static inline std::string ToLower(std::string s) // From http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring // trim from start -static inline std::string <rim(std::string &s, bool newlines = true) +static inline std::string &TrimLeft(std::string &s, bool newlines = true) { if (!newlines) s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(Assimp::IsSpace)))); @@ -110,7 +127,7 @@ static inline std::string <rim(std::string &s, bool newlines = true) } // trim from end -static inline std::string &rtrim(std::string &s, bool newlines = true) +static inline std::string &TrimRight(std::string &s, bool newlines = true) { if (!newlines) s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(Assimp::IsSpace))).base(),s.end()); @@ -119,9 +136,9 @@ static inline std::string &rtrim(std::string &s, bool newlines = true) return s; } // trim from both ends -static inline std::string &trim(std::string &s, bool newlines = true) +static inline std::string &Trim(std::string &s, bool newlines = true) { - return ltrim(rtrim(s, newlines), newlines); + return TrimLeft(TrimRight(s, newlines), newlines); } } // Ogre From 45715df263a7d97146e568850fb14bae669ed441 Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Thu, 1 May 2014 17:19:03 +0300 Subject: [PATCH 06/12] OgreImporter: Forgot to rebuild after filename changes so broke the build. Fix includes, add include guards and ASSIMP_BUILD_NO_OGRE_IMPORTER to missing places. --- code/ImporterRegistry.cpp | 2 +- code/OgreImporter.cpp | 5 +++-- code/OgreImporter.h | 8 ++++++++ code/OgreMesh.cpp | 2 +- code/OgreParsingUtils.h | 8 ++++++++ code/OgreSkeleton.cpp | 2 +- 6 files changed, 22 insertions(+), 5 deletions(-) diff --git a/code/ImporterRegistry.cpp b/code/ImporterRegistry.cpp index bc0e59f56..e301804e2 100644 --- a/code/ImporterRegistry.cpp +++ b/code/ImporterRegistry.cpp @@ -140,7 +140,7 @@ corresponding preprocessor flag to selectively disable formats. # include "LWSLoader.h" #endif #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER -# include "OgreImporter.hpp" +# include "OgreImporter.h" #endif #ifndef ASSIMP_BUILD_NO_MS3D_IMPORTER # include "MS3DLoader.h" diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index bcc49c9ea..c67cf4998 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -44,7 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include "OgreImporter.hpp" +#include "OgreImporter.h" #include "TinyFormatter.h" #include "irrXMLWrapper.h" @@ -74,7 +74,8 @@ bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandle { string ext = "mesh.xml"; int len = ext.length(); - return (ASSIMP_stricmp(pFile.substr(pFile.length()-len, len), ext) == 0); + string fileExt = ToLower(pFile.substr(pFile.length()-len, len)); + return (ASSIMP_stricmp(fileExt, ext) == 0); } const char* tokens[] = {""}; return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); diff --git a/code/OgreImporter.h b/code/OgreImporter.h index d41e77230..27d03b85d 100644 --- a/code/OgreImporter.h +++ b/code/OgreImporter.h @@ -1,4 +1,9 @@ +#ifndef AI_OGREIMPORTER_H_INC +#define AI_OGREIMPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + #include "BaseImporter.h" #include "irrXMLWrapper.h" #include "OgreParsingUtils.h" @@ -224,3 +229,6 @@ struct Animation }//namespace Ogre }//namespace Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // AI_OGREIMPORTER_H_INC diff --git a/code/OgreMesh.cpp b/code/OgreMesh.cpp index bd67d3add..c5d8e822e 100644 --- a/code/OgreMesh.cpp +++ b/code/OgreMesh.cpp @@ -42,7 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER -#include "OgreImporter.hpp" +#include "OgreImporter.h" #include "TinyFormatter.h" using namespace std; diff --git a/code/OgreParsingUtils.h b/code/OgreParsingUtils.h index d287f451b..a6a8faa8e 100644 --- a/code/OgreParsingUtils.h +++ b/code/OgreParsingUtils.h @@ -1,4 +1,9 @@ +#ifndef AI_OGREPARSINGUTILS_H_INC +#define AI_OGREPARSINGUTILS_H_INC + +#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER + #include "ParsingUtils.h" #include "irrXMLWrapper.h" #include "fast_atof.h" @@ -143,3 +148,6 @@ static inline std::string &Trim(std::string &s, bool newlines = true) } // Ogre } // Assimp + +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // AI_OGREPARSINGUTILS_H_INC diff --git a/code/OgreSkeleton.cpp b/code/OgreSkeleton.cpp index f231cc465..fe247afe5 100644 --- a/code/OgreSkeleton.cpp +++ b/code/OgreSkeleton.cpp @@ -42,7 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER -#include "OgreImporter.hpp" +#include "OgreImporter.h" #include "TinyFormatter.h" using namespace std; From f5c7b283bc5e57faee5d047a8d57ccfd4cd1413e Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Thu, 1 May 2014 18:51:26 +0300 Subject: [PATCH 07/12] OgreImporter: Cleanup and bugfixes to OgreSkeleton.cpp. This was actually so badly broken that it did nothing if the mesh referenced a binary skeleton. Now logs a warning for this case and tries to read from .skeleton.xml like the original author intended it to work. The assimp skeleton is still broken, I will fix that later on when I (eventually) get to that part of the code. --- code/OgreImporter.cpp | 22 ++- code/OgreImporter.h | 9 +- code/OgreMaterial.cpp | 5 - code/OgreMesh.cpp | 7 +- code/OgreParsingUtils.h | 26 ++- code/OgreSkeleton.cpp | 342 ++++++++++++++++++---------------------- 6 files changed, 195 insertions(+), 216 deletions(-) diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index c67cf4998..697c47a62 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -39,6 +39,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "AssimpPCH.h" + #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER #include @@ -71,14 +72,10 @@ namespace Ogre bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool checkSig) const { if (!checkSig) - { - string ext = "mesh.xml"; - int len = ext.length(); - string fileExt = ToLower(pFile.substr(pFile.length()-len, len)); - return (ASSIMP_stricmp(fileExt, ext) == 0); - } - const char* tokens[] = {""}; - return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); + return EndsWith(pFile, ".mesh.xml", false); + + const char* tokens[] = { "" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); } void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Assimp::IOSystem *pIOHandler) @@ -101,7 +98,7 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass // Read root node NextNode(reader.get()); if (!CurrentNodeNameEquals(reader, "mesh")) - throw DeadlyImportError("Root node is not but <" + string(reader->getNodeName()) + ">"); + throw DeadlyImportError("Root node is not but <" + string(reader->getNodeName()) + "> in " + pFile); // Node names const string nnSharedGeometry = "sharedgeometry"; @@ -242,8 +239,7 @@ void OgreImporter::SetupProperties(const Importer* pImp) m_detectTextureTypeFromFilename = pImp->GetPropertyBool(AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME, false); } +} // Ogre +} // Assimp -}//namespace Ogre -}//namespace Assimp - -#endif // !! ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/code/OgreImporter.h b/code/OgreImporter.h index 27d03b85d..240de85fc 100644 --- a/code/OgreImporter.h +++ b/code/OgreImporter.h @@ -155,7 +155,7 @@ struct BoneAssignment /// Ogre Bone weight struct BoneWeight { - /// Bone ID + /// Bone Id unsigned int Id; /// BoneWeight float Value; @@ -187,8 +187,11 @@ struct Bone { } - /// This operator is needed to sort the bones after Id's - bool operator<(const Bone &other) const { return Id < other.Id; } + /// 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; } diff --git a/code/OgreMaterial.cpp b/code/OgreMaterial.cpp index f19e9ec88..506b0fde9 100644 --- a/code/OgreMaterial.cpp +++ b/code/OgreMaterial.cpp @@ -38,11 +38,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ -/** -This file contains material related code. This is -spilitted up from the main file OgreImporter.cpp -to make it shorter easier to maintain. -*/ #include "AssimpPCH.h" #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/code/OgreMesh.cpp b/code/OgreMesh.cpp index c5d8e822e..06361ca87 100644 --- a/code/OgreMesh.cpp +++ b/code/OgreMesh.cpp @@ -548,8 +548,7 @@ aiMesh* OgreImporter::CreateAssimpSubMesh(aiScene *pScene, const SubMesh& submes return NewAiMesh; } +} // Ogre +} // Assimp -}//namespace Ogre -}//namespace Assimp - -#endif // !! ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER diff --git a/code/OgreParsingUtils.h b/code/OgreParsingUtils.h index a6a8faa8e..40ee35d4c 100644 --- a/code/OgreParsingUtils.h +++ b/code/OgreParsingUtils.h @@ -118,10 +118,25 @@ static inline std::string ToLower(std::string s) return s; } -// ------------------------------------------------------------------------------------------------ -// From http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring +/// Returns if @c s ends with @c suffix. If @c caseSensitive is false, both strings will be lower cased before matching. +static inline bool EndsWith(const std::string &s, const std::string &suffix, bool caseSensitive = true) +{ + if (s.empty() || suffix.empty()) + return false; + else if (s.length() < suffix.length()) + return false; -// trim from start + if (!caseSensitive) + return EndsWith(ToLower(s), ToLower(suffix), true); + + size_t len = suffix.length(); + std::string sSuffix = s.substr(s.length()-len, len); + return (ASSIMP_stricmp(sSuffix, suffix) == 0); +} + +// Below trim functions adapted from http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring + +/// Trim from start static inline std::string &TrimLeft(std::string &s, bool newlines = true) { if (!newlines) @@ -131,7 +146,7 @@ static inline std::string &TrimLeft(std::string &s, bool newlines = true) return s; } -// trim from end +/// Trim from end static inline std::string &TrimRight(std::string &s, bool newlines = true) { if (!newlines) @@ -140,7 +155,8 @@ static inline std::string &TrimRight(std::string &s, bool newlines = true) s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(Assimp::IsSpaceOrNewLine)))); return s; } -// trim from both ends + +/// Trim from both ends static inline std::string &Trim(std::string &s, bool newlines = true) { return TrimLeft(TrimRight(s, newlines), newlines); diff --git a/code/OgreSkeleton.cpp b/code/OgreSkeleton.cpp index fe247afe5..b46bbcadb 100644 --- a/code/OgreSkeleton.cpp +++ b/code/OgreSkeleton.cpp @@ -55,226 +55,205 @@ namespace Ogre void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOHandler, const aiScene *pScene, const std::string &skeletonFile, vector &Bones, vector &Animations) const { - //most likely the skeleton file will only end with .skeleton - //But this is a xml reader, so we need: .skeleton.xml - string skeletonPath = skeletonFile + ".xml"; + 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"; + } - DefaultLogger::get()->debug(string("Loading Skeleton: ")+skeletonFile); + if (!pIOHandler->Exists(filename)) + { + DefaultLogger::get()->error("Failed to find skeleton file '" + filename + "', skeleton will be missing."); + return; + } - //Open the File: - boost::scoped_ptr File(pIOHandler->Open(skeletonFile)); - if(NULL==File.get()) - throw DeadlyImportError("Failed to open skeleton file "+skeletonFile+"."); + boost::scoped_ptr file(pIOHandler->Open(filename)); + if (!file.get()) + throw DeadlyImportError("Failed to open skeleton file " + filename); - //Read the Mesh File: - boost::scoped_ptr mIOWrapper(new CIrrXML_IOStreamReader(File.get())); - XmlReader* SkeletonFile = irr::io::createIrrXMLReader(mIOWrapper.get()); - if(!SkeletonFile) - throw DeadlyImportError(string("Failed to create XML Reader for ")+skeletonFile); + 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); - NextNode(SkeletonFile); - if(string("skeleton")!=SkeletonFile->getNodeName()) - throw DeadlyImportError("No node in SkeletonFile: "+skeletonFile); + 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); - //------------------------------------load bones----------------------------------------- - NextNode(SkeletonFile); - if(string("bones")!=SkeletonFile->getNodeName()) - throw DeadlyImportError("No bones node in skeleton "+skeletonFile); - - NextNode(SkeletonFile); - - while(string("bone")==SkeletonFile->getNodeName()) + NextNode(reader); + while(CurrentNodeNameEquals(reader, "bone")) { //TODO: Maybe we can have bone ids for the errrors, but normaly, they should never appear, so what.... + /// @todo What does the above mean? - //read a new bone: - Bone NewBone; - NewBone.Id=GetAttribute(SkeletonFile, "id"); - NewBone.Name=GetAttribute(SkeletonFile, "name"); + Bone bone; + bone.Id = GetAttribute(reader, "id"); + bone.Name = GetAttribute(reader, "name"); - //load the position: - NextNode(SkeletonFile); - if(string("position")!=SkeletonFile->getNodeName()) + NextNode(reader); + if (!CurrentNodeNameEquals(reader, "position")) throw DeadlyImportError("Position is not first node in Bone!"); - NewBone.Position.x=GetAttribute(SkeletonFile, "x"); - NewBone.Position.y=GetAttribute(SkeletonFile, "y"); - NewBone.Position.z=GetAttribute(SkeletonFile, "z"); - //Rotation: - NextNode(SkeletonFile); - if(string("rotation")!=SkeletonFile->getNodeName()) + 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!"); - NewBone.RotationAngle=GetAttribute(SkeletonFile, "angle"); - NextNode(SkeletonFile); - if(string("axis")!=SkeletonFile->getNodeName()) + + bone.RotationAngle = GetAttribute(reader, "angle"); + + NextNode(reader); + if (!CurrentNodeNameEquals(reader, "axis")) throw DeadlyImportError("No axis specified for bone rotation!"); - NewBone.RotationAxis.x=GetAttribute(SkeletonFile, "x"); - NewBone.RotationAxis.y=GetAttribute(SkeletonFile, "y"); - NewBone.RotationAxis.z=GetAttribute(SkeletonFile, "z"); + + bone.RotationAxis.x = GetAttribute(reader, "x"); + bone.RotationAxis.y = GetAttribute(reader, "y"); + bone.RotationAxis.z = GetAttribute(reader, "z"); - //append the newly loaded bone to the bone list - Bones.push_back(NewBone); + Bones.push_back(bone); - //Proceed to the next bone: - NextNode(SkeletonFile); + NextNode(reader); } - //The bones in the file a not neccesarly ordered by there id's so we do it now: + + // Order bones by Id std::sort(Bones.begin(), Bones.end()); - //now the id of each bone should be equal to its position in the vector: - //so we do a simple check: + // 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")) { - bool IdsOk=true; - for(int i=0; i(Bones.size()); ++i)//i is signed, because all Id's are also signed! + 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()) { - if(Bones[i].Id!=i) - IdsOk=false; + iterChild->ParentId = iterParent->Id; + iterParent->Children.push_back(iterChild->Id); } - if(!IdsOk) - throw DeadlyImportError("Bone Ids are not valid!"+skeletonFile); + else + DefaultLogger::get()->warn("Failed to find bones for parenting: Child " + childName + " Parent " + parentName); + + NextNode(reader); } - DefaultLogger::get()->debug((Formatter::format(),"Number of bones: ",Bones.size())); - //________________________________________________________________________________ - - - - - - //----------------------------load bonehierarchy-------------------------------- - if(string("bonehierarchy")!=SkeletonFile->getNodeName()) - throw DeadlyImportError("no bonehierarchy node in "+skeletonFile); - - DefaultLogger::get()->debug("loading bonehierarchy..."); - NextNode(SkeletonFile); - while(string("boneparent")==SkeletonFile->getNodeName()) - { - string Child, Parent; - Child=GetAttribute(SkeletonFile, "bone"); - Parent=GetAttribute(SkeletonFile, "parent"); - - unsigned int ChildId, ParentId; - ChildId=find(Bones.begin(), Bones.end(), Child)->Id; - ParentId=find(Bones.begin(), Bones.end(), Parent)->Id; - - Bones[ChildId].ParentId=ParentId; - Bones[ParentId].Children.push_back(ChildId); - - NextNode(SkeletonFile); - } - //_____________________________________________________________________________ - - - //--------- Calculate the WorldToBoneSpace Matrix recursively for all bones: ------------------ + // Calculate bone matrices for root bones. Recursively does their children. BOOST_FOREACH(Bone &theBone, Bones) { - if(-1==theBone.ParentId) //the bone is a root bone - { + if (!theBone.IsParented()) theBone.CalculateBoneToWorldSpaceMatrix(Bones); - } } - //_______________________________________________________________________ + aiVector3D zeroVec(0.f, 0.f, 0.f); - //---------------------------load animations----------------------------- - if(string("animations")==SkeletonFile->getNodeName())//animations are optional values + // Animations + if (CurrentNodeNameEquals(reader, "animations")) { - DefaultLogger::get()->debug("Loading Animations"); - NextNode(SkeletonFile); - while(string("animation")==SkeletonFile->getNodeName()) + DefaultLogger::get()->debug(" - Animations"); + + NextNode(reader); + while(CurrentNodeNameEquals(reader, "animation")) { - Animation NewAnimation; - NewAnimation.Name=GetAttribute(SkeletonFile, "name"); - NewAnimation.Length=GetAttribute(SkeletonFile, "length"); + Animation animation; + animation.Name = GetAttribute(reader, "name"); + animation.Length = GetAttribute(reader, "length"); - //Load all Tracks - NextNode(SkeletonFile); - if(string("tracks")!=SkeletonFile->getNodeName()) - throw DeadlyImportError("no tracks node in animation"); - NextNode(SkeletonFile); - while(string("track")==SkeletonFile->getNodeName()) + // 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 NewTrack; - NewTrack.BoneName=GetAttribute(SkeletonFile, "bone"); + Track track; + track.BoneName = GetAttribute(reader, "bone"); - //Load all keyframes; - NextNode(SkeletonFile); - if(string("keyframes")!=SkeletonFile->getNodeName()) - throw DeadlyImportError("no keyframes node!"); - NextNode(SkeletonFile); - while(string("keyframe")==SkeletonFile->getNodeName()) + // 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 NewKeyframe; - NewKeyframe.Time=GetAttribute(SkeletonFile, "time"); - - //loop over the attributes: - - while(true) //will quit, if a Node is not a animationkey + KeyFrame keyFrame; + keyFrame.Time = GetAttribute(reader, "time"); + + NextNode(reader); + while(CurrentNodeNameEquals(reader, "translate") || CurrentNodeNameEquals(reader, "rotate") || CurrentNodeNameEquals(reader, "scale")) { - NextNode(SkeletonFile); - - //If any property doesn't show up, it will keep its initialization value - - //Position: - if(string("translate")==SkeletonFile->getNodeName()) + if (CurrentNodeNameEquals(reader, "translate")) { - NewKeyframe.Position.x=GetAttribute(SkeletonFile, "x"); - NewKeyframe.Position.y=GetAttribute(SkeletonFile, "y"); - NewKeyframe.Position.z=GetAttribute(SkeletonFile, "z"); + keyFrame.Position.x = GetAttribute(reader, "x"); + keyFrame.Position.y = GetAttribute(reader, "y"); + keyFrame.Position.z = GetAttribute(reader, "z"); } - - //Rotation: - else if(string("rotate")==SkeletonFile->getNodeName()) + else if (CurrentNodeNameEquals(reader, "rotate")) { - float RotationAngle=GetAttribute(SkeletonFile, "angle"); - aiVector3D RotationAxis; - NextNode(SkeletonFile); - if(string("axis")!=SkeletonFile->getNodeName()) + float angle = GetAttribute(reader, "angle"); + + NextNode(reader); + if(string("axis")!=reader->getNodeName()) throw DeadlyImportError("No axis for keyframe rotation!"); - RotationAxis.x=GetAttribute(SkeletonFile, "x"); - RotationAxis.y=GetAttribute(SkeletonFile, "y"); - RotationAxis.z=GetAttribute(SkeletonFile, "z"); + + aiVector3D axis; + axis.x = GetAttribute(reader, "x"); + axis.y = GetAttribute(reader, "y"); + axis.z = GetAttribute(reader, "z"); - if(0==RotationAxis.x && 0==RotationAxis.y && 0==RotationAxis.z)//we have an invalid rotation axis + if (axis.Equal(zeroVec)) { - RotationAxis.x=1.0f; - if(0!=RotationAngle)//if we don't rotate at all, the axis does not matter - { - DefaultLogger::get()->warn("Invalid Rotation Axis in KeyFrame!"); - } + axis.x = 1.0f; + if (angle != 0) + DefaultLogger::get()->warn("Found invalid a key frame with a zero rotation axis in animation '" + animation.Name + "'"); } - NewKeyframe.Rotation=aiQuaternion(RotationAxis, RotationAngle); + keyFrame.Rotation = aiQuaternion(axis, angle); } - - //Scaling: - else if(string("scale")==SkeletonFile->getNodeName()) + else if (CurrentNodeNameEquals(reader, "scale")) { - NewKeyframe.Scaling.x=GetAttribute(SkeletonFile, "x"); - NewKeyframe.Scaling.y=GetAttribute(SkeletonFile, "y"); - NewKeyframe.Scaling.z=GetAttribute(SkeletonFile, "z"); - } - - //we suppose, that we read all attributes and this is a new keyframe or the end of the animation - else - break; + keyFrame.Scaling.x = GetAttribute(reader, "x"); + keyFrame.Scaling.y = GetAttribute(reader, "y"); + keyFrame.Scaling.z = GetAttribute(reader, "z"); + } + NextNode(reader); } - - NewTrack.Keyframes.push_back(NewKeyframe); + track.Keyframes.push_back(keyFrame); } - - NewAnimation.Tracks.push_back(NewTrack); + animation.Tracks.push_back(track); } - - Animations.push_back(NewAnimation); + 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(!pScene->mRootNode) @@ -414,31 +393,22 @@ aiNode* OgreImporter::CreateAiNodeFromBone(int BoneId, const std::vector & void Bone::CalculateBoneToWorldSpaceMatrix(vector &Bones) { - //Calculate the matrix for this bone: + aiMatrix4x4 t0, t1; + aiMatrix4x4 transform = aiMatrix4x4::Rotation(-RotationAngle, RotationAxis, t1) * aiMatrix4x4::Translation(-Position, t0); - aiMatrix4x4 t0,t1; - aiMatrix4x4 Transf= aiMatrix4x4::Rotation(-RotationAngle, RotationAxis, t1) - * aiMatrix4x4::Translation(-Position, t0); - - if(-1==ParentId) - { - BoneToWorldSpace=Transf; - } + if (!IsParented()) + BoneToWorldSpace = transform; else - { - BoneToWorldSpace=Transf*Bones[ParentId].BoneToWorldSpace; - } - + BoneToWorldSpace = transform * Bones[ParentId].BoneToWorldSpace; - //and recursivly for all children: - BOOST_FOREACH(int theChildren, Children) + // Recursively for all children now that the parent matrix has been calculated. + BOOST_FOREACH(int childId, Children) { - Bones[theChildren].CalculateBoneToWorldSpaceMatrix(Bones); + Bones[childId].CalculateBoneToWorldSpaceMatrix(Bones); } } +} // Ogre +} // Assimp -}//namespace Ogre -}//namespace Assimp - -#endif // !! ASSIMP_BUILD_NO_OGRE_IMPORTER +#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER From 283394d695952dedee8995ebc3ae840fc4998464 Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Fri, 2 May 2014 00:19:57 +0300 Subject: [PATCH 08/12] OgreImporter: Cleanup skeleton related code and code that populates the assimp scene. Tested skeleton/animation imports and it seems to work correctly now. --- code/OgreImporter.cpp | 69 ++++++------- code/OgreImporter.h | 19 ++-- code/OgreMesh.cpp | 151 ++++++++++++++-------------- code/OgreSkeleton.cpp | 215 ++++++++++++++++++++-------------------- include/assimp/config.h | 2 +- 5 files changed, 225 insertions(+), 231 deletions(-) diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index 697c47a62..e11c5b0f1 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -69,6 +69,18 @@ namespace Assimp namespace Ogre { +const aiImporterDesc* OgreImporter::GetInfo() const +{ + return &desc; +} + + +void OgreImporter::SetupProperties(const Importer* pImp) +{ + m_userDefinedMaterialLibFile = pImp->GetPropertyString(AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE, "Scene.material"); + m_detectTextureTypeFromFilename = pImp->GetPropertyBool(AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME, false); +} + bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool checkSig) const { if (!checkSig) @@ -127,7 +139,7 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass if (!CurrentNodeNameEquals(reader, nnSubMeshes)) throw DeadlyImportError("Could not find node inside root node"); - list > subMeshes; + vector > subMeshes; vector materials; NextNode(reader.get()); @@ -195,48 +207,33 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass // -------------------- Apply to aiScene -------------------- - //put the aiMaterials in the scene: - pScene->mMaterials=new aiMaterial*[materials.size()]; - pScene->mNumMaterials=materials.size(); - for(unsigned int i=0; imMaterials[i]=materials[i]; + // Materials + pScene->mMaterials = new aiMaterial*[materials.size()]; + pScene->mNumMaterials = materials.size(); - //create the aiMehs... - vector aiMeshes; - BOOST_FOREACH(boost::shared_ptr theSubMesh, subMeshes) + for(size_t i=0, len=materials.size(); imMaterials[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); } - //... and put them in the scene: - pScene->mNumMeshes=aiMeshes.size(); - pScene->mMeshes=new aiMesh*[aiMeshes.size()]; - memcpy(pScene->mMeshes, &(aiMeshes[0]), sizeof(aiMeshes[0])*aiMeshes.size()); - //Create the root node - pScene->mRootNode=new aiNode("root"); - - //link the meshs with the root node: - pScene->mRootNode->mMeshes=new unsigned int[subMeshes.size()]; - pScene->mRootNode->mNumMeshes=subMeshes.size(); + // Create the root node + pScene->mRootNode = new aiNode(); + pScene->mRootNode->mMeshes = new unsigned int[subMeshes.size()]; + pScene->mRootNode->mNumMeshes = subMeshes.size(); - for(unsigned int i=0; imRootNode->mMeshes[i]=i; + for(size_t i=0, len=subMeshes.size(); imRootNode->mMeshes[i] = static_cast(i); + // Skeleton and animations CreateAssimpSkeleton(pScene, Bones, Animations); - PutAnimationsInScene(pScene, Bones, Animations); -} - - -const aiImporterDesc* OgreImporter::GetInfo () const -{ - return &desc; -} - - -void OgreImporter::SetupProperties(const Importer* pImp) -{ - m_userDefinedMaterialLibFile = pImp->GetPropertyString(AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE, "Scene.material"); - m_detectTextureTypeFromFilename = pImp->GetPropertyBool(AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME, false); } } // Ogre diff --git a/code/OgreImporter.h b/code/OgreImporter.h index 240de85fc..b4c958a65 100644 --- a/code/OgreImporter.h +++ b/code/OgreImporter.h @@ -71,16 +71,16 @@ class OgreImporter : public BaseImporter { public: /// BaseImporter override. - virtual bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; + virtual bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const; /// BaseImporter override. - virtual void InternReadFile(const std::string &pFile, aiScene* pScene, IOSystem* pIOHandler); + virtual void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler); /// BaseImporter override. - virtual const aiImporterDesc* GetInfo () const; + virtual const aiImporterDesc *GetInfo() const; /// BaseImporter override. - virtual void SetupProperties(const Importer* pImp); + virtual void SetupProperties(const Importer *pImp); private: //-------------------------------- OgreMesh.cpp ------------------------------- @@ -98,7 +98,7 @@ private: 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; + aiMesh *CreateAssimpSubMesh(aiScene *pScene, const SubMesh &submesh, const std::vector &bones) const; //-------------------------------- OgreSkeleton.cpp ------------------------------- @@ -110,10 +110,10 @@ private: 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); + void CreateAssimpSkeleton(aiScene *pScene, const std::vector &bones, const std::vector &animations); - /// Recursivly creates a filled aiNode from a given root bone. - static aiNode* CreateAiNodeFromBone(int BoneId, const std::vector &Bones, aiNode* ParentNode); + /// Recursively creates a filled aiNode from a given root bone. + static aiNode* CreateNodeFromBone(int boneId, const std::vector &bones, aiNode *parent); //-------------------------------- OgreMaterial.cpp ------------------------------- @@ -131,7 +131,8 @@ private: std::string m_userDefinedMaterialLibFile; bool m_detectTextureTypeFromFilename; - SubMesh m_SharedGeometry;///< we will just use the vertexbuffers of the submesh + /// VertexBuffer for the sub meshes that use shader geometry. + SubMesh m_SharedGeometry; std::map m_textures; }; diff --git a/code/OgreMesh.cpp b/code/OgreMesh.cpp index 06361ca87..4fa29f9ff 100644 --- a/code/OgreMesh.cpp +++ b/code/OgreMesh.cpp @@ -442,110 +442,103 @@ void OgreImporter::ProcessSubMesh(SubMesh &submesh, SubMesh &sharedGeometry) //_________________________________________________________ } -aiMesh* OgreImporter::CreateAssimpSubMesh(aiScene *pScene, const SubMesh& submesh, const vector& bones) const +aiMesh *OgreImporter::CreateAssimpSubMesh(aiScene *pScene, const SubMesh& submesh, const vector& bones) const { - aiMesh* NewAiMesh = new aiMesh(); - - //Positions - NewAiMesh->mVertices=new aiVector3D[submesh.Positions.size()]; - memcpy(NewAiMesh->mVertices, &submesh.Positions[0], submesh.Positions.size()*sizeof(aiVector3D)); - NewAiMesh->mNumVertices=submesh.Positions.size(); + const size_t sizeVector3D = sizeof(aiVector3D); - //Normals - if(submesh.HasNormals) + aiMesh *dest = new aiMesh(); + + // Material + 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) { - NewAiMesh->mNormals=new aiVector3D[submesh.Normals.size()]; - memcpy(NewAiMesh->mNormals, &submesh.Normals[0], submesh.Normals.size()*sizeof(aiVector3D)); + 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); } - - //until we have support for bitangents, no tangents will be written - /* - //Tangents - if(submesh.HasTangents) + // UVs + for (size_t i=0, len=submesh.Uvs.size(); imTangents=new aiVector3D[submesh.Tangents.size()]; - memcpy(NewAiMesh->mTangents, &submesh.Tangents[0], submesh.Tangents.size()*sizeof(aiVector3D)); + dest->mNumUVComponents[i] = 2; + dest->mTextureCoords[i] = new aiVector3D[submesh.Uvs[i].size()]; + memcpy(dest->mTextureCoords[i], &(submesh.Uvs[i][0]), submesh.Uvs[i].size() * sizeVector3D); } - */ - //Uvs - if(submesh.Uvs.size()>0) + // 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(); boneIdmNumUVComponents[i]=2; - NewAiMesh->mTextureCoords[i]=new aiVector3D[submesh.Uvs[i].size()]; - memcpy(NewAiMesh->mTextureCoords[i], &(submesh.Uvs[i][0]), submesh.Uvs[i].size()*sizeof(aiVector3D)); + const BoneWeight &ogreWeight = vertexWeights[boneId]; + assimpWeights[ogreWeight.Id].push_back(aiVertexWeight(vertexId, ogreWeight.Value)); } } + // Bones. + vector assimpBones; + assimpBones.reserve(submesh.BonesUsed); - //---------------------------------------- bones -------------------------------------------- - - //Copy the weights in in Bone-Vertices Struktur - //(we have them in a Vertex-bones Structur, this is much easier for making them unique, which is required by assimp - vector< vector > aiWeights(submesh.BonesUsed);//now the outer list are the bones, and the inner vector the vertices - for(unsigned int VertexId=0; VertexId &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); } - - - vector aiBones; - aiBones.reserve(submesh.BonesUsed);//the vector might be smaller, because there might be empty bones (bones that are not attached to any vertex) - - //create all the bones and fill them with informations - for(unsigned int i=0; i0) - { - aiBone* NewBone=new aiBone(); - NewBone->mNumWeights=aiWeights[i].size(); - NewBone->mWeights=new aiVertexWeight[aiWeights[i].size()]; - memcpy(NewBone->mWeights, &(aiWeights[i][0]), sizeof(aiVertexWeight)*aiWeights[i].size()); - NewBone->mName=bones[i].Name;//The bone list should be sorted after its id's, this was done in LoadSkeleton - NewBone->mOffsetMatrix=bones[i].BoneToWorldSpace; - - aiBones.push_back(NewBone); - } - } - NewAiMesh->mNumBones=aiBones.size(); - - // mBones must be NULL if mNumBones is non 0 or the validation fails. - if (aiBones.size()) { - NewAiMesh->mBones=new aiBone* [aiBones.size()]; - memcpy(NewAiMesh->mBones, &(aiBones[0]), aiBones.size()*sizeof(aiBone*)); + 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(); - - - //Faces - NewAiMesh->mFaces=new aiFace[submesh.Faces.size()]; - for(unsigned int i=0; imFaces[i].mNumIndices=3; - NewAiMesh->mFaces[i].mIndices=new unsigned int[3]; + dest->mFaces[i].mNumIndices = 3; + dest->mFaces[i].mIndices = new unsigned int[3]; - NewAiMesh->mFaces[i].mIndices[0]=submesh.Faces[i].VertexIndices[0]; - NewAiMesh->mFaces[i].mIndices[1]=submesh.Faces[i].VertexIndices[1]; - NewAiMesh->mFaces[i].mIndices[2]=submesh.Faces[i].VertexIndices[2]; + 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]; } - NewAiMesh->mNumFaces=submesh.Faces.size(); - //Link the material: - NewAiMesh->mMaterialIndex=submesh.MaterialIndex;//the index is set by the function who called ReadSubMesh - - return NewAiMesh; + return dest; } } // Ogre diff --git a/code/OgreSkeleton.cpp b/code/OgreSkeleton.cpp index b46bbcadb..ea414604a 100644 --- a/code/OgreSkeleton.cpp +++ b/code/OgreSkeleton.cpp @@ -5,11 +5,11 @@ 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, +Redistribution and use of this software in aSource and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above +* Redistributions of aSource code must retain the above copyright notice, this list of conditions and the following disclaimer. @@ -254,143 +254,146 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH } } -void OgreImporter::CreateAssimpSkeleton(aiScene *pScene, const std::vector &Bones, const std::vector &/*Animations*/) +void OgreImporter::CreateAssimpSkeleton(aiScene *pScene, const std::vector &bones, const std::vector &animations) { - if(!pScene->mRootNode) - throw DeadlyImportError("No root node exists!!"); - if(0!=pScene->mRootNode->mNumChildren) - throw DeadlyImportError("Root Node already has childnodes!"); + 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!"); - //Createt the assimp bone hierarchy - vector RootBoneNodes; - BOOST_FOREACH(const Bone &theBone, Bones) + // Bones + vector rootBones; + BOOST_FOREACH(const Bone &bone, bones) { - if(-1==theBone.ParentId) //the bone is a root bone - { - //which will recursily add all other nodes - RootBoneNodes.push_back(CreateAiNodeFromBone(theBone.Id, Bones, pScene->mRootNode)); - } + if (!bone.IsParented()) + rootBones.push_back(CreateNodeFromBone(bone.Id, bones, pScene->mRootNode)); } - if(RootBoneNodes.size() > 0) + if (!rootBones.empty()) { - pScene->mRootNode->mNumChildren=RootBoneNodes.size(); - pScene->mRootNode->mChildren=new aiNode*[RootBoneNodes.size()]; - memcpy(pScene->mRootNode->mChildren, &RootBoneNodes[0], sizeof(aiNode*)*RootBoneNodes.size()); - } -} + 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]; + } -void OgreImporter::PutAnimationsInScene(aiScene *pScene, const std::vector &Bones, const std::vector &Animations) -{ // TODO: Auf nicht vorhandene Animationskeys achten! // @todo Pay attention to non-existing animation Keys (google translated from above german comment) - - if(Animations.size()>0)//Maybe the model had only a skeleton and no animations. (If it also has no skeleton, this function would'nt have been called + + // Animations + if (!animations.empty()) { - pScene->mNumAnimations=Animations.size(); - pScene->mAnimations=new aiAnimation*[Animations.size()]; - for(unsigned int i=0; imAnimations = new aiAnimation*[animations.size()]; + pScene->mNumAnimations = animations.size(); + + for(size_t ai=0, alen=animations.size(); aimName=Animations[i].Name; - NewAnimation->mDuration=Animations[i].Length; - NewAnimation->mTicksPerSecond=1.0f; + const Animation &aSource = animations[ai]; - //Create all tracks in this animation - NewAnimation->mNumChannels=Animations[i].Tracks.size(); - NewAnimation->mChannels=new aiNodeAnim*[Animations[i].Tracks.size()]; - for(unsigned int j=0; jmName = 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=Animations[i].Tracks[j].BoneName; + const Track &tSource = aSource.Tracks[ti]; - //we need this, to acces the bones default pose, which we need to make keys absolute to the default bone pose - vector::const_iterator CurBone=find(Bones.begin(), Bones.end(), NewNodeAnim->mNodeName); - aiMatrix4x4 t0, t1; - aiMatrix4x4 DefBonePose=aiMatrix4x4::Translation(CurBone->Position, t1) - * aiMatrix4x4::Rotation(CurBone->RotationAngle, CurBone->RotationAxis, t0); - + aiNodeAnim *animationNode = new aiNodeAnim(); + animationNode->mNodeName = tSource.BoneName; - //Create the keyframe arrays... - unsigned int KeyframeCount=Animations[i].Tracks[j].Keyframes.size(); - NewNodeAnim->mNumPositionKeys=KeyframeCount; - NewNodeAnim->mNumRotationKeys=KeyframeCount; - NewNodeAnim->mNumScalingKeys =KeyframeCount; - NewNodeAnim->mPositionKeys=new aiVectorKey[KeyframeCount]; - NewNodeAnim->mRotationKeys=new aiQuatKey[KeyframeCount]; - NewNodeAnim->mScalingKeys =new aiVectorKey[KeyframeCount]; - - //...and fill them - for(unsigned int k=0; k::const_iterator boneIter = find(bones.begin(), bones.end(), tSource.BoneName); + if (boneIter == bones.end()) { - aiMatrix4x4 t2, t3; - - //Create a matrix to transfrom a vector from the bones default pose to the bone bones in this animation key - aiMatrix4x4 PoseToKey= - aiMatrix4x4::Translation(Animations[i].Tracks[j].Keyframes[k].Position, t3) //pos - * aiMatrix4x4(Animations[i].Tracks[j].Keyframes[k].Rotation.GetMatrix()) //rot - * aiMatrix4x4::Scaling(Animations[i].Tracks[j].Keyframes[k].Scaling, t2); //scale - - - //calculate the complete transformation from world space to bone space - aiMatrix4x4 CompleteTransform=DefBonePose * PoseToKey; + for(unsigned int a=0; amAnimations[a]; + delete [] pScene->mAnimations; + pScene->mAnimations = NULL; + pScene->mNumAnimations = 0; - aiVector3D Pos; - aiQuaternion Rot; - aiVector3D Scale; - - CompleteTransform.Decompose(Scale, Rot, Pos); - - double Time=Animations[i].Tracks[j].Keyframes[k].Time; - - NewNodeAnim->mPositionKeys[k].mTime=Time; - NewNodeAnim->mPositionKeys[k].mValue=Pos; - - NewNodeAnim->mRotationKeys[k].mTime=Time; - NewNodeAnim->mRotationKeys[k].mValue=Rot; - - NewNodeAnim->mScalingKeys[k].mTime=Time; - NewNodeAnim->mScalingKeys[k].mValue=Scale; + 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; } - - NewAnimation->mChannels[j]=NewNodeAnim; - } - pScene->mAnimations[i]=NewAnimation; + 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::CreateAiNodeFromBone(int BoneId, const std::vector &Bones, aiNode* ParentNode) +aiNode* OgreImporter::CreateNodeFromBone(int boneId, const std::vector &bones, aiNode* parent) { - //----Create the node for this bone and set its values----- - aiNode* NewNode=new aiNode(Bones[BoneId].Name); - NewNode->mParent=ParentNode; - aiMatrix4x4 t0,t1; - NewNode->mTransformation= - aiMatrix4x4::Translation(Bones[BoneId].Position, t0) - *aiMatrix4x4::Rotation(Bones[BoneId].RotationAngle, Bones[BoneId].RotationAxis, 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); - //---------- recursivly create all children Nodes: ---------- - NewNode->mNumChildren=Bones[BoneId].Children.size(); - NewNode->mChildren=new aiNode*[Bones[BoneId].Children.size()]; - for(unsigned int i=0; imChildren[i]=CreateAiNodeFromBone(Bones[BoneId].Children[i], Bones, NewNode); + 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 NewNode; + return boneNode; } - void Bone::CalculateBoneToWorldSpaceMatrix(vector &Bones) { aiMatrix4x4 t0, t1; diff --git a/include/assimp/config.h b/include/assimp/config.h index 96df293b8..6bd31aada 100644 --- a/include/assimp/config.h +++ b/include/assimp/config.h @@ -809,7 +809,7 @@ enum aiComponent * order: .material, .material and * lastly the material name defined by this config property. *
- * Property type: String. Default value: n/a. + * Property type: String. Default value: Scene.material. */ #define AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE \ "IMPORT_OGRE_MATERIAL_FILE" From 409c2cf3329cb71929c85a6c6230b7b79ad65512 Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Fri, 2 May 2014 00:56:35 +0300 Subject: [PATCH 09/12] OgreImporter: One more function cleanup. --- code/OgreImporter.cpp | 4 +- code/OgreMesh.cpp | 161 +++++++++++++++++++----------------------- 2 files changed, 75 insertions(+), 90 deletions(-) diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index e11c5b0f1..7886a36d9 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -200,9 +200,9 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass ReadBoneWeights(m_SharedGeometry, reader.get()); // -------------------- Process Results -------------------- - BOOST_FOREACH(boost::shared_ptr theSubMesh, subMeshes) + BOOST_FOREACH(boost::shared_ptr submesh, subMeshes) { - ProcessSubMesh(*theSubMesh, m_SharedGeometry); + ProcessSubMesh(*submesh.get(), m_SharedGeometry); } // -------------------- Apply to aiScene -------------------- diff --git a/code/OgreMesh.cpp b/code/OgreMesh.cpp index 4fa29f9ff..0ce01f4d4 100644 --- a/code/OgreMesh.cpp +++ b/code/OgreMesh.cpp @@ -324,122 +324,107 @@ void OgreImporter::ReadBoneWeights(SubMesh &submesh, XmlReader *reader) void OgreImporter::ProcessSubMesh(SubMesh &submesh, SubMesh &sharedGeometry) { - //---------------Make all Vertexes unique: (this is required by assimp)----------------------- - vector UniqueFaceList(submesh.Faces.size()); - unsigned int UniqueVertexCount=submesh.Faces.size()*3;//*3 because each face consists of 3 vertexes, because we only support triangles^^ + // Make all vertexes unique. Required by Assimp. + vector uniqueFaceList(submesh.Faces.size()); + unsigned int uniqueVertexCount = submesh.Faces.size() * 3; - vector UniquePositions(UniqueVertexCount); + vector uniquePositions(uniqueVertexCount); + vector uniqueNormals(uniqueVertexCount); + vector uniqueTangents(uniqueVertexCount); - vector UniqueNormals(UniqueVertexCount); + vector > uniqueWeights(uniqueVertexCount); + vector > uniqueUvs(submesh.UseSharedGeometry ? sharedGeometry.Uvs.size() : submesh.Uvs.size()); - vector UniqueTangents(UniqueVertexCount); + for(size_t uvi=0; uvi > UniqueWeights(UniqueVertexCount); - - vector< vector > UniqueUvs(submesh.Uvs.size()); - for(unsigned int i=0; i0) + 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.size() > 0) + if (!vertexSource.Weights.empty()) { - UniqueWeights[3*i+0]=VertexSource.Weights[Vertex1]; - UniqueWeights[3*i+1]=VertexSource.Weights[Vertex2]; - UniqueWeights[3*i+2]=VertexSource.Weights[Vertex3]; + uniqueWeights[pos] = vertexSource.Weights[v1]; + uniqueWeights[pos+1] = vertexSource.Weights[v2]; + uniqueWeights[pos+2] = vertexSource.Weights[v3]; } - - //The indexvalues a just continuous numbers (0, 1, 2, 3, 4, 5, 6...) - UniqueFaceList[i].VertexIndices[0]=3*i+0; - UniqueFaceList[i].VertexIndices[1]=3*i+1; - UniqueFaceList[i].VertexIndices[2]=3*i+2; } - //_________________________________________________________________________________________ - //now we have the unique datas, 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); + // 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 weights ----------------------------- - //The Blender exporter doesn't care about whether the sum of all boneweights for a single vertex equals 1 or not, - //so we have to make this sure: - for(unsigned int VertexId=0; VertexId &weights = submesh.Weights[vertexId]; + + float sum = 0.0f; + for(size_t boneId=0, blen=weights.size(); boneId1.0f+0.05f) - { - //normalize all weights: - for(unsigned int BoneId=0; BoneId (1.0f + 0.05f))) + for(size_t boneId=0, blen=weights.size(); boneId& bones) const From dcf6002bed89939fe0836433169104f603ee814c Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Fri, 2 May 2014 01:38:13 +0300 Subject: [PATCH 10/12] Try fix travis ci produced build errors on gcc and clang. --- code/OgreImporter.h | 3 --- code/OgreMaterial.cpp | 13 ++++++------- code/OgreParsingUtils.h | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/code/OgreImporter.h b/code/OgreImporter.h index b4c958a65..1a16aa860 100644 --- a/code/OgreImporter.h +++ b/code/OgreImporter.h @@ -5,11 +5,8 @@ #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER #include "BaseImporter.h" -#include "irrXMLWrapper.h" #include "OgreParsingUtils.h" -#include - namespace Assimp { namespace Ogre diff --git a/code/OgreMaterial.cpp b/code/OgreMaterial.cpp index 506b0fde9..5ce162ed9 100644 --- a/code/OgreMaterial.cpp +++ b/code/OgreMaterial.cpp @@ -46,7 +46,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "OgreImporter.h" -#include "irrXMLWrapper.h" #include "TinyFormatter.h" using namespace std; @@ -195,8 +194,8 @@ aiMaterial* OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSyste if (linePart == partTechnique) { - string techniqueName = Trim(SkipLine(ss)); - ReadTechnique(techniqueName, ss, material); + string techniqueName = SkipLine(ss); + ReadTechnique(Trim(techniqueName), ss, material); } // Read informations from a custom material @@ -315,8 +314,8 @@ bool OgreImporter::ReadTechnique(const std::string &techniqueName, stringstream /// @todo Techniques have other attributes than just passes. if (linePart == partPass) { - string passName = Trim(SkipLine(ss)); - ReadPass(passName, ss, material); + string passName = SkipLine(ss); + ReadPass(Trim(passName), ss, material); } } return true; @@ -374,8 +373,8 @@ bool OgreImporter::ReadPass(const std::string &passName, stringstream &ss, aiMat } else if (linePart == partTextureUnit) { - string textureUnitName = Trim(SkipLine(ss)); - ReadTextureUnit(textureUnitName, ss, material); + string textureUnitName = SkipLine(ss); + ReadTextureUnit(Trim(textureUnitName), ss, material); } } return true; diff --git a/code/OgreParsingUtils.h b/code/OgreParsingUtils.h index 40ee35d4c..9e5b83e82 100644 --- a/code/OgreParsingUtils.h +++ b/code/OgreParsingUtils.h @@ -33,7 +33,10 @@ inline int GetAttribute(const XmlReader* reader, const std::string &name) if (value) return atoi(value); else + { ThrowAttibuteError(reader, name); + return 0; + } } template<> @@ -43,7 +46,10 @@ inline unsigned int GetAttribute(const XmlReader* reader, const st if (value) return static_cast(atoi(value)); ///< @todo Find a better way... else + { ThrowAttibuteError(reader, name); + return 0; + } } template<> @@ -53,7 +59,10 @@ inline float GetAttribute(const XmlReader* reader, const std::string &nam if (value) return fast_atof(value); else + { ThrowAttibuteError(reader, name); + return 0.f; + } } template<> @@ -63,7 +72,10 @@ inline std::string GetAttribute(const XmlReader* reader, const std: if (value) return std::string(value); else + { ThrowAttibuteError(reader, name); + return ""; + } } template<> @@ -75,7 +87,10 @@ inline bool GetAttribute(const XmlReader* reader, const std::string &name) 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) From 22a4215c0679d53994ee837d08b27d1eab6fa69a Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Fri, 2 May 2014 01:54:42 +0300 Subject: [PATCH 11/12] OgreImporter: Remove unnecessary comments. --- code/OgreImporter.h | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/code/OgreImporter.h b/code/OgreImporter.h index 1a16aa860..e40809073 100644 --- a/code/OgreImporter.h +++ b/code/OgreImporter.h @@ -122,9 +122,6 @@ private: bool ReadPass(const std::string &passName, std::stringstream &ss, aiMaterial *material); bool ReadTextureUnit(const std::string &textureUnitName, std::stringstream &ss, aiMaterial *material); - // Now we don't have to give theses parameters to all functions - /// @todo Remove this m_Current* bookkeeping. - std::string m_userDefinedMaterialLibFile; bool m_detectTextureTypeFromFilename; @@ -160,9 +157,7 @@ struct BoneWeight }; -/// Helper Class to describe an ogre-bone for the skeleton: -/** All Id's are signed ints, because than we have -1 as a simple INVALID_ID Value (we start from 0 so 0 is a valid bone ID! - @todo Cleanup if possible. Seems like this is overly complex for what we are reading. */ +/// Ogre Bone struct Bone { std::string Name; @@ -228,8 +223,8 @@ struct Animation std::vector Tracks; }; -}//namespace Ogre -}//namespace Assimp +} // Ogre +} // Assimp #endif // ASSIMP_BUILD_NO_OGRE_IMPORTER #endif // AI_OGREIMPORTER_H_INC From 9ad74e461e3d9c43369ebe16b8192ecb87cbc95f Mon Sep 17 00:00:00 2001 From: Jonne Nauha Date: Sat, 3 May 2014 00:20:17 +0300 Subject: [PATCH 12/12] OgreImporter: Added brackets for all conditional etc. statements, even when there is a single line as requested by @kimkulling. I'm still not sure about the coding convention but looked for guidance in the obj importer code. Now newline before { if only one line and no else/else if after it, othewise a newline before it. --- code/OgreImporter.cpp | 43 ++++++++++++------ code/OgreMaterial.cpp | 96 +++++++++++++++++++++++++++-------------- code/OgreMesh.cpp | 71 ++++++++++++++++++++++-------- code/OgreParsingUtils.h | 34 ++++++++++++++- code/OgreSkeleton.cpp | 79 ++++++++++++++++++++++----------- 5 files changed, 233 insertions(+), 90 deletions(-) diff --git a/code/OgreImporter.cpp b/code/OgreImporter.cpp index 7886a36d9..65c68b7d6 100644 --- a/code/OgreImporter.cpp +++ b/code/OgreImporter.cpp @@ -74,7 +74,6 @@ const aiImporterDesc* OgreImporter::GetInfo() const return &desc; } - void OgreImporter::SetupProperties(const Importer* pImp) { m_userDefinedMaterialLibFile = pImp->GetPropertyString(AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE, "Scene.material"); @@ -83,8 +82,9 @@ void OgreImporter::SetupProperties(const Importer* pImp) bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool checkSig) const { - if (!checkSig) + if (!checkSig) { return EndsWith(pFile, ".mesh.xml", false); + } const char* tokens[] = { "" }; return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); @@ -96,21 +96,24 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass // Open boost::scoped_ptr file(pIOHandler->Open(pFile)); - if (file.get() == NULL) + if (!file.get()) { throw DeadlyImportError("Failed to open file " + pFile); + } // Read boost::scoped_ptr xmlStream(new CIrrXML_IOStreamReader(file.get())); boost::scoped_ptr reader(irr::io::createIrrXMLReader(xmlStream.get())); - if (!reader) + 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, "mesh")) + if (!CurrentNodeNameEquals(reader, "mesh")) { throw DeadlyImportError("Root node is not but <" + string(reader->getNodeName()) + "> in " + pFile); + } // Node names const string nnSharedGeometry = "sharedgeometry"; @@ -130,14 +133,16 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass unsigned int NumVertices = GetAttribute(reader.get(), "vertexcount"); NextNode(reader.get()); - while(CurrentNodeNameEquals(reader, nnVertexBuffer)) + while(CurrentNodeNameEquals(reader, nnVertexBuffer)) { ReadVertexBuffer(m_SharedGeometry, reader.get(), NumVertices); + } } // -------------------- Sub Meshes -------------------- - if (!CurrentNodeNameEquals(reader, nnSubMeshes)) + if (!CurrentNodeNameEquals(reader, nnSubMeshes)) { throw DeadlyImportError("Could not find node inside root node"); + } vector > subMeshes; vector materials; @@ -162,20 +167,23 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass materials.push_back(material); } - if (subMeshes.empty()) + 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()) + 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, nnSubMeshNames)) { NextNode(reader.get()); - while(CurrentNodeNameEquals(reader, nnSubMesh)) + while(CurrentNodeNameEquals(reader, nnSubMesh)) { NextNode(reader.get()); + } } // -------------------- Skeleton -------------------- @@ -187,17 +195,24 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass { 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()); } else + { DefaultLogger::get()->debug("Mesh has no assigned skeleton with <" + nnSkeletonLink + ">"); + } // Now there might be for the shared geometry - if (CurrentNodeNameEquals(reader, "boneassignments")) + if (CurrentNodeNameEquals(reader, "boneassignments")) { ReadBoneWeights(m_SharedGeometry, reader.get()); + } // -------------------- Process Results -------------------- BOOST_FOREACH(boost::shared_ptr submesh, subMeshes) @@ -211,8 +226,9 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass pScene->mMaterials = new aiMaterial*[materials.size()]; pScene->mNumMaterials = materials.size(); - for(size_t i=0, len=materials.size(); imMaterials[i] = materials[i]; + } // Meshes pScene->mMeshes = new aiMesh*[subMeshes.size()]; @@ -229,8 +245,9 @@ void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Ass 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); diff --git a/code/OgreMaterial.cpp b/code/OgreMaterial.cpp index 5ce162ed9..b284dbb31 100644 --- a/code/OgreMaterial.cpp +++ b/code/OgreMaterial.cpp @@ -62,8 +62,9 @@ static const string partBlockEnd = "}"; aiMaterial* OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string materialName) { /// @todo Should we return null ptr here or a empty material? - if (materialName.empty()) + if (materialName.empty()) { return new aiMaterial(); + } // Full reference and examples of Ogre Material Script // can be found from http://www.ogre3d.org/docs/manual/manual_14.html @@ -109,8 +110,9 @@ aiMaterial* OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSyste for(size_t i=0; iOpen(potentialFiles[i]); - if (materialFile) + if (materialFile) { break; + } DefaultLogger::get()->debug(Formatter::format() << "Source file for material '" << materialName << "' " << potentialFiles[i] << " does not exist"); } if (!materialFile) @@ -204,76 +206,74 @@ aiMaterial* OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSyste parent texture unit name in your cloned material. This is not yet supported and below code is probably some hack from the original author of this Ogre importer. Should be removed? */ - if(linePart=="set") + if (linePart=="set") { ss >> linePart; - if(linePart=="$specular")//todo load this values: + if (linePart=="$specular")//todo load this values: { } - if(linePart=="$diffuse") + else if (linePart=="$diffuse") { } - if(linePart=="$ambient") + else if (linePart=="$ambient") { } - if(linePart=="$colormap") + else if (linePart=="$colormap") { ss >> linePart; - aiString ts(linePart.c_str()); + aiString ts(linePart); material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); } - if(linePart=="$normalmap") + else if (linePart=="$normalmap") { ss >> linePart; - aiString ts(linePart.c_str()); + aiString ts(linePart); material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0)); } - - if(linePart=="$shininess_strength") + else if (linePart=="$shininess_strength") { ss >> linePart; - float Shininess=fast_atof(linePart.c_str()); + float Shininess = fast_atof(linePart.c_str()); material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS_STRENGTH); } - - if(linePart=="$shininess_exponent") + else if (linePart=="$shininess_exponent") { ss >> linePart; - float Shininess=fast_atof(linePart.c_str()); + float Shininess = fast_atof(linePart.c_str()); material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS); } - //Properties from Venetica: - if(linePart=="$diffuse_map") + else if (linePart=="$diffuse_map") { ss >> linePart; - if(linePart[0]=='"')// "file" -> file - linePart=linePart.substr(1, linePart.size()-2); - aiString ts(linePart.c_str()); + if (linePart[0] == '"')// "file" -> file + linePart = linePart.substr(1, linePart.size()-2); + aiString ts(linePart); material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0)); } - if(linePart=="$specular_map") + else if (linePart=="$specular_map") { ss >> linePart; - if(linePart[0]=='"')// "file" -> file - linePart=linePart.substr(1, linePart.size()-2); - aiString ts(linePart.c_str()); + if (linePart[0] == '"')// "file" -> file + linePart = linePart.substr(1, linePart.size()-2); + aiString ts(linePart); material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_SHININESS, 0)); } - if(linePart=="$normal_map") + else if (linePart=="$normal_map") { ss >> linePart; - if(linePart[0]=='"')// "file" -> file - linePart=linePart.substr(1, linePart.size()-2); - aiString ts(linePart.c_str()); + if (linePart[0]=='"')// "file" -> file + linePart = linePart.substr(1, linePart.size()-2); + aiString ts(linePart); material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0)); } - if(linePart=="$light_map") + else if (linePart=="$light_map") { ss >> linePart; - if(linePart[0]=='"')// "file" -> file - linePart=linePart.substr(1, linePart.size()-2); - aiString ts(linePart.c_str()); + if (linePart[0]=='"') { + linePart = linePart.substr(1, linePart.size() - 2); + } + aiString ts(linePart); material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, 0)); } } @@ -363,13 +363,21 @@ bool OgreImporter::ReadPass(const std::string &passName, stringstream &ss, aiMat DefaultLogger::get()->debug(Formatter::format() << " " << linePart << " " << r << " " << g << " " << b); if (linePart == partAmbient) + { material->AddProperty(&color, 1, AI_MATKEY_COLOR_AMBIENT); + } else if (linePart == partDiffuse) + { material->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE); + } else if (linePart == partSpecular) + { material->AddProperty(&color, 1, AI_MATKEY_COLOR_SPECULAR); + } else if (linePart == partEmissive) + { material->AddProperty(&color, 1, AI_MATKEY_COLOR_EMISSIVE); + } } else if (linePart == partTextureUnit) { @@ -430,18 +438,30 @@ bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstr DefaultLogger::get()->debug(Formatter::format() << "Detecting texture type from filename postfix '" << identifier << "'"); if (identifier == "_n" || identifier == "_nrm" || identifier == "_nrml" || identifier == "_normal" || identifier == "_normals" || identifier == "_normalmap") + { textureType = aiTextureType_NORMALS; + } else if (identifier == "_s" || identifier == "_spec" || identifier == "_specular" || identifier == "_specularmap") + { textureType = aiTextureType_SPECULAR; + } else if (identifier == "_l" || identifier == "_light" || identifier == "_lightmap" || identifier == "_occ" || identifier == "_occlusion") + { textureType = aiTextureType_LIGHTMAP; + } else if (identifier == "_disp" || identifier == "_displacement") + { textureType = aiTextureType_DISPLACEMENT; + } else + { textureType = aiTextureType_DIFFUSE; + } } else + { textureType = aiTextureType_DIFFUSE; + } } // Detect from texture unit name. This cannot be too broad as // authors might give names like "LightSaber" or "NormalNinja". @@ -449,15 +469,25 @@ bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstr { string unitNameLower = Ogre::ToLower(textureUnitName); if (unitNameLower.find("normalmap") != string::npos) + { textureType = aiTextureType_NORMALS; + } else if (unitNameLower.find("specularmap") != string::npos) + { textureType = aiTextureType_SPECULAR; + } else if (unitNameLower.find("lightmap") != string::npos) + { textureType = aiTextureType_LIGHTMAP; + } else if (unitNameLower.find("displacementmap") != string::npos) + { textureType = aiTextureType_DISPLACEMENT; + } else + { textureType = aiTextureType_DIFFUSE; + } } } else if (linePart == partTextCoordSet) diff --git a/code/OgreMesh.cpp b/code/OgreMesh.cpp index 0ce01f4d4..ff481b1b4 100644 --- a/code/OgreMesh.cpp +++ b/code/OgreMesh.cpp @@ -54,12 +54,15 @@ namespace Ogre void OgreImporter::ReadSubMesh(const unsigned int submeshIndex, SubMesh &submesh, XmlReader *reader) { - if (reader->getAttributeValue("material")) + if (reader->getAttributeValue("material")) { submesh.MaterialName = GetAttribute(reader, "material"); - if (reader->getAttributeValue("use32bitindexes")) + } + if (reader->getAttributeValue("use32bitindexes")) { submesh.Use32bitIndexes = GetAttribute(reader, "use32bitindexes"); - if (reader->getAttributeValue("usesharedvertices")) + } + 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 << "'"); @@ -100,8 +103,9 @@ void OgreImporter::ReadSubMesh(const unsigned int submeshIndex, SubMesh &submesh NewFace.VertexIndices[2] = GetAttribute(reader, "v3"); /// @todo Support quads - if (!quadWarned && reader->getAttributeValue("v4")) + if (!quadWarned && reader->getAttributeValue("v4")) { DefaultLogger::get()->warn("Submesh has quads, only triangles are supported at the moment!"); + } submesh.Faces.push_back(NewFace); @@ -111,20 +115,27 @@ void OgreImporter::ReadSubMesh(const unsigned int submeshIndex, SubMesh &submesh } 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) + while(string(reader->getNodeName()) == nnVertexBuffer) { ReadVertexBuffer(submesh, reader, numVertices); + } } else if (reader->getNodeName() == nnBoneAssignments) + { ReadBoneWeights(submesh, reader); + } currentNodeName = reader->getNodeName(); } @@ -157,13 +168,15 @@ void OgreImporter::ReadVertexBuffer(SubMesh &submesh, XmlReader *reader, const u 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) + if (!submesh.HasPositions) { throw DeadlyImportError("Vertex buffer does not contain positions!"); + } const string nnVertex = "vertex"; const string nnPosition = "position"; @@ -227,8 +240,9 @@ void OgreImporter::ReadVertexBuffer(SubMesh &submesh, XmlReader *reader, const u { for(size_t i=0, len=submesh.Uvs.size(); i(reader, "u"); @@ -248,26 +262,39 @@ void OgreImporter::ReadVertexBuffer(SubMesh &submesh, XmlReader *reader, const u 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) + if (warn) { DefaultLogger::get()->warn(string("Vertex buffer attribute read not implemented for element: ") + currentNodeName); + } } // Advance @@ -282,15 +309,18 @@ void OgreImporter::ReadVertexBuffer(SubMesh &submesh, XmlReader *reader, const u " Tangents " << submesh.Tangents.size()); // Sanity checks - if (submesh.HasNormals && submesh.Normals.size() != numVertices) + 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) + } + 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 > uniqueWeights(uniqueVertexCount); vector > uniqueUvs(submesh.UseSharedGeometry ? sharedGeometry.Uvs.size() : submesh.Uvs.size()); - for(size_t uvi=0; uvi &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 &boneWeights = assimpWeights[boneId]; - if (boneWeights.size() == 0) + if (boneWeights.size() == 0) { continue; + } // @note The bones list is sorted by id's, this was done in LoadSkeleton. aiBone *assimpBone = new aiBone(); @@ -504,8 +540,9 @@ aiMesh *OgreImporter::CreateAssimpSubMesh(aiScene *pScene, const SubMesh& submes dest->mBones = new aiBone*[assimpBones.size()]; dest->mNumBones = assimpBones.size(); - for(size_t i=0, len=assimpBones.size(); imBones[i] = assimpBones[i]; + } } // Faces diff --git a/code/OgreParsingUtils.h b/code/OgreParsingUtils.h index 9e5b83e82..dfe655571 100644 --- a/code/OgreParsingUtils.h +++ b/code/OgreParsingUtils.h @@ -18,9 +18,13 @@ 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 @@ -31,7 +35,9 @@ 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); @@ -44,7 +50,9 @@ inline unsigned int GetAttribute(const XmlReader* reader, const st { const char* value = reader->getAttributeValue(name.c_str()); if (value) + { return static_cast(atoi(value)); ///< @todo Find a better way... + } else { ThrowAttibuteError(reader, name); @@ -57,7 +65,9 @@ inline float GetAttribute(const XmlReader* reader, const std::string &nam { const char* value = reader->getAttributeValue(name.c_str()); if (value) + { return fast_atof(value); + } else { ThrowAttibuteError(reader, name); @@ -70,7 +80,9 @@ inline std::string GetAttribute(const XmlReader* reader, const std: { const char* value = reader->getAttributeValue(name.c_str()); if (value) + { return std::string(value); + } else { ThrowAttibuteError(reader, name); @@ -83,9 +95,13 @@ inline bool GetAttribute(const XmlReader* reader, const std::string &name) { std::string value = 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 + "'"); @@ -97,8 +113,9 @@ inline bool NextNode(XmlReader* reader) { do { - if (!reader->read()) + if (!reader->read()) { return false; + } } while(reader->getNodeType() != irr::io::EXN_ELEMENT); return true; @@ -137,12 +154,17 @@ static inline std::string ToLower(std::string s) static inline bool EndsWith(const std::string &s, const std::string &suffix, bool caseSensitive = true) { if (s.empty() || suffix.empty()) + { return false; + } else if (s.length() < suffix.length()) + { return false; + } - if (!caseSensitive) + if (!caseSensitive) { return EndsWith(ToLower(s), ToLower(suffix), true); + } size_t len = suffix.length(); std::string sSuffix = s.substr(s.length()-len, len); @@ -155,9 +177,13 @@ static inline bool EndsWith(const std::string &s, const std::string &suffix, boo static inline std::string &TrimLeft(std::string &s, bool newlines = true) { if (!newlines) + { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(Assimp::IsSpace)))); + } else + { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(Assimp::IsSpaceOrNewLine)))); + } return s; } @@ -165,9 +191,13 @@ static inline std::string &TrimLeft(std::string &s, bool newlines = true) static inline std::string &TrimRight(std::string &s, bool newlines = true) { if (!newlines) + { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(Assimp::IsSpace))).base(),s.end()); + } else + { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(Assimp::IsSpaceOrNewLine)))); + } return s; } diff --git a/code/OgreSkeleton.cpp b/code/OgreSkeleton.cpp index ea414604a..3f0b7abb1 100644 --- a/code/OgreSkeleton.cpp +++ b/code/OgreSkeleton.cpp @@ -69,53 +69,60 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH } boost::scoped_ptr file(pIOHandler->Open(filename)); - if (!file.get()) + 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) + 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")) + if (!CurrentNodeNameEquals(reader, "skeleton")) { throw DeadlyImportError("Root node is not but <" + string(reader->getNodeName()) + "> in " + filename); + } // Bones NextNode(reader); - if (!CurrentNodeNameEquals(reader, "bones")) + if (!CurrentNodeNameEquals(reader, "bones")) { throw DeadlyImportError("No node in skeleton " + skeletonFile); + } NextNode(reader); while(CurrentNodeNameEquals(reader, "bone")) { - //TODO: Maybe we can have bone ids for the errrors, but normaly, they should never appear, so what.... - /// @todo What does the above mean? + /** @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")) + 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")) + 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")) + if (!CurrentNodeNameEquals(reader, "axis")) { throw DeadlyImportError("No axis specified for bone rotation!"); + } bone.RotationAxis.x = GetAttribute(reader, "x"); bone.RotationAxis.y = GetAttribute(reader, "y"); @@ -133,14 +140,18 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH /** @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)) + { + if (static_cast(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")) + if (!CurrentNodeNameEquals(reader, "bonehierarchy")) { throw DeadlyImportError("No node found after in " + skeletonFile); + } NextNode(reader); while(CurrentNodeNameEquals(reader, "boneparent")) @@ -157,7 +168,9 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH iterParent->Children.push_back(iterChild->Id); } else + { DefaultLogger::get()->warn("Failed to find bones for parenting: Child " + childName + " Parent " + parentName); + } NextNode(reader); } @@ -165,8 +178,9 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH // Calculate bone matrices for root bones. Recursively does their children. BOOST_FOREACH(Bone &theBone, Bones) { - if (!theBone.IsParented()) + if (!theBone.IsParented()) { theBone.CalculateBoneToWorldSpaceMatrix(Bones); + } } aiVector3D zeroVec(0.f, 0.f, 0.f); @@ -185,8 +199,9 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH // Tracks NextNode(reader); - if (!CurrentNodeNameEquals(reader, "tracks")) + if (!CurrentNodeNameEquals(reader, "tracks")) { throw DeadlyImportError("No node found in animation '" + animation.Name + "' in " + skeletonFile); + } NextNode(reader); while(CurrentNodeNameEquals(reader, "track")) @@ -196,8 +211,9 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH // Keyframes NextNode(reader); - if (!CurrentNodeNameEquals(reader, "keyframes")) + if (!CurrentNodeNameEquals(reader, "keyframes")) { throw DeadlyImportError("No node found in a track in animation '" + animation.Name + "' in " + skeletonFile); + } NextNode(reader); while(CurrentNodeNameEquals(reader, "keyframe")) @@ -219,8 +235,9 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH float angle = GetAttribute(reader, "angle"); NextNode(reader); - if(string("axis")!=reader->getNodeName()) - throw DeadlyImportError("No axis for keyframe rotation!"); + if (!CurrentNodeNameEquals(reader, "axis")) { + throw DeadlyImportError("No axis for keyframe rotation in animation '" + animation.Name + "'"); + } aiVector3D axis; axis.x = GetAttribute(reader, "x"); @@ -230,8 +247,9 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH if (axis.Equal(zeroVec)) { axis.x = 1.0f; - if (angle != 0) + 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); } @@ -256,20 +274,24 @@ void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOH void OgreImporter::CreateAssimpSkeleton(aiScene *pScene, const std::vector &bones, const std::vector &animations) { - if (bones.empty()) + if (bones.empty()) { return; + } - if (!pScene->mRootNode) + if (!pScene->mRootNode) { throw DeadlyImportError("Creating Assimp skeleton: No root node created!"); - if (pScene->mRootNode->mNumChildren > 0) + } + 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()) + if (!bone.IsParented()) { rootBones.push_back(CreateNodeFromBone(bone.Id, bones, pScene->mRootNode)); + } } if (!rootBones.empty()) @@ -277,8 +299,9 @@ void OgreImporter::CreateAssimpSkeleton(aiScene *pScene, const std::vector 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! @@ -315,8 +338,9 @@ void OgreImporter::CreateAssimpSkeleton(aiScene *pScene, const std::vector vector::const_iterator boneIter = find(bones.begin(), bones.end(), tSource.BoneName); if (boneIter == bones.end()) { - for(unsigned int a=0; amAnimations[a]; + for(size_t createdAnimationIndex=0; createdAnimationIndexmAnimations[createdAnimationIndex]; + } delete [] pScene->mAnimations; pScene->mAnimations = NULL; pScene->mNumAnimations = 0; @@ -387,8 +411,9 @@ aiNode* OgreImporter::CreateNodeFromBone(int boneId, const std::vector &bo 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; @@ -400,9 +425,13 @@ void Bone::CalculateBoneToWorldSpaceMatrix(vector &Bones) 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)