text: // 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<f32>& 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_
  