#include "Mesh.h"

//=======================================================================
//=======================================================================

struct PolyToTri
{
	int			m_firstTri;
	int			m_triCount;
};

struct VMADStruct
{
	void operator=(const VMADStruct &v)
	{
		m_oldVertex = v.m_oldVertex;
		m_newVertex = v.m_newVertex;
		memcpy(m_value,v.m_value,sizeof(float)*16);
	}
	int			m_oldVertex;
	int			m_newVertex;
	float		m_value[16];
};

void Mesh::loadLW6( )
{
	int chunklen,chunkname,subchunkname,subchunklen;
	int i;
	Vector3 p;
	float r;
	Color color,spec;
	UINT8 *chunks, *chunkend,*subchunkend,*vertexPointer = NULL;
	int PNTS = 0, POLS = 0, VMAP = 0, TAGS = 0, PTAG = 0, VMAD = 0, SURF = 0;
	std::vector<const char *> tags;
	std::vector<const char *> vmaps;
	std::vector<PolyToTri> polygons;
	std::vector<VMADStruct> vmads;

	m_boundingBoxMin.set(FLT_MAX,FLT_MAX,FLT_MAX);
	m_boundingBoxMax.set(-FLT_MAX,-FLT_MAX,-FLT_MAX);
	m_boundingRadius = 0.0f;

	chunks = m_readptr;

	m_nextchunkptr = chunks;
	while(m_nextchunkptr < m_fileend)
	{
		m_readptr = m_nextchunkptr;
		chunkname = readID4();
		chunklen = readU4();
		chunkend = m_readptr + chunklen;
		m_nextchunkptr = chunkend;
		if( chunklen & 1 ) m_nextchunkptr++;	//if size is odd, add 1 padding

		switch(chunkname)
		{
		case 'PNTS':
			svPrint("PNTS %d vertices\n",chunklen/12);
			if( PNTS == 0 )	//consider only the first PNTS chunk
			{
				m_vertices.resize( chunklen / 12 );
				for(i=0;i<m_vertices.size();++i)
				{
					p = readVEC12();
					m_vertices[i].m_position = p;
					m_vertices[i].m_normal.set(0,0,0);
					m_vertices[i].m_triangleUsing = -1;
					if( p.x > m_boundingBoxMax.x ) m_boundingBoxMax.x = p.x;
					if( p.x < m_boundingBoxMin.x ) m_boundingBoxMin.x = p.x;
					if( p.y > m_boundingBoxMax.y ) m_boundingBoxMax.y = p.y;
					if( p.y < m_boundingBoxMin.y ) m_boundingBoxMin.y = p.y;
					if( p.z > m_boundingBoxMax.z ) m_boundingBoxMax.z = p.z;
					if( p.z < m_boundingBoxMin.z ) m_boundingBoxMin.z = p.z;

					r = dot(p, p);
					if(r > m_boundingRadius) m_boundingRadius = r;
				}
			}
			PNTS++;
			break;
		case 'VMAP':
			svPrint("VMAP");
			if( PNTS == 0 ) svError("invalid LWO2 file: VMAP chunk before PNTS");
			if( PNTS == 1 )	//consider only VMAPS associated to the first PNTS chunk
			{
				UINT32 type = readID4();	//PICK,WGHT,MNVW,TXUV,RGB,RGBA,MORF,SPOT
				UINT32 dimension = readU2();
				const char *name = readS0();	//VMAD needs to be associated with the VMAP with the same name
				vmaps.push_back( name );
				static float val[16];	//we assume max dimension to be 16
				int read = 0;
				while( m_readptr < chunkend )
				{
					++read;
					int vi = readVX();
					for(i=0;i<dimension;++i)
						val[i] = readF4();
					switch( type )
					{
					case 'TXUV':
						if( dimension == 2 )	//just in case..
							m_vertices[vi].m_uv = Vector2(val[0],1.0f-val[1]);
						break;
					case 'RGB ':
						m_vertices[vi].m_diffuse.set( val[0],val[1],val[2], 1 );
						break;
					case 'RGBA':
						m_vertices[vi].m_diffuse.set( val[0],val[1],val[2],val[3] );
						break;
					}
				}
				svPrint(" %c%c%c%c %s %d",((type>>24)&0xff),((type>>16)&0xff),((type>>8)&0xff),(type&0xff),name,read);
			}
			svPrint("\n");
			VMAP++;
			break;
		case 'VMAD':
			svPrint("VMAD");
			if( PNTS == 0 || POLS == 0 ) svError("invalid LWO2 file: VMAD chunk before PNTS or POLS");
			if( PNTS == 1 && POLS == 1 ) //VMADS associated to the first PNTS and POLS
			{
				UINT32 type = readID4();	//PICK,WGHT,MNVW,TXUV,RGB,RGBA,MORF,SPOT
				UINT32 dimension = readU2();
				const char *name = readS0();	//VMAD needs to be associated with the VMAP with the same name
				int vmap = -1;
				int read = 0;
				for(i=0;i<vmaps.size();++i)
				{
					if( !strcmp(name,vmaps[i]) )
					{
						vmap = i;
						break;
					}
				}
				if( vmap != -1 )	//corresponding VMAP found
				{
					static float val[16];	//we assume max dimension to be 16
					while( m_readptr < chunkend )
					{
						++read;
						int vi = readVX();
						int pi = readVX();
						for(i=0;i<dimension;++i)
							val[i] = readF4();
//						svPrint(" %d/%f",vi,val[0]);

						//create new vertex
						if( type == 'TXUV' || type == 'RGB ' || type == 'RGBA' )
						{
							//check whether there's already new vertex with the same attributes
							int nv = -1;
							for(i=0;i<vmads.size();++i)
							{
								if( vmads[i].m_oldVertex == vi && !memcmp(val,vmads[i].m_value,dimension*sizeof(float)) )
								{
									nv = vmads[i].m_newVertex;
									break;
								}
							}
							if( nv == -1 )
							{	//vertex doesn't exist yet
								static Vertex ver;
								ver = m_vertices[vi];
								ver.m_triangleUsing = -1;
								switch( type )
								{
								case 'TXUV':
									if( dimension == 2 )	//just in case..
										ver.m_uv = Vector2(val[0],1.0f-val[1]);
									break;
								case 'RGB ':
									ver.m_diffuse.set( val[0],val[1],val[2], 1 );
									break;
								case 'RGBA':
									ver.m_diffuse.set( val[0],val[1],val[2],val[3] );
									break;
								}

								nv = m_vertices.size();
								m_vertices.push_back( ver );

								static VMADStruct vmad;
								vmad.m_oldVertex = vi;
								vmad.m_newVertex = nv;
								memcpy( vmad.m_value, val, dimension*sizeof(float) );
								vmads.push_back( vmad );
							}

							//remap polygons using the old vertex
							int base = polygons[pi].m_firstTri;
							for(i=0;i<polygons[pi].m_triCount;++i)
							{
								for(int j=0;j<3;++j)
								{
									if( m_triangles[base + i].m_v[j] == vi )
										m_triangles[base + i].m_v[j] = nv;
								}
							}
						}
					}
				}
				//else: each polygon has discontinuous mapping
				svPrint(" %c%c%c%c %s %d",((type>>24)&0xff),((type>>16)&0xff),((type>>8)&0xff),(type&0xff),name,read);
			}
			svPrint("\n");
			VMAD++;
			break;
		case 'POLS':
			svPrint("POLS");
			if( PNTS == 0 ) svError("invalid LWO2 file: POLS chunk before PNTS");
			if( PNTS == 1 )	//POLS associated only with the first PNTS
			{
				UINT32 type = readID4();	//FACE,CURV,PTCH,MBAL,BONE
				int read = 0;
				static int vtable[1023];	//max number of vertices is 1023
				while( m_readptr < chunkend )
				{
					++read;
					UINT32 vert = readU2() & 0x3f;	//6 msb are flags, we don't need them
					for(i=0;i<vert;++i)
					{
						vtable[i] = readVX();
					}
					if( type == 'FACE' )
					{
						static PolyToTri poly;
						poly.m_firstTri = m_triangles.size();
						poly.m_triCount = vert - 2;
						polygons.push_back( poly );

						static Triangle t;
						static Vector3 n,e1,e2;
						t.m_surface = -1;
						t.m_n[0] = -1;
						t.m_n[1] = -1;
						t.m_n[2] = -1;

						//normal is cross product of the first and the last edges
						e1 = m_vertices[vtable[1]].m_position;
						e1 -= m_vertices[vtable[0]].m_position;
						e2 = m_vertices[vtable[vert-1]].m_position;
						e2 -= m_vertices[vtable[0]].m_position;
						n = cross(e1, e2);
						n.normalize();

						for(i=0;i<vert-2;i++)
						{
							t.m_v[0] = vtable[0];
							t.m_v[1] = vtable[i+1];
							t.m_v[2] = vtable[i+2];
							t.m_normal = n;
							
							e1 = m_vertices[t.m_v[1]].m_position;
							e1 -= m_vertices[t.m_v[0]].m_position;
							e2 = m_vertices[t.m_v[2]].m_position;
							e2 -= m_vertices[t.m_v[0]].m_position;
							e1 = cross(e1, e2);
							t.m_area = 0.5f * e1.length();
							m_triangles.push_back( t );
						}
						for(i=0;i<vert;i++)
						{
							m_vertices[vtable[i]].m_normal += n;
						}
					}
				}
				svPrint(" %c%c%c%c %d",((type>>24)&0xff),((type>>16)&0xff),((type>>8)&0xff),(type&0xff),read);
			}
			svPrint("\n");
			POLS++;
			break;
		case 'TAGS':
			svPrint("TAGS");
			if( TAGS == 0 )	//consider only the first TAGS chunk
			{
				while( m_readptr < chunkend )
				{
					svPrint(" %s",m_readptr);
					tags.push_back( (const char *)m_readptr );
					readS0();
				}
			}
			svPrint("\n");
			TAGS++;
			break;
		case 'PTAG':
			svPrint("PTAG");
			if( TAGS == 0 || POLS == 0 ) svError("invalid LWO2 file: PTAG chunk before TAGS or POLS");
			if( TAGS == 1 && POLS == 1 ) //PTAGs associated with the first TAGS and POLS chunk
			{
				UINT32 type = readID4();	//SURF,PART,SMGP
				int read = 0;
				while( m_readptr < chunkend )
				{
					++read;
					int polyindex = readVX();
					if( polyindex < 0 || polyindex > polygons.size() - 1 )
						svError("invalid LWO2 file: PTAG polygon index invalid (%d)",polyindex);
					int tagindex = readU2();
					if( tagindex < 0 || tagindex > tags.size() - 1 )
						svError("invalid LWO2 file: PTAG polygon tag invalid (%d)",tagindex);
					int base = polygons[polyindex].m_firstTri;
					for(i=0;i<polygons[polyindex].m_triCount;++i)
					{
						if( m_triangles[base + i].m_surface != -1 )
							svPrint(" duplicate PTAG %d", polyindex);
						m_triangles[base + i].m_surface = tagindex;
					}
				}
				svPrint(" %c%c%c%c %d",((type>>24)&0xff),((type>>16)&0xff),((type>>8)&0xff),(type&0xff),read);
			}
			svPrint("\n");
			PTAG++;
			break;
		case 'SURF':
			svPrint("SURF %s",m_readptr);
			{
				int tagindex = -1;
				for(i=0;i<tags.size();++i)
				{
					if( !strcmp((const char*)m_readptr, tags[i]) )
					{
						tagindex = i;
						break;
					}
				}
				if( tagindex == -1 ) svError("invalid LWO2 file: surface %s not found from the TAGS list",m_readptr);
				if( m_surfaces.size() < tagindex + 1 ) m_surfaces.resize( tagindex + 1 );
				m_surfaces[tagindex].m_name = (const char *)m_readptr;
				readS0();	//read the name
				readS0();	//read the source surface (we're not going to support that)

				m_nextsubchunkptr = m_readptr;
				while(m_nextsubchunkptr < chunkend)
				{
					m_readptr = m_nextsubchunkptr;
					subchunkname = readID4();
					subchunklen = readU2();
					subchunkend = m_readptr + subchunklen;
					m_nextsubchunkptr = subchunkend;
					if( subchunklen & 1 ) m_nextsubchunkptr++;	//if size is odd, add 1 padding

					switch(subchunkname)
					{
					case 'COLR':
						m_surfaces[tagindex].m_color.set( readCOL12() );
						break;
					}
				}
			}
			svPrint("\n");
			SURF++;
			break;
		default:
			break;
		}
	}

	m_boundingRadius = float(sqrt( m_boundingRadius ));

	//calculate how many triangles are using each surface
	//set triangle using info
	for(i=0;i<m_triangles.size();++i)
	{
		if( m_triangles[i].m_surface == -1 )
			svError("invalid LWO2 file: not every polygon has a surface");
		++m_surfaces[m_triangles[i].m_surface].m_polygons;

		m_vertices[m_triangles[i].m_v[0]].m_triangleUsing = (i<<2) | 0;
		m_vertices[m_triangles[i].m_v[1]].m_triangleUsing = (i<<2) | 1;
		m_vertices[m_triangles[i].m_v[2]].m_triangleUsing = (i<<2) | 2;
	}

	//normalize vertex normals and calculate initial tangent spaces
	static Vector3 up(0,1,0);
	for(i=0;i<m_vertices.size();i++)
	{
		m_vertices[i].m_normal.normalize();
		m_vertices[i].m_t1 = cross(up, m_vertices[i].m_normal);
		m_vertices[i].m_t2 = cross(m_vertices[i].m_normal, m_vertices[i].m_t1);
    if( (float)SS::fabs(dot(up, m_vertices[i].m_normal)) > 0.999f ) m_vertices[i].m_reliable = false;
		else m_vertices[i].m_reliable = true;
	}
}

//=======================================================================
//=======================================================================

UINT32 Mesh::readID4()
{
	return readU4();
}
int Mesh::readI1()
{
	signed char a = readU1();
	return a;
}
int Mesh::readI2()
{
	signed short a = readU2();
	return a;
}
int Mesh::readI4()
{
	signed int a = readU4();
	return a;
}
UINT8 Mesh::readU1()
{
	UINT8 a;
	a = *m_readptr++;
	return a;
}
UINT16 Mesh::readU2()
{
	UINT8 a,b;
	a = *m_readptr++;
	b = *m_readptr++;
	return (a<<8) + b;
}
UINT32 Mesh::readU4()
{
	UINT8 a,b,c,d;
	a = *m_readptr++;
	b = *m_readptr++;
	c = *m_readptr++;
	d = *m_readptr++;
	return (a<<24) + (b<<16) + (c<<8) + d;
}
float Mesh::readF4()
{
	UINT32 a = readU4();
	float *b = (float *)&a;
	return *b;
}
const char *Mesh::readS0()
{
	UINT8 *ptr = m_readptr;
	int l = strlen( (const char*)m_readptr ) + 1;	//file length + terminating 0
	if( l & 1 ) ++m_readptr;	//if odd, add 1 padding
	m_readptr += l;
	return (const char*)ptr;
}
UINT32 Mesh::readVX()
{
	UINT8 a = *m_readptr++;
	if( a == 0xff )	//if first is 0xff, 4 byte index is used, otherwise 2 bytes
	{
		UINT8 b = *m_readptr++;
		UINT8 c = *m_readptr++;
		UINT8 d = *m_readptr++;
		return (b<<16) + (c<<8) + d;
	}
	UINT8 b = *m_readptr++;
	return (a << 8 ) + b;
}
Vector3 Mesh::readCOL12()
{
	return readVEC12();
}
Vector3 Mesh::readVEC12()
{
	Vector3 a;
	a.x = readF4();
	a.y = readF4();
	a.z = readF4();
	return a;
}

//=======================================================================
//=======================================================================
