/******************************************************************************
 * Copyright 1986-2002 by mental images GmbH & Co.KG, Fasanenstr. 81, D-10623
 * Berlin, Germany. All rights reserved.
 ******************************************************************************
 * Author:	thomas
 * Created:	4.9.95
 * Module:	shader
 * Purpose:	Softimage shaders for raylib
 *
 * Exports: 
 *	soft_color_3d_exit		clean up shader or shader instance
 *	soft_color_3d_init		initialize shader or shader instance
 *	soft_color_3d_version		shader version
 *	soft_color_3d			3d texture shader
 *
 *
 * Description:
 * the Softimage 3D shaders derived from Softimage's ttlib. This file is a
 * supplement to softshade.c because the 3d shader is rather large. It is
 * installed by softshade.c.
 *****************************************************************************/

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

#ifndef IRIX
#define fsin sin
#define ftan tan
#define fsqrt sqrt
#endif

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

#define NTERMS		8		/* max # of terms in spectral sum */
#define COLOR_MAP_SIZE	1024		/* number of colors in color map */
#define COLOR_MAP_MAX	(COLOR_MAP_SIZE-1)
#define TABLE_SIZE	0x10000		/* noise table size */
#define TABLE_MAX	(TABLE_SIZE-1)

typedef enum   { MARBLE, WOOD, CLOUD } Tex3d_type;
typedef struct { float weight; float scale; } Term;
typedef struct {
	Tex3d_type	type;
	miBoolean	do_tform;	/* paras->transform is not identity */
	float		spacing;	/* how close are bands or rings	*/
	float		taper;		/* tan(angle) */
	float		strength;	/* volume of noise for distortion */
	int		no_terms;	/* number of terms in spectral sum */
	Term		terms[NTERMS];	/* terms of the spectral sum */
	miColor		color_map[COLOR_MAP_SIZE];
	int		pos_col1;	/* position of color 1 in color map */
	int		pos_col2;	/* position of color 2 in color map */
	int		pos_col3;	/* position of color 3 in color map */
} Tex3d;

static float	*table;			/* noise table */
static float	getnoise(float x, float y, float z);
static miColor	lookup_color(miColor *color_map, float pos);
static int      init_count = 0;         /* counts frame init / exits */

/*
 * when mental ray completes, it calls this function to release the shader
 * instance memory allocated by the init routine below. When the shader
 * itself is exited at the end of the frame, delete the noise table.
 */

miBoolean soft_color_3d_exit(
	miState			*state,
	struct soft_color_3d	*paras)
{
	void			**userptr;

	if (!paras) {					/* shader exit */
	        mi_lock(state->global_lock);
	        init_count--;	
		if (!init_count)
		        mi_mem_release(table);
		mi_unlock(state->global_lock);
	} else {					/* instance exit */
		mi_query(miQ_FUNC_USERPTR, state, 0, &userptr);
		mi_mem_release(*userptr);
		*userptr = 0;
	}
	return(miTRUE);
}


/*
 * Initialize 3D texture shading. This is called before rendering begins.
 * If this is a shader init at the beginning of a frame, request instance
 * inits and initialize the random-number lattice for the coherent noise
 * function. All shaders share the noise table.
 * If this is an instance init, initialize the context struct used for 3D
 * texture evaluation that avoids having to recompute this for every shader
 * call.
 */

miBoolean soft_color_3d_init(
	miState			*state,
	struct soft_color_3d	*paras,
	miBoolean		*inst_req)
{
	register Tex3d		*tex;
	register int		i, j, k;

	if (!paras) {					/* shader init */
		*inst_req = miTRUE;
		
		mi_lock(state->global_lock);
		if (!init_count) {
			unsigned short seed[3];
			seed[0] = 0x330e;
			seed[1] = 1;
			seed[2] = 0;
		        table = (float *)
                                mi_mem_allocate(TABLE_SIZE * sizeof(float));
			for (i=0; i < TABLE_SIZE; i++)
			          table[i] = mi_erandom(seed);
			init_count++;
		}
		mi_unlock(state->global_lock);

	} else {					/* instance init */
		/*
		 * step 1: create and initialize the Tex3d context struct
		 */
		{
		float		weight = 0.5;
		float		scale = 1.0;
		register float	tw = 0.0;
		void		**userptr;

		mi_query(miQ_FUNC_USERPTR, state, 0, &userptr);
		tex = mi_mem_allocate(sizeof(Tex3d));
		*userptr = (void *)tex;

		tex->type     = paras->type;
		tex->do_tform = miFALSE;
		tex->spacing  = 2 * 3.141592653589793116 * paras->spacing;
		tex->taper    = ftan(paras->angle);
		tex->strength = paras->strength;

		if (paras->type != WOOD) {
			tex->no_terms = paras->iteration < NTERMS ?
						paras->iteration : NTERMS;
			for (i=0; i < paras->iteration; i++) {
				tex->terms[i].weight = weight;
				tex->terms[i].scale  = scale;
				tw    += weight;
				scale += scale;
				weight = 0.5 * pow(scale, -paras->power);
			}
		} else {
			tex->no_terms	     = 2;
			tex->terms[0].weight = 0.8;
			tex->terms[0].scale  = 1.0;
			tex->terms[1].weight = 0.8 * pow(8.0, -paras->power);
			tex->terms[1].scale  = 8.0;
			tw = tex->terms[0].weight + tex->terms[1].weight;
		}
		for (i=0; i < tex->no_terms; i++)
			tex->terms[i].weight /= tw;

		for (i=0; i < 4; i++)
			for (j=0; j < 4; j++)
				if (paras->transform[i+4*j] != (double)(i==j))
					tex->do_tform = miTRUE;
		}

		/*
		 * step 2: calculate color spread
		 */
		{
		miColor	col[5];
		int	pos_col[5];
		int	delta;
		miColor	step;
		miColor	c;
		miColor	*ptr_col_map = tex->color_map;

		col[0] = paras->color0;
		col[1] = paras->color1;
		col[2] = paras->color2;
		col[3] = paras->color3;
		col[4] = paras->color4;

		pos_col[0] = 0;
		pos_col[1] = tex->pos_col1 = paras->weight1 * COLOR_MAP_MAX;
		pos_col[2] = tex->pos_col2 = paras->weight2 * COLOR_MAP_MAX;
		pos_col[3] = tex->pos_col3 = paras->weight3 * COLOR_MAP_MAX;
		pos_col[4] = COLOR_MAP_MAX;

		for (i=0, j=1; j < 5; i++, j++) {
			if ((delta = pos_col[j] - pos_col[i]) > 0) {
				step.r = (col[j].r - col[i].r) / delta;
				step.g = (col[j].g - col[i].g) / delta;
				step.b = (col[j].b - col[i].b) / delta;
				step.a = (col[j].a - col[i].a) / delta;
				c = col[i];
				for (k=pos_col[i]; k < pos_col[j]; k++) {
					*ptr_col_map++ = c;
					c.r += step.r;
					c.g += step.g;
					c.b += step.b;
					c.a += step.a;
				}
				*ptr_col_map = col[j];
			} else
				ptr_col_map += delta;
		}
		}
	}
	return(miTRUE);
}


/*
 * The 3D texture shader -- compute the 3D texture color for a point.
 *
 * ------------ marble -------------
 * Use the sum to distort planes perpendicular to the x-axis.
 *
 * spacing:  Determine how finely spaced the planes are.
 * strength: weight describing the strength of the distortion.
 *
 * When spacing = 1.0 and strength = 0.0:
 *
 *             Y
 *                    first color
 *             |     /
 *            1+    *
 *             |  *   *      third color
 *             | *     *    /
 *             |*       *  /       txyz[0] = 1.0 ==> 1 plane
 *             |*       * /       /
 *           --+-----------------+-- X
 *             |         *       *
 *             |         *       |
 *             |          *     *|
 *             |           *   * |
 *           -1+             *  2_PI
 *             |              \
 *                             fifth color
 *
 * ------------ wood -------------
 * Use the sum to distort concentric cylinders around the x-axis.
 *
 * spacing:  Determine how finely spaced the cylinders are.
 * taper:    Change the concentric cylinders to cones whose sides
 *           make an angle with the x-axis.
 * strength: A weight describing the strength of the distortion.
 *
 * When strength = 0.0 and spacing = 1.0: Same as for marble, except
 * r = 1.0 ==> 1 cylinder (not 1 plane)
 *
 * ------------ cloud -------------
 * Spread out the sum value.
 *
 *             Y
 *                             fifth color
 *             |              /
 *          1.0+            **
 *             |          *
 *             |        *
 *             |       *
 *             |      *
 *          0.5+      *---third color
 *             |      *
 *             |     *
 *             |    *
 *             |  *
 *          0.0**-----+------+-- X
 *              \    0.5    1.0
 *               \
 *                first color
 */

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

miBoolean soft_color_3d(
	miColor		*result,
	miState		*state,
	struct soft_color_3d *paras)
{
	void		**userptr;
	register Tex3d	*tex;
	miVector	coord;
	float		txyz[3];
	float		sk, sum=0, t_noise, x, r;
	int		i;

	mi_query(miQ_FUNC_USERPTR, state, 0, &userptr);
	tex = (Tex3d *)*userptr;
	miASSERT(tex);

	if (state->time != 0.0) {
		miVector point = state->point;
		point.x -= state->time * state->motion.x;
		point.y -= state->time * state->motion.y;
		point.z -= state->time * state->motion.z;
		mi_point_to_object(state, &coord, &point);
	} else
		mi_point_to_object(state, &coord, &state->point);

	if (tex->do_tform) {
		txyz[0] = paras->transform[0*4+0] * coord.x
			+ paras->transform[1*4+0] * coord.y
			+ paras->transform[2*4+0] * coord.z
			+ paras->transform[3*4+0];
		txyz[1] = paras->transform[0*4+1] * coord.x
			+ paras->transform[1*4+1] * coord.y
			+ paras->transform[2*4+1] * coord.z
			+ paras->transform[3*4+1];
		txyz[2] = paras->transform[0*4+2] * coord.x
			+ paras->transform[1*4+2] * coord.y
			+ paras->transform[2*4+2] * coord.z
			+ paras->transform[3*4+2];
	} else {
		txyz[0] = coord.x;
		txyz[1] = coord.y;
		txyz[2] = coord.z;
	}

	switch (tex->type) {
	  default:
	  case MARBLE:
		for (i=0; i < tex->no_terms; i++) {
			sk = tex->terms[i].scale;
			t_noise = getnoise(sk * txyz[0],
					   sk * txyz[1],
					   sk * txyz[2]);
			sum += tex->terms[i].weight
				* (t_noise > 0.5 ? t_noise + t_noise - 1.0
						 : 1.0 - t_noise - t_noise);
		}
		x = txyz[0] + txyz[1] * tex->taper;
		*result = lookup_color(tex->color_map,
					0.5 - 0.5 * fsin(tex->spacing
						* (x + tex->strength * sum)));
		break;

	  case WOOD:
		sk   = tex->terms[0].scale;
		sum  = tex->terms[0].weight * getnoise(sk * txyz[0],
						       sk * txyz[1],
						       sk * txyz[2]);
		sk   = tex->terms[1].scale;
		sum += tex->terms[1].weight * getnoise(sk * txyz[0],
						       sk * txyz[1],
						       sk * txyz[2]);
		r = fsqrt(txyz[1] * txyz[1] + txyz[2] * txyz[2])
			+ txyz[0] * tex->taper;
		*result = lookup_color(tex->color_map,
					0.5 - 0.5 * fsin(tex->spacing
						* (r + tex->strength * sum)));
		break;

	  case CLOUD:
		for (i=0; i < tex->no_terms; i++) {
			sk   = tex->terms[i].scale;
			sum += tex->terms[i].weight * getnoise(sk * txyz[0],
							       sk * txyz[1],
							       sk * txyz[2]);
		}
		if (sum > 1.0)
			sum = 1.0;
		*result = lookup_color(tex->color_map,
					sum * sum * (3.0 - sum - sum));
	}
	return(miTRUE);
}


/*
 * Do a linear interpolation to get a color map value.
 * Limitations:	the color position must be between 0.0 and 1.0.
 */

static miColor lookup_color(
	register miColor *color_map,
	float		pos)
{
	register float	p, lfr, hfr;
	register int	l, h;
	miColor		ret;

	l = p = pos * COLOR_MAP_MAX;
	hfr = 1.0 - (lfr = p - l);
	h = l == COLOR_MAP_MAX ? COLOR_MAP_MAX : l+1;

	ret.r = hfr * color_map[l].r + lfr * color_map[h].r;
	ret.g = hfr * color_map[l].g + lfr * color_map[h].g;
	ret.b = hfr * color_map[l].b + lfr * color_map[h].b;
	ret.a = hfr * color_map[l].a + lfr * color_map[h].a;
	return(ret);
}


/*
 * Compute a band-limited noise value for an (x,y,z) point.
 */

static float getnoise(
	float	x,
	float	y,
	float	z)
{
	float	xx, yy, zz;
	int	ix, iy, iz, ixyz;
	float	fr_x, fr_y, fr_z;
	float	u_0_0, u_0_1, u_0_2;
	float	u_1_0, u_1_1, u_1_2;
	float	u_2_0, u_2_1, u_2_2;
	float	dxyz_0_0_0, dxyz_0_0_1, dxyz_0_0_2;
	float	dxyz_0_1_0, dxyz_0_1_1, dxyz_0_1_2;
	float	dxyz_0_2_0, dxyz_0_2_1, dxyz_0_2_2;
	float	dxyz_1_0_0, dxyz_1_0_1, dxyz_1_0_2;
	float	dxyz_1_1_0, dxyz_1_1_1, dxyz_1_1_2;
	float	dxyz_1_2_0, dxyz_1_2_1, dxyz_1_2_2;
	float	dxyz_2_0_0, dxyz_2_0_1, dxyz_2_0_2;
	float	dxyz_2_1_0, dxyz_2_1_1, dxyz_2_1_2;
	float	dxyz_2_2_0, dxyz_2_2_1, dxyz_2_2_2;

	ix = xx = x + 39999.5;
	iy = yy = y + 39999.5;
	iz = zz = z + 39999.5;

	fr_x = xx - ix;
	fr_y = yy - iy;
	fr_z = zz - iz;

	u_2_0 = 0.5 * fr_x * fr_x;
	u_0_0 = 0.5 - fr_x + u_2_0;
	u_1_0 = 1.0 - u_0_0 - u_2_0;
	u_2_1 = 0.5 * fr_y * fr_y;
	u_0_1 = 0.5 - fr_y + u_2_1;
	u_1_1 = 1.0 - u_0_1 - u_2_1;
	u_2_2 = 0.5 * fr_z * fr_z;
	u_0_2 = 0.5 - fr_z + u_2_2;
	u_1_2 = 1.0 - u_0_2 - u_2_2;

	ixyz = ix * 1341 + iy * 719 + iz * 2031;

	dxyz_0_0_0 = table[(ixyz)        & TABLE_MAX];
	dxyz_0_0_1 = table[(ixyz + 1341) & TABLE_MAX];
	dxyz_0_0_2 = table[(ixyz + 2682) & TABLE_MAX];
	dxyz_0_1_0 = table[(ixyz +  719) & TABLE_MAX];
	dxyz_0_1_1 = table[(ixyz + 2060) & TABLE_MAX];
	dxyz_0_1_2 = table[(ixyz + 3401) & TABLE_MAX];
	dxyz_0_2_0 = table[(ixyz + 1438) & TABLE_MAX];
	dxyz_0_2_1 = table[(ixyz + 2779) & TABLE_MAX];
	dxyz_0_2_2 = table[(ixyz + 4120) & TABLE_MAX];
	dxyz_1_0_0 = table[(ixyz + 2031) & TABLE_MAX];
	dxyz_1_0_1 = table[(ixyz + 3372) & TABLE_MAX];
	dxyz_1_0_2 = table[(ixyz + 4713) & TABLE_MAX];
	dxyz_1_1_0 = table[(ixyz + 2750) & TABLE_MAX];
	dxyz_1_1_1 = table[(ixyz + 4091) & TABLE_MAX];
	dxyz_1_1_2 = table[(ixyz + 5432) & TABLE_MAX];
	dxyz_1_2_0 = table[(ixyz + 3469) & TABLE_MAX];
	dxyz_1_2_1 = table[(ixyz + 4810) & TABLE_MAX];
	dxyz_1_2_2 = table[(ixyz + 6151) & TABLE_MAX];
	dxyz_2_0_0 = table[(ixyz + 4062) & TABLE_MAX];
	dxyz_2_0_1 = table[(ixyz + 5403) & TABLE_MAX];
	dxyz_2_0_2 = table[(ixyz + 6744) & TABLE_MAX];
	dxyz_2_1_0 = table[(ixyz + 4781) & TABLE_MAX];
	dxyz_2_1_1 = table[(ixyz + 6122) & TABLE_MAX];
	dxyz_2_1_2 = table[(ixyz + 7463) & TABLE_MAX];
	dxyz_2_2_0 = table[(ixyz + 5500) & TABLE_MAX];
	dxyz_2_2_1 = table[(ixyz + 6841) & TABLE_MAX];
	dxyz_2_2_2 = table[(ixyz + 8182) & TABLE_MAX];

	return(u_0_2 * (u_0_1 * (u_0_0 * dxyz_0_0_0
			       + u_1_0 * dxyz_0_0_1
			       + u_2_0 * dxyz_0_0_2)
		      + u_1_1 * (u_0_0 * dxyz_0_1_0
			       + u_1_0 * dxyz_0_1_1
			       + u_2_0 * dxyz_0_1_2)
		      + u_2_1 * (u_0_0 * dxyz_0_2_0
			       + u_1_0 * dxyz_0_2_1
			       + u_2_0 * dxyz_0_2_2))
	     + u_1_2 * (u_0_1 * (u_0_0 * dxyz_1_0_0
			       + u_1_0 * dxyz_1_0_1
			       + u_2_0 * dxyz_1_0_2)
		      + u_1_1 * (u_0_0 * dxyz_1_1_0
			       + u_1_0 * dxyz_1_1_1
			       + u_2_0 * dxyz_1_1_2)
		      + u_2_1 * (u_0_0 * dxyz_1_2_0
			       + u_1_0 * dxyz_1_2_1
			       + u_2_0 * dxyz_1_2_2))
	     + u_2_2 * (u_0_1 * (u_0_0 * dxyz_2_0_0
			       + u_1_0 * dxyz_2_0_1
			       + u_2_0 * dxyz_2_0_2)
		      + u_1_1 * (u_0_0 * dxyz_2_1_0
			       + u_1_0 * dxyz_2_1_1
			       + u_2_0 * dxyz_2_1_2)
		      + u_2_1 * (u_0_0 * dxyz_2_2_0
			       + u_1_0 * dxyz_2_2_1
			       + u_2_0 * dxyz_2_2_2)));
}
