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

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

	File:			x_rndr_vector.c
	Author:			auto*des*sys, Inc.
	Created on:		02/03/04
	Description:	X-Renderer Vector Renderer plugin.
					Renders scene by rendering edges of objects such
					that the lines are a bit wiggly (not exactly
					straight), and perhaps don't even connect at the
					corners.

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

/* standard headers */

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

/* this renderer's header */

#include "x_rndr.h"



/************************************************************************
							Helper Functions
*************************************************************************/

/************************************************************************
	disturb
		returns a random integer within a range, the bounds of which
		are determined by the amount parameter
*************************************************************************/
static fzrt_int disturb(double amount)
/************************************************************************/
{
	fzrt_int disturbance;
	disturbance = (fzrt_int)(_math_funcs.fz_math_random_range(0.0-amount,0.99+amount));
	return disturbance;
}

/************************************************************************
	shorten_rnd
		returns a floating pt number used as a percentage.
*************************************************************************/
static double shorten_rnd(void)
/************************************************************************/
{
	return _math_funcs.fz_math_random_range(0.2,1.0);
}



/************************************************************************
							Wiggle Data Structures
*************************************************************************/

/************************************************************************
	x_vctr_opts_td

	Description
		Structure for options/parameters of this vector renderer.
		
	Members
		amplitude 			- maximum perpendicular pixel distance 
							  offset from original line
		amplitude_parallel	- maximum parallel pixel distance offset
							  from original point for now always set
							  to 1/4 of amplitude 
		resolution			- the number of pixels before a break.
				 	 		  larger values => lower res
		extend_beyond_end_pts - whether or not to extend beyond end
								points
		extend_len_abs		- extension beyond end points amount in
						 	  number of pixels
		connect_end_pts		- whether or not to perturb end points
		perturb_extens		- whether or not to perturb extensions
		rnd_shorten_extens	- whether or not to shorten extensions by
							  some random percentage.
		extend_len_relative_to_seg - whether or not to extend relative
									 to length of the line
		extend_len_rel		- extension beyond end points amount
						      relative to segment length
		random_seed			- allows user to alter the random look of
					 		  the rendered scene.	 
		res_max				- highest value for resolution allowed.
*************************************************************************/
typedef struct
{
	double			amplitude;
	double			amplitude_parallel; 
	double			resolution;
	fzrt_boolean	connect_end_pts;
	fzrt_boolean	extend_beyond_end_pts;
	double			extend_len_abs;
	fzrt_boolean	perturb_extens;
	fzrt_boolean	rnd_shorten_extens;
	fzrt_boolean	extend_len_relative_to_seg;
	double			extend_len_rel;
	unsigned short	random_seed;
	double			res_max;
} x_vctr_opts_td;

typedef x_vctr_opts_td* x_vctr_opts_td_ptr;


/************************************************************************
	Indices for parameters of this renderer.
*************************************************************************/
enum
{
	X_VCTR_PARM_AMPLITUDE = 0,
	X_VCTR_PARM_RESOLUTION,
	X_VCTR_PARM_CONNECT_END_PTS,
	X_VCTR_PARM_EXT_BYD_END_PTS,
	X_VCTR_PARM_EXT_AMOUNT,
	X_VCTR_PARM_PERTURB_EXT,
	X_VCTR_PARM_RND_SHORTEN_EXT,
	X_VCTR_PARM_SEED,
	
	X_VCTR_PARM_MAX
};



/************************************************************************
							Renderer Callbacks
*************************************************************************/


/************************************************************************
	x_vctr_wind_opts_defaults
		Sets default values for the user options on a window-level basis.
*************************************************************************/
static fzrt_error_td x_vctr_wind_opts_defaults(fzrt_int windex, fzrt_ptr x_vctr_opts_ptr)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_vctr_opts_td_ptr x_vctr_opts;
	
	x_vctr_opts = (x_vctr_opts_td_ptr)x_vctr_opts_ptr;
	if (x_vctr_opts != NULL)
	{
		/* set defaults */
		x_vctr_opts->res_max 			= 100.0;
		x_vctr_opts->amplitude			= 3.0;
		x_vctr_opts->amplitude_parallel	= x_vctr_opts->amplitude*0.25;
		x_vctr_opts->resolution			= 60.0;
		x_vctr_opts->extend_beyond_end_pts = FALSE;
		x_vctr_opts->extend_len_abs		= 10.0;
		x_vctr_opts->connect_end_pts	= TRUE;
		x_vctr_opts->perturb_extens		= FALSE;
		x_vctr_opts->rnd_shorten_extens	= FALSE;
		x_vctr_opts->extend_len_relative_to_seg = FALSE;
		x_vctr_opts->extend_len_rel		= 0.15;
		x_vctr_opts->random_seed		= 0;
	}
	
	return err;
}

/************************************************************************
	x_vctr_wind_opts_init
		Initializes the user options for this renderer (called for each
		window).
*************************************************************************/
static fzrt_error_td x_vctr_wind_opts_init(fzrt_int windex, fzrt_ptr x_vctr_opts_ptr)
/************************************************************************/
{
	fzrt_error_td err;
	
	err = x_vctr_wind_opts_defaults(windex, x_vctr_opts_ptr);
	
	return err;
}

/************************************************************************
	x_vctr_wind_opts_finit
		Cleans up user options (called for each window).
*************************************************************************/
static fzrt_error_td x_vctr_wind_opts_finit(fzrt_int windex, fzrt_ptr x_vctr_opts_ptr)
/************************************************************************/
{	
	return FZRT_NOERR;
}



/************************************************************************
	x_vctr_wind_opts_io
		Saves user options for this renderer (called for each window).
*************************************************************************/
static fzrt_error_td x_vctr_wind_opts_io(
	fzrt_int					windex,
	fz_iost_ptr				iost, 
	fz_iost_dir_td_enum		dir,	
	fzpl_vers_td * const version, 
	fzrt_ptr				data	
	)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_vctr_opts_td_ptr x_vctr_opts;
	
	x_vctr_opts = (x_vctr_opts_td_ptr) data;
	
	if((err = _iost_funcs.fz_iost_double(iost,&x_vctr_opts->amplitude,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_double(iost,&x_vctr_opts->amplitude_parallel,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_double(iost,&x_vctr_opts->resolution,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_boolean(iost,&x_vctr_opts->connect_end_pts,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_boolean(iost,&x_vctr_opts->extend_beyond_end_pts,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_double(iost,&x_vctr_opts->extend_len_abs,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_boolean(iost,&x_vctr_opts->perturb_extens,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_boolean(iost,&x_vctr_opts->rnd_shorten_extens,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_boolean(iost,&x_vctr_opts->extend_len_relative_to_seg,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_double(iost,&x_vctr_opts->extend_len_rel,1)) == FZRT_NOERR &&
	   (err = _iost_funcs.fz_iost_ushort(iost,&x_vctr_opts->random_seed,1)) == FZRT_NOERR
	  )
	{
		err = _iost_funcs.fz_iost_double(iost,&x_vctr_opts->res_max,1);
	}

	return err;
}



/************************************************************************
	x_vctr_wind_opts_copy
		Copies one set of user options for one window to another window's
		buffer of user options.
*************************************************************************/
static fzrt_error_td x_vctr_wind_opts_copy(fzrt_int src_windex, fzrt_ptr src_opts_ptr, fzrt_int dst_windex, fzrt_ptr dst_opts_ptr)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;

	if (err == FZRT_NOERR && dst_opts_ptr != NULL)
	{
		memcpy(dst_opts_ptr, src_opts_ptr, sizeof(x_vctr_opts_td));
	}
	
	return err;
}

/************************************************************************
	x_vctr_wind_opts_cmp
		Compares two buffers (containing user options data) for equality.
		returns TRUE if they're the same, FALSE otherwise.
*************************************************************************/
static fzrt_boolean x_vctr_wind_opts_cmp(fzrt_ptr src_opts_ptr, fzrt_ptr dst_opts_ptr)
/************************************************************************/
{
	fzrt_boolean rv = FALSE;
	
	rv = (memcmp(dst_opts_ptr, src_opts_ptr, sizeof(x_vctr_opts_td)) == 0);
	
	return rv;
}


/************************************************************************
	x_vctr_init
		Initializes renderer.
*************************************************************************/
static fzrt_error_td x_vctr_init(void)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	
	return err;
}

/************************************************************************
	x_vctr_finit
		Cleans up as renderer quits.
*************************************************************************/
static fzrt_error_td x_vctr_finit(void)
/************************************************************************/
{
	return FZRT_NOERR;
}

/************************************************************************
	x_vctr_name
		Returns name of renderer.
*************************************************************************/
static fzrt_error_td x_vctr_name(char* str_name, fzrt_int max_len)
/************************************************************************/
{
	char str[STRING_SIZE];

	_base_funcs.fzrt_fzr_get_string(_rsrc_ref, X_VCTR_STRINGS, X_VCTR_NAME_STR, str);
	strncpy(str_name, str, max_len);

	return FZRT_NOERR;
}

/************************************************************************
	x_vctr_uuid
		Returns uuid of renderer.
*************************************************************************/
static fzrt_error_td x_vctr_uuid(fzrt_UUID_td uuid)
/************************************************************************/
{
	_base_funcs.fzrt_UUID_copy(X_VCTR_PLUGIN_UUID, uuid);
	return FZRT_NOERR;
}

/************************************************************************
	x_vctr_opts_name
		Returns name of renderer's options.
*************************************************************************/
static fzrt_error_td x_vctr_opts_name(char* str_name, fzrt_int max_len)
/************************************************************************/
{
	char str[FZPL_NAME_SIZE];
	
	_base_funcs.fzrt_fzr_get_string(_rsrc_ref, X_VCTR_STRINGS, X_VCTR_OPTS_NAME_STR, str);
	strncpy(str_name, str, max_len);

	return FZRT_NOERR;
}

/************************************************************************
	x_vctr_opts_uuid
		Returns uuid of renderer's options.
*************************************************************************/
static fzrt_error_td x_vctr_opts_uuid(fzrt_UUID_td uuid)
/************************************************************************/
{
	_base_funcs.fzrt_UUID_copy(X_VCTR_PLUGIN_TMPL_UUID, uuid);
	return FZRT_NOERR;
}


/************************************************************************
	x_vctr_info
		Returns general info about renderer.
*************************************************************************/
static fzrt_error_td x_vctr_info(
	fz_rndr_type_enum		*type,
	fz_rndr_behave_enum		*behave,
	fzrt_int					*proj_data_size,
	fzrt_int					*wind_data_size,
	fzrt_int					*wind_opts_size
	)
/************************************************************************/
{
	/* what this renderer is */
	*type 	= FZ_RNDR_TYPE_VECTR;
	*behave = FZ_RNDR_BEHAVE_STATIC;
	/* sizes of data for formZ to manage about project, window, and options */
	*proj_data_size = 0;
	*wind_data_size = 0;
	*wind_opts_size = sizeof(x_vctr_opts_td);
	
	return FZRT_NOERR;
}

/************************************************************************
	x_vctr_view_type
		Tells formZ what views this renderer supports.
*************************************************************************/
static fzrt_boolean x_vctr_view_type(fzrt_int windex, fz_view_ptr view)
/************************************************************************/
{
	fzrt_boolean 		rv = FALSE;	
  	fz_type_td 			value;
 	fz_view_type_enum	view_type;

 	_view_funcs.fz_view_get_parm_data(windex, view, FZ_VIEW_PARM_TYPE, &value);
 	_type_funcs.fz_type_get_enum(&value, (fzrt_int*)&view_type);

	switch (view_type)
	{	
  		case FZ_VIEW_TYPE_AXONOMETRIC:
		case FZ_VIEW_TYPE_ISOMETRIC:
		case FZ_VIEW_TYPE_PERSPECTIVE:
		case FZ_VIEW_TYPE_OBLIQUE:
    		rv = TRUE;
    		break;
    	case FZ_VIEW_TYPE_PANORAMIC:
    		rv = FALSE;
    		break;
   	}

	return rv; 
}


/************************************************************************
	x_vctr_attr
		Tells formZ what rendering attributes this renderer cares about
		when rendering.
*************************************************************************/
static fzrt_boolean x_vctr_attr(fzrt_int windex, fz_rndr_attr_enum rndr_attr)
/************************************************************************/
{
	fzrt_boolean rv = FALSE;
	switch(rndr_attr)
	{
		case FZ_RNDR_ATTR_IN_PVIEW:				/* offer renderer preview in dialog */
			rv = TRUE;
			break;
		case FZ_RNDR_ATTR_RADIOS:				/* not radiosity solution	 */
		case FZ_RNDR_ATTR_RADIOS_PVIEW:			/* not radiosity solution in preview */
		case FZ_RNDR_ATTR_ALPHA_CHANNEL:		/* not a pixel renderer that supports alpha */
		case FZ_RNDR_ATTR_SUN_ONLY:				/* not a pixel renderer that uses only the sun */
		case FZ_RNDR_ATTR_CAN_SS:				/* not super-samplable */
		case FZ_RNDR_ATTR_STAY_IN_IACT_MODE:	/* if switching temporarily out of static rendering, stay out? */
		case FZ_RNDR_ATTR_USE_PARTIAL_IMAGE:	/* allow partial image rendering in options dialog */
		case FZ_RNDR_ATTR_USE_SAVE_IMAGE:		/* allow saving of image after rendering in options dialog */
		case FZ_RNDR_ATTR_USE_SHAD_PROJ_OPTS:	/* offer smooth shading options tab in dialog */
		case FZ_RNDR_ATTR_USE_TCTL_PROJ_OPTS:	/* offer the standard Texture Map Control options tab in options dialog  */
		case FZ_RNDR_ATTR_HANDLE_PIXBUFF:		/* don't do buffering by itself */
		case FZ_RNDR_ATTR_HANDLE_PANORAMIC:		/* not going to do panoramic drawing on its own, let formZ do the Panoramic drawing */
			rv = FALSE;
			break;
		default:
			break;
	}
	return rv;
}

/************************************************************************
	x_vctr_disp_attr
		Tells formZ what attributes are displayed for this renderer.
		returns TRUE if attribute is present.
				FALSE otherwise.
*************************************************************************/
static fzrt_boolean x_vctr_disp_attr(fzrt_int windex, fz_rndr_disp_attr_enum rndr_disp_attr)
/************************************************************************/
{
	fzrt_boolean rv;
	
	switch(rndr_disp_attr)
	{
		case FZ_RNDR_DISP_ATTR_GRID:	/* this renderer does not display grid */
		case FZ_RNDR_DISP_ATTR_AXIS:	/* axis, */
		case FZ_RNDR_DISP_ATTR_MARQUEE: /* axis markers, */
		case FZ_RNDR_DISP_ATTR_LITE:	/* graphic representation of lights, */
		case FZ_RNDR_DISP_ATTR_ULAY:	/* or underlay image */
		default:
			rv = FALSE;
			break;
	}
	return rv;
}


/************************************************************************
	x_vctr_wind_init
		Sets up when a window is opened.
*************************************************************************/
static fzrt_error_td x_vctr_wind_init(fzrt_int windex, fzrt_ptr wind_data)
/************************************************************************/
{
	return FZRT_NOERR;
}

/************************************************************************
	x_vctr_wind_finit
		Cleans up when a window is closed.
*************************************************************************/
static fzrt_error_td x_vctr_wind_finit(fzrt_int windex, fzrt_ptr wind_data)
/************************************************************************/
{
	return FZRT_NOERR;
}


/************************************************************************
	x_vctr_proj_init
		Sets up when a new project is opened/loaded.
*************************************************************************/
static fzrt_error_td x_vctr_proj_init(fzrt_int windex, fzrt_ptr proj_data_ptr)
/************************************************************************/
{
	return FZRT_NOERR;
}

/************************************************************************
	x_vctr_proj_finit
		Cleans up when a project is closed.
*************************************************************************/
static fzrt_error_td x_vctr_proj_finit(fzrt_int windex, fzrt_ptr proj_data_ptr)
/************************************************************************/
{
	return FZRT_NOERR;
}

/************************************************************************
	enum indices into fuim stack of data.
*************************************************************************/
enum
{
	X_VCTR_STACK_WINDEX = 0,
	
	X_VCTR_STACK_MAX
};

/************************************************************************
	x_vctr_opts_ok
		Called when an option has changed.  Refreshes screen immediately
		if called from palette, not dialog.
*************************************************************************/
static fzrt_boolean x_vctr_opts_ok(fz_fuim_tmpl_ptr fuim_tmpl, void* actn_data)
/************************************************************************/
{
	fzrt_int 				windex;
	x_vctr_opts_td_ptr	x_vctr_opts;
	fzrt_boolean 		rv  = TRUE;
	fzrt_error_td 		err = FZRT_NOERR;
	
	_fuim_funcs.fz_fuim_stack_get(fuim_tmpl, X_VCTR_STACK_WINDEX, &windex);

	/* Ensure amplitude is in correct proportion whenever  */
	/* it could possibly change. */
	err = _rndr_funcs.fz_rndr_wind_opts_get(windex, X_VCTR_PLUGIN_UUID, (fzrt_ptr*)&x_vctr_opts);
	x_vctr_opts->amplitude_parallel = x_vctr_opts->amplitude*0.25;

	return rv;
}

/************************************************************************
    x_vctr_fuim_setup
    	Creates user interface for user options/parameters in a dialog
    	or palette depending on where it is invoked.
*************************************************************************/
static fzrt_error_td x_vctr_fuim_setup(fzrt_int windex, fz_fuim_tmpl_ptr fuim_tmpl, fzrt_ptr fuim_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	short si1, si2, si3, si4, ei1, ei2, ei3, ei4, ci1, ci2, ci3, ci4;
	x_vctr_opts_td_ptr x_vctr_opts;
	char str[STRING_SIZE];
	
	err = _rndr_funcs.fz_rndr_wind_opts_get(windex, X_VCTR_PLUGIN_UUID, (fzrt_ptr*)&x_vctr_opts);

	if (err == FZRT_NOERR && x_vctr_opts != NULL)
	{
		/* setup UI template to place UI components on. */
		_base_funcs.fzrt_fzr_get_string(_rsrc_ref, X_VCTR_STRINGS, X_VCTR_OPTS_NAME_STR, str);
		err = _fuim_funcs.fz_fuim_tmpl_init(fuim_tmpl, str, FZ_FUIM_FLAG_NONE,X_VCTR_PLUGIN_TMPL_UUID,0);

		/* fuim callback to be called when okay button clicked. */
		_fuim_funcs.fz_fuim_tmpl_set_ok_func(fuim_tmpl, x_vctr_opts_ok, NULL);

		/* save the windex variable for use outside this fuim function. */
		_fuim_funcs.fz_fuim_stack_put(fuim_tmpl, X_VCTR_STACK_WINDEX, sizeof(windex), &windex);

		if (err == FZRT_NOERR)
		{
			/* double slider */
			_base_funcs.fzrt_fzr_get_string(
				_rsrc_ref, X_VCTR_STRINGS, AMPLITUDE_STR, str);
			_fuim_funcs.fz_fuim_new_slid_edit_double(fuim_tmpl, FZ_FUIM_ROOT, str, FZ_FUIM_NONE, FZ_FUIM_NONE, 0, 10, FZ_FUIM_RANGE_MIN, NULL, &x_vctr_opts->amplitude, &si1, &ei3);
			
			/* percent slider */
			_base_funcs.fzrt_fzr_get_string(_rsrc_ref, X_VCTR_STRINGS, RESOLUTION_STR, str);
			_fuim_funcs.fz_fuim_new_slid_edit_pcent_double(fuim_tmpl, FZ_FUIM_ROOT, str, FZ_FUIM_NONE, FZ_FUIM_NONE, 0, 99, 0, 100, FZ_FUIM_RANGE_MIN|FZ_FUIM_RANGE_MAX, NULL, &x_vctr_opts->resolution, &si2, &ei1);
			/* check box */
			_base_funcs.fzrt_fzr_get_string(_rsrc_ref, X_VCTR_STRINGS, CONNECT_STR, str);
			ci1 = _fuim_funcs.fz_fuim_new_check(fuim_tmpl, FZ_FUIM_ROOT, FZ_FUIM_NONE, FZ_FUIM_FLAG_NONE, str, NULL, NULL);
			_fuim_funcs.fz_fuim_item_unary_bool(fuim_tmpl, ci1, &x_vctr_opts->connect_end_pts, TRUE);
			
			/* check box with a group */
			_base_funcs.fzrt_fzr_get_string(
				_rsrc_ref, X_VCTR_STRINGS, EXT_BYND_STR, str);
			ci2 = _fuim_funcs.fz_fuim_new_check(
					fuim_tmpl, FZ_FUIM_ROOT, FZ_FUIM_NONE, FZ_FUIM_FLAG_BRDR, str, NULL, NULL);
			_fuim_funcs.fz_fuim_item_unary_bool(
					fuim_tmpl, ci2, &x_vctr_opts->extend_beyond_end_pts, TRUE);
			
			{
				/* double slider */
				_base_funcs.fzrt_fzr_get_string(
					_rsrc_ref, X_VCTR_STRINGS, EXT_LEN_STR, str);
				_fuim_funcs.fz_fuim_new_slid_edit_double(
						fuim_tmpl, ci2, str, FZ_FUIM_NONE, FZ_FUIM_NONE, 0, 50, FZ_FUIM_RANGE_MIN, NULL, &x_vctr_opts->extend_len_abs, &si3, &ei2);	
				/* check box */
				_base_funcs.fzrt_fzr_get_string(
					_rsrc_ref, X_VCTR_STRINGS, PERTURB_EXT_STR, str);
				ci3 = _fuim_funcs.fz_fuim_new_check(
						fuim_tmpl, ci2, FZ_FUIM_NONE, FZ_FUIM_FLAG_NONE, str, NULL, NULL);
				_fuim_funcs.fz_fuim_item_unary_bool(
						fuim_tmpl, ci3, &x_vctr_opts->perturb_extens, TRUE);
				/* check box		 */
				_base_funcs.fzrt_fzr_get_string(
					_rsrc_ref, X_VCTR_STRINGS, SHORTEN_LEN_STR, str);
				ci4 = _fuim_funcs.fz_fuim_new_check(
						fuim_tmpl, ci2, FZ_FUIM_NONE, FZ_FUIM_FLAG_NONE, str, NULL, NULL);
				_fuim_funcs.fz_fuim_item_unary_bool(
						fuim_tmpl, ci4, &x_vctr_opts->rnd_shorten_extens, TRUE);
			}
			/* short slider */
			_base_funcs.fzrt_fzr_get_string(
				_rsrc_ref, X_VCTR_STRINGS, RND_SEED_STR, str);
			_fuim_funcs.fz_fuim_new_slid_edit_short(
					fuim_tmpl, FZ_FUIM_ROOT, str, FZ_FUIM_NONE, FZ_FUIM_NONE, 0, 20, FZ_FUIM_RANGE_MIN|FZ_FUIM_RANGE_MAX, NULL, (short*)&x_vctr_opts->random_seed, &si4, &ei4);	
		}
	}
	return err;
}


/************************************************************************
    x_vctr_get_parm
    	Returns the value of the options parameter of this renderer
    	indicated by the index.
*************************************************************************/
static fzrt_error_td x_vctr_get_parm(fzrt_int windex, fzrt_int parm_indx, fz_type_td* data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_vctr_opts_td_ptr x_vctr_opts;

	err = _rndr_funcs.fz_rndr_wind_opts_get(windex, X_VCTR_PLUGIN_UUID, (fzrt_ptr*)&x_vctr_opts);
	
	if (err == FZRT_NOERR && data != NULL && parm_indx >= X_VCTR_PARM_AMPLITUDE && parm_indx < X_VCTR_PARM_MAX)
	{
		switch(parm_indx)
		{
			case X_VCTR_PARM_AMPLITUDE:
				_type_funcs.fz_type_set_double(&x_vctr_opts->amplitude, data);
				break;
			case X_VCTR_PARM_RESOLUTION:
				_type_funcs.fz_type_set_double(&x_vctr_opts->resolution, data);
				break;
			case X_VCTR_PARM_CONNECT_END_PTS:
				_type_funcs.fz_type_set_boolean(&x_vctr_opts->connect_end_pts, data);
				break;
			case X_VCTR_PARM_EXT_BYD_END_PTS:
				_type_funcs.fz_type_set_boolean(&x_vctr_opts->extend_beyond_end_pts, data);
				break;
			case X_VCTR_PARM_EXT_AMOUNT:
				_type_funcs.fz_type_set_double(&x_vctr_opts->extend_len_abs, data);
				break;
			case X_VCTR_PARM_PERTURB_EXT:
				_type_funcs.fz_type_set_boolean(&x_vctr_opts->perturb_extens, data);
				break;
			case X_VCTR_PARM_RND_SHORTEN_EXT:
				_type_funcs.fz_type_set_boolean(&x_vctr_opts->rnd_shorten_extens, data);
				break;
			case X_VCTR_PARM_SEED:
				_type_funcs.fz_type_set_ushort(&x_vctr_opts->random_seed, data);
				break;
		}
	}
	else
	{
		err = _base_funcs.fzrt_error_set(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0);
	}
	return err;
}

/************************************************************************
    x_vctr_set_parm
    	Sets the value of the options parameter of this renderer
    	indicated by the index with the given data.
*************************************************************************/
static fzrt_error_td x_vctr_set_parm(fzrt_int windex, fzrt_int parm_indx, fz_type_td* data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_vctr_opts_td_ptr x_vctr_opts;

	err = _rndr_funcs.fz_rndr_wind_opts_get(windex, X_VCTR_PLUGIN_UUID, (fzrt_ptr*)&x_vctr_opts);
	
	if (err == FZRT_NOERR && data != NULL && parm_indx >= X_VCTR_PARM_AMPLITUDE && parm_indx < X_VCTR_PARM_MAX)
	{
		switch(parm_indx)
		{
			case X_VCTR_PARM_AMPLITUDE:
				_type_funcs.fz_type_get_double(data, &x_vctr_opts->amplitude);
				/* ensure integrity. */
				x_vctr_opts->amplitude_parallel = x_vctr_opts->amplitude*0.25;
				break;
			case X_VCTR_PARM_RESOLUTION:
				_type_funcs.fz_type_get_double(data, &x_vctr_opts->resolution);
				break;
			case X_VCTR_PARM_CONNECT_END_PTS:
				_type_funcs.fz_type_get_boolean(data, &x_vctr_opts->connect_end_pts);
				break;
			case X_VCTR_PARM_EXT_BYD_END_PTS:
				_type_funcs.fz_type_get_boolean(data, &x_vctr_opts->extend_beyond_end_pts);
				break;
			case X_VCTR_PARM_EXT_AMOUNT:
				_type_funcs.fz_type_get_double(data, &x_vctr_opts->extend_len_abs);
				break;
			case X_VCTR_PARM_PERTURB_EXT:
				_type_funcs.fz_type_get_boolean(data, &x_vctr_opts->perturb_extens);
				break;
			case X_VCTR_PARM_RND_SHORTEN_EXT:
				_type_funcs.fz_type_get_boolean(data, &x_vctr_opts->rnd_shorten_extens);
				break;
			case X_VCTR_PARM_SEED:
				_type_funcs.fz_type_get_ushort(data, &x_vctr_opts->random_seed);
				break;
		}
	}
	else
	{
		err = _base_funcs.fzrt_error_set(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0);
	}
	return err;
}


/* used just for passing parameters between rendering functions */
typedef struct render_parms_td
{
	/* vector image export */
	fzrt_boolean do_export;
	fz_expt_vect_ptr expt_vect;
	/* wiggle algorithm */
	fzrt_int			numpoints;
	fzrt_int			numpoints_allocated;
	fzrt_point*		points; /* new set of points for each segment */
	fz_xy_td*		points_exp; /* new set of points for each segment */
}render_parms_td;


/************************************************************************
	draw_line
		Draws a line to screen or exported vector image.
*************************************************************************/
static void draw_line(fzrt_int windex, render_parms_td* render_parms, double x1, double y1, double x2, double y2)
{
	if (!render_parms->do_export)
	{
		_grph_funcs.fzrt_move_to((short)floor(x1+0.5), (short)floor(y1+0.5));
		_grph_funcs.fzrt_line_to((short)floor(x2+0.5), (short)floor(y2+0.5));
	}
	else
	{
		_expt_funcs.fz_expt_vect_draw_line(windex, render_parms->expt_vect, x1, y1, x2, y2);
	}
}


/************************************************************************
	draw_wiggle_line
		Given two points in world space, converts them to screen
		space, and performs wiggle algorithm, and draws them.
*************************************************************************/
static fzrt_error_td draw_wiggle_line(fzrt_int windex, x_vctr_opts_td* x_vctr_opts, render_parms_td* render_parms, fz_xyz_td* xyz1, fz_xyz_td* xyz2, fz_mat4x4_td* mat)
{
	fzrt_error_td err = FZRT_NOERR;
	/* transform space and clipping */
	fz_xyz_td		tform_c1, tform_c2;
	fz_view_clip_enum clip_stat = FZ_VIEW_CLIP_IN;
	fzrt_boolean	  clip1 = FALSE, clip2 = FALSE;
	/* wiggle algorithm with extra points */
	double			tx, ty;
	double 			ratio;
	fz_xy_td		vec2d_exp;
	fz_xy_td		vec2d_xy,perp_vec;
	double			dist2d;
	double			len2d;
	double			perp_turb_amount;
	double			parl_turb_amount;
	double			rnd_factor1, rnd_factor2;
	fz_xy_td		cur;
	fzrt_int			i;
	/* vector image points */
	fz_xy_td		p1, p2;
	
	if (mat != NULL)
	{
		/* tranform symbol points */
		_math_funcs.fz_math_4x4_multiply_mat_xyz(mat, xyz1);
		_math_funcs.fz_math_4x4_multiply_mat_xyz(mat, xyz2);
	}

	/* tform - clipped */
	_view_funcs.fz_view_math_world_2_tform_clipped(windex, xyz1, xyz2, NULL, NULL, &tform_c1, &tform_c2, &clip1, &clip2, &clip_stat);
	if (clip_stat != FZ_VIEW_CLIP_OUT)
	{
		/* then perhaps there is some visible portion of this segment */

		/* convert to vector space */
		_view_funcs.fz_view_math_tform_2_screen_xy(windex, &tform_c1, &p1);
		_view_funcs.fz_view_math_tform_2_screen_xy(windex, &tform_c2, &p2);

		/*														*/
		/* Now, in export image/screen space, perform wiggle	*/
		/* algorithm of breaking up this segment into			*/
		/* multiple segments and perturbing along it			*/
		/* randomly.											*/
		/*														*/
		
		/* vector between screen points */
		vec2d_exp.x = p2.x - p1.x;
		vec2d_exp.y = p2.y - p1.y;
		/* length of vector in screen pixel units */
		len2d = sqrt(vec2d_exp.x*vec2d_exp.x + vec2d_exp.y*vec2d_exp.y);
		
		/* ensure valid vector. */
		if ((vec2d_exp.x != 0 || vec2d_exp.y != 0) && len2d > 0.0)
		{
			/* only break up segments if */
			/* amplitude is greater than 0.0 because */
			/* otherwise we have resolution dependent */
			/* artifacts when ampl is 0.0. */
			if (x_vctr_opts->amplitude > 0.0)
			{
				render_parms->numpoints = (fzrt_int)(len2d/(x_vctr_opts->res_max - x_vctr_opts->resolution));
			}
			else
				render_parms->numpoints = 0;

			/* calc distance, not based on any beyond end-point extensions */
			dist2d = 1.0/(double)(render_parms->numpoints+1);

			vec2d_xy.x = vec2d_exp.x;
			vec2d_xy.y = vec2d_exp.y;
			_math_funcs.fz_math_2d_perp_vec(&vec2d_xy, &perp_vec);
			_math_funcs.fz_math_2d_normalize_vec(&perp_vec, &perp_vec);
			_math_funcs.fz_math_2d_normalize_vec(&vec2d_xy, &vec2d_xy);
			/* create more points */
			if (render_parms->numpoints > 0)
			{
				if (render_parms->numpoints_allocated < render_parms->numpoints)
				{
					if (render_parms->numpoints_allocated != 0)
					{
						/* reallocation */
						render_parms->numpoints_allocated = render_parms->numpoints*2;
						err = _mem_funcs.fz_mem_zone_realloc(NULL, (fzrt_ptr*)&render_parms->points_exp, render_parms->numpoints_allocated*sizeof(fz_xy_td));
					}
					else /* allocate for the first time */
					{
						render_parms->numpoints_allocated = render_parms->numpoints*2;
						err = _mem_funcs.fz_mem_zone_alloc(NULL, render_parms->numpoints_allocated*sizeof(fz_xy_td), FALSE, (fzrt_ptr*)&render_parms->points_exp);
					}
				}
			}
			if (render_parms->numpoints == 0 || (err == FZRT_NOERR && render_parms->points_exp != NULL))
			{
				/* set points locations and disturb them randomly perpendicular */
				/* to original line. */
				for (i = 0; i < render_parms->numpoints; i++)
				{
					render_parms->points_exp[i].x = vec2d_exp.x*dist2d*(i+1) + p1.x;
					render_parms->points_exp[i].y = vec2d_exp.y*dist2d*(i+1) + p1.y;

					perp_turb_amount = disturb(x_vctr_opts->amplitude);
					parl_turb_amount = disturb(x_vctr_opts->amplitude_parallel);

					render_parms->points_exp[i].x += perp_vec.x*perp_turb_amount;
					render_parms->points_exp[i].y += perp_vec.y*perp_turb_amount;
					render_parms->points_exp[i].x += vec2d_xy.x*parl_turb_amount;
					render_parms->points_exp[i].y += vec2d_xy.y*parl_turb_amount;
				}
				/* calc shorten rand numbers here first, even if not used */
				rnd_factor1 = shorten_rnd();
				rnd_factor2 = shorten_rnd();
				perp_turb_amount = disturb(x_vctr_opts->amplitude);
				parl_turb_amount = disturb(x_vctr_opts->amplitude_parallel);
				/* draw begining extension */
				if (x_vctr_opts->extend_beyond_end_pts == TRUE /*&&
					clip1 == FALSE *//*001: DUP CODE*/)
				{
					if (x_vctr_opts->extend_len_relative_to_seg == TRUE)
					{
						tx = vec2d_exp.x*-x_vctr_opts->extend_len_rel + p1.x;
						ty = vec2d_exp.y*-x_vctr_opts->extend_len_rel + p1.y;
					}
					else
					{
						ratio = -x_vctr_opts->extend_len_abs / len2d;
						if (x_vctr_opts->rnd_shorten_extens == TRUE)
							ratio *= rnd_factor1;
						tx = ratio*vec2d_exp.x + p1.x;
						ty = ratio*vec2d_exp.y + p1.y;
					}
					if (x_vctr_opts->perturb_extens == TRUE)
					{
						tx += perp_vec.x*perp_turb_amount;
						ty += perp_vec.y*perp_turb_amount;
						tx += vec2d_xy.x*parl_turb_amount;
						ty += vec2d_xy.y*parl_turb_amount;
					}
					cur.x = tx;
					cur.y = ty;
					
				}
				/* draw beginning end point */
				perp_turb_amount = disturb(x_vctr_opts->amplitude);
				parl_turb_amount = disturb(x_vctr_opts->amplitude_parallel);
				if (/*clip1 == TRUE || */x_vctr_opts->connect_end_pts == FALSE)
				{
					p1.x += perp_vec.x*perp_turb_amount;
					p1.y += perp_vec.y*perp_turb_amount;
					p1.x += vec2d_xy.x*parl_turb_amount;
					p1.y += vec2d_xy.y*parl_turb_amount;
				}
				if (x_vctr_opts->extend_beyond_end_pts == TRUE /*&&
					clip1 == FALSE *//*001: DUP CODE*/)
				{
					draw_line(windex, render_parms, cur.x, cur.y, p1.x, p1.y);
					cur.x = p1.x;
					cur.y = p1.y;
				}
				else
				{
					/*_grph_funcs.fzrt_move_to(px1, screen1.v); */
					cur.x = p1.x;
					cur.y = p1.y;
				}
				/* draw intermediate points */
				for (i = 0; i < render_parms->numpoints; i++)
				{
					draw_line(windex, render_parms, cur.x, cur.y, render_parms->points_exp[i].x, render_parms->points_exp[i].y);
					cur.x = render_parms->points_exp[i].x;
					cur.y = render_parms->points_exp[i].y;
				}
				/* draw ending end point */
				perp_turb_amount = disturb(x_vctr_opts->amplitude);
				parl_turb_amount = disturb(x_vctr_opts->amplitude_parallel);
				if (/*clip2 == TRUE || */x_vctr_opts->connect_end_pts == FALSE)
				{
					p2.x += perp_vec.x*perp_turb_amount;
					p2.y += perp_vec.y*perp_turb_amount;
					p2.x += vec2d_xy.x*parl_turb_amount;
					p2.y += vec2d_xy.y*parl_turb_amount;
				}
				draw_line(windex, render_parms, cur.x, cur.y, p2.x, p2.y);
				cur.x = p2.x;
				cur.y = p2.y;

				/* draw ending extension */
				perp_turb_amount = disturb(x_vctr_opts->amplitude);
				parl_turb_amount = disturb(x_vctr_opts->amplitude_parallel);
				if (x_vctr_opts->extend_beyond_end_pts == TRUE/* &&
					clip2 == FALSE*/)
				{
					if (x_vctr_opts->extend_len_relative_to_seg == TRUE)
					{
						tx = vec2d_exp.x*x_vctr_opts->extend_len_rel + p2.x;
						ty = vec2d_exp.y*x_vctr_opts->extend_len_rel + p2.y;
					}
					else
					{
						ratio = x_vctr_opts->extend_len_abs / len2d;
						if (x_vctr_opts->rnd_shorten_extens == TRUE)
							ratio *= rnd_factor2;
						tx = ratio*vec2d_exp.x + p2.x;
						ty = ratio*vec2d_exp.y + p2.y;
					}
					
					if (x_vctr_opts->perturb_extens == TRUE)
					{									
						tx += perp_vec.x*perp_turb_amount;
						ty += perp_vec.y*perp_turb_amount;
						tx += vec2d_xy.x*parl_turb_amount;
						ty += vec2d_xy.y*parl_turb_amount;
					}
					
					draw_line(windex, render_parms, cur.x, cur.y, tx, ty);

				}
														 
			}
			else /* else if mem not allocated */
			{
				/* No wiggles */
				/* draw line between two points */
				draw_line(windex, render_parms, p1.x, p1.y, p2.x, p2.y);
			}
		}
		else /* else not a valid vector (beg and end point are the same) */
		{
			draw_line(windex, render_parms, p1.x, p1.y, p2.x, p2.y);
		}
		
	} /* skip if completely clipped */
	
	return err;
}

/************************************************************************
	render_object
		Walks an object in the scene, and renders it.
*************************************************************************/
static fzrt_error_td render_object(fzrt_int windex, x_vctr_opts_td* x_vctr_opts, render_parms_td* render_parms, fz_objt_ptr obj, fz_mat4x4_td* mat)
{
	fzrt_error_td	err = FZRT_NOERR;
	/* trace object */
	fzrt_boolean	valid;
	fz_attr_visib_enum vis;
	fz_objt_model_type_enum query_model_type = FZ_OBJT_MODEL_TYPE_UNSPEC, actual_model_type;
	fzrt_int 			face_count = -1, curve_count = -1, seg_count = -1, tot_seg_count = -1;
	fzrt_int			curve_iter;
	fzrt_int			face_index, crv_index, seg_index, beg_seg_index, rev_index;
	fzrt_int 			beg_pnt_index, end_pnt_index;
	fzrt_boolean*	vis_seg; /* already visited a segment */
	fzrt_int			i;
	/* show color */
	fz_rgb_float_td	mtl_color_float;
	fzrt_rgb_color_td mtl_color;
	fz_rmtl_ptr		rmtl;
	fz_tag_td		rmtl_tag;
	/* world points */
	fz_xyz_td		xyz1;
	fz_xyz_td		xyz2;
	/* smooth objects */
	fzrt_int npts, niso, j;
	/* vector image export */
	fz_tag_td layr_tag;
	fz_layr_ptr layr;
	fzrt_int from_face, from_curve;
	
	_model_funcs.fz_objt_alys_objt_is_valid(windex, obj, &valid);
	_model_attr_funcs.fz_objt_attr_get_objt_visib(windex, obj, TRUE, &vis);
	if (vis == FZ_ATTR_VISIB_VISIB && valid == TRUE) /* object is visible and valid */
	{
		/* get color of object to use for pen */
		_model_attr_funcs.fz_objt_attr_get_objt_rmtl(windex, obj, TRUE, &rmtl_tag);
		_rmtl_funcs.fz_rmtl_tag_to_ptr(windex, &rmtl_tag, &rmtl);
		
		_rmtl_funcs.fz_rmtl_get_avg_color(windex, rmtl, &mtl_color_float);
		mtl_color.red   = (unsigned short)(mtl_color_float.red  *65535);
		mtl_color.green = (unsigned short)(mtl_color_float.green*65535);
		mtl_color.blue  = (unsigned short)(mtl_color_float.blue *65535);
	 	
	 	/* set current pen color */
		if (!render_parms->do_export)
			_grph_funcs.fzrt_rgb_fore_color(&mtl_color);
		else
			_expt_funcs.fz_expt_vect_set_line_color(windex, render_parms->expt_vect, &mtl_color);
		
		/* for vector image export, tell exporter which object and layer line comes from */
		if (render_parms->do_export)
		{
			_expt_funcs.fz_expt_vect_set_objt(windex, render_parms->expt_vect, obj);
			_model_attr_funcs.fz_objt_attr_get_objt_layer(windex, obj, &layr_tag);
			_layr_funcs.fz_layr_tag_to_ptr(windex, &layr_tag, &layr);
			_expt_funcs.fz_expt_vect_set_layr(windex, render_parms->expt_vect, layr);
		}
		
		/* For each object, get faces. */
		_model_funcs.fz_objt_get_face_count(windex, obj, query_model_type, &face_count);
		
		_model_funcs.fz_objt_get_segt_count(windex, obj, query_model_type, &tot_seg_count);
		
		err = _mem_funcs.fz_mem_zone_alloc(NULL, sizeof(fzrt_boolean)*tot_seg_count, FALSE, (fzrt_ptr*)&vis_seg);
		
		if (err == FZRT_NOERR)
		{
			for (i = 0; i < tot_seg_count; i++)
			{
				vis_seg[i] = FALSE;
			}
			
			_model_funcs.fz_objt_alys_get_objt_model_type(windex, obj, &actual_model_type);
			if (actual_model_type == FZ_OBJT_MODEL_TYPE_FACT)
			{
				/* draw facets of object */
				
				/* iterate through faces */
				for (face_index = 0; face_index < face_count; face_index++)
				{
					/* for vector image export, tell exporter which face line comes from */
					if (render_parms->do_export)
					{
						_expt_funcs.fz_expt_vect_set_tag(windex, render_parms->expt_vect, FZ_EXPT_VECT_TAG_FACE, face_index);
					}

					_model_funcs.fz_objt_face_get_curv_count(windex, obj, face_index, query_model_type, &curve_count);
					
					_model_funcs.fz_objt_face_get_cindx(windex, obj, face_index, query_model_type, &crv_index);
					/* crv_index is the first, outer curve of this face. */
					
					/* iterate through curves (list of segments) */
					for (curve_iter = 0; curve_iter < curve_count; curve_iter++)
					{
						_model_funcs.fz_objt_curv_get_segt_count(windex, obj, crv_index, query_model_type, &seg_count);
						
						/* for each curve, trace through its segments, go on to next curve. */
						_model_funcs.fz_objt_curv_get_sindx(windex, obj, crv_index, query_model_type, &seg_index);
						beg_seg_index = seg_index;
						
						
						while (err == FZRT_NOERR)
						{
							/* does this segment have a reverse segment? */
							_model_funcs.fz_objt_segt_get_reverse(windex, obj, seg_index, query_model_type, &rev_index);
							
							/* don't visit same segment twice, so only one */
							/* wiggled line appears on screen.  So, to draw, */
							/* this segment must not have been visited, and */
							/* the reverse must not have been visited or does not */
							/* exist. */
							if (vis_seg[seg_index] == FALSE &&
								((rev_index != -1 && vis_seg[rev_index] == FALSE) || rev_index == -1))
							{
								vis_seg[seg_index] = TRUE;
								if (rev_index != -1)
									vis_seg[rev_index] = TRUE;
								
								/* get beg and end point indices of current segment */
								_model_funcs.fz_objt_segt_get_start_pindx(windex, obj, seg_index, query_model_type, &beg_pnt_index);
								_model_funcs.fz_objt_segt_get_end_pindx(windex, obj, seg_index, query_model_type, &end_pnt_index);
								
								/* get the world coordinates of points */
								_model_funcs.fz_objt_point_get_xyz(windex, obj, beg_pnt_index, query_model_type, &xyz1);
								if (end_pnt_index >= 0)
									_model_funcs.fz_objt_point_get_xyz(windex, obj, end_pnt_index, query_model_type, &xyz2);
								else
									xyz2 = xyz1;
								
								err = draw_wiggle_line(windex, x_vctr_opts, render_parms, &xyz1, &xyz2, mat);

							} /* skip if already visisted */
							
							/* go on to next segment */
							_model_funcs.fz_objt_segt_get_next(windex, obj, seg_index, query_model_type, &seg_index);
															
							if (seg_index == beg_seg_index ||
								seg_index == -1)
							{
								/* at beginning of outline/curve again, OR, */
								/* at end of open curve, so go on to next curve. */
								break;
							}
						}
						
						/* get next curve */
						_model_funcs.fz_objt_curv_get_next(windex, obj, crv_index, query_model_type, &crv_index);
					
					}
					/* done with curves of this face, go on to next face. */
				}
			}
			else /* smooth object */
			{
				/* draw wire edges - per segment */
				for(i = 0; i < tot_seg_count && err == FZRT_NOERR; i++)
				{
					/* for vector image export, tell exporter which face line comes from */
					if (render_parms->do_export)
					{
						_model_funcs.fz_objt_segt_get_fromcurv(windex, obj, i, actual_model_type, &from_curve);
						_model_funcs.fz_objt_curv_get_fromface(windex, obj, from_curve, actual_model_type, &from_face);
						_expt_funcs.fz_expt_vect_set_tag(windex, render_parms->expt_vect, FZ_EXPT_VECT_TAG_FACE, from_face);
					}

					/* does this segment have a reverse segment? */
					_model_funcs.fz_objt_segt_get_reverse(windex, obj, i, actual_model_type, &rev_index);
					
					/* don't visit same segment twice, so only one */
					/* doodled line appears on screen.  So, to draw, */
					/* this segment must not have been visited, and */
					/* the reverse must not have been visited or does not */
					/* exist. */
					if (vis_seg[i] == FALSE &&
						((rev_index != -1 && vis_seg[rev_index] == FALSE) || rev_index == -1))
					{
						vis_seg[i] = TRUE;
						if (rev_index != -1)
							vis_seg[rev_index] = TRUE;
						
						_model_funcs.fz_objt_segt_get_num_wire_pnts(windex, obj, i, &npts);
						for(j = 0; j < npts-1 && err == FZRT_NOERR; j++)
						{
							_model_funcs.fz_objt_segt_get_wire_pnt(windex, obj, i, j, &xyz1);
							_model_funcs.fz_objt_segt_get_wire_pnt(windex, obj, i, j+1, &xyz2);
							
							err = draw_wiggle_line(windex, x_vctr_opts, render_parms, &xyz1, &xyz2, mat);
						}
					}
				}
			
				/* draw iso lines - per face */
				_model_funcs.fz_objt_get_face_count(windex, obj, actual_model_type, &face_count);
				for (face_index = 0; face_index < face_count && err == FZRT_NOERR; face_index++)
				{
					/* for vector image export, tell exporter which face line comes from */
					if (render_parms->do_export)
					{
						_expt_funcs.fz_expt_vect_set_tag(windex, render_parms->expt_vect, FZ_EXPT_VECT_TAG_FACE, face_index);
					}

					_model_funcs.fz_objt_face_get_iso_count(windex, obj, face_index, &niso);
					for(i = 0; i < niso && err == FZRT_NOERR; i++)
					{
						_model_funcs.fz_objt_iso_get_num_iso_pnts(windex, obj, face_index, i, &npts);
						for(j = 0; j < npts-1 && err == FZRT_NOERR; j++)
						{
							_model_funcs.fz_objt_iso_get_iso_pnt(windex, obj, face_index, i, j, &xyz1);
							_model_funcs.fz_objt_iso_get_iso_pnt(windex, obj, face_index, i, j+1, &xyz2);
							
							err = draw_wiggle_line(windex, x_vctr_opts, render_parms, &xyz1, &xyz2, mat);
						}
					}
				}
			}
		}
		else /* error with vis_seg allocation, stop the loop */
		{
			err = _base_funcs.fzrt_error_set(X_ERROR_VCTR_RENDER, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _x_rndr_plugin_runtime_id);
		}
		
		if (err == FZRT_NOERR)
			err = _mem_funcs.fz_mem_zone_free(NULL, (fzrt_ptr*)&vis_seg);
	}/* is visible and valid */

	return err;
}



static fzrt_error_td traverse_symbol(fzrt_int windex, x_vctr_opts_td* x_vctr_opts, render_parms_td* render_parms, fz_objt_ptr sym_obj, fz_mat4x4_td* cur_mat);

/************************************************************************
	found_symbol
		Prepares for the traversing of a symbol, by transforming the
		current matrix by the symbol's matrix.
*************************************************************************/
static fzrt_error_td found_symbol(fzrt_int windex, x_vctr_opts_td* x_vctr_opts, render_parms_td* render_parms, fz_objt_ptr sym_obj, fz_mat4x4_td* cur_mat)
{
	fzrt_error_td err = FZRT_NOERR;
	fz_mat4x4_td mat, backup;
	fzrt_boolean has_backup;

	/* apply current matrix to this symbol's one */
	_model_funcs.fz_objt_symb_ins_get_matrix(windex, sym_obj, &mat);
	has_backup = FALSE;
	if (cur_mat != NULL)
	{
		has_backup = TRUE;
		backup = *cur_mat;
		_math_funcs.fz_math_4x4_multiply_mat_mat(&mat, cur_mat, cur_mat);
	}
	else
	{
		cur_mat = &mat;
	}

	/* traverse the symbol object */
	err = traverse_symbol(windex, x_vctr_opts, render_parms, sym_obj, cur_mat);

	/* restore matrix */
	if (has_backup)
	{
		*cur_mat = backup;
	}
	else
	{
		cur_mat = NULL;
	}

	return err;
}

/************************************************************************
	traverse_symbol
		Traverses a symbol object, which may include other symbols and
		objects, for the purpose of rendering it.
*************************************************************************/
static fzrt_error_td traverse_symbol(fzrt_int windex, x_vctr_opts_td* x_vctr_opts, render_parms_td* render_parms, fz_objt_ptr sym_obj, fz_mat4x4_td* cur_mat)
{
	fzrt_error_td err = FZRT_NOERR;
	fzrt_int i;
	fz_symb_lev_ptr lev;
	fz_type_td data;
	fzrt_int num_sym_objs;
	fz_objt_ptr obj_next;
	fzrt_UUID_td uuid;
		
	/* iterate through symbol's objects */
	_model_funcs.fz_objt_edit_symb_parm_get(windex, sym_obj, FZ_OBJT_SYMB_PARM_LEVPTR, &data);
	_type_funcs.fz_type_get_ptr(&data, (void**)&lev);
	
	/* make sure we have a level -- there is no level if symbol library file not found */
	if (lev != NULL)
	{
		_sym_funcs.fz_symb_lev_model_get_num_objts(windex, lev, &num_sym_objs);
		/* begin symbol iteration through its objects */
		for (i = 0; i < num_sym_objs && err == FZRT_NOERR; i++)
		{
			_sym_funcs.fz_symb_lev_model_get_objt(windex, lev, i, &obj_next);
			
			_model_funcs.fz_objt_alys_get_objt_cntl_uuid(windex, obj_next, uuid);
			if (_base_funcs.fzrt_UUID_compare(uuid, FZ_OBJT_TYPE_SYMB) == 0)
			{
				/* found another symbol */
				err = found_symbol(windex, x_vctr_opts, render_parms, obj_next, cur_mat);
			}
			else
			{
				/* found object */

				/* place here so each object drawn the same */
				/* regardless of its order in the list. */
				/* use object's address so that each object is drawn differently from each other, yet same random way every rendering. */
				/* add parameter so user can controlably alter the random look of it. */
				srand((fzrt_int)sym_obj + i + x_vctr_opts->random_seed);

				err = render_object(windex, x_vctr_opts, render_parms, obj_next, cur_mat);
			}
		}
	}
	return err;
}

/************************************************************************
	traverse_objects
		Walks all objects in the scene, including symbols and renders
		them.
*************************************************************************/
static fzrt_error_td traverse_objects(fzrt_int windex, x_vctr_opts_td* x_vctr_opts, render_parms_td* render_parms, fz_mat4x4_td* cur_mat)
{
	fzrt_error_td err = FZRT_NOERR;
	/* object tracing */
	fz_objt_ptr		obj_next;
	/* iterating through symbol objects */
	fzrt_UUID_td uuid;

	/*												*/
	/* Trace topology of the geometry of the scene. */
	/*												*/
	
	/* get the first object on the list */
	_model_funcs.fz_objt_get_next(windex, NULL, &obj_next);
	/* we've found the first object. */
	
	while (obj_next != NULL && err == FZRT_NOERR) /* quit when obj_next is NULL (end of object list) */
	{
		_model_funcs.fz_objt_alys_get_objt_cntl_uuid(windex, obj_next, uuid);
		if (_base_funcs.fzrt_UUID_compare(uuid, FZ_OBJT_TYPE_SYMB) == 0)
		{
			/* found symbol */
			err = found_symbol(windex, x_vctr_opts, render_parms, obj_next, cur_mat);
		}
		else
		{
			/* found object */
		
			/* place here so each object drawn the same */
			/* regardless of its order in the list. */
			/* use object's address so that each object is drawn differently from each other, yet same random way every rendering. */
			/* add parameter so user can controlably alter the random look of it. */
			srand((fzrt_int)obj_next + x_vctr_opts->random_seed);
			
			err = render_object(windex, x_vctr_opts, render_parms, obj_next, cur_mat);
		}
		
		_model_funcs.fz_objt_get_next(windex, obj_next, &obj_next);
	}
	
	return err;
}



/************************************************************************
	render_scene
		Walks all objects in the scene, including symbols, and renders
		them.
*************************************************************************/
static fzrt_error_td render_scene(fzrt_int windex, render_parms_td* render_parms)
{
	/* error catching */
	fzrt_error_td err = FZRT_NOERR;
	/* use options */
	x_vctr_opts_td* x_vctr_opts;
	/* clear background */
	fzrt_rect		screen_rect;
	/* iterating through symbol objects */

	render_parms->numpoints_allocated = 0;

	render_parms->points = NULL;
	render_parms->points_exp = NULL;
	
	_rndr_funcs.fz_rndr_wind_opts_get(windex, X_VCTR_PLUGIN_UUID, (fzrt_ptr*)&x_vctr_opts);
	
	if (x_vctr_opts != NULL)
	{
		if (!render_parms->do_export)
		{
			/* erase the window rect if rendering to the window */
			_wind_funcs.fz_wind_get_image_rect(windex, &screen_rect);
			_grph_funcs.fzrt_erase_rect(&screen_rect);
		}
		
		err = traverse_objects(windex, x_vctr_opts, render_parms, NULL);

		/* delete memory used for extra points along segment lengths */
		if (err == FZRT_NOERR && render_parms->points != NULL)
		{
			err = _mem_funcs.fz_mem_zone_free(NULL, (fzrt_ptr*)&render_parms->points);
		}
		if (err == FZRT_NOERR && render_parms->points_exp != NULL)
		{
			err = _mem_funcs.fz_mem_zone_free(NULL, (fzrt_ptr*)&render_parms->points_exp);
		}
		
	} /* else cannot find x_vctr_opts */
	
	if (err != FZRT_NOERR)
	{
		err = _base_funcs.fzrt_error_set(X_ERROR_VCTR_RENDER, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _x_rndr_plugin_runtime_id);
	}
	
	return err;
}



/************************************************************************
    x_vctr_image_prep
    	Prepares window for rendering.
*************************************************************************/
static fzrt_error_td x_vctr_image_prep(fzrt_int windex)
/************************************************************************/
{
	return FZRT_NOERR;
}



/************************************************************************
	x_vctr_image_render
		Renders the scene.  Walks objects in scene.  For each segment,
		creates several new line segments in screen space, and perturbs
		the points to create a wiggly, haphazard look.
*************************************************************************/
static fzrt_error_td x_vctr_image_render(fzrt_int windex, fzrt_error_td prep_err, fzrt_rect *rect)
/************************************************************************/
{
	/* error catching */
	fzrt_error_td	err;
	/* render params */
	render_parms_td render_parms;
	
	/* initialize */
	
	err = prep_err;

	render_parms.expt_vect = NULL;
	render_parms.do_export = FALSE;

	if (err == FZRT_NOERR)
		err = render_scene(windex, &render_parms);

	return err;
}


/************************************************************************
	x_vctr_image_finit
		Cleans up after having rendered the image.
*************************************************************************/
static fzrt_error_td x_vctr_image_finit(fzrt_int windex)
/************************************************************************/
{
	return FZRT_NOERR;
}


/************************************************************************
	x_vctr_export_image
		Renders the scene, but instead of calculating screen coordinates,
		calculates more precise vector image coordinates, and instead of
		outputing to the screen, outputs to a vector image format.  The
		same wiggle algorithm as in image_disp is used.
*************************************************************************/
static fzrt_error_td x_vctr_export_image(fzrt_int windex, char* prg_str, char* fname, fzrt_boolean do_back, fz_expt_vect_ptr expt_vect)
/************************************************************************/
{

	/* error catching */
	fzrt_error_td	err;
	/* render params */
	render_parms_td render_parms;
	
	/* initialize */

	render_parms.expt_vect = expt_vect;
	render_parms.do_export = TRUE;

	err = render_scene(windex, &render_parms);

	return err;
}


/************************************************************************
	x_vctr_activate
		Called when this renderer is selected.
*************************************************************************/
static fzrt_error_td x_vctr_activate(fzrt_int windex)
/************************************************************************/
{
	return FZRT_NOERR;
}

/************************************************************************
	x_vctr_deactivate
		Called when this renderer has been deselected.
*************************************************************************/
static fzrt_error_td x_vctr_deactivate(fzrt_int windex)
/************************************************************************/
{
	return FZRT_NOERR;
}


/************************************************************************
	x_vctr_export_check_feature
		Called to inform formZ what types of vector data will be exported.
*************************************************************************/
static fzrt_error_td x_vctr_export_check_feature( fzrt_int windex, fz_rndr_expt_vect_feature_enum rndr_expt_vect_feature, fzrt_boolean *check)
/************************************************************************/
{
	if(check != NULL)
	{
		switch(rndr_expt_vect_feature)
		{
			case FZ_RNDR_EXPT_VECT_FEATURE_EXPT_LAYER:
				*check = TRUE;
			break;

			case FZ_RNDR_EXPT_VECT_FEATURE_EXPT_OBJ:
				*check = TRUE;
			break;

			case FZ_RNDR_EXPT_VECT_FEATURE_EXPT_FACE:
				*check = TRUE;
			break;

			case FZ_RNDR_EXPT_VECT_FEATURE_EXPT_COLOR:
				*check = TRUE;
			break;

			case FZ_RNDR_EXPT_VECT_FEATURE_EXPT_FILL_POLY:
				*check = FALSE;
			break;
			
			default:
				break;
		}
	}

	return(FZRT_NOERR);
}


/************************************************************************
							Renderer Fill Function
*************************************************************************/

/************************************************************************
	x_vctr_fill_rndr_fset
		Fills the fset structure with the callback functions for a
		renderer plugin.
*************************************************************************/
fzrt_error_td x_vctr_fill_rndr_fset(const fzpl_fset_def_ptr fset_def, fzpl_fset_td* const fset)
/************************************************************************/
{
	fzrt_error_td			err = FZRT_NOERR;
	fz_rndr_cbak_fset*	rndr_fset;

	if (fset_def && fset)
	{
		err = _fset_glue->fzpl_fset_def_check( fset_def,
										 FZ_RNDR_CBAK_FSET_VERSION,
										 FZPL_TYPE_STRING(fz_rndr_cbak_fset),
										 sizeof(fz_rndr_cbak_fset),
										 FZPL_VERSION_OP_NEWER );
		if (err == FZRT_NOERR)
		{
			rndr_fset = (fz_rndr_cbak_fset*)fset;

			/* renderer management/interaction w/formZ */
			rndr_fset->fz_rndr_cbak_init 					= x_vctr_init;
			rndr_fset->fz_rndr_cbak_info 					= x_vctr_info;
			rndr_fset->fz_rndr_cbak_finit 					= x_vctr_finit;
			rndr_fset->fz_rndr_cbak_name 					= x_vctr_name;
			rndr_fset->fz_rndr_cbak_uuid 					= x_vctr_uuid;
			rndr_fset->fz_rndr_cbak_opts_name 				= x_vctr_opts_name;
			rndr_fset->fz_rndr_cbak_opts_uuid 				= x_vctr_opts_uuid;
			rndr_fset->fz_rndr_cbak_attr					= x_vctr_attr;
			rndr_fset->fz_rndr_cbak_disp_attr				= x_vctr_disp_attr;
			rndr_fset->fz_rndr_cbak_handle_view				= x_vctr_view_type;
			rndr_fset->fz_rndr_cbak_activate				= x_vctr_activate;
			rndr_fset->fz_rndr_cbak_deactivate				= x_vctr_deactivate;
			
			/* project management for renderer */
			rndr_fset->fz_rndr_cbak_proj_data_init			= x_vctr_proj_init;
			rndr_fset->fz_rndr_cbak_proj_data_finit			= x_vctr_proj_finit;
			rndr_fset->fz_rndr_cbak_proj_clear_mem			= NULL;
			
			/* window management for renderer */
			rndr_fset->fz_rndr_cbak_wind_data_init			= x_vctr_wind_init;
			rndr_fset->fz_rndr_cbak_wind_data_finit			= x_vctr_wind_finit;
			rndr_fset->fz_rndr_cbak_wind_opts_init			= x_vctr_wind_opts_init;
			rndr_fset->fz_rndr_cbak_wind_opts_default			= x_vctr_wind_opts_defaults;
			rndr_fset->fz_rndr_cbak_wind_opts_io			= x_vctr_wind_opts_io;
			rndr_fset->fz_rndr_cbak_wind_opts_copy			= x_vctr_wind_opts_copy;
			rndr_fset->fz_rndr_cbak_wind_opts_are_equal		= x_vctr_wind_opts_cmp;
			rndr_fset->fz_rndr_cbak_wind_opts_finit			= x_vctr_wind_opts_finit;

			/* rendering functions */
			rndr_fset->fz_rndr_cbak_image_init				= x_vctr_image_prep;
			rndr_fset->fz_rndr_cbak_image_disp				= x_vctr_image_render;
			rndr_fset->fz_rndr_cbak_image_finit				= x_vctr_image_finit;
			rndr_fset->fz_rndr_cbak_image_inval				= NULL;
			rndr_fset->fz_rndr_cbak_image_dirty				= NULL;
			
			/* image export for renderer */
			rndr_fset->fz_rndr_cbak_expt_vect_out				= x_vctr_export_image;
			rndr_fset->fz_rndr_cbak_expt_vect_check_feature		= x_vctr_export_check_feature;
		
			/* user interface for renderer */
			rndr_fset->fz_rndr_cbak_iface_tmpl				= x_vctr_fuim_setup;
			rndr_fset->fz_rndr_cbak_get_parm				= x_vctr_get_parm;
			rndr_fset->fz_rndr_cbak_set_parm				= x_vctr_set_parm;
		
			/* necessary resources for distributed rendering */
			rndr_fset->fz_rndr_cbak_proj_linked_files		= NULL;
		}
	}
	else
		err = _base_funcs.fzrt_error_set(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0);

	return err;
}



