/*
 * shade.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: shade.c,v 4.0 91/07/17 14:47:36 kolb Exp Locker: kolb $
 *
 * $Log:	shade.c,v $
 * Revision 4.0  91/07/17  14:47:36  kolb
 * Initial version.
 * 
 */
#include "rayshade.h"
#include "texture.h"
#include "surface.h"
#include "light.h"
#include "atmosphere.h"
#include "options.h"
#include "stats.h"

Medium	TopMedium;
Atmosphere *AtmosEffects;

static void shade(), LightRay(), Lighting(), ReflectRay();
static int TransmitRay();

/*
 * Calculate color of ray.
 */
void
ShadeRay(hitlist, ray, dist, back, color, contrib)
HitList *hitlist;		/* Information about point of intersection. */
Ray *ray;			/* Direction and origin of ray. */
Float dist;			/* Distance from origin of intersection. */
Color	*back,			/* "Background" color */
	*color,			/* Color to assign current ray. */
	*contrib;		/* Contribution of this ray to final color */
{
	Vector norm, gnorm, pos; /* surface normal, point of intersection */
	Surface surf, *stmp;	/* surface properties */
	int enter, smooth;	/* entering ?, gnorm != snorm ?*/

	if (hitlist->nodes == 0) {
		/*
		 * No valid intersection.  Set distance for atmospheric
		 * effects and set color of ray to background.
		 */
		*color = *back;
		VecAddScaled(ray->pos, FAR_AWAY, ray->dir, &pos);
		if (!ray->media && AtmosEffects)
			Atmospherics(AtmosEffects, ray, FAR_AWAY, &pos, color);
		return;
	}

	/*
	 * Compute normal, surface properties, etc.
	 */
	stmp = GetShadingSurf(hitlist);
	surf = *stmp;
	enter = ComputeSurfProps(hitlist, ray, &pos, &norm, &gnorm, &surf,
			&smooth);
	Stats.HitRays++;

	/*
	 * Calculate ray color.
	 */
	shade(&pos, ray, &norm, &gnorm, smooth, enter, &surf, back, color,
			contrib);
	if (!ray->media && AtmosEffects)
		Atmospherics(AtmosEffects, ray, dist, &pos, color);
}

/*
 * Perform lighting calculations based on surface normal & other properties,
 * incident ray direction and position, and light source properties.
 * Spawn any necessary reflected and transmitted rays.
 */
static void
shade(pos, ray, nrm, gnrm, smooth, enter, surf, back, color, contrib)
Vector *pos, *nrm, *gnrm;	/* hit pos, shade normal, geo normal */
int smooth;			/* true if shading norm and geo norm differ */
int enter;			/* TRUE if entering surface */
Ray *ray;			/* indicent ray */
Surface *surf;			/* properties of hit surface */
Color *back, *color;		/* background color, computed color */
Color *contrib;			/* contribution to final pixel value */
{
	Float	k;		/* -ray . normal */
	Color	newcontrib;
	Vector	refl;		/* reflected direction */
	Color	reflectivity,	/* effective surface reflectivity */
		intens;		/* reflected/transmitted intensity */
	Light *lp;		/* current light source */
	extern Light *Lights;	/* list of defined sources */

	/*
	 * Ambient color is always included.
	 */
	ColorMultiply(surf->amb, Options.ambient, color);

	/*
	 * Calculate direction of reflected ray.
	 */
	k = -dotp(&ray->dir, nrm);
	VecAddScaled(ray->dir, 2.*k, *nrm, &refl);

	/*
	 * Calculate intensity contributed by each light source.
	 */
	for (lp = Lights; lp; lp = lp->next)
		LightRay(lp, pos, nrm, gnrm, smooth, &refl, surf,
				ray->depth, ray->sample, ray->time, color);

	if (ray->depth >= Options.maxdepth)
		/*
		 * Don't spawn any transmitted/reflected rays.
		 */
		return;
	/*
	 * Specular transmission (refraction).
	 */
	ColorScale(surf->reflect, surf->spec, &reflectivity);

	if (surf->transp > EPSILON) {
		ColorScale(surf->transp, surf->body, &intens);
		ColorMultiply(intens, *contrib, &newcontrib);
		if (newcontrib.r > Options.cutoff.r ||
		    newcontrib.g > Options.cutoff.g ||
		    newcontrib.b > Options.cutoff.b)
			/*
			 * Transmit ray.  If TIR occurs, add transmitted
			 * component to reflected component.  Kinda strange, but...
			 */
			if (TransmitRay(ray, pos, nrm, k, surf->index,
			    surf->statten, enter, back, &newcontrib, &intens, color))
				ColorAdd(reflectivity, intens, &reflectivity);
	}

	if (reflectivity.r > EPSILON ||
	    reflectivity.g > EPSILON ||
	    reflectivity.b > EPSILON) {
		ColorMultiply(reflectivity, *contrib, &newcontrib);
		if (newcontrib.r > Options.cutoff.r ||
		    newcontrib.g > Options.cutoff.g ||
		    newcontrib.b > Options.cutoff.b)
			ReflectRay(ray, pos, &refl, back, &reflectivity,
				&newcontrib, color);
	}
}

/*
 * Lighting calculations
 */
static void
LightRay(lp, pos, norm, gnorm, smooth, reflect, surf, depth, samp, time, color)
Light *lp;			/* Light source */
Vector *pos, *norm, *gnorm;	/* hit pos, shade norm, geo norm */
int smooth;			/* true if shade and geo norm differ */
Vector *reflect;		/* reflection direction */
Surface *surf;			/* surface characteristics */
int depth, samp;		/* ray depth, sample # */
Float time;
Color *color;			/* resulting color */
{
	Color lcolor;
	Ray newray;
	Float costheta, cosalpha, dist;

	newray.pos = *pos;
	newray.depth = depth;
	newray.sample = samp;
	newray.time = time; 
	newray.media = (Medium *)NULL;	

	LightDirection(lp, pos, &newray.dir, &dist);

	costheta = dotp(&newray.dir, norm);

	if (smooth) {
		cosalpha = dotp(&newray.dir, gnorm); 
		/*
		 * If shading normal indicates self-shadowing
		 * and geom normal indicates no self-shadowing,
		 * trust the geom normal.
		 */
		if (costheta <= 0. && cosalpha > 0.)
			costheta = cosalpha;
		/*
		 * If geom normal indicates self-shadowing and
		 * geom normal doesn't, then have to do something
		 * clever ala Snyder & Barr.
		 */
	}

	if (costheta <= 0.) {
		/*
		 * Light source is on opposite side of surface,
		 * hence light must be transmitted through...
		 */
		if (surf->translucency < EPSILON)
			return;
		if (!LightIntens(lp, &newray, dist,
			(int)surf->noshadow, &lcolor))
			return;
		cosalpha = -dotp(reflect, &newray.dir);
		Lighting(-costheta, cosalpha, &lcolor, &surf->translu,
				&surf->body, surf->stexp, color);
		ColorScale(surf->translucency, *color, color);
	} else {
		if (!LightIntens(lp, &newray, dist,
			(int)surf->noshadow, &lcolor))
			return;  /* prim is in shadow w.r.t light source */

		cosalpha = dotp(reflect, &newray.dir);
		Lighting(costheta, cosalpha, &lcolor, &surf->diff,
				&surf->spec, surf->srexp, color);
	}
}

/*
 * Compute shading function (diffuse reflection and specular highlight)
 *
 * This function *adds* the computed color to "color".
 */
static void
Lighting(costheta, cosalpha, lcolor, diff, spec, coef, color)
Float costheta, cosalpha, coef;
Color *diff, *spec, *color, *lcolor;
{
	Float intens;

	/*
	 * Diffuse reflection.
	 * Falls off as the cosine of the angle between
	 * the normal and the ray to the light (costheta).
	 */
	color->r += diff->r * costheta * lcolor->r;
	color->g += diff->g * costheta * lcolor->g;
	color->b += diff->b * costheta * lcolor->b;
	/*
	 * Specularly reflected highlights.
	 * Fall off as the cosine of the angle
	 * between the reflected ray and the ray to the light source.
	 */
	if (coef < EPSILON || cosalpha <= 0.)
		return;
	/*
	 * Specular highlight = cosine of the angle raised to the
	 * appropriate power.
	 */
	intens = pow(cosalpha, coef);
	color->r += spec->r * intens * lcolor->r;
	color->g += spec->g * intens * lcolor->g;
	color->b += spec->b * intens * lcolor->b;
}

/*
 * Spawn a transmitted ray.  Returns TRUE if total internal reflection
 * occurs, FALSE otherwise.
 */
static int
TransmitRay(ray, pos, norm, k, index, statten, enter, back, contrib, intens, color)
Ray *ray;
Vector *pos, *norm;
Float k, index, statten;
int enter;
Color *back, *contrib, *intens, *color;
{
	int total_int_refl = FALSE;
	Ray NewRay;
	Float dist;
	Color newcol;
	HitList hittmp;		/* Geom intersection record */

	NewRay.pos = *pos;		/* Origin == hit point */
	NewRay.media = ray->media;	/* Media == old media */
	NewRay.sample = ray->sample;
	NewRay.time = ray->time;
	NewRay.depth = ray->depth + 1;

	if (enter) {
		/*
		 * Entering surface.
		 */
		if (Refract(&NewRay.dir,
		    NewRay.media ? NewRay.media->index :
		    TopMedium.index, index, &ray->dir, norm, k)) {
			total_int_refl = TRUE;
		} else {
			/*
			 * Push information for new medium.
			 */
			NewRay.media = MediumPush(index, statten, NewRay.media);
		}
	} else {
		/*
		 * Exiting surface
		 * Pop medium from stack.
		 */
		if (NewRay.media != (Medium *)0)
			NewRay.media = NewRay.media->next;
		if (Refract(&NewRay.dir, index,
		    NewRay.media ? NewRay.media->index :
		    TopMedium.index, &ray->dir, norm, k)) {
			total_int_refl = TRUE;
		}
	}

	/*
	 * At this point, NewRay.media is the medium into which
	 * the new ray is entering.
	 */

	if (!total_int_refl) {
		Stats.RefractRays++;
		hittmp.nodes = 0;
		dist = FAR_AWAY;
		TraceRay(&NewRay, &hittmp, EPSILON, &dist);
		ShadeRay(&hittmp, &NewRay, dist, back, &newcol, contrib);
		ColorMultiply(newcol, *intens, &newcol);
		/*
		 * Attenuate transmitted color.  Note that
		 * if the transmitted ray hit nothing, we still
		 * perform this computation, as it's possible
		 * that 'air' has a non-unit statten.
		 */
		statten = NewRay.media ? NewRay.media->statten :
			TopMedium.statten;
		if (statten != 1.0) {
			statten = pow(statten, dist);
			ColorScale(statten, newcol, &newcol);
		}
		ColorAdd(*color, newcol, color);
		/* Free pushed medium */
		if (enter)
			free((voidstar)NewRay.media);
	}

	return total_int_refl;
}

static void
ReflectRay(ray, pos, dir, back, intens, contrib, color)
Ray *ray;
Vector *pos, *dir;
Color *back, *intens, *contrib, *color;
{
	Ray NewRay;
	HitList hittmp;		/* Geom intersection record */
	Color newcol;
	Float dist;

	NewRay.pos = *pos;		/* Origin == hit point */
	NewRay.dir = *dir;		/* Direction == reflection */
	NewRay.media = ray->media;	/* Medium == old medium */
	NewRay.sample = ray->sample;
	NewRay.time = ray->time;
	NewRay.depth = ray->depth + 1;
	Stats.ReflectRays++;
	hittmp.nodes = 0;
	dist = FAR_AWAY;
	(void)TraceRay(&NewRay, &hittmp, EPSILON, &dist);
	ShadeRay(&hittmp, &NewRay, dist, back, &newcol, contrib);
	ColorMultiply(newcol, *intens, &newcol);
	ColorAdd(*color, newcol, color);
}
