/*
   ======================================================================
   pntspols.c

   Point and polygon functions for an LWO2 reader.

   Ernie Wright  17 Sep 00
   ====================================================================== */

#include "../picointernal.h"
#include "lwo2.h"


/*
   ======================================================================
   lwFreePoints()

   Free the memory used by an lwPointList.
   ====================================================================== */

void lwFreePoints( lwPointList *point ){
	int i;

	if ( point ) {
		if ( point->pt ) {
			for ( i = 0; i < point->count; i++ ) {
				if ( point->pt[ i ].pol ) {
					_pico_free( point->pt[ i ].pol );
				}
				if ( point->pt[ i ].vm ) {
					_pico_free( point->pt[ i ].vm );
				}
			}
			_pico_free( point->pt );
		}
		memset( point, 0, sizeof( lwPointList ) );
	}
}


/*
   ======================================================================
   lwFreePolygons()

   Free the memory used by an lwPolygonList.
   ====================================================================== */

void lwFreePolygons( lwPolygonList *plist ){
	int i, j;

	if ( plist ) {
		if ( plist->pol ) {
			for ( i = 0; i < plist->count; i++ ) {
				if ( plist->pol[ i ].v ) {
					for ( j = 0; j < plist->pol[ i ].nverts; j++ )
						if ( plist->pol[ i ].v[ j ].vm ) {
							_pico_free( plist->pol[ i ].v[ j ].vm );
						}
				}
			}
			if ( plist->pol[ 0 ].v ) {
				_pico_free( plist->pol[ 0 ].v );
			}
			_pico_free( plist->pol );
		}
		memset( plist, 0, sizeof( lwPolygonList ) );
	}
}


/*
   ======================================================================
   lwGetPoints()

   Read point records from a PNTS chunk in an LWO2 file.  The points are
   added to the array in the lwPointList.
   ====================================================================== */

int lwGetPoints( picoMemStream_t *fp, int cksize, lwPointList *point ){
	float *f;
	int np, i, j;

	if ( cksize == 1 ) {
		return 1;
	}

	/* extend the point array to hold the new points */

	np = cksize / 12;
	point->offset = point->count;
	point->count += np;
	if ( !_pico_realloc( (void *) &point->pt, ( point->count - np ) * sizeof( lwPoint ), point->count * sizeof( lwPoint ) ) ) {
		return 0;
	}
	memset( &point->pt[ point->offset ], 0, np * sizeof( lwPoint ) );

	/* read the whole chunk */

	f = ( float * ) getbytes( fp, cksize );
	if ( !f ) {
		return 0;
	}
	revbytes( f, 4, np * 3 );

	/* assign position values */

	for ( i = 0, j = 0; i < np; i++, j += 3 ) {
		point->pt[ i ].pos[ 0 ] = f[ j ];
		point->pt[ i ].pos[ 1 ] = f[ j + 1 ];
		point->pt[ i ].pos[ 2 ] = f[ j + 2 ];
	}

	_pico_free( f );
	return 1;
}


/*
   ======================================================================
   lwGetBoundingBox()

   Calculate the bounding box for a point list, but only if the bounding
   box hasn't already been initialized.
   ====================================================================== */

void lwGetBoundingBox( lwPointList *point, float bbox[] ){
	int i, j;

	if ( point->count == 0 ) {
		return;
	}

	for ( i = 0; i < 6; i++ )
		if ( bbox[ i ] != 0.0f ) {
			return;
		}

	bbox[ 0 ] = bbox[ 1 ] = bbox[ 2 ] = 1e20f;
	bbox[ 3 ] = bbox[ 4 ] = bbox[ 5 ] = -1e20f;
	for ( i = 0; i < point->count; i++ ) {
		for ( j = 0; j < 3; j++ ) {
			if ( bbox[ j ] > point->pt[ i ].pos[ j ] ) {
				bbox[ j ] = point->pt[ i ].pos[ j ];
			}
			if ( bbox[ j + 3 ] < point->pt[ i ].pos[ j ] ) {
				bbox[ j + 3 ] = point->pt[ i ].pos[ j ];
			}
		}
	}
}


/*
   ======================================================================
   lwAllocPolygons()

   Allocate or extend the polygon arrays to hold new records.
   ====================================================================== */

int lwAllocPolygons( lwPolygonList *plist, int npols, int nverts ){
	int i;

	plist->offset = plist->count;
	plist->count += npols;
	if ( !_pico_realloc( (void *) &plist->pol, ( plist->count - npols ) * sizeof( lwPolygon ), plist->count * sizeof( lwPolygon ) ) ) {
		return 0;
	}
	memset( plist->pol + plist->offset, 0, npols * sizeof( lwPolygon ) );

	plist->voffset = plist->vcount;
	plist->vcount += nverts;
	if ( !_pico_realloc( (void *) &plist->pol[ 0 ].v, ( plist->vcount - nverts ) * sizeof( lwPolVert ), plist->vcount * sizeof( lwPolVert ) ) ) {
		return 0;
	}
	memset( plist->pol[ 0 ].v + plist->voffset, 0, nverts * sizeof( lwPolVert ) );

	/* fix up the old vertex pointers */

	for ( i = 1; i < plist->offset; i++ )
		plist->pol[ i ].v = plist->pol[ i - 1 ].v + plist->pol[ i - 1 ].nverts;

	return 1;
}


/*
   ======================================================================
   lwGetPolygons()

   Read polygon records from a POLS chunk in an LWO2 file.  The polygons
   are added to the array in the lwPolygonList.
   ====================================================================== */

int lwGetPolygons( picoMemStream_t *fp, int cksize, lwPolygonList *plist, int ptoffset ){
	lwPolygon *pp;
	lwPolVert *pv;
	unsigned char *buf, *bp;
	int i, j, flags, nv, nverts, npols;
	unsigned int type;


	if ( cksize == 0 ) {
		return 1;
	}

	/* read the whole chunk */

	set_flen( 0 );
	type = getU4( fp );
	buf = getbytes( fp, cksize - 4 );
	if ( cksize != get_flen() ) {
		goto Fail;
	}

	/* count the polygons and vertices */

	nverts = 0;
	npols = 0;
	bp = buf;

	while ( bp < buf + cksize - 4 ) {
		nv = sgetU2( &bp );
		nv &= 0x03FF;
		nverts += nv;
		npols++;
		for ( i = 0; i < nv; i++ )
			j = sgetVX( &bp );
	}

	if ( !lwAllocPolygons( plist, npols, nverts ) ) {
		goto Fail;
	}

	/* fill in the new polygons */

	bp = buf;
	pp = plist->pol + plist->offset;
	pv = plist->pol[ 0 ].v + plist->voffset;

	for ( i = 0; i < npols; i++ ) {
		nv = sgetU2( &bp );
		flags = nv & 0xFC00;
		nv &= 0x03FF;

		pp->nverts = nv;
		pp->flags = flags;
		pp->type = type;
		if ( !pp->v ) {
			pp->v = pv;
		}
		for ( j = 0; j < nv; j++ )
			pp->v[ j ].index = sgetVX( &bp ) + ptoffset;

		pp++;
		pv += nv;
	}

	_pico_free( buf );
	return 1;

Fail:
	if ( buf ) {
		_pico_free( buf );
	}
	lwFreePolygons( plist );
	return 0;
}


/*
   ======================================================================
   lwGetPolyNormals()

   Calculate the polygon normals.  By convention, LW's polygon normals
   are found as the cross product of the first and last edges.  It's
   undefined for one- and two-point polygons.
   ====================================================================== */

void lwGetPolyNormals( lwPointList *point, lwPolygonList *polygon ){
	int i, j;
	float p1[ 3 ], p2[ 3 ], pn[ 3 ], v1[ 3 ], v2[ 3 ];

	for ( i = 0; i < polygon->count; i++ ) {
		if ( polygon->pol[ i ].nverts < 3 ) {
			continue;
		}
		for ( j = 0; j < 3; j++ ) {
			p1[ j ] = point->pt[ polygon->pol[ i ].v[ 0 ].index ].pos[ j ];
			p2[ j ] = point->pt[ polygon->pol[ i ].v[ 1 ].index ].pos[ j ];
			pn[ j ] = point->pt[ polygon->pol[ i ].v[
									 polygon->pol[ i ].nverts - 1 ].index ].pos[ j ];
		}

		for ( j = 0; j < 3; j++ ) {
			v1[ j ] = p2[ j ] - p1[ j ];
			v2[ j ] = pn[ j ] - p1[ j ];
		}

		cross( v1, v2, polygon->pol[ i ].norm );
		normalize( polygon->pol[ i ].norm );
	}
}


/*
   ======================================================================
   lwGetPointPolygons()

   For each point, fill in the indexes of the polygons that share the
   point.  Returns 0 if any of the memory allocations fail, otherwise
   returns 1.
   ====================================================================== */

int lwGetPointPolygons( lwPointList *point, lwPolygonList *polygon ){
	int i, j, k;

	/* count the number of polygons per point */

	for ( i = 0; i < polygon->count; i++ )
		for ( j = 0; j < polygon->pol[ i ].nverts; j++ )
			++point->pt[ polygon->pol[ i ].v[ j ].index ].npols;

	/* alloc per-point polygon arrays */

	for ( i = 0; i < point->count; i++ ) {
		if ( point->pt[ i ].npols == 0 ) {
			continue;
		}
		point->pt[ i ].pol = _pico_calloc( point->pt[ i ].npols, sizeof( int ) );
		if ( !point->pt[ i ].pol ) {
			return 0;
		}
		point->pt[ i ].npols = 0;
	}

	/* fill in polygon array for each point */

	for ( i = 0; i < polygon->count; i++ ) {
		for ( j = 0; j < polygon->pol[ i ].nverts; j++ ) {
			k = polygon->pol[ i ].v[ j ].index;
			point->pt[ k ].pol[ point->pt[ k ].npols ] = i;
			++point->pt[ k ].npols;
		}
	}

	return 1;
}


/*
   ======================================================================
   lwResolvePolySurfaces()

   Convert tag indexes into actual lwSurface pointers.  If any polygons
   point to tags for which no corresponding surface can be found, a
   default surface is created.
   ====================================================================== */

int lwResolvePolySurfaces( lwPolygonList *polygon, lwTagList *tlist,
						   lwSurface **surf, int *nsurfs ){
	lwSurface **s, *st;
	int i, index;

	if ( tlist->count == 0 ) {
		return 1;
	}

	s = _pico_calloc( tlist->count, sizeof( lwSurface * ) );
	if ( !s ) {
		return 0;
	}

	for ( i = 0; i < tlist->count; i++ ) {
		st = *surf;
		while ( st ) {
			if ( !strcmp( st->name, tlist->tag[ i ] ) ) {
				s[ i ] = st;
				break;
			}
			st = st->next;
		}
	}

	for ( i = 0; i < polygon->count; i++ ) {
		index = ( size_t ) polygon->pol[ i ].surf;
		if ( index < 0 || index > tlist->count ) {
			return 0;
		}
		if ( !s[ index ] ) {
			s[ index ] = lwDefaultSurface();
			if ( !s[ index ] ) {
				return 0;
			}
			s[ index ]->name = _pico_alloc( strlen( tlist->tag[ index ] ) + 1 );
			if ( !s[ index ]->name ) {
				return 0;
			}
			strcpy( s[ index ]->name, tlist->tag[ index ] );
			lwListAdd( (void *) surf, s[ index ] );
			*nsurfs = *nsurfs + 1;
		}
		polygon->pol[ i ].surf = s[ index ];
	}

	_pico_free( s );
	return 1;
}


/*
   ======================================================================
   lwGetVertNormals()

   Calculate the vertex normals.  For each polygon vertex, sum the
   normals of the polygons that share the point.  If the normals of the
   current and adjacent polygons form an angle greater than the max
   smoothing angle for the current polygon's surface, the normal of the
   adjacent polygon is excluded from the sum.  It's also excluded if the
   polygons aren't in the same smoothing group.

   Assumes that lwGetPointPolygons(), lwGetPolyNormals() and
   lwResolvePolySurfaces() have already been called.
   ====================================================================== */

void lwGetVertNormals( lwPointList *point, lwPolygonList *polygon ){
	int j, k, n, g, h, p;
	float a;

	for ( j = 0; j < polygon->count; j++ ) {
		for ( n = 0; n < polygon->pol[ j ].nverts; n++ ) {
			for ( k = 0; k < 3; k++ )
				polygon->pol[ j ].v[ n ].norm[ k ] = polygon->pol[ j ].norm[ k ];

			if ( polygon->pol[ j ].surf->smooth <= 0 ) {
				continue;
			}

			p = polygon->pol[ j ].v[ n ].index;

			for ( g = 0; g < point->pt[ p ].npols; g++ ) {
				h = point->pt[ p ].pol[ g ];
				if ( h == j ) {
					continue;
				}

				if ( polygon->pol[ j ].smoothgrp != polygon->pol[ h ].smoothgrp ) {
					continue;
				}
				a = vecangle( polygon->pol[ j ].norm, polygon->pol[ h ].norm );
				if ( a > polygon->pol[ j ].surf->smooth ) {
					continue;
				}

				for ( k = 0; k < 3; k++ )
					polygon->pol[ j ].v[ n ].norm[ k ] += polygon->pol[ h ].norm[ k ];
			}

			normalize( polygon->pol[ j ].v[ n ].norm );
		}
	}
}


/*
   ======================================================================
   lwFreeTags()

   Free memory used by an lwTagList.
   ====================================================================== */

void lwFreeTags( lwTagList *tlist ){
	int i;

	if ( tlist ) {
		if ( tlist->tag ) {
			for ( i = 0; i < tlist->count; i++ )
				if ( tlist->tag[ i ] ) {
					_pico_free( tlist->tag[ i ] );
				}
			_pico_free( tlist->tag );
		}
		memset( tlist, 0, sizeof( lwTagList ) );
	}
}


/*
   ======================================================================
   lwGetTags()

   Read tag strings from a TAGS chunk in an LWO2 file.  The tags are
   added to the lwTagList array.
   ====================================================================== */

int lwGetTags( picoMemStream_t *fp, int cksize, lwTagList *tlist ){
	char *buf, *bp;
	int i, len, ntags;

	if ( cksize == 0 ) {
		return 1;
	}

	/* read the whole chunk */

	set_flen( 0 );
	buf = getbytes( fp, cksize );
	if ( !buf ) {
		return 0;
	}

	/* count the strings */

	ntags = 0;
	bp = buf;
	while ( bp < buf + cksize ) {
		len = strlen( bp ) + 1;
		len += len & 1;
		bp += len;
		++ntags;
	}

	/* expand the string array to hold the new tags */

	tlist->offset = tlist->count;
	tlist->count += ntags;
	if ( !_pico_realloc( (void *) &tlist->tag, ( tlist->count - ntags ) * sizeof( char * ), tlist->count * sizeof( char * ) ) ) {
		goto Fail;
	}
	memset( &tlist->tag[ tlist->offset ], 0, ntags * sizeof( char * ) );

	/* copy the new tags to the tag array */

	bp = buf;
	for ( i = 0; i < ntags; i++ )
		tlist->tag[ i + tlist->offset ] = sgetS0( (unsigned char **) &bp );

	_pico_free( buf );
	return 1;

Fail:
	if ( buf ) {
		_pico_free( buf );
	}
	return 0;
}


/*
   ======================================================================
   lwGetPolygonTags()

   Read polygon tags from a PTAG chunk in an LWO2 file.
   ====================================================================== */

int lwGetPolygonTags( picoMemStream_t *fp, int cksize, lwTagList *tlist,
					  lwPolygonList *plist ){
	unsigned int type;
	int rlen = 0, i, j;

	set_flen( 0 );
	type = getU4( fp );
	rlen = get_flen();
	if ( rlen < 0 ) {
		return 0;
	}

	if ( type != ID_SURF && type != ID_PART && type != ID_SMGP ) {
		_pico_memstream_seek( fp, cksize - 4, PICO_SEEK_CUR );
		return 1;
	}

	while ( rlen < cksize ) {
		i = getVX( fp ) + plist->offset;
		j = getVX( fp ) + tlist->offset;
		rlen = get_flen();
		if ( rlen < 0 || rlen > cksize ) {
			return 0;
		}

		switch ( type ) {
		case ID_SURF:  plist->pol[ i ].surf = ( lwSurface * ) (size_t) j;  break;
		case ID_PART:  plist->pol[ i ].part = j;  break;
		case ID_SMGP:  plist->pol[ i ].smoothgrp = j;  break;
		}
	}

	return 1;
}
