From 2a3382b4ea4af90315ffa9b778f02949aade56fa Mon Sep 17 00:00:00 2001 From: Armin Burgmeier Date: Fri, 10 Jul 2009 00:18:26 +0200 Subject: [PATCH] Added mesh loading (from OGRE XML file) --- standard/inc/StdMesh.h | 27 ++- standard/src/StdMesh.cpp | 388 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 406 insertions(+), 9 deletions(-) diff --git a/standard/inc/StdMesh.h b/standard/inc/StdMesh.h index 14ca97ead..e190c4835 100644 --- a/standard/inc/StdMesh.h +++ b/standard/inc/StdMesh.h @@ -37,8 +37,8 @@ protected: // Interface to load skeleton files. Given a filename occuring in the // mesh file, this should load the skeleton file from wherever the mesh file -// was loaded from, for example from a C4Group. Return an empty string if -// the loading failed. +// was loaded from, for example from a C4Group. Return default-construted +// StdStrBuf with NULL data in case of error. class StdMeshSkeletonLoader { public: @@ -73,14 +73,16 @@ class StdMeshBone { friend class StdMesh; public: - unsigned int Index; // Index in master bone array + StdMeshBone() {} + + unsigned int Index; // Index in master bone table int ID; // Bone ID StdStrBuf Name; // Bone name // Bone transformation - StdMeshMatrix trans; + StdMeshMatrix Trans; // Inverse transformation - StdMeshMatrix inverse_trans; + StdMeshMatrix InverseTrans; const StdMeshBone* GetParent() const { return Parent; } @@ -149,8 +151,12 @@ class StdMeshAnimation { friend class StdMesh; public: + StdMeshAnimation() {} + StdMeshAnimation(const StdMeshAnimation& other); ~StdMeshAnimation(); + StdMeshAnimation& operator=(const StdMeshAnimation& other); + StdStrBuf Name; float Length; @@ -164,8 +170,10 @@ public: StdMesh(); ~StdMesh(); - // Throws StdMeshError - void InitXML(const char* xml_data, StdMeshSkeletonLoader& skel_loader, const StdMeshMatManager& manager); + // filename is only used to show it in error messages. The model is + // loaded from xml_data. + // Throws StdMeshError. + void InitXML(const char* filename, const char* xml_data, StdMeshSkeletonLoader& skel_loader, const StdMeshMatManager& manager); const StdMeshVertex& GetVertex(unsigned int i) const { return Vertices[i]; } unsigned int GetNumVertices() const { return Vertices.size(); } @@ -180,6 +188,11 @@ public: const StdMeshMaterial& GetMaterial() const { return *Material; } private: + void AddMasterBone(StdMeshBone* bone); + + StdMesh(const StdMesh& other); // non-copyable + StdMesh& operator=(const StdMesh& other); // non-assignable + // Remember bone assignments for vertices class Vertex: public StdMeshVertex { diff --git a/standard/src/StdMesh.cpp b/standard/src/StdMesh.cpp index 614e8fd34..9e3d0b5e9 100644 --- a/standard/src/StdMesh.cpp +++ b/standard/src/StdMesh.cpp @@ -18,11 +18,88 @@ #include +#include + StdMeshError::StdMeshError(const StdStrBuf& message, const char* file, unsigned int line) { Buf.Format("%s[%u]: %s", file, line, message.getData()); } +// Helper class to load things from an XML file with error checking +class StdMeshXML +{ +public: + StdMeshXML(const char* filename, const char* xml_data); + + const char* RequireStrAttribute(TiXmlElement* element, const char* attribute) const; + int RequireIntAttribute(TiXmlElement* element, const char* attribute) const; + float RequireFloatAttribute(TiXmlElement* element, const char* attribute) const; + + TiXmlElement* RequireFirstChild(TiXmlElement* element, const char* child); + + void Error(const StdStrBuf& message, TiXmlElement* element) const; + +private: + TiXmlDocument Document; + StdStrBuf FileName; +}; + +StdMeshXML::StdMeshXML(const char* filename, const char* xml_data): + FileName(filename) +{ + Document.Parse(filename); + if(Document.Error()) + throw StdMeshError(StdStrBuf(Document.ErrorDesc()), FileName.getData(), Document.ErrorRow()); +} + +const char* StdMeshXML::RequireStrAttribute(TiXmlElement* element, const char* attribute) const +{ + const char* value = element->Attribute(attribute); + if(!value) Error(FormatString("Element '%s' does not have attribute '%s'", element->Value(), attribute), element); + return value; +} + +int StdMeshXML::RequireIntAttribute(TiXmlElement* element, const char* attribute) const +{ + int retval; + if(element->QueryIntAttribute(attribute, &retval) != TIXML_SUCCESS) + Error(FormatString("Element '%s' does not have integer attribute '%s'", element->Value(), attribute), element); + return retval; +} + +float StdMeshXML::RequireFloatAttribute(TiXmlElement* element, const char* attribute) const +{ + float retval; + if(element->QueryFloatAttribute(attribute, &retval) != TIXML_SUCCESS) + Error(FormatString("Element '%s' does not have integer attribute '%s'", element->Value(), attribute), element); + return retval; +} + +TiXmlElement* StdMeshXML::RequireFirstChild(TiXmlElement* element, const char* child) +{ + TiXmlElement* retval; + + if(element) + { + retval = element->FirstChildElement(child); + if(!retval) + Error(FormatString("Element '%s' does not contain '%s' child", element->Value(), child), element); + } + else + { + retval = Document.RootElement(); + if(strcmp(retval->Value(), child) != 0) + Error(FormatString("Root element is not '%s'", child), retval); + } + + return retval; +} + +void StdMeshXML::Error(const StdStrBuf& message, TiXmlElement* element) const +{ + throw StdMeshError(message, FileName.getData(), element->Row()); +} + void StdMeshMatrix::SetIdentity() { a[0][0] = 1.0f; a[0][1] = 0.0f; a[0][2] = 0.0f; a[0][3] = 0.0f; @@ -206,12 +283,40 @@ StdMeshMatrix StdMeshTrack::GetTransformAt(float time) const return trans1; } +StdMeshAnimation::StdMeshAnimation(const StdMeshAnimation& other): + Name(other.Name), Length(other.Length), Tracks(other.Tracks.size()) +{ + // Note that all Tracks are already default-initialized to zero + for(unsigned int i = 0; i < Tracks.size(); ++i) + if(other.Tracks[i]) + Tracks[i] = new StdMeshTrack(*other.Tracks[i]); +} + StdMeshAnimation::~StdMeshAnimation() { for(unsigned int i = 0; i < Tracks.size(); ++i) delete Tracks[i]; } +StdMeshAnimation& StdMeshAnimation::operator=(const StdMeshAnimation& other) +{ + if(this == &other) return *this; + + Name = other.Name; + Length = other.Length; + + for(unsigned int i = 0; i < Tracks.size(); ++i) + delete Tracks[i]; + + Tracks.resize(other.Tracks.size()); + + for(unsigned int i = 0; i < Tracks.size(); ++i) + if(other.Tracks[i]) + Tracks[i] = new StdMeshTrack(*other.Tracks[i]); + + return *this; +} + StdMesh::StdMesh(): Material(NULL) { @@ -223,7 +328,286 @@ StdMesh::~StdMesh() delete Bones[i]; } -void StdMesh::InitXML(const char* xml_data, StdMeshSkeletonLoader& skel_loader, const StdMeshMatManager& manager) +void StdMesh::InitXML(const char* filename, const char* xml_data, StdMeshSkeletonLoader& skel_loader, const StdMeshMatManager& manager) { - + StdMeshXML mesh(filename, xml_data); + + TiXmlElement* mesh_elem = mesh.RequireFirstChild(NULL, "mesh"); + TiXmlElement* submeshes_elem = mesh.RequireFirstChild(mesh_elem, "submeshes"); + // Load first submesh only for now + TiXmlElement* submesh_elem = mesh.RequireFirstChild(submeshes_elem, "submesh"); + TiXmlElement* geometry_elem = mesh.RequireFirstChild(submesh_elem, "geometry"); + + int VertexCount = mesh.RequireIntAttribute(geometry_elem, "vertexcount"); + Vertices.resize(VertexCount); + + TiXmlElement* buffer_elem = mesh.RequireFirstChild(geometry_elem, "vertexbuffer"); + + unsigned int i = 0; + for(TiXmlElement* vertex_elem = buffer_elem->FirstChildElement("vertex"); vertex_elem != NULL && i < Vertices.size(); vertex_elem = vertex_elem->NextSiblingElement("vertex"), ++i) + { + TiXmlElement* position_elem = mesh.RequireFirstChild(vertex_elem, "position"); + TiXmlElement* normal_elem = mesh.RequireFirstChild(vertex_elem, "normal"); + TiXmlElement* texcoord_elem = mesh.RequireFirstChild(vertex_elem, "texcoord"); + + Vertices[i].x = mesh.RequireFloatAttribute(position_elem, "x"); + Vertices[i].y = mesh.RequireFloatAttribute(position_elem, "y"); + Vertices[i].z = mesh.RequireFloatAttribute(position_elem, "z"); + Vertices[i].nx = mesh.RequireFloatAttribute(normal_elem, "x"); + Vertices[i].ny = mesh.RequireFloatAttribute(normal_elem, "y"); + Vertices[i].nz = mesh.RequireFloatAttribute(normal_elem, "z"); + Vertices[i].u = mesh.RequireFloatAttribute(texcoord_elem, "u"); + Vertices[i].v = mesh.RequireFloatAttribute(texcoord_elem, "v"); + } + + TiXmlElement* faces_elem = mesh.RequireFirstChild(submesh_elem, "faces"); + int FaceCount = mesh.RequireIntAttribute(faces_elem, "count"); + Faces.resize(FaceCount); + + i = 0; + for(TiXmlElement* face_elem = faces_elem->FirstChildElement("face"); face_elem != NULL && i < Faces.size(); face_elem = face_elem->NextSiblingElement("face"), ++i) + { + int v[3]; + + v[0] = mesh.RequireIntAttribute(face_elem, "v1"); + v[1] = mesh.RequireIntAttribute(face_elem, "v2"); + v[2] = mesh.RequireIntAttribute(face_elem, "v3"); + + for(unsigned int j = 0; j < 3; ++j) + { + if(v[j] < 0 || static_cast(v[j]) >= Vertices.size()) + mesh.Error(FormatString("Vertex index v%u (%d) is out of range", j+1, v[j]), face_elem); + Faces[i].Vertices[j] = v[j]; + } + } + + // Read skeleton + TiXmlElement* skeletonlink_elem = mesh.RequireFirstChild(mesh_elem, "skeletonlink"); + const char* name = mesh.RequireStrAttribute(skeletonlink_elem, "name"); + StdStrBuf xml_filename(name); xml_filename.Append(".xml"); + + StdStrBuf skeleton_xml_data = skel_loader.LoadSkeleton(xml_filename.getData()); + if(skeleton_xml_data.isNull()) mesh.Error(FormatString("Failed to load '%s'", xml_filename.getData()), skeletonlink_elem); + + StdMeshXML skeleton(xml_filename.getData(), skeleton_xml_data.getData()); + TiXmlElement* skeleton_elem = skeleton.RequireFirstChild(NULL, "skeleton"); + TiXmlElement* bones_elem = skeleton.RequireFirstChild(skeleton_elem, "bones"); + + // Read bones. Don't insert into Master bone table yet, as the master bone + // table is sorted hierarchically, and we will read the hierarchy only + // afterwards. + std::vector bones; + for(TiXmlElement* bone_elem = bones_elem->FirstChildElement("bone"); bone_elem != NULL; bone_elem = bone_elem->NextSiblingElement("bone")) + { + StdMeshBone* bone = new StdMeshBone; + bones.push_back(bone); + + bone->ID = skeleton.RequireIntAttribute(bone_elem, "id"); + bone->Name = skeleton.RequireStrAttribute(bone_elem, "name"); + + // TODO: Make sure ID and name are unique + + TiXmlElement* position_elem = skeleton.RequireFirstChild(bone_elem, "position"); + TiXmlElement* rotation_elem = skeleton.RequireFirstChild(bone_elem, "rotation"); + TiXmlElement* axis_elem = skeleton.RequireFirstChild(rotation_elem, "axis"); + + float dx = skeleton.RequireFloatAttribute(position_elem, "x"); + float dy = skeleton.RequireFloatAttribute(position_elem, "y"); + float dz = skeleton.RequireFloatAttribute(position_elem, "z"); + float angle = skeleton.RequireFloatAttribute(rotation_elem, "angle"); + float rx = skeleton.RequireFloatAttribute(axis_elem, "x"); + float ry = skeleton.RequireFloatAttribute(axis_elem, "y"); + float rz = skeleton.RequireFloatAttribute(axis_elem, "z"); + + StdMeshMatrix helper; + helper.SetTranslate(dx, dy, dz); + bone->Trans.SetRotate(angle, rx, ry, rz); + bone->Trans.Transform(helper); + + helper.SetRotate(-angle, rx, ry, rz); + bone->InverseTrans.SetTranslate(-dx, -dy, -dz); + bone->InverseTrans.Transform(helper); + + bone->Parent = NULL; + + // Index of bone will be set when building Master Bone Table later + } + + // Bone hierarchy + TiXmlElement* bonehierarchy_elem = skeleton.RequireFirstChild(skeleton_elem, "bonehierarchy"); + for(TiXmlElement* boneparent_elem = bonehierarchy_elem->FirstChildElement("boneparent"); boneparent_elem != NULL; boneparent_elem = boneparent_elem->NextSiblingElement("boneparent")) + { + const char* child_name = skeleton.RequireStrAttribute(boneparent_elem, "bone"); + const char* parent_name = skeleton.RequireStrAttribute(boneparent_elem, "parent"); + + // Lookup the two bones + StdMeshBone* child = NULL; + StdMeshBone* parent = NULL; + for(unsigned int i = 0; i < bones.size() && (!child || !parent); ++i) + { + if(!child && bones[i]->Name == child_name) + child = bones[i]; + if(!parent && bones[i]->Name == parent_name) + parent = bones[i]; + } + + if(!child) skeleton.Error(FormatString("There is no such bone with name '%s'", child_name), boneparent_elem); + if(!parent) skeleton.Error(FormatString("There is no such bone with name '%s'", parent_name), boneparent_elem); + + child->Parent = parent; + parent->Children.push_back(child); + + // Apply parent transformation + child->Trans.Transform(parent->Trans); + child->InverseTrans.Mul(parent->InverseTrans); + } + + // Fill master bone table in hierarchical order: + for(unsigned int i = 0; i < bones.size(); ++i) + if(bones[i]->Parent == NULL) + AddMasterBone(bones[i]); + + // Vertex<->Bone assignments + TiXmlElement* boneassignments_elem = mesh.RequireFirstChild(submesh_elem, "boneassignments"); + for(TiXmlElement* vertexboneassignment_elem = boneassignments_elem->FirstChildElement("vertexboneassignment"); vertexboneassignment_elem != NULL; vertexboneassignment_elem = vertexboneassignment_elem->NextSiblingElement("vertexboneassignment")) + { + int BoneID = mesh.RequireIntAttribute(vertexboneassignment_elem, "boneindex"); + int VertexIndex = mesh.RequireIntAttribute(vertexboneassignment_elem, "vertexindex"); + float weight = mesh.RequireFloatAttribute(vertexboneassignment_elem, "weight"); + + if(VertexIndex < 0 || static_cast(VertexIndex) >= Vertices.size()) + mesh.Error(FormatString("Vertex index in bone assignment (%d) is out of range", VertexIndex), vertexboneassignment_elem); + + StdMeshBone* bone = NULL; + for(unsigned int i = 0; !bone && i < bones.size(); ++i) + if(bones[i]->ID == BoneID) + bone = bones[i]; + + if(!bone) mesh.Error(FormatString("There is no such bone with index %d", BoneID), vertexboneassignment_elem); + + Vertex& vertex = Vertices[VertexIndex]; + vertex.BoneAssignments.push_back(StdMeshVertexBoneAssignment()); + StdMeshVertexBoneAssignment& assignment = vertex.BoneAssignments.back(); + assignment.BoneIndex = bone->Index; + assignment.Weight = weight; + } + + // Normalize vertex bone assignment weights (this is not guaranteed in the + // Ogre file format). + for(unsigned int i = 0; i < Vertices.size(); ++i) + { + Vertex& vertex = Vertices[i]; + float sum = 0.0f; + for(unsigned int j = 0; j < vertex.BoneAssignments.size(); ++j) + sum += vertex.BoneAssignments[j].Weight; + for(unsigned int j = 0; j < vertex.BoneAssignments.size(); ++j) + vertex.BoneAssignments[j].Weight /= sum; + } + + // Load Animations + TiXmlElement* animations_elem = skeleton.RequireFirstChild(skeleton_elem, "animations"); + for(TiXmlElement* animation_elem = animations_elem->FirstChildElement("animation"); animation_elem != NULL; animation_elem = animation_elem->NextSiblingElement("animation")) + { + StdStrBuf name(skeleton.RequireStrAttribute(animation_elem, "name")); + if(Animations.find(name) != Animations.end()) + skeleton.Error(FormatString("There is already an animation with name '%s'", name.getData()), animation_elem); + + StdMeshAnimation& animation = Animations.insert(std::make_pair(name, StdMeshAnimation())).first->second; + animation.Name = name; + animation.Length = skeleton.RequireFloatAttribute(animation_elem, "length"); + animation.Tracks.resize(Bones.size()); + + TiXmlElement* tracks_elem = skeleton.RequireFirstChild(animation_elem, "tracks"); + for(TiXmlElement* track_elem = tracks_elem->FirstChildElement("track"); track_elem != NULL; track_elem = track_elem->NextSiblingElement("track")) + { + const char* bone_name = skeleton.RequireStrAttribute(track_elem, "bone"); + StdMeshBone* bone = NULL; + for(unsigned int i = 0; !bone && i < Bones.size(); ++i) + if(Bones[i]->Name == bone_name) + bone = Bones[i]; + if(!bone) skeleton.Error(FormatString("There is no such bone with name '%s'", bone_name), track_elem); + + if(animation.Tracks[bone->Index] != NULL) skeleton.Error(FormatString("There is already a track for bone '%s' in animation '%s'", bone_name, animation.Name.getData()), track_elem); + + StdMeshTrack* track = new StdMeshTrack; + animation.Tracks[bone->Index] = track; + + TiXmlElement* keyframes_elem = skeleton.RequireFirstChild(track_elem, "keyframes"); + for(TiXmlElement* keyframe_elem = keyframes_elem->FirstChildElement("keyframe"); keyframe_elem != NULL; keyframe_elem = keyframe_elem->NextSiblingElement("keyframe")) + { + float time = skeleton.RequireFloatAttribute(keyframe_elem, "time"); + StdMeshKeyFrame& frame = track->Frames[time]; + + TiXmlElement* translate_elem = skeleton.RequireFirstChild(keyframe_elem, "translate"); + TiXmlElement* rotate_elem = skeleton.RequireFirstChild(keyframe_elem, "rotate"); + TiXmlElement* scale_elem = skeleton.RequireFirstChild(scale_elem, "scale"); + TiXmlElement* axis_elem = skeleton.RequireFirstChild(rotate_elem, "axis"); + + float dx = skeleton.RequireFloatAttribute(translate_elem, "x"); + float dy = skeleton.RequireFloatAttribute(translate_elem, "y"); + float dz = skeleton.RequireFloatAttribute(translate_elem, "z"); + float sx = skeleton.RequireFloatAttribute(scale_elem, "x"); + float sy = skeleton.RequireFloatAttribute(scale_elem, "y"); + float sz = skeleton.RequireFloatAttribute(scale_elem, "z"); + float angle = skeleton.RequireFloatAttribute(rotate_elem, "angle"); + float rx = skeleton.RequireFloatAttribute(axis_elem, "x"); + float ry = skeleton.RequireFloatAttribute(axis_elem, "y"); + float rz = skeleton.RequireFloatAttribute(axis_elem, "z"); + + StdMeshMatrix helper; + frame.Trans.SetRotate(angle, rx, ry, rz); + helper.SetScale(sx, sy, sz); + frame.Trans.Transform(helper); + helper.SetTranslate(-dx, -dy, -dz); + frame.Trans.Transform(helper); + } + } + + // Apply bone transformation on animation frames. We need to do this + // after the actual loading because we need to walk the bone list + // hierarchically. + for(unsigned int i = 0; i < Bones.size(); ++i) + { + if(animation.Tracks[i]) + { + StdMeshTrack& track = *animation.Tracks[i]; + + // Get next parent track + StdMeshTrack* parent_track = NULL; + StdMeshBone* parent_bone = Bones[i]->Parent; + while(parent_bone && !(parent_track = animation.Tracks[parent_bone->Index])) + parent_bone = parent_bone->Parent; + assert(!parent_bone || parent_track); + + for(std::map::iterator iter = track.Frames.begin(); iter != track.Frames.end(); ++iter) + { + // TODO: If this bone's track is not as smooth as the parent animation + // (which means if there is more than one keyframe in the parent + // animation for each keyframe pair in the this bone's animation), + // then we need to insert additional child keyframes, so that the + // transformation for the child does not skip parent keyframes. + StdMeshKeyFrame& frame = iter->second; + + // Apply transformation of parent tracks (for which we computed + // already the bone transformations, as we walk the bone list + // hierarchically) in bone's coordinate system. + frame.Trans.Mul(Bones[i]->InverseTrans); + frame.Trans.Transform(Bones[i]->Trans); + + if(parent_bone) + frame.Trans.Transform(parent_track->GetTransformAt(iter->first)); + } + } + } + } } + +void StdMesh::AddMasterBone(StdMeshBone* bone) +{ + bone->Index = Bones.size(); // Remember index in master bone table + Bones.push_back(bone); + for(unsigned int i = 0; i < bone->Children.size(); ++i) + AddMasterBone(bone->Children[i]); +} + +// vim: et ts=2 sw=2