/******************************************************************************
 * Copyright 1986-2002 by mental images GmbH & Co.KG, Fasanenstr. 81, D-10623
 * Berlin, Germany. All rights reserved.
 ******************************************************************************
 * Created:	13.2.98
 * Module:	baseshader
 * Purpose:	base shaders for Phenomenon writers
 *
 * Exports:
 *	mib_light_point
 *	mib_light_point_version
 *	mib_light_spot
 *	mib_light_spot_version
 *	mib_light_infinite
 *	mib_light_infinite_version
 *
 * History:
 *
 * Description:
 *****************************************************************************/

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

#define EPS	 1e-4
#define BLACK(C) ((C).r==0 && (C).g==0 && (C).b==0)


/*
 * Point light source. Must have an origin in the input file. Ignores the
 * direction. If this light shader is called because the light source is a
 * visible area light source and was hit, the result is simply paras->color
 * (the color of the light source).
 */

struct mib_light_point {
	miColor		color;		/* color of light source */
	miBoolean	shadow;		/* light casts shadows */
	miScalar	factor;		/* makes opaque objects transparent */
	miBoolean	atten;		/* distance attenuation */
	miScalar	start, stop;	/* if atten, distance range */
};

DLLEXPORT int mib_light_point_version(void) {return(1);}

DLLEXPORT miBoolean mib_light_point(
	register miColor	*result,
	register miState	*state,
	register struct mib_light_point *paras)
{
	register miScalar	d, t, start, stop;

	*result = *mi_eval_color(&paras->color);
	if (state->type != miRAY_LIGHT)			/* visible area light*/
		return(miTRUE);
	if (*mi_eval_scalar(&paras->atten)) {			/* dist atten*/
		stop = *mi_eval_scalar(&paras->stop);
		if (state->dist >= stop)
			return(miFALSE);

		start = *mi_eval_scalar(&paras->start);
		if (state->dist > start && fabs(stop - start) > EPS) {
			t = 1 - (state->dist - start) / (stop - start);
			result->r *= t;
			result->g *= t;
			result->b *= t;
		}
	}
	if (*mi_eval_boolean(&paras->shadow)) {			/* shadows: */
		d = *mi_eval_scalar(&paras->factor);
		if (d < 1) {
			miColor filter;
			filter.r = filter.g = filter.b = filter.a = 1;
								/* opaque */
			if (!mi_trace_shadow(&filter,state) || BLACK(filter)) {
				result->r *= d;
				result->g *= d;
				result->b *= d;
				if (d == 0)
					return(miFALSE);
			} else {				/* transparnt*/
				double omf = 1 - d;
				result->r *= d + omf * filter.r;
				result->g *= d + omf * filter.g;
				result->b *= d + omf * filter.b;
			}
		}
	}
	return(miTRUE);
}


/*
 * Spot light source. Must have an origin and direction in the input file.
 * Takes a variable number of parameters. The first is a boolean which says
 * whether the light casts a shadow or not. The second is the shadow factor.
 */

struct mib_light_spot {
	miColor		color;		/* color of light source */
	miBoolean	shadow;		/* light casts shadows */
	miScalar	factor;		/* makes opaque objects transparent */
	miBoolean	atten;		/* distance attenuation */
	miScalar	start, stop;	/* if atten, distance range */
	miScalar	cone;		/* inner solid cone */
};

DLLEXPORT int mib_light_spot_version(void) {return(1);}

DLLEXPORT miBoolean mib_light_spot(
	register miColor	*result,
	register miState	*state,
	register struct mib_light_spot *paras)
{
	register miScalar	d, t, start, stop, cone;
	miScalar		spread;
	miVector		ldir, dir;
	miTag			ltag;

	*result = *mi_eval_color(&paras->color);
	if (state->type != miRAY_LIGHT)			/* visible area light*/
		return(miTRUE);
								/*angle atten*/
	ltag = ((miInstance *)mi_db_access(state->light_instance))->item;
	mi_db_unpin(state->light_instance);
	mi_query(miQ_LIGHT_DIRECTION, state, ltag, &ldir);
	mi_vector_to_light(state, &dir, &state->dir);
	mi_vector_normalize(&dir);
	d = mi_vector_dot(&dir, &ldir);
	if (d <= 0)
		return(miFALSE);
	mi_query(miQ_LIGHT_SPREAD, state, ltag, &spread);
	if (d < spread)
		return(miFALSE);
	cone = *mi_eval_scalar(&paras->cone);
	if (d < cone) {
		t = 1 - (d - cone) / (spread - cone);
		result->r *= t;
		result->g *= t;
		result->b *= t;
	}
	if (*mi_eval_scalar(&paras->atten)) {			/* dist atten*/
		stop = *mi_eval_scalar(&paras->stop);
		if (state->dist >= stop)
			return(miFALSE);

		start = *mi_eval_scalar(&paras->start);
		if (state->dist > start && fabs(stop - start) > EPS) {
			t = 1 - (state->dist - start) / (stop - start);
			result->r *= t;
			result->g *= t;
			result->b *= t;
		}
	}
	if (*mi_eval_boolean(&paras->shadow)) {			/* shadows: */
		d = *mi_eval_scalar(&paras->factor);
		if (d < 1) {
			miColor filter;
			filter.r = filter.g = filter.b = filter.a = 1;
								/* opaque */
			if (!mi_trace_shadow(&filter,state) || BLACK(filter)) {
				result->r *= d;
				result->g *= d;
				result->b *= d;
				if (d == 0)
					return(miFALSE);
			} else {				/* transparnt*/
				double omf = 1 - d;
				result->r *= d + omf * filter.r;
				result->g *= d + omf * filter.g;
				result->b *= d + omf * filter.b;
			}
		}
	}
	return(miTRUE);
}


/*
 * Infinite light source. Must have a direction in the input file.
 * Takes two parameters. The first is a boolean which says whether the
 * light casts a shadow or not. The second is the shadow factor.
 */

struct mib_light_infinite {
	miColor		color;		/* color of light source */
	miBoolean	shadow;		/* light casts shadows */
	miScalar	factor;		/* makes opaque objects transparent */
};

DLLEXPORT int mib_light_infinite_version(void) {return(1);}

DLLEXPORT miBoolean mib_light_infinite(
	register miColor	*result,
	register miState	*state,
	register struct mib_light_infinite *paras)
{
	register miScalar	d;

	*result = *mi_eval_color(&paras->color);
	if (state->type != miRAY_LIGHT)			/* visible area light*/
		return(miFALSE);
								/* shadows: */
	d = *mi_eval_scalar(&paras->factor);
	if (*mi_eval_boolean(&paras->shadow) && d < 1) {
		miColor filter;
		filter.r = filter.g = filter.b = filter.a = 1;
							/* opaque */
		if (!mi_trace_shadow(&filter,state) || BLACK(filter)) {
			result->r *= d;
			result->g *= d;
			result->b *= d;
			if (d == 0)
				return(miFALSE);
		} else {				/* transparnt*/
			double omf = 1 - d;
			result->r *= d + omf * filter.r;
			result->g *= d + omf * filter.g;
			result->b *= d + omf * filter.b;
		}
	}
	return(miTRUE);
}
