
/*
 * (c) 1988 by George Kyriazis
 */

/*
 * here is the shading function
 */

#include	"ray.h"
#include	"vector.h"
#include	<math.h>

struct	color	trace();
struct	intersect	intersect();
struct	ray	sample_ray();

/*
 * Shadow check.  Check if the ray from the intersection point to the
 * light, intersection any objects inbetween.  If it does, we have a
 * shadow.
 * One improvement that can be made is to check the transparencies of all
 * the intersecting objects and make the shadow have a different intensity
 * depending on the transparency of the objects
 */
int	shadow(r)
struct	ray	r;
{
	struct	intersect	i;
	struct	ray	r2;
/* to have a shadow, the light ray must intersect an object */
/* first sample the ray around the shadow ray */
	r2 = sample_ray(r, r.theta);
	i = intersect(r2);

	if( i.obj == NULL )
		return FALSE;
	else
		return (r.obj != i.obj);
}

/*
 * calculate the reflection vector.
 * D. Rogers: "Procedural elements for computer graphics". 5-12. p. 367
 */
struct	vector	reflect(n,v1)
struct	vector	n, v1;
{
	struct	vector	v2;

	v2 = vsub( v1, svproduct( 2 * vdot(n, v1), n) );


	return v2;
}

/*
 * calculate the refracted vector.
 * D. Rogers: "Procedural elements for computer graphics". 5-12. p. 367
 */
struct	vector	refract(n, v1, index)
struct	vector	n, v1;
double	index;
{
	double	p, t;
	struct	vector	v2;

	v2.x = 0.; v2.y = 0.; v2.z = 0.;

	p = vdot(n, v1);
	if(p < 0) {
		t = 1 - ( 1 - p*p ) / ( index*index );
		if( t <= 0 )
			return v2;
		t = -p/index - sqrt(t);
	} else {
		index = 1 / index;
		t = 1 - ( 1 - p*p ) / ( index*index );
		if( t <= 0 )
			return v2;
		t = -p/index + sqrt(t);
	}

	v2 = vadd( svproduct(1/index, v1), svproduct(t, n) );

	return v2;
}

/*
 * the actual shading function.  Recursively calls trace()
 */
struct	color	shade(i, r, n)
struct	intersect	i;
struct	ray	r;
int	n;		/* recursion level */
{
	struct	color	col;
	struct	vector	pt;
	struct	ray	shadow_ray;
	struct	vector	ldir;
	int	shad;
	double	ldot;
	double	spec;
	struct	vector	rr;	/* reflected light direction */
	struct	ray	reflected, refracted;	/* refl, refr directions */
	struct	color	c;

/* if the recursion level has been exceeded, return peacefully*/
	if(n > MAXLEVEL) {
		col.r = col.g = col.b = 0.;
		return col;
	}

/* initially get the ambient color */
	col = i.obj->ambient;

/* first calculate the intersection point */
	pt = vadd( r.pos, svproduct(i.t, r.dir) );

/* for the light source first get the vector from it to the intersection */
	ldir = norm( vsub(light.org, pt) );
/* then calc the dot product between the light direction and the normal   */
/* negative because the light direction is reverse (comes from the light) */
	ldot = vdot(i.n, ldir);
/* and find if that shadow ray is stopped by an object */
	shadow_ray.pos = pt;
	shadow_ray.dir = ldir;
	shadow_ray.obj = i.obj;
/* the following is the spreading angle of the ray */
	shadow_ray.theta = light.angle;
	shad = shadow(shadow_ray);
	if( (ldot>0) && !shad ) {
/* statistics */
		shadowline++;
/* add some diffuse color. */
		col.r += ldot * i.obj->diffuse.r;
		col.g += ldot * i.obj->diffuse.g;
		col.b += ldot * i.obj->diffuse.b;
/* now calc the specular color */
/* first calculate the reflected light vector */
		rr = reflect(i.n, ldir);
/* then take the dot of the reflected ray with the viewing direction */
/* that is the specular component. The minus is there for obvious reasons */
		spec = vdot(rr, r.dir);
		spec = (spec < 0) ? 0 : spec;
/* remember the specular width factor?  We use it here! */
		spec = pow(spec, i.obj->width);
		col.r += spec * i.obj->specular.r;
		col.g += spec * i.obj->specular.g;
		col.b += spec * i.obj->specular.b;
	}

/* setup the reflected ray */
	reflected.pos = pt;
	reflected.dir = reflect(i.n, r.dir);
	reflected.obj = i.obj;
	reflected.theta = i.obj->refl_diffuse;
/* calculate the reflection */
	reflectline++;
	c = trace(reflected, n+1);
	col.r += c.r * i.obj->refl_color.r * i.obj->reflection;
	col.g += c.g * i.obj->refl_color.g * i.obj->reflection;
	col.b += c.b * i.obj->refl_color.b * i.obj->reflection;
/* now setup the refracted ray */
	refracted.pos = pt;
	refracted.dir = refract(i.n, r.dir, i.obj->index);
	refracted.obj = i.obj;
	refracted.theta = i.obj->refr_diffuse;
	if( (refracted.dir.x == 0.) &&
	    (refracted.dir.y == 0.) &&
	    (refracted.dir.z == 0.) )
		return col;
/* and calculate the reflection */
	refractline++;
	c = trace(refracted, n+1);
	col.r += c.r * i.obj->refr_color.r * i.obj->refraction;
	col.g += c.g * i.obj->refr_color.g * i.obj->refraction;
	col.b += c.b * i.obj->refr_color.b * i.obj->refraction;

	return col;
}
