/*
	Twilight Prophecy 3D/Multimedia SDK
	A multi-platform development system for virtual reality and multimedia.

	Copyright (C) 1997-2001 by Twilight 3D Finland Oy Ltd.

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Please read the file LICENSE.TXT for additional details.


	source:
		ID Software Quake III Arena: model reader (.md3)

	revision history:
		Dec/26/1999 - Jukka Liimatta - initial revision
		Feb/10/2001 - Jukka Liimatta - renaissance build
		Aug/17/2001 - Jukka Liimatta - added support for vertex normals
*/
#include <primport/primport.hpp>

using namespace prcore;
using namespace primport;



//////////////////////////////////////////////////////
// reader declaration                              //
////////////////////////////////////////////////////

class ReaderQ3MDL
{
	public:

	ReaderQ3MDL(ImportQ3MDL& import, Stream& stream);
	~ReaderQ3MDL();

	private:

	ImportQ3MDL&	import;
	Stream&			stream;

	vec2f			ReadVector2D();
	vec3f			ReadVector3D();
	vec3f			Remap(const vec3f& v) const;
	matrix4x4f		Remap(const matrix4x4f& m) const;
	vec3f			GetVertexNormal(uint8 longtitude, uint8 latitude) const;
};


//////////////////////////////////////////////////////
// reader implementation                           //
////////////////////////////////////////////////////

ReaderQ3MDL::ReaderQ3MDL(ImportQ3MDL& im, Stream& s)
: import(im), stream(s)
{
	// reset stream
	stream.Seek(0,Stream::START);

	// id
	uint32 id = ReadLittleEndian<uint32>(stream);
	if ( id != PRCORE_CODE32('I','D','P','3') )
		PRCORE_EXCEPTION( "ImportMD3()", "Incorrect header id." );

	// header
	ReadLittleEndian<int32>(stream); // version

	char filename[68]; 
	stream.Read(filename,68);

	int numframe = ReadLittleEndian<int32>(stream);
	int numtag = ReadLittleEndian<int32>(stream);
	int nummesh = ReadLittleEndian<int32>(stream);
	ReadLittleEndian<int32>(stream); // numskin
	ReadLittleEndian<int32>(stream); // headersize
	ReadLittleEndian<int32>(stream); // tag0
	ReadLittleEndian<int32>(stream); // tag1
	ReadLittleEndian<int32>(stream); // filesize

	// frames
	int i = 0;
	for ( i=0; i<numframe; i++ )
	{
		ReadVector3D(); // min
		ReadVector3D(); // max
		ReadVector3D(); // pos
		ReadLittleEndian<float>(stream); // scale
		char creator[16]; stream.Read(creator,16);
	}

	// tags
	import.tags.SetSize( numtag );
	for ( i=0; i<numframe; i++ )
	{
		for ( int j=0; j<numtag; j++ )
		{
			char name[64]; 
			stream.Read(name,64);

			matrix4x4f m;
			m.Identity();
			m.SetT( ReadVector3D() );
			m.SetX( ReadVector3D() );
			m.SetY( ReadVector3D() );
			m.SetZ( ReadVector3D() );

			import.tags[j].matrices.PushBack( Remap(m) );
		}
	}

	// meshes
	for ( i=0; i<nummesh; i++ )
	{
		MeshQ3MDL mesh;

		// id
		uint32 id = ReadLittleEndian<uint32>(stream);
		if ( id != PRCORE_CODE32('I','D','P','3') )
			PRCORE_EXCEPTION( "ImportMD3()", "Incorrect header id." );

		// header
		char meshname[68]; 
		stream.Read(meshname,68);

		int numframemesh = ReadLittleEndian<int32>(stream);
		ReadLittleEndian<int32>(stream); // numskinmesh
		int numvertexmesh = ReadLittleEndian<int32>(stream);
		int numtrimesh = ReadLittleEndian<int32>(stream);
		ReadLittleEndian<int32>(stream); // tri0
		ReadLittleEndian<int32>(stream); // headersize
		ReadLittleEndian<int32>(stream); // texvec0
		ReadLittleEndian<int32>(stream); // vertex0
		ReadLittleEndian<int32>(stream); // meshsize

		char skinname[68]; 
		stream.Read(skinname,68);
		skinname[0] = 'm'; // fix skinname

		mesh.name = meshname;
		mesh.skin = skinname;

		// setup arrays
		int j = 0;
		mesh.faces.SetSize( numtrimesh );
		mesh.texcoords.SetSize( numvertexmesh );

		// faces
		for ( j=0; j<numtrimesh; j++ )
		{
			FaceQ3MDL& face = mesh.faces[ j ];
			face.index[0] = ReadLittleEndian<int32>(stream);
			face.index[1] = ReadLittleEndian<int32>(stream);
			face.index[2] = ReadLittleEndian<int32>(stream);
		}

		// texcoords
		for ( j=0; j<numvertexmesh; j++ )
		{
			mesh.texcoords[j] = ReadVector2D();
		}
	
		// points
		for ( j=0; j<numframemesh; j++ )
		{
			FrameQ3MDL frame;

			const float scale = 1.0f / 64.0f;
			for ( int k=0; k<numvertexmesh; k++ )
			{
				vec3f point;
				point.x = float( ReadLittleEndian<int16>(stream) ) * scale;
				point.y = float( ReadLittleEndian<int16>(stream) ) * scale;
				point.z = float( ReadLittleEndian<int16>(stream) ) * scale;
				
				uint8 longtitude = ReadLittleEndian<int8>(stream);
				uint8 latitude = ReadLittleEndian<int8>(stream);
				
				VertexQ3MDL vertex;
				vertex.point = Remap(point);
				vertex.normal = Remap(GetVertexNormal(longtitude,latitude));
				
				frame.vertices.PushBack( vertex );
			}

			mesh.frames.PushBack( frame );
		}

		import.meshes.PushBack( mesh );
	}
}


ReaderQ3MDL::~ReaderQ3MDL()
{
}


vec2f ReaderQ3MDL::ReadVector2D()
{
	vec2f v;
	v.x = ReadLittleEndian<float>(stream);
	v.y = ReadLittleEndian<float>(stream);
	return v;
}


vec3f ReaderQ3MDL::ReadVector3D()
{
	vec3f v;
	v.x = ReadLittleEndian<float>(stream);
	v.y = ReadLittleEndian<float>(stream);
	v.z = ReadLittleEndian<float>(stream);
	return v;
}


vec3f ReaderQ3MDL::Remap(const vec3f& v) const
{
	return vec3f(-v.y, v.z, v.x);
}


matrix4x4f ReaderQ3MDL::Remap(const matrix4x4f& m) const
{
	matrix4x4f e(1);
	e.SetX(-Remap( m.GetY() ) );
	e.SetY( Remap( m.GetZ() ) );
	e.SetZ( Remap( m.GetX() ) );
	e.SetT( Remap( m.GetT() ) );
	return e;
}


vec3f ReaderQ3MDL::GetVertexNormal(uint8 longtitude8bit, uint8 latitude8bit) const
{
	// based on Nate Miller's MD3 reader
	const float sp = prmath::pi * 2 / 255.0f;

	float longtitude = (float)longtitude8bit * sp;
	float latitude = (float)latitude8bit * sp;
	float sinlong = sinf(longtitude);
	
	return vec3f(
		cosf(latitude) * sinlong,
		sinf(latitude) * sinlong,
		cosf(longtitude) );
}


//////////////////////////////////////////////////////
// ImportQ3MDL                                     //
////////////////////////////////////////////////////

ImportQ3MDL::ImportQ3MDL()
{
}


ImportQ3MDL::ImportQ3MDL(Stream& stream)
{
	ReaderQ3MDL reader(*this,stream);
}


ImportQ3MDL::ImportQ3MDL(const char* filename)
{
	FileStream stream(filename);
	ReaderQ3MDL reader(*this,stream);
}


ImportQ3MDL::~ImportQ3MDL()
{
}
