/* shaftculling.c */

/*
 * Shaft culling ala Haines, E. A. and Wallace, J. R. "Shaft culling for
 *		     efficient ray-traced radiosity", 2nd Eurographics Workshop
 *		     on Rendering, Barcelona, Spain, may 1991
 */

#include <stdlib.h>
#include "shaftculling.h"
#include "error.h"
#include "patchlist.h"
#include "compound.h"
#include "geom.h"
#include "boolean.h"

/* default (if both are undef'ed) strategy is "overlap open" */
#undef ALWAYS_OPEN 
#undef KEEP_CLOSED

void ShaftOmit(SHAFT *shaft, GEOM *geom)
{
	shaft->omit[shaft->nromit++] = geom;
}

/* constructs a shaft for two given bounding boxes */
SHAFT *ConstructShaft(float *ref1, float *ref2, SHAFT *shaft)
{
	int i, j, hasminmax1[6], hasminmax2[6];
	SHAFTPLANE *plane;

	shaft->nromit = 0;

	shaft->ref1 = ref1;
	shaft->ref2 = ref2;

	for (i=0; i<6; i++) {
		hasminmax1[i] = hasminmax2[i] = 0;
	}

/* create extent box of both items and keep track which coordinates of which
 * box become the minimum or maximum */
	for (i=MIN_X; i<=MIN_Z; i++) {
		if (shaft->ref1[i] < shaft->ref2[i]) {
			shaft->extent[i] = shaft->ref1[i];
			hasminmax1[i] = 1;
		} else {
			shaft->extent[i] = shaft->ref2[i];
			if (!FLOATEQUAL(shaft->ref1[i], shaft->ref2[i]))
				hasminmax2[i] = 1;
		}
	}

	for (i=MAX_X; i<=MAX_Z; i++) {
		if (shaft->ref1[i] > shaft->ref2[i]) {
			shaft->extent[i] = shaft->ref1[i];
			hasminmax1[i] = 1;
		} else {
			shaft->extent[i] = shaft->ref2[i];
			if (!FLOATEQUAL(shaft->ref1[i], shaft->ref2[i]))
				hasminmax2[i] = 1;
		}
	}

/* create plane set */
	plane = &shaft->plane[0];
	for (i=0; i<6; i++) {
		if (!hasminmax1[i]) continue;
		for (j=0; j<6; j++) {
			float u1, u2, v1, v2, du, dv;
			int a=(i%3), b=(j%3); /* directions */

			if (!hasminmax2[j] ||
			    a == b) /*same direction*/ continue;

			u1 = shaft->ref1[i];  /* coords. defining the plane */
			v1 = shaft->ref1[j];
			u2 = shaft->ref2[i];
			v2 = shaft->ref2[j];

			if ((i<=MIN_Z && j<=MIN_Z) || (i>=MAX_X && j>=MAX_X)) {
				du = v2-v1;
				dv = u1-u2;
			} else { /* normal must point outwards shaft */
				du = v1-v2;
				dv = u2-u1;
			}

			plane->n[a] = du;
			plane->n[b] = dv;
			plane->n[3-a-b] = 0.;
			plane->d = -(du*u1 + dv*v1);

			plane->coord_offset[0] = plane->n[0] > 0. ? MIN_X : MAX_X;
			plane->coord_offset[1] = plane->n[1] > 0. ? MIN_Y : MAX_Y;
			plane->coord_offset[2] = plane->n[2] > 0. ? MIN_Z : MAX_Z;

			plane++;
		}
	}
	shaft->planes = plane - &shaft->plane[0];

	return shaft;
}

#define INSIDE -1
#define OVERLAP 0
#define OUTSIDE 1

/* Tests a bounding volume against the shaft: returns INSIDE if the bounding volume
 * is inside the shaft, OVERLAP if it overlaps, OUTSIDE if it is otside the shaft */
int ShaftCullTest(float *bounds, SHAFT *shaft)
{
	int i;
	SHAFTPLANE *plane;

/* test against extent box */
	if (DisjunctBounds(bounds, shaft->extent))
		return OUTSIDE;

/* test against reference items */
	if (!DisjunctBounds(bounds, shaft->ref1) ||
	    !DisjunctBounds(bounds, shaft->ref2))
		return OVERLAP;

/* test against plane set: if nearest corner of the bounding box is outside any shaft-
 * plane, the object is outside the shaft */
	for (i=0, plane=&shaft->plane[0]; i<shaft->planes; i++, plane++)
		if (plane->n[0] * bounds[plane->coord_offset[0]] +
		    plane->n[1] * bounds[plane->coord_offset[1]] +
		    plane->n[2] * bounds[plane->coord_offset[2]] +
		    plane->d > 0.)
			return OUTSIDE;
#ifndef ALWAYS_OPEN
#ifndef KEEP_CLOSED
/* if the bounding box survides all previous tests, it must overlap or be inside the
 * shaft. If the farest corner of the bounding box is outside any shaft-plane, it
 * overlaps the shaft, otherwise it is inside the shaft */
	for (i=0, plane=&shaft->plane[0]; i<shaft->planes; i++, plane++)
		if (plane->n[0] * bounds[(plane->coord_offset[0]+3)%6] +
		    plane->n[1] * bounds[(plane->coord_offset[1]+3)%6] +
		    plane->n[2] * bounds[(plane->coord_offset[2]+3)%6] +
		    plane->d > 0.)
			return OVERLAP;
#endif /*KEEP_CLOSED*/
#endif /*ALWAYS_OPEN*/

	return INSIDE;
}

static int Omit(SHAFT *shaft, GEOM *geom)
{
	int i;

	for (i=0; i<shaft->nromit; i++)
		if (shaft->omit[i] == geom) return TRUE;
	return FALSE;
}

PATCHLIST *ShaftCullPatchlist(PATCHLIST *pl, SHAFT *shaft, PATCHLIST *culledpatchlist)
{
	int total, kept; /* can be used for ratio-open strategies */

	for (kept=total=0; pl; pl=pl->next, total++) {
		if (Omit(shaft, (GEOM *)pl->patch))
			continue;

		if (!pl->patch->bounds) { /* compute bounds */
			BOUNDINGBOX bounds;
			PatchBounds(pl->patch, bounds);
		}
		if (ShaftCullTest(pl->patch->bounds, shaft)!=OUTSIDE) {
			culledpatchlist = PatchListAdd(culledpatchlist, pl->patch);
			kept++;
		}
	}
	return culledpatchlist;
}

/* adds all objects from world that overlap or lay inside the shaft to
 * candlist, returns the new candidate list */

/* During shaftculling patchlist "geoms" are created - they (and only they)
 * need to be destroyed when destroying a geom candidate list created by 
 * DoShaftCulling - for other kinds of geoms, only a pointer is copied */
GEOMLIST *DoShaftCulling(GEOMLIST *world, SHAFT *shaft, GEOMLIST *candlist)
{
	for (; world; world=world->next) {
		if (Omit(shaft, world->geom)) continue;

/* unbounded geoms always overlap the shaft */
		switch (GeomBounded(world->geom) ? ShaftCullTest(world->geom->bounds, shaft)
			                  : OVERLAP) {
		case INSIDE: 
			if (GeomIsCompound(world->geom)) {
#ifdef ALWAYS_OPEN
				candlist = DoShaftCulling(GeomPrimList(world->geom), shaft, candlist); 
#else /*ALWAYS_OPEN*/
				candlist = GeomListAdd(candlist, world->geom); 
#endif /*ALWAYS_OPEN*/
			} else {
#ifdef ALWAYS_OPEN
				PATCHLIST *patchlist;
				patchlist = GeomPatchList(world->geom);
				patchlist = ShaftCullPatchlist(patchlist, shaft, PatchListCreate());
				if (patchlist) {
					GEOM *geom;
					geom = GeomCreate(patchlist, PatchlistMethods());
					candlist = GeomListAdd(candlist, geom);
				}
#else /*ALWAYS_OPEN*/
				if (GeomIsPatchlist(world->geom))
					candlist = GeomListAdd(candlist, GeomDuplicate(world->geom)); 
				else	
					candlist = GeomListAdd(candlist, world->geom); 
#endif /*ALWAYS_OPEN*/
			}
			break;
		case OVERLAP: 
			if (GeomIsCompound(world->geom)) {
#ifdef KEEP_CLOSED
				candlist = GeomListAdd(candlist, world->geom); 
#else /*KEEP_CLOSED*/
				candlist = DoShaftCulling(GeomPrimList(world->geom), shaft, candlist);
#endif /*KEEP_CLOSED*/
			} else {
#ifdef KEEP_CLOSED
				if (GeomIsPatchlist(world->geom))
					candlist = GeomListAdd(candlist, GeomDuplicate(world->geom)); 
				else	
					candlist = GeomListAdd(candlist, world->geom); 
#else /*KEEP_CLOSED*/
				PATCHLIST *patchlist;
				patchlist = GeomPatchList(world->geom);
				patchlist = ShaftCullPatchlist(patchlist, shaft, NULL);

				if (patchlist) {
					GEOM *geom;
					geom = GeomCreate(patchlist, PatchListMethods());
					candlist = GeomListAdd(candlist, geom);
				}
#endif /*KEEP_CLOSED*/
			}
			break;
		default:
			break;
		}
	}

	return candlist;
}

void FreeCandidateList(GEOMLIST *candlist)
{
	GEOMLIST *gl;

/* all patchlist geoms on the candidate list were created by DoShaftCulling */
	for (gl=candlist; gl; gl=gl->next)
		if (GeomIsPatchlist(gl->geom))
			GeomDestroy(gl->geom);

	GeomListDestroy(candlist);
}







