Prefer loading meshes from binary format

This implements a loader for Ogre's binary mesh format. Loading times
are vastly superior to the TinyXML parser. If no binary mesh exists,
the loader will fall back to parsing XML.

This changeset introduces a dependency on the Boost libraries.
Nicolas Hake 2010-03-02 17:12:28 +01:00
parent 411a61276b
commit 11e9f3235b
14 changed files with 2065 additions and 430 deletions

View File

@ -430,6 +430,12 @@ set(OC_CLONK_SOURCES
src/platform/StdGL.h
src/platform/StdMesh.cpp
src/platform/StdMesh.h
src/platform/StdMeshLoader.h
src/platform/StdMeshLoaderBinary.cpp
src/platform/StdMeshLoaderBinaryChunks.cpp
src/platform/StdMeshLoaderBinaryChunks.h
src/platform/StdMeshLoaderDataStream.h
src/platform/StdMeshLoaderXml.cpp
src/platform/StdMeshMaterial.cpp
src/platform/StdMeshMaterial.h
src/platform/StdNoGfx.cpp
@ -657,6 +663,10 @@ SET(JPEG_NAMES ${JPEG_NAMES} libjpeg)
include(FindJPEG)
include(FindPNG)
include(FindZLIB)
include(FindBoost)
if(NOT Boost_INCLUDE_DIR)
message(SEND_ERROR "Could not find the Boost C++ Libraries")
endif()
include(FindThreads)
if(NOT WIN32)

View File

@ -423,6 +423,12 @@ src/platform/StdGLCtx.cpp \
src/platform/StdGL.h \
src/platform/StdMesh.cpp \
src/platform/StdMesh.h \
src/platform/StdMeshLoader.h \
src/platform/StdMeshLoaderBinary.cpp \
src/platform/StdMeshLoaderBinaryChunks.cpp \
src/platform/StdMeshLoaderBinaryChunks.h \
src/platform/StdMeshLoaderDataStream.h \
src/platform/StdMeshLoaderXml.cpp \
src/platform/StdMeshMaterial.cpp \
src/platform/StdMeshMaterial.h \
src/platform/StdNoGfx.cpp \

View File

@ -7,7 +7,7 @@ make gcc g++
automake autoconf
libc6-dev libx11-dev libxxf86vm-dev libxrandr-dev libxpm-dev libglew1.5-dev
libgl1-mesa-dev libpng12-dev libssl-dev libsdl1.2-dev libsdl-mixer1.2-dev
libgtk2.0-dev libjpeg62-dev zlib1g-dev
libgtk2.0-dev libjpeg62-dev zlib1g-dev libboost-dev
Build
=====

View File

@ -12,6 +12,9 @@ You can build on Windows using either:
* Some other compilers and IDEs which are supported by CMake might also work
Building OpenClonk requires the Boost C++ Libraries (from http://www.boost.org/). It uses the
header-only parts of the library, so you don't have to build any of the binary libraries.
Notes for MinGW
===============

View File

@ -86,7 +86,8 @@
#define C4CFN_ScenarioObjects "Objects.txt"
#define C4CFN_ScenarioDesc "Desc%s.rtf"
#define C4CFN_DefMaterials "*.material"
#define C4CFN_DefMesh "Graphics.mesh.xml"
#define C4CFN_DefMesh "Graphics.mesh"
#define C4CFN_DefMeshXml (C4CFN_DefMesh ".xml")
#define C4CFN_DefGraphics "Graphics.bmp"
#define C4CFN_DefGraphicsPNG "Graphics.png"
#define C4CFN_ClrByOwnerPNG "Overlay.png"

View File

@ -42,6 +42,7 @@
#include <C4RankSystem.h>
#include <C4GraphicsResource.h>
#include <C4MeshAnimation.h>
#include "StdMeshLoader.h"
// Helper class to load additional ressources required for meshes from
// a C4Group.
@ -164,30 +165,23 @@ bool C4DefGraphics::LoadBitmap(C4Group &hGroup, const char *szFilename, const ch
bool C4DefGraphics::LoadMesh(C4Group &hGroup, StdMeshSkeletonLoader& loader)
{
char* buf;
char* buf = NULL;
size_t size;
if(!hGroup.LoadEntry(C4CFN_DefMesh, &buf, &size, 1)) return false;
Mesh = new StdMesh;
bool result;
try
{
Mesh->InitXML(C4CFN_DefMesh, buf, loader, Game.MaterialManager);
result = true;
if (hGroup.LoadEntry(C4CFN_DefMesh, &buf, &size, 1))
Mesh = StdMeshLoader::LoadMeshBinary(buf, size, Game.MaterialManager, loader);
else if (hGroup.LoadEntry(C4CFN_DefMeshXml, &buf, &size, 1))
Mesh = StdMeshLoader::LoadMeshXml(buf, size, Game.MaterialManager, loader);
else
return false;
delete[] buf;
}
catch(const StdMeshError& ex)
catch (const StdMeshLoader::LoaderException &ex)
{
DebugLogF("Failed to load mesh: %s", ex.what());
result = false;
}
delete[] buf;
if(!result)
{
delete Mesh;
Mesh = NULL;
delete[] buf;
return false;
}

View File

@ -24,8 +24,6 @@
# include <math.h>
#endif
#include <tinyxml/tinyxml.h>
#include <algorithm>
namespace
@ -77,86 +75,6 @@ namespace
}
}
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(xml_data);
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 = 0;
if(element->QueryFloatAttribute(attribute, &retval) != TIXML_SUCCESS)
Error(FormatString("Element '%s' does not have floating point 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());
}
/* Boring Math stuff begins here */
StdMeshVector StdMeshVector::Zero()
@ -849,303 +767,6 @@ StdMesh::~StdMesh()
delete Bones[i];
}
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");
TiXmlElement* submesh_elem_base = mesh.RequireFirstChild(submeshes_elem, "submesh");
for(TiXmlElement* submesh_elem = submesh_elem_base; submesh_elem != NULL; submesh_elem = submesh_elem->NextSiblingElement("submesh"))
{
TiXmlElement* geometry_elem = mesh.RequireFirstChild(submesh_elem, "geometry");
SubMeshes.push_back(StdSubMesh());
StdSubMesh& submesh = SubMeshes.back();
const char* material = mesh.RequireStrAttribute(submesh_elem, "material");
submesh.Material = manager.GetMaterial(material);
if(!submesh.Material)
mesh.Error(FormatString("There is no such material named '%s'", material), submesh_elem);
int VertexCount = mesh.RequireIntAttribute(geometry_elem, "vertexcount");
submesh.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 < submesh.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");
submesh.Vertices[i].x = mesh.RequireFloatAttribute(position_elem, "x");
submesh.Vertices[i].y = mesh.RequireFloatAttribute(position_elem, "y");
submesh.Vertices[i].z = mesh.RequireFloatAttribute(position_elem, "z");
submesh.Vertices[i].nx = mesh.RequireFloatAttribute(normal_elem, "x");
submesh.Vertices[i].ny = mesh.RequireFloatAttribute(normal_elem, "y");
submesh.Vertices[i].nz = mesh.RequireFloatAttribute(normal_elem, "z");
submesh.Vertices[i].u = mesh.RequireFloatAttribute(texcoord_elem, "u");
submesh.Vertices[i].v = mesh.RequireFloatAttribute(texcoord_elem, "v");
// Construct BoundingBox
if(i == 0 && SubMeshes.size() == 1)
{
// First vertex
BoundingBox.x1 = BoundingBox.x2 = submesh.Vertices[i].x;
BoundingBox.y1 = BoundingBox.y2 = submesh.Vertices[i].y;
BoundingBox.z1 = BoundingBox.z2 = submesh.Vertices[i].z;
}
else
{
BoundingBox.x1 = Min(submesh.Vertices[i].x, BoundingBox.x1);
BoundingBox.x2 = Max(submesh.Vertices[i].x, BoundingBox.x2);
BoundingBox.y1 = Min(submesh.Vertices[i].y, BoundingBox.y1);
BoundingBox.y2 = Max(submesh.Vertices[i].y, BoundingBox.y2);
BoundingBox.z1 = Min(submesh.Vertices[i].z, BoundingBox.z1);
BoundingBox.z2 = Max(submesh.Vertices[i].z, BoundingBox.z2);
}
}
TiXmlElement* faces_elem = mesh.RequireFirstChild(submesh_elem, "faces");
int FaceCount = mesh.RequireIntAttribute(faces_elem, "count");
submesh.Faces.resize(FaceCount);
i = 0;
for(TiXmlElement* face_elem = faces_elem->FirstChildElement("face"); face_elem != NULL && i < submesh.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]) >= submesh.Vertices.size())
mesh.Error(FormatString("Vertex index v%u (%d) is out of range", j+1, v[j]), face_elem);
submesh.Faces[i].Vertices[j] = v[j];
}
}
}
// Read skeleton, if any
TiXmlElement* skeletonlink_elem = mesh_elem->FirstChildElement("skeletonlink");
if(skeletonlink_elem)
{
const char* name = mesh.RequireStrAttribute(skeletonlink_elem, "name");
StdCopyStrBuf 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
bone->Parent = NULL;
// Index of bone will be set when building Master Bone Table later
TiXmlElement* position_elem = skeleton.RequireFirstChild(bone_elem, "position");
TiXmlElement* rotation_elem = skeleton.RequireFirstChild(bone_elem, "rotation");
TiXmlElement* axis_elem = skeleton.RequireFirstChild(rotation_elem, "axis");
StdMeshVector d, r;
d.x = skeleton.RequireFloatAttribute(position_elem, "x");
d.y = skeleton.RequireFloatAttribute(position_elem, "y");
d.z = skeleton.RequireFloatAttribute(position_elem, "z");
float angle = skeleton.RequireFloatAttribute(rotation_elem, "angle");
r.x = skeleton.RequireFloatAttribute(axis_elem, "x");
r.y = skeleton.RequireFloatAttribute(axis_elem, "y");
r.z = skeleton.RequireFloatAttribute(axis_elem, "z");
bone->Transformation.scale = StdMeshVector::UnitScale();
bone->Transformation.rotate = StdMeshQuaternion::AngleAxis(angle, r);
bone->Transformation.translate = d;
// We need this later for applying animations, and attaching meshes, therefore cache it here
bone->InverseTransformation = StdMeshTransformation::Inverse(bone->Transformation);
}
// 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);
}
// 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 for all vertices (need to go through SubMeshes again...)
unsigned int submesh_index = 0;
for(TiXmlElement* submesh_elem = submesh_elem_base; submesh_elem != NULL; submesh_elem = submesh_elem->NextSiblingElement("submesh"), ++submesh_index)
{
StdSubMesh& submesh = SubMeshes[submesh_index];
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) >= submesh.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);
StdSubMesh::Vertex& vertex = submesh.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 < submesh.Vertices.size(); ++i)
{
StdSubMesh::Vertex& vertex = submesh.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_elem->FirstChildElement("animations");
if (animations_elem)
{
for(TiXmlElement* animation_elem = animations_elem->FirstChildElement("animation"); animation_elem != NULL; animation_elem = animation_elem->NextSiblingElement("animation"))
{
StdCopyStrBuf 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(keyframe_elem, "scale");
TiXmlElement* axis_elem = skeleton.RequireFirstChild(rotate_elem, "axis");
StdMeshVector d, s, r;
d.x = skeleton.RequireFloatAttribute(translate_elem, "x");
d.y = skeleton.RequireFloatAttribute(translate_elem, "y");
d.z = skeleton.RequireFloatAttribute(translate_elem, "z");
s.x = skeleton.RequireFloatAttribute(scale_elem, "x");
s.y = skeleton.RequireFloatAttribute(scale_elem, "y");
s.z = skeleton.RequireFloatAttribute(scale_elem, "z");
float angle = skeleton.RequireFloatAttribute(rotate_elem, "angle");
r.x = skeleton.RequireFloatAttribute(axis_elem, "x");
r.y = skeleton.RequireFloatAttribute(axis_elem, "y");
r.z = skeleton.RequireFloatAttribute(axis_elem, "z");
frame.Transformation.scale = StdMeshVector::UnitScale();
frame.Transformation.rotate = StdMeshQuaternion::AngleAxis(angle, r);
frame.Transformation.translate = bone->InverseTransformation.rotate * (bone->InverseTransformation.scale * d);
}
}
}
}
}
else
{
// Mesh has no skeleton
for(TiXmlElement* submesh_elem = submesh_elem_base; submesh_elem != NULL; submesh_elem = submesh_elem->NextSiblingElement("submesh"))
{
TiXmlElement* boneassignments_elem = submesh_elem->FirstChildElement("boneassignments");
if(boneassignments_elem)
{
// Bone assignements do not make sense then, as the
// actual bones are defined in the skeleton file.
mesh.Error(StdStrBuf("Mesh has bone assignments, but no skeleton"), boneassignments_elem);
}
}
}
// Apply parent transformation to each bone transformation. We need to
// do this late since animation keyframe computation needs the bone
// transformations, not bone+parent.
for(unsigned int i = 0; i < Bones.size(); ++i)
{
if(Bones[i]->Parent)
{
// Apply parent transformation
Bones[i]->Transformation = Bones[i]->Parent->Transformation * Bones[i]->Transformation;
// Update inverse
Bones[i]->InverseTransformation = StdMeshTransformation::Inverse(Bones[i]->Transformation);
}
}
}
void StdMesh::AddMasterBone(StdMeshBone* bone)
{
bone->Index = Bones.size(); // Remember index in master bone table

View File

@ -21,29 +21,7 @@
#include <StdMeshMaterial.h>
// Loader for OGRE meshes. Currently supports XML files only.
class StdMeshError: public std::exception
{
public:
StdMeshError(const StdStrBuf& message, const char* file, unsigned int line);
virtual ~StdMeshError() throw() {}
virtual const char* what() const throw() { return Buf.getData(); }
protected:
StdCopyStrBuf Buf;
};
// 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 default-construted
// StdStrBuf with NULL data in case of error.
class StdMeshSkeletonLoader
{
public:
virtual StdStrBuf LoadSkeleton(const char* filename) = 0;
};
// OGRE mesh
struct StdMeshVector
{
@ -162,6 +140,7 @@ StdMeshVertex operator*(const StdMeshMatrix& lhs, const StdMeshVertex& rhs);
class StdMeshBone
{
friend class StdMesh;
friend class StdMeshLoader;
public:
StdMeshBone() {}
@ -210,7 +189,7 @@ public:
// Animation track, specifies transformation for one bone for each keyframe
class StdMeshTrack
{
friend class StdMesh;
friend class StdMeshLoader;
public:
StdMeshTransformation GetTransformAt(float time) const;
@ -221,7 +200,7 @@ private:
// Animation, consists of one Track for each animated Bone
class StdMeshAnimation
{
friend class StdMesh;
friend class StdMeshLoader;
friend class StdMeshInstance;
public:
StdMeshAnimation() {}
@ -246,6 +225,7 @@ struct StdMeshBox
class StdSubMesh
{
friend class StdMesh;
friend class StdMeshLoader;
public:
// Remember bone assignments for vertices
class Vertex: public StdMeshVertex
@ -273,16 +253,11 @@ private:
class StdMesh
{
//friend class StdMeshInstance;
public:
friend class StdMeshLoader;
StdMesh();
public:
~StdMesh();
// 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 StdSubMesh& GetSubMesh(unsigned int i) const { return SubMeshes[i]; }
unsigned int GetNumSubMeshes() const { return SubMeshes.size(); }

View File

@ -0,0 +1,81 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2010 Nicolas Hake
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
// A loader for the OGRE .mesh binary file format
#ifndef INC_StdMeshLoader
#define INC_StdMeshLoader
#include <stdexcept>
class StdMesh;
class StdMeshMatManager;
// 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 default-construted
// StdStrBuf with NULL data in case of error.
class StdMeshSkeletonLoader
{
public:
virtual StdStrBuf LoadSkeleton(const char* filename) = 0;
};
class StdMeshLoader
{
public:
class LoaderException : public std::runtime_error { public: LoaderException(const char *msg) : std::runtime_error(msg) {} };
// filename is only used to show it in error messages. The model is
// loaded from src. Throws LoaderException.
static StdMesh *LoadMeshBinary(const char *src, size_t size, const StdMeshMatManager &mat_mgr, StdMeshSkeletonLoader &loader, const char *filename = 0);
static StdMesh *LoadMeshXml(const char *src, size_t size, const StdMeshMatManager &mat_mgr, StdMeshSkeletonLoader &loader, const char *filename = 0);
private:
static void LoadSkeletonBinary(StdMesh *mesh, const char *src, size_t size);
};
#define DEFINE_EXCEPTION(_cls, _text) class _cls : public StdMeshLoader::LoaderException { public: _cls(const char *msg = _text) : LoaderException(msg) {} }
namespace Ogre
{
DEFINE_EXCEPTION(InsufficientData, "Premature end of data stream");
DEFINE_EXCEPTION(MultipleSingletonChunks, "A singleton chunk was found multiple times");
namespace Mesh
{
DEFINE_EXCEPTION(InvalidVersion, "Mesh header does not contain the expected version");
DEFINE_EXCEPTION(SharedVertexGeometryForbidden, "A CID_Geometry chunk was found in a submesh with shared vertices");
DEFINE_EXCEPTION(InvalidSubmeshOp, "The render operation of a CID_Submesh_Op chunk was invalid");
DEFINE_EXCEPTION(InvalidVertexType, "The vertex type of a CID_Geometry_Vertex_Decl_Element chunk was invalid");
DEFINE_EXCEPTION(InvalidVertexSemantic, "The vertex semantic of a CID_Geometry_Vertex_Decl_Element chunk was invalid");
DEFINE_EXCEPTION(InvalidVertexDeclaration, "The vertex declaration of a CID_Geometry chunk was invalid");
DEFINE_EXCEPTION(InvalidMaterial, "The material referenced by a mesh or submesh is not defined");
DEFINE_EXCEPTION(VertexNotFound, "A specified vertex was not found");
DEFINE_EXCEPTION(NotImplemented, "The requested operation is not implemented");
}
namespace Skeleton
{
DEFINE_EXCEPTION(InvalidVersion, "Skeleton header does not contain the expected version");
DEFINE_EXCEPTION(IdNotUnique, "An element with an unique ID appeared multiple times");
DEFINE_EXCEPTION(BoneNotFound, "A specified bone was not found");
DEFINE_EXCEPTION(MissingMasterBone, "The skeleton does not have a master bone");
DEFINE_EXCEPTION(MultipleBoneTracks, "An animation has multiple tracks for one bone");
}
}
#undef DEFINE_EXCEPTION
#endif

View File

@ -0,0 +1,339 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2010 Nicolas Hake
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
// A loader for the OGRE .mesh binary file format
#include "C4Include.h"
#include "C4Log.h"
#include "StdMesh.h"
#include "StdMeshLoader.h"
#include "StdMeshLoaderBinaryChunks.h"
#include "StdMeshLoaderDataStream.h"
#include "StdMeshMaterial.h"
#include <cassert>
#include <vector>
#include <boost/foreach.hpp>
#include <boost/ptr_container/ptr_map.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
namespace
{
bool VertexDeclarationIsSane(const boost::ptr_vector<Ogre::Mesh::ChunkGeometryVertexDeclElement> &decl)
{
bool semanticSeen[Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_MAX + 1] = { false };
BOOST_FOREACH(Ogre::Mesh::ChunkGeometryVertexDeclElement element, decl)
{
switch (element.semantic)
{
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_Texcoords:
// Normally, you can use multiple texture coordinates, but we currently support only one.
// So complain if we get multiple sets.
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_Position:
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_Normals:
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_Diffuse:
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_Specular:
// Only one set of each of these elements allowed
if (semanticSeen[element.semantic])
return false;
}
semanticSeen[element.semantic] = true;
}
return true;
}
template<size_t N>
void ReadNormalizedVertexData(float (&dest)[N], const char *source, Ogre::Mesh::ChunkGeometryVertexDeclElement::Type vdet)
{
BOOST_STATIC_ASSERT(N >= 4);
dest[0] = dest[1] = dest[2] = 0; dest[3] = 1;
switch (vdet)
{
// All VDET_Float* fall through.
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float4:
dest[3] = *reinterpret_cast<const float*>(source + sizeof(float) * 3);
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float3:
dest[2] = *reinterpret_cast<const float*>(source + sizeof(float) * 2);
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float2:
dest[1] = *reinterpret_cast<const float*>(source + sizeof(float) * 1);
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float1:
dest[0] = *reinterpret_cast<const float*>(source + sizeof(float) * 0);
break;
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Color_ABGR:
dest[3] = *reinterpret_cast<const float*>(source);
for (int i = 0; i < 3; ++i)
dest[i] = *reinterpret_cast<const float*>(source + sizeof(float) * (3 - i));
break;
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Color_ARGB:
dest[3] = *reinterpret_cast<const float*>(source);
for (int i = 0; i < 3; ++i)
dest[i] = *reinterpret_cast<const float*>(source + sizeof(float) * (i + 1));
break;
}
}
std::vector<StdSubMesh::Vertex> ReadSubmeshGeometry(const Ogre::Mesh::ChunkGeometry &geo)
{
if (!VertexDeclarationIsSane(geo.vertexDeclaration))
throw Ogre::Mesh::InvalidVertexDeclaration();
// Generate array of vertex buffer cursors
std::vector<const char *> cursors;
BOOST_FOREACH(const Ogre::Mesh::ChunkGeometryVertexBuffer &buf, geo.vertexBuffers)
cursors.push_back(static_cast<const char *>(buf.data->data));
// Generate vertices
std::vector<StdSubMesh::Vertex> vertices;
vertices.reserve(geo.vertexCount);
for (size_t i = 0; i < geo.vertexCount; ++i)
{
StdSubMesh::Vertex vertex;
vertex.nx = vertex.ny = vertex.nz = 0;
vertex.x = vertex.y = vertex.z = 0;
vertex.u = vertex.v = 0;
// Read vertex declaration
BOOST_FOREACH(Ogre::Mesh::ChunkGeometryVertexDeclElement element, geo.vertexDeclaration)
{
float values[4];
ReadNormalizedVertexData(values, cursors[element.source] + element.offset, element.type);
switch (element.semantic)
{
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_Position:
vertex.x = values[0];
vertex.y = values[1];
vertex.z = values[2];
break;
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_Normals:
vertex.nx = values[0];
vertex.ny = values[1];
vertex.nz = values[2];
break;
case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_Texcoords:
vertex.u = values[0];
vertex.v = values[1];
break;
}
}
vertices.push_back(vertex);
// Advance vertex buffer cursors
for (size_t cursor = 0; cursor < geo.vertexBuffers.size(); ++cursor)
cursors[cursor] += geo.vertexBuffers[cursor].vertexSize;
}
return vertices;
}
}
StdMesh *StdMeshLoader::LoadMeshBinary(const char *src, size_t length, const StdMeshMatManager &mat_mgr, StdMeshSkeletonLoader &loader, const char *filename)
{
boost::scoped_ptr<Ogre::Mesh::Chunk> root;
Ogre::DataStream stream(src, length);
// First chunk must be the header
root.reset(Ogre::Mesh::Chunk::Read(&stream));
if (root->GetType() != Ogre::Mesh::CID_Header)
throw Ogre::Mesh::InvalidVersion();
// Second chunk is the mesh itself
root.reset(Ogre::Mesh::Chunk::Read(&stream));
if (root->GetType() != Ogre::Mesh::CID_Mesh)
throw Ogre::Mesh::InvalidVersion();
// Generate mesh from data
Ogre::Mesh::ChunkMesh &cmesh = *static_cast<Ogre::Mesh::ChunkMesh*>(root.get());
std::auto_ptr<StdMesh> mesh(new StdMesh);
mesh->BoundingBox = cmesh.bounds;
// Read skeleton (if exists)
if (!cmesh.skeletonFile.empty())
{
StdStrBuf skel = loader.LoadSkeleton(cmesh.skeletonFile.c_str());
if (skel.isNull())
throw Ogre::InsufficientData("The specified skeleton file was not found");
LoadSkeletonBinary(mesh.get(), skel.getData(), skel.getLength());
}
// Build bone handle->index quick access table
std::map<uint16_t, size_t> bone_lookup;
for (size_t i = 0; i < mesh->GetNumBones(); ++i)
{
bone_lookup[mesh->GetBone(i).ID] = i;
}
// Read submeshes
mesh->SubMeshes.reserve(cmesh.submeshes.size());
for (size_t i = 0; i < cmesh.submeshes.size(); ++i)
{
mesh->SubMeshes.push_back(StdSubMesh());
StdSubMesh &sm = mesh->SubMeshes.back();
Ogre::Mesh::ChunkSubmesh &csm = cmesh.submeshes[i];
sm.Material = mat_mgr.GetMaterial(csm.material.c_str());
if (!sm.Material)
throw Ogre::Mesh::InvalidMaterial();
if (csm.operation != Ogre::Mesh::ChunkSubmesh::SO_TriList)
throw Ogre::Mesh::NotImplemented("Submesh operations other than TriList aren't implemented yet");
sm.Faces.resize(csm.faceVertices.size() / 3);
for (size_t face = 0; face < sm.Faces.size(); ++face)
{
sm.Faces[face].Vertices[0] = csm.faceVertices[face * 3 + 0];
sm.Faces[face].Vertices[1] = csm.faceVertices[face * 3 + 1];
sm.Faces[face].Vertices[2] = csm.faceVertices[face * 3 + 2];
}
Ogre::Mesh::ChunkGeometry &geo = *(csm.hasSharedVertices ? cmesh.geometry : csm.geometry);
sm.Vertices.swap(ReadSubmeshGeometry(geo));
// Read bone assignments
BOOST_FOREACH(const Ogre::Mesh::BoneAssignment &ba, csm.boneAssignments)
{
if (ba.vertex >= sm.GetNumVertices())
throw Ogre::Mesh::VertexNotFound();
if (bone_lookup.find(ba.bone) == bone_lookup.end())
throw Ogre::Skeleton::BoneNotFound();
StdMeshVertexBoneAssignment assignment;
assignment.BoneIndex = bone_lookup[ba.bone];
assignment.Weight = ba.weight;
sm.Vertices[ba.vertex].BoneAssignments.push_back(assignment);
}
// Normalize bone assignments
BOOST_FOREACH(StdSubMesh::Vertex &vertex, sm.Vertices)
{
float sum = 0;
BOOST_FOREACH(StdMeshVertexBoneAssignment &ba, vertex.BoneAssignments)
sum += ba.Weight;
BOOST_FOREACH(StdMeshVertexBoneAssignment &ba, vertex.BoneAssignments)
ba.Weight /= sum;
}
DebugLogF("Loaded submesh with %d faces, %d vertices, material %s", sm.GetNumFaces(), sm.GetNumVertices(), sm.GetMaterial().Name.getData());
}
return mesh.release();
}
void StdMeshLoader::LoadSkeletonBinary(StdMesh *mesh, const char *src, size_t size)
{
boost::scoped_ptr<Ogre::Skeleton::Chunk> chunk;
Ogre::DataStream stream(src, size);
// First chunk must be the header
chunk.reset(Ogre::Skeleton::Chunk::Read(&stream));
if (chunk->GetType() != Ogre::Skeleton::CID_Header)
throw Ogre::Skeleton::InvalidVersion();
boost::ptr_map<uint16_t, StdMeshBone> bones;
boost::ptr_vector<Ogre::Skeleton::ChunkAnimation> animations;
for (Ogre::Skeleton::ChunkID id = Ogre::Skeleton::Chunk::Peek(&stream);
id == Ogre::Skeleton::CID_Bone || id == Ogre::Skeleton::CID_Bone_Parent || id == Ogre::Skeleton::CID_Animation;
id = Ogre::Skeleton::Chunk::Peek(&stream)
)
{
std::auto_ptr<Ogre::Skeleton::Chunk> chunk(Ogre::Skeleton::Chunk::Read(&stream));
switch (chunk->GetType())
{
case Ogre::Skeleton::CID_Bone:
{
Ogre::Skeleton::ChunkBone &cbone = *static_cast<Ogre::Skeleton::ChunkBone*>(chunk.get());
// Check that the bone ID is unique
if (bones.find(cbone.handle) != bones.end())
throw Ogre::Skeleton::IdNotUnique();
StdMeshBone *bone = new StdMeshBone;
bone->Parent = NULL;
bone->ID = cbone.handle;
bone->Name = cbone.name.c_str();
bone->Transformation.translate = cbone.position;
bone->Transformation.rotate = cbone.orientation;
bone->Transformation.scale = cbone.scale;
bone->InverseTransformation = StdMeshTransformation::Inverse(bone->Transformation);
bones.insert(cbone.handle, bone);
}
break;
case Ogre::Skeleton::CID_Bone_Parent:
{
Ogre::Skeleton::ChunkBoneParent &cbparent = *static_cast<Ogre::Skeleton::ChunkBoneParent*>(chunk.get());
if (bones.find(cbparent.parentHandle) == bones.end() || bones.find(cbparent.childHandle) == bones.end())
throw Ogre::Skeleton::BoneNotFound();
bones[cbparent.parentHandle].Children.push_back(&bones[cbparent.childHandle]);
bones[cbparent.childHandle].Parent = &bones[cbparent.parentHandle];
}
break;
case Ogre::Skeleton::CID_Animation:
// Collect animations for later (need bone table index, which we don't know yet)
animations.push_back(static_cast<Ogre::Skeleton::ChunkAnimation*>(chunk.release()));
break;
}
if (stream.AtEof()) break;
}
// Find master bone (i.e., the one without a parent)
StdMeshBone *master = NULL;
for (boost::ptr_map<uint16_t, StdMeshBone>::iterator it = bones.begin(); it != bones.end(); ++it)
{
if (!it->second->Parent)
{
if (master)
DebugLogF("More than one master bone: %s has no parent, but %s already master", it->second->Name.getData(), master->Name.getData());
master = it->second;
mesh->AddMasterBone(master);
}
}
if (!master)
throw Ogre::Skeleton::MissingMasterBone();
// Transfer bone ownership to mesh (double .release() is correct)
bones.release().release();
// Build handle->index quick access table
std::map<uint16_t, size_t> handle_lookup;
for (size_t i = 0; i < mesh->GetNumBones(); ++i)
{
handle_lookup[mesh->GetBone(i).ID] = i;
}
// Fixup animations
BOOST_FOREACH(Ogre::Skeleton::ChunkAnimation &canim, animations)
{
StdMeshAnimation &anim = mesh->Animations[StdCopyStrBuf(canim.name.c_str())];
anim.Name = canim.name.c_str();
anim.Length = canim.duration;
anim.Tracks.resize(mesh->GetNumBones());
BOOST_FOREACH(Ogre::Skeleton::ChunkAnimationTrack &catrack, canim.tracks)
{
const StdMeshBone &bone = mesh->GetBone(handle_lookup[catrack.bone]);
StdMeshTrack *&track = anim.Tracks[bone.Index];
if (track != NULL)
throw Ogre::Skeleton::MultipleBoneTracks();
track = new StdMeshTrack;
BOOST_FOREACH(Ogre::Skeleton::ChunkAnimationTrackKF &catkf, catrack.keyframes)
{
StdMeshKeyFrame &kf = track->Frames[catkf.time];
kf.Transformation.rotate = catkf.rotation;
kf.Transformation.scale = catkf.scale;
kf.Transformation.translate = bone.InverseTransformation.rotate * (bone.InverseTransformation.scale * catkf.translation);
}
}
}
// Fixup bone transforms
BOOST_FOREACH(StdMeshBone *bone, mesh->Bones)
{
if (bone->Parent)
{
bone->Transformation = bone->Parent->Transformation * bone->Transformation;
bone->InverseTransformation = StdMeshTransformation::Inverse(bone->Transformation);
}
}
DebugLogF("Loaded skeleton with %d bones, %d animations", mesh->GetNumBones(), animations.size());
}

View File

@ -0,0 +1,454 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2010 Nicolas Hake
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
#include "C4Include.h"
#include "StdMeshLoaderBinaryChunks.h"
#include "StdMeshLoaderDataStream.h"
#include <cassert>
#include <boost/static_assert.hpp>
#include <string>
namespace Ogre
{
namespace Mesh
{
const std::string ChunkFileHeader::ExpectedVersion("[MeshSerializer_v1.41]");
// Chunk factory
Chunk *Chunk::Read(DataStream *stream)
{
assert(stream->GetRemainingBytes() >= ChunkHeaderLength);
// Read metadata
ChunkID id = CID_Invalid;
id = static_cast<ChunkID>(stream->Read<uint16_t>());
size_t size = 0;
// Special case: CID_Header doesn't have any size info.
if (id != CID_Header)
{
// All others are proper chunks.
size = stream->Read<uint32_t>();
size -= ChunkHeaderLength;
}
// Create chunk
std::auto_ptr<Chunk> chunk;
switch (id)
{
case CID_Header: chunk.reset(new ChunkFileHeader()); break;
case CID_Mesh: chunk.reset(new ChunkMesh()); break;
case CID_Mesh_Bone_Assignment:
case CID_Submesh_Bone_Assignment:
chunk.reset(new ChunkMeshBoneAssignments()); break;
case CID_Mesh_Skeleton_Link: chunk.reset(new ChunkMeshSkeletonLink()); break;
case CID_Mesh_Bounds: chunk.reset(new ChunkMeshBounds()); break;
case CID_Submesh: chunk.reset(new ChunkSubmesh()); break;
case CID_Submesh_Op: chunk.reset(new ChunkSubmeshOp()); break;
case CID_Geometry: chunk.reset(new ChunkGeometry()); break;
case CID_Geometry_Vertex_Buffer: chunk.reset(new ChunkGeometryVertexBuffer()); break;
case CID_Geometry_Vertex_Data: chunk.reset(new ChunkGeometryVertexData()); break;
case CID_Geometry_Vertex_Decl: chunk.reset(new ChunkGeometryVertexDecl()); break;
case CID_Geometry_Vertex_Decl_Element: chunk.reset(new ChunkGeometryVertexDeclElement()); break;
default:
LogF("StdMeshLoader: I don't know what to do with a chunk of type 0x%xu", id);
// Fall through
case CID_Edge_List: case CID_Submesh_Name_Table:
// We don't care about these
chunk.reset(new ChunkUnknown()); break;
};
chunk->type = id;
chunk->size = size;
chunk->ReadImpl(stream);
return chunk.release();
}
void ChunkUnknown::ReadImpl(DataStream *stream) { stream->Seek(GetSize()); }
void ChunkFileHeader::ReadImpl(DataStream *stream)
{
// Simple version check
version = stream->Read<std::string>();
if (version != ExpectedVersion)
throw InvalidVersion();
}
void ChunkMesh::ReadImpl(DataStream *stream)
{
hasAnimatedSkeleton = stream->Read<bool>();
for (ChunkID id = Chunk::Peek(stream);
id == CID_Geometry || id == CID_Submesh || id == CID_Mesh_Skeleton_Link || id == CID_Mesh_Bone_Assignment || id == CID_Mesh_LOD || id == CID_Submesh_Name_Table || id == CID_Mesh_Bounds || id == CID_Edge_List || id == CID_Pose_List || id == CID_Animation_List;
id = Chunk::Peek(stream)
)
{
Chunk *chunk = Chunk::Read(stream);
switch (chunk->GetType())
{
case CID_Geometry:
if (geometry)
{
delete chunk;
throw MultipleSingletonChunks("There's only one CID_Geometry chunk allowed within a CID_Mesh chunk");
}
geometry.reset(static_cast<ChunkGeometry*>(chunk));
break;
case CID_Submesh:
submeshes.push_back(static_cast<ChunkSubmesh*>(chunk));
break;
case CID_Mesh_Skeleton_Link:
if (!skeletonFile.empty())
{
delete chunk;
throw MultipleSingletonChunks("There's only one CID_Mesh_Skeleton_Link chunk allowed within a CID_Mesh chunk");
}
skeletonFile = static_cast<ChunkMeshSkeletonLink*>(chunk)->skeleton;
delete chunk;
break;
case CID_Mesh_Bounds:
bounds = static_cast<ChunkMeshBounds*>(chunk)->bounds;
delete chunk;
break;
case CID_Mesh_Bone_Assignment:
default:
LogF("StdMeshLoader: I don't know what to do with a chunk of type 0x%xu inside a CID_Mesh chunk", chunk->GetType());
delete chunk;
break;
}
if (stream->AtEof()) break;
}
}
void ChunkMeshSkeletonLink::ReadImpl(DataStream *stream)
{
skeleton = stream->Read<std::string>();
}
void ChunkSubmesh::ReadImpl(DataStream *stream)
{
operation = SO_TriList; // default if no CID_Submesh_Op chunk exists
material = stream->Read<std::string>();
hasSharedVertices = stream->Read<bool>();
size_t index_count = stream->Read<uint32_t>();
bool indexes_are_32bit = stream->Read<bool>();
faceVertices.reserve(index_count);
while(index_count--)
{
size_t index;
if (indexes_are_32bit)
index = stream->Read<uint32_t>();
else
index = stream->Read<uint16_t>();
faceVertices.push_back(index);
}
for (ChunkID id = Chunk::Peek(stream);
id == CID_Geometry || id == CID_Submesh_Op || id == CID_Submesh_Bone_Assignment;
id = Chunk::Peek(stream)
)
{
Chunk *chunk = Chunk::Read(stream);
switch (chunk->GetType())
{
case CID_Geometry:
if (hasSharedVertices)
{
// Can't have own vertices and at the same time use those of the parent
delete chunk;
throw SharedVertexGeometryForbidden();
}
if (geometry)
{
delete chunk;
throw MultipleSingletonChunks("There's only one CID_Geometry chunk allowed within a CID_Submesh chunk");
}
geometry.reset(static_cast<ChunkGeometry*>(chunk));
break;
case CID_Submesh_Op:
operation = static_cast<ChunkSubmeshOp*>(chunk)->operation;
delete chunk;
break;
case CID_Submesh_Bone_Assignment:
{
// Collect bone assignments
ChunkMeshBoneAssignments *assignments = static_cast<ChunkMeshBoneAssignments*>(chunk);
boneAssignments.insert(boneAssignments.end(), assignments->assignments.begin(), assignments->assignments.end());
}
delete chunk;
break;
default:
LogF("StdMeshLoader: I don't know what to do with a chunk of type 0x%xu inside a CID_Submesh chunk", chunk->GetType());
delete chunk;
break;
}
if (stream->AtEof()) break;
}
}
void ChunkSubmeshOp::ReadImpl(DataStream *stream)
{
uint32_t op = stream->Read<uint16_t>();
if (op < ChunkSubmesh::SO_MIN || op > ChunkSubmesh::SO_MAX)
throw InvalidSubmeshOp();
operation = static_cast<ChunkSubmesh::SubmeshOperation>(op);
}
void ChunkMeshBoneAssignments::ReadImpl(DataStream *stream)
{
size_t bone_count = GetSize() / (sizeof(uint32_t)+sizeof(uint16_t)+sizeof(float));
BoneAssignment assignment;
while (bone_count-- > 0)
{
assignment.vertex = stream->Read<uint32_t>();
assignment.bone = stream->Read<uint16_t>();
assignment.weight = stream->Read<float>();
assignments.push_back(assignment);
}
}
void ChunkMeshBounds::ReadImpl(DataStream *stream)
{
bounds.x1 = stream->Read<float>();
bounds.y1 = stream->Read<float>();
bounds.z1 = stream->Read<float>();
bounds.x2 = stream->Read<float>();
bounds.y2 = stream->Read<float>();
bounds.z2 = stream->Read<float>();
radius = stream->Read<float>();
}
void ChunkGeometry::ReadImpl(DataStream *stream)
{
vertexCount = stream->Read<uint32_t>();
for (ChunkID id = Chunk::Peek(stream);
id == CID_Geometry_Vertex_Decl || id == CID_Geometry_Vertex_Buffer;
id = Chunk::Peek(stream)
)
{
Chunk *chunk = Chunk::Read(stream);
switch (chunk->GetType())
{
case CID_Geometry_Vertex_Decl:
if (!vertexDeclaration.empty())
{
delete chunk;
throw MultipleSingletonChunks("There's only one CID_Geometry_Vertex_Decl chunk allowed within a CID_Geometry chunk");
}
vertexDeclaration.swap(static_cast<ChunkGeometryVertexDecl*>(chunk)->declaration);
delete chunk;
break;
case CID_Geometry_Vertex_Buffer:
vertexBuffers.push_back(static_cast<ChunkGeometryVertexBuffer*>(chunk));
break;
default:
LogF("StdMeshLoader: I don't know what to do with a chunk of type 0x%xu inside a CID_Geometry chunk", chunk->GetType());
delete chunk;
break;
}
if (stream->AtEof()) break;
}
}
void ChunkGeometryVertexDecl::ReadImpl(DataStream *stream)
{
while (Chunk::Peek(stream) == CID_Geometry_Vertex_Decl_Element)
{
Chunk *chunk = Chunk::Read(stream);
assert(chunk->GetType() == CID_Geometry_Vertex_Decl_Element);
declaration.push_back(static_cast<ChunkGeometryVertexDeclElement*>(chunk));
if (stream->AtEof()) break;
}
}
void ChunkGeometryVertexDeclElement::ReadImpl(DataStream *stream)
{
source = stream->Read<uint16_t>();
uint32_t t = stream->Read<uint16_t>();
if (t < VDET_MIN || t > VDET_MAX)
throw InvalidVertexType();
type = static_cast<Type>(t);
t = stream->Read<uint16_t>();
if (t < VDES_MIN || t > VDES_MAX)
throw InvalidVertexSemantic();
semantic = static_cast<Semantic>(t);
offset = stream->Read<uint16_t>();
index = stream->Read<uint16_t>();
}
void ChunkGeometryVertexBuffer::ReadImpl(DataStream *stream)
{
index = stream->Read<uint16_t>();
vertexSize = stream->Read<uint16_t>();
while (Chunk::Peek(stream) == CID_Geometry_Vertex_Data)
{
Chunk *chunk = Chunk::Read(stream);
assert(chunk->GetType() == CID_Geometry_Vertex_Data);
if (data)
{
delete chunk;
throw MultipleSingletonChunks("There's only one CID_Geometry_Vertex_Data chunk allowed within a CID_Geometry_Vertex_Buffer chunk");
}
data.reset(static_cast<ChunkGeometryVertexData*>(chunk));
if (stream->AtEof()) break;
}
}
void ChunkGeometryVertexData::ReadImpl(DataStream *stream)
{
data = new char[GetSize()];
stream->Read(data, GetSize());
}
}
namespace Skeleton
{
const std::string ChunkFileHeader::ExpectedVersion("[Serializer_v1.10]");
Chunk *Chunk::Read(DataStream *stream)
{
assert(stream->GetRemainingBytes() >= ChunkHeaderLength);
// Read metadata
ChunkID id = CID_Invalid;
id = static_cast<ChunkID>(stream->Read<uint16_t>());
size_t size = 0;
// Special case: CID_Header doesn't have any size info.
if (id != CID_Header)
{
// All others are proper chunks.
size = stream->Read<uint32_t>();
size -= ChunkHeaderLength;
}
// Create chunk
std::auto_ptr<Chunk> chunk;
switch (id)
{
case CID_Header: chunk.reset(new ChunkFileHeader()); break;
case CID_Bone: chunk.reset(new ChunkBone()); break;
case CID_Bone_Parent: chunk.reset(new ChunkBoneParent()); break;
case CID_Animation: chunk.reset(new ChunkAnimation()); break;
case CID_Animation_Track: chunk.reset(new ChunkAnimationTrack()); break;
case CID_Animation_Track_KF: chunk.reset(new ChunkAnimationTrackKF()); break;
case CID_Animation_Link: chunk.reset(new ChunkAnimationLink()); break;
default:
LogF("StdMeshLoader: I don't know what to do with a chunk of type 0x%xu", id);
chunk.reset(new ChunkUnknown()); break;
};
chunk->type = id;
chunk->size = size;
chunk->ReadImpl(stream);
return chunk.release();
}
void ChunkUnknown::ReadImpl(DataStream *stream) { stream->Seek(GetSize()); }
void ChunkFileHeader::ReadImpl(DataStream *stream)
{
// Simple version check
version = stream->Read<std::string>();
if (version != ExpectedVersion)
throw InvalidVersion();
}
void ChunkBone::ReadImpl(DataStream *stream)
{
name = stream->Read<std::string>();
handle = stream->Read<uint16_t>();
position.x = stream->Read<float>();
position.y = stream->Read<float>();
position.z = stream->Read<float>();
orientation.x = stream->Read<float>();
orientation.y = stream->Read<float>();
orientation.z = stream->Read<float>();
orientation.w = stream->Read<float>();
// Guess whether we have a scale element
if (GetSize() > name.size() + 1 + sizeof(handle) + sizeof(float) * 7)
{
scale.x = stream->Read<float>();
scale.y = stream->Read<float>();
scale.z = stream->Read<float>();
}
else
{
scale = StdMeshVector::UnitScale();
}
}
void ChunkBoneParent::ReadImpl(DataStream *stream)
{
childHandle = stream->Read<uint16_t>();
parentHandle = stream->Read<uint16_t>();
}
void ChunkAnimation::ReadImpl(DataStream *stream)
{
name = stream->Read<std::string>();
duration = stream->Read<float>();
while (Chunk::Peek(stream) == CID_Animation_Track)
{
Chunk *chunk = Chunk::Read(stream);
assert(chunk->GetType() == CID_Animation_Track);
tracks.push_back(static_cast<ChunkAnimationTrack*>(chunk));
if (stream->AtEof()) break;
}
}
void ChunkAnimationTrack::ReadImpl(DataStream *stream)
{
bone = stream->Read<uint16_t>();
while (Chunk::Peek(stream) == CID_Animation_Track_KF)
{
Chunk *chunk = Chunk::Read(stream);
assert(chunk->GetType() == CID_Animation_Track_KF);
keyframes.push_back(static_cast<ChunkAnimationTrackKF*>(chunk));
if (stream->AtEof()) break;
}
}
void ChunkAnimationTrackKF::ReadImpl(DataStream *stream)
{
time = stream->Read<float>();
rotation.x = stream->Read<float>();
rotation.y = stream->Read<float>();
rotation.z = stream->Read<float>();
rotation.w = stream->Read<float>();
translation.x = stream->Read<float>();
translation.y = stream->Read<float>();
translation.z = stream->Read<float>();
// Guess whether we have a scale element
if (GetSize() > sizeof(float) * 8)
{
scale.x = stream->Read<float>();
scale.y = stream->Read<float>();
scale.z = stream->Read<float>();
}
else
{
scale = StdMeshVector::UnitScale();
}
}
void ChunkAnimationLink::ReadImpl(DataStream *stream)
{
file = stream->Read<std::string>();
scale.x = stream->Read<float>();
scale.y = stream->Read<float>();
scale.z = stream->Read<float>();
}
}
}

View File

@ -0,0 +1,633 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2010 Nicolas Hake
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
#ifndef INC_StdMeshLoaderChunks
#define INC_StdMeshLoaderChunks
#include "Standard.h"
#include "StdMesh.h"
#include "StdMeshLoaderDataStream.h"
#include <boost/scoped_ptr.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/static_assert.hpp>
// ==== Ogre file format ====
// The Ogre file format is a chunked format similar to PNG.
// Each chunk except for the file header (type 0x1000) has the following format:
// uint32_t chunk_type, values dependent on file type (mesh or skeleton)
// uint32_t chunk_length, in bytes; includes the size of the header
// uint8_t data[]
// The file header omits the length field.
// Ogre files can be stored in either big-endian or little-endian byte order. The
// byte order is determined by the first two bytes in the file, which must always
// be the chunk type of the file header (0x1000). If the bytes are 0x00 0x10, the
// file is assumed to be little-endian, if they are 0x10 0x00, the file is big-
// endian. If any other bytes are encountered, the file is invalid.
// This implementation only reads files stored in the platform's native byte order.
//
// ==== Data types ====
// Most of the numeric data is stored directly in binary as a memory representation.
// Strings are stored without a leading length field, and terminated by a 0x0A byte.
// Boolean fields are stored as a single byte, which is 0x00 for false or anything
// else for true.
//
// ==== Mesh chunks ====
// Unless specified, this section will only describe the data part of each chunk.
// Any values that are in parentheses are not available in this implementation.
//
// Type 0x1000: File Header
// string version
// Depending on the version, different features are enabled or disabled while
// loading the file.
// This implementation does not support fallback to different versions of the
// file format.
// This chunk does not contain a length field; the type id is directly followed
// by the version string.
// The file header must be immediately followed by a 0x3000 Mesh Data chunk.
// ----------
// Type 0x3000: Mesh Data
// bool skeletal_animation
// This field is only used by Ogre to optimize rendering. Set if the mesh has
// animation data associated with it.
// Allowed subchunks:
// 0x4000 Submesh Data
// 0x5000 Geometry Data (may occur at most once)
// 0x6000 Skeleton Link (may occur at most once)
// 0x7000 Mesh Bone Assignments
// (0x8000 Precomputed Level of Detail Data)
// 0x9000 Mesh Boundaries
// (0xA000 Submesh Name Table)
// (0xB000 Edge List)
// (0xC000 Animation Poses)
// (0xD000 Animation List)
// (0xE000 Table of Extremes)
// ----------
// Type 0x4000: Submesh data
// string material
// The name of a material to be used for this submesh. Must be loaded externally.
// bool vertices_shared
// If set, the submesh uses the geometry data of its parent mesh. In this case, the
// submesh may not contain its own 0x5000 Geometry Data chunk.
// uint32_t index_count
// The number of vertex indices used by this submesh.
// bool indices_are_32_bit
// If set, each vertex index is 32 bits wide. If unset, each vertex index is 16 bits
// wide.
// uint16_t/uint32_t indices[index_count]
// A list of vertex indices used by this submesh.
// Allowed subchunks:
// 0x5000 Geometry Data (only if vertices_shared == false)
// 0x4010 Submesh Operation
// 0x4100 Submesh Bone Assignments
// (0x4200 Submesh Texture Replacement)
// If no 0x4010 Submesh Operation chunk is present, the submesh uses SO_TriList.
// ----------
// Type 0x4010: Submesh Operation
// uint16_t operation
// This value specifies how the indices of a submesh are to be interpreted.
// Acceptable values:
// (0x01 SO_PointList)
// Each index defines a single point.
// (0x02 SO_LineList)
// Each pair (2k, 2k+1) of indices defines a line.
// (0x03 SO_LineStrip)
// Each pair (k, k+1) of indices defines a line.
// 0x04 SO_TriList
// Each triplet (3k, 3k+1, 3k+2) of indices defines a triangle.
// (0x05 SO_TriStrip)
// For odd k, (k, k+1, k+2) define a triangle. For even k, (k+1, k, k+2) define a triangle.
// (0x06 SO_TriFan)
// Indices (1, k+1, k+2) define a triangle.
// ----------
// Type 0x4100: Submesh Bone Assignments
// (Type 0x7000: Mesh Bone Assignments)
// uint32_t vertex_index
// uint32_t bone_index
// float weight
// This defines the strength of the influence a bone has on a vertex. The sum of
// the weights on each vertex is not guaranteed to be 1.0.
// These values repeat until the size of the chunk is exhausted.
// ----------
// Type 0x5000: Geometry Data
// uint32_t vertex_count
// The number of vertices stored in each contained 0x5020 Vertex Buffer chunk.
// Allowed subchunks:
// 0x5100 Vertex Declaration (required; must occur exactly once)
// 0x5200 Vertex Buffer (required; must occur at least once)
// ----------
// Type 0x5100 Vertex Declaration
// This chunk does not store any values itself. It only acts as a container for
// 0x5110 Vertex Declaration Element chunks.
// Allowed subchunks:
// 0x5110 Vertex Declaration Element (required; repeats until the size of the chunk is exhausted)
// ----------
// Type 0x5110 Vertex Declaration Element
// uint16_t source
// The index of the stream where this element is found.
// uint16_t type
// The type of data this element contains.
// Acceptable values:
// 0x00 VDET_Float1
// A single 32-bit float, expanded to (a, 0, 0, 1)
// 0x01 VDET_Float2
// Two 32-bit floats, expanded to (a, b, 0, 1)
// 0x02 VDET_Float3
// Three 32-bit floats, expanded to (a, b, c, 1)
// 0x03 VDET_Float4
// Four 32-bit floats
// (0x04 VDET_Reserved)
// (0x05 VDET_Short1)
// A single 16-bit integer
// (0x06 VDET_Short2)
// Two 16-bit integers
// (0x07 VDET_Short3)
// Three 16-bit integers
// (0x08 VDET_Short4)
// Four 16-bit integers
// (0x09 VDET_UByte4)
// Four 8-bit integers
// 0x0A VDET_Color_ARGB
// Four 32-bit floats, describing a color in the order Alpha, Red, Green, Blue.
// Acceptable values range from 0.0 to 1.0.
// 0x0B VDET_Color_ABGR
// Four 32-bit floats, describing a color in the order Alpha, Blue, Green, Red.
// Acceptable values range from 0.0 to 1.0.
// uint16_t semantic
// The semantic of the data this element contains.
// Acceptable values:
// 0x01 VDES_Position
// The element contains the vertex's position in world space.
// (0x02 VDES_Blend_Weights)
// (0x03 VDES_Blend_Indices)
// 0x04 VDES_Normals
// The element contains vertex normals.
// (0x05 VDES_Diffuse)
// The element contains the diffuse color of the vertex.
// (0x06 VDES_Specular)
// The element contains the specular color of the vertex.
// 0x07 VDES_Texcoords
// The element contains the texture coordinates of the vertex.
// uint16_t offset
// The element's offset in bytes from the beginning of the stream.
// uint16_t index
// Index of the element semantic. Used with colors and texture coordinates.
// ----------
// 0x5200 Vertex Buffer
// uint16_t index
// The index of the stream this buffer will get bound to.
// uint16_t stride
// The distance in bytes between two elements inside the buffer.
// Allowed subchunks:
// 0x5210 Vertex Buffer Data (required; must occur exactly once)
// ----------
// 0x5210 Vertex Buffer Data
// uint8_t data[]
// The buffered data. This field spans the whole extent of the chunk.
// ----------
// 0x6000 Skeleton Link
// string file
// The name of the file that contains the skeleton of this mesh.
// ----------
// 0x9000 Mesh Boundaries
// float min[3]
// The minimum extents of the axis-aligned bounding box of the mesh.
// float max[3]
// The maximum extents of the axis-aligned bounding box of the mesh.
// float radius
// The radius of the minimal enclosing sphere of the mesh.
//
// ==== Skeleton files ====
// Each skeleton file must begin with a 0x1000 File Header chunk. Afterwards,
// any combination of the following chunks may appear:
// 0x2000 Bone Data
// 0x3000 Bone Hierarchy
// 0x4000 Animation
// (0x5000 Animation Link)
//
// Unless specified, this section will only describe the data part of each chunk.
// Any values that are in parentheses are not available in this implementation.
//
// Type 0x1000: File Header
// string version
// Depending on the version, different features are enabled or disabled while
// loading the file.
// This implementation does not support fallback to different versions of the
// file format.
// This chunk does not contain a length field; the type id is directly followed
// by the version string.
// ----------
// Type 0x2000: Bone Data
// string name
// The name of the bone. This is only used to produce human-readable output.
// uint16_t handle
// The internal handle of the bone. All other chunks that refer to a bone reference
// this.
// float position[3]
// The position of the bone, relative to its parent.
// float orientation[4]
// The orientation of the bone, as a quaternion (x,y,z,w). Relative to the parent.
// float scale[3]
// The scale of the bone, relative to its parent. This element only appears if the
// chunk size is large enough; if it is omitted, it defaults to (1,1,1).
// ----------
// Type 0x3000 Bone Hierarchy
// uint16_t child
// The handle of the parent bone.
// uint16_t parent
// The handle of the parent bone.
// ----------
// Type 0x4000 Animation
// string name
// The name of this animation.
// float duration
// The length of this animation, in seconds.
// Allowed subchunks:
// 0x4100 Animation Track
// ----------
// Type 0x4100 Animation Track
// uint16_t bone
// The handle of the bone this track belongs to.
// Allowed subchunks:
// 0x4110 Animation Track Keyframe
// ----------
// Type 0x4110 Animation Track Keyframe
// float time
// The time this keyframe matches.
// float rotation[4]
// The rotation of the bone at the corresponding time, as a quaternion (x,y,z,w).
// float translation[3]
// The translation of the bone at the corresponding time.
// float scale[3]
// The scale of the bone at the time of the keyframe. This element only appears if the
// chunk size is large enough; if it is omitted, it defaults to (1,1,1).
// Most of the chunk classes below faithfully match the abovementioned file format.
namespace Ogre
{
class DataStream;
template<class _Type>
class ChunkBase
{
protected:
ChunkBase() : type(static_cast<Type>(0)) {}
virtual void ReadImpl(DataStream *stream) = 0;
typedef _Type Type;
Type type;
size_t size;
public:
virtual ~ChunkBase() {}
Type GetType() const { return type; }
size_t GetSize() const { return size; }
static const size_t ChunkHeaderLength = sizeof(unsigned short) + sizeof(unsigned long);
static Type Peek(const DataStream *stream)
{
return static_cast<Type>(stream->Peek<uint16_t>());
}
};
namespace Mesh
{
enum ChunkID
{
CID_Invalid = 0,
CID_Header = 0x1000,
CID_Mesh = 0x3000,
CID_Submesh = 0x4000,
CID_Submesh_Op = 0x4010,
CID_Submesh_Bone_Assignment = 0x4100,
CID_Submesh_Texture_Alias = 0x4200,
CID_Geometry = 0x5000,
CID_Geometry_Vertex_Decl = 0x5100,
CID_Geometry_Vertex_Decl_Element = 0x5110,
CID_Geometry_Vertex_Buffer = 0x5200,
CID_Geometry_Vertex_Data = 0x5210,
CID_Mesh_Skeleton_Link = 0x6000,
CID_Mesh_Bone_Assignment = 0x7000,
CID_Mesh_LOD = 0x8000,
CID_Mesh_LOD_Usage = 0x8100,
CID_Mesh_LOD_Manual = 0x8110,
CID_Mesh_LOD_Generated = 0x8120,
CID_Mesh_Bounds = 0x9000,
CID_Submesh_Name_Table = 0xA000,
CID_Submesh_Name_Table_Entry = 0xA100,
CID_Edge_List = 0xB000,
CID_Edge_List_LOD = 0xB100,
CID_Edge_Group = 0xB110,
CID_Pose_List = 0xC000,
CID_Pose = 0xC100,
CID_Pose_Vertex = 0xC111,
CID_Animation_List = 0xD000,
CID_Animation = 0xD100,
CID_Animation_Track = 0xD110,
CID_Animation_Morph_Keyframe = 0xD111,
CID_Animation_Pose_Keyframe = 0xD112,
CID_Animation_Pose_Ref = 0xD113,
CID_Table_Extremes = 0xE000
};
struct BoneAssignment
{
uint32_t vertex;
uint32_t bone;
float weight;
};
class Chunk : public ChunkBase<ChunkID>
{
public:
static Chunk *Read(DataStream *stream);
};
class ChunkUnknown; class ChunkFileHeader;
class ChunkMesh; class ChunkMeshSkeletonLink; class ChunkMeshBoneAssignments; class ChunkMeshBounds;
class ChunkSubmesh; class ChunkSubmeshOp;
class ChunkGeometry; class ChunkGeometryVertexDecl; class ChunkGeometryVertexDeclElement; class ChunkGeometryVertexBuffer; class ChunkGeometryVertexData;
class ChunkPoseList; class ChunkPose; class ChunkPoseVertex;
class ChunkAnimationList; class ChunkAnimation; class ChunkAnimationTrack;
class ChunkAnimationMorphKF; class ChunkAnimationPoseKF; class ChunkAnimationPoseRef;
class ChunkUnknown : public Chunk
{
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkFileHeader : public Chunk
{
static const std::string ExpectedVersion;
public:
std::string version;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkMesh : public Chunk
{
public:
ChunkMesh() : hasAnimatedSkeleton(false) {}
bool hasAnimatedSkeleton;
std::string skeletonFile;
boost::scoped_ptr<ChunkGeometry> geometry;
boost::ptr_vector<ChunkSubmesh> submeshes;
StdMeshBox bounds;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkMeshSkeletonLink : public Chunk
{
public:
std::string skeleton;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkSubmesh : public Chunk
{
public:
ChunkSubmesh() : operation(SO_TriList) {}
std::string material;
bool hasSharedVertices;
std::vector<size_t> faceVertices;
boost::scoped_ptr<ChunkGeometry> geometry;
enum SubmeshOperation
{
SO_PointList = 1,
SO_LineList = 2,
SO_LineStrip = 3,
SO_TriList = 4,
SO_TriStrip = 5,
SO_TriFan = 6,
SO_MIN = SO_PointList,
SO_MAX = SO_TriFan
} operation;
std::vector<BoneAssignment> boneAssignments;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkSubmeshOp : public Chunk
{
public:
ChunkSubmesh::SubmeshOperation operation;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkMeshBoneAssignments : public Chunk
{
public:
std::vector<BoneAssignment> assignments;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkMeshBounds : public Chunk
{
public:
StdMeshBox bounds;
float radius;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkGeometry : public Chunk
{
public:
size_t vertexCount;
boost::ptr_vector<ChunkGeometryVertexDeclElement> vertexDeclaration;
boost::ptr_vector<ChunkGeometryVertexBuffer> vertexBuffers;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkGeometryVertexDecl : public Chunk
{
public:
boost::ptr_vector<ChunkGeometryVertexDeclElement> declaration;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkGeometryVertexDeclElement : public Chunk
{
public:
uint16_t source;
uint16_t offset;
enum Type
{
VDET_Float1 = 0,
VDET_Float2 = 1,
VDET_Float3 = 2,
VDET_Float4 = 3,
/* 4 omitted intentionally */
VDET_Short1 = 5,
VDET_Short2 = 6,
VDET_Short3 = 7,
VDET_Short4 = 8,
VDET_UByte4 = 9,
VDET_Color_ARGB = 10,
VDET_Color_ABGR = 11,
VDET_MIN = VDET_Float1,
VDET_MAX = VDET_Color_ABGR
} type;
enum Semantic
{
VDES_Position = 1,
VDES_Blend_Weights = 2,
VDES_Blend_Indices = 3,
VDES_Normals = 4,
VDES_Diffuse = 5,
VDES_Specular = 6,
VDES_Texcoords = 7,
VDES_MIN = VDES_Position,
VDES_MAX = VDES_Texcoords
} semantic;
uint16_t index;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkGeometryVertexBuffer : public Chunk
{
public:
uint16_t index;
uint16_t vertexSize;
boost::scoped_ptr<ChunkGeometryVertexData> data;
protected:
void ReadImpl(DataStream *stream);
};
class ChunkGeometryVertexData : public Chunk
{
public:
ChunkGeometryVertexData() : data(NULL) {}
~ChunkGeometryVertexData() { delete[] static_cast<char*>(data); }
void *data;
protected:
void ReadImpl(DataStream *stream);
};
}
namespace Skeleton
{
enum ChunkID
{
CID_Invalid = 0,
CID_Header = 0x1000,
CID_Bone = 0x2000,
CID_Bone_Parent = 0x3000,
CID_Animation = 0x4000,
CID_Animation_Track = 0x4100,
CID_Animation_Track_KF = 0x4110,
CID_Animation_Link = 0x5000
};
class Chunk : public ChunkBase<ChunkID>
{
public:
static Chunk *Read(DataStream *stream);
};
class ChunkUnknown; class ChunkFileHeader;
class ChunkBone; class ChunkBoneParent;
class ChunkAnimation; class ChunkAnimationTrack; class ChunkAnimationTrackKF; class ChunkAnimationLink;
class ChunkUnknown : public Chunk
{
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkFileHeader : public Chunk
{
static const std::string ExpectedVersion;
public:
std::string version;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkBone : public Chunk
{
public:
std::string name;
uint16_t handle;
StdMeshVector position;
StdMeshQuaternion orientation;
StdMeshVector scale;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkBoneParent : public Chunk
{
public:
uint16_t childHandle;
uint16_t parentHandle;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkAnimation : public Chunk
{
public:
std::string name;
float duration;
boost::ptr_vector<ChunkAnimationTrack> tracks;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkAnimationTrack : public Chunk
{
public:
uint16_t bone;
boost::ptr_vector<ChunkAnimationTrackKF> keyframes;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkAnimationTrackKF : public Chunk
{
public:
float time;
StdMeshQuaternion rotation;
StdMeshVector translation;
StdMeshVector scale;
protected:
virtual void ReadImpl(DataStream *stream);
};
class ChunkAnimationLink : public Chunk
{
public:
std::string file;
StdMeshVector scale;
protected:
virtual void ReadImpl(DataStream *stream);
};
}
}
#endif

View File

@ -0,0 +1,111 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2010 Nicolas Hake
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
#ifndef INC_StdMeshLoaderDataStream
#define INC_StdMeshLoaderDataStream
#include "StdMeshLoader.h"
#include <boost/noncopyable.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits/has_trivial_copy.hpp>
namespace Ogre
{
class DataStream : boost::noncopyable
{
const char *begin, *cursor, *end;
public:
DataStream(const char *src, size_t length)
{
begin = cursor = src;
end = cursor + length;
}
bool AtEof() const { return cursor == end; }
size_t GetRemainingBytes() const { return end - cursor; }
void Rewind() { cursor = begin; }
void Seek(ptrdiff_t offset)
{
if (offset > 0 && GetRemainingBytes() < static_cast<size_t>(offset))
throw InsufficientData();
if (cursor - begin < -offset)
throw InsufficientData();
cursor += offset;
}
// Only read directly into T when T is trivially copyable (i.e., allows bit-wise copy)
template<class T>
typename boost::enable_if<boost::has_trivial_copy<T>,
typename boost::disable_if<boost::is_pointer<T>, T>::type>::type
Peek() const
{
if (GetRemainingBytes() < sizeof(T))
throw InsufficientData();
T temp;
std::memcpy(reinterpret_cast<char*>(&temp), cursor, sizeof(T));
return temp;
}
// declaration for non-trivially copyable types
template<class T> typename boost::disable_if<boost::has_trivial_copy<T>, T>::type
Peek() const;
template<class T> T Read()
{
T temp = Peek<T>();
Seek(sizeof(T));
return temp;
}
void Peek(void *dest, size_t size) const
{
if (GetRemainingBytes() < size)
throw InsufficientData();
std::memcpy(dest, cursor, size);
}
void Read(void *dest, size_t size)
{
Peek(dest, size);
cursor += size;
}
};
template<> inline bool DataStream::Peek<bool>() const
{
if (GetRemainingBytes() < 1)
throw InsufficientData();
return *cursor != '\0';
}
template<> inline std::string DataStream::Peek<std::string>() const
{
// Ogre terminates strings with \n
const char *terminator = static_cast<const char*>(std::memchr(cursor, '\n', GetRemainingBytes()));
if (!terminator)
throw InsufficientData("Unterminated string");
return std::string(cursor, terminator);
}
template<> inline std::string DataStream::Read<std::string>()
{
std::string temp = Peek<std::string>();
Seek(temp.size() + 1);
return temp;
}
}
#endif

View File

@ -0,0 +1,407 @@
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2009-2010 Armin Burgmeier
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
// A loader for the OGRE .mesh binary file format
#include "C4Include.h"
#include "StdMesh.h"
#include "StdMeshLoader.h"
#include <tinyxml/tinyxml.h>
// 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;
void Error(const StdStrBuf& message, int row) const;
private:
TiXmlDocument Document;
StdStrBuf FileName;
};
StdMeshXML::StdMeshXML(const char* filename, const char* xml_data):
FileName(filename)
{
Document.Parse(xml_data);
if(Document.Error())
Error(StdStrBuf(Document.ErrorDesc()), 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 = 0;
if(element->QueryFloatAttribute(attribute, &retval) != TIXML_SUCCESS)
Error(FormatString("Element '%s' does not have floating point 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
{
Error(message, element->Row());
}
void StdMeshXML::Error(const StdStrBuf& message, int row) const
{
throw StdMeshLoader::LoaderException(FormatString("%s:%u: %s", FileName.getData(), row, message.getData()).getData());
}
StdMesh *StdMeshLoader::LoadMeshXml(const char* xml_data, size_t size, const StdMeshMatManager& manager, StdMeshSkeletonLoader& skel_loader, const char* filename)
{
StdMeshXML xml(filename ? filename : "<unknown>", xml_data);
std::auto_ptr<StdMesh> mesh(new StdMesh);
TiXmlElement* mesh_elem = xml.RequireFirstChild(NULL, "mesh");
TiXmlElement* submeshes_elem = xml.RequireFirstChild(mesh_elem, "submeshes");
TiXmlElement* submesh_elem_base = xml.RequireFirstChild(submeshes_elem, "submesh");
for(TiXmlElement* submesh_elem = submesh_elem_base; submesh_elem != NULL; submesh_elem = submesh_elem->NextSiblingElement("submesh"))
{
TiXmlElement* geometry_elem = xml.RequireFirstChild(submesh_elem, "geometry");
mesh->SubMeshes.push_back(StdSubMesh());
StdSubMesh& submesh = mesh->SubMeshes.back();
const char* material = xml.RequireStrAttribute(submesh_elem, "material");
submesh.Material = manager.GetMaterial(material);
if(!submesh.Material)
xml.Error(FormatString("There is no such material named '%s'", material), submesh_elem);
int VertexCount = xml.RequireIntAttribute(geometry_elem, "vertexcount");
submesh.Vertices.resize(VertexCount);
TiXmlElement* buffer_elem = xml.RequireFirstChild(geometry_elem, "vertexbuffer");
unsigned int i = 0;
for(TiXmlElement* vertex_elem = buffer_elem->FirstChildElement("vertex"); vertex_elem != NULL && i < submesh.Vertices.size(); vertex_elem = vertex_elem->NextSiblingElement("vertex"), ++i)
{
TiXmlElement* position_elem = xml.RequireFirstChild(vertex_elem, "position");
TiXmlElement* normal_elem = xml.RequireFirstChild(vertex_elem, "normal");
TiXmlElement* texcoord_elem = xml.RequireFirstChild(vertex_elem, "texcoord");
submesh.Vertices[i].x = xml.RequireFloatAttribute(position_elem, "x");
submesh.Vertices[i].y = xml.RequireFloatAttribute(position_elem, "y");
submesh.Vertices[i].z = xml.RequireFloatAttribute(position_elem, "z");
submesh.Vertices[i].nx = xml.RequireFloatAttribute(normal_elem, "x");
submesh.Vertices[i].ny = xml.RequireFloatAttribute(normal_elem, "y");
submesh.Vertices[i].nz = xml.RequireFloatAttribute(normal_elem, "z");
submesh.Vertices[i].u = xml.RequireFloatAttribute(texcoord_elem, "u");
submesh.Vertices[i].v = xml.RequireFloatAttribute(texcoord_elem, "v");
// Construct BoundingBox
StdMeshBox &BoundingBox = mesh->BoundingBox;
if(i == 0 && mesh->SubMeshes.size() == 1)
{
// First vertex
BoundingBox.x1 = BoundingBox.x2 = submesh.Vertices[i].x;
BoundingBox.y1 = BoundingBox.y2 = submesh.Vertices[i].y;
BoundingBox.z1 = BoundingBox.z2 = submesh.Vertices[i].z;
}
else
{
BoundingBox.x1 = Min(submesh.Vertices[i].x, BoundingBox.x1);
BoundingBox.x2 = Max(submesh.Vertices[i].x, BoundingBox.x2);
BoundingBox.y1 = Min(submesh.Vertices[i].y, BoundingBox.y1);
BoundingBox.y2 = Max(submesh.Vertices[i].y, BoundingBox.y2);
BoundingBox.z1 = Min(submesh.Vertices[i].z, BoundingBox.z1);
BoundingBox.z2 = Max(submesh.Vertices[i].z, BoundingBox.z2);
}
}
TiXmlElement* faces_elem = xml.RequireFirstChild(submesh_elem, "faces");
int FaceCount = xml.RequireIntAttribute(faces_elem, "count");
submesh.Faces.resize(FaceCount);
i = 0;
for(TiXmlElement* face_elem = faces_elem->FirstChildElement("face"); face_elem != NULL && i < submesh.Faces.size(); face_elem = face_elem->NextSiblingElement("face"), ++i)
{
int v[3];
v[0] = xml.RequireIntAttribute(face_elem, "v1");
v[1] = xml.RequireIntAttribute(face_elem, "v2");
v[2] = xml.RequireIntAttribute(face_elem, "v3");
for(unsigned int j = 0; j < 3; ++j)
{
if(v[j] < 0 || static_cast<unsigned int>(v[j]) >= submesh.Vertices.size())
xml.Error(FormatString("Vertex index v%u (%d) is out of range", j+1, v[j]), face_elem);
submesh.Faces[i].Vertices[j] = v[j];
}
}
}
// Read skeleton, if any
TiXmlElement* skeletonlink_elem = mesh_elem->FirstChildElement("skeletonlink");
if(skeletonlink_elem)
{
const char* name = xml.RequireStrAttribute(skeletonlink_elem, "name");
StdCopyStrBuf xml_filename(name); xml_filename.Append(".xml");
StdStrBuf skeleton_xml_data = skel_loader.LoadSkeleton(xml_filename.getData());
if(skeleton_xml_data.isNull()) xml.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
bone->Parent = NULL;
// Index of bone will be set when building Master Bone Table later
TiXmlElement* position_elem = skeleton.RequireFirstChild(bone_elem, "position");
TiXmlElement* rotation_elem = skeleton.RequireFirstChild(bone_elem, "rotation");
TiXmlElement* axis_elem = skeleton.RequireFirstChild(rotation_elem, "axis");
StdMeshVector d, r;
d.x = skeleton.RequireFloatAttribute(position_elem, "x");
d.y = skeleton.RequireFloatAttribute(position_elem, "y");
d.z = skeleton.RequireFloatAttribute(position_elem, "z");
float angle = skeleton.RequireFloatAttribute(rotation_elem, "angle");
r.x = skeleton.RequireFloatAttribute(axis_elem, "x");
r.y = skeleton.RequireFloatAttribute(axis_elem, "y");
r.z = skeleton.RequireFloatAttribute(axis_elem, "z");
bone->Transformation.scale = StdMeshVector::UnitScale();
bone->Transformation.rotate = StdMeshQuaternion::AngleAxis(angle, r);
bone->Transformation.translate = d;
// We need this later for applying animations, and attaching meshes, therefore cache it here
bone->InverseTransformation = StdMeshTransformation::Inverse(bone->Transformation);
}
// 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);
}
// Fill master bone table in hierarchical order:
for(unsigned int i = 0; i < bones.size(); ++i)
if(bones[i]->Parent == NULL)
mesh->AddMasterBone(bones[i]);
// Vertex<->Bone assignments for all vertices (need to go through SubMeshes again...)
unsigned int submesh_index = 0;
for(TiXmlElement* submesh_elem = submesh_elem_base; submesh_elem != NULL; submesh_elem = submesh_elem->NextSiblingElement("submesh"), ++submesh_index)
{
StdSubMesh& submesh = mesh->SubMeshes[submesh_index];
TiXmlElement* boneassignments_elem = xml.RequireFirstChild(submesh_elem, "boneassignments");
for(TiXmlElement* vertexboneassignment_elem = boneassignments_elem->FirstChildElement("vertexboneassignment"); vertexboneassignment_elem != NULL; vertexboneassignment_elem = vertexboneassignment_elem->NextSiblingElement("vertexboneassignment"))
{
int BoneID = xml.RequireIntAttribute(vertexboneassignment_elem, "boneindex");
int VertexIndex = xml.RequireIntAttribute(vertexboneassignment_elem, "vertexindex");
float weight = xml.RequireFloatAttribute(vertexboneassignment_elem, "weight");
if(VertexIndex < 0 || static_cast<unsigned int>(VertexIndex) >= submesh.Vertices.size())
xml.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) xml.Error(FormatString("There is no such bone with index %d", BoneID), vertexboneassignment_elem);
StdSubMesh::Vertex& vertex = submesh.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 < submesh.Vertices.size(); ++i)
{
StdSubMesh::Vertex& vertex = submesh.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_elem->FirstChildElement("animations");
if (animations_elem)
{
for(TiXmlElement* animation_elem = animations_elem->FirstChildElement("animation"); animation_elem != NULL; animation_elem = animation_elem->NextSiblingElement("animation"))
{
StdCopyStrBuf name(skeleton.RequireStrAttribute(animation_elem, "name"));
if(mesh->Animations.find(name) != mesh->Animations.end())
skeleton.Error(FormatString("There is already an animation with name '%s'", name.getData()), animation_elem);
StdMeshAnimation& animation = mesh->Animations.insert(std::make_pair(name, StdMeshAnimation())).first->second;
animation.Name = name;
animation.Length = skeleton.RequireFloatAttribute(animation_elem, "length");
animation.Tracks.resize(mesh->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 < mesh->Bones.size(); ++i)
if(mesh->Bones[i]->Name == bone_name)
bone = mesh->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(keyframe_elem, "scale");
TiXmlElement* axis_elem = skeleton.RequireFirstChild(rotate_elem, "axis");
StdMeshVector d, s, r;
d.x = skeleton.RequireFloatAttribute(translate_elem, "x");
d.y = skeleton.RequireFloatAttribute(translate_elem, "y");
d.z = skeleton.RequireFloatAttribute(translate_elem, "z");
s.x = skeleton.RequireFloatAttribute(scale_elem, "x");
s.y = skeleton.RequireFloatAttribute(scale_elem, "y");
s.z = skeleton.RequireFloatAttribute(scale_elem, "z");
float angle = skeleton.RequireFloatAttribute(rotate_elem, "angle");
r.x = skeleton.RequireFloatAttribute(axis_elem, "x");
r.y = skeleton.RequireFloatAttribute(axis_elem, "y");
r.z = skeleton.RequireFloatAttribute(axis_elem, "z");
frame.Transformation.scale = StdMeshVector::UnitScale();
frame.Transformation.rotate = StdMeshQuaternion::AngleAxis(angle, r);
frame.Transformation.translate = bone->InverseTransformation.rotate * (bone->InverseTransformation.scale * d);
}
}
}
}
}
else
{
// Mesh has no skeleton
for(TiXmlElement* submesh_elem = submesh_elem_base; submesh_elem != NULL; submesh_elem = submesh_elem->NextSiblingElement("submesh"))
{
TiXmlElement* boneassignments_elem = submesh_elem->FirstChildElement("boneassignments");
if(boneassignments_elem)
{
// Bone assignements do not make sense then, as the
// actual bones are defined in the skeleton file.
xml.Error(StdStrBuf("Mesh has bone assignments, but no skeleton"), boneassignments_elem);
}
}
}
// Apply parent transformation to each bone transformation. We need to
// do this late since animation keyframe computation needs the bone
// transformations, not bone+parent.
for(unsigned int i = 0; i < mesh->Bones.size(); ++i)
{
if(mesh->Bones[i]->Parent)
{
// Apply parent transformation
mesh->Bones[i]->Transformation = mesh->Bones[i]->Parent->Transformation * mesh->Bones[i]->Transformation;
// Update inverse
mesh->Bones[i]->InverseTransformation = StdMeshTransformation::Inverse(mesh->Bones[i]->Transformation);
}
}
return mesh.release();
}