/* bounds.c */
#include <stdio.h>
#include <math.h>
#include "bounds.h"
#include "Boolean.h"
#include "mymath.h"
#include "error.h"
#include "pools.h"

#ifdef NOPOOLS
#define NEWBOUNDINGBOX()	(float *)Alloc(sizeof(BOUNDINGBOX))
#define DISPOSEBOUNDINGBOX(ptr) Free((char *)ptr, sizeof(BOUNDINGBOX))
#else
static POOL *boundsPool = (POOL *)NULL;
#define NEWBOUNDINGBOX()	(float *)NewPoolCell(sizeof(BOUNDINGBOX), 0, "boundingboxes", &boundsPool)
#define DISPOSEBOUNDINGBOX(ptr) Dispose((char *)ptr, &boundsPool)
#endif

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].
 */

/* This routine computes the segment of intersection of the ray and the bounding box.
 * On input, tmin and tmax contain minimum and maximum allowed distance to the ray 
 * origin. On output, tmin and tmin contain the distance to the eye origin of
 * the intersection points of the ray with the boundingbox.
 * If there are no intersection in the given interval, FALSE is returned. If there
 * are intersections, TRUE is returned. */
int BoundsIntersectingSegment(RAY *ray, float *bounds, float *tmin, float *tmax)
{
  float t, mindist, maxdist;
  float dir, pos;

  mindist = *tmin;
  maxdist = *tmax;

  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 * (1.+EPSILON))
	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 * (1.+EPSILON))
	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 * (1.+EPSILON))
	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 * (1.+EPSILON))
	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 * (1.+EPSILON))
	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 * (1.+EPSILON))
	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) {
      return TRUE;
    }
  } else {
    if (*tmin < maxdist) {
      return TRUE;
    }
  }
  return FALSE;	/* hit, but not closer than maxdist */
}

int BoundsIntersect(RAY *ray, float *bounds, float mindist, float *maxdist)
{
  float tmin, tmax;
  int hit;

  tmin = mindist;
  tmax = *maxdist;
  hit = BoundsIntersectingSegment(ray, bounds, &tmin, &tmax);
  if (hit) {
    /* reduce maxdist */
    if (tmin == mindist) {
      if (tmax < *maxdist) 
	*maxdist = tmax;
    } else {
      if (tmin < *maxdist)
	*maxdist = tmin;
    }
  }
  return hit;
}

/* 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 <= 0.;
}

/* Computes boundingbox after transforming bbx with xf. Result is filled
 * in transbbx and a pointer to transbbx returned. */
float *BoundsTransform(float *bbx, TRANSFORM *xf, float *transbbx)
{
  VECTOR v[8];
  int i;
  double d;

  VECTORSET(v[0], bbx[MIN_X], bbx[MIN_Y], bbx[MIN_Z]);
  VECTORSET(v[1], bbx[MAX_X], bbx[MIN_Y], bbx[MIN_Z]);
  VECTORSET(v[2], bbx[MIN_X], bbx[MAX_Y], bbx[MIN_Z]);
  VECTORSET(v[3], bbx[MAX_X], bbx[MAX_Y], bbx[MIN_Z]);
  VECTORSET(v[4], bbx[MIN_X], bbx[MIN_Y], bbx[MAX_Z]);
  VECTORSET(v[5], bbx[MAX_X], bbx[MIN_Y], bbx[MAX_Z]);
  VECTORSET(v[6], bbx[MIN_X], bbx[MAX_Y], bbx[MAX_Z]);
  VECTORSET(v[7], bbx[MAX_X], bbx[MAX_Y], bbx[MAX_Z]);

  BoundsInit(transbbx);
  for (i=0; i<8; i++) {
    TRANSFORM_POINT_3D(*xf, v[i], v[i]);
    BoundsEnlargePoint(transbbx, &v[i]);
  }

  d = (transbbx[MAX_X] - transbbx[MIN_X]) * EPSILON;
  transbbx[MIN_X] -= d;
  transbbx[MAX_X] += d;
  d = (transbbx[MAX_Y] - transbbx[MIN_Y]) * EPSILON;
  transbbx[MIN_Y] -= d;
  transbbx[MAX_Y] += d;
  d = (transbbx[MAX_Z] - transbbx[MIN_Z]) * EPSILON;
  transbbx[MIN_Z] -= d;
  transbbx[MAX_Z] += d;

  return transbbx;
}
