/* geom.c */
#include <stdio.h>
#include <stdlib.h>
#include "geom.h"
#include "bounds.h"
#include "error.h"
#include "mymath.h"
#include "pools.h"
#include "radiance.h"

#ifdef NOPOOLS
#define NEWGEOM()	(GEOM *)Alloc(sizeof(GEOM))
#define DISPOSEGEOM(ptr) Free((char *)ptr, sizeof(GEOM))
#else
static POOL *geomPool = (POOL *)NULL;
#define NEWGEOM()	(GEOM *)NewPoolCell(sizeof(GEOM), 0, "geoms", &geomPool)
#define DISPOSEGEOM(ptr) Dispose((char *)ptr, &geomPool)
#endif

/* statistics */
int nrgeoms = 0, id = 0;

static void BoundsEnlargeTinyBit(float *bounds)
{
  float Dx = (bounds[MAX_X] - bounds[MIN_X]) * 1e-4;
  float Dy = (bounds[MAX_Y] - bounds[MIN_Y]) * 1e-4;
  float Dz = (bounds[MAX_Z] - bounds[MIN_Z]) * 1e-4;
  if (Dx < EPSILON) Dx = EPSILON;
  if (Dy < EPSILON) Dy = EPSILON;
  if (Dz < EPSILON) Dz = EPSILON;
  bounds[MIN_X] -= Dx; bounds[MAX_X] += Dx;
  bounds[MIN_Y] -= Dy; bounds[MAX_Y] += Dy;
  bounds[MIN_Z] -= Dz; bounds[MAX_Z] += Dz;
}

/* This function is used to create a new GEOMetry with given specific data and
 * methods. A pointer to the new GEOMetry is returned. */
GEOM *GeomCreate(void *obj, GEOM_METHODS *methods)
{
  GEOM *p = (GEOM *)NULL;

  if (obj == NULL) return (GEOM *)NULL;

  p = NEWGEOM(); nrgeoms++;
  p->id = id++;
  p->obj = obj;
  p->methods = methods;

  if (methods->bounds) {
    methods->bounds(obj, p->bounds);
    /* enlarge bounding box a tiny bit for more conservative bounding box culling */
    BoundsEnlargeTinyBit(p->bounds);
    p->bounded = TRUE;
  } else {
    BoundsInit(p->bounds);
    p->bounded = FALSE;
  }
  p->shaftcullgeom = FALSE;

  p->radiance_data = (void *)NULL;
  p->tmp.i = 0;
  p->omit = FALSE;

  p->dlistid = -1;

  return p;
}

/* This function prints the GEOMetry data to the file out */
void GeomPrint(FILE *out, GEOM *geom)
{
  fprintf(out, "Geom %d, bounded = %s, shaftcullgeom = %s:\n",
	  geom->id, 
	  geom->bounded ? "TRUE" : "FALSE",
	  geom->shaftcullgeom ? "TRUE" : "FALSE");

  geom->methods->print(out, geom->obj);
}

/* This function returns a bounding box for the GEOMetry */
float *GeomBounds(GEOM *geom)
{
  return geom->bounds;
}

/* This function destroys the given GEOMetry */
void GeomDestroy(GEOM *geom)
{
  geom->methods->destroy(geom->obj);
  DISPOSEGEOM(geom); nrgeoms--;
}

/* This function returns nonzero if the given GEOMetry is an aggregate. An
 * aggregate is a geometry that consists of simpler geometries. Currently,
 * there is only one type of aggregate geometry: the compound, which is basically 
 * just a list of simpler geometries. Other aggregate geometries are also
 * possible, e.g. CSG objects. If the given GEOMetry is a primitive, zero is 
 * returned. A primitive GEOMetry is a GEOMetry that does not consist of
 * simpler GEOMetries. */
int GeomIsAggregate(GEOM *geom)
{
  return geom->methods->primlist != (GEOMLIST *(*)(void *))NULL;
}

/* Returns a linear list of the simpler GEOMEtries making up an aggregate GEOMetry.
 * A NULL pointer is returned if the GEOMetry is a primitive. */
GEOMLIST *GeomPrimList(GEOM *geom)
{
  if (geom->methods->primlist)
    return geom->methods->primlist(geom->obj);
  else
    return (GEOMLIST *)NULL;
}

/* Returns a linear list of patches making up a primitive GEOMetry. A NULL
 * pointer is returned if the given GEOMetry is an aggregate. */
PATCHLIST *GeomPatchList(GEOM *geom)
{
  if (geom->methods->patchlist)
    return geom->methods->patchlist(geom->obj);
  else
    return (PATCHLIST *)NULL;
}

/* This routine creates and returns a duplicate of the given geometry. Needed for
 * shaft culling. */
GEOM *GeomDuplicate(GEOM *geom)
{
  GEOM *p = (GEOM *)NULL;

  if (!geom->methods->duplicate) {
    Error("GeomDuplicate", "geometry has no duplicate method");
    return (GEOM *)NULL;
  }

  p = NEWGEOM(); nrgeoms++;
  *p = *geom;
  p->obj = geom->methods->duplicate(geom->obj);

  return p;
}

/* Will avoid intersection testing with geom1 and geom2 (possibly NULL 
 * pointers). Can be used for avoiding immediate selfintersections. */
GEOM *excludedGeom1=(GEOM *)NULL, *excludedGeom2=(GEOM *)NULL;
void GeomDontIntersect(GEOM *geom1, GEOM *geom2)
{
  excludedGeom1 = geom1;
  excludedGeom2 = geom2;
}

#ifdef IDEBUG
extern int idebug;
#endif

/* This routine returns NULL is the ray doesn't hit the discretisation of the
 * GEOMetry. If the ray hits the discretisation of the GEOM, a pointer to a
 * struct containing (among other information) the hit patch is returned. 
 * The hitflags (defined in ray.h) determine whether the nearest intersection
 * is returned, or rather just any intersection (e.g. for shadow rays in 
 * ray tracing or for form factor rays in radiosity), whether to consider
 * intersections with front/back facing patches and what other information
 * besides the hit patch (interpolated normal, intersection point, material 
 * properties) to return. */
HITREC *GeomDiscretisationIntersect(GEOM *geom, RAY *ray, 
				   float mindist, float *maxdist,
				   int hitflags, HITREC *hitstore)
{
  VECTOR vtmp;
  float nmaxdist;

#ifdef IDEBUG
  if (idebug) {
    fprintf(stderr, "====> %s %d: GeomDiscretisationIntersect\n", __FILE__, __LINE__);
  }
#endif

  if (geom==excludedGeom1 || geom==excludedGeom2) {
#ifdef IDEBUG
    if (idebug) {
      fprintf(stderr, "%s %d: excluded geometry -> no intersection\n", __FILE__, __LINE__);
    }
#endif
    return (HITREC *)NULL;
  }

  if (geom->bounded) {
    /* Check ray/bounding volume intersection */
    VECTORADDSCALED(ray->pos, mindist, ray->dir, vtmp);
    if (OutOfBounds(&vtmp, geom->bounds)) {
      nmaxdist = *maxdist;
      if (!BoundsIntersect(ray, geom->bounds, mindist, &nmaxdist)) {
#ifdef IDEBUG
	if (idebug) {
	  fprintf(stderr, "%s %d: bounding box test fails -> no intersection\n", __FILE__, __LINE__);
	}
#endif
	return NULL;
      }
    }
  }
#ifdef IDEBUG
  if (idebug) {
    fprintf(stderr, "%s %d: bounding box test succeeded, now calling discretisatoinintersect method ...\n", __FILE__, __LINE__);
  }
#endif
  return geom->methods->discretisation_intersect(geom->obj, ray, mindist, maxdist, hitflags, hitstore);
}

HITLIST *GeomAllDiscretisationIntersections(HITLIST *hits, 
					    GEOM *geom, RAY *ray, 
					    float mindist, float maxdist,
					    int hitflags)
{
  VECTOR vtmp;
  float nmaxdist;

  if (geom==excludedGeom1 || geom==excludedGeom2) {
    return hits;
  }

  if (geom->bounded) {
    /* Check ray/bounding volume intersection */
    VECTORADDSCALED(ray->pos, mindist, ray->dir, vtmp);
    if (OutOfBounds(&vtmp, geom->bounds)) {
      nmaxdist = maxdist;
      if (!BoundsIntersect(ray, geom->bounds, mindist, &nmaxdist)) {
	return hits;
      }
    }
  }
  return geom->methods->all_discretisation_intersections(hits, geom->obj, ray, mindist, maxdist, hitflags);
}

