/* polygon.c */
#include <math.h>
#include "polygon.h"
#include "vector.h"
#include "error.h"

/* this code was taken from Rayshade 4.0.6 */

/*
 * Quadrants are defined as:
 *        |
 *   1    |   0
 *        |
 * -------c--------
 *        |
 *   2    |   3
 *        |
 */
#define quadrant(p, c) ((p.u<c.u) ? ((p.v<c.v) ? 2 : 1) : ((p.v<c.v) ? 3 : 0))

/* is the intersection point of the ray with the plane of the polygon
 * inside the polygon ? returns the winding number which is 0 is the
 * point is not inside the polygon */
static int InsidePolygon(PATCH *poly, VEC2D *center)
{
	int winding, i;
	int quad, lastquad;
	float left, right;
	VEC2D cur, last;

	/*
	 * Is the point inside the polygon?
	 *
	 * Compute the winding number by finding the quadrant each
	 * polygon point lies in with respect to the the point in
	 * question, and computing a "delta" (winding number).  If we
	 * end up going around in a complete circle around
	 * the point (winding number is non-zero at the end), then
	 * we're inside.  Otherwise, the point is outside.
	 *
	 * Note that we can turn this into a 2D problem by projecting
	 * all the points along the axis defined by poly->index,
	 * the "dominant" part of the polygon's normal vector.
	 */
	winding = 0;
	VECTORPROJECT(last, *(poly->vertex[poly->nrvertices-1]->point), poly->index);
	lastquad = quadrant(last, (*center));
	for(i = 0; i < poly->nrvertices; i++, last = cur) {
		VECTORPROJECT(cur, *(poly->vertex[i]->point), poly->index);
		quad = quadrant(cur, (*center));
		if (quad == lastquad)
			continue;
		if(((lastquad + 1) & 3) == quad)
			winding++;
		else if(((quad + 1) & 3) == lastquad)
			winding--;
		else {
			/*
			 * Find where edge crosses
			 * center's X axis.
			 */
			right = last.u - cur.u;
			left = (last.v - cur.v) * (center->u - last.u);
			if(left + last.v * right > right * center->v)
				winding += 2;
			else
				winding -= 2;
		}
		lastquad = quad;
	}

	return winding;
}

static int IntersectionInsidePolygon(PATCH *poly, RAY *ray, float dist)
{
	VEC2D center;

	/*
	 * Compute the point of intersection, projected appropriately.
	 */
	if(poly->index == XNORMAL) {
		center.u = ray->pos.y + dist * ray->dir.y;
		center.v = ray->pos.z + dist * ray->dir.z;
	} else if(poly->index == YNORMAL) {
		center.v = ray->pos.z + dist * ray->dir.z;
		center.u = ray->pos.x + dist * ray->dir.x;
	} else  {
		center.u = ray->pos.x + dist * ray->dir.x;
		center.v = ray->pos.y + dist * ray->dir.y;
	}	

	return InsidePolygon(poly, &center);
}

int PointInPolygon(PATCH *poly, POINT *point)
{
	VEC2D center;

	VECTORPROJECT(center, *point, poly->index);
	return InsidePolygon(poly, &center);
}

/*
 * Perform ray-polygon intersection test.
 */
PATCH *PolygonIntersect(PATCH *poly, RAY *ray, float mindist, float *maxdist)
{
	float dist;

	/*
	 * First, find where ray hits polygon plane, projecting
	 * along the polygon's dominant normal component.
	 */

	dist = VECTORDOTPRODUCT(poly->norm, ray->dir);
	if (fabs(dist) < EPSILON)
		/*
	 	 * No intersection with polygon plane.
		 */
		return NULL;

	dist = (poly->d - VECTORDOTPRODUCT(poly->norm, ray->pos)) / dist;
	if (dist < mindist || dist > *maxdist)
		/*
		 * Intersection point behind origin or too far.
		 */
		return NULL;

	if (IntersectionInsidePolygon(poly, ray, dist)) {
		*maxdist = dist;
		return poly;
	}
	return (PATCH *)NULL;
}

void TriangleUV(PATCH *poly, POINT *point, VEC2D *uv)
{
	static PATCH *cached_poly;
	VECTOR d0, d1, d2, cu, cv;

	if (!poly)
		poly = cached_poly;

	VECTORSUBSTRACT(*poly->vertex[0]->point, *point, d0);
	VECTORSUBSTRACT(*poly->vertex[1]->point, *point, d1);
	VECTORSUBSTRACT(*poly->vertex[2]->point, *point, d2);
	VECTORCROSSPRODUCT(d2, d0, cu);
	VECTORCROSSPRODUCT(d0, d1, cv);
	uv->u = 0.5 * VECTORNORM(cu) / poly->area;
	uv->v = 0.5 * VECTORNORM(cv) / poly->area;
}

/* returns (u,v) parameters for the point in the polygon 
 * ref: Eric Haines, Essential Ray-tracing algorithms, in A. Glassner, Introduction to
 * Ray-Tracing, p 59 */
void QuadUV(PATCH *poly, POINT *point, VEC2D *uv)
{
	static float Du0, Du1, Du2, Dux, Duy, Dv0, Dv1, Dv2, Dvx, Dvy;
        static VECTOR Na, Nb, Nc, Qux, Quy, Qvx, Qvy;
	POINT Pa, Pb, Pc, Pd;

/* use the values computed last time if poly==NULL, this way we can do some caching. 
 * Storing this data with (toplevel) patches would be more efficient ... but would
 * take quite some memory. */
	if (poly) {		
		Pd = *poly->vertex[0]->point;
		VECTORSUBSTRACT(*poly->vertex[1]->point, *poly->vertex[0]->point, Pb);
		VECTORSUBSTRACT(*poly->vertex[3]->point, *poly->vertex[0]->point, Pc);
		VECTORSUBSTRACT(*poly->vertex[2]->point, *poly->vertex[3]->point, Pa);
		VECTORSUBSTRACT(Pa, Pb, Pa);

		VECTORCROSSPRODUCT(Pa, poly->norm, Na);
		VECTORCROSSPRODUCT(Pb, poly->norm, Nb);
		VECTORCROSSPRODUCT(Pc, poly->norm, Nc);

		Du0 = VECTORDOTPRODUCT(Nc, Pd);
		Du1 = VECTORDOTPRODUCT(Na, Pd) + VECTORDOTPRODUCT(Nc, Pb);
		Du2 = VECTORDOTPRODUCT(Na, Pb);

		if (fabs(Du2) > EPSILON) {		/* 'u' axes are not parallel */
			VECTORSCALEINVERSE(2.*Du2, Na, Qux);
			Dux = - Du1 / (2.*Du2);
			VECTORSCALEINVERSE(-Du2, Nc, Quy);
			Duy = Du0 / Du2;
		}

		Dv0 = VECTORDOTPRODUCT(Nb, Pd);
		Dv1 = VECTORDOTPRODUCT(Na, Pd) + VECTORDOTPRODUCT(Nb, Pc);
		Dv2 = VECTORDOTPRODUCT(Na, Pc);
		
		if (fabs(Dv2) > EPSILON) {		/* 'v' axes are not parallel */
			VECTORSCALEINVERSE(2.*Dv2, Na, Qvx);
			Dvx = - Dv1 / (2.*Dv2);
			VECTORSCALEINVERSE(-Dv2, Nb, Qvy);
			Dvy = Dv0 / Dv2;
		}
	}

	if (fabs(Du2) < EPSILON) {		/* 'u' axes are parallel */
		float B, C;

		B = Du1 - VECTORDOTPRODUCT(Na, *point);
		if (fabs(B) < EPSILON)
			uv->u = 0.0;		/* vertex of a triangle */
		else {
			C = VECTORDOTPRODUCT(Nc, *point) - Du0;
			uv->u = C/B;
		}
	} else {				/* 'u' axes are not parallel */
		float Ka, Kb, D;

		Ka = Dux + VECTORDOTPRODUCT(Qux, *point);
		Kb = Duy + VECTORDOTPRODUCT(Quy, *point);
		D = sqrt(Ka*Ka - Kb);
		uv->u = (Ka > D) ? (Ka - D) : (Ka + D);		
	}

	if (fabs(Dv2) < EPSILON) {		/* 'v' axes are parallel */
		float B, C;

		B = Dv1 - VECTORDOTPRODUCT(Na, *point);
		if (fabs(B) < EPSILON)
			uv->v = 0.0;		/* vertex of a triangle */
		else {
			C = VECTORDOTPRODUCT(Nb, *point) - Dv0;
			uv->v = C/B;
		}
	} else {				/* 'v' axes are not parallel */
		float Ka, Kb, D;

		Ka = Dvx + VECTORDOTPRODUCT(Qvx, *point);
		Kb = Dvy + VECTORDOTPRODUCT(Qvy, *point);
		D = sqrt(Ka*Ka - Kb);
		uv->v = (Ka > D) ? (Ka - D) : (Ka + D);		
	}
}

void PolygonUV(PATCH *poly, POINT *point, VEC2D *uv)
{
	switch (poly->nrvertices) {
	case 3:
		TriangleUV(poly, point, uv);
		break;
	case 4:
		QuadUV(poly, point, uv);
		break;
	default:
		Fatal(3, "PolygonUV", "Can only handle triangular or quadrilateral patches");
	}
}




