/*
 * Copyright 2000 Greg Velichansky
 * released under the GNU General Public License    
 * see the files README and COPYING for details.
 */
 

short int get_w(FILE *stream){
	short int r;
	fread( (void *) &r, 2, 1, stream);
	/* 2 because a word should be 16 bits; 2 bytes that is */
	return r;
}

unsigned int put_w(unsigned int i, FILE *stream){
	putc(i & 255, stream);
	putc(i >> 8, stream);
	return i;
}


#ifndef maxpoints
#define maxpoints 10000
#endif

#ifndef maxpolys
#define maxpolys 10000
#endif

typedef struct{
	int x, y, z, vNorm;
} point_s;

typedef struct{
	int vert[4], norm;	/* Indexes into point array, just like in 3d engine */
	int color;
	int tx_x[4];
	int tx_y[4];
	char tx_name[8];
	int type;
} poly_s;


vector p_norm[maxpolys];	/* Stores the normal as a real, only is conversion. Used for precision in calculating vertex normals */

int alwaysadd = 0; /*	Contains boolean value :
								if 1 then the program will always add new vertexes to store the normals
								if 0 then the program will attempt to determine by itself whether new points are necessary or not.
								Modified by command line parameter */

#ifndef ScrewArrays
point_s points[maxpoints];
poly_s polys[maxpolys];
#endif


int nextfreepoint = 0;

int freepoint(){
	if(nextfreepoint == maxpoints){
		puts("Out of space in the point array.");
		exit(1);
	}
	nextfreepoint++;
	return nextfreepoint-1;
}

int nextfreepoly = 0;

int freepoly(){
	if(nextfreepoint == maxpoints){
		puts("Out of space in the poly array.");
		exit(1);
	}
	nextfreepoly++;
	return nextfreepoly-1;
}


#define pointnum nextfreepoint
#define polynum nextfreepoly


vector ptov(point_s thepoint){
	vector result;

	result.x = (double) thepoint.x;
	result.y = (double) thepoint.y;
	result.z = (double) thepoint.z;

	return result;
}

vector vminusp(vector thevect, point_s thepoint){
	vector result;

	result.x = thevect.x - (double) thepoint.x;
	result.y = thevect.y - (double) thepoint.y;
	result.z = thevect.z - (double) thepoint.z;

	return result;
}

void calculatenormal(int polyind){
	vector v1, v2, vn;
	int p0, p1, p2, n;

	if(polys[polyind].norm >= pointnum) {
		polys[polyind].norm = 0;
	}

	if((polys[polyind].norm == 0) || (alwaysadd)){
		n = freepoint();
		polys[polyind].norm = n;
	} else n = polys[polyind].norm;

	p0 = polys[polyind].vert[0];
	p1 = polys[polyind].vert[1];
	p2 = polys[polyind].vert[2];

	v1 = ptov(points[p0]);
	v1 = vminusp(v1, points[p1]);

	v2 = ptov(points[p2]);
	v2 = vminusp(v2, points[p1]);

	vn = getnormal(v2, v1);

	p_norm[polyind] = vn;
	points[n].x = /*points[p1].x +*/ (int) floor(vn.x);
	points[n].y = /*points[p1].y +*/ (int) floor(vn.y);
	points[n].z = /*points[p1].z +*/ (int) floor(vn.z);
	points[n].vNorm = -1; /* Semi-hack: don't calculate normals for normals */
}

void calculatevnormal(int pointind){
	vector v;
	int n, i;

	v.x = v.y = v.z = 0.0;

	if(points[pointind].vNorm == -1) return; /* Semi-hack: don't create normals for normals. */

	if(points[pointind].vNorm >= pointnum) {
		points[pointind].vNorm = 0;
	}

	if((points[pointind].vNorm == 0) || alwaysadd){
		n = freepoint();
		points[pointind].vNorm = n;
	} else n = points[pointind].vNorm;

	points[n].vNorm = -1;

	for(i = 0; i < polynum; ++i){
		if((polys[i].vert[0] == pointind) || (polys[i].vert[1] == pointind) || (polys[i].vert[2] == pointind) || (polys[i].vert[3] == pointind)){
			v.x += p_norm[i].x;
			v.y += p_norm[i].y;
			v.z += p_norm[i].z;
		}
	}
	v = normalize(v);
	points[n].x = (int) floor(v.x);
	points[n].y = (int) floor(v.y);
	points[n].z = (int) floor(v.z);
}


void audit_points(){
	int point, n, i;
	int used;
	int cleared;

	cleared = 0;
	point = 0;

	if(pointnum == 0) return;

	do {
		used = 0;
		for(n = 0; n < polynum; ++n){
			if((polys[n].vert[0] == point) || (polys[n].vert[1] == point) || (polys[n].vert[2] == point) || (polys[n].vert[3] == point)) used = 1;
		}
		if((used == 0) || (points[point].vNorm == -1)){
			++cleared;
			--nextfreepoint;
			points[point] = points[nextfreepoint];
			for(n = 0; n < polynum; ++n){
				if(polys[n].vert[0] == nextfreepoint) polys[n].vert[0] = point;
				if(polys[n].vert[1] == nextfreepoint) polys[n].vert[1] = point;
				if(polys[n].vert[2] == nextfreepoint) polys[n].vert[2] = point;
				if(polys[n].vert[3] == nextfreepoint) polys[n].vert[3] = point;
			}
		} else {
			++point;
		}
	} while (point < pointnum);
	printf("%d points were removed.\n", cleared);
	alwaysadd = 1;	/* at this point indexes to normals may be invalid */
}

void optimise_normals(int tolerance){
	int n, m, o;
	int killme, killed = 0;

	for (o = 0; o < polynum - 1; ++o){
		for (m = o + 1; m < polynum; ++m){
			killme = polys[m].norm;
			if ((polys[o].norm != killme) && (abs (points[polys[o].norm].x - points[killme].x) <= tolerance) && (abs (points[polys[o].norm].y - points[killme].y) <= tolerance) && (abs (points[polys[o].norm].z - points[killme].z) <= tolerance)){
				polys[m].norm = polys[o].norm;
				--nextfreepoint;
				points[killme] = points[nextfreepoint];
				for (n = 0; n < polynum; ++n){
					if (polys[n].norm == nextfreepoint) polys[n].norm = killme;
				}
				for (n = 0; n < pointnum; ++n){
					if (points[n].vNorm == nextfreepoint) points[n].vNorm = killme;
				}
				++killed;
			}
		}
	}
	printf("\n%d normal points killed.\n", killed);
}

#pragma warn -aus

point_s readpoint(FILE *stream){
	point_s result;

	result.x = get_w(stream);
	result.y = get_w(stream);
	result.z = get_w(stream);
	result.vNorm = get_w(stream);

	return result;
}

void writepoint(point_s point, FILE *stream){
	put_w(point.x, stream);
	put_w(point.y, stream);
	put_w(point.z, stream);
	put_w(point.vNorm, stream);
}


poly_s readpoly_old(FILE *stream){
	poly_s result;
	int dummy;

	result.vert[0] = get_w(stream);
	result.vert[1] = get_w(stream);
	result.vert[2] = get_w(stream);
	result.vert[3] = get_w(stream);
	result.norm = get_w(stream);
	result.color = get_w(stream);

	return result;
}

poly_s readpoly(FILE *stream){
	poly_s result;
	int dummy;
	int m;

	result.vert[0] = get_w(stream);
	result.vert[1] = get_w(stream);
	result.vert[2] = get_w(stream);
	result.vert[3] = get_w(stream);
	result.norm = get_w(stream);
	result.color = get_w(stream); /* 14 bytes */

	for(m = 0; m < 4; ++m) result.tx_x[m] = get_w(stream); /* 22 */

	for(m = 0; m < 4; ++m) result.tx_y[m] = get_w(stream); /* 30 */

	for(m = 0; m < 8; ++m) result.tx_name[m] = getc(stream); /* 38 */

	result.type = getc(stream); 

	for(m = 0; m < 5; ++m) dummy = getc(stream); /* skip 5 dummy bytes; 44 */

	return result;
}


void writepoly_old(poly_s wpoly, FILE *stream){
	int n;

	for(n = 0; n < 4; n++) put_w(wpoly.vert[n], stream);
	put_w(wpoly.norm, stream);
	put_w(wpoly.color, stream);
}

void writepoly(poly_s wpoly, FILE *stream){
	int n;

	for (n = 0; n < 4; ++n) put_w (wpoly.vert[n], stream);
	put_w (wpoly.norm, stream);
	put_w (wpoly.color, stream);
	for (n = 0; n < 4; ++n) put_w (wpoly.tx_x[n], stream);
	for (n = 0; n < 4; ++n) put_w (wpoly.tx_y[n], stream);
	for (n = 0; n < 8; ++n) putc (wpoly.tx_name[n], stream);
	putc (wpoly.type, stream);
	for (n = 0; n < 5; ++n) putc (1, stream); /* write 5 dummy bytes */
	/* 1 byte for word alignment, one dword for internal engine use */
}


void readmodel_old(FILE *stream){
	unsigned int numpolys, numpoints, n, m;

	numpoints = get_w(stream);
	numpolys = get_w(stream);

	for(n = 1; n <= numpoints; n++){
		m = freepoint();
		points[m] = readpoint(stream);
	}

	for(n = 1; n <= numpolys; n++){
		m = freepoly();
		polys[m] = readpoly_old(stream);
	}
}

void readmodel(FILE *stream){
	unsigned int numpolys, numpoints, n, m;

	numpoints = get_w(stream);
	numpolys = get_w(stream);

	for(n = 1; n <= numpoints; n++){
		m = freepoint();
		points[m] = readpoint(stream);
	}

	for(n = 1; n <= numpolys; n++){
		m = freepoly();
		polys[m] = readpoly(stream);
	}
}


void writemodel_old(FILE *stream){
	int numpoints, numpolys, n;

	numpoints = put_w(nextfreepoint, stream);
	numpolys = put_w(nextfreepoly, stream);

	for(n = 0; n < numpoints; n++){
		writepoint(points[n], stream);
	}

	for(n = 0; n < numpolys; n++){
		writepoly_old(polys[n], stream);
	}
}


void writemodel(FILE *stream){
	int numpoints, numpolys, n;

	numpoints = put_w(nextfreepoint, stream);
	numpolys = put_w(nextfreepoly, stream);

	for(n = 0; n < numpoints; n++){
		writepoint(points[n], stream);
	}

	for(n = 0; n < numpolys; n++){
		writepoly(polys[n], stream);
	}
}


/* polygon render types */
#define p_flat 1		; /* plain flatshaded color */
#define p_gouraud 2		; /* gouraud shaded color */
#define p_phong 3		; /* phong shaded color */
#define p_metal 4		; /* pure metal shaded - maybe */
#define p_env 5			; /* pure reflective/env mapped - maybe */
#define p_texture 6		; /* unshaded texture mapping */
#define p_flat_tex 7		; /* flatshaded texture map */
#define p_gouraud_tex 8		; /* gouraud texture map */
#define p_phong_tex 9		; /* phonged texture */
#define p_metal_tex 10		; /* metal shaded texture - probably useless/too difficult */
#define p_env_tex 11		; /* env mapped texture - probably same deal as above */
