/******************************************************************************
 * Copyright 1986-2002 by mental images GmbH & Co.KG, Fasanenstr. 81, D-10623
 * Berlin, Germany. All rights reserved.
 ******************************************************************************
 * Author:	thomas/jasonb
 * Created:	22.6.99
 * Module:	shader
 * Purpose:	new soft_light shader
 *
 * Exports:
 *	soft_light			point, spot, and infinite light source
 *	soft_light_photons		global illumination & caustics emitter
 *
 *
 * Description:
 * This is a new incarnation of the soft_light shader, rewritten by Softimage
 * for Softimage|3D 3.9 a.k.a. Tasmania. It's not compatible to the previous
 * version in any way. Note that the mode parameter got a totally new meaning.
 *****************************************************************************/

#include <math.h>
#include <shader.h>
#include <mi_softshade.h>

/*****************************************************************************
 **									    **
 **			     structures etc				    **
 **									    **
 *****************************************************************************/
typedef struct {
	miVector	dir;
	miVector	org;
	miInteger	lighttype;
	miScalar	falloff;
	miColor 	energy;
	miScalar	cone;
	miScalar	cone_rad;
	miScalar	spread;
} soft_light_transfo;

/*****************************************************************************
 **									    **
 **			     macros etc 				    **
 **									    **
 *****************************************************************************/

#define BLACK(c)	((c).r < 1e-6 && (c).g < 1e-6 && (c).b < 1e-6)
#define RAD2DEG(x)	((x) * 57.2957795)
#define DEG2RAD(x)	((x) * 0.017453292)

#define siEPS		1e-4

#ifdef DEBUG
#define MI_QUERY(A,B,C,D)	\
	{if (!mi_query(A,B,C,D)) mi_info("FAILED LINE: %i\n", __LINE__);}

#define VERIFY_TAG(x,y) 	\
	{if (mi_db_type(x) != y) mi_info("FAILED LINE: %i\n", __LINE__);}
#else
#define MI_QUERY(A,B,C,D) mi_query(A,B,C,D)
#define VERIFY_TAG(x,y)
#endif					/* DEBUG */


/*****************************************************************************
 **									    **
 **			     support functions				    **
 **									    **
 *****************************************************************************/

void soft_light_transform(
	miState 	   *state,
	struct soft_light  *paras,
	soft_light_transfo *transform)
{
	miScalar	cone;
	miScalar	spread;
	miTag		realLight;
	miMatrix	*transfoMatrix;

	/* need to calculate our direction properly using */
	/* the instance coordinate transforms */
	/* retrieve the tag of the real light */
	VERIFY_TAG(state->light_instance, miSCENE_INSTANCE);
	MI_QUERY(miQ_INST_ITEM, NULL, state->light_instance, &realLight);
	VERIFY_TAG(realLight, miSCENE_LIGHT);
	MI_QUERY(miQ_LIGHT_TYPE,      NULL, realLight, &transform->lighttype);
	MI_QUERY(miQ_LIGHT_ORIGIN,    NULL, realLight, &transform->org);
	MI_QUERY(miQ_LIGHT_DIRECTION, NULL, realLight, &transform->dir);
	MI_QUERY(miQ_LIGHT_ENERGY,    NULL, realLight, &transform->energy);
	MI_QUERY(miQ_LIGHT_EXPONENT,  NULL, realLight, &transform->falloff);
	MI_QUERY(miQ_LIGHT_SPREAD,    NULL, realLight, &transform->cone);

	if (transform->dir.x == 0.0f &&
	    transform->dir.y == 0.0f &&
	    transform->dir.z == 0.0f) {
		transform->dir.x =
		transform->dir.y = 0.0;
		transform->dir.z = -1.0;
	}
	/* get the transformation matrix for the light */
	MI_QUERY(miQ_INST_LOCAL_TO_GLOBAL,
				NULL, state->light_instance, &transfoMatrix);

	/* transform the origin and direction to internal space */
	mi_vector_transform(&transform->dir, &transform->dir, *transfoMatrix);
	mi_vector_from_world(state, &transform->dir, &transform->dir);
	mi_vector_normalize(&transform->dir);
	mi_point_transform(&transform->org, &transform->org, *transfoMatrix);
	mi_point_from_world(state, &transform->org, &transform->org);

	/* Process SPREAD angle with CONE angle. */
	transform->cone_rad = acos( transform->cone );
	cone = RAD2DEG(transform->cone_rad);
	spread = cone - *mi_eval_scalar(&paras->spread);
	if (spread < 0.0f)
		spread = 0.0f;
	transform->spread = cos(DEG2RAD(spread));

	/* Check exponent. */
	if (transform->falloff < siEPS)
		transform->falloff = siEPS;
}

/*****************************************************************************
 **									    **
 **			  initialization & version			    **
 **									    **
 *****************************************************************************/

DLLEXPORT int soft_light_version(void) {
	return(2);
}

DLLEXPORT void soft_light_init(
	miState 	  *state,
	struct soft_light *paras,
	miBoolean	  *inst_init_req)
{
	if (!paras) {
		/* request instance initialization */
		*inst_init_req = miTRUE;
	} else {
		/* using this to get the instance user data pointer */
		void	**userptr;

		mi_query(miQ_FUNC_USERPTR, state, 0, &userptr);
		*userptr = mi_mem_allocate(sizeof(soft_light_transfo));
		soft_light_transform(state, paras,
					(soft_light_transfo *)*userptr);
	}
}

DLLEXPORT void	soft_light_exit(
	miState 	  *state,
	struct soft_light *paras)
{
	if (!paras) {				/* main shader exit */
		/* nothing to do right now */
	} else {
		/* release our instance data */
		void	**userptr;

		mi_query(miQ_FUNC_USERPTR, state, 0, &userptr);
		mi_mem_release(*userptr);
		*userptr = NULL;
	}
}


DLLEXPORT int soft_light_photons_version(void) {
	return(2);
}

DLLEXPORT void soft_light_photons_init(
	miState 	  *state,
	struct soft_light *paras,
	miBoolean	  *inst_init_req)
{
	soft_light_init(state, paras, inst_init_req);
}

DLLEXPORT void	soft_light_photons_exit(
	miState 	  *state,
	struct soft_light *paras)
{
	soft_light_exit(state, paras);
}


/*****************************************************************************
 **									    **
 **			      Photon emitter				    **
 **									    **
 *****************************************************************************/

DLLEXPORT  miBoolean soft_light_photons(
	register miColor	    *result,
	register miState	    *state,
	register struct soft_light  *paras)
{
	soft_light_transfo  *l_cache;
	miScalar	    intensity;
	void		    **userptr;

	/*---- Process non-global illumination. */
	if ( !miRAY_PHOTON(state->type) )
		return( soft_light(result,state,paras) );

	/*---- Get the handle to our instance data*/
	mi_query(miQ_FUNC_USERPTR,state,0, &userptr );
	if ( *userptr == NULL )
		return( miFALSE );
	l_cache = (soft_light_transfo*) *userptr;

	/*---- Set photon origin. */
	state->org = l_cache->org;

	/*---- SPOT ----*/
	if ( l_cache->lighttype == 2 ) {
		miScalar   l_phi;     /* Current Angle with Z axis. [0...PI] */
		miScalar   l_phi2;    /* New	 Angle with Z axis. [0...PI] */
		miVector   l_vA;      /* Prep axis to Z and ray.	     */
		miMatrix   l_mtx;     /* Rotation matrix.		     */

		/*---- Find a ray on the unit sphere. */
		mi_scattering_dir_diffuse( &state->dir, state );

		/*
		 * Given an omni-directional ray, fold it to a cone
		 * like an umbrella. In this case, the angle between
		 * the local Z axis and the ray direction is in the
		 * range [0...PI] and we want to 'fold' it in the
		 * range [0...cone] instead. So it's a matter of
		 * computing the rotation angle to apply to the ray
		 * direction around the axis perpendicular to the
		 * plane formed by the ray and the local Z axis.
		 */
		mi_vector_prod( &l_vA, &state->dir, &l_cache->dir );
		l_phi = (miScalar) acos( mi_vector_dot(&state->dir, &l_cache->dir) );
		l_phi2 = l_phi / M_PI * l_cache->cone_rad;
		mi_matrix_rotate_axis( l_mtx, &l_vA, l_phi - l_phi2 );
		mi_vector_transform( &state->dir, &state->dir, l_mtx );
		mi_vector_normalize( &state->dir );
	}
	/*---- POINT ----*/
	else if (  l_cache->lighttype == 0 ) {
		/*---- Find a ray on the unit sphere. */
	        mi_scattering_dir_diffuse( &state->dir, state );
	}
	/*---- DIRECTIONAL ----*/
	else
		return( miFALSE );

	/*---- Where does the energy comes from ? Shader or Light Primitive ? */
	if ( paras->bGlbUseColor )
		*result   = *mi_eval_color(&paras->color);
	else
		*result   = l_cache->energy;

	/*---- Scale the energy intensity. */
	intensity = *mi_eval_scalar(&paras->glb_intensity);
	result->r *= intensity;
	result->g *= intensity;
	result->b *= intensity;
	result->a *= intensity;

	/*---- Store the photon energy. */
	mi_photon_light( result, state );
	return( miTRUE );
}

/*****************************************************************************
 **									    **
 **				light shader				    **
 **									    **
 *****************************************************************************/

DLLEXPORT miBoolean soft_light(
	register miColor	   *result,
	register miState	   *state,
	register struct soft_light *paras)
{
	register double 	d, t;
	soft_light_transfo	*transform;
	miScalar		cone;
	miScalar		factor;
	miScalar		intensity;
	void			**userptr;

	/*---- Process global illumination. */
	if ( miRAY_PHOTON(state->type) )
		return( soft_light_photons(result,state,paras) );

	/*---- Get the handle to our instance data*/
	mi_query(miQ_FUNC_USERPTR, state, 0, &userptr);
	if (*userptr == NULL)
		return(miTRUE);
	transform = (soft_light_transfo *) * userptr;
	*result   = *mi_eval_color(&paras->color);
	intensity = *mi_eval_scalar(&paras->intensity);
	result->r *= intensity;
	result->g *= intensity;
	result->b *= intensity;
	result->a *= intensity;

	/*---- Filter out unwanted ray types (such as visible light). */
	if (state->type != miRAY_LIGHT)
		return(miTRUE);

	/*---- Angle attenuation: SPOT ----*/
	if (transform->lighttype == 2) {	/* angle attenuation */
		d = mi_vector_dot(&state->dir, &transform->dir);

		/*---- The outer cone comes from miLight. */
		if (d < transform->cone)
			return(miFALSE);

		/*---- The inner cone comes from the shader params. */
		cone = transform->cone;
		if (d < transform->spread &&
		    fabs(transform->spread - cone) > siEPS) {
			t = (d - cone) / (transform->spread - cone);
			result->r *= t;
			result->g *= t;
			result->b *= t;
		}
	}
	/*---- Distance Attenuation: POINT or SPOT (i.e. not DIRECTIONAL) ---*/
	if (transform->lighttype != 1 && *mi_eval_boolean(&paras->atten)) {
		miScalar	stop, start;

		start = *mi_eval_scalar(&paras->start);
		stop  = *mi_eval_scalar(&paras->stop);
		if (state->dist >= stop)
			return(miFALSE);

		else if (state->dist > start) {
			/*---- Falloff Mode. NOTE thomas: this +1 is weird. */
			switch (*mi_eval_integer(&paras->mode)) {
			  case 0:	/* Classical SI3D linear falloff */
				t = fabs(stop - start) < siEPS ? 1.0 :
				    1 - (state->dist - start) / (stop - start);
				break;
			  default:	/* Natural 1/R^(n) falloff (sort of) */
				t = pow(state->dist - start + 1.0,
							-transform->falloff);
				break;
			}
			result->r *= t;
			result->g *= t;
			result->b *= t;
		}
	}
	factor = *mi_eval_scalar(&paras->factor);

	if ((*mi_eval_boolean(&paras->shadow)) && factor < 1) { /* shadows: */
		miColor filter;

		filter.r = filter.g = filter.b = filter.a = 1;
		if (!mi_trace_shadow(&filter, state) || BLACK(filter)) {
			result->r *= factor;
			result->g *= factor;
			result->b *= factor;
			if (factor == 0)
				return(miFALSE);
		} else {			/* transparent */
			double	omf = 1 - factor;

			result->r *= factor + omf * filter.r;
			result->g *= factor + omf * filter.g;
			result->b *= factor + omf * filter.b;
		}
	}
	return(miTRUE);
}
