/* bounds.c */
#include <stdio.h>
#include <math.h>
#include "bounds.h"
#include "boolean.h"
#include "mymath.h"
#include "error.h"
#include "Memory.h"

#ifdef BETTER_MEMMAN
static STORAGE *boundingboxStor = (STORAGE *)NULL;
#define NEWBOUNDINGBOX()  	(float *)New(sizeof(BOUNDINGBOX), &boundingboxStor)
#define DISPOSEBOUNDINGBOX(ptr) Dispose((unsigned char *)(ptr), &boundingboxStor)
#else /*BETTER_MEMMAN*/
#define NEWBOUNDINGBOX()	(float *)Alloc(sizeof(BOUNDINGBOX))
#define DISPOSEBOUNDINGBOX(ptr) Free((char *)ptr, sizeof(BOUNDINGBOX))
#endif /*BETTER_MEMMAN*/


float *BoundsCreate(void)
{
	float *bounds;

	bounds = NEWBOUNDINGBOX();
	return bounds;
}

void BoundsDestroy(float *bounds)
{
	DISPOSEBOUNDINGBOX(bounds);
}

float *BoundsInit(float *bounds)
{
	bounds[MIN_X] = bounds[MIN_Y] = bounds[MIN_Z] = HUGE;
	bounds[MAX_X] = bounds[MAX_Y] = bounds[MAX_Z] = -HUGE;
	return bounds;
}

#define SetIfLess(a, b)		(a = ((a) < (b) ? (a) : (b)))
#define SetIfGreater(a, b)	(a = ((a) > (b) ? (a) : (b)))

/* enlarge bounds with extra */
float *BoundsEnlarge(float *bounds, float *extra)
{
	SetIfLess(bounds[MIN_X], extra[MIN_X]);
	SetIfLess(bounds[MIN_Y], extra[MIN_Y]);
	SetIfLess(bounds[MIN_Z], extra[MIN_Z]);
	SetIfGreater(bounds[MAX_X], extra[MAX_X]);
	SetIfGreater(bounds[MAX_Y], extra[MAX_Y]);
	SetIfGreater(bounds[MAX_Z], extra[MAX_Z]);
	return bounds;
}

float *BoundsEnlargePoint(float *bounds, POINT *point)
{	
	SetIfLess(bounds[MIN_X], point->x);
	SetIfLess(bounds[MIN_Y], point->y);
	SetIfLess(bounds[MIN_Z], point->z);
	SetIfGreater(bounds[MAX_X], point->x);
	SetIfGreater(bounds[MAX_Y], point->y);
	SetIfGreater(bounds[MAX_Z], point->z);
	return bounds;
}

float *BoundsCopy(float *from, float *to)
{
	to[MIN_X] = from[MIN_X];
	to[MIN_Y] = from[MIN_Y];
	to[MIN_Z] = from[MIN_Z];
	to[MAX_X] = from[MAX_X];
	to[MAX_Y] = from[MAX_Y];
	to[MAX_Z] = from[MAX_Z];
	return to;
}

void BoundsPrint(FILE *out, float *box)
{
	fprintf(out, "\tX: %f to %f\n", box[MIN_X], box[MAX_X]);
	fprintf(out, "\tY: %f to %f\n", box[MIN_Y], box[MAX_Y]);
	fprintf(out, "\tZ: %f to %f\n", box[MIN_Z], box[MAX_Z]);
}

/*
 * Check for intersection between bounding box and the given ray.
 * If there is an intersection between mindist and *maxdist along
 * the ray, *maxdist is replaced with the distance to the point of
 * intersection, and TRUE is returned.  Otherwise, FALSE is returned.
 *
 * If this routine is used to check for intersection with a volume
 * rather than a "hollow" box, one should first determine if
 * (ray->pos + mindist * ray->dir) is inside the bounding volume, and
 * call BoundsIntersect() only if it is not.
 *
 * This routine was taken from rayshade [PhB].
 */
int BoundsIntersect(RAY *ray, float *bounds, float mindist, float *maxdist)
{
	float t, tmin, tmax;
	float dir, pos;

	tmax = *maxdist;
	tmin = mindist;

	dir = ray->dir.x;
	pos = ray->pos.x;

	if (dir < 0) {
		t = (bounds[MIN_X] - pos) / dir;
		if (t < tmin)
			return FALSE;
		if (t <= tmax)
			tmax = t;
		t = (bounds[MAX_X] - pos) / dir;
		if (t >= tmin) {
			if (t > tmax)
				return FALSE;
			tmin = t;
		}
	} else if (dir > 0.) {
		t = (bounds[MAX_X] - pos) / dir;
		if (t < tmin)
			return FALSE;
		if (t <= tmax)
			tmax = t;
		t = (bounds[MIN_X] - pos) / dir;
		if (t >= tmin) {
			if (t > tmax)
				return FALSE;
			tmin = t;
		}
	} else if (pos < bounds[MIN_X] || pos > bounds[MAX_X])
		return FALSE;

	dir = ray->dir.y;
	pos = ray->pos.y;

	if (dir < 0) {
		t = (bounds[MIN_Y] - pos) / dir;
		if (t < tmin)
			return FALSE;
		if (t <= tmax)
			tmax = t;
		t = (bounds[MAX_Y] - pos) / dir;
		if (t >= tmin) {
			if (t > tmax)
				return FALSE;
			tmin = t;
		}
	} else if (dir > 0.) {
		t = (bounds[MAX_Y] - pos) / dir;
		if (t < tmin)
			return FALSE;
		if (t <= tmax)
			tmax = t;
		t = (bounds[MIN_Y] - pos) / dir;
		if (t >= tmin) {
			if (t > tmax)
				return FALSE;
			tmin = t;
		}
	} else if (pos < bounds[MIN_Y] || pos > bounds[MAX_Y])
		return FALSE;

	dir = ray->dir.z;
	pos = ray->pos.z;

	if (dir < 0) {
		t = (bounds[MIN_Z] - pos) / dir;
		if (t < tmin)
			return FALSE;
		if (t <= tmax)
			tmax = t;
		t = (bounds[MAX_Z] - pos) / dir;
		if (t >= tmin) {
			if (t > tmax)
				return FALSE;
			tmin = t;
		}
	} else if (dir > 0.) {
		t = (bounds[MAX_Z] - pos) / dir;
		if (t < tmin)
			return FALSE;
		if (t <= tmax)
			tmax = t;
		t = (bounds[MIN_Z] - pos) / dir;
		if (t >= tmin) {
			if (t > tmax)
				return FALSE;
			tmin = t;
		}
	} else if (pos < bounds[MIN_Z] || pos > bounds[MAX_Z])
		return FALSE;

	/*
	 * If tmin == mindist, then there was no "near"
	 * intersection farther than EPSILON away.
	 */
	if (tmin == mindist) {
		if (tmax < *maxdist) {
			*maxdist = tmax;
			return TRUE;
		}
	} else {
		if (tmin < *maxdist) {
			*maxdist = tmin;
			return TRUE;
		}
	}
	return FALSE;	/* hit, but not closer than maxdist */
}

/* returns TRUE if the boundingbox is behind the plane defined by norm and d */
/* see F. Tampieri, Fast Vertex Radiosity Update, Graphics Gems II, p 303 */
int BoundsBehindPlane(float *bounds, VECTOR *norm, float d)
{
	VECTOR P;

	if (norm->x > 0.)
		P.x = bounds[MAX_X];
	else
		P.x = bounds[MIN_X];

	if (norm->y > 0.)
		P.y = bounds[MAX_Y];
	else
		P.y = bounds[MIN_Y];

	if (norm->z > 0.)
		P.z = bounds[MAX_Z];
	else
		P.z = bounds[MIN_Z];

	return VECTORDOTPRODUCT(*norm, P) <= d;
}

