/******************************************************************************
 * Copyright 1986, 2014 NVIDIA Corporation. All rights reserved.
 ******************************************************************************
 * Created:	30.10.97
 * Module:	baseshader
 * Purpose:	base shaders for Phenomenon writers
 *
 * Exports:
 *	mib_ray_marcher
 *
 * History:
 *	23.11.00: mib_texture_vector used its vertex param incorrectly
 *
 * Description:
 *	This is the ray marching code
 *****************************************************************************/

#include <math.h>
#include <stddef.h>
#include <shader.h>


struct mrm {
	miTag		s;
	miScalar	distance;
	miInteger	num;
	miInteger	subdiv;
	miColor		contrast;
};


/*
 * return true if contrast found
 */

static miBoolean component_contrast(
	miScalar	c1,
	miScalar	c2,
	miScalar	contrast)
{
	miScalar	denom = c1 + c2;

	if (denom > 0.0)
		return(fabs(c1 - c2) / denom >= contrast);
	else
		return(miFALSE);
}


/*
 * return true if contrast found
 */

static miBoolean color_contrast(
	miColor		*c1,
	miColor		*c2,
	miColor		*contrast)
{
	miBoolean	has_contrast = miFALSE;

	has_contrast  = component_contrast(c1->r, c2->r, contrast->r);
	has_contrast |= component_contrast(c1->g, c2->g, contrast->g);
	has_contrast |= component_contrast(c1->b, c2->b, contrast->b);
	has_contrast |= component_contrast(c1->a, c2->a, contrast->a);
	return(has_contrast);
}


/*
 * the recursive evaluation in high contrast regions
 */

static void recurse(
	miColor		*result,
	miState		*state,
	miVector	from,
	miVector	dir,
	miScalar	dist,
	miColor		*prev,
	miColor		*next,
	miInteger	level,
	struct mrm	*p)
{
	miVector	step, pos;
	miColor		col;

	if (level > p->subdiv)
		return;

	step = dir;
	mi_vector_mul(&step, dist*0.5);

	pos = from;
	mi_vector_add(&pos, &pos, &step);

	state->point  = pos;
	state->normal = dir;
	state->pri    = 0;	/* required for ray casting from empty space */

	mi_call_shader_x(&col, miSHADER_MATERIAL, state, p->s, NULL);

	/* recurse further if necessary */
	if (color_contrast(prev, &col, &p->contrast))
		recurse(result, state, from, dir, dist*0.5, prev, &col,
								level+1, p);
	if (color_contrast(next, &col, &p->contrast))
		recurse(result, state, pos, dir, dist*0.5, &col, next,
								level+1, p);
	/* accumulate result */
	result->r += 0.5*(col.r - 0.5*(prev->r - next->r)) * dist;
	result->g += 0.5*(col.g - 0.5*(prev->g - next->g)) * dist;
	result->b += 0.5*(col.b - 0.5*(prev->b - next->b)) * dist;
	result->a += 0.5*(col.a - 0.5*(prev->a - next->a)) * dist;
}


/*
 * the basic raymarcher
 */

static void raymarch(
	miColor		*result,
	miState		*state,
	struct mrm	*p)
{
	miVector	step, pos, ppos;
	miVector	old_point, old_normal;
	miScalar	step_size;
	miColor		col, prev;
	miBoolean	has_prev = miFALSE;
	int		i;

	step       = state->dir;
	step_size  = state->dist / ((miScalar)p->num - 1);
	mi_vector_mul(&step, step_size);

	old_point  = state->point;
	old_normal = state->normal;
	pos        = state->org;

	/* begin raymarch */
	for (i=0; i < p->num; i++) {
		state->point  = pos;
		state->normal = state->dir;
		state->pri    = 0;	/* cats rays from empty space */

		mi_call_shader_x(&col, miSHADER_MATERIAL, state, p->s, NULL);

		if (has_prev && p->subdiv &&
				color_contrast(&prev, &col, &p->contrast))
		/* adaptive sampling */
			recurse(result, state, ppos, state->dir, step_size,
							&prev, &col, 1, p);
		result->r += col.r;
		result->g += col.g;
		result->b += col.b;
		result->a += col.a;

		prev     = col;
		ppos     = pos;
		has_prev = miTRUE;
		mi_vector_add(&pos, &pos, &step);
	}
	state->point  = old_point;
	state->normal = old_normal;
}


/*
 * the ray marching entry point
 */

extern "C" DLLEXPORT int mib_ray_marcher_version(void) {return(1);}

extern "C" DLLEXPORT miBoolean mib_ray_marcher(
	miColor		*result,
	miState		*state,
	struct mrm	*p)
{
	struct mrm	pe;
	miScalar	scale;

	result->r = result->g = result->b = result->a = 0.0;

	/*
	 * copy all parameters to a static structure to
	 * avoid all the mi_eval_* calls in the code
	 */
	pe.s        = *mi_eval_tag(&p->s);
	pe.distance = *mi_eval_scalar(&p->distance);
	pe.num      = *mi_eval_integer(&p->num);
	pe.subdiv   = *mi_eval_integer(&p->subdiv);
	pe.contrast = *mi_eval_color(&p->contrast);

	if (pe.num == 0.0)
		if (pe.distance > 0.0)
			pe.num = state->dist / pe.distance;
		else
			pe.num = 4; /* default #samples */
	if (pe.num < 2)
		pe.num = 2;

	/* go! */
	raymarch(result, state, &pe);

	/* normalize result */
	scale = 1.0 / (miScalar)pe.num;
	result->r *= scale;
	result->g *= scale;
	result->b *= scale;
	result->a *= scale;
	return(miTRUE);
}
