/*
	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:
		NewTek Lightwave object v5 (.lwo) reader

	revision history:
		Apr/14/1999 - Jukka Liimatta - Initial revision
		Feb/10/2001 - Jukka Liimatta - renaissance build
*/
#include <primport/primport.hpp>

using namespace prcore;
using namespace primport;



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

class ReaderLWO
{
	public:

	ReaderLWO(ImportLWO& import, Stream& stream);
	~ReaderLWO();

	private:

	ImportLWO&		import;
	Stream&			stream;
	SurfaceLWO*		cursurface;
	TextureLWO*		curtexture;

	void			Switch(bool subchunk);
	void			ChunkPNTS(int length);
	void			ChunkPOLS(int length);
	void			ChunkSRFS(int length);
	void			ChunkSURF(int length);
	void			ChunkCOLR(int length);
	void			ChunkFLAG(int length);
	void			ChunkCTEX(int length);
	void			ChunkDTEX(int length);
	void			ChunkSTEX(int length);
	void			ChunkRTEX(int length);
	void			ChunkTTEX(int length);
	void			ChunkBTEX(int length);
	void			ChunkTIMG(int length);
	void			ChunkTFLG(int length);
	void			ChunkTSIZ(int length);
	void			ChunkTCTR(int length);
	void			ChunkTFAL(int length);
	void			ChunkTVEL(int length);
	void			ChunkTWRP(int length);
	void			ChunkTFP0(int length);
	void			ChunkTFP1(int length);
	void			ChunkLUMI(int length);
	void			ChunkDIFF(int length);
	void			ChunkSPEC(int length);
	void			ChunkREFL(int length);
	void			ChunkTRAN(int length);

	vec3f			ReadVector3();
	char*			ReadAscii();
	int				ReadMapStyle();
	void			SetupTexture(TextureLWO& texture);
	void			SetupSurface(SurfaceLWO& surface, const char* name);
};


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

ReaderLWO::ReaderLWO(ImportLWO& i, Stream& s)
: import(i), stream( s )
{
	// reset stream
	stream.Seek(0,Stream::START);

	// check header
	uint32 id = ReadBigEndian<uint32>(stream);
	if ( id != PRCORE_CODE32('M','R','O','F') )
		PRCORE_EXCEPTION( "ImportLWO()", "not a valif iff/lwo file." );

	//int size = 
		ReadBigEndian<uint32>(stream);
	stream.Seek(4,Stream::CURRENT);

	while ( !stream.IsEOS() )
	{
		Switch( false );
	}
}


ReaderLWO::~ReaderLWO()
{
}


void ReaderLWO::Switch(bool subchunk)
{
	uint32 id = ReadBigEndian<uint32>(stream);
	int32 length = subchunk ? 
		ReadBigEndian<uint16>(stream) :
		ReadBigEndian<uint32>(stream);

	switch ( id )
	{
		case PRCORE_CODE32('S','T','N','P'):	ChunkPNTS( length ); break;	// points
		case PRCORE_CODE32('S','L','O','P'):	ChunkPOLS( length ); break;	// polygons
		case PRCORE_CODE32('S','F','R','S'):	ChunkSRFS( length ); break;	// surfaces
		case PRCORE_CODE32('F','R','U','S'):	ChunkSURF( length ); break;	// surface index
		case PRCORE_CODE32('R','L','O','C'):	ChunkCOLR( length ); break;	// color
		case PRCORE_CODE32('G','A','L','F'):	ChunkFLAG( length ); break;	// flags
		case PRCORE_CODE32('X','E','T','C'):	ChunkCTEX( length ); break;	// tmap: color
		case PRCORE_CODE32('X','E','T','D'):	ChunkDTEX( length ); break;	// tmap: diffuse
		case PRCORE_CODE32('X','E','T','S'):	ChunkSTEX( length ); break;	// tmap: specular
		case PRCORE_CODE32('X','E','T','R'):	ChunkRTEX( length ); break;	// tmap: reflection
		case PRCORE_CODE32('X','E','T','T'):	ChunkTTEX( length ); break;	// tmap: transparency
		case PRCORE_CODE32('X','E','T','B'):	ChunkBTEX( length ); break;	// tmap: bump
		case PRCORE_CODE32('G','M','I','T'):	ChunkTIMG( length ); break;	// texture filename
		case PRCORE_CODE32('G','L','F','T'):	ChunkTFLG( length ); break;	// texture flags
		case PRCORE_CODE32('Z','I','S','T'):	ChunkTSIZ( length ); break;	// texture size
		case PRCORE_CODE32('R','T','C','T'):	ChunkTCTR( length ); break;	// texture center
		case PRCORE_CODE32('L','A','F','T'):	ChunkTFAL( length ); break;	// texture center
		case PRCORE_CODE32('L','E','V','T'):	ChunkTVEL( length ); break;	// texture center
		case PRCORE_CODE32('P','R','W','T'):	ChunkTWRP( length ); break;	// texture wrap
		case PRCORE_CODE32('0','P','F','T'):	ChunkTFP0( length ); break;	// texture xtile
		case PRCORE_CODE32('1','P','F','T'):	ChunkTFP1( length ); break;	// texture ytile
		case PRCORE_CODE32('I','M','U','L'):	ChunkLUMI( length ); break;	// luminosity
		case PRCORE_CODE32('F','F','I','D'):	ChunkDIFF( length ); break;	// diffuse
		case PRCORE_CODE32('C','E','P','S'):	ChunkSPEC( length ); break;	// specular
		case PRCORE_CODE32('L','F','E','R'):	ChunkREFL( length ); break;	// reflection
		case PRCORE_CODE32('N','A','R','T'):	ChunkTRAN( length ); break;	// transparency
		default: stream.Seek(length,Stream::CURRENT); break; // unknown
	};
}


void ReaderLWO::ChunkPNTS(int length)
{
	int count = length / sizeof(point3f);
	import.points.SetSize( count );

	for ( int i=0; i<count; i++ )
	{
		import.points[i] = ReadVector3();
	}
}


void ReaderLWO::ChunkPOLS(int length)
{
	int max = stream.GetOffset() + length;

	while ( stream.GetOffset() < max )
	{
		uint16 numvert = ReadBigEndian<uint16>(stream);

		PolygonLWO poly;
		poly.vertices.SetSize( numvert );

		for ( int i=0; i<numvert; i++ )
		{
			poly.vertices[i] = ReadBigEndian<uint16>(stream);
		}
		int16 sid = ReadBigEndian<int16>(stream);

		if ( sid<0 )
		{
			// detail polygons
			for ( ; sid<0; sid++ )
			{
				uint16 numvert = ReadBigEndian<uint16>(stream);
				stream.Seek( numvert * sizeof(uint16), Stream::CURRENT );
			}
		}
		else
		{
			// surface id
			poly.surface = sid - 1;
			import.polygons.PushBack( poly );
		}
	}
}


void ReaderLWO::ChunkSRFS(int length)
{
	int end = stream.GetOffset() + length;
	while ( stream.GetOffset() < end )
	{
		char* name = ReadAscii();

		SurfaceLWO surface;
		SetupSurface( surface, name );
		import.surfaces.PushBack( surface );
	}
}


void ReaderLWO::ChunkSURF(int length)
{
	int end = stream.GetOffset() + length;
	char* name = ReadAscii();

	cursurface = NULL;
	for ( int i=0; i<import.surfaces.GetSize(); i++ )
	{
		if ( strcmp(import.surfaces[i].name,name) == 0 )
		{
			cursurface = &import.surfaces[i];
			break;
		}
	}

	while ( stream.GetOffset() < end )
	{
		Switch( true );
	}
}


void ReaderLWO::ChunkCOLR(int length)
{
	uint8 r = ReadBigEndian<uint8>(stream);
	uint8 g = ReadBigEndian<uint8>(stream);
	uint8 b = ReadBigEndian<uint8>(stream);
	ReadBigEndian<uint8>(stream); // ignore

	cursurface->color = Color32( r, g, b, 0xff );
}


void ReaderLWO::ChunkFLAG(int length)
{
	cursurface->flags = ReadBigEndian<uint16>(stream);
}


void ReaderLWO::ChunkCTEX(int length)
{
	curtexture = &cursurface->ctex;
	curtexture->style = ReadMapStyle();
}


void ReaderLWO::ChunkDTEX(int length)
{
	curtexture = &cursurface->dtex;
	curtexture->style = ReadMapStyle();
}


void ReaderLWO::ChunkSTEX(int length)
{
	curtexture = &cursurface->stex;
	curtexture->style = ReadMapStyle();
}


void ReaderLWO::ChunkRTEX(int length)
{
	curtexture = &cursurface->rtex;
	curtexture->style = ReadMapStyle();
}


void ReaderLWO::ChunkTTEX(int length)
{
	curtexture = &cursurface->ttex;
	curtexture->style = ReadMapStyle();
}


void ReaderLWO::ChunkBTEX(int length)
{
	curtexture = &cursurface->btex;
	curtexture->style = ReadMapStyle();
}


void ReaderLWO::ChunkTIMG(int length)
{
	char* name = ReadAscii();

	if ( strcmp(name,"(none)") )
	{
		curtexture->filename = name;
	}
}


void ReaderLWO::ChunkTFLG(int length)
{
	curtexture->flags = ReadBigEndian<uint16>(stream);
}


void ReaderLWO::ChunkTSIZ(int length)
{
	curtexture->size = ReadVector3();
}


void ReaderLWO::ChunkTCTR(int length)
{
	curtexture->center = ReadVector3();
}


void ReaderLWO::ChunkTFAL(int length)
{
	curtexture->falloff = ReadVector3();
}


void ReaderLWO::ChunkTVEL(int length)
{
	curtexture->velocity = ReadVector3();
}


void ReaderLWO::ChunkTWRP(int length)
{
	ReadBigEndian<uint32>(stream); // wrap
}


void ReaderLWO::ChunkTFP0(int length)
{
	curtexture->tile.x = ReadBigEndian<float>(stream);
}


void ReaderLWO::ChunkTFP1(int length)
{
	curtexture->tile.y = ReadBigEndian<float>(stream);
}


void ReaderLWO::ChunkLUMI(int length)
{
	cursurface->luminosity = ReadBigEndian<uint16>(stream) / 256.0f;
}


void ReaderLWO::ChunkDIFF(int length)
{
	cursurface->diffuse = ReadBigEndian<uint16>(stream) / 256.0f;
}


void ReaderLWO::ChunkSPEC(int length)
{
	cursurface->specular = ReadBigEndian<uint16>(stream) / 256.0f;
}


void ReaderLWO::ChunkREFL(int length)
{
	cursurface->reflection = ReadBigEndian<uint16>(stream) / 256.0f;
}


void ReaderLWO::ChunkTRAN(int length)
{
	cursurface->transparency = ReadBigEndian<uint16>(stream) / 256.0f;
}


vec3f ReaderLWO::ReadVector3()
{
	vec3f v;
	v.x = ReadBigEndian<float>(stream);
	v.y = ReadBigEndian<float>(stream);
	v.z = ReadBigEndian<float>(stream);
	return v;
}


char* ReaderLWO::ReadAscii()
{
	static char buffer[256];

	char* ptr = buffer;
	for ( ;; )
	{
		char c;
		stream.Read(&c,1);
		*ptr++ = c;

		if ( !c ) break;
	}

	int count = strlen(buffer) + 1;
	if ( count%2 )
		stream.Seek(1,Stream::CURRENT);

	return buffer;
}


int ReaderLWO::ReadMapStyle()
{
	static const char* type[] = 
	{
		"Planar Image Map",
		"Cylindrical Image Map",
		"Spherical Image Map",
		"Cubic Image Map"
	};
	static const int numtype = sizeof(type)/sizeof(char*);


	char* name = ReadAscii();

	for ( int i=0; i<numtype; i++ )
	{
		if ( !strcmp(name,type[i]) )
			return i;
	}
	return 0;
}


//////////////////////////////////////////////////////
// setup                                           //
////////////////////////////////////////////////////

void ReaderLWO::SetupTexture(TextureLWO& texture)
{
	texture.flags = TextureLWO::X_AXIS;
	texture.style = 0;
	texture.center = point3f(0,0,0);
	texture.velocity = vec3f(0,0,0);
	texture.falloff = vec3f(0,0,0);
	texture.size = vec3f(1,1,1);
	texture.tile = vec3f(1,1,1);
}


void ReaderLWO::SetupSurface(SurfaceLWO& surface, const char* name)
{
	surface.flags = 0;
	surface.name = name;
	surface.color = Color32( 0xffffffff );
	surface.luminosity = 0;
	surface.diffuse = 0;
	surface.specular = 0;
	surface.reflection = 0;
	surface.transparency = 0;

	SetupTexture( surface.ctex );
	SetupTexture( surface.dtex );
	SetupTexture( surface.stex );
	SetupTexture( surface.rtex );
	SetupTexture( surface.ttex );
	SetupTexture( surface.btex );
}


//////////////////////////////////////////////////////
// texture coordinate generation                   //
////////////////////////////////////////////////////

static const float PI = prmath::pi;
static const float HALFPI = PI * 0.5f;
static const float TWOPI = PI * 2.0f;


static void xyztoh(float x, float y, float z, float *h)
{
	if (x==0.0f && z==0.0f)
	{
	   *h = 0.0f;
	}
	else
	{
		if (z == 0.0f)     *h = (x < 0.0f) ? HALFPI : -HALFPI;
		else if (z < 0.0f) *h = -float( atan(x/z) + PI );
		else               *h = -float( atan(x/z) );
	}
}


static void xyztohp(float x, float y, float z, float *h, float *p)
{
	if (x == 0.0f && z == 0.0f)
	{
		*h = 0.0f;
		if (y != 0.0f)  *p = (y < 0.0f) ? -HALFPI : HALFPI;
		else            *p = 0.f;
	}
	else
	{
		if (z == 0.0f)      *h = (x < 0.0f) ? HALFPI : -HALFPI;
		else if (z < 0.0f)  *h = -float( atan(x/z) + PI );
		else                *h = -float( atan(x/z) );

		x = (float)sqrt(x*x + z*z);
		if (x == 0.f)   *p = (y < 0.0f) ? -HALFPI : HALFPI;
		else            *p = float( atan(y/x) );
	}
}


static inline float fract(float v)
{
	return v - float( floor(v) );
}


point2f TextureLWO::GetTexCoord(const point3f& point, float time) const
{
	point3f p = point - center + (velocity-falloff*time) * time;

	switch ( style )
	{
		case PLANAR:
		{
			float s = (flags & X_AXIS) ? p.z / size.z + 0.5f :  p.x / size.x + 0.5f;
			float t = (flags & Y_AXIS) ? -p.z / size.z + 0.5f : -p.y / size.y + 0.5f;
			return point2f( s*tile.x, t*tile.y );
		}
		case CYLINDRICAL:
		{
			float lon, t;
			if ( flags & X_AXIS )
			{
				xyztoh( p.z, p.x, -p.y, &lon );
				t = -p.x / size.x + 0.5f;
			}
			else if ( flags & Y_AXIS )
			{
				xyztoh( -p.x, p.y, p.z, &lon );
				t = -p.y / size.y + 0.5f;
			}
			else
			{
				xyztoh( -p.x, p.z, -p.y, &lon );
				t = -p.z / size.z + 0.5f;
			}

			lon = 1.0f - lon/TWOPI;
			if ( tile.x != 1.f ) lon = fract( lon ) * tile.x;
			float s = fract( lon ) / 4.0f;
			t = fract( t );
			return point2f( s, t );
		}
		case SPHERICAL:
		{
			float lon, lat;

			if ( flags & X_AXIS )		xyztohp( p.z, p.x,-p.y, &lon, &lat );
			else if ( flags & Y_AXIS )	xyztohp(-p.x, p.y, p.z, &lon, &lat );
			else						xyztohp(-p.x, p.z,-p.y, &lon, &lat);

			lon = 1.f - lon / TWOPI;
			lat = 0.5f - lat / PI;
			float s = fract( fract(lon) * tile.x ) / 4.0f;
			float t = fract( fract(lat) * tile.y );
			return point2f( s, t );
		};
		case CUBIC:
		default:
			return point2f(0,0);
	}
}


//////////////////////////////////////////////////////
// ImportLWO                                       //
////////////////////////////////////////////////////

ImportLWO::ImportLWO()
{
}


ImportLWO::ImportLWO(Stream& stream)
{
	ReaderLWO reader(*this,stream);
}


ImportLWO::ImportLWO(const char* filename)
{
	FileStream stream(filename);
	ReaderLWO reader(*this,stream);
}


ImportLWO::~ImportLWO()
{
}
