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.

pull/266/head
Jonne Nauha 2014-05-01 18:51:26 +03:00
parent 45715df263
commit f5c7b283bc
6 changed files with 195 additions and 216 deletions

View File

@ -39,6 +39,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "AssimpPCH.h" #include "AssimpPCH.h"
#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER
#include <vector> #include <vector>
@ -71,14 +72,10 @@ namespace Ogre
bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool checkSig) const bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool checkSig) const
{ {
if (!checkSig) if (!checkSig)
{ return EndsWith(pFile, ".mesh.xml", false);
string ext = "mesh.xml";
int len = ext.length(); const char* tokens[] = { "<mesh>" };
string fileExt = ToLower(pFile.substr(pFile.length()-len, len)); return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
return (ASSIMP_stricmp(fileExt, ext) == 0);
}
const char* tokens[] = {"<mesh>"};
return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
} }
void OgreImporter::InternReadFile(const std::string &pFile, aiScene *pScene, Assimp::IOSystem *pIOHandler) 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 // Read root node
NextNode(reader.get()); NextNode(reader.get());
if (!CurrentNodeNameEquals(reader, "mesh")) if (!CurrentNodeNameEquals(reader, "mesh"))
throw DeadlyImportError("Root node is not <mesh> but <" + string(reader->getNodeName()) + ">"); throw DeadlyImportError("Root node is not <mesh> but <" + string(reader->getNodeName()) + "> in " + pFile);
// Node names // Node names
const string nnSharedGeometry = "sharedgeometry"; 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); m_detectTextureTypeFromFilename = pImp->GetPropertyBool(AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME, false);
} }
} // Ogre
} // Assimp
}//namespace Ogre #endif // ASSIMP_BUILD_NO_OGRE_IMPORTER
}//namespace Assimp
#endif // !! ASSIMP_BUILD_NO_OGRE_IMPORTER

View File

@ -155,7 +155,7 @@ struct BoneAssignment
/// Ogre Bone weight /// Ogre Bone weight
struct BoneWeight struct BoneWeight
{ {
/// Bone ID /// Bone Id
unsigned int Id; unsigned int Id;
/// BoneWeight /// BoneWeight
float Value; float Value;
@ -187,8 +187,11 @@ struct Bone
{ {
} }
/// This operator is needed to sort the bones after Id's /// Returns if this bone is parented.
bool operator<(const Bone &other) const { return Id < other.Id; } bool IsParented() const { return (ParentId != -1); }
/// This operator is needed to sort the bones by Id in a vector<Bone>.
bool operator<(const Bone &other) const { return (Id < other.Id); }
/// This operator is needed to find a bone by its name in a vector<Bone> /// This operator is needed to find a bone by its name in a vector<Bone>
bool operator==(const std::string& other) const { return Name == other; } bool operator==(const std::string& other) const { return Name == other; }

View File

@ -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" #include "AssimpPCH.h"
#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER #ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER

View File

@ -548,8 +548,7 @@ aiMesh* OgreImporter::CreateAssimpSubMesh(aiScene *pScene, const SubMesh& submes
return NewAiMesh; return NewAiMesh;
} }
} // Ogre
} // Assimp
}//namespace Ogre #endif // ASSIMP_BUILD_NO_OGRE_IMPORTER
}//namespace Assimp
#endif // !! ASSIMP_BUILD_NO_OGRE_IMPORTER

View File

@ -118,10 +118,25 @@ static inline std::string ToLower(std::string s)
return s; return s;
} }
// ------------------------------------------------------------------------------------------------ /// Returns if @c s ends with @c suffix. If @c caseSensitive is false, both strings will be lower cased before matching.
// From http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring 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) static inline std::string &TrimLeft(std::string &s, bool newlines = true)
{ {
if (!newlines) if (!newlines)
@ -131,7 +146,7 @@ static inline std::string &TrimLeft(std::string &s, bool newlines = true)
return s; return s;
} }
// trim from end /// Trim from end
static inline std::string &TrimRight(std::string &s, bool newlines = true) static inline std::string &TrimRight(std::string &s, bool newlines = true)
{ {
if (!newlines) 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<char>)))); s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(Assimp::IsSpaceOrNewLine<char>))));
return s; return s;
} }
// trim from both ends
/// 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 TrimLeft(TrimRight(s, newlines), newlines); return TrimLeft(TrimRight(s, newlines), newlines);

View File

@ -55,226 +55,205 @@ namespace Ogre
void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOHandler, const aiScene *pScene, void OgreImporter::ReadSkeleton(const std::string &pFile, Assimp::IOSystem *pIOHandler, const aiScene *pScene,
const std::string &skeletonFile, vector<Bone> &Bones, vector<Animation> &Animations) const const std::string &skeletonFile, vector<Bone> &Bones, vector<Animation> &Animations) const
{ {
//most likely the skeleton file will only end with .skeleton string filename = skeletonFile;
//But this is a xml reader, so we need: .skeleton.xml if (EndsWith(filename, ".skeleton"))
string skeletonPath = skeletonFile + ".xml"; {
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<IOStream> file(pIOHandler->Open(filename));
boost::scoped_ptr<IOStream> File(pIOHandler->Open(skeletonFile)); if (!file.get())
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<CIrrXML_IOStreamReader> stream(new CIrrXML_IOStreamReader(file.get()));
boost::scoped_ptr<CIrrXML_IOStreamReader> mIOWrapper(new CIrrXML_IOStreamReader(File.get())); XmlReader* reader = irr::io::createIrrXMLReader(stream.get());
XmlReader* SkeletonFile = irr::io::createIrrXMLReader(mIOWrapper.get()); if (!reader)
if(!SkeletonFile) throw DeadlyImportError("Failed to create XML reader for skeleton file " + filename);
throw DeadlyImportError(string("Failed to create XML Reader for ")+skeletonFile);
NextNode(SkeletonFile); DefaultLogger::get()->debug("Reading skeleton '" + filename + "'");
if(string("skeleton")!=SkeletonFile->getNodeName())
throw DeadlyImportError("No <skeleton> node in SkeletonFile: "+skeletonFile);
// Root
NextNode(reader);
if (!CurrentNodeNameEquals(reader, "skeleton"))
throw DeadlyImportError("Root node is not <skeleton> but <" + string(reader->getNodeName()) + "> in " + filename);
// Bones
NextNode(reader);
if (!CurrentNodeNameEquals(reader, "bones"))
throw DeadlyImportError("No <bones> node in skeleton " + skeletonFile);
//------------------------------------load bones----------------------------------------- NextNode(reader);
NextNode(SkeletonFile); while(CurrentNodeNameEquals(reader, "bone"))
if(string("bones")!=SkeletonFile->getNodeName())
throw DeadlyImportError("No bones node in skeleton "+skeletonFile);
NextNode(SkeletonFile);
while(string("bone")==SkeletonFile->getNodeName())
{ {
//TODO: Maybe we can have bone ids for the errrors, but normaly, they should never appear, so what.... //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 bone;
Bone NewBone; bone.Id = GetAttribute<int>(reader, "id");
NewBone.Id=GetAttribute<int>(SkeletonFile, "id"); bone.Name = GetAttribute<string>(reader, "name");
NewBone.Name=GetAttribute<string>(SkeletonFile, "name");
//load the position: NextNode(reader);
NextNode(SkeletonFile); if (!CurrentNodeNameEquals(reader, "position"))
if(string("position")!=SkeletonFile->getNodeName())
throw DeadlyImportError("Position is not first node in Bone!"); throw DeadlyImportError("Position is not first node in Bone!");
NewBone.Position.x=GetAttribute<float>(SkeletonFile, "x");
NewBone.Position.y=GetAttribute<float>(SkeletonFile, "y");
NewBone.Position.z=GetAttribute<float>(SkeletonFile, "z");
//Rotation: bone.Position.x = GetAttribute<float>(reader, "x");
NextNode(SkeletonFile); bone.Position.y = GetAttribute<float>(reader, "y");
if(string("rotation")!=SkeletonFile->getNodeName()) bone.Position.z = GetAttribute<float>(reader, "z");
NextNode(reader);
if (!CurrentNodeNameEquals(reader, "rotation"))
throw DeadlyImportError("Rotation is not the second node in Bone!"); throw DeadlyImportError("Rotation is not the second node in Bone!");
NewBone.RotationAngle=GetAttribute<float>(SkeletonFile, "angle");
NextNode(SkeletonFile); bone.RotationAngle = GetAttribute<float>(reader, "angle");
if(string("axis")!=SkeletonFile->getNodeName())
NextNode(reader);
if (!CurrentNodeNameEquals(reader, "axis"))
throw DeadlyImportError("No axis specified for bone rotation!"); throw DeadlyImportError("No axis specified for bone rotation!");
NewBone.RotationAxis.x=GetAttribute<float>(SkeletonFile, "x");
NewBone.RotationAxis.y=GetAttribute<float>(SkeletonFile, "y"); bone.RotationAxis.x = GetAttribute<float>(reader, "x");
NewBone.RotationAxis.z=GetAttribute<float>(SkeletonFile, "z"); bone.RotationAxis.y = GetAttribute<float>(reader, "y");
bone.RotationAxis.z = GetAttribute<float>(reader, "z");
//append the newly loaded bone to the bone list Bones.push_back(bone);
Bones.push_back(NewBone);
//Proceed to the next bone: NextNode(reader);
NextNode(SkeletonFile);
} }
//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()); std::sort(Bones.begin(), Bones.end());
//now the id of each bone should be equal to its position in the vector: // Validate that bone indexes are not skipped.
//so we do a simple check: /** @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<len; ++i)
if (static_cast<int>(Bones[i].Id) != static_cast<int>(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 <bonehierarchy> node found after <bones> in " + skeletonFile);
NextNode(reader);
while(CurrentNodeNameEquals(reader, "boneparent"))
{ {
bool IdsOk=true; string childName = GetAttribute<string>(reader, "bone");
for(int i=0; i<static_cast<signed int>(Bones.size()); ++i)//i is signed, because all Id's are also signed! string parentName = GetAttribute<string>(reader, "parent");
vector<Bone>::iterator iterChild = find(Bones.begin(), Bones.end(), childName);
vector<Bone>::iterator iterParent = find(Bones.begin(), Bones.end(), parentName);
if (iterChild != Bones.end() && iterParent != Bones.end())
{ {
if(Bones[i].Id!=i) iterChild->ParentId = iterParent->Id;
IdsOk=false; iterParent->Children.push_back(iterChild->Id);
} }
if(!IdsOk) else
throw DeadlyImportError("Bone Ids are not valid!"+skeletonFile); 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()));
//________________________________________________________________________________
// Calculate bone matrices for root bones. Recursively does their children.
//----------------------------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<string>(SkeletonFile, "bone");
Parent=GetAttribute<string>(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: ------------------
BOOST_FOREACH(Bone &theBone, Bones) BOOST_FOREACH(Bone &theBone, Bones)
{ {
if(-1==theBone.ParentId) //the bone is a root bone if (!theBone.IsParented())
{
theBone.CalculateBoneToWorldSpaceMatrix(Bones); theBone.CalculateBoneToWorldSpaceMatrix(Bones);
}
} }
//_______________________________________________________________________
aiVector3D zeroVec(0.f, 0.f, 0.f);
//---------------------------load animations----------------------------- // Animations
if(string("animations")==SkeletonFile->getNodeName())//animations are optional values if (CurrentNodeNameEquals(reader, "animations"))
{ {
DefaultLogger::get()->debug("Loading Animations"); DefaultLogger::get()->debug(" - Animations");
NextNode(SkeletonFile);
while(string("animation")==SkeletonFile->getNodeName()) NextNode(reader);
while(CurrentNodeNameEquals(reader, "animation"))
{ {
Animation NewAnimation; Animation animation;
NewAnimation.Name=GetAttribute<string>(SkeletonFile, "name"); animation.Name = GetAttribute<string>(reader, "name");
NewAnimation.Length=GetAttribute<float>(SkeletonFile, "length"); animation.Length = GetAttribute<float>(reader, "length");
//Load all Tracks // Tracks
NextNode(SkeletonFile); NextNode(reader);
if(string("tracks")!=SkeletonFile->getNodeName()) if (!CurrentNodeNameEquals(reader, "tracks"))
throw DeadlyImportError("no tracks node in animation"); throw DeadlyImportError("No <tracks> node found in animation '" + animation.Name + "' in " + skeletonFile);
NextNode(SkeletonFile);
while(string("track")==SkeletonFile->getNodeName()) NextNode(reader);
while(CurrentNodeNameEquals(reader, "track"))
{ {
Track NewTrack; Track track;
NewTrack.BoneName=GetAttribute<string>(SkeletonFile, "bone"); track.BoneName = GetAttribute<string>(reader, "bone");
//Load all keyframes; // Keyframes
NextNode(SkeletonFile); NextNode(reader);
if(string("keyframes")!=SkeletonFile->getNodeName()) if (!CurrentNodeNameEquals(reader, "keyframes"))
throw DeadlyImportError("no keyframes node!"); throw DeadlyImportError("No <keyframes> node found in a track in animation '" + animation.Name + "' in " + skeletonFile);
NextNode(SkeletonFile);
while(string("keyframe")==SkeletonFile->getNodeName()) NextNode(reader);
while(CurrentNodeNameEquals(reader, "keyframe"))
{ {
KeyFrame NewKeyframe; KeyFrame keyFrame;
NewKeyframe.Time=GetAttribute<float>(SkeletonFile, "time"); keyFrame.Time = GetAttribute<float>(reader, "time");
//loop over the attributes: NextNode(reader);
while(CurrentNodeNameEquals(reader, "translate") || CurrentNodeNameEquals(reader, "rotate") || CurrentNodeNameEquals(reader, "scale"))
while(true) //will quit, if a Node is not a animationkey
{ {
NextNode(SkeletonFile); if (CurrentNodeNameEquals(reader, "translate"))
//If any property doesn't show up, it will keep its initialization value
//Position:
if(string("translate")==SkeletonFile->getNodeName())
{ {
NewKeyframe.Position.x=GetAttribute<float>(SkeletonFile, "x"); keyFrame.Position.x = GetAttribute<float>(reader, "x");
NewKeyframe.Position.y=GetAttribute<float>(SkeletonFile, "y"); keyFrame.Position.y = GetAttribute<float>(reader, "y");
NewKeyframe.Position.z=GetAttribute<float>(SkeletonFile, "z"); keyFrame.Position.z = GetAttribute<float>(reader, "z");
} }
else if (CurrentNodeNameEquals(reader, "rotate"))
//Rotation:
else if(string("rotate")==SkeletonFile->getNodeName())
{ {
float RotationAngle=GetAttribute<float>(SkeletonFile, "angle"); float angle = GetAttribute<float>(reader, "angle");
aiVector3D RotationAxis;
NextNode(SkeletonFile); NextNode(reader);
if(string("axis")!=SkeletonFile->getNodeName()) if(string("axis")!=reader->getNodeName())
throw DeadlyImportError("No axis for keyframe rotation!"); throw DeadlyImportError("No axis for keyframe rotation!");
RotationAxis.x=GetAttribute<float>(SkeletonFile, "x");
RotationAxis.y=GetAttribute<float>(SkeletonFile, "y"); aiVector3D axis;
RotationAxis.z=GetAttribute<float>(SkeletonFile, "z"); axis.x = GetAttribute<float>(reader, "x");
axis.y = GetAttribute<float>(reader, "y");
axis.z = GetAttribute<float>(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; axis.x = 1.0f;
if(0!=RotationAngle)//if we don't rotate at all, the axis does not matter if (angle != 0)
{ DefaultLogger::get()->warn("Found invalid a key frame with a zero rotation axis in animation '" + animation.Name + "'");
DefaultLogger::get()->warn("Invalid Rotation Axis in KeyFrame!");
}
} }
NewKeyframe.Rotation=aiQuaternion(RotationAxis, RotationAngle); keyFrame.Rotation = aiQuaternion(axis, angle);
} }
else if (CurrentNodeNameEquals(reader, "scale"))
//Scaling:
else if(string("scale")==SkeletonFile->getNodeName())
{ {
NewKeyframe.Scaling.x=GetAttribute<float>(SkeletonFile, "x"); keyFrame.Scaling.x = GetAttribute<float>(reader, "x");
NewKeyframe.Scaling.y=GetAttribute<float>(SkeletonFile, "y"); keyFrame.Scaling.y = GetAttribute<float>(reader, "y");
NewKeyframe.Scaling.z=GetAttribute<float>(SkeletonFile, "z"); keyFrame.Scaling.z = GetAttribute<float>(reader, "z");
} }
NextNode(reader);
//we suppose, that we read all attributes and this is a new keyframe or the end of the animation
else
break;
} }
track.Keyframes.push_back(keyFrame);
NewTrack.Keyframes.push_back(NewKeyframe);
} }
animation.Tracks.push_back(track);
NewAnimation.Tracks.push_back(NewTrack);
} }
Animations.push_back(animation);
Animations.push_back(NewAnimation);
DefaultLogger::get()->debug(Formatter::format() << " " << animation.Name << " (" << animation.Length << " sec, " << animation.Tracks.size() << " tracks)");
} }
} }
//_____________________________________________________________________________
} }
void OgreImporter::CreateAssimpSkeleton(aiScene *pScene, const std::vector<Bone> &Bones, const std::vector<Animation> &/*Animations*/) void OgreImporter::CreateAssimpSkeleton(aiScene *pScene, const std::vector<Bone> &Bones, const std::vector<Animation> &/*Animations*/)
{ {
if(!pScene->mRootNode) if(!pScene->mRootNode)
@ -414,31 +393,22 @@ aiNode* OgreImporter::CreateAiNodeFromBone(int BoneId, const std::vector<Bone> &
void Bone::CalculateBoneToWorldSpaceMatrix(vector<Bone> &Bones) void Bone::CalculateBoneToWorldSpaceMatrix(vector<Bone> &Bones)
{ {
//Calculate the matrix for this bone: aiMatrix4x4 t0, t1;
aiMatrix4x4 transform = aiMatrix4x4::Rotation(-RotationAngle, RotationAxis, t1) * aiMatrix4x4::Translation(-Position, t0);
aiMatrix4x4 t0,t1; if (!IsParented())
aiMatrix4x4 Transf= aiMatrix4x4::Rotation(-RotationAngle, RotationAxis, t1) BoneToWorldSpace = transform;
* aiMatrix4x4::Translation(-Position, t0);
if(-1==ParentId)
{
BoneToWorldSpace=Transf;
}
else else
{ BoneToWorldSpace = transform * Bones[ParentId].BoneToWorldSpace;
BoneToWorldSpace=Transf*Bones[ParentId].BoneToWorldSpace;
}
//and recursivly for all children: // Recursively for all children now that the parent matrix has been calculated.
BOOST_FOREACH(int theChildren, Children) BOOST_FOREACH(int childId, Children)
{ {
Bones[theChildren].CalculateBoneToWorldSpaceMatrix(Bones); Bones[childId].CalculateBoneToWorldSpaceMatrix(Bones);
} }
} }
} // Ogre
} // Assimp
}//namespace Ogre #endif // ASSIMP_BUILD_NO_OGRE_IMPORTER
}//namespace Assimp
#endif // !! ASSIMP_BUILD_NO_OGRE_IMPORTER