Added mesh loading (from OGRE XML file)

stable-5.2
Armin Burgmeier 2009-07-10 00:18:26 +02:00
parent e52a4364b6
commit 2a3382b4ea
2 changed files with 406 additions and 9 deletions

View File

@ -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
{

View File

@ -18,11 +18,88 @@
#include <StdMesh.h>
#include <tinyxml/tinyxml.h>
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<unsigned int>(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<StdMeshBone*> 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<unsigned int>(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<float, StdMeshKeyFrame>::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