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

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

	File:			snow.c
	Author:			auto*des*sys, Inc.
	Created on:		02/27/04
	Description:	Snow foreground shader plugin.  Shades foreground
					with volumetric snow.

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

/* standard headers */

#include <stdlib.h>

/* this plugin's header */

#include "snow.h"

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

#include "fz_plugin_glue_api.h"

/* generic plugin functions */
static fzrt_error_td	snow_check_config(const fzpl_fset_glue_fset* const fset_glue, const fzpl_host_config_td* const hostConfig);
static fzrt_error_td	snow_acquire_fsets(void);
static void				snow_release_fsets(void);
static fzrt_error_td	snow_register_plugins(const fzpl_fset_glue_fset* const fset_glue, const fzpl_host_config_td* const hostConfig);
static fzrt_error_td	snow_finit(const fzpl_fset_glue_fset* const fset_glue);

/* function called by formZ to fill a shader's function set */
/* with pointers to snow's own callback functions. */
static fzrt_error_td	snow_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	snow_uuid(fzrt_UUID_td uuid);
static fzrt_error_td 	snow_name(char* name, fzrt_int max_len);
static fzrt_error_td 	snow_io_version(fzpl_vers_td* version);
static fzrt_error_td	snow_set_parameters(void);
static fzrt_error_td 	snow_pre_render(void** shdr_data);
static fzrt_error_td 	snow_post_render(void* shdr_data);
static fz_rgb_float_td	snow_shade_pixel(void* shdr_data);



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

/************************************************************************
	snow_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 snow_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 = snow_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 snow resource file eg. snow.ENU.fzr */
				err = _base_funcs.fzrt_fzr_open(floc, "snow", &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(_visibility, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 4, buffer);
					strcpy(_density, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 5, buffer);
					strcpy(_snow_scale, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 6, buffer);
					strcpy(_do_noise, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 7, buffer);
					strcpy(_max_dist, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 8, buffer);
					strcpy(_do_accum, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 9, buffer);
					strcpy(_accum_scale, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 10, buffer);
					strcpy(_accum_percent, buffer);
					_base_funcs.fzrt_fzr_get_string(resource_ref, 1, 11, buffer);
					strcpy(_accum_percent_fuzz, buffer);

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

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

	return err;
}


/************************************************************************
	snow_acquire_fsets
		Acquires formZ provided service function sets.
*************************************************************************/
static fzrt_error_td snow_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;
}

/************************************************************************
	snow_release_fsets
		Releases all function sets used from formZ.
*************************************************************************/
static void snow_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 );
}

 

/************************************************************************
	snow_register_plugins
		Registers plugins and their associated function sets.
*************************************************************************/
static fzrt_error_td snow_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( SNOW_PLUGIN_UUID,
									 _name,
									 SNOW_PLUGIN_VERSION,
									 SNOW_PLUGIN_VENDOR,
									 SNOW_PLUGIN_URL,
									 FZ_SHDR_EXTS_TYPE,
									 FZ_SHDR_EXTS_VERSION,
									 NULL /*error string function*/,
									 0,
									 NULL,
									 &_snow_plugin_runtime_id);
	if (err == FZRT_NOERR)
	{
		/* register a foreground shader fset for the plugin with matching runtime id. */
		err = fset_glue->fzpl_plugin_add_fset( _snow_plugin_runtime_id,
										 FZ_SHDR_CBAK_FGND_FSET_TYPE,
										 FZ_SHDR_CBAK_FGND_FSET_VERSION,
										 FZ_SHDR_CBAK_FGND_FSET_NAME,
										 FZPL_TYPE_STRING(fz_shdr_cbak_fgnd_fset),
										 sizeof(fz_shdr_cbak_fgnd_fset),
										 snow_fill_fset,
										 FALSE);
	}

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

	return err;
}

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

	err = fset_glue->fzpl_plugin_unregister(_snow_plugin_runtime_id);

	snow_release_fsets();

	return err;
}


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

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

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

			install_struct->fz_shdr_cbak_fgnd_uuid				= snow_uuid;
			install_struct->fz_shdr_cbak_fgnd_name				= snow_name;
			install_struct->fz_shdr_cbak_fgnd_version			= snow_io_version;
			install_struct->fz_shdr_cbak_fgnd_set_parameters	= snow_set_parameters;
			install_struct->fz_shdr_cbak_fgnd_pre_render		= snow_pre_render;
			install_struct->fz_shdr_cbak_fgnd_post_render		= snow_post_render;
			install_struct->fz_shdr_cbak_fgnd_pixel				= snow_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 = snow_check_config(fset_glue, hostConfig);
		break;
		
		case FZPL_PLUGIN_INITIALIZE:
			err = snow_register_plugins(fset_glue, hostConfig);
		break;
		
		case FZPL_PLUGIN_EXIT:
			err = snow_finit(fset_glue);
		break;
	}

	return (err);
}









/************************************************************************
						Snow Shader Data Structures
*************************************************************************/

/************************************************************************
	snow_data_td
	
		Description
			Options/Parameters that a user can change to alter how
			the snow shader behaves.
			
		Members
			visibility		- how visible the snow flakes are.
			vis_ninety		- 95% of visibility.
			density			- amount of snow flakes per volume throughout
							  scene.
			snow_scale		- how large snow flakes are.
			num_samples		- how many samples to use along a ray to sample
							  in the volume.
			turb_threshold	- threshold where a snow flake is found.
			turb_threshold2	- threshold where to transparent part of snow
			                  flake is found.
			do_noise		- a filter to create more natural-looking snow.
			max_dist		- the maximum distance to use in calculations.
			max_dist_inv	- inverse of maximum distance.
			do_accum		- whether or not to accumulate snow or snow on
			                  tops of structures.
			accum_scale		- how large puddles or patches of snow appear.
			accum_percent	- how much accumulation appears.
			accum_percent_fuzz - how much accumulation to blur.
*************************************************************************/
typedef struct
{
	double	visibility;
	double	vis_ninety;
	double	density;
	double	snow_scale;
	fzrt_int 	num_samples;
	double	turb_threshold;
	double	turb_threshold2;
	fzrt_boolean do_noise;
	double	max_dist;
	double	max_dist_inv;
	fzrt_boolean do_accum;
	double	accum_scale;
	double	accum_percent;
	double	accum_percent_fuzz;
	fz_mat4x4_td snow_mat;
	fz_mat4x4_td accum_mat;
	
} snow_data_td;




/************************************************************************
						  Snow Shader Callbacks
*************************************************************************/

/************************************************************************
	snow_uuid
		Returns the UUID of this shader's function set.
*************************************************************************/
static fzrt_error_td snow_uuid(fzrt_UUID_td uuid)
/************************************************************************/
{
	_base_funcs.fzrt_UUID_copy(SNOW_PLUGIN_FSET_UUID, uuid);
	return(FZRT_NOERR);
}
/************************************************************************
	snow_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 snow_name(char* name, fzrt_int max_len)
/************************************************************************/
{
	strncpy(name, _short_name, max_len);
	return(FZRT_NOERR);
}

/************************************************************************
	snow_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 snow_io_version(fzpl_vers_td* version)
/************************************************************************/
{
	*version = SNOW_PLUGIN_DATA_IO_VERSION;
	return(FZRT_NOERR);
}

/************************************************************************
	snow_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 snow_set_parameters(void)
/************************************************************************/
{
	/* setup slider for snow visibility (0 = more transparent to 100% = more visible). */
	_shdr_funcs.fz_shdr_set_sld_flt_parm(_visibility, 0.9, 1, 1, 0);
	/* setup slider for density (0 = less dense, 100% more dense) */
	_shdr_funcs.fz_shdr_set_sld_flt_parm(_density, 0.25, 1, 1, 1);
	/* setup slider for snow flake scale (0 = smallest, 100% largest) */
	_shdr_funcs.fz_shdr_set_sld_flt_parm(_snow_scale, 0.5, 1, 1, 2);
	/* blizzard on/off */
	_shdr_funcs.fz_shdr_set_bool_parm(_do_noise, 1, 3);
	/* float field for max distance to calculate snow to */
	_shdr_funcs.fz_shdr_set_flt_parm(_max_dist, 5000, 1, 10000, FZ_FUIM_FORMAT_FLOAT_DISTANCE, 1, 2, 4);
	/* accumulation on/off */
	_shdr_funcs.fz_shdr_set_bool_parm(_do_accum, 0, 5);
	/* setup slider for accumulation patches scale (0 = smallest patches, 100% largest) */
	_shdr_funcs.fz_shdr_set_sld_flt_parm(_accum_scale, 0.9, 1, 1, 6);
	/* setup slider for accum threshold, how much ground cover (0 = no ground cover, 100% complete ground cover) */
	_shdr_funcs.fz_shdr_set_sld_flt_parm(_accum_percent, 0.7, 1, 1, 7);
	/* setup slider for accum fuzz threshold (0 = no fuzz, value > accum_percent fuzziness to that percent value) */
	_shdr_funcs.fz_shdr_set_sld_flt_parm(_accum_percent_fuzz, 0.9, 1, 1, 8);

	return(FZRT_NOERR);
}


/************************************************************************
	snow_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 snow_pre_render(void** shdr_data)
/************************************************************************/
{
	fzrt_error_td	err = FZRT_NOERR;
	/* get params */
	fz_type_td		data;
	snow_data_td*	snow_data;
	fz_xyz_td		scale_vec;
	/* randomization */
	fz_xyz_td		eye;

	/* initialize */

	*shdr_data = NULL;

	/* keep randomness looking the same across successive renderings */
	/* but different for different eye points */
	_shdr_funcs.fz_shdr_get_view_eye(&eye);
	srand(1001 + (fzrt_int)(eye.x*100+eye.y*100+eye.z*100));

	/* allocate memory for parameters */
	err =_mem_funcs.fz_mem_zone_alloc(NULL,sizeof(snow_data_td),FALSE, (fzrt_ptr*)shdr_data);
	if (err == FZRT_NOERR)
	{
		snow_data = (snow_data_td*)*shdr_data;

		/* Get parameters in options dialog */

		_shdr_funcs.fz_shdr_get_parm(0, &data);
		_type_funcs.fz_type_get_double(&data, &snow_data->visibility);
		snow_data->visibility = snow_data->visibility*0.8 + 0.2;
		snow_data->vis_ninety = 0.95 * snow_data->visibility;
		_shdr_funcs.fz_shdr_get_parm(1, &data);
		_type_funcs.fz_type_get_double(&data, &snow_data->density);
		/* number of samples between 10 and 100 */
		snow_data->num_samples = (fzrt_int)(90*snow_data->density+10);
		/* most visible part of snow between 97-99% of turbulence function */
		snow_data->turb_threshold = 0.02*snow_data->density + 0.97;
		/* transparent part of snow between 92-97% of turbulence function */
		snow_data->turb_threshold2 = 0.05*snow_data->density + 0.92;
		
		_shdr_funcs.fz_shdr_get_parm(2, &data);
		_type_funcs.fz_type_get_double(&data, &snow_data->snow_scale);
		snow_data->snow_scale = 1 - snow_data->snow_scale;
		/* x & y scale between 0.2 and 0.4 */
		scale_vec.x = 0.2*snow_data->snow_scale + 0.2;
		scale_vec.y = 0.2*snow_data->snow_scale + 0.2;
		/* z scales between 0.06 and 0.1 (smaller than x,y for elongation) */
		scale_vec.z = 0.04*snow_data->snow_scale + 0.06;
		/* scale differently for preview */
		if (_shdr_funcs.fz_shdr_is_in_preview() == TRUE)
		{
			scale_vec.x *= 20;
			scale_vec.y *= 20;
			scale_vec.z *= 20;
		}
		_math_funcs.fz_math_4x4_set_scl_mat(&snow_data->snow_mat, &scale_vec);
		
		_shdr_funcs.fz_shdr_get_parm(3, &data);
		_type_funcs.fz_type_get_boolean(&data, &snow_data->do_noise);
		_shdr_funcs.fz_shdr_get_parm(4, &data);
		_type_funcs.fz_type_get_double(&data, &snow_data->max_dist);
		snow_data->max_dist_inv = 1.0 / snow_data->max_dist;
		
		_shdr_funcs.fz_shdr_get_parm(5, &data);
		_type_funcs.fz_type_get_boolean(&data, &snow_data->do_accum);
		_shdr_funcs.fz_shdr_get_parm(6, &data);
		_type_funcs.fz_type_get_double(&data, &snow_data->accum_scale);
		snow_data->accum_scale = 1 - snow_data->accum_scale;
		/* scale by 10% */
		snow_data->accum_scale *= 0.1;
		scale_vec.x = snow_data->accum_scale;
		scale_vec.y = snow_data->accum_scale;
		scale_vec.z = snow_data->accum_scale;
		_math_funcs.fz_math_4x4_set_scl_mat(&snow_data->accum_mat, &scale_vec);
		
		_shdr_funcs.fz_shdr_get_parm(7, &data);
		_type_funcs.fz_type_get_double(&data, &snow_data->accum_percent);
		snow_data->accum_percent = 1 - snow_data->accum_percent;
		_shdr_funcs.fz_shdr_get_parm(8, &data);
		_type_funcs.fz_type_get_double(&data, &snow_data->accum_percent_fuzz);
		snow_data->accum_percent_fuzz = 1 - snow_data->accum_percent_fuzz;
	}

	return(err);
}


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

/************************************************************************
	snow_shade_pixel
		Shades a pixel.  The fz_shdr_turbulance_3d function is used to
		determine where to place the snow flakes.  To do so, each ray is
		sampled along its ray for points at which the thresholds are
		crossed.  The volumetric approach has the advantage of causing
		to appear more dense weather in regions of space where there
		is more distance to an intersection point, and more sparse weather
		closer to the camera and at closer intersection points.  That is,
		the weather is distributed more evenly throughout the volume.
		If a non-volumetric approach is taken, a shader shades a
		scene causing a more flat look, but shading more quickly.
		
		returns
			color computed at this pixel.
*************************************************************************/
static fz_rgb_float_td snow_shade_pixel(void* shdr_data)
/************************************************************************/
{
	/* pre-computed data	 */
	snow_data_td*	snow_data;
	/* snow flakes */
	fz_rgb_float_td	col;
	double			dist_to_isect, dist, dist_f;
	fzrt_int			i;
	double			snow_intensity;
	fz_xyz_td		view_vec;
	fz_xyz_td		eye;
	fz_xyz_td		sample_pt;
	fz_rgb_float_td	sample_col;
	double 			res, cur_mag;
	double			incrmnt;
	fzrt_int			test_for_snowflake;
	double			fuzz;
	/* accumulate snow on top surfaces */
	fz_xyz_td		world_pt;
	fz_xyz_td		geom_norm;
	fz_xyz_td		up;
	double			dot_res;
	double			top;
	
	/* initialize */
	
	snow_data = (snow_data_td*)shdr_data;

	sample_col.red   = 0.0;
	sample_col.green = 0.0;
	sample_col.blue  = 0.0;
	
	snow_intensity = 0;

	/* get distance to intersection if any */
	_shdr_funcs.fz_shdr_get_dist_eye_world_pnt(&dist);
	dist_to_isect = dist;

	/* clamp distance (especially needed if hit a background pixel) */
	if (dist > snow_data->max_dist)
		dist = snow_data->max_dist;

	/* normalize */
	dist = dist*snow_data->max_dist_inv;
	/* reverse so that 0 = farthest away, 1 = closest */
	dist = 1 - dist;
	/* fade colors by at most 50% as max distance approaches */
	dist_f = dist*0.5 + 0.5;


	/* get the color at this pixel, as it has been computed by  */
	/* all other shaders. */
	_shdr_funcs.fz_shdr_get_col(&col);

	/* blizzardish noise */
	if (snow_data->do_noise == TRUE)
	{
		test_for_snowflake = (fzrt_int)(_math_funcs.fz_math_random_range(0.0, 2.0));
	}
	else
	{
		test_for_snowflake = 0;
	}
	
	if (test_for_snowflake == 0)
	{
		/* get current view direction */
		_shdr_funcs.fz_shdr_get_view_dir(&view_vec);
		
		/* get current eye point */
		_shdr_funcs.fz_shdr_get_view_eye(&eye);

		/* Sample along ray, up to a maximum distance */
		incrmnt = snow_data->max_dist / snow_data->num_samples;
		for(i = 0; i < snow_data->num_samples; i++)
		{
			/* current magnitude along ray to sample at */
			cur_mag = i*incrmnt;
			/* do not sample beyond distance of intersection */
			if (cur_mag > dist_to_isect)
			{
				break;
			}
			/* get sample world point */
			sample_pt.x = cur_mag * view_vec.x + eye.x;
			sample_pt.y = cur_mag * view_vec.y + eye.y;
			sample_pt.z = cur_mag * view_vec.z + eye.z;
			/* scale world point so that turbulence makes sense */
			_math_funcs.fz_math_4x4_multiply_mat_xyz(&snow_data->snow_mat, &sample_pt);
			/* compute turbulence */
			res = _shdr_funcs.fz_shdr_turbulance_3d(&sample_pt, 1, FZ_SHDR_TURB_TYPE_SIMPLE, 1);

			if (snow_data->turb_threshold < res)
			{
				/* intersection with most visible part of snow flake */
				dist = cur_mag * snow_data->max_dist_inv;
				dist = 1 - dist;
				dist = dist*0.5 + 0.5;
				snow_intensity = dist;
				sample_col.red   += (float)(snow_intensity);
				sample_col.green += (float)(snow_intensity);
				sample_col.blue  += (float)(snow_intensity);
			}
			else if (snow_data->turb_threshold2 < res)
			{
				/* intersection with edge of snow flake */
				fuzz = _shdr_funcs.fz_shdr_smooth_step(snow_data->turb_threshold2, snow_data->turb_threshold, res);
				dist = cur_mag * snow_data->max_dist_inv;
				dist = 1 - dist;
				dist = dist*0.5 + 0.5;
				snow_intensity = dist * fuzz;
				sample_col.red   += (float)(snow_intensity);
				sample_col.green += (float)(snow_intensity);
				sample_col.blue  += (float)(snow_intensity);

			}
		}
		
		/* reduce according to visibility */
		sample_col.red   *= (float)(snow_data->visibility);
		sample_col.green *= (float)(snow_data->visibility);
		sample_col.blue  *= (float)(snow_data->visibility);

		/* clamp */
		if (sample_col.red > 1)
			sample_col.red = 1;
		if (sample_col.green > 1)
			sample_col.green = 1;
		if (sample_col.blue > 1)
			sample_col.blue = 1;

		/* blend current color with weather, fade distance */
		col.red   = (float)(sample_col.red   + col.red   * dist_f);
		col.green = (float)(sample_col.green + col.green * dist_f);
		col.blue  = (float)(sample_col.blue  + col.blue  * dist_f);
	}
	else /* no snow flake in the way of this pixel */
	{
		col.red   = (float)(col.red   * dist_f);
		col.green = (float)(col.green * dist_f);
		col.blue  = (float)(col.blue  * dist_f);
	}

	/* does the snow accumulate on the top of surfaces? */
	if (snow_data->do_accum)
	{
		/* don't accumulate on the background plane */
		if (dist_to_isect < 1e6)
		{
			/* get the intersection point */
			_shdr_funcs.fz_shdr_get_world_pnt(&world_pt);
			/* uniformly scale world space to within noise function realm */
			/* so it looks reasonable */
			_math_funcs.fz_math_4x4_multiply_mat_xyz(&snow_data->accum_mat, &world_pt);
			/* get whether or not there's a blotch of snow here. */
			top = _shdr_funcs.fz_shdr_noise_3d(&world_pt, FZ_SHDR_TURB_TYPE_BEST, 1);
			/* depending on user settings, determine how much ground cover there is. */
			if (snow_data->accum_percent < top)
			{
				/* determine if this surface is upward facing */
				up.x = 0;
				up.y = 0;
				up.z = 1;
				_shdr_funcs.fz_shdr_get_world_shading_normal(&geom_norm);
				dot_res = FZ_MATH_3D_DOT_PROD(&geom_norm, &up);
				dot_res = snow_data->vis_ninety * _shdr_funcs.fz_shdr_smooth_step(0, 1, dot_res);
				/* add white-ish blanket of snow to color as appropriate */
				col.red   = (float)(dot_res * (1 - col.red  ) + col.red);
				col.green = (float)(dot_res * (1 - col.green) + col.green);
				col.blue  = (float)(dot_res * (1 - col.blue ) + col.blue);
			}
			/* fade between ground and snow patches */
			else if (snow_data->accum_percent_fuzz < top)
			{
				/* determine if this surface is upward facing */
				up.x = 0;
				up.y = 0;
				up.z = 1;
				_shdr_funcs.fz_shdr_get_world_shading_normal(&geom_norm);
				dot_res = FZ_MATH_3D_DOT_PROD(&geom_norm, &up);
				dot_res = snow_data->vis_ninety * _shdr_funcs.fz_shdr_smooth_step(0, 1, dot_res);
				/* compute fuzz */
				dot_res *= _shdr_funcs.fz_shdr_smooth_step(snow_data->accum_percent_fuzz, snow_data->accum_percent, top);
				/* add fuzzy white-ish blanket of snow to color as appropriate */
				col.red   = (float)(dot_res * (1 - col.red  ) + col.red);
				col.green = (float)(dot_res * (1 - col.green) + col.green);
				col.blue  = (float)(dot_res * (1 - col.blue ) + col.blue);
			}
		}
	}
		
	/* clamp */
	if (col.red < 0)
		col.red = 0;
	if (col.red > 1)
		col.red = 1;
	if (col.green < 0)
		col.green = 0;
	if (col.green > 1)
		col.green = 1;
	if (col.blue < 0)
		col.blue = 0;
	if (col.blue > 1)
		col.blue = 1;

	return(col);
}
