/*
 * triangle.c
 *
 * Copyright (C) 1989, 1991, Craig E. Kolb
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 *
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 * $Id$
 *
 * $Log$
 */
#include "object.h"
#include "triangle.h"

static Methods *iTriangleMethods = NULL;
static char triName[] = "triangle";

unsigned long TriTests, TriHits;

static void TriangleSetdPdUV();

/*
 * Create and return reference to a triangle.
 */
Triangle *
TriangleCreate(type, p1, p2, p3, n1, n2, n3, u1, u2, u3, flipflag)
int	type;
Vector	*p1, *p2, *p3, *n1, *n2, *n3;
Vec2d	*u1, *u2, *u3;
int flipflag;
{
	Triangle *triangle;
	Vector ptmp, anorm;
	Float d;

	/*
	 * Allocate new triangle and primitive to point to it.
	 */
	triangle = (Triangle *)Malloc(sizeof(Triangle));
	triangle->type = type;	/* so inttri can tell the difference */

	VecSub(*p2, *p1, &triangle->e[0]);
	VecSub(*p3, *p2, &triangle->e[1]);
	VecSub(*p1, *p3, &triangle->e[2]);

	/* Find plane normal. */
	VecCross(&triangle->e[0], &triangle->e[1], &ptmp);
	triangle->nrm = ptmp;
	if (VecNormalize(&triangle->nrm) == 0.) {
		RLerror(RL_ADVISE, "Degenerate triangle.\n");
		free((voidstar)triangle);
		return (Triangle *)NULL;
	}

	if (flipflag)
		VecScale(-1, triangle->nrm, &triangle->nrm);

	triangle->d = dotp(&triangle->nrm, p1);

	triangle->p[0] = *p1;
	triangle->p[1] = *p2;
	triangle->p[2] = *p3;

	if (type == PHONGTRI) {
		if (VecNormalize(n1) == 0. || VecNormalize(n2) == 0. ||
		    VecNormalize(n3) == 0.) {
			RLerror(RL_WARN, "Degenerate vertex normal.\n");
			return (Triangle *)NULL;
		}
		triangle->vnorm = (Vector *)Malloc(3 * sizeof(Vector));
		triangle->vnorm[0] = *n1;
		triangle->vnorm[1] = *n2;
		triangle->vnorm[2] = *n3;
		if (flipflag) {
			/* Flip the vertex normals */
			VecScale(-1, triangle->vnorm[0],
				&triangle->vnorm[0]);
			VecScale(-1, triangle->vnorm[1],
				&triangle->vnorm[1]);
			VecScale(-1, triangle->vnorm[2],
				&triangle->vnorm[2]);
		} else if (dotp(&triangle->vnorm[0], &triangle->nrm) < 0.) {
			/*
			 * Reverse direction of surface normal on Phong
			 * triangle if the surface normal points "away"
			 * from the first vertex normal.
			 * Note that this means that we trust the vertex
			 * normals rather than trust that the user gave the
			 * vertices in the correct order.
			 */
			RLerror(RL_ADVISE, "Inconsistant triangle normals.\n");
			VecScale(-1., triangle->nrm, &triangle->nrm);
			VecScale(-1., ptmp, &ptmp);
			triangle->d = -triangle->d;
			VecScale(-1., triangle->e[0], &triangle->e[0]);
			VecScale(-1., triangle->e[1], &triangle->e[1]);
			VecScale(-1., triangle->e[2], &triangle->e[2]);
		}
	}

	/*
	 * If UV coordinates are given for the vertices, allocate and
	 * store them.
	 */
	if (u1 && u2 && u3) {
		triangle->uv = (Vec2d *)Malloc(3 * sizeof(Vec2d));
		triangle->uv[0] = *u1;
		triangle->uv[1] = *u2;
		triangle->uv[2] = *u3;
		/* Calculate dpdu and dpdv vectors */
		triangle->dpdu = (Vector *)Malloc(sizeof(Vector));
		triangle->dpdv = (Vector *)Malloc(sizeof(Vector));
		TriangleSetdPdUV(triangle->p, triangle->uv,
				 triangle->dpdu, triangle->dpdv);
	} else {
		triangle->uv = (Vec2d *)NULL;
	}

	/*
	 * Find "dominant" part of normal vector.
	 */
	anorm.x = fabs(ptmp.x);
	anorm.y = fabs(ptmp.y);
	anorm.z = fabs(ptmp.z);

	/*
	 * Scale edges by dominant part of normal.  This makes intersection
	 * testing a bit faster.
	 */ 
	if (anorm.x > anorm.y && anorm.x > anorm.z) {
		triangle->index = XNORMAL;
		d = 1. / ptmp.x;
	} else if (anorm.y > anorm.z) {
		triangle->index = YNORMAL;
		d = 1. / ptmp.y;
	} else {
		triangle->index = ZNORMAL;
		d = 1. /ptmp.z;
	}

	VecScale(d, triangle->e[0], &triangle->e[0]);
	VecScale(d, triangle->e[1], &triangle->e[1]);
	VecScale(d, triangle->e[2], &triangle->e[2]);

	return triangle;
}

Methods *
TriangleMethods()
{
	if (iTriangleMethods == (Methods *)NULL) {
		iTriangleMethods = MethodsCreate();
		iTriangleMethods->create = (ObjCreateFunc *)TriangleCreate;
		iTriangleMethods->methods = TriangleMethods;
		iTriangleMethods->name = TriangleName;
		iTriangleMethods->intersect = TriangleIntersect;
		iTriangleMethods->normal = TriangleNormal;
		iTriangleMethods->uv = TriangleUV;
		iTriangleMethods->bounds = TriangleBounds;
		iTriangleMethods->stats = TriangleStats;
		iTriangleMethods->checkbounds = TRUE;
		iTriangleMethods->closed = FALSE;
	}
	return iTriangleMethods;
}

/*
 * Intersect ray with triangle.  This is an optimized version of the
 * intersection routine from Snyder and Barr's '87 SIGGRAPH paper.
 */
int
TriangleIntersect(tri, ray, mindist, maxdist)
Triangle *tri;
Ray *ray;
Float mindist, *maxdist;
{
	Float qi1, qi2, s, k, b0, b1, b2;
	Vector pos, dir;

	TriTests++;
	pos = ray->pos;
	dir = ray->dir;
	/*
	 * Plane intersection.
	 */
	k = dotp(&tri->nrm, &dir);
	if (fabs(k) < EPSILON)
		return FALSE;
	s = (tri->d - dotp(&tri->nrm, &pos)) / k;
	if (s < mindist || s > *maxdist)
		return FALSE;
	
	if (tri->index == XNORMAL) {
		qi1 = pos.y + s * dir.y;
		qi2 = pos.z + s * dir.z;
		b0 = tri->e[1].y * (qi2 - tri->p[1].z) -
				tri->e[1].z * (qi1 - tri->p[1].y);
		if (b0 < 0. || b0 > 1.)
			return FALSE;
		b1 = tri->e[2].y * (qi2 - tri->p[2].z) -
				tri->e[2].z * (qi1 - tri->p[2].y);
		if (b1 < 0. || b1 > 1.)
			return FALSE;
		b2 = tri->e[0].y * (qi2 - tri->p[0].z) -
				tri->e[0].z * (qi1 - tri->p[0].y);
		if (b2 < 0. || b2 > 1.)
			return FALSE;
	} else if (tri->index == YNORMAL) {
		qi1 = pos.x + s * dir.x;
		qi2 = pos.z + s * dir.z;
		b0 = tri->e[1].z * (qi1 - tri->p[1].x) -
			tri->e[1].x * (qi2 - tri->p[1].z);
		if (b0 < 0. || b0 > 1.)
			return FALSE;
		b1 = tri->e[2].z * (qi1 - tri->p[2].x) -
			tri->e[2].x * (qi2 - tri->p[2].z);
		if (b1 < 0. || b1 > 1.)
			return FALSE;
		b2 = tri->e[0].z * (qi1 - tri->p[0].x) -
			tri->e[0].x * (qi2 - tri->p[0].z);
		if (b2 < 0. || b2 > 1.)
			return FALSE;
	} else {
		qi1 = pos.x + s * dir.x;
		qi2 = pos.y + s * dir.y;
		b0 = tri->e[1].x * (qi2 - tri->p[1].y) -
			tri->e[1].y * (qi1 - tri->p[1].x);
		if (b0 < 0. || b0 > 1.)
			return FALSE;
		b1 = tri->e[2].x * (qi2 - tri->p[2].y) -
				tri->e[2].y * (qi1 - tri->p[2].x);
		if (b1 < 0. || b1 > 1.)
			return FALSE;
		b2 = tri->e[0].x * (qi2 - tri->p[0].y) -
				tri->e[0].y * (qi1 - tri->p[0].x);
		if (b2 < 0. || b2 > 1.)
			return FALSE;
	}

	tri->b[0] = b0;
	tri->b[1] = b1;
	tri->b[2] = b2;

	TriHits++;
	*maxdist = s;
	return TRUE;
}

int
TriangleNormal(tri, pos, nrm, gnrm)
Triangle *tri;
Vector *pos, *nrm, *gnrm;
{
	*gnrm = tri->nrm;

	if (tri->type == FLATTRI) {
		*nrm = tri->nrm;
		return FALSE;
	}

	/*
	 * Interpolate normals of Phong-shaded triangles.
	 */
	nrm->x = tri->b[0]*tri->vnorm[0].x+tri->b[1]*tri->vnorm[1].x+
		tri->b[2]*tri->vnorm[2].x;
	nrm->y = tri->b[0]*tri->vnorm[0].y+tri->b[1]*tri->vnorm[1].y+
		tri->b[2]*tri->vnorm[2].y;
	nrm->z = tri->b[0]*tri->vnorm[0].z+tri->b[1]*tri->vnorm[1].z+
		tri->b[2]*tri->vnorm[2].z;
	(void)VecNormalize(nrm);
	return TRUE;
}

/*ARGSUSED*/
void
TriangleUV(tri, pos, norm, uv, dpdu, dpdv)
Triangle *tri;
Vector *pos, *norm, *dpdu, *dpdv;
Vec2d *uv;
{
	Float d;

	/*
	 * Normalize barycentric coordinates.
	 */
	d = tri->b[0]+tri->b[1]+tri->b[2];

	tri->b[0] /= d;
	tri->b[1] /= d; 
	tri->b[2] /= d;

	if (dpdu) {
		if (tri->uv == (Vec2d *)NULL) {
			*dpdu = tri->e[0];
			(void)VecNormalize(dpdu);
			VecSub(tri->p[0], *pos, dpdv);
			(void)VecNormalize(dpdv);
		} else {
			*dpdu = *tri->dpdu;
			*dpdv = *tri->dpdv;
		}
	}

	if (tri->uv == (Vec2d *)NULL) {
		uv->v = tri->b[2];
		if (equal(uv->v, 1.))
			uv->u = 0.;
		else
			uv->u = tri->b[1] / (tri->b[0] + tri->b[1]);
	} else {
		/*
		 * Compute UV by taking weighted sum of UV coordinates.
		 */
		uv->u = tri->b[0]*tri->uv[0].u + tri->b[1]*tri->uv[1].u +
			tri->b[2]*tri->uv[2].u;
		uv->v = tri->b[0]*tri->uv[0].v + tri->b[1]*tri->uv[1].v +
			tri->b[2]*tri->uv[2].v;
	}
}

void
TriangleBounds(tri, bounds)
Triangle *tri;
Float bounds[2][3];
{
	bounds[LOW][X] = bounds[HIGH][X] = tri->p[0].x;
	bounds[LOW][Y] = bounds[HIGH][Y] = tri->p[0].y;
	bounds[LOW][Z] = bounds[HIGH][Z] = tri->p[0].z;

	if (tri->p[1].x < bounds[LOW][X]) bounds[LOW][X] = tri->p[1].x;
	if (tri->p[1].x > bounds[HIGH][X]) bounds[HIGH][X] = tri->p[1].x;
	if (tri->p[2].x < bounds[LOW][X]) bounds[LOW][X] = tri->p[2].x;
	if (tri->p[2].x > bounds[HIGH][X]) bounds[HIGH][X] = tri->p[2].x;

	if (tri->p[1].y < bounds[LOW][Y]) bounds[LOW][Y] = tri->p[1].y;
	if (tri->p[1].y > bounds[HIGH][Y]) bounds[HIGH][Y] = tri->p[1].y;
	if (tri->p[2].y < bounds[LOW][Y]) bounds[LOW][Y] = tri->p[2].y;
	if (tri->p[2].y > bounds[HIGH][Y]) bounds[HIGH][Y] = tri->p[2].y;

	if (tri->p[1].z < bounds[LOW][Z]) bounds[LOW][Z] = tri->p[1].z;
	if (tri->p[1].z > bounds[HIGH][Z]) bounds[HIGH][Z] = tri->p[1].z;
	if (tri->p[2].z < bounds[LOW][Z]) bounds[LOW][Z] = tri->p[2].z;
	if (tri->p[2].z > bounds[HIGH][Z]) bounds[HIGH][Z] = tri->p[2].z;
}

char *
TriangleName()
{
	return triName;
}

void
TriangleStats(tests, hits)
unsigned long *tests, *hits;
{
	*tests = TriTests;
	*hits = TriHits;
}

/*
 * Given three vertices of a triangle and the uv coordinates associated
 * with each, compute directions of u and v axes.
 */
static void
TriangleSetdPdUV(p, t, dpdu, dpdv)
Vector p[3];			/* Triangle vertices */
Vec2d t[3];			/* uv coordinates for each vertex */
Vector *dpdu, *dpdv;		/* u and v axes (return values) */
{
	Float scale;
	int hi, mid, lo;
	Vector base;

	/* sort u coordinates */
	if (t[2].u > t[1].u) {
		if (t[1].u > t[0].u) {
			hi = 2; mid = 1; lo = 0;
		} else if (t[2].u > t[0].u) {
			hi = 2; mid = 0; lo = 1;
		} else {
			hi = 0; mid = 2; lo = 1;
		}
	} else {
		if (t[2].u > t[0].u) {
			hi = 1; mid = 2; lo = 0;
		} else if (t[1].u > t[0].u) {
			hi = 1; mid = 0; lo = 2;
		} else {
			hi = 0; mid = 1; lo = 2;
		}
	}
	if (t[hi].u - t[lo].u == 0.) {
		/* degenerate axis */
		dpdv->x = dpdv->y = dpdv->z = 0.;
	} else {
		/*
		 * Given u coordinates of vertices forming the
		 * 'long' edge, find where 'middle'
		 * vertex falls on that edge given its u coordinate.
		 */
		scale = (t[mid].u - t[lo].u) / (t[hi].u - t[lo].u);
		VecComb(1.0 - scale, p[lo], scale, p[hi], &base);
		/*
		 * v axis extends from computed basepoint to
		 * middle vertex -- but in which direction?
		 */
		if (t[mid].v < ((1.0 - scale)*t[lo].v + scale*t[hi].v))
			VecSub(base, p[mid], dpdv);
		else
			VecSub(p[mid], base, dpdv);
		(void)VecNormalize(dpdv);
	}

	/* sort v coordinates */
	if (t[2].v > t[1].v) {
		if (t[1].v > t[0].v) {
			hi = 2; mid = 1; lo = 0;
		} else if (t[2].v > t[0].v) {
			hi = 2; mid = 0; lo = 1;
		} else {
			hi = 0; mid = 2; lo = 1;
		}
	} else {
		if (t[2].v > t[0].v) {
			hi = 1; mid = 2; lo = 0;
		} else if (t[1].v > t[0].v) {
			hi = 1; mid = 0; lo = 2;
		} else {
			hi = 0; mid = 1; lo = 2;
		}
	}
	if (t[hi].v - t[lo].v == 0.) {
		/* degenerate axis */
		dpdu->x = dpdu->y = dpdu->z = 0.;
	} else {
		/*
		 * Given v coordinates of vertices forming the
		 * 'long' edge, find where 'middle'
		 * vertex falls on that edge given its v coordinate.
		 */
		scale = (t[mid].v - t[lo].v) / (t[hi].v - t[lo].v);
		VecComb(1.0 - scale, p[lo], scale, p[hi], &base);
		/*
		 * u axis extends from computed basepoint to
		 * middle vertex -- but in which direction?
		 */
		if (t[mid].u < ((1.0 - scale)*t[lo].u + scale*t[hi].u))
			VecSub(base, p[mid], dpdu);
		else
			VecSub(p[mid], base, dpdu);
		(void)VecNormalize(dpdu);
	}
}
