// Copyright (C) 2002-2007 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#include "CAnimatedMeshMD2.h"
#include "os.h"
#include "SColor.h"
#include "IReadFile.h"
#include "irrMath.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

	// structs needed to load the md2-format

	const s32 MD2_MAGIC_NUMBER = 844121161;
	const s32 MD2_VERSION		= 8;
	const s32 MD2_MAX_VERTS		= 2048;

	// TA: private
	const s32 MD2_FRAME_SHIFT	= 2;
	const f32 MD2_FRAME_SHIFT_RECIPROCAL = 1.f / ( 1 << MD2_FRAME_SHIFT );

	struct SMD2Header
	{
		s32 magic;
		s32 version;
		s32 skinWidth;
		s32 skinHeight;
		s32 frameSize;
		s32 numSkins;
		s32 numVertices;
		s32 numTexcoords;
		s32 numTriangles;
		s32 numGlCommands;
		s32 numFrames;
		s32 offsetSkins;
		s32 offsetTexcoords;
		s32 offsetTriangles;
		s32 offsetFrames;
		s32 offsetGlCommands;
		s32 offsetEnd;
	} PACK_STRUCT;

	struct SMD2Vertex
	{
		u8 vertex[3];
		u8 lightNormalIndex;
	} PACK_STRUCT;

	struct SMD2Frame
	{
		f32	scale[3];
		f32	translate[3];
		c8	name[16];
		SMD2Vertex vertices[1];
	} PACK_STRUCT;

	struct SMD2Triangle
	{
		u16 vertexIndices[3];
		u16 textureIndices[3];
	} PACK_STRUCT;

	struct SMD2TextureCoordinate
	{
		s16 s;
		s16 t;
	} PACK_STRUCT;

	struct SMD2GLCommand
	{
		f32 s, t;
		s32 vertexIndex;
	} PACK_STRUCT;

// Default alignment
#if defined(_MSC_VER) ||  defined(__BORLANDC__) || defined (__BCPLUSPLUS__) 
#	pragma pack( pop, packing )
#endif

#undef PACK_STRUCT


const s32 Q2_VERTEX_NORMAL_TABLE_SIZE = 162;

static const f32 Q2_VERTEX_NORMAL_TABLE[Q2_VERTEX_NORMAL_TABLE_SIZE][3] = {
	{-0.525731f, 0.000000f, 0.850651f},
	{-0.442863f, 0.238856f, 0.864188f},
	{-0.295242f, 0.000000f, 0.955423f},
	{-0.309017f, 0.500000f, 0.809017f},
	{-0.162460f, 0.262866f, 0.951056f},
	{0.000000f, 0.000000f, 1.000000f},
	{0.000000f, 0.850651f, 0.525731f},
	{-0.147621f, 0.716567f, 0.681718f},
	{0.147621f, 0.716567f, 0.681718f},
	{0.000000f, 0.525731f, 0.850651f},
	{0.309017f, 0.500000f, 0.809017f},
	{0.525731f, 0.000000f, 0.850651f},
	{0.295242f, 0.000000f, 0.955423f},
	{0.442863f, 0.238856f, 0.864188f},
	{0.162460f, 0.262866f, 0.951056f},
	{-0.681718f, 0.147621f, 0.716567f},
	{-0.809017f, 0.309017f, 0.500000f},
	{-0.587785f, 0.425325f, 0.688191f},
	{-0.850651f, 0.525731f, 0.000000f},
	{-0.864188f, 0.442863f, 0.238856f},
	{-0.716567f, 0.681718f, 0.147621f},
	{-0.688191f, 0.587785f, 0.425325f},
	{-0.500000f, 0.809017f, 0.309017f},
	{-0.238856f, 0.864188f, 0.442863f},
	{-0.425325f, 0.688191f, 0.587785f},
	{-0.716567f, 0.681718f, -0.147621f},
	{-0.500000f, 0.809017f, -0.309017f},
	{-0.525731f, 0.850651f, 0.000000f},
	{0.000000f, 0.850651f, -0.525731f},
	{-0.238856f, 0.864188f, -0.442863f},
	{0.000000f, 0.955423f, -0.295242f},
	{-0.262866f, 0.951056f, -0.162460f},
	{0.000000f, 1.000000f, 0.000000f},
	{0.000000f, 0.955423f, 0.295242f},
	{-0.262866f, 0.951056f, 0.162460f},
	{0.238856f, 0.864188f, 0.442863f},
	{0.262866f, 0.951056f, 0.162460f},
	{0.500000f, 0.809017f, 0.309017f},
	{0.238856f, 0.864188f, -0.442863f},
	{0.262866f, 0.951056f, -0.162460f},
	{0.500000f, 0.809017f, -0.309017f},
	{0.850651f, 0.525731f, 0.000000f},
	{0.716567f, 0.681718f, 0.147621f},
	{0.716567f, 0.681718f, -0.147621f},
	{0.525731f, 0.850651f, 0.000000f},
	{0.425325f, 0.688191f, 0.587785f},
	{0.864188f, 0.442863f, 0.238856f},
	{0.688191f, 0.587785f, 0.425325f},
	{0.809017f, 0.309017f, 0.500000f},
	{0.681718f, 0.147621f, 0.716567f},
	{0.587785f, 0.425325f, 0.688191f},
	{0.955423f, 0.295242f, 0.000000f},
	{1.000000f, 0.000000f, 0.000000f},
	{0.951056f, 0.162460f, 0.262866f},
	{0.850651f, -0.525731f, 0.000000f},
	{0.955423f, -0.295242f, 0.000000f},
	{0.864188f, -0.442863f, 0.238856f},
	{0.951056f, -0.162460f, 0.262866f},
	{0.809017f, -0.309017f, 0.500000f},
	{0.681718f, -0.147621f, 0.716567f},
	{0.850651f, 0.000000f, 0.525731f},
	{0.864188f, 0.442863f, -0.238856f},
	{0.809017f, 0.309017f, -0.500000f},
	{0.951056f, 0.162460f, -0.262866f},
	{0.525731f, 0.000000f, -0.850651f},
	{0.681718f, 0.147621f, -0.716567f},
	{0.681718f, -0.147621f, -0.716567f},
	{0.850651f, 0.000000f, -0.525731f},
	{0.809017f, -0.309017f, -0.500000f},
	{0.864188f, -0.442863f, -0.238856f},
	{0.951056f, -0.162460f, -0.262866f},
	{0.147621f, 0.716567f, -0.681718f},
	{0.309017f, 0.500000f, -0.809017f},
	{0.425325f, 0.688191f, -0.587785f},
	{0.442863f, 0.238856f, -0.864188f},
	{0.587785f, 0.425325f, -0.688191f},
	{0.688191f, 0.587785f, -0.425325f},
	{-0.147621f, 0.716567f, -0.681718f},
	{-0.309017f, 0.500000f, -0.809017f},
	{0.000000f, 0.525731f, -0.850651f},
	{-0.525731f, 0.000000f, -0.850651f},
	{-0.442863f, 0.238856f, -0.864188f},
	{-0.295242f, 0.000000f, -0.955423f},
	{-0.162460f, 0.262866f, -0.951056f},
	{0.000000f, 0.000000f, -1.000000f},
	{0.295242f, 0.000000f, -0.955423f},
	{0.162460f, 0.262866f, -0.951056f},
	{-0.442863f, -0.238856f, -0.864188f},
	{-0.309017f, -0.500000f, -0.809017f},
	{-0.162460f, -0.262866f, -0.951056f},
	{0.000000f, -0.850651f, -0.525731f},
	{-0.147621f, -0.716567f, -0.681718f},
	{0.147621f, -0.716567f, -0.681718f},
	{0.000000f, -0.525731f, -0.850651f},
	{0.309017f, -0.500000f, -0.809017f},
	{0.442863f, -0.238856f, -0.864188f},
	{0.162460f, -0.262866f, -0.951056f},
	{0.238856f, -0.864188f, -0.442863f},
	{0.500000f, -0.809017f, -0.309017f},
	{0.425325f, -0.688191f, -0.587785f},
	{0.716567f, -0.681718f, -0.147621f},
	{0.688191f, -0.587785f, -0.425325f},
	{0.587785f, -0.425325f, -0.688191f},
	{0.000000f, -0.955423f, -0.295242f},
	{0.000000f, -1.000000f, 0.000000f},
	{0.262866f, -0.951056f, -0.162460f},
	{0.000000f, -0.850651f, 0.525731f},
	{0.000000f, -0.955423f, 0.295242f},
	{0.238856f, -0.864188f, 0.442863f},
	{0.262866f, -0.951056f, 0.162460f},
	{0.500000f, -0.809017f, 0.309017f},
	{0.716567f, -0.681718f, 0.147621f},
	{0.525731f, -0.850651f, 0.000000f},
	{-0.238856f, -0.864188f, -0.442863f},
	{-0.500000f, -0.809017f, -0.309017f},
	{-0.262866f, -0.951056f, -0.162460f},
	{-0.850651f, -0.525731f, 0.000000f},
	{-0.716567f, -0.681718f, -0.147621f},
	{-0.716567f, -0.681718f, 0.147621f},
	{-0.525731f, -0.850651f, 0.000000f},
	{-0.500000f, -0.809017f, 0.309017f},
	{-0.238856f, -0.864188f, 0.442863f},
	{-0.262866f, -0.951056f, 0.162460f},
	{-0.864188f, -0.442863f, 0.238856f},
	{-0.809017f, -0.309017f, 0.500000f},
	{-0.688191f, -0.587785f, 0.425325f},
	{-0.681718f, -0.147621f, 0.716567f},
	{-0.442863f, -0.238856f, 0.864188f},
	{-0.587785f, -0.425325f, 0.688191f},
	{-0.309017f, -0.500000f, 0.809017f},
	{-0.147621f, -0.716567f, 0.681718f},
	{-0.425325f, -0.688191f, 0.587785f},
	{-0.162460f, -0.262866f, 0.951056f},
	{0.442863f, -0.238856f, 0.864188f},
	{0.162460f, -0.262866f, 0.951056f},
	{0.309017f, -0.500000f, 0.809017f},
	{0.147621f, -0.716567f, 0.681718f},
	{0.000000f, -0.525731f, 0.850651f},
	{0.425325f, -0.688191f, 0.587785f},
	{0.587785f, -0.425325f, 0.688191f},
	{0.688191f, -0.587785f, 0.425325f},
	{-0.955423f, 0.295242f, 0.000000f},
	{-0.951056f, 0.162460f, 0.262866f},
	{-1.000000f, 0.000000f, 0.000000f},
	{-0.850651f, 0.000000f, 0.525731f},
	{-0.955423f, -0.295242f, 0.000000f},
	{-0.951056f, -0.162460f, 0.262866f},
	{-0.864188f, 0.442863f, -0.238856f},
	{-0.951056f, 0.162460f, -0.262866f},
	{-0.809017f, 0.309017f, -0.500000f},
	{-0.864188f, -0.442863f, -0.238856f},
	{-0.951056f, -0.162460f, -0.262866f},
	{-0.809017f, -0.309017f, -0.500000f},
	{-0.681718f, 0.147621f, -0.716567f},
	{-0.681718f, -0.147621f, -0.716567f},
	{-0.850651f, 0.000000f, -0.525731f},
	{-0.688191f, 0.587785f, -0.425325f},
	{-0.587785f, 0.425325f, -0.688191f},
	{-0.425325f, 0.688191f, -0.587785f},
	{-0.425325f, -0.688191f, -0.587785f},
	{-0.587785f, -0.425325f, -0.688191f},
	{-0.688191f, -0.587785f, -0.425325f},
	};

struct SMD2AnimationType
{
	s32 begin;
	s32 end;
	s32 fps;
};

static const SMD2AnimationType MD2AnimationTypeList[21] =
{
    {   0,  39,  9 },   // STAND
    {  40,  45, 10 },   // RUN
    {  46,  53, 10 },   // ATTACK
    {  54,  57,  7 },   // PAIN_A
    {  58,  61,  7 },   // PAIN_B
    {  62,  65,  7 },   // PAIN_C
    {  66,  71,  7 },   // JUMP
    {  72,  83,  7 },   // FLIP
    {  84,  94,  7 },   // SALUTE
    {  95, 111, 10 },   // FALLBACK
    { 112, 122,  7 },   // WAVE
    { 123, 134,  6 },   // POINT
    { 135, 153, 10 },   // CROUCH_STAND
    { 154, 159,  7 },   // CROUCH_WALK
    { 160, 168, 10 },   // CROUCH_ATTACK
    { 169, 172,  7 },   // CROUCH_PAIN
    { 173, 177,  5 },   // CROUCH_DEATH
    { 178, 183,  7 },   // DEATH_FALLBACK
    { 184, 189,  7 },   // DEATH_FALLFORWARD
    { 190, 197,  7 },   // DEATH_FALLBACKSLOW
    { 198, 198,  5 },   // BOOM
};


//! constructor
CAnimatedMeshMD2::CAnimatedMeshMD2()
: FrameList(0), FrameCount(0), TriangleCount(0)
{
	#ifdef _DEBUG
	IAnimatedMesh::setDebugName("CAnimatedMeshMD2 IAnimatedMesh");
	IMesh::setDebugName("CAnimatedMeshMD2 IMesh");
	IMeshBuffer::setDebugName("CAnimatedMeshMD2 IMeshBuffer");
	#endif
}



//! destructor
CAnimatedMeshMD2::~CAnimatedMeshMD2()
{
	if (FrameList)
		delete [] FrameList;
}



//! returns the amount of frames in milliseconds. If the amount is 1, it is a static (=non animated) mesh.
s32 CAnimatedMeshMD2::getFrameCount()
{
	return FrameCount<<MD2_FRAME_SHIFT;
}



//! returns the animated mesh based on a detail level. 0 is the lowest, 255 the highest detail. Note, that some Meshes will ignore the detail level.
IMesh* CAnimatedMeshMD2::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop)
{
	if ((u32)frame > (FrameCount<<MD2_FRAME_SHIFT))
		frame = (frame % (FrameCount<<MD2_FRAME_SHIFT));

	if (startFrameLoop == -1 && endFrameLoop == -1)
	{
		startFrameLoop = 0;
		endFrameLoop = FrameCount<<MD2_FRAME_SHIFT;
	}

	updateInterpolationBuffer(frame, startFrameLoop, endFrameLoop);
	return this;
}


//! returns amount of mesh buffers.
u32 CAnimatedMeshMD2::getMeshBufferCount() const
{
	return 1;
}


//! returns pointer to a mesh buffer
IMeshBuffer* CAnimatedMeshMD2::getMeshBuffer(u32 nr) const
{
	return (IMeshBuffer*) this;
}


//! Returns pointer to a mesh buffer which fits a material
IMeshBuffer* CAnimatedMeshMD2::getMeshBuffer( const video::SMaterial &material) const
{
	if (Material == material)
		return (IMeshBuffer*) this;
	else
		return 0;
}


//! returns the material of this meshbuffer
const video::SMaterial& CAnimatedMeshMD2::getMaterial() const
{
	return Material;
}


//! returns the material of this meshbuffer
video::SMaterial& CAnimatedMeshMD2::getMaterial()
{
	return Material;
}



//! returns pointer to vertices
const void* CAnimatedMeshMD2::getVertices() const
{
	return InterpolateBuffer.const_pointer();
}


//! returns pointer to vertices
void* CAnimatedMeshMD2::getVertices()
{
	return InterpolateBuffer.pointer();
}



//! returns which type of vertex data is stored.
video::E_VERTEX_TYPE CAnimatedMeshMD2::getVertexType() const
{
	return video::EVT_STANDARD;
}

//! returns the byte size (stride, pitch) of the vertex
u32 CAnimatedMeshMD2::getVertexPitch() const
{
	return sizeof ( video::S3DVertex );
}



//! returns amount of vertices
u32 CAnimatedMeshMD2::getVertexCount() const
{
	return FrameList[0].size();
}



//! returns pointer to Indices
const u16* CAnimatedMeshMD2::getIndices() const
{
	return Indices.const_pointer();
}



//! returns pointer to Indices
u16* CAnimatedMeshMD2::getIndices()
{
	return Indices.pointer();
}



//! returns amount of indices
u32 CAnimatedMeshMD2::getIndexCount() const
{
	return Indices.size();
}


// updates the interpolation buffer
void CAnimatedMeshMD2::updateInterpolationBuffer(s32 frame, s32 startFrameLoop, s32 endFrameLoop)
{
	u32 firstFrame, secondFrame;
	f32 div;

	// TA: resolve missing ipol in loop between end-start

	if (endFrameLoop - startFrameLoop == 0)
	{
		firstFrame = frame>>MD2_FRAME_SHIFT;
		secondFrame = frame>>MD2_FRAME_SHIFT;
		div = 1.0f;
	}
	else
	{
		// key frames
		u32 s = startFrameLoop >> MD2_FRAME_SHIFT;
		u32 e = endFrameLoop >> MD2_FRAME_SHIFT;

		firstFrame = frame >> MD2_FRAME_SHIFT;
		secondFrame = core::if_c_a_else_b ( firstFrame + 1 > e, s, firstFrame + 1 );

		firstFrame = core::s32_min ( FrameCount - 1, firstFrame );
		secondFrame = core::s32_min ( FrameCount - 1, secondFrame );

		//div = (frame % (1<<MD2_FRAME_SHIFT)) / (f32)(1<<MD2_FRAME_SHIFT);
		frame &= (1<<MD2_FRAME_SHIFT) - 1;
		div = frame * MD2_FRAME_SHIFT_RECIPROCAL;
	}

	video::S3DVertex* target = &InterpolateBuffer[0];
	video::S3DVertex* first = FrameList[firstFrame].pointer();
	video::S3DVertex* second = FrameList[secondFrame].pointer();

	s32 count = FrameList[firstFrame].size();

	// interpolate both frames
	for (s32 i=0; i<count; ++i)
	{
		target->Pos = (second->Pos - first->Pos) * div + first->Pos;
		target->Normal = (second->Normal - first->Normal) * div + first->Normal;

		++target;
		++first;
		++second;
	}

	//update bounding box
	BoundingBox = BoxList[secondFrame].getInterpolated(BoxList[firstFrame], div);
}




//! loads an md2 file
bool CAnimatedMeshMD2::loadFile(io::IReadFile* file)
{
	if (!file)
		return false;

	SMD2Header header;

	file->read(&header, sizeof(SMD2Header));

#ifdef __BIG_ENDIAN__
	header.magic = os::Byteswap::byteswap(header.magic);
	header.version = os::Byteswap::byteswap(header.version);
	header.skinWidth = os::Byteswap::byteswap(header.skinWidth);
	header.skinHeight = os::Byteswap::byteswap(header.skinHeight);
	header.frameSize = os::Byteswap::byteswap(header.frameSize);
	header.numSkins = os::Byteswap::byteswap(header.numSkins);
	header.numVertices = os::Byteswap::byteswap(header.numVertices);
	header.numTexcoords = os::Byteswap::byteswap(header.numTexcoords);
	header.numTriangles = os::Byteswap::byteswap(header.numTriangles);
	header.numGlCommands = os::Byteswap::byteswap(header.numGlCommands);
	header.numFrames = os::Byteswap::byteswap(header.numFrames);
	header.offsetSkins = os::Byteswap::byteswap(header.offsetSkins);
	header.offsetTexcoords = os::Byteswap::byteswap(header.offsetTexcoords);
	header.offsetTriangles = os::Byteswap::byteswap(header.offsetTriangles);
	header.offsetFrames = os::Byteswap::byteswap(header.offsetFrames);
	header.offsetGlCommands = os::Byteswap::byteswap(header.offsetGlCommands);
	header.offsetEnd = os::Byteswap::byteswap(header.offsetEnd);
#endif

	if (header.magic != MD2_MAGIC_NUMBER || header.version != MD2_VERSION)
	{
		os::Printer::log("MD2 Loader: Wrong file header", file->getFileName(), ELL_WARNING);
		return false;
	}

	// create Memory for indices and frames

	TriangleCount = header.numTriangles;
	if (FrameList)
		delete [] FrameList;
	FrameList = new core::array<video::S3DVertex>[header.numFrames];
	FrameCount = header.numFrames;

	s32 i;

	for (i=0; i<header.numFrames; ++i)
		FrameList[i].reallocate(header.numVertices);

	// read TextureCoords

	file->seek(header.offsetTexcoords, false);
	SMD2TextureCoordinate* textureCoords = new SMD2TextureCoordinate[header.numTexcoords];

	if (!file->read(textureCoords, sizeof(SMD2TextureCoordinate)*header.numTexcoords))
	{
		os::Printer::log("MD2 Loader: Error reading TextureCoords.", file->getFileName(), ELL_ERROR);
		return false;
	}

#ifdef __BIG_ENDIAN__
	for (i=0; i<header.numTexcoords; ++i)
	{
		textureCoords[i].s = os::Byteswap::byteswap(textureCoords[i].s);
		textureCoords[i].t = os::Byteswap::byteswap(textureCoords[i].t);
	}
#endif

	// read Triangles

	file->seek(header.offsetTriangles, false);

	SMD2Triangle *triangles = new SMD2Triangle[header.numTriangles];
	if (!file->read(triangles, header.numTriangles *sizeof(SMD2Triangle)))
	{
		os::Printer::log("MD2 Loader: Error reading triangles.", file->getFileName(), ELL_ERROR);
		return false;
	}

#ifdef __BIG_ENDIAN__
	for (i=0; i<header.numTriangles; ++i)
	{
		triangles[i].vertexIndices[0] = os::Byteswap::byteswap(triangles[i].vertexIndices[0]);
		triangles[i].vertexIndices[1] = os::Byteswap::byteswap(triangles[i].vertexIndices[1]);
		triangles[i].vertexIndices[2] = os::Byteswap::byteswap(triangles[i].vertexIndices[2]);
		triangles[i].textureIndices[0] = os::Byteswap::byteswap(triangles[i].textureIndices[0]);
		triangles[i].textureIndices[1] = os::Byteswap::byteswap(triangles[i].textureIndices[1]);
		triangles[i].textureIndices[2] = os::Byteswap::byteswap(triangles[i].textureIndices[2]);
	}
#endif

	// read Vertices

	u8 buffer[MD2_MAX_VERTS*4+128];
	SMD2Frame* frame = (SMD2Frame*)buffer;

	core::array< core::vector3df >* vertices = new core::array< core::vector3df >[header.numFrames];
	core::array< core::vector3df >* normals = new core::array< core::vector3df >[header.numFrames];

	file->seek(header.offsetFrames, false);

	for (i = 0; i<header.numFrames; ++i)
	{
		// read vertices

		file->read(frame, header.frameSize);

#ifdef __BIG_ENDIAN__
		frame->scale[0] = os::Byteswap::byteswap(frame->scale[0]);
		frame->scale[1] = os::Byteswap::byteswap(frame->scale[1]);
		frame->scale[2] = os::Byteswap::byteswap(frame->scale[2]);
		frame->translate[0] = os::Byteswap::byteswap(frame->translate[0]);
		frame->translate[1] = os::Byteswap::byteswap(frame->translate[1]);
		frame->translate[2] = os::Byteswap::byteswap(frame->translate[2]);
#endif
		// store frame data

		SFrameData fdata;
		fdata.begin = i;
		fdata.end = i;
		fdata.fps = 7;

		if (frame->name[0])
		{
			for (s32 s = 0; frame->name[s]!=0 && (frame->name[s] < '0' ||
				frame->name[s] > '9'); ++s)
				fdata.name += frame->name[s];

			if (!FrameData.empty() && FrameData[FrameData.size()-1].name == fdata.name)
				++FrameData[FrameData.size()-1].end;
			else
				FrameData.push_back(fdata);
		}

		// add vertices

		vertices[i].reallocate(header.numVertices);
		for (s32 j=0; j<header.numVertices; ++j)
		{
			core::vector3df v;
			v.X = frame->vertices[j].vertex[0] * frame->scale[0] + frame->translate[0];
			v.Z = frame->vertices[j].vertex[1] * frame->scale[1] + frame->translate[1];
			v.Y = frame->vertices[j].vertex[2] * frame->scale[2] + frame->translate[2];

			vertices[i].push_back(v);

			u8 normalidx = frame->vertices[j].lightNormalIndex;
			if (normalidx < Q2_VERTEX_NORMAL_TABLE_SIZE)
			{
				v.X = Q2_VERTEX_NORMAL_TABLE[normalidx][0];
				v.Z = Q2_VERTEX_NORMAL_TABLE[normalidx][1];
				v.Y = Q2_VERTEX_NORMAL_TABLE[normalidx][2];
			}

			normals[i].push_back(v);
		}

		// calculate bounding boxes
		if (header.numVertices)
		{
			core::aabbox3d<f32> box;
			box.reset(vertices[i][0]);

			for (s32 j=1; j<header.numVertices; ++j)
				box.addInternalPoint(vertices[i][j]);

			BoxList.push_back(box);
		}

	}

	// put triangles into frame list

	f32 dmaxs = 1.0f/(header.skinWidth);
	f32 dmaxt = 1.0f/(header.skinHeight);

	video::S3DVertex vtx;
	vtx.Color = video::SColor(255,255,255,255);

	for (s32 f = 0; f<header.numFrames; ++f)
	{
		core::array< core::vector3df >& vert = vertices[f];

		for (s32 t=0; t<header.numTriangles; ++t)
		{
			for (s32 n=0; n<3; ++n)
			{
				vtx.Pos = vert[triangles[t].vertexIndices[n]];
				vtx.Normal = normals[f].pointer()[triangles[t].vertexIndices[n]];
				vtx.TCoords.X = (textureCoords[triangles[t].textureIndices[n]].s + 0.5f) * dmaxs;
				vtx.TCoords.Y = (textureCoords[triangles[t].textureIndices[n]].t + 0.5f) * dmaxt;
				FrameList[f].push_back(vtx);
			}
		}
	}

	// create indices

	Indices.reallocate(header.numVertices);
	s16 count = TriangleCount*3;
	for (s16 n=0; n<count; n+=3)
	{
		Indices.push_back(n);
		Indices.push_back(n+1);
		Indices.push_back(n+2);
	}

	// reallocate interpolate buffer
	if (header.numFrames)
	{
		s32 currCount = FrameList[0].size();
		InterpolateBuffer.set_used(currCount);

		for (i=0; i<currCount; ++i)
		{
			InterpolateBuffer[i].TCoords = FrameList[0].pointer()[i].TCoords;
			InterpolateBuffer[i].Color = vtx.Color;
		}
	}

	// clean up

	delete [] normals;
	delete [] vertices;
	delete [] triangles;
	delete [] textureCoords;

	// return

	calculateBoundingBox();

	return true;
}



//! calculates the bounding box
void CAnimatedMeshMD2::calculateBoundingBox()
{
	BoundingBox.reset(0,0,0);

	if (FrameCount)
	{
		u32 defaultFrame = 1;

		if (defaultFrame>=FrameCount)
			defaultFrame = 0;

			for (u32 j=0; j<FrameList[defaultFrame].size(); ++j)
				BoundingBox.addInternalPoint(FrameList[defaultFrame].pointer()[j].Pos);
	}
}


//! sets a flag of all contained materials to a new value
void CAnimatedMeshMD2::setMaterialFlag(video::E_MATERIAL_FLAG flag, bool newvalue)
{
	Material.setFlag(flag, newvalue);
}



//! returns an axis aligned bounding box
const core::aabbox3d<f32>& CAnimatedMeshMD2::getBoundingBox() const
{
	return BoundingBox;
}

//! set user axis aligned bounding box
void CAnimatedMeshMD2::setBoundingBox( const core::aabbox3df& box)
{
	BoundingBox = box;
}



//! calculates normals
void CAnimatedMeshMD2::calculateNormals()
{
	for (u32 i=0; i<FrameCount; ++i)
	{
		video::S3DVertex* vtx = FrameList[i].pointer();

		for (u32 j=0; j<Indices.size(); j+=3)
		{
			core::plane3d<f32> plane(
				vtx[Indices[j]].Pos, vtx[Indices[j+1]].Pos,	vtx[Indices[j+2]].Pos);

			vtx[Indices[j]].Normal = plane.Normal;
			vtx[Indices[j+1]].Normal = plane.Normal;
			vtx[Indices[j+2]].Normal = plane.Normal;
		}
	}
}


//! Returns the type of the animated mesh.
E_ANIMATED_MESH_TYPE CAnimatedMeshMD2::getMeshType() const
{
	return EAMT_MD2;
}


//! Returns frame loop data for a special MD2 animation type.
void CAnimatedMeshMD2::getFrameLoop(EMD2_ANIMATION_TYPE l,
									s32& outBegin, s32& outEnd, s32& outFPS) const
{
	if (l < 0 || l >= EMAT_COUNT)
		return;

	outBegin = MD2AnimationTypeList[l].begin << MD2_FRAME_SHIFT;
	outEnd = MD2AnimationTypeList[l].end << MD2_FRAME_SHIFT;

	// correct to anim between last->first frame
	outEnd += MD2_FRAME_SHIFT == 0 ? 1 : ( 1 << MD2_FRAME_SHIFT ) - 1;
	outFPS = MD2AnimationTypeList[l].fps << MD2_FRAME_SHIFT;
}


//! Returns frame loop data for a special MD2 animation type.
bool CAnimatedMeshMD2::getFrameLoop(const c8* name,
	s32& outBegin, s32&outEnd, s32& outFPS) const
{
	for (s32 i=0; i<(s32)FrameData.size(); ++i)
		if (FrameData[i].name == name)
		{
			outBegin = FrameData[i].begin << MD2_FRAME_SHIFT;
			outEnd = FrameData[i].end << MD2_FRAME_SHIFT;
			outEnd += MD2_FRAME_SHIFT == 0 ? 1 : ( 1 << MD2_FRAME_SHIFT ) - 1;
			outFPS = FrameData[i].fps << MD2_FRAME_SHIFT;
			return true;
		}

	return false;
}



//! Returns amount of md2 animations in this file.
s32 CAnimatedMeshMD2::getAnimationCount() const
{
	return FrameData.size();
}


//! Returns name of md2 animation.
const c8* CAnimatedMeshMD2::getAnimationName(s32 nr) const
{
	if (nr < 0 || nr >= (s32)FrameData.size())
		return 0;

	return FrameData[nr].name.c_str();
}


} // end namespace scene
} // end namespace irr

