/*
 * 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$
 *
 * $Log$
 */
#include "rayshade.h"
#include "libtext/texture.h"
#include "libsurf/surface.h"
#include "liblight/light.h"
#include "libsurf/atmosphere.h"
#include "options.h"
#include "stats.h"

Medium	TopMedium;

static void shade(), LightRay(), Lighting();

/*
 * 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;			/* Color to assign current ray. */
Float contrib;			/* Contribution of this ray to final color */
{
	Vector norm, gnorm, pos; /* surface normal, point of intersection */
	Surface surf;		/* surface properties */
	Medium *medium;		/* medium through which ray is traveling */
	int enter, smooth;	/* entering ?, gnorm != snorm ?*/

	/*
	 * If ray medium isn't set, use default.
	 */
	if (ray->media)
		medium = ray->media;
	else
		medium = &TopMedium;

	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 (medium->atmos)
			Atmospherics(medium->atmos, ray, FAR_AWAY, &pos, color);
		return;
	}

	/*
	 * Compute normal, surface properties, etc.
	 */
	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 (medium->atmos)
		Atmospherics(medium->atmos, 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 */
Float contrib;			/* contribution to final pixel value */
{
	Float dist, k;		/* hit distance, -ray . normal */
	Color newcol;		/* color of spawned rays. */
	HitList hittmp;		/* Object intersection record */
	Ray NewRay;		/* spawned ray */
	Vector refl;		/* reflected direction */
	int total_int_refl;	/* total internal reflection flag */
	Float reflectivity;	/* effective surface reflectivity */
	Light *lp;		/* current light source */
	extern Light *Lights;	/* list of defined sources */

	/*
	 * Ambient color is always included.
	 */
	*color = surf->amb;

	/*
	 * 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, color);

	if (ray->depth >= Options.maxdepth)
		/*
		 * Don't spawn any transmitted/reflected rays.
		 */
		return;
	/*
	 * Specular transmission (refraction).
	 */
	reflectivity = surf->refl;
	total_int_refl = FALSE;
	if (surf->transp != 0. && contrib * surf->transp > Options.cutoff) {
		NewRay.pos = *pos;		/* Origin == hit point */
		NewRay.media = ray->media;	/* Media == old media */
		NewRay.sample = ray->sample;
		NewRay.depth = ray->depth + 1;
		if (enter) {
			/*
			 * Entering surface.
			 */
			if (Refract(&NewRay.dir,
			    NewRay.media ? NewRay.media->index :
			    TopMedium.index, surf->kref, ray->dir, *nrm, k)) {
				total_int_refl = TRUE;
			} else {
				NewRay.media = MediumPush(surf->kref,
					surf->atmos, NewRay.media);
			}
		} else {
			/*
			 * Exiting surface
			 * Pop medium from stack.
			 * Hmm, maybe should use
			 * popped medium rather than surf's kref?
			 */
			if (NewRay.media != (Medium *)0)
				NewRay.media = NewRay.media->next;
			if (Refract(&NewRay.dir, surf->kref,
			    NewRay.media ? NewRay.media->index :
			    TopMedium.index, ray->dir, *nrm, k)) {
				total_int_refl = TRUE;
			}
		}
		if (total_int_refl) {
			reflectivity += surf->transp;
		} else {
			Stats.RefractRays++;
			hittmp.nodes = 0;
			dist = FAR_AWAY;
			(void)TraceRay(&NewRay, &hittmp, EPSILON, &dist);
			ShadeRay(&hittmp, &NewRay, dist, back, &newcol,
				contrib * surf->transp);
			ColorAddScaled(*color, surf->transp, newcol, color);
			/* Free pushed medium */
			if (enter)
				free((voidstar)NewRay.media);
		}
	}
	/*
	 * Specular reflection.
	 */
	if (reflectivity > 0. && contrib * reflectivity > Options.cutoff) {
		NewRay.pos = *pos;		/* Origin == hit point */
		NewRay.dir = refl;		/* Direction == reflection */
		NewRay.media = ray->media;	/* Medium == old medium */
		NewRay.sample = ray->sample;
		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*reflectivity);
		ColorAddScaled(*color, reflectivity, newcol, color);
	}

}

/*
 * Lighting calculations
 */
static void
LightRay(lp, pos, norm, gnorm, smooth, reflect, surf, depth, samp, 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 # */
Color *color;			/* resulting color */
{
	Color lcolor;
	Ray newray;
	Float costheta, cosalpha, dist;

	newray.pos = *pos;
	newray.depth = depth;
	newray.sample = samp;
	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.
		 */
		if (surf->translucency <= 0.)
			return;
		if (!LightIntens(lp, &newray, dist,
			(int)surf->noshadow, &lcolor))
			return;
		cosalpha = -dotp(reflect, &newray.dir);
		/*
		 * attenuate the light source color by the translucency
		 */
		ColorScale(surf->translucency, lcolor, &lcolor);
		Lighting(-costheta, cosalpha, &lcolor, &surf->diff,
				&surf->spec, surf->stcoef, 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->coef, 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;
}
