#include <math.h>
#include "getglut.h"
#include "vertex.h"
#include "smalloc.h"

#define AMBIENCE_FACTOR 0.5
#define NO_TEXTURE (texture *) 1
/* Can't use null for NO_TEXTURE, as that's already in use for 'previous texture' */

long polycount;

void vertex_init(vertex* v) {
	v->x = 0.; v->y = 0.; v->z = 0.;
	v->u = 0.; v->v = 0.;
	v->r = 1.; v->g = 1.; v->b = 1.; v->a = 1.;
}

void triangle_init(triangle* t) {
	polycount++;
	vertex_init(&(t->p));
	vertex_init(&(t->q));
	vertex_init(&(t->r));
	t->tex = 0;
	t->n[0] = 0.; t->n[1] = 0.; t->n[2] = 0.;
	t->next = 0;
}
/* To neatly destruct a triangle, it would be necessary to follow all the 'next' pointers */

void triangle_setv(triangle* t,
                   GLfloat x1, GLfloat y1, GLfloat z1,
                   GLfloat x2, GLfloat y2, GLfloat z2,
                   GLfloat x3, GLfloat y3, GLfloat z3) {
	t->p.x = x1; t->p.y = y1; t->p.z = z1;
	t->p.x = x2; t->p.y = y2; t->p.z = z2;
	t->p.x = x3; t->p.y = y3; t->p.z = z3;
}

triangle* triangle_makeroomx(GLfloat x1, GLfloat z1, GLfloat x2, GLfloat z2,
                             GLfloat x3, GLfloat z3, GLfloat x4, GLfloat z4,
			     GLfloat y1, GLfloat y2, texture *tex, GLfloat tx) {
	/* Chain of triangles (1, 2, 3; 3, 4, 1) */
	/* Ceiling is done in reverse order to get correct normal */
	/* t, T -- floor; s, S -- ceiling */
	/* Doesn't create walls! */
	/* Actually uses tx for both texturing directions */
	triangle* t = smalloc(sizeof(triangle)); triangle_init(t);
	/* Use the preprocessor to avoid extra pointers */
#define T t->next
#define s T->next
#define S s->next
	T = smalloc(sizeof(triangle)); triangle_init(T);
	s = smalloc(sizeof(triangle)); triangle_init(s);
	S = smalloc(sizeof(triangle)); triangle_init(S);
	t->p.x = x1; t->p.z = z1; t->q.x = x2; t->q.z = z2; t->r.x = x3; t->r.z = z3;
	s->r.x = x1; s->r.z = z1; s->q.x = x2; s->q.z = z2; s->p.x = x3; s->p.z = z3;
	t->p.y = y1; s->r.y = y2; t->q.y = y1; s->q.y = y2; t->r.y = y1; s->p.y = y2;
	T->p.x = x3; T->p.z = z3; T->q.x = x4; T->q.z = z4; T->r.x = x1; T->r.z = z1;
	S->r.x = x3; S->r.z = z3; S->q.x = x4; S->q.z = z4; S->p.x = x1; S->p.z = z1;
	T->p.y = y1; S->r.y = y2; T->q.y = y1; S->q.y = y2; T->r.y = y1; S->p.y = y2;
	t->n[1] = 1.; T->n[1] = 1.; s->n[1] = -1.; S->n[1] = -1.;
	t->tex  = tex; T->tex  = tex; s->tex = tex; S->tex = tex;
	t->p.u = 0.; t->p.v = 0.; t->q.u = tx; t->q.v = 0.; t->r.u = tx; t->r.v = tx;
	s->p.u = 0.; s->p.v = 0.; s->q.u = tx; s->q.v = 0.; s->r.u = tx; s->r.v = tx;
	T->p.u = tx; T->p.v = tx; T->q.u = 0.; T->q.v = tx; T->r.u = 0.; T->r.v = 0.;
	S->p.u = tx; S->p.v = tx; S->q.u = 0.; S->q.v = tx; S->r.u = 0.; S->r.v = 0.;
	S->next = 0;
#undef S
#undef s
#undef T
	return t;
}
triangle* triangle_makeroom(GLfloat x1, GLfloat z1, GLfloat x2, GLfloat z2,
                            GLfloat x3, GLfloat z3, GLfloat x4, GLfloat z4,
			    texture *tex, GLfloat tx) {
	return triangle_makeroomx(x1, z1, x2, z2, x3, z3, x4, z4, -1., 1., tex, tx);
}

triangle* triangle_makewallx(GLfloat x1, GLfloat z1, GLfloat x2, GLfloat z2,
		             GLfloat y1, GLfloat y2, texture *tex, GLfloat ty) {
	/* Side view; triangles ABC, DEF
	 * A(x1,-2,z1) B(x2,-2,z2)E
	 * C(x1, 2,z1)D (x2, 2,z2)F
	 */
	/* Build walls ANTICLOCKWISE to get correct normals to see them from inside! */
	GLfloat tx, dx, dz, l;
	triangle* t;
	/* Calculate length of wall.. */
	dx = x2 - x1; dz = z2 - z1; l = sqrt(dx * dx + dz * dz);
	/* ...if length of wall is more than double the height, autosplit into two walls */
	/* (better lighting and less texture stretching; may recurse for really long walls)... */
	if(l > fabs((y2 - y1) * 2.)) {
		/* Re-use dx/dz for midpoint */
		dx = (x1 + x2) / 2.; dz = (z1 + z2) / 2.;
		t = triangle_makewallx(x1, z1, dx, dz, y1, y2, tex, ty);
		triangle_tail(t)->next =
		    triangle_makewallx(dx, dz, x2, z2, y1, y2, tex, ty);
		return t;
	}
	/* ...and calculate tx */
	/*tx = ty * sqrt(dx * dx + dz * dz) / (y2 - y1);*/
	/* REMOVED Doesn't actually work that well -- creates very noticable seams */
	tx = ty; /* Just use ty for both */
	/* Allocate for new triangles */
	t = smalloc(sizeof(triangle)); triangle_init(t);
#define T t->next
	T = smalloc(sizeof(triangle)); triangle_init(T);
	t->p.x = x1; t->p.y = y1; t->p.z = z1;
	t->q.x = x2; t->q.y = y1; t->q.z = z2;
	t->r.x = x1; t->r.y = y2; t->r.z = z1;
	T->p.x = x1; T->p.y = y2; T->p.z = z1;
	T->q.x = x2; T->q.y = y1; T->q.z = z2;
	T->r.x = x2; T->r.y = y2; T->r.z = z2;
	t->tex = tex; T->tex = tex;
	t->p.u = 0.; t->p.v = 0.; t->q.u = tx; t->q.v = 0.; t->r.u = 0.; t->r.v = ty;
	T->p.u = 0.; T->p.v = ty; T->q.u = tx; T->q.v = 0.; T->r.u = tx; T->r.v = ty;
	T->next = 0;
	triangle_autonormal(t);
	triangle_autonormal(T);
#undef T
	return t;
}
triangle* triangle_makewall(GLfloat x1, GLfloat z1, GLfloat x2, GLfloat z2,
		            texture *tex, GLfloat tx) {
	return triangle_makewallx(x1, z1, x2, z2, -1., 1., tex, tx);
}

triangle* triangle_tail(triangle* t) {
	triangle* u = t;
	while(u->next != 0) {
		u = u->next;
	}
	return u;
}

void triangle_autonormal(triangle* t) {
	/* Cross product-tastic. Normal is of unit length. */
	GLfloat v1x, v1y, v1z, v2x, v2y, v2z, l;
	v1x = t->p.x - t->q.x; v1y = t->p.y - t->q.y; v1z = t->p.z - t->q.z;
	v2x = t->r.x - t->q.x; v2y = t->r.y - t->q.y; v2z = t->r.z - t->q.z;
	t->n[0] = (v1y * v2z) - (v2y * v1z);
	t->n[1] = (v2x * v1z) - (v1x * v2z);
	t->n[2] = (v1x * v2y) - (v2x * v1y);
	l = sqrt( t->n[0] * t->n[0] + t->n[1] * t->n[1] + t->n[2] * t->n[2] );
	t->n[0] /= l; t->n[1] /= l; t->n[2] /= l;
}

void triangle_draw(triangle* t) {
	/* Let's NOT be recursive, for the sake of speed. */
	GLfloat m[4]; /* Annoying obligatory array */
	texture* lasttex = NO_TEXTURE;
	while(t != 0) {
		/* Texture switching is a major performance hit, so avoid it. */
		if(t->tex != 0 && t->tex != lasttex) {
			/* You *can* use one GL 'section' for all of the triangles, */
			/* but this won't work with texture switching.              */
			if(lasttex != NO_TEXTURE)
				{ glEnd(); }
			texture_use(t->tex);
			glBegin(GL_TRIANGLES);
			lasttex = t->tex;
		}
		glNormal3fv(t->n);
		m[0] = t->p.r; m[1] = t->p.g; m[2] = t->p.b; m[3] = t->p.a;
		glMaterialfv(GL_FRONT, GL_DIFFUSE, m);
		m[0] *= AMBIENCE_FACTOR; m[1] *= AMBIENCE_FACTOR; m[2] *= AMBIENCE_FACTOR;
		glMaterialfv(GL_FRONT, GL_AMBIENT, m);
		glTexCoord2f(t->p.u, t->p.v);
		glVertex3f(t->p.x, t->p.y, t->p.z);
		m[0] = t->q.r; m[1] = t->q.g; m[2] = t->q.b; m[3] = t->q.a;
		glMaterialfv(GL_FRONT, GL_DIFFUSE, m);
		m[0] *= AMBIENCE_FACTOR; m[1] *= AMBIENCE_FACTOR; m[2] *= AMBIENCE_FACTOR;
		glMaterialfv(GL_FRONT, GL_AMBIENT, m);
		glTexCoord2f(t->q.u, t->q.v);
		glVertex3f(t->q.x, t->q.y, t->q.z);
		m[0] = t->r.r; m[1] = t->r.g; m[2] = t->r.b; m[3] = t->r.a;
		glMaterialfv(GL_FRONT, GL_DIFFUSE, m);
		m[0] *= AMBIENCE_FACTOR; m[1] *= AMBIENCE_FACTOR; m[2] *= AMBIENCE_FACTOR;
		glMaterialfv(GL_FRONT, GL_AMBIENT, m);
		glTexCoord2f(t->r.u, t->r.v);
		glVertex3f(t->r.x, t->r.y, t->r.z);
		t = t->next;
	}
	glEnd();
}
