diff --git a/CMakeLists.txt b/CMakeLists.txt index 668117e39..3fc53133f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/Makefile.am b/Makefile.am index d232cbfa8..fe7fc0942 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/README.linux.txt b/README.linux.txt index 87f9473ff..a05574f5e 100644 --- a/README.linux.txt +++ b/README.linux.txt @@ -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 ===== diff --git a/README.windows.txt b/README.windows.txt index 667db224c..48b0de483 100644 --- a/README.windows.txt +++ b/README.windows.txt @@ -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 =============== diff --git a/src/c4group/C4Components.h b/src/c4group/C4Components.h index 4b164dc6b..494273929 100644 --- a/src/c4group/C4Components.h +++ b/src/c4group/C4Components.h @@ -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" diff --git a/src/game/object/C4DefGraphics.cpp b/src/game/object/C4DefGraphics.cpp index 33575150f..a62052a5b 100644 --- a/src/game/object/C4DefGraphics.cpp +++ b/src/game/object/C4DefGraphics.cpp @@ -42,6 +42,7 @@ #include #include #include +#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; } diff --git a/src/platform/StdMesh.cpp b/src/platform/StdMesh.cpp index 98f0ce190..4f02a1f78 100644 --- a/src/platform/StdMesh.cpp +++ b/src/platform/StdMesh.cpp @@ -24,8 +24,6 @@ # include #endif -#include - #include 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(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 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(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 diff --git a/src/platform/StdMesh.h b/src/platform/StdMesh.h index 9be5f1518..6638d65d1 100644 --- a/src/platform/StdMesh.h +++ b/src/platform/StdMesh.h @@ -21,29 +21,7 @@ #include -// 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(); } diff --git a/src/platform/StdMeshLoader.h b/src/platform/StdMeshLoader.h new file mode 100644 index 000000000..15a55b7f1 --- /dev/null +++ b/src/platform/StdMeshLoader.h @@ -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 + +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 diff --git a/src/platform/StdMeshLoaderBinary.cpp b/src/platform/StdMeshLoaderBinary.cpp new file mode 100644 index 000000000..f0db574af --- /dev/null +++ b/src/platform/StdMeshLoaderBinary.cpp @@ -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 +#include +#include +#include +#include + +namespace +{ + bool VertexDeclarationIsSane(const boost::ptr_vector &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 + 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(source + sizeof(float) * 3); + case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float3: + dest[2] = *reinterpret_cast(source + sizeof(float) * 2); + case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float2: + dest[1] = *reinterpret_cast(source + sizeof(float) * 1); + case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float1: + dest[0] = *reinterpret_cast(source + sizeof(float) * 0); + break; + case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Color_ABGR: + dest[3] = *reinterpret_cast(source); + for (int i = 0; i < 3; ++i) + dest[i] = *reinterpret_cast(source + sizeof(float) * (3 - i)); + break; + case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Color_ARGB: + dest[3] = *reinterpret_cast(source); + for (int i = 0; i < 3; ++i) + dest[i] = *reinterpret_cast(source + sizeof(float) * (i + 1)); + break; + } + } + + std::vector ReadSubmeshGeometry(const Ogre::Mesh::ChunkGeometry &geo) + { + if (!VertexDeclarationIsSane(geo.vertexDeclaration)) + throw Ogre::Mesh::InvalidVertexDeclaration(); + + // Generate array of vertex buffer cursors + std::vector cursors; + BOOST_FOREACH(const Ogre::Mesh::ChunkGeometryVertexBuffer &buf, geo.vertexBuffers) + cursors.push_back(static_cast(buf.data->data)); + + // Generate vertices + std::vector 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 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(root.get()); + std::auto_ptr 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 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 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 bones; + boost::ptr_vector 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 chunk(Ogre::Skeleton::Chunk::Read(&stream)); + switch (chunk->GetType()) + { + case Ogre::Skeleton::CID_Bone: + { + Ogre::Skeleton::ChunkBone &cbone = *static_cast(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(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(chunk.release())); + break; + } + if (stream.AtEof()) break; + } + + // Find master bone (i.e., the one without a parent) + StdMeshBone *master = NULL; + for (boost::ptr_map::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 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()); +} diff --git a/src/platform/StdMeshLoaderBinaryChunks.cpp b/src/platform/StdMeshLoaderBinaryChunks.cpp new file mode 100644 index 000000000..850c6f742 --- /dev/null +++ b/src/platform/StdMeshLoaderBinaryChunks.cpp @@ -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 +#include +#include + +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(stream->Read()); + 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(); + size -= ChunkHeaderLength; + } + + // Create chunk + std::auto_ptr 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(); + if (version != ExpectedVersion) + throw InvalidVersion(); + } + + void ChunkMesh::ReadImpl(DataStream *stream) + { + hasAnimatedSkeleton = stream->Read(); + 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(chunk)); + break; + case CID_Submesh: + submeshes.push_back(static_cast(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(chunk)->skeleton; + delete chunk; + break; + case CID_Mesh_Bounds: + bounds = static_cast(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(); + } + + void ChunkSubmesh::ReadImpl(DataStream *stream) + { + operation = SO_TriList; // default if no CID_Submesh_Op chunk exists + material = stream->Read(); + hasSharedVertices = stream->Read(); + size_t index_count = stream->Read(); + bool indexes_are_32bit = stream->Read(); + faceVertices.reserve(index_count); + while(index_count--) + { + size_t index; + if (indexes_are_32bit) + index = stream->Read(); + else + index = stream->Read(); + 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(chunk)); + break; + case CID_Submesh_Op: + operation = static_cast(chunk)->operation; + delete chunk; + break; + case CID_Submesh_Bone_Assignment: + { + // Collect bone assignments + ChunkMeshBoneAssignments *assignments = static_cast(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(); + if (op < ChunkSubmesh::SO_MIN || op > ChunkSubmesh::SO_MAX) + throw InvalidSubmeshOp(); + operation = static_cast(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(); + assignment.bone = stream->Read(); + assignment.weight = stream->Read(); + assignments.push_back(assignment); + } + } + + void ChunkMeshBounds::ReadImpl(DataStream *stream) + { + bounds.x1 = stream->Read(); + bounds.y1 = stream->Read(); + bounds.z1 = stream->Read(); + bounds.x2 = stream->Read(); + bounds.y2 = stream->Read(); + bounds.z2 = stream->Read(); + radius = stream->Read(); + } + + void ChunkGeometry::ReadImpl(DataStream *stream) + { + vertexCount = stream->Read(); + 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(chunk)->declaration); + delete chunk; + break; + case CID_Geometry_Vertex_Buffer: + vertexBuffers.push_back(static_cast(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(chunk)); + if (stream->AtEof()) break; + } + } + + void ChunkGeometryVertexDeclElement::ReadImpl(DataStream *stream) + { + source = stream->Read(); + uint32_t t = stream->Read(); + if (t < VDET_MIN || t > VDET_MAX) + throw InvalidVertexType(); + type = static_cast(t); + t = stream->Read(); + if (t < VDES_MIN || t > VDES_MAX) + throw InvalidVertexSemantic(); + semantic = static_cast(t); + offset = stream->Read(); + index = stream->Read(); + } + + void ChunkGeometryVertexBuffer::ReadImpl(DataStream *stream) + { + index = stream->Read(); + vertexSize = stream->Read(); + + 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(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(stream->Read()); + 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(); + size -= ChunkHeaderLength; + } + + // Create chunk + std::auto_ptr 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(); + if (version != ExpectedVersion) + throw InvalidVersion(); + } + + void ChunkBone::ReadImpl(DataStream *stream) + { + name = stream->Read(); + handle = stream->Read(); + position.x = stream->Read(); + position.y = stream->Read(); + position.z = stream->Read(); + orientation.x = stream->Read(); + orientation.y = stream->Read(); + orientation.z = stream->Read(); + orientation.w = stream->Read(); + // Guess whether we have a scale element + if (GetSize() > name.size() + 1 + sizeof(handle) + sizeof(float) * 7) + { + scale.x = stream->Read(); + scale.y = stream->Read(); + scale.z = stream->Read(); + } + else + { + scale = StdMeshVector::UnitScale(); + } + } + + void ChunkBoneParent::ReadImpl(DataStream *stream) + { + childHandle = stream->Read(); + parentHandle = stream->Read(); + } + + void ChunkAnimation::ReadImpl(DataStream *stream) + { + name = stream->Read(); + duration = stream->Read(); + while (Chunk::Peek(stream) == CID_Animation_Track) + { + Chunk *chunk = Chunk::Read(stream); + assert(chunk->GetType() == CID_Animation_Track); + tracks.push_back(static_cast(chunk)); + if (stream->AtEof()) break; + } + } + + void ChunkAnimationTrack::ReadImpl(DataStream *stream) + { + bone = stream->Read(); + 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(chunk)); + if (stream->AtEof()) break; + } + } + + void ChunkAnimationTrackKF::ReadImpl(DataStream *stream) + { + time = stream->Read(); + rotation.x = stream->Read(); + rotation.y = stream->Read(); + rotation.z = stream->Read(); + rotation.w = stream->Read(); + translation.x = stream->Read(); + translation.y = stream->Read(); + translation.z = stream->Read(); + // Guess whether we have a scale element + if (GetSize() > sizeof(float) * 8) + { + scale.x = stream->Read(); + scale.y = stream->Read(); + scale.z = stream->Read(); + } + else + { + scale = StdMeshVector::UnitScale(); + } + } + + void ChunkAnimationLink::ReadImpl(DataStream *stream) + { + file = stream->Read(); + scale.x = stream->Read(); + scale.y = stream->Read(); + scale.z = stream->Read(); + } + } +} diff --git a/src/platform/StdMeshLoaderBinaryChunks.h b/src/platform/StdMeshLoaderBinaryChunks.h new file mode 100644 index 000000000..f73f7a90b --- /dev/null +++ b/src/platform/StdMeshLoaderBinaryChunks.h @@ -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 +#include +#include + +// ==== 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 ChunkBase + { + protected: + ChunkBase() : type(static_cast(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(stream->Peek()); + } + }; + + 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 + { + 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 geometry; + boost::ptr_vector 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 faceVertices; + boost::scoped_ptr 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 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 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 vertexDeclaration; + boost::ptr_vector vertexBuffers; + protected: + virtual void ReadImpl(DataStream *stream); + }; + + class ChunkGeometryVertexDecl : public Chunk + { + public: + boost::ptr_vector 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 data; + protected: + void ReadImpl(DataStream *stream); + }; + + class ChunkGeometryVertexData : public Chunk + { + public: + ChunkGeometryVertexData() : data(NULL) {} + ~ChunkGeometryVertexData() { delete[] static_cast(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 + { + 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 tracks; + protected: + virtual void ReadImpl(DataStream *stream); + }; + + class ChunkAnimationTrack : public Chunk + { + public: + uint16_t bone; + boost::ptr_vector 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 diff --git a/src/platform/StdMeshLoaderDataStream.h b/src/platform/StdMeshLoaderDataStream.h new file mode 100644 index 000000000..06cd62163 --- /dev/null +++ b/src/platform/StdMeshLoaderDataStream.h @@ -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 +#include +#include + +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(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 + typename boost::enable_if, + typename boost::disable_if, T>::type>::type + Peek() const + { + if (GetRemainingBytes() < sizeof(T)) + throw InsufficientData(); + T temp; + std::memcpy(reinterpret_cast(&temp), cursor, sizeof(T)); + return temp; + } + + // declaration for non-trivially copyable types + template typename boost::disable_if, T>::type + Peek() const; + + template T Read() + { + T temp = Peek(); + 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() const + { + if (GetRemainingBytes() < 1) + throw InsufficientData(); + return *cursor != '\0'; + } + + template<> inline std::string DataStream::Peek() const + { + // Ogre terminates strings with \n + const char *terminator = static_cast(std::memchr(cursor, '\n', GetRemainingBytes())); + if (!terminator) + throw InsufficientData("Unterminated string"); + return std::string(cursor, terminator); + } + + template<> inline std::string DataStream::Read() + { + std::string temp = Peek(); + Seek(temp.size() + 1); + return temp; + } + +} + +#endif diff --git a/src/platform/StdMeshLoaderXml.cpp b/src/platform/StdMeshLoaderXml.cpp new file mode 100644 index 000000000..bbef0ef74 --- /dev/null +++ b/src/platform/StdMeshLoaderXml.cpp @@ -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 + +// 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 : "", xml_data); + + std::auto_ptr 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(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 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(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(); +}