/*
 * shadow.c
 *
 * Copyright (C) 1989, 1991, 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 "libobj/object.h"
#include "libsurf/surface.h"
#include "light.h"

/*
 * Shadow stats.
 * External functions have read access via ShadowStats().
 */
static unsigned long	ShadowRays, ShadowHits, CacheMisses, CacheHits;
/*
 * Options controlling how shadowing information is determined.
 * Set by external modules via ShadowSetOptions().
 */
static long		ShadowOptions;

/*
 * Trace ray from point of intersection to a light.  If an intersection
 * occurs at a distance less than "dist" (the distance to the
 * light source), then the point is in shadow, and TRUE is returned.
 * Otherwise, the brightness/color of the light is computed ('result'),
 * and FALSE is returned.
 */
int
Shadowed(result, color, cache, ray, dist, noshadow)
Color *result, *color;	/* resultant intensity, light color */
ShadowCache *cache;	/* shadow cache for light */
Ray *ray;		/* ray, origin on surface, dir towards light */
Float dist;		/* distance from pos to light source */
int noshadow;		/* If TRUE, no shadow ray is cast. */
{
	Float s;
	int i, smooth;
	HitList hitlist;
	Ray tmpray;
	HitNode *np;
	ShadowCache *cp;
	Vector hitpos, norm, gnorm;
	Surface surf, *sptr;
	Float atten, totaldist;

	if (noshadow || NOSHADOWS(ShadowOptions)) {
		*result = *color;
		return FALSE;
	}

	ShadowRays++;
	s = dist;
	cp = &cache[ray->depth];
	/*
	 * Check shadow cache.  SHADOWCACHE() is implied.
	 */
	if (cp->obj) {
		/*
		 * Transform ray to the space of the cached primitive.
		 */
		tmpray = *ray;
		if (cp->dotrans)
			s *= RayTransform(&tmpray, &cp->trans);
		/*
		 * s = distance to light source in 'primitive space'.
		 * Intersect ray with cached object.
		 */
		if (IsAggregate(cp->obj)) {
			if ((*cp->obj->methods->intersect)(cp->obj->obj,
				&tmpray, &hitlist, SHADOW_EPSILON, &s)) {
				CacheHits++;
				return TRUE;
			}
		} else if ((*cp->obj->methods->intersect)(cp->obj->obj,
					&tmpray, SHADOW_EPSILON, &s)) {
			/* Hit cached object. */
			CacheHits++;
			return TRUE;
		}
		/*
		 * Did not hit anything -- zero out the cache.
		 */
		CacheMisses++;
		/*
		 * Transformed -- reset s for use below.
		 */
		s = dist;
		cp->obj = (Object *)NULL;
		cp->dotrans = FALSE;
	}

	hitlist.nodes = 0;
	if (!TraceRay(ray, &hitlist, SHADOW_EPSILON, &s)) {
		/* Shadow ray didn't hit anything. */
		*result = *color;
		return FALSE;
	}

	/*
	 * Otherwise, we've hit something.
	 */
	ShadowHits++;

	/*
	 * If we're not worrying about transparent objects...
	 * This is ugly due to the fact that we have to find
	 * the surface associated with the object that was hit.
	 * GetShadingSurf() will always return a non-null value.
	 */
	sptr = GetShadingSurf(&hitlist);
	if (!SHADOWTRANSP(ShadowOptions) || sptr->transp == 0.) {
		if (SHADOWCACHE(ShadowOptions)) {
			if (SHADOWCSG(ShadowOptions)) {
				/*
				 * There's possibly a CSG object in the
				 * hitlist, so we can't simply cache the
				 * primitive that was hit.  Find the
				 * object lowest in hit that's not part
				 * of a CSG object, and cache it.
				 */
				i = FirstCSGObj(&hitlist);
			} else {
				/*
				 * Cache the primitive that was hit.
				 */
				i = 0;
			}
			/*
			 * Compute total world-->cached object
			 * transformation and store in cp->trans.
			 */
			/*
			 * Find the first transformation...
			 */
			np = &hitlist.data[i];
			cp->obj = np->obj;
			cp->dotrans = FALSE;
			while (i < hitlist.nodes -1) {
				if (np->obj->trans) {
					if (cp->dotrans) {
						MatrixMult(
						    &np->obj->trans->itrans,
						    &cp->trans,
						    &cp->trans);
					} else {
						MatrixCopy(
						    &np->obj->trans->itrans,
						    &cp->trans);
						cp->dotrans = TRUE;
					}
				}
				i++;
				np++;
			}
		}
		return TRUE;
	}

	/*
	 * We've hit a transparent object.  Attenuate the color of the light
	 * source and continue the ray until we hit background or a
	 * non-transparent object.  Note that this is incorrect if DefIndex or
	 * any of the indices of refraction of the surfaces differ.
	 *
	 * "atten" is a misnomer.
	 */

	atten = 1.;
	totaldist = 0.;

	do {
		/*
		 * If surf.transp is 0 at this point, we could
		 * cache the object and return 0 directly.
		 * This would mean that if transp is to be set to nonzero
		 * by a texture, the surface should also have nonzero
		 * transp *before* texturing or else shadows won't
		 * work correctly.
		 */
		/*
		 * Perform texturing and the like in case surface
		 * transparency is modulated.
		 */
		(void)ComputeSurfProps(&hitlist, &ray, &hitpos,
			&norm, &gnorm, &surf, &smooth);
		atten *= surf.transp;
		/*
		 * Return if attenuation becomes large ('atten' is small).
		 */
		if (atten < EPSILON)
			return TRUE;
		/*
		 * Max distance is previous max minus previous distance
		 * traveled.
		 */
		totaldist = s + EPSILON;
		s = dist;
		/*
		 * Trace ray starting at new origin and in the
		 * same direction.
		 */
		hitlist.nodes = 0;
	} while (TraceRay(ray, &hitlist, totaldist, &s));

	ColorScale(atten, *color, result);
	return FALSE;
}

void
ShadowStats(shadowrays, shadowhit, cachehit, cachemiss)
unsigned long *shadowrays, *shadowhit, *cachehit, *cachemiss;
{
	*shadowrays = ShadowRays;
	*shadowhit = ShadowHits;
	*cachehit = CacheHits;
	*cachemiss = CacheMisses;
}

void
ShadowSetOptions(options)
{
	ShadowOptions = options;
	if (SHADOWCACHE(ShadowOptions) && SHADOWTRANSP(ShadowOptions)) {
		/*
		 * Ensure that if shadowtransp is true that
		 * caching is disabled.  We don't really have to do
		 * this, but the code isn't yet in place to handle
		 * this properly.
		 */
		ShadowOptions &= ~SHADOW_CACHE;
	}
}
