/*
	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 object2 v6 (.lwo) reader

	revision history:
		Apr/12/2001 - Jukka Liimatta - Initial revision
*/
#include <primport/primport.hpp>

using namespace prcore;
using namespace prmath;
using namespace primport;

//#define LWO_DEBUG
#ifdef LWO_DEBUG
#include <iostream>
using namespace std;
#endif



//////////////////////////////////////////////////////
// reader internal types                           //
////////////////////////////////////////////////////

typedef VertexBufferLWO2 VertexArray;
typedef Array<struct MeshLW6> MeshArray;
typedef Array<uint32> IndexArray;
typedef Array<struct ClipLW6> ClipArray;


struct ClipLW6
{
	StringLWO2	filename;
	uint32		index;
};


struct PolyLW6
{
	uint16		count;
	uint32		index;
	uint16		surface;
	uint16		smoothgroup;
};


struct MeshLW6
{
	// layer
	StringLWO2		name;
	uint16			number;
	uint16			parent;
	point3f			pivot;
	bool			hidden;

	// mesh
	VertexArray		vertices;
	IndexArray		indices;
	Array<PolyLW6>	polys;
};


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

class ReaderLWO2
{
	public:

	ReaderLWO2(ImportLWO2& import, Stream& stream);
	~ReaderLWO2();

	private:

	int				depth;
	ImportLWO2&		import;
	Stream&			stream;
	MeshArray		meshes;
	ClipArray		clips;
	int				texchannel;

	vec3f			ReadVector3();
	char*			ReadAscii(int& length);
	uint32			ReadIndex(int& length);

	int				ReadChunk();
	int				ReadSubChunk();
	void			ChunkUnknown(int length);

	void			ChunkLAYR(int length);
	void			ChunkPNTS(int length);
	void			ChunkVMAP(int length);
	void			ChunkPOLS(int length);
	void			ChunkTAGS(int length);
	void			ChunkPTAG(int length);
	void			ChunkVMAD(int length);
	void			ChunkENVL(int length);
	void			ChunkCLIP(int length);
	void			ChunkSURF(int length);

	void			ChunkSTIL(int length);
	void			ChunkCOLR(int length);
	void			ChunkDIFF(int length);
	void			ChunkLUMI(int length);
	void			ChunkSPEC(int length);
	void			ChunkREFL(int length);
	void			ChunkTRAN(int length);
	void			ChunkTRNL(int length);
	void			ChunkGLOS(int length);
	void			ChunkSHRP(int length);
	void			ChunkBUMP(int length);
	void			ChunkSIDE(int length);
	void			ChunkBLOK(int length);
	void			ChunkIMAP(int length);
	void			ChunkTMAP(int length);
	void			ChunkIMAG(int length);
	void			ChunkCHAN(int length);

	void			InitSurface(SurfaceLWO2& surface);
	SurfaceLWO2*	FindSurface(StringLWO2& name);
	SurfaceLWO2&	GetCurrentSurface();
	ClipLW6&		GetCurrentClip();
	ClipLW6*		FindClip(uint32 index);
	MeshLW6&		GetCurrentMesh();

	void			GenerateVertexNormals(MeshLW6& mesh);
	void			ProcessGeometry();
};


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

ReaderLWO2::ReaderLWO2(ImportLWO2& ip, Stream& str)
: depth(0), import(ip), stream(str)
{
	// reset stream
	stream.Seek(0,Stream::START);

	// read iff85 header
	uint32 id = ReadBigEndian<uint32>(stream);
	if ( id != PRCORE_CODE32('M','R','O','F') )
		PRCORE_EXCEPTION( "ImportLWO2()", "not a valif iff85/lwo2 file." );

	// chunkdata size
	int size = ReadBigEndian<uint32>(stream) - 4;

	// read lwo2 magic
	uint32 magic = ReadBigEndian<uint32>(stream);
	if ( magic != PRCORE_CODE32('2','O','W','L') )
		PRCORE_EXCEPTION( "ImportLWO2()", "not an lwo2 file." );

	// read chunks
	while ( size > 0 )
	{
		size -= ReadChunk();
	}

	// process
	ProcessGeometry();
}


ReaderLWO2::~ReaderLWO2()
{
}


int ReaderLWO2::ReadChunk()
{
	// chunk header
	uint32 id = ReadBigEndian<uint32>(stream);
	uint32 length = ReadBigEndian<uint32>(stream);

	#ifdef LWO_DEBUG
	uint8* p = (uint8*)&id;
	for ( int i=0; i<depth; i++ ) cout << "  ";
	cout << p[3];
	cout << p[2];
	cout << p[1];
	cout << p[0] << endl;
	#endif

	++depth;

	switch ( id )
	{
		case PRCORE_CODE32('R','Y','A','L'): ChunkLAYR(length); break;
		case PRCORE_CODE32('S','T','N','P'): ChunkPNTS(length); break;
		case PRCORE_CODE32('P','A','M','V'): ChunkVMAP(length); break;
		case PRCORE_CODE32('S','L','O','P'): ChunkPOLS(length); break;
		case PRCORE_CODE32('S','G','A','T'): ChunkTAGS(length); break;
		case PRCORE_CODE32('G','A','T','P'): ChunkPTAG(length); break;
		case PRCORE_CODE32('D','A','M','V'): ChunkVMAD(length); break;
		case PRCORE_CODE32('L','V','N','E'): ChunkENVL(length); break;
		case PRCORE_CODE32('P','I','L','C'): ChunkCLIP(length); break;
		case PRCORE_CODE32('F','R','U','S'): ChunkSURF(length); break;
		default: ChunkUnknown(length); break;
	};

	--depth;

	return length + 8;
}


int ReaderLWO2::ReadSubChunk()
{
	// chunk header
	uint32 id = ReadBigEndian<uint32>(stream);
	uint16 length = ReadBigEndian<uint16>(stream);

	#ifdef LWO_DEBUG
	uint8* p = (uint8*)&id;
	for ( int i=0; i<depth; i++ ) cout << "  ";
	cout << p[3];
	cout << p[2];
	cout << p[1];
	cout << p[0] << endl;
	#endif

	++depth;

	switch ( id )
	{
		case PRCORE_CODE32('L','I','T','S'): ChunkSTIL(length); break;
		case PRCORE_CODE32('R','L','O','C'): ChunkCOLR(length); break;
		case PRCORE_CODE32('F','F','I','D'): ChunkDIFF(length); break;
		case PRCORE_CODE32('I','M','U','L'): ChunkLUMI(length); break;
		case PRCORE_CODE32('C','E','P','S'): ChunkSPEC(length); break;
		case PRCORE_CODE32('L','F','E','R'): ChunkREFL(length); break;
		case PRCORE_CODE32('N','A','R','T'): ChunkTRAN(length); break;
		case PRCORE_CODE32('L','N','R','T'): ChunkTRNL(length); break;
		case PRCORE_CODE32('S','O','L','G'): ChunkGLOS(length); break;
		case PRCORE_CODE32('P','R','H','S'): ChunkSHRP(length); break;
		case PRCORE_CODE32('P','M','U','B'): ChunkBUMP(length); break;
		case PRCORE_CODE32('E','D','I','S'): ChunkSIDE(length); break;
		case PRCORE_CODE32('K','O','L','B'): ChunkBLOK(length); break;
		case PRCORE_CODE32('P','A','M','I'): ChunkIMAP(length); break;
		case PRCORE_CODE32('P','A','M','T'): ChunkTMAP(length); break;
		case PRCORE_CODE32('G','A','M','I'): ChunkIMAG(length); break;
		case PRCORE_CODE32('N','A','H','C'): ChunkCHAN(length); break;
		default: ChunkUnknown(length); break;
	};

	--depth;

	return length + 6;
}


void ReaderLWO2::ChunkUnknown(int length)
{
	stream.Seek(length,Stream::CURRENT);
}


//////////////////////////////////////////////////////
// chunks                                          //
////////////////////////////////////////////////////

void ReaderLWO2::ChunkLAYR(int length)
{
	MeshLW6& mesh = meshes.PushBack();

	mesh.number = ReadBigEndian<uint16>(stream);
	uint16 flags = ReadBigEndian<uint16>(stream);
	mesh.hidden = (flags & 1) != 0;
	mesh.pivot = ReadVector3();
	mesh.name = ReadAscii(length);
	mesh.parent = 0xffff;

	if ( length > 16 )
	{
		mesh.parent = ReadBigEndian<uint16>(stream);
	}
}


void ReaderLWO2::ChunkPNTS(int length)
{
	int count = length / sizeof(point3f);

	MeshLW6& mesh = GetCurrentMesh();
	mesh.vertices.SetSize( count );

	for ( int i=0; i<count; i++ )
	{
		VertexLWO2& vertex = mesh.vertices[i];

		vertex.point = ReadVector3();
		vertex.normal = vec3f(0,0,0);
		vertex.texture = vec2f(0,0);
	}
}


void ReaderLWO2::ChunkVMAP(int length)
{
	uint32 type = ReadBigEndian<uint32>(stream);
	uint16 dimension = ReadBigEndian<uint16>(stream);
	StringLWO2 name = ReadAscii(length);
	length -= 6;

	switch ( type )
	{
		case PRCORE_CODE32('V','U','X','T'): 
			if ( dimension != 2 )
			{
				ChunkUnknown(length);
				return;
			}
			break;

		case PRCORE_CODE32('K','C','I','P'): 
		case PRCORE_CODE32('T','H','G','W'): 
		default:
			ChunkUnknown(length);
			return;
	}

	MeshLW6& mesh = GetCurrentMesh();

	while ( length > 0 )
	{
		uint32 index = ReadIndex(length);
		VertexLWO2& vertex = mesh.vertices[index];

		// read vertex data
		for ( int i=0; i<dimension; i++ )
		{
			vertex.texture[i] = ReadBigEndian<float>(stream);
		}
		length -= dimension * sizeof(float);
	}
}


void ReaderLWO2::ChunkPOLS(int length)
{
	uint32 type = ReadBigEndian<uint32>(stream);
	length -= sizeof(uint32);

	switch ( type )
	{
		case PRCORE_CODE32('E','C','A','F'): 
			break;

		case PRCORE_CODE32('V','R','U','C'):
		case PRCORE_CODE32('H','C','T','P'):
		case PRCORE_CODE32('L','A','B','M'):
		case PRCORE_CODE32('E','N','O','B'):
		default:
			ChunkUnknown(length);
			return;
	};

	MeshLW6& mesh = GetCurrentMesh();
	mesh.polys.SetSize(0);
	int cindex = 0;

	while ( length > 0 )
	{
		uint16 count = ReadBigEndian<uint16>(stream) & 0x03ff;
		length -= sizeof(uint16);

		PolyLW6& poly = mesh.polys.PushBack();
		poly.count = count;
		poly.index = cindex;
		poly.surface = 0;
		poly.smoothgroup = 0;

		for ( int i=0; i<(int)count; i++ )
		{
			uint32 index = ReadIndex(length);
			mesh.indices.PushBack( index );
		}
		cindex += count;
	}
}


void ReaderLWO2::ChunkTAGS(int length)
{
	while ( length > 0 )
	{
		StringLWO2 name = ReadAscii(length);
		//TODO: store name(tag)
		// current polygon lists's "tag" 
		// ( used to find which SURF is associated with the PTAG chunk below )
	}
}


void ReaderLWO2::ChunkPTAG(int length)
{
	uint32 type = ReadBigEndian<uint32>(stream);
	length -= sizeof(uint32);

	MeshLW6& mesh = GetCurrentMesh();

	while ( length > 0 )
	{
		uint32 index = ReadIndex(length);
		uint16 tag = ReadBigEndian<uint16>(stream);
		length -= sizeof(uint16);

		PolyLW6& poly = mesh.polys[index];

		switch ( type )
		{
			case PRCORE_CODE32('F','R','U','S'): 
				poly.surface = tag;
				break;

			case PRCORE_CODE32('T','R','A','P'):
				//TODO
				break;

			case PRCORE_CODE32('P','G','M','S'):
				poly.smoothgroup = tag;
				break;
		};
	}
}


void ReaderLWO2::ChunkVMAD(int length)
{
	uint32 type = ReadBigEndian<uint32>(stream);
	uint16 dimension = ReadBigEndian<uint16>(stream);
	StringLWO2 name = ReadAscii(length);
	length -= 6;

	switch ( type )
	{
		case PRCORE_CODE32('V','U','X','T'): 
			if ( dimension != 2 )
			{
				ChunkUnknown(length);
				return;
			}
			break;

		case PRCORE_CODE32('K','C','I','P'): 
		case PRCORE_CODE32('T','H','G','W'): 
		default:
			ChunkUnknown(length);
			return;
	}

	MeshLW6& mesh = GetCurrentMesh();

	while ( length > 0 )
	{
		uint32 v = ReadIndex(length);
		uint32 p = ReadIndex(length);

		PolyLW6& poly = mesh.polys[p];

		// find vertex with correct index
		uint32* pindex = &mesh.indices[ poly.index ];
		for ( int c=0; c<poly.count; c++ )
		{
			if ( *pindex == v )
				break;
			++pindex;
		}

		// new vertex
		VertexLWO2& vertex = mesh.vertices.PushBack();
		vertex = mesh.vertices[ v ];
		*pindex = mesh.vertices.GetSize() - 1;

		// read vertex data
		for ( int i=0; i<dimension; i++ )
		{
			vertex.texture[i] =	ReadBigEndian<float>(stream);
		}
		length -= dimension * sizeof(float);
	}
}


void ReaderLWO2::ChunkENVL(int length)
{
	//TODO: if anyone has urgent need for envelopes, this is the place to begin ;-)
	ChunkUnknown(length);
}


void ReaderLWO2::ChunkCLIP(int length)
{
	uint32 index = ReadBigEndian<uint32>(stream);
	length -= sizeof(uint32);

	ClipLW6& clip = clips.PushBack();
	clip.index = index;

	// subchunks
	while ( length > 0 )
	{
		length -= ReadSubChunk();
	}
}


void ReaderLWO2::ChunkSURF(int length)
{
	StringLWO2 name = ReadAscii(length);
	StringLWO2 sourcename = ReadAscii(length);

	SurfaceLWO2* source = FindSurface(sourcename);
	SurfaceLWO2& surface = import.surfaces.PushBack();
	InitSurface(surface);

	if ( source )
		surface = *source;

	surface.name = name;

	// subchunks
	while ( length > 0 )
	{
		length -= ReadSubChunk();
	}
}


//////////////////////////////////////////////////////
// sub-chunks                                      //
////////////////////////////////////////////////////

void ReaderLWO2::ChunkSTIL(int length)
{
	ClipLW6& clip = GetCurrentClip();

	StringLWO2 filename = ReadAscii(length);
	clip.filename = GetFilenameNoPath(filename);
}


void ReaderLWO2::ChunkCOLR(int length)
{
	float r = ReadBigEndian<float>(stream);
	float g = ReadBigEndian<float>(stream);
	float b = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope

	SurfaceLWO2& surface = GetCurrentSurface();

	surface.color.b = (uint8)(b*255);
	surface.color.g = (uint8)(g*255);
	surface.color.r = (uint8)(r*255);
	surface.color.a = 0xff;
}


void ReaderLWO2::ChunkDIFF(int length)
{
	SurfaceLWO2& surface = GetCurrentSurface();
	surface.diffuse = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope
}


void ReaderLWO2::ChunkLUMI(int length)
{
	SurfaceLWO2& surface = GetCurrentSurface();
	surface.luminosity = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope
}


void ReaderLWO2::ChunkSPEC(int length)
{
	SurfaceLWO2& surface = GetCurrentSurface();
	surface.specular = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope
}


void ReaderLWO2::ChunkREFL(int length)
{
	SurfaceLWO2& surface = GetCurrentSurface();
	surface.reflection = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope
}


void ReaderLWO2::ChunkTRAN(int length)
{
	SurfaceLWO2& surface = GetCurrentSurface();
	surface.transparency = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope
}


void ReaderLWO2::ChunkTRNL(int length)
{
	SurfaceLWO2& surface = GetCurrentSurface();
	surface.translucency = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope
}


void ReaderLWO2::ChunkGLOS(int length)
{
	SurfaceLWO2& surface = GetCurrentSurface();
	surface.glossiness = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope
}


void ReaderLWO2::ChunkSHRP(int length)
{
	SurfaceLWO2& surface = GetCurrentSurface();
	surface.sharpness = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope
}


void ReaderLWO2::ChunkBUMP(int length)
{
	SurfaceLWO2& surface = GetCurrentSurface();
	surface.bump = ReadBigEndian<float>(stream);
	ReadIndex(length); // envelope
}


void ReaderLWO2::ChunkSIDE(int length)
{
	uint16 side = ReadBigEndian<uint16>(stream);
	if ( side > 1 )
	{
		SurfaceLWO2& surface = GetCurrentSurface();
		surface.twosided = true;
	}
}


void ReaderLWO2::ChunkBLOK(int length)
{
	// subchunks
	while ( length > 0 )
	{
		length -= ReadSubChunk();
	}
}


void ReaderLWO2::ChunkIMAP(int length)
{
	StringLWO2 ordinal = ReadAscii(length);

	// subchunks
	while ( length > 0 )
	{
		length -= ReadSubChunk();
	}
}


void ReaderLWO2::ChunkTMAP(int length)
{
	// subchunks
	while ( length > 0 )
	{
		length -= ReadSubChunk();
	}
}


void ReaderLWO2::ChunkIMAG(int length)
{
	uint32 index = ReadIndex(length);
	ClipLW6* clip = FindClip( index );
	if ( clip )
	{
		SurfaceLWO2& surface = GetCurrentSurface();
		TextureLWO2& texture = surface.texture[ texchannel ];

		if ( clip->filename.GetLength() > 0 )
		{
			texture.filename = clip->filename;
			texture.enable = true;
		}
	}
}


void ReaderLWO2::ChunkCHAN(int length)
{
	uint32 chan = ReadBigEndian<uint32>(stream);

	switch ( chan )
	{
		case PRCORE_CODE32('R','L','O','C'): texchannel = LWO2_TEXTURE_COLOR; break;
		case PRCORE_CODE32('F','F','I','D'): texchannel = LWO2_TEXTURE_DIFFUSE; break;
		case PRCORE_CODE32('I','M','U','L'): texchannel = LWO2_TEXTURE_LUMINOSITY; break;
		case PRCORE_CODE32('C','E','P','S'): texchannel = LWO2_TEXTURE_SPECULAR; break;
		case PRCORE_CODE32('S','O','L','G'): texchannel = LWO2_TEXTURE_REFLECTION; break;
		case PRCORE_CODE32('L','F','E','R'): texchannel = LWO2_TEXTURE_TRANSPARENCY; break;
		case PRCORE_CODE32('N','A','R','T'): texchannel = LWO2_TEXTURE_TRANSLUCENCY; break;
		case PRCORE_CODE32('D','N','I','R'): texchannel = LWO2_TEXTURE_GLOSSINESS; break;
		case PRCORE_CODE32('L','N','R','T'): texchannel = LWO2_TEXTURE_SHARPNESS; break;
		case PRCORE_CODE32('P','M','U','B'): texchannel = LWO2_TEXTURE_BUMP; break;
	}
}


//////////////////////////////////////////////////////
// reader methods                                  //
////////////////////////////////////////////////////

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


inline char* ReaderLWO2::ReadAscii(int& length)
{
	static char buffer[256];

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

		if ( !c ) break;
	}

	int count = ptr - buffer;
	int modulo = count % 2;
	if ( modulo )
		stream.Seek(1,Stream::CURRENT);

	length -= (count + modulo);
	return buffer;
}


inline uint32 ReaderLWO2::ReadIndex(int& length)
{
	uint32 v0 = ReadBigEndian<uint16>(stream);
	if ( v0 < 0xff00 )
	{
		length -= sizeof(uint16);
		return v0;
	}

	length -= sizeof(uint32);
	return (((v0 & 0x00ff) << 16) | ReadBigEndian<uint16>(stream)) - 0xff00;
}


void ReaderLWO2::InitSurface(SurfaceLWO2& surface)
{
	surface.color = 0xffbbbbbb;
	surface.diffuse = 0;
	surface.luminosity = 0;
	surface.specular = 0;
	surface.reflection = 0;
	surface.transparency = 0;
	surface.translucency = 0;
	surface.glossiness = 0.2f;
	surface.sharpness = 0.5f;
	surface.bump = 1;

	surface.twosided = false;

	for ( int i=0; i<LWO2_TEXTURE_COUNT; i++ )
	{
		surface.texture[i].enable = false;
	}
}


SurfaceLWO2* ReaderLWO2::FindSurface(StringLWO2& name)
{
	for ( int i=0; i<import.surfaces.GetSize(); i++ )
	{
		if ( import.surfaces[i].name == name )
			return &import.surfaces[i];
	}
	return NULL;
}


SurfaceLWO2& ReaderLWO2::GetCurrentSurface()
{
	int count = import.surfaces.GetSize();
	if ( count )
	{
		return import.surfaces[count-1];
	}

	// create arbitrary surface
	SurfaceLWO2& surface = import.surfaces.PushBack();
	InitSurface(surface);

	return surface;
}


ClipLW6& ReaderLWO2::GetCurrentClip()
{
	int count = clips.GetSize();
	return clips[count-1];
}


ClipLW6* ReaderLWO2::FindClip(uint32 index)
{
	for ( int i=0; i<clips.GetSize(); i++ )
	{
		if ( clips[i].index == index )
			return &clips[i];
	}
	return NULL;
}


MeshLW6& ReaderLWO2::GetCurrentMesh()
{
	int count = meshes.GetSize();
	if ( count )
	{
		return meshes[count-1];
	}

	// create arbitrary mesh
	MeshLW6& mesh = meshes.PushBack();

	mesh.name = "arbitrary mesh";
	mesh.number = 0xfffe;
	mesh.parent = 0xffff;
	mesh.pivot = point3f(0,0,0);
	mesh.hidden = false;

	return mesh;
}


void ReaderLWO2::GenerateVertexNormals(MeshLW6& mesh)
{
	for ( int i=0; i<mesh.polys.GetSize(); i++ )
	{
		PolyLW6& poly = mesh.polys[i];
		uint32* index = &mesh.indices[ poly.index ];

		point3f& a = mesh.vertices[index[0]].point;
		point3f& b = mesh.vertices[index[1]].point;
		point3f& c = mesh.vertices[index[2]].point;
		vec3f normal = CrossProduct(b-a,c-a);

		for ( int v=0; v<poly.count; v++ )
		{
			VertexLWO2& vertex = mesh.vertices[index[v]];
			vertex.normal += normal;
		}
	}

	for ( int v=0; v<mesh.vertices.GetSize(); v++ )
	{
		VertexLWO2& vertex = mesh.vertices[v];
		vertex.normal.Normalize();
	}
}


void ReaderLWO2::ProcessGeometry()
{
	for ( int i=0; i<meshes.GetSize(); i++ )
	{
		// source
		MeshLW6& mesh = meshes[i];

		// destination
		MeshLWO2& trimesh = import.meshes.PushBack();
		VertexBufferLWO2& vbuffer = trimesh.vertexbuffer;

		// generate vertex normals
		GenerateVertexNormals( mesh );

		// convert into trimesh
		int j = 0;

		for ( j=0; j<mesh.polys.GetSize(); j++ )
		{
			PolyLW6& poly = mesh.polys[j];
			uint32* index = &mesh.indices[ poly.index ];

			// find indexbuffer
			IndexBufferLWO2* cbuffer = NULL;
			for ( int c=0; c<trimesh.indexbuffers.GetSize(); c++ )
			{
				if ( trimesh.indexbuffers[c].surface == poly.surface )
				{
					cbuffer = &trimesh.indexbuffers[c];
					break;
				}
			}

			// create new indexbuffer
			if ( !cbuffer )
			{
				cbuffer = &trimesh.indexbuffers.PushBack();
				cbuffer->surface = poly.surface;
			}

			// write polygon vertex indices as triangle indices
			// into the vertexbuffer
			for ( int v=0; v<(poly.count-2); v++ )
			{
				cbuffer->indexbuffer.PushBack( index[0] );
				cbuffer->indexbuffer.PushBack( index[v+1] );
				cbuffer->indexbuffer.PushBack( index[v+2] );
			}
		}

		// copy vertexbuffer
		for ( j=0; j<mesh.vertices.GetSize(); j++ )
		{
			VertexLWO2& vertex = trimesh.vertexbuffer.PushBack();
			VertexLWO2& source = mesh.vertices[j];

			vertex.point = source.point;
			vertex.normal = source.normal;
			vertex.texture.x = source.texture.x;
			vertex.texture.y = -source.texture.y;
		}
	}
}


//////////////////////////////////////////////////////
// ImportLWO2                                      //
////////////////////////////////////////////////////

ImportLWO2::ImportLWO2()
{
}


ImportLWO2::ImportLWO2(Stream& stream)
{
	ReaderLWO2 reader(*this,stream);
}


ImportLWO2::ImportLWO2(const char* filename)
{
	FileStream stream(filename);
	ReaderLWO2 reader(*this,stream);
}


ImportLWO2::~ImportLWO2()
{
}
