/*************************************************************************

	form*Z by auto*des*sys, Inc.
	Copyright (c) 1989-2004 auto*des*sys, Inc.  All rights reserved.

	File:			glossy.c
	Author:			auto*des*sys, Inc.
	Created on:		01/29/04
	Description:	Glossy reflection shader plugin.  Shades surfaces
					as if they had a blurry, mirror-like gloss.

*************************************************************************/

/* standard headers */

#include <stdlib.h>
#include <math.h>

/* this plugin's header */

#include "glossy.h"

/* required for plugin's source file implementing plugin entry function */

#include "fz_plugin_glue_api.h"

/* generic plugin functions */
static fzrt_error_td	glossy_check_config(const fzpl_fset_glue_fset* const fset_glue, const fzpl_host_config_td* const hostConfig);
static fzrt_error_td	glossy_acquire_fsets(void);
static void				glossy_release_fsets(void);
static fzrt_error_td	glossy_register_plugins(const fzpl_fset_glue_fset* const fset_glue, const fzpl_host_config_td* const hostConfig);
static fzrt_error_td	glossy_unregister_plugins(const fzpl_fset_glue_fset* const fset_glue);

/* function called by formZ to fill a shader's function set */
/* with pointers to glossy's own callback functions. */
static fzrt_error_td	glossy_fill_fset(const fzpl_fset_def_ptr fset_def, fzpl_fset_td* const fset);

/* required plugin entry point function */
FZPL_PLUGIN_FUNC(fzrt_error_td) fz_plugin_entry(const fzpl_fset_glue_fset* const fset_glue, fzpl_command_td cmd, const fzpl_host_config_td* const hostConfig);

/* shader callback functions */
static fzrt_error_td	glossy_uuid(fzrt_UUID_td uuid);
static fzrt_error_td 	glossy_name(char* name, fzrt_int max_len);
static fzrt_error_td 	glossy_io_version(fzpl_vers_td* version);
static fzrt_error_td	glossy_set_parameters(void);
static fzrt_error_td 	glossy_pre_render(void** shdr_data);
static fzrt_error_td 	glossy_post_render(void* shdr_data);
static fz_rgb_float_td 	glossy_shade_pixel(void* shdr_data);



/************************************************************************
							Plugin Functions
*************************************************************************/

/************************************************************************
	glossy_check_config
		A first pass check to see if the plugin can be loaded.  It does
		the following:
			Acquires formZ function sets.
			Checks licensing.
			Informs the application if this plugin is dependent on
				function sets from other plugins.
			Opens localization resource file and loads strings.
*************************************************************************/
static fzrt_error_td glossy_check_config(const fzpl_fset_glue_fset* const fset_glue, const fzpl_host_config_td* const hostConfig)
/************************************************************************/
{
	fzrt_error_td	err = FZRT_NOERR;
	/* resource file variables */
	fzrt_floc_ptr	floc;
	fzrt_fzr_ref_td	resource_ref;
	char			buffer[STRING_SIZE];
	
	/* assign our copy of the plugin fset pointer to that which formZ provides */
	_fset_glue = fset_glue;

	/* Make sure required application defined function sets are available */
	/* Release the function sets at unload. */

	err = glossy_acquire_fsets();

	/* IF NEEDED: Check License */

	/* IF NEEDED: Tell the plugin manager what plugins this plugin is dependent on */

	/* get localized strings */

	if (err == FZRT_NOERR)
	{
		/* initialize file locator structure */
		err = _file_funcs.fzrt_file_floc_init(&floc);
		if (err == FZRT_NOERR)
		{
			/* get the location of this plugin */
			err = _fset_glue->fzpl_plugin_file_get_floc(floc);

			if (err == FZRT_NOERR)
			{
				/* open the glossy resource file eg. glossy.ENU.fzr */
				err = _base_funcs.fzrt_fzr_open(floc, "glossy", &resource_ref);

				if (err == FZRT_NOERR)
				{
					/* get the localized resource strings for this plugin */
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 1, buffer);
					strcpy(_name, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 2, buffer);
					strcpy(_short_name, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 3, buffer);
					strcpy(_blurriness_angle, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 4, buffer);
					strcpy(_blurriness_quality, buffer);

					/* close the glossy resource file */
					err = _base_funcs.fzrt_fzr_close(resource_ref);
				}
				/* throw away floc */
				_file_funcs.fzrt_file_floc_finit(&floc);
			}
		}
	}
	

	if (err != FZRT_NOERR)
	{
		glossy_release_fsets();
	}

	return err;
}


/************************************************************************
	glossy_acquire_fsets
		Acquires formZ provided service function sets.
*************************************************************************/
static fzrt_error_td glossy_acquire_fsets(void)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;


	err = _fset_glue->fzpl_fset_acquire( FZ_SHDR_FSET_TYPE,
								   FZ_SHDR_FSET_VERSION,
								   FZRT_UUID_NULL,
						 		   FZPL_TYPE_STRING(fz_shdr_fset),
						 		   sizeof(_shdr_funcs),
						 		   (fzpl_fset_td*)&_shdr_funcs );
		

	if (err == FZRT_NOERR)
	{
		err = _fset_glue->fzpl_fset_acquire( FZ_TYPE_FSET_TYPE,
									   FZ_TYPE_FSET_VERSION,
									   FZRT_UUID_NULL,
									   FZPL_TYPE_STRING(fz_type_fset),
									   sizeof(_type_funcs),
									   (fzpl_fset_td*)&_type_funcs );
	}
	
	if (err == FZRT_NOERR)
	{
		err = _fset_glue->fzpl_fset_acquire( FZ_FZRT_MEM_FSET_TYPE,
									   FZ_FZRT_MEM_FSET_VERSION,
									   FZRT_UUID_NULL,
									   FZPL_TYPE_STRING(fz_fzrt_mem_fset),
									   sizeof(_mem_funcs),
									   (fzpl_fset_td*)&_mem_funcs );
	}

	if (err == FZRT_NOERR)
	{
		err = _fset_glue->fzpl_fset_acquire( FZ_FZRT_BASE_FSET_TYPE,
									   FZ_FZRT_BASE_FSET_VERSION,
									   FZRT_UUID_NULL,
									   FZPL_TYPE_STRING(fz_fzrt_base_fset),
									   sizeof(_base_funcs),
									   (fzpl_fset_td*)&_base_funcs );
	}
	
	if (err == FZRT_NOERR)
	{
		err = _fset_glue->fzpl_fset_acquire( FZ_MATH_FSET_TYPE,
									   FZ_MATH_FSET_VERSION,
									   FZRT_UUID_NULL,
									   FZPL_TYPE_STRING(fz_math_fset),
									   sizeof(_math_funcs),
									   (fzpl_fset_td*)&_math_funcs );
	}

	if (err == FZRT_NOERR)
	{
		err = _fset_glue->fzpl_fset_acquire( FZ_FZRT_FILE_FSET_TYPE,
									   FZ_FZRT_FILE_FSET_VERSION,
									   FZRT_UUID_NULL,
									   FZPL_TYPE_STRING(fz_fzrt_file_fset),
									   sizeof(_file_funcs),
									   (fzpl_fset_td*)&_file_funcs );
	}

	return err;
}

/************************************************************************
	glossy_release_fsets
		Releases all function sets used from formZ.
*************************************************************************/
static void glossy_release_fsets(void)
/************************************************************************/
{
	_fset_glue->fzpl_fset_release( (fzpl_fset_td*)&_shdr_funcs );
	_fset_glue->fzpl_fset_release( (fzpl_fset_td*)&_type_funcs );
	_fset_glue->fzpl_fset_release( (fzpl_fset_td*)&_mem_funcs );
	_fset_glue->fzpl_fset_release( (fzpl_fset_td*)&_base_funcs );
	_fset_glue->fzpl_fset_release( (fzpl_fset_td*)&_math_funcs );
	_fset_glue->fzpl_fset_release( (fzpl_fset_td*)&_file_funcs );
}

 

/************************************************************************
	glossy_register_plugins
		Registers plugins and their associated function sets.
*************************************************************************/
static fzrt_error_td glossy_register_plugins(const fzpl_fset_glue_fset* const fset_glue, const fzpl_host_config_td* const hostConfig)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;

	/* register a shader plugin */
	err = fset_glue->fzpl_plugin_register( GLOSSY_PLUGIN_UUID,
									 _name,
									 GLOSSY_PLUGIN_CUR_VERSION,
									 GLOSSY_PLUGIN_VENDOR,
									 GLOSSY_PLUGIN_URL,
									 FZ_SHDR_EXTS_TYPE,
									 FZ_SHDR_EXTS_VERSION,
									 NULL /*error string function*/,
									 0,
									 NULL,
									 &_glossy_plugin_runtime_id);
	if (err == FZRT_NOERR)
	{
		/* register a reflection shader fset for the plugin with matching runtime id. */
		err = fset_glue->fzpl_plugin_add_fset( _glossy_plugin_runtime_id,
										 FZ_SHDR_CBAK_REFL_FSET_TYPE,
										 FZ_SHDR_CBAK_REFL_FSET_VERSION,
										 FZ_SHDR_CBAK_REFL_FSET_NAME,
										 FZPL_TYPE_STRING(fz_shdr_cbak_refl_fset),
										 sizeof(fz_shdr_cbak_refl_fset),
										 glossy_fill_fset,
										 FALSE);
	}

	if (err != FZRT_NOERR)
	{
		glossy_unregister_plugins(fset_glue);
	}

	return err;
}

/************************************************************************
	glossy_unregister_plugins
		Unregisters plugins.  Releases function sets.
*************************************************************************/
static fzrt_error_td glossy_unregister_plugins(const fzpl_fset_glue_fset* const fset_glue)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;

	err = fset_glue->fzpl_plugin_unregister(_glossy_plugin_runtime_id);
	
	glossy_release_fsets();

	return err;
}


/************************************************************************
							Plugin Fill Function
*************************************************************************/

/************************************************************************
	glossy_fill_fset
		Fills the fset structure with the callback functions for a
		reflection shader plugin.
*************************************************************************/
static fzrt_error_td glossy_fill_fset(const fzpl_fset_def_ptr fset_def, fzpl_fset_td* const fset)
/************************************************************************/
{
	fzrt_error_td			  err = FZRT_NOERR;
	fz_shdr_cbak_refl_fset* install_struct;

	if (fset_def && fset)
	{
		err = _fset_glue->fzpl_fset_def_check( fset_def,
										 FZ_SHDR_CBAK_REFL_FSET_VERSION,
										 FZPL_TYPE_STRING(fz_shdr_cbak_refl_fset),
										 sizeof ( fz_shdr_cbak_refl_fset ),
										 FZPL_VERSION_OP_NEWER );
		if (err == FZRT_NOERR)
		{
			install_struct = (fz_shdr_cbak_refl_fset*)fset;

			install_struct->fz_shdr_cbak_refl_uuid				= glossy_uuid;
			install_struct->fz_shdr_cbak_refl_name				= glossy_name;
			install_struct->fz_shdr_cbak_refl_version			= glossy_io_version;
			install_struct->fz_shdr_cbak_refl_set_parameters	= glossy_set_parameters;
			install_struct->fz_shdr_cbak_refl_pre_render		= glossy_pre_render;
			install_struct->fz_shdr_cbak_refl_post_render		= glossy_post_render;
			install_struct->fz_shdr_cbak_refl_pixel				= glossy_shade_pixel;
		}
	}
	else
		err = _base_funcs.fzrt_error_set(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0);

	return err;
}

/************************************************************************
						Required Plugin Functions
*************************************************************************/

/************************************************************************
	fz_plugin_entry
		The main entry point for a formZ plugin.
*************************************************************************/
FZPL_PLUGIN_FUNC(fzrt_error_td) fz_plugin_entry(const fzpl_fset_glue_fset* const fset_glue, fzpl_command_td cmd, const fzpl_host_config_td* const hostConfig)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;

	switch (cmd)
	{
		case FZPL_PLUGIN_CHECK:
			err = glossy_check_config(fset_glue, hostConfig);
		break;
		
		case FZPL_PLUGIN_INITIALIZE:
			err = glossy_register_plugins(fset_glue, hostConfig);
		break;
		
		case FZPL_PLUGIN_EXIT:
			err = glossy_unregister_plugins(fset_glue);
		break;
	}

	return (err);
}









/************************************************************************
						Glossy Shader Data Structures
*************************************************************************/

/************************************************************************
	glossy_data_td
	
		Description
			Options/Parameters that a user can change to alter how
			the glossy shader behaves.
			
		Members
			spread_angle		- The parameter that adjusts how far
								  a reflected ray can stray from its mirror
								  reflection direction to create a new
								  "glossy direction."  This is usually
								  a very small angle, even for very
								  blurry mirrors.
			num_rays			- The quality of how the glossiness looks
								  depends on the number of sample rays
								  shot and averaged in the glossy
								  direction.  More accurate glossiness
								  requires more sample rays.
			sqrt_num_rays		- The square root of the number of sample
								  rays.
			specular_shininess	- How spread out the specular highlight
								  is.
			range_min_phi		- precomputed array of the lower bound of
								  the angles at regular intervals from
								  the zenith to the spread angle from
								  which to sample.
			range_max_phi		- precomputed array of the upper bound of
								  the angles at regular intervals from
								  the zenith to the spread angle from
								  which to sample.
			range_min_theta		- precomputed array of the lower bound of
								  the angles at regular intervals around
								  the original mirror direction from
								  which to sample.
			range_max_theta		- precomputed array of the upper bound of
								  the angles at regular intervals around
								  the original mirror direction from
								  which to sample.
			ray_contribution	- the percentage for how much each ray
								  contributes to the final glossy color.
*************************************************************************/

typedef struct
{	double	spread_angle;
	fzrt_int	num_rays;
	fzrt_int	sqrt_num_rays;
	double	specular_shininess;
	double	range_min_phi[MAX_NUM_RAYS];
	double	range_max_phi[MAX_NUM_RAYS];
	double	range_min_theta[MAX_NUM_RAYS];
	double	range_max_theta[MAX_NUM_RAYS];
	double	ray_contribution;
} glossy_data_td;




/************************************************************************
						Glossy Shader Callbacks
*************************************************************************/



/************************************************************************
	glossy_uuid
		Returns the UUID of this shader's function set.
*************************************************************************/
static fzrt_error_td glossy_uuid(fzrt_UUID_td uuid)
/************************************************************************/
{
	_base_funcs.fzrt_UUID_copy(GLOSSY_PLUGIN_FSET_UUID, uuid);
	return FZRT_NOERR;
}
/************************************************************************
	glossy_name
		Tells formZ what the name of this plugin is which shows up
		in the drop-down menu among other places.
*************************************************************************/
static fzrt_error_td glossy_name(char* name, fzrt_int max_len)
/************************************************************************/
{
	strncpy(name, _short_name, max_len);
	return(FZRT_NOERR);
}

/************************************************************************
	glossy_io_version
		Tells formZ the version of this plugin's data io.
		This is NOT the same thing as the plugin's version.
		The io of the data may not necessarily change between plugin versions.
*************************************************************************/
static fzrt_error_td glossy_io_version(fzpl_vers_td* version)
/************************************************************************/
{
	*version = GLOSSY_PLUGIN_DATA_IO_VERSION;
	return(FZRT_NOERR);
}

#define PARAM_UUID_BLURR_ANGLE		"\x9d\x2c\x4d\x96\x9f\xee\x11\xda\x8e\x35\x00\x03\x93\x63\x5d\xbc"
#define PARAM_UUID_BLURR_QUALITY	"\x9d\x2c\x4e\x5d\x9f\xee\x11\xda\x8e\x35\x00\x03\x93\x63\x5d\xbc"

/************************************************************************
	glossy_set_parameters
		Tells formZ what parameters to cause to show up in the "options"
		dialog of the shader, (not necessarily in same order).
*************************************************************************/
static fzrt_error_td glossy_set_parameters(void)
/************************************************************************/
{

	/* setup slider for blurriness spread angle (0 to 100%). */
	_shdr_funcs.fz_shdr_set_sld_flt_parm(_blurriness_angle, 0.5, 1, 1, 0);
	/* setup slider for blurriness quality 1 through 5 */
	_shdr_funcs.fz_shdr_set_sld_int_parm(_blurriness_quality, 4, 1, 5, 1, 1, 1);
	/* setup ambient */
	_shdr_funcs.fz_shdr_set_ambient_parm(0.1);
	/* setup diffuse */
	_shdr_funcs.fz_shdr_set_diffuse_parm(0.7);
	/* setup specular NOTE: 0.0025 shows up as 0.05. */
	_shdr_funcs.fz_shdr_set_specular_parm(0.85, 0.0025);
	/* setup mirror */
	_shdr_funcs.fz_shdr_set_mirror_parm(0.5);

	_shdr_funcs.fz_shdr_set_parm_anim_id(0,PARAM_UUID_BLURR_ANGLE);	
	_shdr_funcs.fz_shdr_set_parm_anim_id(1,PARAM_UUID_BLURR_QUALITY);		

	return(FZRT_NOERR);
}


/************************************************************************
	glossy_pre_render
		Sets up the shader by precomputing as much as possible.  Gets
		parameters that don't change throughout the rendering and stores
		them in a data structure.
*************************************************************************/
static fzrt_error_td glossy_pre_render(void** shdr_data)
/************************************************************************/
{
	fzrt_error_td	err = FZRT_NOERR;
	glossy_data_td*	glossy;
	fz_type_td		data;
	fzrt_int 			i;
	double			spread_intvl, around_intvl;

	*shdr_data = NULL;
	
	/* keep randomness looking the same across successive renderings */
	srand(1001);

	/* allocate memory */
	err = _mem_funcs.fz_mem_zone_alloc(NULL,sizeof(glossy_data_td),FALSE, (fzrt_ptr*)shdr_data);
	if (err == FZRT_NOERR)
	{
		glossy = (glossy_data_td*) *shdr_data;

		
		/* Get parameters in options dialog */
		

		/* get blurriness spread angle percentage */
		_shdr_funcs.fz_shdr_get_parm(0, &data);
		_type_funcs.fz_type_get_double(&data, &glossy->spread_angle);
		/* get blurriness quality 1 through 5 */
		_shdr_funcs.fz_shdr_get_parm(1, &data);
		_type_funcs.fz_type_get_int(&data, &glossy->sqrt_num_rays);
		/* square it to get number of rays */
		glossy->num_rays = glossy->sqrt_num_rays*glossy->sqrt_num_rays;
		/* get specular roughness */
		_shdr_funcs.fz_shdr_get_specular_roughness(&glossy->specular_shininess);
		/* invert for formula */
		glossy->specular_shininess = 1.0 / glossy->specular_shininess;
		/* each ray sample contributes only what it's worth */
		glossy->ray_contribution = 1.0 / glossy->num_rays;
		
		/* setup intervals to be sampled from. */
		/* this creates a grid on a circular piece of a sphere, with emphasis on */
		/* the center of the piece, yet guaranteeing samples even near outside. */
		spread_intvl = 0.1*(glossy->spread_angle+0.00001) / glossy->sqrt_num_rays;
		around_intvl = FZ_2PI / glossy->sqrt_num_rays;
		for (i = 0; i < glossy->num_rays; i++)
		{
			/* angle tilted away from perfect mirror direction (max of 0.1 radians ~6 deg) */
			glossy->range_min_phi[i] = (i % glossy->sqrt_num_rays) * spread_intvl;
			glossy->range_max_phi[i] = glossy->range_min_phi[i] + spread_intvl;
		
			/* angle around the perfect mirror direction axis */
			glossy->range_min_theta[i] = (i / glossy->sqrt_num_rays) * around_intvl;
			glossy->range_max_theta[i] = glossy->range_min_theta[i] + around_intvl;
		}
	}

	return(err);
}


/************************************************************************
	glossy_post_render
		Frees memory used for parameters in rendering
*************************************************************************/
static fzrt_error_td glossy_post_render(void* shdr_data)
/************************************************************************/
{
	if (shdr_data != NULL)
	{
		_mem_funcs.fz_mem_zone_free(NULL, (fzrt_ptr*)&shdr_data);
	}
	return(FZRT_NOERR);
}

/************************************************************************
	glossy_shade_pixel
		Shades a pixel.  Determines direction of reflection, and 
		depending on the current recursion depth, and the number of
		samples to use, gets the colors of the rays shot at blurry
		angles and averages them to compute color at this pixel.
		Potentiallly gets called multiple times for a single pixel
		because of recursive reflection raytracing.
		
		returns
			color computed at this pixel.
*************************************************************************/
static fz_rgb_float_td glossy_shade_pixel(void* shdr_data)
/************************************************************************/
{
	/* error handling */
	fzrt_error_td	err;
	/* precomputed data */
	glossy_data_td*	glossy;
	/* color computation */
	fz_rgb_float_td	col, diff_col, spec_col, ambt_col;
	double			diff_f, spec_f, ambt_f;
	fz_rgb_float_td	glossy_color_sample[MAX_NUM_RAYS];
	fz_rgb_float_td	glossy_color;
	double 			lin_interp_mirr_f;
	/* glossy raytracing */
	double			mirr_factor;
	fz_xyz_td		world_pt;
	fz_xyz_td		norm_vec, geom_norm_vec;
	fz_xyz_td		view_vec, mirr_vec, orig_mirr_vec;
	fz_xyz_td		mirr_pt, norm_pt, orig_mirr_pt;
	fz_xyz_td		cross_prod, cross_prod_pt, tmp_vec;
	double			phi, theta;
	fzrt_int 			i;
	double			dot_legal_refl;
	fzrt_boolean	is_refl,cross_diff,orig_diff;

	/* initialize  */
	
	glossy = (glossy_data_td*)shdr_data;
	
	glossy_color.red   = 0.0;
	glossy_color.green = 0.0;
	glossy_color.blue  = 0.0;

	spec_col.red = 0.0;
	spec_col.green = 0.0;
	spec_col.blue = 0.0;

	/* get illumination info already computed at this pixel */
	
	_shdr_funcs.fz_shdr_get_ambient_factor(&ambt_f);
	_shdr_funcs.fz_shdr_get_diffuse_factor(&diff_f);
	_shdr_funcs.fz_shdr_get_specular_factor(&spec_f);
	_shdr_funcs.fz_shdr_get_ambient_term(&ambt_col);
	_shdr_funcs.fz_shdr_get_diffuse_term(&diff_col);
	_shdr_funcs.fz_shdr_get_mirror_factor(&mirr_factor);
	
	/* calculate glossiness only if mirror factor exists and glossy data exists */
	if (glossy != NULL && mirr_factor > 0.0)
	{
		_shdr_funcs.fz_shdr_get_specular_term(glossy->specular_shininess, &spec_col);
		
		/* point of intersection */
		_shdr_funcs.fz_shdr_get_world_pnt(&world_pt);
		/* normal at point of intersection, interpolated by smooth shading */
		_shdr_funcs.fz_shdr_get_world_shading_normal(&norm_vec);
		/* vector pointing to eye at point of intersection */
		_shdr_funcs.fz_shdr_get_view_dir(&view_vec);
		/* compute mirror direction based on view direction and normal at point of intrs */
		_shdr_funcs.fz_shdr_ray_reflect(&view_vec, &norm_vec, &orig_mirr_vec);

		/* if we've already been reflected, then reflect only one additional ray */
		_shdr_funcs.fz_shdr_raytrace_depth(&is_refl, NULL);
		if (is_refl)
		{
			_shdr_funcs.fz_shdr_raytrace_reflected(&world_pt, &orig_mirr_vec, mirr_factor, &glossy_color);
		}
		else /* top-level, raytrace as many samples as specified to average them later */
		{
			/* copy */
			mirr_vec.x = orig_mirr_vec.x;
			mirr_vec.y = orig_mirr_vec.y;
			mirr_vec.z = orig_mirr_vec.z;

			/* find points to rotate */
			mirr_pt.x = mirr_vec.x + world_pt.x;
			mirr_pt.y = mirr_vec.y + world_pt.y;
			mirr_pt.z = mirr_vec.z + world_pt.z;
			norm_pt.x = norm_vec.x + world_pt.x;
			norm_pt.y = norm_vec.y + world_pt.y;
			norm_pt.z = norm_vec.z + world_pt.z;

			/* copy */
			orig_mirr_pt.x = mirr_pt.x;
			orig_mirr_pt.y = mirr_pt.y;
			orig_mirr_pt.z = mirr_pt.z;
			
			/* create axis to rotate about */
			if (fabs(mirr_vec.x-norm_vec.x) > 1e-6 ||
				fabs(mirr_vec.y-norm_vec.y) > 1e-6 ||
				fabs(mirr_vec.z-norm_vec.z) > 1e-6)
			{
				_math_funcs.fz_math_3d_cross_prod(&mirr_vec, &norm_vec, &cross_prod);
			}
			else
			{
				/* special case: colinear, use another vec to get the perpendicular */
				if (fabs(mirr_vec.x) < 1e-6 && fabs(mirr_vec.y) < 1e-6)
				{
					/* change x or y */
					tmp_vec.x = mirr_vec.x+1;
					tmp_vec.y = mirr_vec.y;
					tmp_vec.z = mirr_vec.z;
				}
				else
				{
					/* change z */
					tmp_vec.x = mirr_vec.x;
					tmp_vec.y = mirr_vec.y;
					tmp_vec.z = mirr_vec.z+1;
				}
				_math_funcs.fz_math_3d_cross_prod(&mirr_vec, &tmp_vec, &cross_prod);
			}
			
			cross_prod_pt.x = cross_prod.x + world_pt.x;
			cross_prod_pt.y = cross_prod.y + world_pt.y;
			cross_prod_pt.z = cross_prod.z + world_pt.z;
			
			cross_diff = TRUE;
			orig_diff = TRUE;
			
			/* cannot rotate if intersection point is same as axis to rotate */
			/* about point. */
			if (fabs(world_pt.x - cross_prod_pt.x) < 1e-6 &&
				fabs(world_pt.y - cross_prod_pt.y) < 1e-6 &&
				fabs(world_pt.z - cross_prod_pt.z) < 1e-6)
			{
				cross_diff = FALSE;
			}
			
			/* cannot rotate around axis if intersection point is */
			/* same as original mirror direction point. */
			if (fabs(world_pt.x - orig_mirr_pt.x) < 1e-6 &&
				fabs(world_pt.y - orig_mirr_pt.y) < 1e-6 &&
				fabs(world_pt.z - orig_mirr_pt.z) < 1e-6)
			{
				orig_diff = FALSE;
			}
			
			/* iterate through all sample rays */
			for (i = 0; i < glossy->num_rays; i++)
			{
				/* angle in radians to rotate, tilting away from the original mirror direction */
				phi   = _math_funcs.fz_math_random_range(glossy->range_min_phi[i], glossy->range_max_phi[i]);
				
				/* angle in radians to rotate around the original mirror direction axis. */
				theta = _math_funcs.fz_math_random_range(glossy->range_min_theta[i], glossy->range_max_theta[i]);
				
				if (cross_diff)
				{
					/* rotate away from original mirror direction */
					err = _math_funcs.fz_math_3d_rot_pt_around_line(
						&world_pt,
						&cross_prod_pt,
						&mirr_pt,
						phi,
						&mirr_pt);
					
					if (err == FZRT_NOERR)
					{
						if (orig_diff)
						{
							/* rotate around original mirror direction axis */
							err = _math_funcs.fz_math_3d_rot_pt_around_line(
								&world_pt,
								&orig_mirr_pt,
								&mirr_pt,
								theta,
								&mirr_pt);

							if (err == FZRT_NOERR)
							{
								/* new glossy direction vector */
								mirr_vec.x = mirr_pt.x - world_pt.x;
								mirr_vec.y = mirr_pt.y - world_pt.y;
								mirr_vec.z = mirr_pt.z - world_pt.z;
								
								/* check that we're reflecting within the surface */
								/* hemisphere, using the normal to the surface. */
								/* if not, don't raytrace reflect into the object. */
								_shdr_funcs.fz_shdr_get_world_geom_normal(&geom_norm_vec);
								dot_legal_refl = FZ_MATH_3D_DOT_PROD(&mirr_vec, &norm_vec);
								if (dot_legal_refl > 0)
								{
									/* get glossy color */
									_shdr_funcs.fz_shdr_raytrace_reflected(&world_pt, &mirr_vec, mirr_factor, &glossy_color_sample[i]);

									glossy_color.red   += glossy_color_sample[i].red;
									glossy_color.green += glossy_color_sample[i].green;
									glossy_color.blue  += glossy_color_sample[i].blue;
								} /* else ray reflects into material, */
								  /* so, arbitrarily, absorb this ray (contributes 0) */
							}/* else problem with rotation about axis */
						}/* else problem with points */
					}/* else problem with rotation away from mirror dir */
				} /* else problem with points */
				
				/* done with this sample, go on to next sample */
			}
			/* done with all samples */

			glossy_color.red   *= (float)(glossy->ray_contribution);
			glossy_color.green *= (float)(glossy->ray_contribution);
			glossy_color.blue  *= (float)(glossy->ray_contribution);
		}
		
		/* have raytraced with one or many samples at this point. */
		
	} /* else no mirror factor */

	/* get the original *pure* surface color (according to color shader) */
	_shdr_funcs.fz_shdr_get_col(&col);

	/* apply ambient, diffuse, specular terms to original color (ie. take into account *lighting*) */
	col.red   = (float)(col.red   * (ambt_f * ambt_col.red   + diff_f * diff_col.red  ) + spec_col.red   * spec_f);
	col.green = (float)(col.green * (ambt_f * ambt_col.green + diff_f * diff_col.green) + spec_col.green * spec_f);
	col.blue  = (float)(col.blue  * (ambt_f * ambt_col.blue  + diff_f * diff_col.blue ) + spec_col.blue  * spec_f);

	/* add mirror reflection and current color together, treat them as mutually exclusive */
	lin_interp_mirr_f = 1.0 - mirr_factor;
	col.red   = (float)(lin_interp_mirr_f * col.red   + mirr_factor * glossy_color.red);
	col.green = (float)(lin_interp_mirr_f * col.green + mirr_factor * glossy_color.green);
	col.blue  = (float)(lin_interp_mirr_f * col.blue  + mirr_factor * glossy_color.blue);

	return(col);
}
