/*--------------------------------------------------------------------------
 * File: lwo.c
 * Written by: Alexander Boczar & Fredrik Kling, 1997-04-03
 * Description: Lightwave Object (LWO) loader.
 *
 * Updates:
 * -- Date -- | ----- Name ----- |-- Did what....
 * 1997-04-16 | Alexander Boczar | Remade too new system
 *
 * Todo:
 *
 * Surface import! (Attributes etc...)
 -------------------------------------------------------------------------------*/

#include "system/xstdlib.h"
#include "system/xstdio.h"
#include "system/xstring.h"
#include "system/xmath.h"
#include "formats/v3o.h"
#include "formats/lwo.h"
#include "vmath/vector.h"
#include "misc/col.h"

#if !defined(M_PI)
#define M_PI 3.141592654
#endif

#define dprintf if ( debug) printf
static int debug = TRUE;

typedef enum
{
	NONE,
	PLANE,
	SPHERE,
	CYLINDER,
	CUBIC,
	REFLECTIVE,
} MAPPING;

typedef enum
{
	XANGLE,
	YANGLE,
	ZANGLE
} ANGLE;

typedef struct
{
	MAPPING mapping;
	ANGLE angle;
	float xsize,ysize,zsize;
	float xcenter,ycenter,zcenter;
	float xtile,ytile;
} SURF;

static void maptextures(V3O *obj);

static int offset[65536];

static SURF surfs[256];

/*#############################################################################
#
# Points Chunk
#
#############################################################################*/

static void pnts( V3O *obj, XFILE *f, int chunkstart, int chunklen)
{
	int i;

	dprintf("[#] Found points chunk, position %d, size %d\n", chunkstart, chunklen);
	printf("[-] Found %d vertices\n", chunklen / 12);
	obj->numvertex = chunklen / 12;

	// Allocate memory for functions...

  obj->orgvertex=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);
	obj->rotvertex=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);
	obj->projvertex=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);
	obj->orgnormal=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);
	obj->rotnormal=(VECTOR *)xsafe_malloc (sizeof(VECTOR) * obj->numvertex);

	for ( i=0; i<obj->numvertex; i++)
	{
    obj->orgvertex[i].x = (float)xfrle_float( f);
    obj->orgvertex[i].y = -(float)xfrle_float( f);
    obj->orgvertex[i].z = (float)xfrle_float( f);
	}

	dprintf("[#] Done\n");
}

/*#############################################################################
#
# Surface Names Chunk
#
#############################################################################*/

static void srfs( V3O *obj, XFILE *f, int chunkstart, int chunklen)
{
	int chunkend = chunkstart + chunklen;
	int i;
	int material,letter;
	BYTE c1,c2;

	dprintf("[#] Found surfaces name chunk, position %d, size %d\n", chunkstart, chunklen);

	material = 0;

	c1 = '\0';

  while ( xftell( f) < chunkend)
	{
		c2 = c1;
    c1 = xfr_byte( f);
		if ( (c1 == '\0') && (c2 != '\0'))
			material++;
	}

	if ( !material)
	{
		printf("[&] Empty surface chunk, skipping!\n");
		return;
	}

	obj->nummaterial = material;
	obj->material = (V3OMATERIAL *)xsafe_malloc( obj->nummaterial * sizeof(V3OMATERIAL));

  xfseek( f, chunkstart, SEEK_SET);

	material = letter = 0;

	c1 = '\0';

  while ( xftell( f) < chunkend)
	{
		c2 = c1;
    c1 = xfr_byte( f);
		obj->material[material].name[ letter++] = c1;
		if ( (c1 == '\0') && (c2 != '\0'))
			material++, letter = 0;
		if ( (c1 == '\0') && (c2 == '\0'))
			letter--;
	}
	printf("[-] Found %d surfaces(materials)\n", obj->nummaterial);

	for( i=0; i<obj->nummaterial; i++)
	{
    obj->material[i].flags = V3OMATERIALFLAG_FLAT;
		obj->material[i].color.r = 200;
		obj->material[i].color.g = 200;
		obj->material[i].color.b = 200;
	}

	dprintf("[#] Done\n");
}

/*#############################################################################
#
# Polygon Chunk
#
#############################################################################*/

static void pols( V3O *obj, XFILE *f, int chunkstart, int chunklen)
{
	int chunkend = chunkstart + chunklen;
	int i;
	int *list;
	int points, surface, skip;
	int plot, line, tri, quad, other, detail, extra;

	dprintf("[#] Found polygon chunk, position %d, size %d\n", chunkstart, chunklen);

	skip = plot = line = tri = quad = other = detail = extra = 0;

	list = offset;

  while ( xftell( f) < chunkend)
	{
    *list = xftell( f);

    points = xfrle_word( f);
    xfseek( f, points * 2, SEEK_CUR);
    surface = (signed short)xfrle_word( f);

//		dprintf("[#] Polygon with %d vertexes and surface number %d\n", points, surface);

		if ( skip)
			skip--;
		else if ( surface < 0)
		{
			detail++;
      skip = xfrle_word( f);
			dprintf("[#] Detail description encountered, with %d polygons.\n", skip);
			extra += skip;
		}
		else if ( points == 1)
			plot++, list++;
		else if ( points == 2)
			line++, list++;
		else if ( points == 3)
			tri++, list++;
		else if ( points == 4)
			quad++, list++;
		else
			other++;
	}

	printf("[-] Object consists of:\n");
	if ( plot)
		printf("    %4d dots\n", plot);
	if ( line)
		printf("    %4d lines\n", line);
	if ( tri)
		printf("    %4d triangular polygons\n", tri);
	if ( quad)
		printf("    %4d 4-sided polygons = 2 x triangular polygons\n", quad);
	if ( other)
		printf("    %4d unsupported polygons\n", other);
	if ( detail)
		printf("    %4d detailed surfaces\n", detail);
	if ( detail)
		printf("    %4d detail polygons\n", extra);

	obj->numsurface = plot + line + tri + quad*2;

	if ( !obj->numsurface)
	{
		printf("[&] No supported polygons, skipping!\n");
		return;
	}

	surface = 0;
  obj->surface = (V3OSURFACE *)xmalloc( obj->numsurface * sizeof( V3OSURFACE));

	list = offset;
  for( i=0; i<(plot + line + tri + quad); i++)
	{
    xfseek( f, *list, SEEK_SET);

    points = xfrle_word( f);

		if ( points == 1)
		{
      obj->surface[surface].flags = V3OSURFACEFLAG_POINT;
      obj->surface[surface].v1 = xfrle_word( f);
      obj->surface[surface].material = xfrle_word( f) - 1;
			surface++;
		}
		else if ( points == 2)
		{
      obj->surface[surface].flags = V3OSURFACEFLAG_LINE;
      obj->surface[surface].v1 = xfrle_word( f);
      obj->surface[surface].v2 = xfrle_word( f);
      obj->surface[surface].material = xfrle_word( f) - 1;
			surface++;
		}
		else if ( points == 3)
		{
      obj->surface[surface].flags = V3OSURFACEFLAG_POLY;
      obj->surface[surface].v1 = xfrle_word( f);
      obj->surface[surface].v2 = xfrle_word( f);
      obj->surface[surface].v3 = xfrle_word( f);
      obj->surface[surface].material = xfrle_word( f) - 1;
			surface++;
		}
		else if ( points == 4)
		{
      obj->surface[surface].flags = V3OSURFACEFLAG_POLY;
      obj->surface[surface].v1 = xfrle_word( f);
      obj->surface[surface].v2 = xfrle_word( f);
      obj->surface[surface].v3 = xfrle_word( f);
      obj->surface[surface+1].flags = V3OSURFACEFLAG_POLY;
			obj->surface[surface+1].v1 = obj->surface[surface].v1;
			obj->surface[surface+1].v2 = obj->surface[surface].v3;
      obj->surface[surface+1].v3 = xfrle_word( f);
      obj->surface[surface].material = xfrle_word( f) - 1;
			obj->surface[surface+1].material = obj->surface[surface].material;
			surface+=2;
		}
		list++;
	}
	dprintf("[#] Done\n");
}

/*#############################################################################
#
# Surface description Chunk
#
#############################################################################*/

static void surf( V3O *obj, XFILE *f, int chunkstart, int chunklen)
{
	int chunkend = chunkstart + chunklen;
	V3OMATERIAL *current;
	V3OTEXTURE *texture = NULL;
	SURF *surf;
	unsigned int subchunkid;
	int subchunkstart, subchunkend, subchunklen;
	int i;
	char name[256];

	dprintf("[#] Found surfaces description chunk, position %d, size %d\n", chunkstart, chunklen);

	i = 0;
	do
	{
    name[i++] = xfr_byte( f);
	}
	while( name[i-1] != '\0');

	dprintf("[#] Surface name '%s'\n", name);

	if ( ((i + 1) & 0xfe) != i)
    if ( xfr_byte( f) != '\0')
		{
			printf("[&] Warning, expecting alignment byte (0)\n");
      xfseek( f, -1, SEEK_CUR);
		}

	for ( i=0; i<obj->nummaterial; i++)
	{
		if ( strcmp( name, obj->material[i].name) == 0)
		{
			current = &obj->material[i];
			surf = &surfs[i];
			break;
		}
	}

	dprintf("Found matching surface name '%s' - >'%s'\n", name, obj->material[i].name);

	if ( current == NULL)
	{
		printf("[!] Error, Surface not found in index!\n");
		return;
	}
	dprintf("    [%d]: %s\n", i, current->name);

  while ( xftell( f) < chunkend)
	{
    subchunkid = xfrle_dword( f);
    subchunklen = xfrle_word( f);
    subchunkstart = xftell( f);
		subchunkend = subchunkstart + subchunklen;

		switch ( subchunkid)
		{
			case 'COLR':
			{
				dprintf("[#] Color subchunk found.\n");
        current->color.r = xfr_byte( f);
        current->color.g = xfr_byte( f);
        current->color.b = xfr_byte( f);
				//current->color.a = xleread_byte( f);
        xfr_byte (f);
				break;
			}
			case 'FLAG':
			{
        int flags = xfrle_word( f);
			 	dprintf("[#] Flags subchunk found.\n");
        if ( flags & 0x100)
          current->flags |= V3OMATERIALFLAG_DBLSIDE;
				if ( flags & 0x004)
          current->flags &= !V3OMATERIALFLAG_FLAT, current->flags |= V3OMATERIALFLAG_GOURADE;
        break;
			}
			case 'CTEX':
			{
				char type[256];
				int i = 0;

        if( current->flags & V3OMATERIALFLAG_TEXTURE)
				{
					printf("[&] Multiple textures not supported!\n");
					texture = NULL;
					break;
				}
        current->flags |= V3OMATERIALFLAG_TEXTURE;
				current->texture = obj->numtexture++;

				if( obj->texture == NULL)
					obj->texture = xmalloc( sizeof( V3OTEXTURE));
				else
					obj->texture = realloc( obj->texture, obj->numtexture * sizeof( V3OTEXTURE));
				texture = &obj->texture[ current->texture];

				dprintf("[#] Texture subchunk found.\n");

				do
				{
          type[i] = xfr_byte( f);
				} while( type[i++] != '\0');

				if( _stricmp( type, "Spherical Image Map") == 0)
					surf->mapping = SPHERE;
				else if( _stricmp( type, "Planar Image Map") == 0)/* etc..*/
					surf->mapping = PLANE;
				else if ( _stricmp (type, "Cylindrical Image Map") == 0)
					surf->mapping = CYLINDER;
				else if (_stricmp (type, "Cubic Image Map") == 0)
					surf->mapping = CUBIC;

				break;
			}
			case 'RIMG':
			{
				char str[256];
				char *strptr;
				int i = 0;

				dprintf("[#] Reflection image subchunk found, Creating texture map!!\n");

				if (current->flags & V3OMATERIALFLAG_TEXTURE)
				{
					printf("[&] Multiple textures not supported!\n");
					texture = NULL;
					break;
				}
				current->flags = V3OMATERIALFLAG_ENVMAP;
				current->texture = obj->numtexture++;

				if( obj->texture == NULL)
					obj->texture = xmalloc( sizeof( V3OTEXTURE));
				else
					obj->texture = realloc( obj->texture, obj->numtexture * sizeof( V3OTEXTURE));
				texture = &obj->texture[ current->texture];

				// Read some dummy byte...
				xfr_byte ( f);
				do
				{
          str[i] = xfr_byte( f);
				} while( str[i++] != '\0');

				strptr = str + strlen( str) - 3;
				strcpy( strptr, "vio");

				strptr = str + strlen( str);
				while( (strptr != str) && (strptr[-1] != '\\') && (strptr[-1] != '/'))
					strptr--;
				strcpy( texture->filename, strptr);
				dprintf("Filename: %s\n", texture->filename);


				surf->mapping = REFLECTIVE;
			}
			break;
			case 'DTEX':
			case 'STEX':
			case 'RTEX':
			case 'TTEX':
			case 'BTEX':
			{
				dprintf("[#] Unsupported texture type found, ignoring.\n");
				texture = NULL;
				break;
			}
			case 'TIMG':
			{
				int i = 0;

				if( texture != NULL)
				{
					char str[256];
					char *strptr;

					dprintf("[#] Texture image filename subchunk found.\n");

					do
					{
            str[i] = xfr_byte( f);
					} while( str[i++] != '\0');

					strptr = str + strlen( str) - 3;
					strcpy( strptr, "vio");

					strptr = str + strlen( str);
					while( (strptr != str) && (strptr[-1] != '\\') && (strptr[-1] != '/'))
						strptr--;
					strcpy( texture->filename, strptr);

					dprintf("Filename: %s\n", texture->filename);
				}
				break;
			}
			case 'TFLG':
			{
        int flags = xfrle_word( f);

				if( texture != NULL)
				{
				 	dprintf("[#] Texture flags subchunk found.\n");
	        switch (flags & 0x0007)
					{
						case 0x0001: surf->angle = XANGLE; break;
						case 0x0002: surf->angle = YANGLE; break;
						case 0x0004: surf->angle = ZANGLE; break;
					};
				}
        break;
			}
			case 'TSIZ':
			{
				if( texture != NULL)
				{
					dprintf("[#] Texture size subchunk found.\n");

          surf->xsize = xfrle_float( f);
          surf->ysize = xfrle_float( f);
          surf->zsize = xfrle_float( f);
					dprintf("Texture size: %f,%f,%f\n", surf->xsize, surf->ysize, surf->zsize);
				}
				break;
			}
			case 'TCTR':
			{
				if( texture != NULL)
				{
					dprintf("[#] Texture center subchunk found.\n");

          surf->xcenter = xfrle_float( f);
          surf->ycenter = xfrle_float( f);
          surf->zcenter = xfrle_float( f);
					dprintf("Texture center: %f,%f,%f\n", surf->xcenter, surf->ycenter, surf->zcenter);
				}
				break;
			}
			default:
			{
				dprintf("[!] Unknown subchunk '%c%c%c%c', length %d bytes\n",
					(subchunkid & 0xff000000) >> 24,
					(subchunkid & 0x00ff0000) >> 16,
					(subchunkid & 0x0000ff00) >> 8,
					(subchunkid & 0x000000ff),
					subchunklen);
				break;
			}
		}
    xfseek( f, subchunkend, SEEK_SET);
	}
	dprintf("[#] Done\n");
}

/*-----------------------------------------------------------------------------
|
| * Main LWO loader *
|
-----------------------------------------------------------------------------*/

V3O *lwo_load( char *filename)
{
  V3O *obj;
  XFILE *f;
	unsigned int chunkid;
	int filelen, chunkstart, chunkend, chunklen;
	int i;

	for( i=0; i<256; i++)
	{
		surfs[i].mapping = NONE;
		surfs[i].angle = YANGLE;
		surfs[i].xsize = surfs[i].ysize = surfs[i].zsize = 0;
		surfs[i].xcenter = surfs[i].ycenter = surfs[i].zcenter = 0;
		surfs[i].xtile = surfs[i].ytile = 1;
	}

  if ( (f = xfopen( filename, "rb")) == NULL)
		return( NULL);

	/* Check for 'FORM',len */
  chunkid = xfrle_dword( f);
  chunklen = xfrle_dword( f);
	if ( chunkid != 'FORM')
		return( NULL);

	filelen = chunklen + 8;

	/* Check for 'LWOB'*/
  chunkid = xfrle_dword( f);
	if ( chunkid != 'LWOB')
		return( NULL);

  //obj = (V3O *)xmalloc( sizeof( V3O));
	obj = v3o_create ();
  memset( obj, 0, sizeof( V3O));

	do
	{
    chunkid = xfrle_dword( f);
    chunklen = xfrle_dword( f);
    chunkstart = xftell( f);
		chunkend = chunkstart + chunklen;

		switch ( chunkid)
		{
			case( 'PNTS'):
				pnts( obj, f, chunkstart, chunklen);
				break;
			case( 'SRFS'):
			{
				srfs( obj, f, chunkstart, chunklen);
				break;
			}
			case( 'POLS'):
			{
				pols( obj, f, chunkstart, chunklen);
				break;
			}
			case( 'SURF'):
			{
				surf( obj, f, chunkstart, chunklen);
				break;
			}
			default:
			{
		 		dprintf("[?] Ignoring chunk '%c%c%c%c', length %d bytes\n",
			 		(chunkid & 0xff000000) >> 24,
			 		(chunkid & 0x00ff0000) >> 16,
			 		(chunkid & 0x0000ff00) >> 8,
			 		(chunkid & 0x000000ff),
			 		chunklen);
				break;
			}
		}

    xfseek( f, chunkend, SEEK_SET);
  } while ( !(xftell( f) == filelen));

  xfclose (f);

	maptextures( obj);

	return( obj);
}


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

void xyztohp(float x,float y,float z,float *h,float *p)
{
  if (x == 0.0 && z == 0.0)
	{
    *h = 0.0;
    if (y != 0.0)
      *p = (float) ((y < 0.0) ? -M_PI/2 : M_PI/2);
    else
      *p = 0.0;
  }
  else
	{
    if (z == 0.0)
      *h = (float) ((x < 0.0) ? M_PI/2 : -M_PI/2);
    else if (z < 0.0)
      *h = (float) (-atan(x / z) + M_PI);
    else
      *h = (float) -atan(x / z);
    x = (float)sqrt(x * x + z * z);
    if (x == 0.0)
      *p = (float) ((y < 0.0) ? -M_PI/2 : M_PI/2);
    else
      *p = (float) atan(y / x);
  }
}

static void mapcoord( SURF *surf, float x, float y, float z, float *u, float *v)
{
	float s,t;
	float lon,lat;
//	VECTOR w;

	x -= surf->xcenter;
	y -= surf->ycenter;
	z -= surf->zcenter;
	if( surf->mapping == PLANE)
	{
	  s = (surf->angle == XANGLE) ? z / surf->zsize + (float).5 : x / surf->xsize + (float).5;
	  t = (surf->angle == YANGLE) ? -z / surf->zsize + (float).5 : -y / surf->ysize + (float).5;

//		w.x = x, w.y = y, w.z = z;
//		vector_normalize (&w);
//		s = (surf->angle == XANGLE) ? w.z : w.x;
//		t = (surf->angle == YANGLE) ? -w.z : -w.y;

		*u = s;
		*v = t;
//	  *u = ffrac(s);
//	  *v = ffrac(t);
	}
	else if (surf->mapping == CYLINDER)
	{
	  if (surf->angle == XANGLE)
		{
	      xyztoh(z,x,-y,&lon);
	      t = -x / surf->xsize + (float).5;
	  }
	  else if (surf->angle == YANGLE)
		{
	      xyztoh(-x,y,z,&lon);
	      t = -y / surf->ysize + (float).5;
	  }
	  else
		{
	      xyztoh(-x,z,-y,&lon);
	      t = -z / surf->zsize + (float).5;
	  }
	  lon = (float)(1.0 - lon / M_PI*2);
	  if (surf->xtile != 1.0)
	      lon = ffrac(lon) * surf->xtile;
		*u = lon;
		*v = t;
//	  *u = ffrac(lon);
//	  *v = ffrac(t);
	}
	else if (surf->mapping == SPHERE)
	{
	  if (surf->angle == XANGLE)
	      xyztohp(z,x,-y,&lon,&lat);
	  else if (surf->angle == YANGLE)
	      xyztohp(-x,y,z,&lon,&lat);
	  else
	      xyztohp(-x,z,-y,&lon,&lat);
	  lon = (float)(1.0 - lon / M_PI*2);
	  lat = (float)(0.5 - lat / M_PI);
	  if (surf->xtile != 1.0)
	      lon = ffrac(lon) * surf->xtile;
	  if (surf->ytile != 1.0)
	      lat = ffrac(lat) * surf->ytile;
		*u = lon;
		*v = lat;
//	  *u = ffrac(lon);
//	  *v = ffrac(lat);
	}
	else if (surf->mapping == CUBIC)
	{
		// Just mapping some coords...
		*u = (M_PI+atan2 (x,z))/(2*M_PI);
		*v = (M_PI+atan2 (y,z))/(2*M_PI);
	}
}

static void maptextures( V3O *obj)
{
	//V3OTEXTURE *texture;
	SURF *surf;
	int i;


	for( i=0; i<obj->numsurface; i++)
	{
		surf = &surfs[ obj->surface[i].material];
		if( surf->mapping != NONE)
		{
			//texture = &obj->texture[obj->material[ obj->surface[i].material].texture];

			mapcoord( surf, obj->orgvertex[ obj->surface[i].v1].x, obj->orgvertex[ obj->surface[i].v1].y, obj->orgvertex[ obj->surface[i].v1].z, &obj->surface[i].tx1, &obj->surface[i].ty1);
			obj->surface[i].tx1 *= 256;
			obj->surface[i].ty1 *= 256;
			mapcoord( surf, obj->orgvertex[ obj->surface[i].v2].x, obj->orgvertex[ obj->surface[i].v2].y, obj->orgvertex[ obj->surface[i].v2].z, &obj->surface[i].tx2, &obj->surface[i].ty2);
			obj->surface[i].tx2 *= 256;
			obj->surface[i].ty2 *= 256;
			mapcoord( surf, obj->orgvertex[ obj->surface[i].v3].x, obj->orgvertex[ obj->surface[i].v3].y, obj->orgvertex[ obj->surface[i].v3].z, &obj->surface[i].tx3, &obj->surface[i].ty3);
			obj->surface[i].tx3 *= 256;
			obj->surface[i].ty3 *= 256;

		}
	}
}

