/* tvertelim.c --- makes assumptions about the order of the subpatches of a
 * patch, so expect serious trouble if you make changes in patch.c routines
 * QuadrRefine and TriangleRefine */

#include "tvertelim.h"
#include "Memory.h"
#include "error.h"
#include "globals.h"
#include "radiance.h"

#ifdef BETTER_MEMMAN
static STORAGE *tpatchStor = (STORAGE *)NULL;
#define NEWTPATCH()  	(TPATCH *)New(sizeof(TPATCH), &tpatchStor)
#define DISPOSETPATCH(ptr) Dispose((unsigned char *)(ptr), &tpatchStor)
#else /*BETTER_MEMMAN*/
#define NEWTPATCH()	(TPATCH *)Alloc(sizeof(TPATCH))
#define DISPOSETPATCH(ptr) Free((char *)ptr, sizeof(TPATCH))
#endif /*BETTER_MEMMAN*/

static COLOR ambient_radiance;

TPATCH *TPatchCreate(VERTEX *v1, VERTEX *v2, VERTEX *v3)
{
	TPATCH *tpatch;

	tpatch = NEWTPATCH();
	tpatch->tvertex[0] = v1;
	tpatch->tvertex[1] = v2;
	tpatch->tvertex[2] = v3;

	return tpatch;
}

void TPatchDestroy(TPATCH *tpatch)
{
	DISPOSETPATCH(tpatch);
}

void TPatchPrint(FILE *fp, TPATCH *tpatch)
{
	VertexPrint(fp, tpatch->tvertex[0]);
	VertexPrint(fp, tpatch->tvertex[1]);
	VertexPrint(fp, tpatch->tvertex[2]);
	fprintf(fp, "\n");
}

typedef struct TVERTINFO {
	VERTEX *t, *v[4];  	/* T-vertex en hoekpunten zodanig dat de Tvertex tussen v[0] en v[1] ligt (vertices in tegenwijzerzin zoals gewoonlijk) */
	PATCH *Qa, *Qb;		/* de twee elementen die de Tvertex delen: Qa is de subpatch aan vertex v[0], Qb aan v[1]. */
	int side;		/* index van de zijde van de niet onderverdeelde patch P waarop de T-vertex ligt */
} TVERTINFO;

/* computes parameter t of p in the line-equation p = p1 + t (p2-p1) */
/* this should be a macro in vector.h  +  use index to eliminate some operations */
static double ParmOnLine(char index, POINT *p, POINT *p1, POINT *p2)
{
	double d, maxd, d2;

	maxd = fabs(p2->x - p1->x);
	d = fabs(p->x - p1->x);

	d2 = fabs(p2->y - p1->y);
	if (d2 > maxd) {
		maxd = d2;
		d = fabs(p->y - p1->y);
	}
	
	d2 = fabs(p2->z - p1->z);
	if (d2 > maxd) {
		maxd = d2;
		d = fabs(p->z - p1->z);
	}
	return d/maxd;
}

/* bereken coordinaten in parameterruimte van de T-vertex t op zijde met index side
 * van de patch P */
static void TVertexComputeParameters(VERTEX *t, PATCH *P, int side, double *u, double *v)
{
	double s;

	s = ParmOnLine(P->index, t->point, P->vertex[side]->point, P->vertex[(side+1)%P->nrvertices]->point);
	switch (P->nrvertices) {
	case 3:
		switch (side) {
		case 0: *u = s   ; *v = 0.0 ; break;
		case 1: *u = 1.-s; *v = s   ; break;
		case 2: *u = 0.0 ; *v = 1.-s; break;
		default:
			Fatal(3, "TVertexComputeParameters", "Invalid side %d for triangles.\n", side);
			return;
		}
		break;
	case 4:
		switch (side) {
		case 0: *u = s   ; *v = 0.0 ; break;
		case 1: *u = 1.0 ; *v = s   ; break;
		case 2: *u = 1.-s; *v = 1.0 ; break; 
		case 3: *u = 0.0 ; *v = 1.-s; break;
		default:
			Fatal(3, "TVertexComputeParameters", "Invalid side %d for quadrilaterals.\n", side);
			return;
		}
		break;
	default:
		Fatal(3, "TVertexComputeParameters", "Invalid nr of vertices %d\n", P->nrvertices);
		return;
	}	
}

/* de radiantie van de T-vertex op de niet onderverdeelde patch P laten we dubbel 
 * meetellen. */
void TVertexFixRadiance(VERTEX *t, PATCH *P, int side)
{
	int nrpatches;
	PATCHLIST *pl;
	PATCH *patch;
	COLOR vertexradiance, vertextotalradiance;
	double u, v;

	COLORCLEAR(vertextotalradiance);
	pl = t->patches;
	nrpatches = 0;
	while ((patch = PatchListNext(&pl))) {
		if (patch->subpatches) continue;
		vertexradiance = patch->basis->GetValueAtVertex(patch->radiance, PatchWhichVertex(patch, t));

		if (render_ambient) {
			COLOR a;

			COLORPROD(patch->surface->material->Kd, ambient_radiance, a);
			COLORADD(vertexradiance, a, vertexradiance);
		}

		COLORADD(vertextotalradiance, vertexradiance, vertextotalradiance);
		nrpatches++;
	}

	TVertexComputeParameters(t, P, side, &u, &v);
	vertexradiance = P->basis->GetValueAtPoint(P->radiance, u, v);
	if (render_ambient) {
		COLOR a;

		COLORPROD(P->surface->material->Kd, ambient_radiance, a);
		COLORADD(vertexradiance, a, vertexradiance);
	}

	COLORADDSCALED(vertextotalradiance, 2., vertexradiance, vertextotalradiance);

	COLORSCALEINVERSE(nrpatches+2, vertextotalradiance, vertextotalradiance);	
	RadianceToRGB(vertextotalradiance, &t->color);
}

/* FindTVertex wordt opgeroepen met P en Q de twee patches die de zijde v1-v2 delen
 * met Q verder onderverdeeld en P niet, zodat er langs die zijde een T-vertex is.
 * We zoeken de subpatch Qb van Q aan vertex v2. De tweede vertex van Qb is de gezochte 
 * T-vertex (vertices worden altijd in tegenwijzerzin -gezien vanuit de richting van de 
 * normaal- opgegeven). Qa is de subpatch van Q aan vertex v1. De laatste vertex van Qa
 * hoort dezelfde T-vertex te zijn, anders is er iets verschrikkelijk mis.
 * Tenslotte wordt de radiantie van P in rekening gebracht in de T-vertex radiantie */
void FindTVertex(PATCH *P, int side, PATCH *Q, VERTEX *v1, VERTEX *v2, TVERTINFO *tvertinfo)
{
	PATCH *Qsub;
	PATCHLIST *Qsubpatches;

	Qsubpatches = Q->subpatches;
	while ((Qsub = PatchListNext(&Qsubpatches))) {
		if (Qsub->vertex[0] == v1)
			tvertinfo->Qa = Qsub;
		else if (Qsub->vertex[0] == v2) 
			tvertinfo->Qb = Qsub;
	}

	if (tvertinfo->Qb->vertex[1] != tvertinfo->Qa->vertex[tvertinfo->Qa->nrvertices-1]) {
		Fatal(3, "FindTVertex", "Data structures corrupt");
	}

	tvertinfo->t = tvertinfo->Qb->vertex[1];
	tvertinfo->side = side;

	TVertexFixRadiance(tvertinfo->t, P, side); 
}

/* de drie vertices vormen een driehoek die aan geen enkele zijde nog T-vertices bevat.
 * Creeer een TPATCH en voeg te aan de Tpatches lijst van patch P */
void TElimTVertices0(PATCH *P, VERTEX *v1, VERTEX *v2, VERTEX *v3)
{
	P->Tpatches = TPatchListAdd(P->Tpatches, TPatchCreate(v1, v2, v3));
}

/* de drie vertices vormen een driehoek waarvan de eerste zijde (v1-v2) nog nagekeken moet 
 * worden op T-vertices en, indien er zijn, verder gesplitst. 
 * Q is de patch waarmee de zijde v1-v2 gedeeld wordt. */
void TElimTVertices1(PATCH *P, int side,
		     VERTEX *v1, VERTEX *v2, VERTEX *v3,
		     PATCH *Q)
{
	TVERTINFO tvert;

/* Q is niet verder onderverdeeld, dus is er geen T-vertex */
	if (!(Q->subpatches)) {	
		TElimTVertices0(P, v1, v2, v3);
	} 

/* anders wordt de driehoek langs de eerste zijde (v1-v2) langs de T-vertex opgesplitst en
 * hebben we twee driehoeken die weer ten hoogste een T-vertex kunnen hebben */
	else {
		FindTVertex(P, side, Q, v1, v2, &tvert);

		TElimTVertices1(P, side, v1, tvert.t, v3, tvert.Qa);
		TElimTVertices1(P, side, tvert.t, v2, v3, tvert.Qb);
	}
}

/* de drie vertices vormen een driehoek waarvan de zijden (v1-v2) en 
 * (v2-v3) nog nagekeken moeten worden op T-vertices en, indien er zijn, verder gesplitst. 
 * Q1 is de patch waarmee de zijde v1-v2 gedeeld wordt,
 * Q2 is de patch waarmee de zijde v2-v3 gedeeld wordt. */
void TElimTVertices2(PATCH *P, int side1, int side2,
		     VERTEX *v1, VERTEX *v2, VERTEX *v3,
		     PATCH *Q1, PATCH *Q2)
{
	TVERTINFO tvert1, tvert2;

/* Q1 noch Q2 zijn verder onderverdeeld, dus zijn er geen verdere T-vertices */
	if (!(Q1->subpatches) && !(Q2->subpatches)) {	
		TElimTVertices0(P, v1, v2, v3);
	} 

/* Q1 is verder onderverdeeld, dus is er precies een T-vertex op de zijde v1-v2 */
	else if (Q1->subpatches && !(Q2->subpatches)) {
		TElimTVertices1(P, side1, v1, v2, v3, Q1);
	}

/* Q2 is verder onderverdeeld, dus is er precies een T-vertex op de zijde v2-v3 */
	else if (Q2->subpatches && !(Q1->subpatches)) {
		TElimTVertices1(P, side2, v2, v3, v1, Q2);
	}

/* zowel Q1 als Q2 zijn verder onderverdeeld, dus zijn er zowel aan de
 * zijde v1-v2 als aan de zijde v2-v3 T-vertices */
	else {
		FindTVertex(P, side1, Q1, v1, v2, &tvert1);
		FindTVertex(P, side2, Q2, v2, v3, &tvert2);

		TElimTVertices1(P, side1, v1, tvert1.t, tvert2.t, tvert1.Qa);
		TElimTVertices2(P, side1, side2, tvert1.t, v2, tvert2.t, tvert1.Qb, tvert2.Qa);
		TElimTVertices1(P, side2, tvert2.t, v3, v1      , tvert2.Qb);
	}
}

/* Zoekt (als die er is) de andere patch waarmee de zijde van patch P gevormd door
 * de vertices v1 en v2 gedeeld wordt. Opm: in een goede mesh kan er ten hoogste
 * 1 zo'n patch zijn. */
PATCH *OtherPatchSharingEdge(PATCH *P, VERTEX *v1, VERTEX *v2)
{
	PATCHLIST *v1patches, *v2patches;
	PATCH *Q, *v1patch, *v2patch;

	Q = (PATCH *)NULL;
	v1patches = v1->patches;
	while ((v1patch = PatchListNext(&v1patches)) && Q == (PATCH *)NULL) {
		if (v1patch == P) continue;

		v2patches = v2->patches;
		while ((v2patch = PatchListNext(&v2patches))) {
			if (v2patch == v1patch) {
				Q = v1patch;
				break;
			}
		}
	}

	return Q;
}

/* Analyseert de zijde van patch P gevormd door de vertices v1 en v2. Indien er een
 * T vertex op deze zijde voorkomt, worden in tvertinfo wijzers ingevuld naar de Tvertex,
 * naar de vertices van P in tegenwijzerzin zodanig de Tvertex tussen v[0] en v[1] ligt, 
 * en naar de elementen (geen subpatches van P) die de T-vertex delen. Indien er een
 * Tvertex voorkomt op de zijde, geeft deze funtie 1 terug, anders 0. */
/* side is 0 als het om de zijde P->vertex[0] - P->vertex[1] gaat etc... */
int AnalyseEdge(PATCH *P, int side, VERTEX *v1, VERTEX *v2, TVERTINFO *tvertinfo)
{
	PATCH *Q;

/* zoek de andere patch waarmee de zijde gevormd door de vertices v1 en v2 
 * gedeeld wordt. Indien er zo geen buur-patch is, is er zeker geen T-vertex op
 * deze zijde. */
	if ((Q = OtherPatchSharingEdge(P, v1, v2)) == (PATCH *)NULL)
		return 0;

/* er is geen T-vertex als de patch Q niet verder onderverdeeld is */
	if (!(Q->subpatches))
		return 0;

/* anders is er wel een T-vertex: vind ze! */
	FindTVertex(P, side, Q, v1, v2, tvertinfo);

/* vertices van P opsommen in een dusdanige volgorde dat de T-vertex tussen v[0] en v[1]
 * ligt (en in tegenwijzerzin) */
	tvertinfo->v[0] = v1;
	tvertinfo->v[1] = v2;
	if        (v2 == P->vertex[0]) {
		tvertinfo->v[2] = P->vertex[1];
		tvertinfo->v[3] = P->vertex[2];
	} else if (v2 == P->vertex[1]) {
		tvertinfo->v[2] = P->vertex[2];
		tvertinfo->v[3] = P->vertex[3%P->nrvertices];
	} else if (v2 == P->vertex[2]) {
		tvertinfo->v[2] = P->vertex[3%P->nrvertices];
		tvertinfo->v[3] = P->vertex[4%P->nrvertices];
	} else if (v2 == P->vertex[3]) {
		tvertinfo->v[2] = P->vertex[0];
		tvertinfo->v[3] = P->vertex[1];
	}

	return 1;
}

/* eliminatie van T-vertices in een driehoekige patch */
void TriangleElimTVertices(PATCH *P)
{
	TVERTINFO tvert[3];
	int tvertcount;

	tvertcount = 0;
	tvertcount += AnalyseEdge(P, 0, P->vertex[0], P->vertex[1], &tvert[tvertcount]);
	tvertcount += AnalyseEdge(P, 1, P->vertex[1], P->vertex[2], &tvert[tvertcount]);
	tvertcount += AnalyseEdge(P, 2, P->vertex[2], P->vertex[0], &tvert[tvertcount]);

	switch (tvertcount) {
	case 0:
		break;
	case 1:
		TElimTVertices1(P, tvert[0].side,
				tvert[0].v[0], tvert[0].t, tvert[0].v[2], 
				tvert[0].Qa);
		TElimTVertices1(P, tvert[0].side,
				tvert[0].t, tvert[0].v[1], tvert[0].v[2], 
				tvert[0].Qb);
		break;
	case 2:
/* zorg ervoor dat de vertex die volgt op de eerste T-vertex, de vertex net voor
 * de tweede T-vertex is. */
		if (tvert[0].v[1] != tvert[1].v[0]) {
			tvert[2] = tvert[0];
			tvert[0] = tvert[1];
			tvert[1] = tvert[2];
		}

		TElimTVertices1(P, tvert[0].side,
				tvert[0].v[0], tvert[0].t, tvert[1].t,
				tvert[0].Qa);
		TElimTVertices2(P, tvert[0].side, tvert[1].side,
				tvert[0].t, tvert[0].v[1], tvert[1].t,
				tvert[0].Qb, tvert[1].Qa);
		TElimTVertices1(P, tvert[1].side,
				tvert[1].t, tvert[1].v[1], tvert[0].v[0],
				tvert[1].Qb);
		break;
	case 3:
		TElimTVertices2(P, tvert[2].side, tvert[0].side,
				tvert[2].t, tvert[0].v[0], tvert[0].t,
				tvert[2].Qb, tvert[0].Qa);
		TElimTVertices2(P, tvert[0].side, tvert[1].side,
				tvert[0].t, tvert[1].v[0], tvert[1].t,
				tvert[0].Qb, tvert[1].Qa);
		TElimTVertices2(P, tvert[1].side, tvert[2].side,
				tvert[1].t, tvert[2].v[0], tvert[2].t,
				tvert[1].Qb, tvert[2].Qa);
		TElimTVertices0(P, 
				tvert[0].t, tvert[1].t, tvert[2].t);
		break;
	default:
		Fatal(3, "TriangleElimTVertices", "Impossible number of T vertices");
	}
}

/* eliminatie van T-vertices in een vierhoekige patch */
void QuadrElimTVertices(PATCH *P)
{
	TVERTINFO tvert[4];
	int tvertcount;

	tvertcount = 0;
	tvertcount += AnalyseEdge(P, 0, P->vertex[0], P->vertex[1], &tvert[tvertcount]);
	tvertcount += AnalyseEdge(P, 1, P->vertex[1], P->vertex[2], &tvert[tvertcount]);
	tvertcount += AnalyseEdge(P, 2, P->vertex[2], P->vertex[3], &tvert[tvertcount]);
	tvertcount += AnalyseEdge(P, 3, P->vertex[3], P->vertex[0], &tvert[tvertcount]);

	switch (tvertcount) {
	case 0:
		break;
	case 1:
		TElimTVertices1(P, tvert[0].side,
				tvert[0].v[0], tvert[0].t, tvert[0].v[3],
				tvert[0].Qa);
		TElimTVertices0(P,
				tvert[0].t, tvert[0].v[2], tvert[0].v[3]);
		TElimTVertices1(P, tvert[0].side,
				tvert[0].t, tvert[0].v[1], tvert[0].v[2],
				tvert[0].Qb);
		break;
	case 2:
       /* T-vertices in overstaande zijden */
		if (tvert[0].v[2] == tvert[1].v[0]) {
			TElimTVertices1(P, tvert[0].side,
					tvert[0].v[0], tvert[0].t, tvert[1].v[1],
					tvert[0].Qa);
			TElimTVertices1(P, tvert[0].side,
					tvert[0].t, tvert[0].v[1], tvert[1].v[0],
					tvert[0].Qb);
			TElimTVertices1(P, tvert[1].side,
					tvert[1].v[0], tvert[1].t, tvert[0].t,
					tvert[1].Qa);
			TElimTVertices1(P, tvert[1].side,
					tvert[1].t, tvert[1].v[1], tvert[0].t,
					tvert[1].Qb);
		} else {
       /* T-vertices in twee aanliggende zijden: zorg ervoor dat tvert[0].v[1] == tvert[1].v[0] */
			if (tvert[0].v[1] != tvert[1].v[0]) {
				tvert[2] = tvert[0];
				tvert[0] = tvert[1];
				tvert[1] = tvert[2];
			}

			TElimTVertices1(P, tvert[0].side,
					tvert[0].v[0], tvert[0].t, tvert[0].v[3],
					tvert[0].Qa);
			TElimTVertices2(P, tvert[0].side, tvert[1].side,
					tvert[0].t, tvert[1].v[0], tvert[1].t,
					tvert[0].Qb, tvert[1].Qa);
			TElimTVertices1(P, tvert[1].side,
					tvert[1].t, tvert[1].v[1], tvert[1].v[2],
					tvert[1].Qb);
			TElimTVertices0(P, 
					tvert[0].t, tvert[1].t, tvert[0].v[3]);
		}
		break;
	case 3:
/* zorg ervoor dat tvert[0].v[1] == tvert[1].v[0] en tvert[1].v[1] == tvert[2].v[0] */
		if        (tvert[0].v[1] != tvert[1].v[0]) {
			tvert[3] = tvert[0];
			tvert[0] = tvert[1];
			tvert[1] = tvert[2];
			tvert[2] = tvert[3];
		} else if (tvert[1].v[1] != tvert[2].v[0]) {
			tvert[3] = tvert[0];
			tvert[0] = tvert[2];
			tvert[2] = tvert[1];
			tvert[1] = tvert[3];
		}

		TElimTVertices1(P, tvert[0].side,
				tvert[0].v[0], tvert[0].t, tvert[1].t,
				tvert[0].Qa);
		TElimTVertices2(P, tvert[0].side, tvert[1].side,
				tvert[0].t, tvert[1].v[0], tvert[1].t,
				tvert[0].Qb, tvert[1].Qa);
		TElimTVertices2(P, tvert[1].side, tvert[2].side,
				tvert[1].t, tvert[2].v[0], tvert[2].t,
				tvert[1].Qb, tvert[2].Qa);
		TElimTVertices1(P, tvert[2].side,
				tvert[2].t, tvert[2].v[1], tvert[1].t,
				tvert[2].Qb);
		TElimTVertices0(P,
				tvert[0].v[0], tvert[1].t, tvert[2].v[1]);
		break;
	case 4:
		TElimTVertices2(P, tvert[0].side, tvert[1].side,
				tvert[0].t, tvert[1].v[0], tvert[1].t,
				tvert[0].Qb, tvert[1].Qa);
		TElimTVertices2(P, tvert[1].side, tvert[2].side,
				tvert[1].t, tvert[2].v[0], tvert[2].t,
				tvert[1].Qb, tvert[2].Qa);
		TElimTVertices2(P, tvert[2].side, tvert[3].side,
				tvert[2].t, tvert[3].v[0], tvert[3].t,
				tvert[2].Qb, tvert[3].Qa);
		TElimTVertices2(P, tvert[3].side, tvert[0].side,
				tvert[3].t, tvert[0].v[0], tvert[0].t,
				tvert[3].Qb, tvert[0].Qa);
		TElimTVertices0(P,
				tvert[0].t, tvert[1].t, tvert[2].t);
		TElimTVertices0(P,
				tvert[2].t, tvert[3].t, tvert[0].t);
		break;
	default:
		Fatal(3, "QuadrElimTVertices", "Impossible number of T vertices");
	}
}

void ElementElimTVertices(PATCH *P)
{
	switch (P->nrvertices) {
	case 3:
		TriangleElimTVertices(P);
		break;
	case 4:
		QuadrElimTVertices(P);
		break;
	default:
		Fatal(3, NULL, "Can't do T-vertex elimination on patches with >4 vertices");
	}
}

void PatchElimTVertices(PATCH *patch)
{
/* eventuele verouderde gegevens vrijgeven */
	if (patch->Tpatches) {
		TPatchListIterate(patch->Tpatches, TPatchDestroy);
		TPatchListDestroy(patch->Tpatches);
		patch->Tpatches = TPatchListCreate();
	}

/* eliminatie van Tvertices is enkel nodig voor elementen die niet verder 
 * onderverdeeld zijn */
	if (patch->subpatches) {
		PatchListIterate(patch->subpatches, PatchElimTVertices);
	} else 
		ElementElimTVertices(patch);
}

/* T-vertex eliminatie */
#include "defaults.h"
#include "scene.h"

static int doTVertexElimination = DEFAULT_TVERTEXELIMINATION;

void SetTVertexElimination(int bool)
{
	doTVertexElimination = bool;
}

int GetTVertexElimination(void)
{
	return doTVertexElimination;
}

void EliminateTVertices(void)
{
	if (!doTVertexElimination)
		return;

	if (render_ambient)
		ambient_radiance = *GetAmbientRadiance();

	PatchListIterate(Patches, PatchElimTVertices);
}



