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