/*
 * csg.c
 *
 * Copyright (C) 1991, Rod G. Bogart, Craig E. Kolb
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 *
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 *
 * $Id$
 *
 * $Log$
 */
#include "object.h"
#include "csg.h"

#define csg_set_enter(l, f) ((l)->data[0].enter = (f) + 1)

static Methods *iCsgMethods = NULL;
static char csgName[] = "csg";

static int	CsgUnionInt(), CsgDifferenceInt(),
		CsgIntersectInt();
static void	CsgHitlistCopy(), CsgSetBounds();

Csg *
CsgCreate(op)
int op;
{
	Csg *csg;

	csg = (Csg *)share_malloc(sizeof(Csg));
	csg->operator = op;
	csg->obj1 = csg->obj2 = (Object *)NULL;


	switch(op) {
		case CSG_UNION:
			csg->intmeth = CsgUnionInt;
			break;
		case CSG_INTERSECT:
			csg->intmeth = CsgIntersectInt;
			break;
		case CSG_DIFFERENCE:
			csg->intmeth = CsgDifferenceInt;
			break;
		default:
			RLerror(RL_ABORT, "Unknown csg op type %d?\n",op);
	}
	return csg;
}

Methods *
CsgMethods()
{
	if (iCsgMethods== (Methods *)NULL) {
		iCsgMethods = MethodsCreate();
		iCsgMethods->create = (ObjCreateFunc *)CsgCreate;
		iCsgMethods->convert = CsgConvert;
		iCsgMethods->methods = CsgMethods;
		iCsgMethods->name = CsgName;
		iCsgMethods->intersect = CsgIntersect;
		iCsgMethods->bounds = CsgBounds;
		iCsgMethods->checkbounds = FALSE;
		iCsgMethods->closed = TRUE;
	}
	return iCsgMethods;
}

char *
CsgName()
{
	return csgName;
}

csg_intersect_objs(csg, ray, hit1, hit2, mindist, dist1, dist2)
Csg *csg;
Ray *ray;
HitList *hit1, *hit2;
Float mindist, *dist1, *dist2;
{
	int operator;

	hit1->nodes = 0;
	hit2->nodes = 0;
	*dist1 = FAR_AWAY;
	*dist2 = FAR_AWAY;
	operator = csg->operator;

	if (!intersect(csg->obj1, ray, hit1, mindist, dist1) &&
	    ((operator == CSG_INTERSECT) || (operator == CSG_DIFFERENCE))) {
		/*
		 * Intersection and Difference cases: if you miss the first
		 * object, you missed the whole thing.
		 */
		return FALSE;
	}

	if (!intersect(csg->obj2, ray, hit2, mindist, dist2) &&
	    ((operator == CSG_INTERSECT) ||
	     (hit1->nodes == 0) && (operator == CSG_UNION))) {
		/*
		 * Intersect case:  if you miss either object, you miss whole
		 * Union case: if you miss both object, you miss whole
		 */
		return FALSE;
	}

	return TRUE;
}

int
csg_enter_obj(hitp)
HitList *hitp;
{
	if (hitp->data[0].enter)
		return hitp->data[0].enter - 1;

	return PrimEnter(hitp->data[0].obj, &hitp->data[0].ray,
			hitp->data[0].mindist, hitp->data[0].dist);
}

static int
CsgUnionInt(ray, hit1p, hit2p, dist1, dist2, hitclose, distclose)
Ray *ray;
HitList *hit1p, *hit2p, **hitclose;
Float dist1, dist2, *distclose;
{
	Float distnext;
	HitList hitnext, *hittmp;

	while (TRUE) {
		if (hit2p->nodes == 0 ||
		    csg_enter_obj(hit2p)) {
			/* return hit1 */
			*hitclose = hit1p;
			*distclose = dist1;
			csg_set_enter(hit1p, csg_enter_obj(hit1p));
			return TRUE;
		} else {
			distnext = FAR_AWAY;
			hitnext.nodes = 0;
			if (!intersect(hit1p->data[hit1p->nodes-1].obj,
			    ray, &hitnext, dist2+EPSILON, &distnext)) {
				/*
				 * None of obj1 beyond, return hit2 (leaving)
				 */
				*hitclose = hit2p;
				*distclose = dist2;
				csg_set_enter(hit2p, FALSE);
				return TRUE;
			} else {
				/*
				 * Since hit1 is supposed to be the close one,
				 * swap them and copy hitnext into hit2.
	     			 */
				hittmp = hit1p;
				hit1p = hit2p;
				hit2p = hittmp;
				dist1 = dist2;
				CsgHitlistCopy(&hitnext, hit2p);
				dist2 = distnext;
				/* and continue */
			}
		}
	}
}

static int
CsgIntersectInt(ray, hit1p, hit2p, dist1, dist2, hitclose, distclose)
Ray *ray;
HitList *hit1p, *hit2p, **hitclose;
Float dist1, dist2, *distclose;
{
	HitList *hittmp, hitnext;
	Float distnext;

	while (TRUE) {
		if (!csg_enter_obj(hit2p)) {
			/* Ray is leaving obj2 */
			/* Return hit1 info */
			*hitclose = hit1p;
			*distclose = dist1;
			csg_set_enter(hit1p, csg_enter_obj(hit1p));
			return TRUE;
		} else {
			distnext = FAR_AWAY;
			hitnext.nodes = 0;
			if (!intersect(hit1p->data[hit1p->nodes-1].obj,
			    ray, &hitnext, dist2+EPSILON, &distnext)) {
				/*
				 * None of obj1 beyond, so return miss
				 */
				return FALSE;
			} else {
				/*
				 * Since hit1 is supposed to be the
				 * close one, swap them and copy
				 * hitnext into hit2.
				 */
				hittmp = hit1p;
				hit1p = hit2p;
				hit2p = hittmp;
				dist1 = dist2;
				CsgHitlistCopy(&hitnext, hit2p);
				dist2 = distnext;
				/* and continue */
			}
		}
	}
}

static int
CsgDifferenceInt(ray, hit1p, hit2p, dist1, dist2, hitclose, distclose)
Ray *ray;
HitList *hit1p, *hit2p, **hitclose;
Float dist1, dist2, *distclose;
{
	Float distnext;
	HitList hitnext;

	while (TRUE) {
		if (dist1 < dist2) {
			if (hit2p->nodes == 0 ||
			    csg_enter_obj(hit2p)) {
				/* return hit1 */
				*hitclose = hit1p;
				*distclose = dist1;
				csg_set_enter(hit1p, csg_enter_obj(hit1p));
				return TRUE;
			} else {
				distnext = FAR_AWAY;
				hitnext.nodes = 0;
				if (!intersect(hit1p->data[hit1p->nodes-1].obj,
				    ray, &hitnext, dist2+EPSILON, &distnext)) {
					/*
					 * None of obj1 beyond, so
					 * return miss
					 */
					return FALSE;
				} else {
					dist1 = distnext;
					CsgHitlistCopy(&hitnext, hit1p);
					/* and continue */
				}
			}
		} else { /* dist1 <= dist2 */
			if (hit1p->nodes == 0) {
				/* return a miss */
				return FALSE;
			}
			if (!csg_enter_obj(hit1p)) {
				/*
				 * return hit2, but invert hit2
				 * Enter/Leave flag
				 */
				*hitclose = hit2p;
				*distclose = dist2;
				csg_set_enter(hit2p, !csg_enter_obj(hit2p));
				return TRUE;
			} else {
				distnext = FAR_AWAY;
				hitnext.nodes = 0;
				if (!intersect(hit2p->data[hit2p->nodes-1].obj,
				    ray, &hitnext, dist1+EPSILON, &distnext)) {
					/*
					 * None of obj2 beyond, so
					 * return hit1
					 */
					*hitclose = hit1p;
					*distclose = dist1;
					/* we know we're entering obj1 */
					csg_set_enter(hit1p, TRUE);
					return TRUE;
				} else {
					dist2 = distnext;
					CsgHitlistCopy(&hitnext, hit2p);
					/* and continue */
				}
			}
		}
	}
}

int
CsgIntersect(csg, ray, hitlist, mindist, maxdist)
Csg *csg;
Ray *ray;
HitList *hitlist;
Float mindist, *maxdist;
{
	Float dist1, dist2, disttmp, distclose;
	HitList hit1, hit2, *hit1p, *hit2p, *hitclose;

	hit1p = &hit1;
	hit2p = &hit2;
	if (!csg_intersect_objs(csg, ray, hit1p, hit2p, mindist,
	    &dist1, &dist2)) {
		/* missed the csg object */
		return FALSE;
	}

	if ((dist1 > dist2) &&
	    (csg->operator == CSG_UNION || csg->operator == CSG_INTERSECT)) {
		/* swap so 1 is closest (except in difference case) */
		disttmp = dist2;  
		dist2 = dist1;  
		dist1 = disttmp;
		hit1p = &hit2;  
		hit2p = &hit1;
	}

	/*
	 * Call appropriate intersection method.  If FALSE is return,
	 * no hit of any kind was found.
	 */
	if (!(*csg->intmeth)(ray, hit1p, hit2p, dist1, dist2,
	    &hitclose, &distclose))
		return FALSE;

	/*
	 * At this time, the closest hit is in hitclose and
	 * distclose.
	 */
	if (distclose < mindist || distclose > *maxdist)
		return FALSE;

	CsgHitlistCopy(hitclose, hitlist);
	*maxdist = distclose;
	return TRUE;
}

static void
CsgHitlistCopy(from, to)
HitList *from, *to;
{
	int i;

	to->nodes = from->nodes;
	for (i = 0; i < from->nodes; i++)
		to->data[i] = from->data[i];
}

static void
CsgSetBounds(csg, bounds)
Csg *csg;
Float bounds[2][3];
{
	Object *o1, *o2;

	o1 = csg->obj1;
	o2 = csg->obj2;

	switch (csg->operator) {
	case CSG_UNION:
		bounds[LOW][X] = min(o1->bounds[LOW][X], o2->bounds[LOW][X]);
		bounds[HIGH][X] = max(o1->bounds[HIGH][X], o2->bounds[HIGH][X]);
		bounds[LOW][Y] = min(o1->bounds[LOW][Y], o2->bounds[LOW][Y]);
		bounds[HIGH][Y] = max(o1->bounds[HIGH][Y], o2->bounds[HIGH][Y]);
		bounds[LOW][Z] = min(o1->bounds[LOW][Z], o2->bounds[LOW][Z]);
		bounds[HIGH][Z] = max(o1->bounds[HIGH][Z], o2->bounds[HIGH][Z]);
		break;
	case CSG_INTERSECT:
		bounds[LOW][X] = max(o1->bounds[LOW][X], o2->bounds[LOW][X]);
		bounds[HIGH][X] = min(o1->bounds[HIGH][X], o2->bounds[HIGH][X]);
		bounds[LOW][Y] = max(o1->bounds[LOW][Y], o2->bounds[LOW][Y]);
		bounds[HIGH][Y] = min(o1->bounds[HIGH][Y], o2->bounds[HIGH][Y]);
		bounds[LOW][Z] = max(o1->bounds[LOW][Z], o2->bounds[LOW][Z]);
		bounds[HIGH][Z] = min(o1->bounds[HIGH][Z], o2->bounds[HIGH][Z]);
		break;
	case CSG_DIFFERENCE:
		bounds[LOW][X] = o1->bounds[LOW][X];
		bounds[HIGH][X] = o1->bounds[HIGH][X];
		bounds[LOW][Y] = o1->bounds[LOW][Y];
		bounds[HIGH][Y] = o1->bounds[HIGH][Y];
		bounds[LOW][Z] = o1->bounds[LOW][Z];
		bounds[HIGH][Z] = o1->bounds[HIGH][Z];
		break;
	default:
		RLerror(RL_ABORT, "Unknown csg operator type %d?\n",
			csg->operator);
	}
}

/*
 * Return index of hitlist node closest to the leaf and not below any
 * CSG object.
 */
int
FirstCSGObj(hitlist)
HitList *hitlist;
{
	int i;

	/*
	 * UUUUGLY -- detect if obj is a CsgObj by comparing
	 * methods with iCsgMethods.
	 */
	for (i = hitlist->nodes -1; i; i--)
		if (hitlist->data[i].obj->methods == iCsgMethods)
			return i;
	return 0;
}

void
CsgBounds(csg, bounds)
Csg *csg;
Float bounds[2][3];
{
	BoundsCopy(csg->bounds, bounds);
}

int
CsgConvert(csg, list)
Csg *csg;
Object *list;
{
	static int OpenAdvised = FALSE;
	/*
	 * Currently, this only handles two objects.
	 * Will be fixed in the future.
	 * No really we promise.
	 */
	if (!list || !list->next) {
		RLerror(RL_WARN, "CSG needs at least two objects.\n");
		return 0;
	}
	if (list->next->next) {
		RLerror(RL_WARN, "Currently, CSG only handles two objects.\n");
		return 0;
	}
	/*
	 * Things are put into lists backwards....
	 */
	csg->obj2 = list;
	csg->obj1 = list->next;
	if ((!csg->obj1->methods->closed || !csg->obj2->methods->closed) &&
	    !OpenAdvised) {
		RLerror(RL_ADVISE,
			"Performing CSG with non-closed object(s).\n");
		OpenAdvised = TRUE;
	}
	CsgSetBounds(csg, csg->bounds);
	return csg->obj1->prims + csg->obj2->prims;
}
