/******************************************************************************
 * Copyright 1986-2002 by mental images GmbH & Co.KG, Fasanenstr. 81, D-10623
 * Berlin, Germany. All rights reserved.
 ******************************************************************************
 * Created:	4.11.96
 * Module:	shader
 * Purpose:	Physically based shading of dielectric materials
 *
 * Exports:
 *	dielectric_material_init		material shader initialization
 *	dielectric_material_exit		material shader finalization
 *	dielectric_material			material shader
 *	dielectric_material_version		material shader version
 *	dielectric_material_photon_init		photon shader initialization
 *	dielectric_material_photon_exit		photon shader finalization
 *	dielectric_material_photon		photon shader
 *	dielectric_material_photon_version	photon shader version
 *
 * History:
 *	14.01.98: removed install function, this is now a library
 *	16.01.98: use mi_choose_scatter_type() (a must now)
 *	21.01.98: re-added the *_photon_init() functions
 *	26.05.98: changed computation of alpha.	Added assertions.
 *	02.10.98: use mi_eval() & co.
 *	15.02.99: fixed a bug: photon trace level was compared to
 *		  max ray trace depth in the photon shader.
 *	06.07.00: major cleanup
 *
 * Description:
 * - This file handles shading of dielectric materials such as glass, liquids
 *   etc. It uses Fresnel's formulas for computing the specular reflection and
 *   transmission which depends on the indices of refraction and the angle of
 *   the incoming ray.
 * - The colour of the material is computed using Beer's law which is based on
 *   absorption coefficients colours the light based on the distance traversed
 *   through the material.
 * - If called as a shadow shader, it returns miFALSE (i.e. completely opaque).
 *   This is because we cannot compute shadows along bent rays. Instead, this
 *   type of illumination is handled by the photon map.
 *****************************************************************************/

#include <stddef.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <shader.h>
#include "dielecshade.h"

#define miEPS 0.0001

#ifdef DEBUG
#define miASSERT assert
#else
#define miASSERT(x)
#endif

static miColor black = {0, 0, 0, 0};


miBoolean dielectric_material_init(
	miState			   *state,	/* render state */
	struct dielectric_material *paras,	/* parameters */
	miBoolean		   *inst_req)	/* shader inst requests */
{
	struct material_coef	*mc, **mcp;
	miColor			*col;
	float			ior, ior_out;

	if (!paras) {
		*inst_req = miTRUE;
		return(miTRUE);
	}
	col	=  mi_eval_color (&paras->col);
	ior	= *mi_eval_scalar(&paras->ior);
	ior_out	= *mi_eval_scalar(&paras->ior_out);

	mc = (struct material_coef *)
			 	mi_mem_allocate(sizeof(struct material_coef));
	if (col->r <= 0 || col->r > 1 ||
	    col->g <= 0 || col->g > 1 ||
	    col->b <= 0 || col->b > 1) {
		mi_warning("illegal absorption coefficients for dielectric "
			   "material (not [0,1]^3, using 1,1,1)");
		mc->absorp = black;
	} else {
		mc->absorp.r = -log(col->r);
		mc->absorp.g = -log(col->g);
		mc->absorp.b = -log(col->b);
	}
	if (ior <= 0) {
		mi_warning("dielectric material cannot use zero or negative "
			   "index of refraction %f (using 1.0)", ior);
		mc->ior = 1;
	} else
		mc->ior = ior;

	if (col->r <= 0 || col->r > 1 ||
	    col->g <= 0 || col->g > 1 ||
	    col->b <= 0 || col->b > 1) {
		mi_warning("illegal absorption coefficients for dielectric "
			   "material (not [0,1], ignoring)");
		mc->is_interface = 0;
		mc->ior_out	 = 1;

	} else if (ior_out < 0) {
		mi_warning("cannot use negative index of refraction %f for "
			   "dielectric material (ignoring)", ior_out);
		mc->is_interface = 0;
		mc->ior_out	 = 1;

	} else if (ior_out == 0) {
		mc->is_interface = 0;
		mc->ior_out	 = 1;
	} else {
		miColor *col_out = mi_eval_color(&paras->col_out);
		mc->is_interface = 1;
		mc->ior_out	 = ior_out;
		mc->absorp_out.r = -log(col_out->r);
		mc->absorp_out.g = -log(col_out->g);
		mc->absorp_out.b = -log(col_out->b);
	}
	mc->pcoef	   = *mi_eval_scalar (&paras->pcoef);
	mc->ignore_normals = *mi_eval_boolean(&paras->ignore_normals);

	mi_query(miQ_FUNC_USERPTR, state, 0, &mcp);
	*mcp = mc;

	if (state->options->max_samples >= 1) {
		mc->russian_level = 5 - state->options->max_samples;
		if (mc->russian_level <= 1)
			mc->russian_level = 1;
	} else
		mc->russian_level = 4;
	return(miTRUE);
}


miBoolean dielectric_material_photon_init(
	miState			   *state,	/* render state */
	struct dielectric_material *paras,	/* parameters */
	miBoolean		   *inst_req)	/* shader inst requests */
{
	return(dielectric_material_init(state, paras, inst_req));
}


miBoolean dielectric_material_exit(
	miState			   *state,	/* render state */
	struct dielectric_material *paras)	/* parameters */
{
	struct material_coef	   **mcp;

	if (paras) {
		mi_query(miQ_FUNC_USERPTR, state, 0, &mcp);
		mi_mem_release(*mcp);
		*mcp = NULL;
	}
	return(miTRUE);
}


miBoolean dielectric_material_photon_exit(
	miState			   *state,	/* render state */
	struct dielectric_material *paras)	/* parameters */
{
	return(dielectric_material_exit(state, paras));
}


/*
 * Fresnel_reflection: Fresnel reflection for dielectric surfaces with support
 * for interfaces
 */

static float fresnel_reflection(
	miState			*state,
	miVector		*ref_dir,
	const miCBoolean	enter_surf)
{
	struct material_coef	*mc, **mcp;
	float			Ci;
	float			cos1, cos2;
	float			beta, alpha;
	float			nit, ni, nt;
	float			F1, F2, Fr;

	miASSERT(state->shader);
	mi_query(miQ_FUNC_USERPTR, state, 0, &mcp);
	mc = *mcp;

	if (enter_surf) {			/* entering the surface */
		if (mc->is_interface)
			mc->ior_out = state->ior_in; /* from locate_volume() */
		ni = mc->ior_out;
		nt = mc->ior;
	} else {
		if (mc->is_interface)
			mc->ior_out = state->ior;    /* from locate_volume() */
		ni = mc->ior;
		nt = mc->ior_out;
	}
	state->ior    = nt;
	state->ior_in = ni;
	Ci = -state->dot_nd;
	if (fabs(Ci) < miEPS) {
		*ref_dir = state->dir;
		return(0);
	}
	nit  = ni / nt;
	beta = 1 + nit * nit * (Ci * Ci - 1);
	if (beta > 0) {
		beta  = Ci * nit - sqrt(beta);
		alpha = nit;
		ref_dir->x = state->dir.x * alpha + state->normal.x * beta;
		ref_dir->y = state->dir.y * alpha + state->normal.y * beta;
		ref_dir->z = state->dir.z * alpha + state->normal.z * beta;
		mi_vector_normalize(ref_dir);
		cos2 = mi_vector_dot(ref_dir,&state->normal);
	} else
		cos2 = 0;
	cos1 = Ci;
	miASSERT(nt * cos1 - ni * cos2 != 0);
	miASSERT(ni * cos1 - nt * cos2 != 0);
	F1 = (nt * cos1 + ni * cos2) / (nt * cos1 - ni * cos2);
	F2 = (ni * cos1 + nt * cos2) / (ni * cos1 - nt * cos2);
	Fr = 0.5 * (F1 * F1 + F2 * F2);
	miASSERT(Fr <= 1);
	return(Fr);
}


static miCBoolean locate_volume(
	miState			*state,
	const miCBoolean	ignore_normals)
{
	miState			*s, *s_in = NULL;
	miCBoolean		enter_surf;
	int			num = 0;

	enter_surf = !state->inv_normal;
	for (s=state; s; s=s->parent)			/* history? */
		if ((s->type == miRAY_TRANSPARENT	   ||
		     s->type == miRAY_REFRACT		   ||
		     s->type == miPHOTON_TRANSMIT_SPECULAR ||
		     s->type == miPHOTON_TRANSMIT_GLOSSY   ||
		     s->type == miPHOTON_TRANSPARENT	   ||
		     s->type == miPHOTON_TRANSMIT_DIFFUSE) &&
		     s->parent && s->parent->shader == state->shader) {
			num++;
			if (!s_in)
				s_in = s->parent;
		}
	if (ignore_normals)
		enter_surf = !(num & 1);

	if (enter_surf) {			/* entering */
		if (!state->refraction_volume)
			state->refraction_volume = state->volume;
		state->ior_in = state->parent && state->parent->ior != 0 ?
							state->parent->ior : 1;
	} else {
		state->refraction_volume = s_in ? s_in->volume
						: state->camera->volume;
		state->ior = s_in && s_in->ior_in != 0 ? s_in->ior_in : 1;
	}
	return(enter_surf);
}


/*****************************************************************************
 *				photon shader				    **
 *****************************************************************************/

int dielectric_material_photon_version(void) {return(1);}

miBoolean dielectric_material_photon(
	register miColor	   *energy,	/* propagated energy */
	register miState	   *state,	/* active state */
	struct dielectric_material *paras)	/* incoming parameters */
{
	struct material_coef	**mcp;
	float			specular;
	miVector		dir, ref_dir;
	miColor			color;
	float			dist;
	short			enter_surf;
	struct material_coef	*mc;
	miBoolean		do_reflect=miTRUE, do_refract=miTRUE;
	miColor			rspec, tspec;
	miRay_type		rt;

	if (!miRAY_PHOTON(state->type))
		return(dielectric_material(energy, state, paras));

	mi_query(miQ_FUNC_USERPTR, state, 0, &mcp);
	if (!(mc = *mcp)) {
		mi_error("dielectric_material_photon_init not called!");
		return(miFALSE);
	}
	enter_surf = locate_volume(state, mc->ignore_normals);
	specular   = fresnel_reflection(state, &ref_dir, enter_surf);

	/*
	 * use Russian roulette to determine whether we reflect or transmit the
	 * photon
	 */
	rspec.r = rspec.g = rspec.b = specular;
	tspec.r = tspec.g = tspec.b = 1-specular;

	rt = mi_choose_simple_scatter_type(state, NULL, &rspec, NULL, &tspec);
	if (rt == miPHOTON_REFLECT_SPECULAR)
		do_refract = miFALSE;

	else if (rt == miPHOTON_TRANSMIT_SPECULAR)
		do_reflect = miFALSE;
	else
		do_reflect =
		do_refract = miFALSE;

	color = *energy;
	if (do_reflect) {			/* trace reflected photon */
		mi_reflection_dir(&dir,state);
		dist = state->dist;
		if (!enter_surf) { /* !entering surface = internal reflection*/
			color.r *= exp(-mc->absorp.r * dist) * rspec.r;
			color.g *= exp(-mc->absorp.g * dist) * rspec.g;
			color.b *= exp(-mc->absorp.b * dist) * rspec.b;
		} else if (mc->is_interface) {
			color.r *= exp(-mc->absorp_out.r * dist) * rspec.r;
			color.g *= exp(-mc->absorp_out.g * dist) * rspec.g;
			color.b *= exp(-mc->absorp_out.b * dist) * rspec.b;
		}
		mi_photon_reflection_specular(&color, state, &dir);

	} else if (do_refract) {		/* trace refracted photon */
		dist = state->dist;
		if (!enter_surf) {		/* entering surface */
			color.r *= exp(-mc->absorp.r * dist) * tspec.r;
			color.g *= exp(-mc->absorp.g * dist) * tspec.g;
			color.b *= exp(-mc->absorp.b * dist) * tspec.b;
		} else if (mc->is_interface) {
			color.r *= exp(-mc->absorp_out.r * dist) * tspec.r;
			color.g *= exp(-mc->absorp_out.g * dist) * tspec.g;
			color.b *= exp(-mc->absorp_out.b * dist) * tspec.b;
		}
		mi_photon_transmission_specular(&color, state, &ref_dir);
	}
	return(miTRUE);
}


/*****************************************************************************
 *				material shader				    **
 *****************************************************************************/

int dielectric_material_version(void) {return(1);}

miBoolean dielectric_material(
	miColor			   *res,	/* returned colour */
	miState			   *state,	/* render state */
	struct dielectric_material *paras)	/* incoming parameters */
{
	struct material_coef	**mcp;
	float			specular;	/* specular reflection */
	miVector		dir, ref_dir;
	miColor			color;
	float			dist, scale;
	short			enter_surf;
	miTag			*lgt;
	int			i, n_lights;
	miBoolean		do_reflect = miTRUE, do_refract = miTRUE;
	float			rr_scale = 1;
	struct material_coef	*mc;

	/* Material shader called as a photon shader? */
	if (miRAY_PHOTON(state->type))
		return(dielectric_material_photon(res, state, paras));

	*res = black;

	/* Material shader called as a shadow shader? */
	if (state->type == miRAY_SHADOW || state->type == miRAY_LIGHT)
		return(miFALSE);	/* no transparent shadows here */

	/*
	 * Optimization: since the reflection/transmission ray would be
	 * rejected anyways (by mi_trace_reflection/refraction), we can stop
	 * immediately. (per 08.02.99: This is not quite correct, since
	 * reflection could have been chosen even though the refraction
	 * depth has been reached, and vice versa.)
	 */
	if (state->reflection_level >= state->options->reflection_depth ||
	    state->refraction_level >= state->options->refraction_depth)
		return(miFALSE);

	mi_query(miQ_FUNC_USERPTR, state, 0, &mcp);
	if (!(mc = *mcp)) {
		mi_error("dielectric_material_init not called!");
		return(miFALSE);
	}
	enter_surf = locate_volume(state, mc->ignore_normals);
	specular   = fresnel_reflection(state, &ref_dir, enter_surf);

	/*
	 * add fake contribution from lightsources - L(S|D)
	 * We'll just do a normalized phong highlight
	 */
	n_lights = *mi_eval_integer(&paras->n_lights);
	if (n_lights > 0) { /* use local list of light sources? */
		int i_lights = *mi_eval_integer(&paras->i_lights);
		lgt = mi_eval_tag(paras->lights) + i_lights;
	} else
		n_lights = mi_global_lights_info(&lgt);

	if (mc->pcoef > 0) {
		for (i=0; i < n_lights; i++) {
			miColor		csum;
			miInteger	smp = 0;
			miScalar	dot_nl;
			miScalar	ns;

			csum.r = csum.g = csum.b = 0;
			while (mi_sample_light(&color, &dir, &dot_nl, state,
							lgt[i], &smp)) {
				ns = specular * (mc->pcoef+1) * (1/(2*M_PI)) *
				     mi_phong_specular(mc->pcoef, state, &dir);
				csum.r += ns * color.r;
				csum.g += ns * color.g;
				csum.b += ns * color.b;
			}
			res->r += csum.r / smp;
			res->g += csum.g / smp;
			res->b += csum.b / smp;
		}
	}

	/*
	 * prune ray-tree using russian roulette
	 */
	if (state->reflection_level + state->refraction_level >=
							mc->russian_level) {
		miColor		rspec, tspec;
		miRay_type	rt;

		rspec.r = rspec.g = rspec.b = specular;
		tspec.r = tspec.g = tspec.b = 1-specular;
		rt = mi_choose_simple_scatter_type(state, NULL, &rspec,
							  NULL, &tspec);
		/*
		 * mi_choose_simple_scatter_type scales
		 * r,g,b by the same factor
		 */
		if (rt == miPHOTON_REFLECT_SPECULAR) {
			do_refract = miFALSE;
			rr_scale   = rspec.r / specular;

		} else if (rt == miPHOTON_TRANSMIT_SPECULAR) {
			do_reflect = miFALSE;
			rr_scale   = tspec.r / (1-specular);
		} else
			do_reflect = do_refract = miFALSE;
	}

	/*
	 * trace reflected ray
	 */
	res->a = specular;
	if (do_reflect && specular > miEPS) {
		mi_reflection_dir(&dir,state);
		if (mi_trace_reflection(&color, state, &dir) ||
		    mi_trace_environment(&color, state, &dir)) {
			dist = 0;
			if (state->child)
				dist = state->child->dist;
			scale = specular*rr_scale;
			if (!enter_surf) {		/* !entering surface */
				color.r *= (scale * exp(-mc->absorp.r*dist));
				color.g *= (scale * exp(-mc->absorp.g*dist));
				color.b *= (scale * exp(-mc->absorp.b*dist));

			} else if (mc->is_interface) {
				color.r *= (scale*exp(-mc->absorp_out.r*dist));
				color.g *= (scale*exp(-mc->absorp_out.g*dist));
				color.b *= (scale*exp(-mc->absorp_out.b*dist));
			} else {
				color.r *= scale;
				color.g *= scale;
				color.b *= scale;
			}
			res->r += color.r;
			res->g += color.g;
			res->b += color.b;
		}
	}

	/*
	 * trace refracted ray
	 */
	if (do_refract && specular < (1-miEPS)) {
		if (enter_surf)			/* We are entering the object*/
			scale = (1-specular) * mc->ior_out * mc->ior_out /
						(mc->ior * mc->ior) * rr_scale;
		else				/* We are leaving the surface*/
			scale = (1-specular) * mc->ior * mc->ior /
						(mc->ior_out * mc->ior_out) *
						rr_scale;

		if (mi_trace_refraction(&color, state, &ref_dir) ||
		    mi_trace_environment(&color, state, &ref_dir)) {
			dist = 0;
			if (state->child)
				dist = state->child->dist;

			if (enter_surf) {		/* entering surface */
				color.r *= (scale * exp(-mc->absorp.r*dist));
				color.g *= (scale * exp(-mc->absorp.g*dist));
				color.b *= (scale * exp(-mc->absorp.b*dist));

			} else if (mc->is_interface) {
				color.r *= (scale*exp(-mc->absorp_out.r*dist));
				color.g *= (scale*exp(-mc->absorp_out.g*dist));
				color.b *= (scale*exp(-mc->absorp_out.b*dist));
			} else {
				color.r *= scale;
				color.g *= scale;
				color.b *= scale;
			}
			res->r += color.r;
			res->g += color.g;
			res->b += color.b;
			res->a += (1-specular)*color.a;
		}
	}
	return(miTRUE);
}
