// Copyright (C) 2002-2012 Nikolaus Gebhardt / Fabio Concas / Thomas Alten // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_MD3_LOADER_ #include "CAnimatedMeshMD3.h" #include "os.h" namespace irr { namespace scene { // byte-align structures #include "irrpack.h" //! General properties of a single animation frame. struct SMD3Frame { f32 mins[3]; // bounding box per frame f32 maxs[3]; f32 position[3]; // position of bounding box f32 radius; // radius of bounding sphere c8 creator[16]; // name of frame } PACK_STRUCT; //! An attachment point for another MD3 model. struct SMD3Tag { c8 Name[64]; //name of 'tag' as it's usually called in the md3 files try to see it as a sub-mesh/seperate mesh-part. f32 position[3]; //relative position of tag f32 rotationMatrix[9]; //3x3 rotation direction of tag } PACK_STRUCT; //!Shader struct SMD3Shader { c8 name[64]; // name of shader s32 shaderIndex; } PACK_STRUCT; // Default alignment #include "irrunpack.h" //! Constructor CAnimatedMeshMD3::CAnimatedMeshMD3() :Mesh(0), IPolShift(0), LoopMode(0), Scaling(1.f)//, FramesPerSecond(25.f) { #ifdef _DEBUG setDebugName("CAnimatedMeshMD3"); #endif Mesh = new SMD3Mesh(); MeshIPol = new SMesh(); setInterpolationShift(0, 0); } //! Destructor CAnimatedMeshMD3::~CAnimatedMeshMD3() { if (Mesh) Mesh->drop(); if (MeshIPol) MeshIPol->drop(); } //! Returns the amount of frames in milliseconds. If the amount is 1, it is a static (=non animated) mesh. u32 CAnimatedMeshMD3::getFrameCount() const { return Mesh->MD3Header.numFrames << IPolShift; } //! Rendering Hint void CAnimatedMeshMD3::setInterpolationShift(u32 shift, u32 loopMode) { IPolShift = shift; LoopMode = loopMode; } //! returns amount of mesh buffers. u32 CAnimatedMeshMD3::getMeshBufferCount() const { return MeshIPol->getMeshBufferCount(); } //! returns pointer to a mesh buffer IMeshBuffer* CAnimatedMeshMD3::getMeshBuffer(u32 nr) const { return MeshIPol->getMeshBuffer(nr); } //! Returns pointer to a mesh buffer which fits a material IMeshBuffer* CAnimatedMeshMD3::getMeshBuffer(const video::SMaterial &material) const { return MeshIPol->getMeshBuffer(material); } void CAnimatedMeshMD3::setMaterialFlag(video::E_MATERIAL_FLAG flag, bool newvalue) { MeshIPol->setMaterialFlag(flag, newvalue); } //! set the hardware mapping hint, for driver void CAnimatedMeshMD3::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer) { MeshIPol->setHardwareMappingHint(newMappingHint, buffer); } //! flags the meshbuffer as changed, reloads hardware buffers void CAnimatedMeshMD3::setDirty(E_BUFFER_TYPE buffer) { MeshIPol->setDirty(buffer); } //! set user axis aligned bounding box void CAnimatedMeshMD3::setBoundingBox(const core::aabbox3df& box) { MeshIPol->setBoundingBox(box); } //! Returns the animated tag list based on a detail level. 0 is the lowest, 255 the highest detail. SMD3QuaternionTagList *CAnimatedMeshMD3::getTagList(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop) { if (0 == Mesh) return 0; getMesh(frame, detailLevel, startFrameLoop, endFrameLoop); return &TagListIPol; } //! Returns the animated mesh based on a detail level. 0 is the lowest, 255 the highest detail. IMesh* CAnimatedMeshMD3::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop) { if (0 == Mesh) return 0; //! check if we have the mesh in our private cache SCacheInfo candidate(frame, startFrameLoop, endFrameLoop); if (candidate == Current) return MeshIPol; startFrameLoop = core::s32_max(0, startFrameLoop >> IPolShift); endFrameLoop = core::if_c_a_else_b(endFrameLoop < 0, Mesh->MD3Header.numFrames - 1, endFrameLoop >> IPolShift); const u32 mask = 1 << IPolShift; s32 frameA; s32 frameB; f32 iPol; if (LoopMode) { // correct frame to "pixel center" frame -= mask >> 1; // interpolation iPol = f32(frame & (mask - 1)) * core::reciprocal(f32(mask)); // wrap anim frame >>= IPolShift; frameA = core::if_c_a_else_b(frame < startFrameLoop, endFrameLoop, frame); frameB = core::if_c_a_else_b(frameA + 1 > endFrameLoop, startFrameLoop, frameA + 1); } else { // correct frame to "pixel center" frame -= mask >> 1; iPol = f32(frame & (mask - 1)) * core::reciprocal(f32(mask)); // clamp anim frame >>= IPolShift; frameA = core::s32_clamp(frame, startFrameLoop, endFrameLoop); frameB = core::s32_min(frameA + 1, endFrameLoop); } // build current vertex for (u32 i = 0; i!= Mesh->Buffer.size(); ++i) { buildVertexArray(frameA, frameB, iPol, Mesh->Buffer[i], (SMeshBufferLightMap*) MeshIPol->getMeshBuffer(i)); } MeshIPol->recalculateBoundingBox(); // build current tags buildTagArray(frameA, frameB, iPol); Current = candidate; return MeshIPol; } //! create a Irrlicht MeshBuffer for a MD3 MeshBuffer IMeshBuffer * CAnimatedMeshMD3::createMeshBuffer(const SMD3MeshBuffer* source, io::IFileSystem* fs, video::IVideoDriver * driver) { SMeshBufferLightMap * dest = new SMeshBufferLightMap(); dest->Vertices.set_used(source->MeshHeader.numVertices); dest->Indices.set_used(source->Indices.size()); u32 i; // fill in static face info for (i = 0; i < source->Indices.size(); i += 3) { dest->Indices[i + 0] = (u16) source->Indices[i + 0]; dest->Indices[i + 1] = (u16) source->Indices[i + 1]; dest->Indices[i + 2] = (u16) source->Indices[i + 2]; } // fill in static vertex info for (i = 0; i!= (u32)source->MeshHeader.numVertices; ++i) { video::S3DVertex2TCoords &v = dest->Vertices[i]; v.Color = 0xFFFFFFFF; v.TCoords.X = source->Tex[i].u; v.TCoords.Y = source->Tex[i].v; v.TCoords2.X = 0.f; v.TCoords2.Y = 0.f; } // load static texture u32 pos = 0; quake3::tTexArray textureArray; quake3::getTextures(textureArray, source->Shader, pos, fs, driver); dest->Material.MaterialType = video::EMT_SOLID; dest->Material.setTexture(0, textureArray[0]); dest->Material.Lighting = false; return dest; } //! build final mesh's vertices from frames frameA and frameB with linear interpolation. void CAnimatedMeshMD3::buildVertexArray(u32 frameA, u32 frameB, f32 interpolate, const SMD3MeshBuffer* source, SMeshBufferLightMap* dest) { const u32 frameOffsetA = frameA * source->MeshHeader.numVertices; const u32 frameOffsetB = frameB * source->MeshHeader.numVertices; const f32 scale = (1.f/ 64.f); for (s32 i = 0; i != source->MeshHeader.numVertices; ++i) { video::S3DVertex2TCoords &v = dest->Vertices [ i ]; const SMD3Vertex &vA = source->Vertices [ frameOffsetA + i ]; const SMD3Vertex &vB = source->Vertices [ frameOffsetB + i ]; // position v.Pos.X = scale * (vA.position[0] + interpolate * (vB.position[0] - vA.position[0])); v.Pos.Y = scale * (vA.position[2] + interpolate * (vB.position[2] - vA.position[2])); v.Pos.Z = scale * (vA.position[1] + interpolate * (vB.position[1] - vA.position[1])); // normal const core::vector3df nA(quake3::getMD3Normal(vA.normal[0], vA.normal[1])); const core::vector3df nB(quake3::getMD3Normal(vB.normal[0], vB.normal[1])); v.Normal.X = nA.X + interpolate * (nB.X - nA.X); v.Normal.Y = nA.Z + interpolate * (nB.Z - nA.Z); v.Normal.Z = nA.Y + interpolate * (nB.Y - nA.Y); } dest->recalculateBoundingBox(); } //! build final mesh's tag from frames frameA and frameB with linear interpolation. void CAnimatedMeshMD3::buildTagArray(u32 frameA, u32 frameB, f32 interpolate) { const u32 frameOffsetA = frameA * Mesh->MD3Header.numTags; const u32 frameOffsetB = frameB * Mesh->MD3Header.numTags; for (s32 i = 0; i != Mesh->MD3Header.numTags; ++i) { SMD3QuaternionTag &d = TagListIPol [ i ]; const SMD3QuaternionTag &qA = Mesh->TagList[ frameOffsetA + i]; const SMD3QuaternionTag &qB = Mesh->TagList[ frameOffsetB + i]; // rotation d.rotation.slerp(qA.rotation, qB.rotation, interpolate); // position d.position.X = qA.position.X + interpolate * (qB.position.X - qA.position.X); d.position.Y = qA.position.Y + interpolate * (qB.position.Y - qA.position.Y); d.position.Z = qA.position.Z + interpolate * (qB.position.Z - qA.position.Z); } } /*! loads a model */ bool CAnimatedMeshMD3::loadModelFile(u32 modelIndex, io::IReadFile* file, io::IFileSystem* fs, video::IVideoDriver* driver) { if (!file) return false; //! Check MD3Header { file->read(&Mesh->MD3Header, sizeof(SMD3Header)); if (strncmp("IDP3", Mesh->MD3Header.headerID, 4)) { os::Printer::log("MD3 Loader: invalid header"); return false; } } //! store model name Mesh->Name = file->getFileName(); u32 i; //! Frame Data (ignore) #if 0 SMD3Frame frameImport; file->seek(Mesh->MD3Header.frameStart); for (i = 0; i != Mesh->MD3Header.numFrames; ++i) { file->read(&frameImport, sizeof(frameImport)); } #endif //! Tag Data const u32 totalTags = Mesh->MD3Header.numTags * Mesh->MD3Header.numFrames; SMD3Tag import; file->seek(Mesh->MD3Header.tagStart); Mesh->TagList.set_used(totalTags); for (i = 0; i != totalTags; ++i) { file->read(&import, sizeof(import)); SMD3QuaternionTag &exp = Mesh->TagList[i]; //! tag name exp.Name = import.Name; //! position exp.position.X = import.position[0]; exp.position.Y = import.position[2]; exp.position.Z = import.position[1]; //! construct quaternion from a RH 3x3 Matrix exp.rotation.set(import.rotationMatrix[7], 0.f, -import.rotationMatrix[6], 1 + import.rotationMatrix[8]); exp.rotation.normalize(); } //! Meshes u32 offset = Mesh->MD3Header.tagEnd; for (i = 0; i != (u32)Mesh->MD3Header.numMeshes; ++i) { //! construct a new mesh buffer SMD3MeshBuffer * buf = new SMD3MeshBuffer(); // !read mesh header info SMD3MeshHeader &meshHeader = buf->MeshHeader; //! read mesh info file->seek(offset); file->read(&meshHeader, sizeof(SMD3MeshHeader)); //! prepare memory buf->Vertices.set_used(meshHeader.numVertices * Mesh->MD3Header.numFrames); buf->Indices.set_used(meshHeader.numTriangles * 3); buf->Tex.set_used(meshHeader.numVertices); //! read skins (shaders). should be 1 per meshbuffer SMD3Shader skin; file->seek(offset + buf->MeshHeader.offset_shaders); for (s32 g = 0; g != buf->MeshHeader.numShader; ++g) { file->read(&skin, sizeof(skin)); io::path name; cutFilenameExtension(name, skin.name); name.replace('\\', '/'); buf->Shader = name; } //! read texture coordinates file->seek(offset + buf->MeshHeader.offset_st); file->read(buf->Tex.pointer(), buf->MeshHeader.numVertices * sizeof(SMD3TexCoord)); //! read vertices file->seek(offset + meshHeader.vertexStart); file->read(buf->Vertices.pointer(), Mesh->MD3Header.numFrames * meshHeader.numVertices * sizeof(SMD3Vertex)); //! read indices file->seek(offset + meshHeader.offset_triangles); file->read(buf->Indices.pointer(), meshHeader.numTriangles * sizeof(SMD3Face)); //! store meshBuffer Mesh->Buffer.push_back(buf); offset += meshHeader.offset_end; } // Init Mesh Interpolation for (i = 0; i != Mesh->Buffer.size(); ++i) { IMeshBuffer * buffer = createMeshBuffer(Mesh->Buffer[i], fs, driver); MeshIPol->addMeshBuffer(buffer); buffer->drop(); } MeshIPol->recalculateBoundingBox(); // Init Tag Interpolation for (i = 0; i != (u32)Mesh->MD3Header.numTags; ++i) { TagListIPol.push_back(Mesh->TagList[i]); } return true; } SMD3Mesh * CAnimatedMeshMD3::getOriginalMesh() { return Mesh; } //! Returns an axis aligned bounding box const core::aabbox3d& CAnimatedMeshMD3::getBoundingBox() const { return MeshIPol->BoundingBox; } //! Returns the type of the animated mesh. E_ANIMATED_MESH_TYPE CAnimatedMeshMD3::getMeshType() const { return EAMT_MD3; } } // end namespace scene } // end namespace irr #endif // _IRR_COMPILE_WITH_MD3_LOADER_