// Copyright (C) 2002-2007 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 "CAnimatedMeshMD3.h" #include "os.h" namespace irr { namespace scene { #if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__) # pragma pack( push, packing ) # pragma pack( 1 ) # define PACK_STRUCT #elif defined( __GNUC__ ) # define PACK_STRUCT __attribute__((packed)) #else # error compiler not supported #endif struct SMD3Bone { f32 Mins[3]; // bounding box per frame f32 Maxs[3]; f32 Position[3]; // position of bounding box f32 scale; c8 creator[16]; }; 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 }; struct SMD3Skin { c8 name[68]; // name of skin }; // Default alignment #if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__) # pragma pack( pop, packing ) #endif #undef PACK_STRUCT //! Constructor CAnimatedMeshMD3::CAnimatedMeshMD3 () : Mesh ( 0 ) { #ifdef _DEBUG setDebugName("CAnimatedMeshMD3"); #endif Mesh = new SMD3Mesh (); memset ( &Mesh->MD3Header, 0, sizeof ( Mesh->MD3Header ) ); setInterpolationShift ( 0, 0 ); } //! Destructor CAnimatedMeshMD3::~CAnimatedMeshMD3() { if ( Mesh ) Mesh->drop (); } //! Returns the amount of frames in milliseconds. If the amount is 1, it is a static (=non animated) mesh. s32 CAnimatedMeshMD3::getFrameCount() { return Mesh->MD3Header.numFrames << IPolShift; } //! Rendering Hint void CAnimatedMeshMD3::setInterpolationShift ( u32 shift, u32 loopMode ) { IPolShift = shift; } //! Returns the animated tag list based on a detail level. 0 is the lowest, 255 the highest detail. SMD3QuaterionTagList *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; u32 i; //! 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 curren vertex for ( i = 0; i!= Mesh->Buffer.size (); ++i ) { buildVertexArray ( frameA, frameB, iPol, Mesh->Buffer[i], (SMeshBuffer*) 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 ) { SMeshBuffer * dest = new SMeshBuffer (); 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::S3DVertex &v = dest->Vertices [ i ]; v.Color = 0xFFFFFFFF; v.TCoords.X = source->Tex[i].u; v.TCoords.Y = source->Tex[i].v; } 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, SMeshBuffer * dest ) { u32 i; u32 frameOffsetA = frameA * source->MeshHeader.numVertices; u32 frameOffsetB = frameB * source->MeshHeader.numVertices; f32 scale = ( 1.f/ 64.f ); core::vector3df nA; core::vector3df nB; for ( i = 0; i!= (u32)source->MeshHeader.numVertices; ++i ) { video::S3DVertex &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 getNormal ( nA, vA.normal[0], vA.normal[1] ); getNormal ( nB, 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 ) { u32 i; u32 frameOffsetA = frameA * Mesh->MD3Header.numTags; u32 frameOffsetB = frameB * Mesh->MD3Header.numTags; for ( i = 0; i!= (u32)Mesh->MD3Header.numTags; ++i ) { SMD3QuaterionTag &d = TagListIPol [ i ]; const SMD3QuaterionTag &qA = Mesh->TagList.Container[ frameOffsetA + i]; const SMD3QuaterionTag &qB = Mesh->TagList.Container[ 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) { if (!file) return false; u32 i,g; file->seek(0); //! 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(); //! Bone Frames Data ( ignore ) //! Tag Data const u32 totalTags = Mesh->MD3Header.numTags * Mesh->MD3Header.numFrames; SMD3Tag import; SMD3QuaterionTag exp; file->seek( Mesh->MD3Header.tagStart ); for (i = 0; i != totalTags; ++i ) { file->read(&import, sizeof(import) ); //! 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 (); Mesh->TagList.Container.push_back ( exp ); } //! Meshes u32 offset = Mesh->MD3Header.tagEnd; SMD3Skin skin; 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) file->seek( offset + buf->MeshHeader.offset_shaders ); for ( g = 0; g != (u32)buf->MeshHeader.numShader; ++g ) { file->read( &skin, sizeof(skin) ); buf->Shader.push_back ( skin.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] ); MeshIPol.addMeshBuffer ( buffer ); buffer->drop (); } // Init Tag Interpolation for (i = 0; i != (u32)Mesh->MD3Header.numTags; ++i ) { TagListIPol.Container.push_back ( Mesh->TagList.Container[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