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

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

	File:			x_rndr_pixel.c
	Author:			auto*des*sys, Inc.
	Created on:		02/12/04
	Description:	X-Renderer Pixel Renderer Plugin.
					Renders the scene using a simple raytracing algorithm
					to simulate global illumination.

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

/* standard headers */

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

/* this renderer's header */

#include "x_rndr.h"


fzrt_zone_ptr _zone = NULL;
fzrt_int _last_face;

static fzrt_error_td x_pixel_notf_wind(fzrt_int windex, fz_notf_wind_enum wind_notf, fz_notf_proj_enum proj_notf);

/************************************************************************
			Surface style representation essentials
*************************************************************************/
static fzrt_int	_x_srep_indx = -1;
typedef struct
{
	float	ambient;
	float	diffuse;
	float	specular;
	float	specular_expo;

} x_srep_td;

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

static double x_max(double a, double b)
{
	if (a >= b)
		return a;
	return b;
}

static double x_min(double a, double b)
{
	if (a < b)
		return a;
	return b;
}


/************************************************************************
						Raytracing Data Structures
*************************************************************************/

/************************************************************************
	bbox_td

	Description
		Data structure type that represents an axis-aligned bounding box.

	Members
		min_xyz - the minimum xyz coordinate of the axis-aligned
				  bounding box.
		max_xyz - the maximum xyz coordinate of the axis-aligned
				  bounding box.
*************************************************************************/
typedef struct bbox_td
{
	fz_xyz_td min_xyz;
	fz_xyz_td max_xyz;
} bbox_td;


/************************************************************************
	tctl_td

	Description
		Data structure type that represents a texture control.
		
	Members
*************************************************************************/
typedef struct tctl_td
{
	fz_xyz_td origin;
	fz_xyz_td rotation;
	double	  size_x;
	double	  size_y;
	double	  solid_size;
	double	  size_x_inv;
	double	  size_y_inv;
	double	  solid_size_inv;
	fz_map_space_type_enum map_type;
	fzrt_boolean	cntr_horz;
	fzrt_boolean	cntr_vert;
	fzrt_boolean	flip;
} tctl_td;


/************************************************************************
	texture_td

	Description
		Data structure type that represents a texture, either made from
		raw pixels, or procedurally.

	Members
		width			- width of the texture array
		height			- height of the texture array
		pixels			- array of rgb data defining the texture
		tmap			- original texture map reference from formZ.
		tmap_floc		- file location of texture.
		tmap_file_found	- whether or not the tmap file was found on disk (may
					  	  have been moved or renamed).
		tmap_surf_style	- the surface style that has this texture map.
		next			- the next texture in the list of textures.
*************************************************************************/
typedef struct texture_td
{
	fzrt_int				width;
	fzrt_int				height;
	fz_rgb_float_td		*pixels;
	fz_tmap_ptr			tmap;
	fzrt_floc_ptr		tmap_floc;
	fzrt_boolean		tmap_file_found;
	fz_rmtl_ptr			tmap_surf_style;
	struct texture_td*	next;
} texture_td;

/************************************************************************
	texture_group_td

	Description
		Data structure type that represents a texture group, which is
		useful to know how to use a particular texture based on the
		texture control.  So, a set of faces of an object would belong
		to the same texture group.

	Members
		tctl	- texture control used by this texturing group
		next	- the next texture group in the object's list
*************************************************************************/
typedef struct texture_group_td
{
	tctl_td tctl;
	struct texture_group_td* next;
} texture_group_td;


/************************************************************************
	face_td

	Description
		Data structure type that represents a triangular face of an
		object.
		
	Members
		p1				- point 1
		p2				- point 2
		p3				- point 3
		normal			- normal vector of the face
		normal_inv		- inverted normal
		face_plane		- negated plane where this face sits
		color	        - color of the face
		ambient_factor	- percent of ambient light this material reflects
		diffuse_factor  - percent of diffuse color to contribute
		specular_factor - percent of specular highlight to contribute
		specular_expo   - shininess of specular highlight
		attr_obj		- object where attributes should come from
		attr_face_index	- index into original object's list of faces
						  where this face originally came from
		tgroup			- texture group defines how this face
						  is textured, if it has a texture
		texture			- texture of this face
		scale_texture_inv - inverse of scale factor for texture of this
							face
*************************************************************************/
typedef struct face_td
{
	fz_rmtl_ptr mtrl;
	fz_xyz_td p1,p2,p3;
	fz_xyz_td normal;
	fz_xyz_td normal_inv;
	fz_plane_equ_td face_plane;
	fz_rgb_float_td color;
	double ambient_factor;
	double diffuse_factor;
	double specular_factor;
	double specular_expo;
	fz_objt_ptr attr_obj;
	fzrt_int attr_face_index;
	texture_group_td* tgroup;
	texture_td* texture;
	double scale_texture_inv;
} face_td;

/************************************************************************
	obj_td

	Description
		Data structure type that represents an object.  This is different
		from formZ's object representation in that all this renderer
		cares about is an object's faces, bounding box, texture groups,
		and whose next in the list of objects.
		
	Members
		ref				- reference to original formZ object.  for
						  symbols, this reference is shared by all
						  the symbols with the same object.
		notf_sym_inst	- this object receives notification from this
						  symbol instance.
		which_obj_in_sym - the index of this obj within the objs of
						   the symbol instance.
		numfaces		- number of faces this object has.
		faces			- array of faces of this object.
		bbox			- bounding box of the entire object.
		tgroup			- first texture group in the object's list of
						  texture groups.
		next			- next obj_td in the list.
*************************************************************************/
typedef struct obj_td
{
	fz_objt_ptr ref;
	fz_objt_ptr notf_sym_inst;
	fzrt_int which_obj_in_sym;
	fzrt_int num_faces;
	face_td* faces;
	bbox_td bbox;
	fzrt_boolean	visible;
	texture_group_td* tgroup;
	struct obj_td* next;
} obj_td;



/************************************************************************
	x_pixel_opts_td

	Description
		Structure for options/parameters of this pixel renderer.
		
	Members
		have_shadows - whether or not to calculate color of pixels
		               obstructed by other objects from light sources
		do_specular  - whether or not to calculate the specular
		               highlight on all objects with specular factor
		do_texturing - whether or not to apply textures to objects if
					   they have them.
*************************************************************************/
typedef struct x_pixel_opts_td
{
	fzrt_boolean	have_shadows;
	fzrt_boolean	do_specular;
	fzrt_boolean	do_texturing;
} x_pixel_opts_td;

typedef x_pixel_opts_td* x_pixel_opts_td_ptr;


/************************************************************************
	x_pixel_wind_data_td

	Description
		Structure for window data of this pixel renderer.
		
	Members
		dirty   - whether or not the renderer needs to re-raytrace
		          (dirty equals TRUE), or simply throw the current image
		          buffer (dirty equals FALSE) on the screen.
*************************************************************************/
typedef struct x_pixel_wind_data_td
{	
	fzrt_boolean dirty;

} x_pixel_wind_data_td;

typedef x_pixel_wind_data_td* x_pixel_wind_data_td_ptr;


/************************************************************************
	light_td

	Description
		Structure to represent a light in the scene.
		
	Members
		intensity	- how intense light is 0..1
		is_power_on	- whether or not power is on
		do_shadows	- whether or not light makes shadows
		light_color	- color of the light
		light_pt	- position of light in space
		light_dir	- direction of the light (if directional)
		light_type	- type of light emission, point, distant, area, etc.
		outer_ang	- outer angle for cone lights
		inner_ang	- inner angle for cone lights
		ang_range_inv - inverse of the range between the outer and inner
		                angles for cone lights.
*************************************************************************/
typedef struct light_td
{
	fzrt_boolean	is_power_on;
	fzrt_boolean	do_shadows;
	double			intensity;
	fz_rgb_float_td	light_color;
	fz_xyz_td		light_pt;
	fz_xyz_td		light_dir;
	fzrt_UUID_td	light_type;
	double			outer_ang;
	double			inner_ang;
	double			ang_range_inv;
} light_td;



/************************************************************************
	msg_enum

	Description
		Indices for messages in this renderer's message queue, sent
		originally to this renderer from formZ in the form of
		notifications about changes to the geometry.
		
	Members
		X_PIXEL_MSG_CREATE_OBJ - object was created
		X_PIXEL_MSG_DELETE_OBJ - object was deleted
		X_PIXEL_MSG_EDIT_GEOM  - object's points were changed
		X_PIXEL_MSG_EDIT_ATTR  - object's appearance only was changed
*************************************************************************/
typedef enum msg_enum
{
	X_PIXEL_MSG_CREATE_OBJ = 0,
	X_PIXEL_MSG_DELETE_OBJ,
	X_PIXEL_MSG_EDIT_GEOM,
	X_PIXEL_MSG_EDIT_ATTR
} msg_enum;

/************************************************************************
	msg_td

	Description
		Structure for geometry change messages of this pixel renderer.
		
	Members
		msg_type	- Type of geometry change message.
		msg_objt	- reference to object that is changing.
		prev		- pointer to previous message in queue.
		next		- pointer to next message in queue.
*************************************************************************/
typedef struct msg_td
{
	msg_enum msg_type;
	fz_objt_ptr msg_objt;
	struct msg_td* prev;
	struct msg_td* next;
} msg_td;

/************************************************************************
	x_pixel_proj_data_td

	Description
		Structure for project data of this pixel renderer.
		
	Members
		root_obj	- the beginning of the list of objects in the project
		last_obj	- the end of the list of objects in the project
		root_msg	- the beginning of the list of messages sent to this
					  renderer by formZ
		last_msg	- the end of the list of messages
		lights		- array of lights in the project
		num_lights	- number of lights in the project
		ambt_color	- color of ambient light in the scene
		ambt_factor - how much ambient light contributes to scene
		is_activated - whether or not this renderer is activated
		has_been_activated - whehter or not this renderer has ever
							 been activated for this project.
		textures	- array of default textures of the project
		num_textures - number of textures in array
		tctl		- default project-level rendering options texture
					  control to be used
		surf_dirty - did a change to surface styles occur after last rendering
*************************************************************************/
typedef struct x_pixel_proj_data_td
{
	obj_td* root_obj;
	obj_td* last_obj;
	msg_td* root_msg;
	msg_td* last_msg;
	light_td* lights;
	fzrt_int num_lights;
	fz_rgb_float_td ambt_color;
	double ambt_factor;
	fzrt_boolean is_activated;
	fzrt_boolean has_been_activated;
	fzrt_boolean surf_dirty;
	texture_td* root_texture;
	texture_td* last_texture;
	tctl_td tctl;
} x_pixel_proj_data_td;

typedef x_pixel_proj_data_td* x_pixel_proj_data_td_ptr;


typedef struct x_pixel_lrep_td
{
	double intensity_override;
} x_pixel_lrep_td;

typedef x_pixel_lrep_td* x_pixel_lrep_ptr;


/************************************************************************
	Indices for parameters of this renderer.
*************************************************************************/
enum
{
	X_PIXEL_PARM_SHADOWS = 0,
	X_PIXEL_PARM_SPECULAR,
	X_PIXEL_PARM_TEXTURES,

	X_PIXEL_PARM_MAX
};


/************************************************************************
						Raytracer Management Functions
*************************************************************************/

/************************************************************************
	find_obj_td
		Looks for the given formZ object pointer in this renderer's
		list of objects.

	Returns
		NULL if not found, the obj_td reference if found.
*************************************************************************/
static obj_td* is_sym_present(fz_objt_ptr sym_inst, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	obj_td* cur_obj = NULL; /* not found */
	
	cur_obj = proj_data->root_obj;
	
	/* iterate through objects */
	while (cur_obj != NULL)
	{
		if (cur_obj->notf_sym_inst == sym_inst)
		{
			/* found! */
			break;
		}
		
		cur_obj = cur_obj->next; /* end of list should be NULL */
	}
	
	return cur_obj;
}


/************************************************************************
	find_obj_td
		Looks for the given formZ object pointer in this renderer's
		list of objects.

	Returns
		NULL if not found, the obj_td reference if found.
*************************************************************************/
static obj_td* find_obj_td(fz_objt_ptr objt, fz_objt_ptr notf_sym, x_pixel_proj_data_td_ptr proj_data, obj_td* start_after, fzrt_int which_obj_in_sym)
/************************************************************************/
{
	obj_td* cur_obj = NULL; /* not found */
	fzrt_int count = 0;
	
	cur_obj = proj_data->root_obj;
	
	/* start looking only after start_after */
	if (start_after != NULL)
	{
		cur_obj = start_after->next;
	}
	
	/* iterate through objects */
	while (cur_obj != NULL)
	{
		if (notf_sym != NULL) /* looking for an object from a symbol */
		{
			if (cur_obj->notf_sym_inst == notf_sym)
			{
				if (cur_obj->ref == objt && count == which_obj_in_sym)
				{
					/* found! */
					break;
				}
				count++;
			}
		}
		else /* looking for just the object reference, assuming unique */
		{
			if (cur_obj->ref == objt)
				/* found !*/
				break;
		}
		
		cur_obj = cur_obj->next; /* end of list should be NULL */
	}
	
	return cur_obj;
}

/************************************************************************
	get_light_info
		Collects information about all lights in the scene, to be used
		later on when shading pixels.
*************************************************************************/
static fzrt_error_td get_light_info(fzrt_int windex, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	fz_lite_ptr	  light;
	fzrt_int		  i;
	fz_type_td	  data;
	fzrt_int 	  tmp_num_lights;
	fz_xyz_td	  light_coi;
	
	_lite_funcs.fz_lite_get_num_lights(windex, &tmp_num_lights);
	
	/* prevent (re)allocation if not necessary */
	if (tmp_num_lights != proj_data->num_lights)
	{
		/* allocate memory for lights */
		
		if (proj_data->lights == NULL)
		{
			err = _mem_funcs.fz_mem_zone_alloc(_zone, tmp_num_lights*sizeof(light_td), FALSE, (fzrt_ptr*)&proj_data->lights);
		}
		else
		{
			err = _mem_funcs.fz_mem_zone_realloc(_zone, (fzrt_ptr*)&proj_data->lights, tmp_num_lights*sizeof(light_td));
		}
		
		proj_data->num_lights = tmp_num_lights;
	}
	
	/* get ambient light info */
	_lite_funcs.fz_lite_get_ambient_color(windex, &proj_data->ambt_color);
	_lite_funcs.fz_lite_get_ambient_intensity(windex, &proj_data->ambt_factor);
	/* pre-multiply */
	proj_data->ambt_color.red   *= (float)proj_data->ambt_factor;
	proj_data->ambt_color.green *= (float)proj_data->ambt_factor;
	proj_data->ambt_color.blue  *= (float)proj_data->ambt_factor;

	/* get lights */
	if (err == FZRT_NOERR)
	{
		light = NULL;
		for (i = 0; i < proj_data->num_lights; i++)
		{
			_lite_funcs.fz_lite_get_next_light(windex, light, &light);
			
			/* this light contributes to color only if it is ON */
			_lite_funcs.fz_lite_get_parm(windex, light, FZ_LIGHT_REP_PARM_SHINING_ON, FALSE, (fz_type_td*)&data);
			_type_funcs.fz_type_get_boolean(&data, &proj_data->lights[i].is_power_on);
			/* does light contribute shadows? */
			_lite_funcs.fz_lite_get_parm(windex, light, FZ_LIGHT_REP_PARM_SHADOWS_ON, FALSE, (fz_type_td*)&data);
			_type_funcs.fz_type_get_boolean(&data, &proj_data->lights[i].do_shadows);
			/* get light's color */
			_lite_funcs.fz_lite_get_parm(windex, light, FZ_LIGHT_REP_PARM_COLOR, FALSE, (fz_type_td*)&data);
			_type_funcs.fz_type_get_rgb_float(&data, &proj_data->lights[i].light_color);
			/* get light's position */
			_lite_funcs.fz_lite_get_parm(windex, light, FZ_LIGHT_REP_PARM_LOC_EYE, FALSE, (fz_type_td*)&data);
			_type_funcs.fz_type_get_xyz(&data, &proj_data->lights[i].light_pt);
			/* get light's type */
			_lite_funcs.fz_lite_get_parm( windex, light, FZ_LIGHT_REP_PARM_TYPE, FALSE, (fz_type_td*)&data );
			_type_funcs.fz_type_get_uuid( &data, proj_data->lights[i].light_type );

			//////////////////////////////////////////////////////////////////////////
			// CUSTOM PARAM
			//////////////////////////////////////////////////////////////////////////
			_lite_funcs.fz_lite_get_plugin_parm(windex, light, X_PIXEL_LREP_UUID, X_PIXEL_LREP_PARM_INTENS, (fz_type_td*)&data);
			_type_funcs.fz_type_get_double(&data, &proj_data->lights[i].intensity);
			
			if (_base_funcs.fzrt_UUID_is_equal( proj_data->lights[i].light_type, FZ_LIGHT_REP_TYPE_DISTANT ) ||
				_base_funcs.fzrt_UUID_is_equal( proj_data->lights[i].light_type, FZ_LIGHT_REP_TYPE_CONE ))
			{
				/* precompute direction, if directional light */
				
				/* get light's coi */
				_lite_funcs.fz_lite_get_parm(windex, light, FZ_LITE_PARM_COI, FALSE, (fz_type_td*)&data);
				_type_funcs.fz_type_get_xyz(&data, &light_coi);
				
				/* compute direction towards light origin. */
				proj_data->lights[i].light_dir.x = proj_data->lights[i].light_pt.x - light_coi.x;
				proj_data->lights[i].light_dir.y = proj_data->lights[i].light_pt.y - light_coi.y;
				proj_data->lights[i].light_dir.z = proj_data->lights[i].light_pt.z - light_coi.z;
				_math_funcs.fz_math_3d_normalize_vec(&proj_data->lights[i].light_dir, &proj_data->lights[i].light_dir);
				
				if (_base_funcs.fzrt_UUID_is_equal( proj_data->lights[i].light_type, FZ_LIGHT_REP_TYPE_CONE ))
				{
					/* get cone light's outer angle */
					_lite_funcs.fz_lite_get_parm(windex, light, FZ_LIGHT_REP_PARM_OUTER_ANGLE, FALSE, (fz_type_td*)&data);
					_type_funcs.fz_type_get_double(&data, &proj_data->lights[i].outer_ang);
					/* get cone light's inner angle */
					_lite_funcs.fz_lite_get_parm(windex, light, FZ_LIGHT_REP_PARM_INNER_ANGLE, FALSE, (fz_type_td*)&data);
					_type_funcs.fz_type_get_double(&data, &proj_data->lights[i].inner_ang);
					/* work with half the angles */
					proj_data->lights[i].outer_ang /= 2;
					proj_data->lights[i].inner_ang /= 2;
					
					if (proj_data->lights[i].outer_ang != 0)
					{
						proj_data->lights[i].ang_range_inv = 1.0 / (proj_data->lights[i].outer_ang - proj_data->lights[i].inner_ang);
					}
					else
						proj_data->lights[i].ang_range_inv = 0.0;
				}
			}
			
			/* pre-multiply light color by intensity */
			proj_data->lights[i].light_color.red   *= (float)proj_data->lights[i].intensity;
			proj_data->lights[i].light_color.green *= (float)proj_data->lights[i].intensity;
			proj_data->lights[i].light_color.blue  *= (float)proj_data->lights[i].intensity;
			
			/* done getting info about this light, go on to next. */
		}
		/* done iterating through all lights */
		
	}/* else failed to allocate memory for lights array */
	
	return err;
}


/************************************************************************
	proj_retrieve_tctl
		Gets default texture control information from project.
*************************************************************************/
static void proj_retrieve_tctl(fzrt_int windex, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fz_type_td data;

	/* get texture control information */
	
	/* get texture origin */
	_proj_funcs.fz_proj_rndr_opts_tctl_get(windex, FZ_PROJ_RNDR_OPTS_TCTL_ORIGIN, &data);
	_type_funcs.fz_type_get_xyz(&data, &proj_data->tctl.origin);
	/* get texture rotation */
	_proj_funcs.fz_proj_rndr_opts_tctl_get(windex, FZ_PROJ_RNDR_OPTS_TCTL_ROTATION, &data);
	_type_funcs.fz_type_get_xyz(&data, &proj_data->tctl.rotation);
	/* get texture horizontal size */
	_proj_funcs.fz_proj_rndr_opts_tctl_get(windex, FZ_PROJ_RNDR_OPTS_TCTL_SIZE_X, &data);
	_type_funcs.fz_type_get_double(&data, &proj_data->tctl.size_x);
	proj_data->tctl.size_x_inv = 1.0 / proj_data->tctl.size_x;
	/* get texture vertical size */
	_proj_funcs.fz_proj_rndr_opts_tctl_get(windex, FZ_PROJ_RNDR_OPTS_TCTL_SIZE_Y, &data);
	_type_funcs.fz_type_get_double(&data, &proj_data->tctl.size_y);
	proj_data->tctl.size_y_inv = 1.0 / proj_data->tctl.size_y;
	/* get texture solid size */
	_proj_funcs.fz_proj_rndr_opts_tctl_get(windex, FZ_PROJ_RNDR_OPTS_TCTL_SOLID_SIZE, &data);
	_type_funcs.fz_type_get_double(&data, &proj_data->tctl.solid_size);
	proj_data->tctl.solid_size_inv = 1.0 / proj_data->tctl.solid_size;
	/* get texture mapping type */
	_proj_funcs.fz_proj_rndr_opts_tctl_get(windex, FZ_PROJ_RNDR_OPTS_TCTL_MAP_TYPE, &data);
	_type_funcs.fz_type_get_int(&data, (fzrt_int*)&proj_data->tctl.map_type);

}

/************************************************************************
	remove_root_texture
		Removes the root texture from the linked list of textures
		collected in the project.
*************************************************************************/
static fzrt_error_td remove_root_texture(x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	
	texture_td* next_texture;
	if (proj_data->root_texture != NULL)
	{
		next_texture = proj_data->root_texture->next;
		
		err = _mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&proj_data->root_texture->pixels);
		
		if (err == FZRT_NOERR)
		{
			_file_funcs.fzrt_file_floc_finit(&proj_data->root_texture->tmap_floc);
			err = _mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&proj_data->root_texture);
		}
		
		proj_data->root_texture = next_texture;
		
		if (proj_data->root_texture == NULL)
		{
			proj_data->last_texture = NULL;
		}
	}
	
	return err;
}

/************************************************************************
	add_texture
		Adds a texture to the project's linked list of textures at the
		end of the list.
*************************************************************************/
static void add_texture(texture_td* texture, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	if (proj_data->last_texture == NULL)
	{
		proj_data->root_texture = texture;
		proj_data->last_texture = texture;
	}
	else
	{
		proj_data->last_texture->next = texture;
		proj_data->last_texture = texture;
	}
}

/************************************************************************
	add_tmap_2_face
		Adds a texture to the given face and tmap.  If the tmap already
		exists in the project's list of textures, then it doesn't
		duplicate the memory, but uses the already existing one.
*************************************************************************/
static fzrt_error_td add_tmap_2_face(fzrt_int windex, fz_rmtl_ptr tmap_rmtl, fz_tmap_ptr tmap, face_td* face, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	texture_td* cur_texture;
	fzrt_int i, uchar_index, i2, size, cur_row, cur_col;
	unsigned char* pixels;
	fzrt_boolean has_alpha;
	fzrt_floc_ptr tmap_floc;

	/* look through project's list of textures, */
	/* determine if this tmap is already in list */
	cur_texture = proj_data->root_texture;
	while(cur_texture != NULL)
	{
		/* compare texture's original surface styles */
		if (cur_texture->tmap_surf_style == tmap_rmtl)
		{
			/* already existing ! */
			break;
		}
		cur_texture = cur_texture->next;
	}
	
	if (cur_texture == NULL)
	{
		/* create a new texture with this tmap */
		/* and add it to the list of textures, */
		/* and then use it for the face */
		err = _mem_funcs.fz_mem_zone_alloc(_zone, sizeof(texture_td), FALSE, (fzrt_ptr*)&cur_texture);
		if (err == FZRT_NOERR)
		{
			cur_texture->tmap_surf_style = tmap_rmtl; /* remember which surface style */
			cur_texture->next = NULL;

			/* not found in project list, so try to find it with its existing file location, or ask user */
			if(_tmap_funcs.fz_tmap_find_file(windex, tmap, FALSE) == FZRT_NOERR)
			{
				/* found! */

				/* get floc of the potentially new texture */
				_file_funcs.fzrt_file_floc_init(&tmap_floc);
				_tmap_funcs.fz_tmap_get_floc(tmap, tmap_floc);
			
				/* defaults */
				cur_texture->tmap_floc = tmap_floc;
				_tmap_funcs.fz_tmap_get_info(tmap, &cur_texture->width, &cur_texture->height, &has_alpha);
				
				/* allocate arrays of pixels */
				size = cur_texture->width*cur_texture->height;
				err = _mem_funcs.fz_mem_zone_alloc(_zone, size*3, FALSE, (fzrt_ptr*)&pixels);
				if (err == FZRT_NOERR)
				{
					err = _mem_funcs.fz_mem_zone_alloc(_zone, size*sizeof(fz_rgb_float_td), FALSE, (fzrt_ptr*)&cur_texture->pixels);
					if (err == FZRT_NOERR)
					{
						/* get the texture's colors */
						_tmap_funcs.fz_tmap_get_pixels(tmap, pixels, FZ_TMAP_CHANNEL_RGB);
						/* convert uchar pixels to rgb_float_td */
						for (i = 0,uchar_index = 0; i < size; i++,uchar_index += 3)
						{
							cur_row = i / cur_texture->width;
							cur_col = i % cur_texture->width;
							/* i2 -> need to vertically flip the texture */
							i2 = (cur_texture->height - cur_row - 1)*cur_texture->width + cur_col;
							cur_texture->pixels[i2].red   = (float)(pixels[uchar_index]   * UCHAR_FACTOR);
							cur_texture->pixels[i2].green = (float)(pixels[uchar_index+1] * UCHAR_FACTOR);
							cur_texture->pixels[i2].blue  = (float)(pixels[uchar_index+2] * UCHAR_FACTOR);
						}
					}
					/* dispose of array */
					err = _mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&pixels);
				}

				cur_texture->tmap_file_found = TRUE;

			}
			else
			{
				/* not found ! */
				cur_texture->tmap_file_found = FALSE;
				cur_texture->tmap_floc = NULL;
				cur_texture->width = 0;
				cur_texture->height = 0;
				cur_texture->pixels = NULL;
			}
			/* add to project's list */
			add_texture(cur_texture, proj_data);

		}

	}

	/* add the texture to the face by setting the face's texture pointer */
	face->texture = cur_texture;
	
	return err;
}


/************************************************************************
	update_obj_tgroups
		Gets texture control information about a particular object.
*************************************************************************/
static fzrt_error_td update_obj_tgroups(fzrt_int windex, obj_td* cur_obj, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	fz_type_td data;
	fzrt_int num_groups, group_iter;
	fzrt_boolean has_tgroup;
	texture_group_td* cur_group;
	texture_group_td* prev_group;
	
	/* check whether an object has a texture map control attribute, */
	/* assigned by the texture map control attribute tool.  If it does, */
	/* then it has at least one texture mapping group. */
	err = _model_attr_funcs.fz_objt_attr_objt_has_tctl(windex, cur_obj->ref, &has_tgroup);
	
	if (err == FZRT_NOERR && has_tgroup == TRUE)
	{
		/* get texture control information for every texture group of an object */
		
		err = _model_attr_funcs.fz_objt_attr_get_objt_tctl_count(windex, cur_obj->ref, &num_groups);
		group_iter = -1; /* start with object-level texture group */
		cur_group = cur_obj->tgroup;
		prev_group = NULL;
		
		/* iterate through groups */
		while (err == FZRT_NOERR && group_iter < num_groups)
		{
			if (cur_group == NULL)
			{
				/* allocate memory for this group if needed */
				err = _mem_funcs.fz_mem_zone_alloc(_zone, sizeof(texture_group_td), FALSE, (fzrt_ptr*)&cur_group);
				cur_group->next = NULL;
			}

			/* make linked list of groups */
			if (prev_group != NULL)
				/* each group points to the next group */
				prev_group->next = cur_group;
			else
				/* make sure object points to first group */
				cur_obj->tgroup = cur_group;
				
			/* get texture origin */
			_model_attr_funcs.fz_objt_attr_get_objt_tctl_parm(windex, cur_obj->ref, group_iter, FZ_ATTR_TCTL_PARM_ORIGIN, &data);
			_type_funcs.fz_type_get_xyz(&data, &cur_group->tctl.origin);
			/* get texture rotation */
			_model_attr_funcs.fz_objt_attr_get_objt_tctl_parm(windex, cur_obj->ref, group_iter, FZ_ATTR_TCTL_PARM_ROTATION, &data);
			_type_funcs.fz_type_get_xyz(&data, &cur_group->tctl.rotation);
			/* get texture horizontal size */
			_model_attr_funcs.fz_objt_attr_get_objt_tctl_parm(windex, cur_obj->ref, group_iter, FZ_ATTR_TCTL_PARM_SIZE_X, &data);
			_type_funcs.fz_type_get_double(&data, &cur_group->tctl.size_x);
			cur_group->tctl.size_x_inv = 1.0 / cur_group->tctl.size_x;
			/* get texture vertical size */
			_model_attr_funcs.fz_objt_attr_get_objt_tctl_parm(windex, cur_obj->ref, group_iter, FZ_ATTR_TCTL_PARM_SIZE_Y, &data);
			_type_funcs.fz_type_get_double(&data, &cur_group->tctl.size_y);
			cur_group->tctl.size_y_inv = 1.0 / cur_group->tctl.size_y;
			/* get texture solid size */
			_model_attr_funcs.fz_objt_attr_get_objt_tctl_parm(windex, cur_obj->ref, group_iter, FZ_ATTR_TCTL_PARM_SOLID_SIZE, &data);
			_type_funcs.fz_type_get_double(&data, &cur_group->tctl.solid_size);
			cur_group->tctl.solid_size_inv = 1.0 / cur_group->tctl.solid_size;
			/* get texture mapping type */
			_model_attr_funcs.fz_objt_attr_get_objt_tctl_parm(windex, cur_obj->ref, group_iter, FZ_ATTR_TCTL_PARM_MAP_TYPE, &data);
			_type_funcs.fz_type_get_int(&data, (fzrt_int*)&cur_group->tctl.map_type);
			/* get centering */
			_model_attr_funcs.fz_objt_attr_get_objt_tctl_parm(windex, cur_obj->ref, group_iter, FZ_ATTR_TCTL_PARM_CENTER_HORIZ, &data);
			_type_funcs.fz_type_get_boolean(&data, &cur_group->tctl.cntr_horz);
			_model_attr_funcs.fz_objt_attr_get_objt_tctl_parm(windex, cur_obj->ref, group_iter, FZ_ATTR_TCTL_PARM_CENTER_VERT, &data);
			_type_funcs.fz_type_get_boolean(&data, &cur_group->tctl.cntr_vert);
			_model_attr_funcs.fz_objt_attr_get_objt_tctl_parm(windex, cur_obj->ref, group_iter, FZ_ATTR_TCTL_PARM_FLIP, &data);
			_type_funcs.fz_type_get_boolean(&data, &cur_group->tctl.flip);


			prev_group = cur_group;
			cur_group = cur_group->next;
			group_iter++;
		}
		
		/* done iterating through all texture groups */
		
	} /* else object doesn't have texture map control attribute */
	
	return err;
}



/************************************************************************
	update_obj_attr
		Gets attribute information about a particular object.
*************************************************************************/
static fzrt_error_td update_obj_attr(fzrt_int windex, obj_td* cur_obj, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	/* face info */
	fzrt_int face_count;
	fzrt_int			face_index,n;
	/* color of faces */
	fz_rgb_float_td	mtl_color_float;
	fz_rmtl_ptr		rmtl;
	fz_type_td		data;
	fz_shdr_ptr		shdr;
	fzrt_int			tgroup_index;
	fzrt_int			group_iter;
	texture_group_td* cur_group;
	fzrt_int			data_type;
	fz_tmap_ptr		tmap;
	double 			scale_texture;
	x_srep_td		*x_srep;
	void			*srep_data;

	/* iterate through faces */
	
	face_count = cur_obj->num_faces;
	if (face_count > 0)
	{
		/* for each face, get its color, texture */
		
		/* iterate through faces */
		for (face_index = 0; err == FZRT_NOERR && face_index < face_count; face_index++)
		{
			/* get material of face */
			if (err == FZRT_NOERR)
			{
				rmtl = cur_obj->faces[face_index].mtrl;

				/* get lighting parameters from the srep plugin, that x pixel creates */
				_rmtl_funcs.fz_rmtl_get_srep_data(windex,rmtl,NULL,_x_srep_indx,&srep_data);
				x_srep = (x_srep_td*) srep_data;
				
				cur_obj->faces[face_index].ambient_factor 	= x_srep->ambient;
				cur_obj->faces[face_index].diffuse_factor 	= x_srep->diffuse;
				cur_obj->faces[face_index].specular_factor 	= x_srep->specular;
				cur_obj->faces[face_index].specular_expo 	= 1.0 / (1.0 - x_min(x_srep->specular_expo,0.999));

				/* get color of material */
 				_rmtl_funcs.fz_rmtl_get_avg_color(windex, rmtl, &mtl_color_float);
 				
 				/* pre-multiply color & diffuse factor */
 				cur_obj->faces[face_index].color.red   = (float)(mtl_color_float.red   * cur_obj->faces[face_index].diffuse_factor);
 				cur_obj->faces[face_index].color.green = (float)(mtl_color_float.green * cur_obj->faces[face_index].diffuse_factor);
 				cur_obj->faces[face_index].color.blue  = (float)(mtl_color_float.blue  * cur_obj->faces[face_index].diffuse_factor);

				/* get color shader to get its texture */
				_rmtl_funcs.fz_rmtl_get_parm(windex, rmtl, FZ_RMTL_PARM_COL_SHADER, &data);
				_type_funcs.fz_type_get_ptr(&data, (void*)&shdr);

				/* get the shader's parameter 1 type */
				/* if it has a texture, this type will be FZ_TYPE_PTR. */
				data_type = -1;
				
				err = _rmtl_funcs.fz_shdr_ptr_get_num_parm(windex,shdr,&n);
				if (err == FZRT_NOERR && n > 1)
				{	err = _rmtl_funcs.fz_shdr_ptr_get_parm_type(windex, shdr, 1, &data_type);
				}
				else data_type = FZ_TYPE_NONE;
				
				/* has a texture color map? */
				if (err == FZRT_NOERR && data_type == FZ_TYPE_PTR)
				{
					/* get "color map" texture at index 1 */
					_rmtl_funcs.fz_shdr_ptr_get_parm(windex, shdr, 1, &data);
					_type_funcs.fz_type_get_ptr(&data, (void*)&tmap);

					/* assign this texture to the current face */
					err = add_tmap_2_face(windex, rmtl, tmap, &cur_obj->faces[face_index], proj_data);

					/* get scale of texture on face */
					_rmtl_funcs.fz_shdr_ptr_get_parm(windex, shdr, 0, &data);
					_type_funcs.fz_type_get_double(&data, &scale_texture);
					cur_obj->faces[face_index].scale_texture_inv = 1.0 / scale_texture;
				}
				else
				{
					/* no known texture associated with face. */
					cur_obj->faces[face_index].texture = NULL;
					err = FZRT_NOERR;
				}

				/* assign tgroup based on tgroup_index */
				cur_group = cur_obj->tgroup;
				if (cur_group != NULL)
				{
					/* there is at least one texture group. */
					
					/* get this face's texture group's index */
					_model_attr_funcs.fz_objt_attr_get_face_tctl_indx(windex, cur_obj->faces[face_index].attr_obj, cur_obj->faces[face_index].attr_face_index, &tgroup_index);

					/* find it in the object's list of texture groups */
					group_iter = -1; /* start at object level index */
					while(group_iter != tgroup_index && cur_group != NULL)
					{
						cur_group = cur_group->next;
						group_iter++;
					}
				} /* else no group with this object, */
				  /* so will use default project level texture map control attr */

				/* assign what texture group has been found (if any) as */
				/* this face's texture group. */
				
				cur_obj->faces[face_index].tgroup = cur_group;
			}

			/* done with this face, go on to next face. */
		}

		/* done with faces of this object, go on to next object. */

	}/* else there are no faces with this object */
	
	return err;
}




/************************************************************************
	update_obj_geom
		Gets geometric information about a particular object.
*************************************************************************/
static fzrt_error_td update_obj_geom(fzrt_int windex, obj_td* cur_obj, fz_mat4x4_td* cur_mat, x_pixel_proj_data_td_ptr proj_data )
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	/* triangulation */
	fz_type_list_ptr list;
	fz_type_list_ptr list_orig;
	fzrt_int lindex;
	fz_type_td data;

	fzrt_int face_index;
	fz_objt_model_type_enum model_type = FZ_OBJT_MODEL_TYPE_FACT;
	/* world coordinate points */
	fzrt_int pi1, pi2, pi3;
	fzrt_int						bytes_per_ref,nfact_color;
	fzrt_unsigned_int				ntrng,nquad,ncord;
	unsigned int					p1,p2,p3;
	char							has_txuv,has_colr,has_tmat;
	fz_xyz_float_td					*cord;
	fz_tag_td						rmtl_tag;
	unsigned char					*trng1=NULL,*t1;
	unsigned short					*trng2=NULL,*t2;
	fzrt_unsigned_int				*trng4=NULL,*t4;
	fzrt_ptr						trng=NULL;
	fz_rmtl_ptr						*fact_color=NULL;
	fz_rmtl_ptr						rmtl_obj;
	fz_xyz_float_td					*cord_norm=NULL;
	fz_xy_float_td					*cord_txuv=NULL;
	fz_rgba_float_td				*cord_colr=NULL;
	fz_mat4x4_td					tmat;
	
	_type_funcs.fz_type_list_init(FZ_TYPE_INT, &list);
	_type_funcs.fz_type_list_init(FZ_TYPE_INT, &list_orig);
	
	/* for each object, get its bbox. */
	err = _model_funcs.fz_objt_alys_get_objt_bbox(windex, cur_obj->ref, &cur_obj->bbox.min_xyz, &cur_obj->bbox.max_xyz);

	if (err == FZRT_NOERR && cur_mat != NULL)
	{
		_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->bbox.min_xyz);
		_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->bbox.max_xyz);
	}

	if (err == FZRT_NOERR)
	{
		// Gets the sizes of verts, normals, uvs, and indices. Also lets you know if the mesh has a color and if it has a transformation
		// matrix. bytes_per_ref tell the programmer the amount of memory used by the indices (1, 2 or 4 bytes). If nfact_color is greater
		// than 0, then this object has more than one material and therefore the materials come in at the face level. fact_color comes in as
		// an array of materials, one for each face (see line 1151).
		err = _model_funcs.fz_objt_get_rndr_mesh_sizes( windex, cur_obj->ref, FALSE, &bytes_per_ref, &ntrng, &nquad, &nfact_color, &ncord,
														&has_txuv, &has_colr, &has_tmat );

		// Allocate space for all of the data
		cord = malloc( sizeof( fz_xyz_float_td ) * ncord );
		cord_norm =  malloc( sizeof( fz_xyz_float_td ) * ncord );
		cord_txuv =  malloc( sizeof( fz_xy_float_td ) * ncord );
		fact_color = malloc( sizeof(  fz_rmtl_ptr ) * nfact_color );

		if( err == FZRT_NOERR && ntrng > 0 )
		{
			// Allocate for indices based on byte depth
			switch(bytes_per_ref)
			{
			case 1 :
				trng1 = malloc( ntrng * 3 );
				trng = (fzrt_ptr)trng1;
				break;
			case 2 :
				trng2 = malloc( sizeof(short) * ntrng * 3 );
				trng = (fzrt_ptr)trng2;
				break;
			case 4 :
				trng4 = malloc( sizeof(fzrt_unsigned_int) * ntrng * 3 );
				trng = (fzrt_ptr)trng4;
				break;
			}

			// Pulls the render mesh's data
			err = _model_funcs.fz_objt_get_rndr_mesh( windex, cur_obj->ref, FALSE, &bytes_per_ref, &ntrng, trng, &nquad, NULL, &nfact_color, 
				                                      fact_color, &ncord, cord, cord_norm, cord_txuv, cord_colr, &has_tmat, &tmat );
		}
		
		if (err == FZRT_NOERR && ntrng > 0)
		{
			if (cur_obj->faces == NULL)
			{
				err = _mem_funcs.fz_mem_zone_incr_array(_zone, (fzrt_ptr*)&cur_obj->faces, &cur_obj->num_faces, sizeof(face_td), (ntrng+cur_obj->num_faces), ntrng);
			}
			
			switch(bytes_per_ref)
			{
			case 1:
				t1 = trng1;
				
				for (lindex = 1, face_index = 0; face_index < ntrng; face_index++, lindex+=4)
				{
					p1 = *(t1);
					p2 = *(t1+1);
					p3 = *(t1+2);
					t1+=3;
					
					// There are multiple materials for this object, so we need to pull this face's materials
					// and add it to the structure.
					if( nfact_color > 0 )
					{
						cur_obj->faces[face_index].mtrl = fact_color[p1];
					}

					pi1 = p1;
					pi2 = p2;
					pi3 = p3;

					cur_obj->faces[face_index].p1.x = cord[p1].x;
					cur_obj->faces[face_index].p1.y = cord[p1].y;
					cur_obj->faces[face_index].p1.z = cord[p1].z;

					cur_obj->faces[face_index].p2.x = cord[p2].x;
					cur_obj->faces[face_index].p2.y = cord[p2].y;
					cur_obj->faces[face_index].p2.z = cord[p2].z;

					cur_obj->faces[face_index].p3.x = cord[p3].x;
					cur_obj->faces[face_index].p3.y = cord[p3].y;
					cur_obj->faces[face_index].p3.z = cord[p3].z;

					
					cur_obj->faces[face_index].attr_face_index = face_index;
					//_type_funcs.fz_type_get_int(&data, &cur_obj->faces[face_index].attr_face_index);

					cur_obj->faces[face_index].attr_obj = cur_obj->ref;
					
					/* get points xyz */
					_model_funcs.fz_objt_point_get_xyz(windex, cur_obj->ref, pi1, model_type, &cur_obj->faces[face_index].p1);
					_model_funcs.fz_objt_point_get_xyz(windex, cur_obj->ref, pi2, model_type, &cur_obj->faces[face_index].p2);
					_model_funcs.fz_objt_point_get_xyz(windex, cur_obj->ref, pi3, model_type, &cur_obj->faces[face_index].p3);
					
					if (cur_mat != NULL)
					{
						_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->faces[face_index].p1);
						_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->faces[face_index].p2);
						_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->faces[face_index].p3);
					}
					
					/* find out plane equation with d. */
					_math_funcs.fz_math_3d_equ_from_3_pts(&cur_obj->faces[face_index].p1, &cur_obj->faces[face_index].p2, &cur_obj->faces[face_index].p3, &cur_obj->faces[face_index].face_plane);
					/* must negate to get plane facing correct direction for later intersection calculation. */
					cur_obj->faces[face_index].face_plane.d *= -1;
					
					/* find out normal of face */
					cur_obj->faces[face_index].normal.x = cur_obj->faces[face_index].face_plane.a;
					cur_obj->faces[face_index].normal.y = cur_obj->faces[face_index].face_plane.b;
					cur_obj->faces[face_index].normal.z = cur_obj->faces[face_index].face_plane.c;
					
					cur_obj->faces[face_index].normal_inv.x = -cur_obj->faces[face_index].normal.x;
					cur_obj->faces[face_index].normal_inv.y = -cur_obj->faces[face_index].normal.y;
					cur_obj->faces[face_index].normal_inv.z = -cur_obj->faces[face_index].normal.z;
					/* done with this face, go on to next face. */
				}
				break;
			case 2:
				t2 = trng2;
				/* for each face, get its points and calculate its normal. */

				/* iterate through faces */
				for (lindex = 1, face_index = 0; face_index < ntrng; face_index++, lindex+=4)
				{
					p1 = *(t2);
					p2 = *(t2+1);
					p3 = *(t2+2);
					t2+=3;

					if( nfact_color > 0 )
					{
						cur_obj->faces[face_index].mtrl = fact_color[p1];
					}

					pi1 = p1;
					pi2 = p2;
					pi3 = p3;

					cur_obj->faces[face_index].p1.x = cord[p1].x;
					cur_obj->faces[face_index].p1.y = cord[p1].y;
					cur_obj->faces[face_index].p1.z = cord[p1].z;

					cur_obj->faces[face_index].p2.x = cord[p2].x;
					cur_obj->faces[face_index].p2.y = cord[p2].y;
					cur_obj->faces[face_index].p2.z = cord[p2].z;

					cur_obj->faces[face_index].p3.x = cord[p3].x;
					cur_obj->faces[face_index].p3.y = cord[p3].y;
					cur_obj->faces[face_index].p3.z = cord[p3].z;


					_type_funcs.fz_type_list_get_item(list_orig, face_index, &data);
					_type_funcs.fz_type_get_int(&data, &cur_obj->faces[face_index].attr_face_index);

					cur_obj->faces[face_index].attr_obj = cur_obj->ref;

					/* get points xyz */
					_model_funcs.fz_objt_point_get_xyz(windex, cur_obj->ref, pi1, model_type, &cur_obj->faces[face_index].p1);
					_model_funcs.fz_objt_point_get_xyz(windex, cur_obj->ref, pi2, model_type, &cur_obj->faces[face_index].p2);
					_model_funcs.fz_objt_point_get_xyz(windex, cur_obj->ref, pi3, model_type, &cur_obj->faces[face_index].p3);

					if (cur_mat != NULL)
					{
						_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->faces[face_index].p1);
						_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->faces[face_index].p2);
						_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->faces[face_index].p3);
					}

					/* find out plane equation with d. */
					_math_funcs.fz_math_3d_equ_from_3_pts(&cur_obj->faces[face_index].p1, &cur_obj->faces[face_index].p2, &cur_obj->faces[face_index].p3, &cur_obj->faces[face_index].face_plane);
					/* must negate to get plane facing correct direction for later intersection calculation. */
					cur_obj->faces[face_index].face_plane.d *= -1;

					/* find out normal of face */
					cur_obj->faces[face_index].normal.x = cur_obj->faces[face_index].face_plane.a;
					cur_obj->faces[face_index].normal.y = cur_obj->faces[face_index].face_plane.b;
					cur_obj->faces[face_index].normal.z = cur_obj->faces[face_index].face_plane.c;

					cur_obj->faces[face_index].normal_inv.x = -cur_obj->faces[face_index].normal.x;
					cur_obj->faces[face_index].normal_inv.y = -cur_obj->faces[face_index].normal.y;
					cur_obj->faces[face_index].normal_inv.z = -cur_obj->faces[face_index].normal.z;

					/* done with this face, go on to next face. */
				}
				break;

			case 4:
				t4 = trng4;
				/* for each face, get its points and calculate its normal. */

				/* iterate through faces */
				for (lindex = 1, face_index = 0; face_index < ntrng; face_index++, lindex+=4)
				{
					p1 = *(t4);
					p2 = *(t4+1);
					p3 = *(t4+2);
					t4+=3;

					if( nfact_color > 0 )
					{
						cur_obj->faces[face_index].mtrl = fact_color[p1];
					}

					pi1 = p1;
					pi2 = p2;
					pi3 = p3;

					cur_obj->faces[face_index].p1.x = cord[p1].x;
					cur_obj->faces[face_index].p1.y = cord[p1].y;
					cur_obj->faces[face_index].p1.z = cord[p1].z;

					cur_obj->faces[face_index].p2.x = cord[p2].x;
					cur_obj->faces[face_index].p2.y = cord[p2].y;
					cur_obj->faces[face_index].p2.z = cord[p2].z;

					cur_obj->faces[face_index].p3.x = cord[p3].x;
					cur_obj->faces[face_index].p3.y = cord[p3].y;
					cur_obj->faces[face_index].p3.z = cord[p3].z;

					cur_obj->faces[face_index].attr_obj = cur_obj->ref;

					/* get points xyz */
					_model_funcs.fz_objt_point_get_xyz(windex, cur_obj->ref, pi1, model_type, &cur_obj->faces[face_index].p1);
					_model_funcs.fz_objt_point_get_xyz(windex, cur_obj->ref, pi2, model_type, &cur_obj->faces[face_index].p2);
					_model_funcs.fz_objt_point_get_xyz(windex, cur_obj->ref, pi3, model_type, &cur_obj->faces[face_index].p3);

					if (cur_mat != NULL)
					{
						_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->faces[face_index].p1);
						_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->faces[face_index].p2);
						_math_funcs.fz_math_4x4_multiply_mat_xyz(cur_mat, &cur_obj->faces[face_index].p3);
					}

					/* find out plane equation with d. */
					_math_funcs.fz_math_3d_equ_from_3_pts(&cur_obj->faces[face_index].p1, &cur_obj->faces[face_index].p2, &cur_obj->faces[face_index].p3, &cur_obj->faces[face_index].face_plane);
					/* must negate to get plane facing correct direction for later intersection calculation. */
					cur_obj->faces[face_index].face_plane.d *= -1;

					/* find out normal of face */
					cur_obj->faces[face_index].normal.x = cur_obj->faces[face_index].face_plane.a;
					cur_obj->faces[face_index].normal.y = cur_obj->faces[face_index].face_plane.b;
					cur_obj->faces[face_index].normal.z = cur_obj->faces[face_index].face_plane.c;

					cur_obj->faces[face_index].normal_inv.x = -cur_obj->faces[face_index].normal.x;
					cur_obj->faces[face_index].normal_inv.y = -cur_obj->faces[face_index].normal.y;
					cur_obj->faces[face_index].normal_inv.z = -cur_obj->faces[face_index].normal.z;

					/* done with this face, go on to next face. */
				}
				break;
				
				/* done with faces of this object, go on to next object. */
				
			}

			// if nfactcolor == 0, theres only one material on the entire object
			// otherwise, we need to handle multiple materials (see above.)
			for( face_index = 0; face_index < ntrng; face_index++ )
			{
				if( nfact_color == 0 )
				{
					_model_attr_funcs.fz_objt_attr_get_objt_rmtl(windex, cur_obj->ref, true, &rmtl_tag);
					_rmtl_funcs.fz_rmtl_tag_to_ptr(windex, &rmtl_tag, &rmtl_obj);
					
					cur_obj->faces[face_index].mtrl = rmtl_obj;
				}
			}
		}
		else /* else there are no faces with this object */
		{
			if (ntrng == 0)
			{
				if (cur_obj->faces != NULL)
				{
					// Clean up memory
					_mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&cur_obj->faces);
					cur_obj->num_faces = 0;
				}
			}
		}
		
		_type_funcs.fz_type_list_finit(&list);
		_type_funcs.fz_type_list_finit(&list_orig);

		free(cord);
		free(cord_norm);
		free(cord_txuv);
		free(fact_color);
	}
	
	return err;
}



				
/************************************************************************
	add_obj_core
		Adds an object to this renderer's own list of objects.
*************************************************************************/
static fzrt_error_td add_obj_core(fzrt_int windex, fz_objt_ptr objt, x_pixel_proj_data_td_ptr proj_data, obj_td** cur_obj_ptr, fz_objt_ptr notf_sym, fzrt_int which_obj_in_sym, fz_mat4x4_td* cur_mat)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;

	if (cur_obj_ptr != NULL)
	{
		/* allocate memory for object */
		err = _mem_funcs.fz_mem_zone_alloc(_zone, sizeof(obj_td), FALSE, (fzrt_ptr*)cur_obj_ptr);
		
		/* no faces by default */
		(*cur_obj_ptr)->faces = NULL;
		(*cur_obj_ptr)->num_faces = 0;
		
		/* no next object by default */
		(*cur_obj_ptr)->next = NULL;
		
		/* for each object, remember its reference */
		(*cur_obj_ptr)->ref = objt;

		/* from a symbol? */
		(*cur_obj_ptr)->notf_sym_inst = notf_sym;
		(*cur_obj_ptr)->which_obj_in_sym = which_obj_in_sym;
		
		/* no texture data yet */
		(*cur_obj_ptr)->tgroup = NULL;
		
		/* init visibility */
		(*cur_obj_ptr)->visible = FALSE;


		/* adjust project's linked list */
		if (proj_data->last_obj != NULL)
		{
			proj_data->last_obj->next = (void*)*cur_obj_ptr;
			proj_data->last_obj = *cur_obj_ptr;
		}
		if (proj_data->root_obj == NULL)
		{
			proj_data->root_obj = *cur_obj_ptr;
			proj_data->last_obj = *cur_obj_ptr;
		}

		/* update faces, colors, etc. of this object */
		if (err == FZRT_NOERR)
			err = update_obj_geom(windex, *cur_obj_ptr, cur_mat, proj_data);
		if (err == FZRT_NOERR)
			err = update_obj_tgroups(windex, *cur_obj_ptr, proj_data);
		if (err == FZRT_NOERR)
			err = update_obj_attr(windex, *cur_obj_ptr, proj_data);
	}
	else
	{
		err = _base_funcs.fzrt_error_set(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0);
	}

	return err;
}

static fzrt_error_td traverse_symbol(fzrt_int windex, fz_objt_ptr par_sym, fz_objt_ptr notf_sym, fzrt_int* which_obj_in_sym, x_pixel_proj_data_td_ptr proj_data, obj_td** cur_obj_ptr, fz_mat4x4_td* cur_mat, fzrt_int flag);

/************************************************************************
	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, fz_objt_ptr par_sym, fz_objt_ptr notf_sym, fzrt_int* which_obj_in_sym, x_pixel_proj_data_td_ptr proj_data, obj_td** cur_obj_ptr, fz_mat4x4_td* cur_mat, fzrt_int flag)
{
	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, par_sym, &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, par_sym, notf_sym, which_obj_in_sym, proj_data, cur_obj_ptr, cur_mat, flag);

	/* 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, fz_objt_ptr par_sym, fz_objt_ptr notf_sym, fzrt_int* which_obj_in_sym, x_pixel_proj_data_td_ptr proj_data, obj_td** cur_obj_ptr, fz_mat4x4_td* cur_mat, fzrt_int flag)
{
	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;
	obj_td* edit_obj;
		
	/* iterate through symbol's objects */
	_model_funcs.fz_objt_edit_symb_parm_get(windex, par_sym, 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, obj_next, notf_sym, which_obj_in_sym, proj_data, cur_obj_ptr, cur_mat, flag);
			}
			else
			{
				/* found object */
				if (flag == 0)
				{
					/* add a new obj_td */
					err = add_obj_core(windex, obj_next, proj_data, cur_obj_ptr, notf_sym, *which_obj_in_sym, cur_mat);
				}
				else
				{
					/* get the existing obj_td to edit */
					edit_obj = find_obj_td(obj_next, notf_sym, proj_data, NULL, *which_obj_in_sym);
						
					if (edit_obj == NULL)
						break;
					if (flag == 1)
					{
						err = update_obj_geom(windex, edit_obj, cur_mat, proj_data);
					}
					if (flag == 1 || flag == 2)
					{
						if (err == FZRT_NOERR)
							err = update_obj_tgroups(windex, edit_obj, proj_data);
						if (err == FZRT_NOERR)
							err = update_obj_attr(windex, edit_obj, proj_data);
					}
				}
				(*which_obj_in_sym)++;
			}
		}
	}
	return err;
}


/************************************************************************
	add_obj
		Adds an object to this renderer's own list of objects.
*************************************************************************/
static fzrt_error_td add_obj(fzrt_int windex, fz_objt_ptr objt, x_pixel_proj_data_td_ptr proj_data, obj_td** cur_obj_ptr)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	obj_td* look_for_obj,*look_for_sym;
	fzrt_UUID_td uuid;
	fzrt_int which_obj_in_sym = 0; /* start at 0 */
	
	/* make sure not on the list. */
	look_for_obj = find_obj_td(objt, NULL, proj_data, NULL, -1);
	look_for_sym = is_sym_present(objt, proj_data);

	if (look_for_obj == NULL && look_for_sym == NULL) /* not on list, so add */
	{
		/* maybe this is a symbol though ... */

		_model_funcs.fz_objt_alys_get_objt_cntl_uuid(windex, objt, uuid);
		if (_base_funcs.fzrt_UUID_compare(uuid, FZ_OBJT_TYPE_SYMB) == 0)
		{
			/* found symbol */
			err = found_symbol(windex, objt /*parent*/, objt/*notf sym*/, &which_obj_in_sym, proj_data, cur_obj_ptr, NULL /* no matrix */, 0/* new obj */);
		}
		else
		{
			/* found object */
			err = add_obj_core(windex, objt, proj_data, cur_obj_ptr, NULL/* no notf sym */, -1/* no index of sym */, NULL/* no matrix */);
		}
	}
	
	
	return err;
}


/************************************************************************
	edit_obj
		Updates an object in this renderer's own list of objects.
*************************************************************************/
static fzrt_error_td edit_obj(fzrt_int windex, fz_objt_ptr objt, x_pixel_proj_data_td_ptr proj_data, fzrt_int flag)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	obj_td* look_for_obj,*look_for_sym;
	fzrt_UUID_td uuid;
	fzrt_int which_obj_in_sym = 0;


	look_for_obj = find_obj_td(objt, NULL, proj_data, NULL, -1);
	look_for_sym = is_sym_present(objt, proj_data);
	if (look_for_obj != NULL || look_for_sym != NULL)
	{
		_model_funcs.fz_objt_alys_get_objt_cntl_uuid(windex, objt, uuid);
		if (_base_funcs.fzrt_UUID_compare(uuid, FZ_OBJT_TYPE_SYMB) == 0)
		{
			/* found symbol */
			err = found_symbol(windex, objt/*start at top*/, objt, &which_obj_in_sym, proj_data, NULL/*don't know obj_td*/, NULL /* no matrix */, flag/* update */);
		}
		else
		{
			/* found object */
			if (flag == 1)
			{
				err = update_obj_geom(windex, look_for_obj, NULL, proj_data);
			}
			if (flag == 1 || flag == 2)
			{
				if (err == FZRT_NOERR)
					err = update_obj_tgroups(windex, look_for_obj, proj_data);
				if (err == FZRT_NOERR)
					err = update_obj_attr(windex, look_for_obj, proj_data);
			}
		}
		
	}

	return err;
}


/************************************************************************
	obj_free
		Frees the actual memory used by an obj_td.
*************************************************************************/
static fzrt_error_td obj_free(obj_td* cur_obj)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	texture_group_td* cur_tgroup;
	texture_group_td* next_tgroup;
	
	if (cur_obj->faces != NULL)
	{
		/* free memory of faces array */
		err = _mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&cur_obj->faces);
	}
	
	if (err == FZRT_NOERR)
	{
		/* free memory for texture groups */
		cur_tgroup = cur_obj->tgroup;
		while (err == FZRT_NOERR && cur_tgroup != NULL)
		{
			next_tgroup = cur_tgroup->next;
			err = _mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&cur_tgroup);
			cur_tgroup = next_tgroup;
		}
	}
	
	if (err == FZRT_NOERR)
	{
		/* free memory of cur_obj */
		err = _mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&cur_obj);
	}
	
	return err;
}

/************************************************************************
	remove_obj
		Removes an object from this renderer's linked list of objects.
*************************************************************************/
static fzrt_error_td remove_obj(fz_objt_ptr objt, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	obj_td* cur_obj;
	obj_td* prev_obj;
	fzrt_boolean found;
	
	do /* if removing a symbol, there might be more than one removal */
	{
		cur_obj = NULL;
		prev_obj = NULL;
		found = FALSE;

		/* look for object to delete, remember previous object. */
		cur_obj = proj_data->root_obj;
		
		while (cur_obj != NULL)
		{
			if (cur_obj->notf_sym_inst == objt)
			{
				found = TRUE;
				break;
			}
			if (cur_obj->ref == objt)
			{
				found = TRUE;
				break;
			}
			
			prev_obj = cur_obj;
			cur_obj = cur_obj->next;
		}
		
		if (found == TRUE)
		{
			
			/* fix project data pointers of objects linked list */
			
			
			if (proj_data->root_obj == cur_obj &&
				proj_data->last_obj == cur_obj)
			{
				/* only object in the list */
				proj_data->root_obj = NULL;
				proj_data->last_obj = NULL;
			}
			else if (proj_data->root_obj == cur_obj &&
					 proj_data->last_obj != cur_obj)
			{
				/* at beginning of list of many */
				proj_data->root_obj = cur_obj->next;
			}
			else if (proj_data->root_obj != cur_obj &&
					 proj_data->last_obj == cur_obj)
			{
				/* at end of list of many */
				proj_data->last_obj = prev_obj;
			}
			else
			{
				/* some where in the list of many */
				prev_obj->next = cur_obj->next;
			}

			
			/* deallocate memory for current object */
			
			
			err = obj_free(cur_obj);

		} /* else not found, so can't delete. */
	}while(found && err == FZRT_NOERR);

	return err;
}

/************************************************************************
	remove_msg
		Removes a message from the message queue which informs this
		Renderer of changes to the geometry in the current project, which
		at image prep time will be dequeued to update this renderer's
		object list.
*************************************************************************/
static fzrt_error_td remove_msg(msg_td* msg, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;

	if (proj_data->root_msg != NULL)
	{
		/* only one on the list */
		if (msg == proj_data->root_msg &&
			msg == proj_data->last_msg)
		{
			proj_data->root_msg = NULL;		
			proj_data->last_msg = NULL;		
		}
		/* at beginning of many */
		else if (msg == proj_data->root_msg &&
				 msg != proj_data->last_msg)
		{
			proj_data->root_msg = msg->next;
			proj_data->root_msg->prev = NULL;
		}
		/* at ending of many */
		else if (msg != proj_data->root_msg &&
				 msg == proj_data->last_msg)
		{
			proj_data->last_msg = msg->prev;
			proj_data->last_msg->next = NULL;
		}
		/* in the middle of the list */
		else if (msg != proj_data->root_msg &&
				 msg != proj_data->last_msg)
		{
			(msg->prev)->next = msg->next;
			(msg->next)->prev = msg->prev;
		}
	}

	/* delete memory for msg_td */
	err = _mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&msg);

	return err;
}

/************************************************************************
	add_msg
		Adds a message to the message queue which informs this
		Renderer of changes to the geometry in the current project, which
		at image prep time will be dequeued to update this renderer's
		object list.
*************************************************************************/
static fzrt_error_td add_msg(msg_enum msg_type, fz_objt_ptr objt, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	msg_td* cur_msg;
	msg_td* next_msg;
	
	/* allocate memory for message */
	err = _mem_funcs.fz_mem_zone_alloc(_zone, sizeof(msg_td), FALSE, (fzrt_ptr*)&cur_msg);
	
	if (err == FZRT_NOERR)
	{
		/* no next object by default	 */
		cur_msg->next = NULL;
		cur_msg->prev = NULL;
		
		/* for each object, remember its reference */
		cur_msg->msg_objt = objt;

		/* remember what kind of message */
		cur_msg->msg_type = msg_type;
		
		/* add to end of list, */
		/* adjust project linked list of messages */
		if (proj_data->last_msg != NULL)
		{
			proj_data->last_msg->next = (void*)cur_msg;
			cur_msg->prev = proj_data->last_msg;
			proj_data->last_msg = cur_msg;
		}

		if (proj_data->root_msg == NULL)
		{
			proj_data->root_msg = cur_msg;
			proj_data->last_msg = cur_msg;
		}
	
		switch(msg_type)
		{
			case X_PIXEL_MSG_CREATE_OBJ:
				/* do nothing else */
				break;
			case X_PIXEL_MSG_DELETE_OBJ:
				/* iterate through list, and delete occurences of same */
				/* object with CREATE message, if found, then delete */
				/* the message we just added since they cancel each */
				/* other out. */
				cur_msg = proj_data->root_msg;
				while(err == FZRT_NOERR && cur_msg != NULL)
				{
					if (cur_msg->msg_objt == objt && cur_msg->msg_type == X_PIXEL_MSG_CREATE_OBJ)
					{
						/* found occurence of CREATE message */
						/* delete it since it is in vain. */
						next_msg = cur_msg->next;
						err = remove_msg(cur_msg, proj_data);
						cur_msg = next_msg;
					}
					else if (cur_msg->msg_objt == objt && (cur_msg->msg_type == X_PIXEL_MSG_EDIT_GEOM ||
														   cur_msg->msg_type == X_PIXEL_MSG_EDIT_ATTR))
					{
						/* found occurence of EDITING the object, which */
						/* are in vain since object will be deleted. */
						next_msg = cur_msg->next;
						err = remove_msg(cur_msg, proj_data);
						cur_msg = next_msg;
					}
					else if (cur_msg->msg_objt == objt && cur_msg->msg_type == X_PIXEL_MSG_DELETE_OBJ &&
							 proj_data->last_msg != cur_msg)
					{
						/* found another DELETE message, so get rid of duplicates */
						/* if we are not at the last one just added. */
						next_msg = cur_msg->next;
						err = remove_msg(cur_msg, proj_data);
						cur_msg = next_msg;
					}
					else /* go on to next */
					{
						cur_msg = cur_msg->next;
					}
				}
				break;
			case X_PIXEL_MSG_EDIT_GEOM:
				/* iterate through list, and delete occurences of same */
				/* object with EDIT messages, except for the last one. */
				cur_msg = proj_data->root_msg;
				while(err == FZRT_NOERR && cur_msg != NULL)
				{
					if (cur_msg->msg_objt == objt && (cur_msg->msg_type == X_PIXEL_MSG_EDIT_GEOM ||
													  cur_msg->msg_type == X_PIXEL_MSG_EDIT_ATTR)
												  && cur_msg != proj_data->last_msg)
					{
						/* found occurence of another EDIT message, */
						/* so delete it, because only one EDIT message */
						/* is needed to update the object. */
						next_msg = cur_msg->next;
						err = remove_msg(cur_msg, proj_data);
						cur_msg = next_msg;
					}
					else /* go on to next */
					{
						cur_msg = cur_msg->next;
					}
				}
				break;
			case X_PIXEL_MSG_EDIT_ATTR:
				/* iterate through list, and delete occurences of same */
				/* object with EDIT_ATTR messages, except for the last one. */
				cur_msg = proj_data->root_msg;
				while(err == FZRT_NOERR && cur_msg != NULL)
				{
					if (cur_msg->msg_objt == objt && 
						cur_msg->msg_type == X_PIXEL_MSG_EDIT_ATTR &&
						cur_msg != proj_data->last_msg)
					{
						/* found occurence of another EDIT_ATTR message, */
						/* so delete it, because only one EDIT_ATTR message */
						/* is needed to update the object. */
						next_msg = cur_msg->next;
						err = remove_msg(cur_msg, proj_data);
						cur_msg = next_msg;
					}
					else if (cur_msg->msg_objt == objt && 
							 cur_msg->msg_type == X_PIXEL_MSG_EDIT_GEOM &&
							 cur_msg != proj_data->last_msg)
					{
						/* found occurence of an EDIT_GEOM message, */
						/* so delete EDIT_ATTR, because EDIT_GEOM message */
						/* updates the attributes already. */
						err = remove_msg(proj_data->last_msg, proj_data);
						break;
					}
					else /* go on to next */
					{
						cur_msg = cur_msg->next;
					}
				}
				break;
		}
		
	} /* else failed to allocate memory */
	
	return err;
}



/************************************************************************
	proj_objs_free
		Frees memory used by objects in this renderer.
*************************************************************************/
static fzrt_error_td proj_objs_free(x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	
	obj_td* cur_obj;
	obj_td* next_obj;
	
	cur_obj = proj_data->root_obj;
		
	/* iterate through all objects */
	while (cur_obj != NULL && err == FZRT_NOERR)
	{
		next_obj = cur_obj->next;
		
		err = obj_free(cur_obj);
		
		cur_obj = next_obj;
	}
	
	if (err == FZRT_NOERR)
		proj_data->root_obj = proj_data->last_obj = NULL;
	
	return err;
}

/************************************************************************
	proj_data_delete
		Frees all project-level data.
*************************************************************************/
static fzrt_error_td proj_data_delete(x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;

	/* free memory for objects */
	err = proj_objs_free(proj_data);

	/* free memory for textures */
	while (err == FZRT_NOERR && proj_data->root_texture != NULL)
	{
		err = remove_root_texture(proj_data);
	}
	
	/* free memory for lights */
	if (err == FZRT_NOERR && proj_data->lights != NULL)
	{
		err = _mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&proj_data->lights);
	}
	
	/* free memory for messages not processed */
	while (err == FZRT_NOERR && proj_data->root_msg != NULL)
	{
		err = remove_msg(proj_data->root_msg, proj_data);
	}

	return err;
}


/************************************************************************
	proj_data_init
		Initializes project data variables.
*************************************************************************/
static void proj_data_init(x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	proj_data->root_obj = NULL;
	proj_data->last_obj = NULL;
	proj_data->root_msg = NULL;
	proj_data->last_msg = NULL;
	proj_data->num_lights = 0;
	proj_data->lights = NULL;
	proj_data->is_activated = FALSE;
	proj_data->has_been_activated = FALSE;
	proj_data->root_texture = NULL;
	proj_data->last_texture = NULL;
	proj_data->surf_dirty   = TRUE;
	
}






/************************************************************************
					Raytracing Algorithm Functions
*************************************************************************/


/************************************************************************
	cross_prod
		Computes the cross product of two vectors (not normalized).
		
	parameters
		[in] vec1 - first vector.
		[in] vec2 - second vector.
		[out] res - resulting vector, must be different memory than
		            input vectors.
*************************************************************************/
static void cross_prod(fz_xyz_td* vec1, fz_xyz_td* vec2, fz_xyz_td* res)
/************************************************************************/
{
	res->x = vec1->y*vec2->z - vec2->y*vec1->z;
	res->y = -(vec1->x*vec2->z - vec2->x*vec1->z);
	res->z = vec1->x*vec2->y - vec2->x*vec1->y;
}

/************************************************************************
	same_side
		Determines if a point is on the same side of a line as another
		point.  Useful for computing whether or not a 3d point is within
		a polygon.  Requires use of a second point for reference.
		
	parameters
		[in] p1   - point to determine which side of the line it is on.
		[in] p2   - reference point which caller knows is on one side or
				    the other
		[in] xyz1 - begining point of line segment
		[in] xyz2 - ending point of line segment

	returns
		TRUE if p1 and p2 are on the same side of segment from a to b.
		FALSE otherwise.
*************************************************************************/
static fzrt_boolean same_side(fz_xyz_td* p1, fz_xyz_td* p2, fz_xyz_td* xyz1, fz_xyz_td* xyz2)
/************************************************************************/
{
	fzrt_boolean rv;
	fz_xyz_td vec1, vec2, vec3, cpv1, cpv2;

	FZ_MATH_3D_CREATE_VEC(xyz1, xyz2,  &vec1);
	FZ_MATH_3D_CREATE_VEC(xyz1, p1, &vec2);
    cross_prod(&vec1, &vec2, &cpv1);
	FZ_MATH_3D_CREATE_VEC(xyz1, p2, &vec3);
	cross_prod(&vec1, &vec3, &cpv2);
    if (FZ_MATH_3D_DOT_PROD(&cpv1, &cpv2) >= 0)
    	rv = TRUE;
    else
    	rv = FALSE;
    return rv;
}

/************************************************************************
	intersect
		Iterates through all objects in scene, and finds the one that
		intersects with the given vector, from the given origin.
		Given multiple objects that intersect, the closest one's index
		is stored in the obj parameter; the closest face's index in the
		face parameter, and the intersection magnitude along the vector
		in the i_sect parameter.
		if no object intersects, returns -1 or NULL in the obj, face,
		t_isect, and isect parameters.
		
	parameters
		[in] windex		- project window index.
		[in] origin		- the origin of the ray.
		[in] ray_dir	- the ray's direction.
		[in] proj_data	- the project data.
		[out] obj		- pointer to the reference of the intersected object.
		[out] face		- the index of the intersected face on the object.
		[out] t_isect	- the magnitude parameter along the ray where the
		                  intersection occurs.
		                  e.g. origin + ray_dir*t_isect = pt_of_intersection
*************************************************************************/
static void intersect(fzrt_int windex, fz_xyz_td* origin, fz_xyz_td* ray_dir, x_pixel_proj_data_td_ptr proj_data, obj_td** obj, fzrt_int* face, double* t_isect, fz_xyz_td* isect)
/************************************************************************/
{
	/* object tracing */
	obj_td*	cur_obj;
	fzrt_int	face_count;
	fzrt_int	face_index;
	/* bboxes */
	fz_xyz_td	points[2];
	fzrt_int		num_points; /* number of intersections with bbox */
	/* intersection calculation */
	double			dot_res, dot_res2;
	double			t, t_min = 9e100;
	fzrt_boolean	found_one = FALSE;
	obj_td*			isect_obj_min = NULL;
	fzrt_int			isect_face_min;
	fz_xyz_td		isect_tmp, isect_min;
	fz_xyz_td 		look_at;
	fzrt_boolean	try_this_face, skip_face_later;
	
	/* initialization */
	
	look_at.x = origin->x + ray_dir->x;
	look_at.y = origin->y + ray_dir->y;
	look_at.z = origin->z + ray_dir->z;
	
	/* Given the current ray, find intersection point, if any, */
	/* with the objects in the scene. */
	
	
	cur_obj = proj_data->root_obj;
	while (cur_obj != NULL) /* quit when cur_obj is NULL (end of object list) */
	{
		if ( cur_obj->visible == TRUE )
		{
			try_this_face = FALSE;
			
			/* intersects object's bbox? */
			/* if yes, then go on to check for faces' intersections */
			_math_funcs.fz_math_3d_isect_line_cube(origin, &look_at, &cur_obj->bbox.min_xyz, &cur_obj->bbox.max_xyz, (fz_xyz_td*)&points, &num_points);
			
			if (num_points > 0) /* then yes intersects bbox, possible intersection with real object */
			{
				/* For each object, with faces' points and normal, determine */
				/* if there is an intersection with an actual face. */
				
				/* iterate through faces */
				face_count = cur_obj->num_faces;
				
				if (_last_face != -1)
				{
					if (_last_face < face_count)
					{
						try_this_face = TRUE;
					}
				}
				
				skip_face_later = FALSE;
				for (face_index = 0; face_index < face_count; face_index++)
				{
					/* optimization: last face used will most likely be used again, 
					   so check intersection there first. */
					if (try_this_face)
					{
						face_index = _last_face;
					}
					if (skip_face_later)
						if (face_index == _last_face)
							continue; /* skip, already done */
					
					/* find out if this face is forward facing. */
					dot_res = FZ_MATH_3D_DOT_PROD(ray_dir, &cur_obj->faces[face_index].normal);
					
					/* for each face, determine which, if any, is intersected. */
					
					
					/* ray/plane intersection test */
					/* when other kinds of objects are supported, add their */
					/* tests here by determining first which kind of object. */
					
					dot_res2 = FZ_MATH_3D_DOT_PROD(origin, &cur_obj->faces[face_index].normal);
					dot_res2 += cur_obj->faces[face_index].face_plane.d;
					dot_res2 = -dot_res2;
					
					if ((dot_res2 > 0 && dot_res > 0) ||
					    (dot_res2 < 0 && dot_res < 0)) /* same as t > 0, intersection in front of camera */
					{
					 	/* divide only if in front of camera. */
					    t = dot_res2 / dot_res;
						
						/* is it the closest intersection so far? */
						/* (Keeping t greater than a very small distance like 1e-3 avoids */
						/* intersection with same polygon when doing shadow detection). */
						if ((found_one == FALSE || (found_one == TRUE && t < t_min)) && t > 1e-3 /* floating pt inaccuracy*/)
						{
							/* intersection is in front of camera. */
							isect_tmp.x = origin->x + t*ray_dir->x;
							isect_tmp.y = origin->y + t*ray_dir->y;
							isect_tmp.z = origin->z + t*ray_dir->z;

							/* find out if isect_tmp point is in triangle or not. */
							if (same_side(&isect_tmp, &cur_obj->faces[face_index].p3, &cur_obj->faces[face_index].p1, &cur_obj->faces[face_index].p2) &&
								same_side(&isect_tmp, &cur_obj->faces[face_index].p1, &cur_obj->faces[face_index].p2, &cur_obj->faces[face_index].p3) &&
								same_side(&isect_tmp, &cur_obj->faces[face_index].p2, &cur_obj->faces[face_index].p3, &cur_obj->faces[face_index].p1))
							{
								/* inside triangle! */

								found_one = TRUE;
								t_min = t;
								isect_face_min = face_index;
								isect_obj_min = cur_obj;
								isect_min.x = isect_tmp.x;
								isect_min.y = isect_tmp.y;
								isect_min.z = isect_tmp.z;
								
							}/* else outside the triangle */
						} /* else this t is not the minimal one so far */
					}/* else plane intersects behind camera */

					if (try_this_face)
					{
						try_this_face = FALSE;
						face_index = -1; /* start over */
						skip_face_later = TRUE;
					}
				}
				/* done with faces of this object, go on to next object. */
				
			}/* else, no possible intersection with object at all, go on to next object. */
	
		}/* else object not visible, ignore it and go on to the next object */
		
		cur_obj = cur_obj->next;
	}/* loop through all objects */
		
	if (found_one == TRUE) /* found closest intersection */
	{
		*obj = isect_obj_min;
		*face = isect_face_min;
		_last_face = isect_face_min;
		*t_isect = t_min;
		if (isect != NULL)
		{
			isect->x = isect_min.x;
			isect->y = isect_min.y;
			isect->z = isect_min.z;
		}
	}
	else /* no intersection found */
	{
		*obj = NULL;
		*face = -1;
		_last_face = -1;
		*t_isect = -1;
	}
}


/************************************************************************
	texture_get_pixel
		Gets the texture color at the given texture space coordinate
		from the given texture.
*************************************************************************/
static void texture_get_pixel(texture_td* texture, fz_xy_td* st, fz_rgb_float_td* color)
/************************************************************************/
{
	fzrt_int p_x, p_y, p_index;
	
	/* st is already in normalized texture space. */
	p_x = (fzrt_int)((st->x - (fzrt_int)st->x)*texture->width);
	p_y = (fzrt_int)((st->y - (fzrt_int)st->y)*texture->height);
	
	/* now p_x, p_y in pixelized texture space. */
	
	if (p_x < 0)
		p_x += texture->width;
	if (p_y < 0)
		p_y += texture->height;
	
	/* index into one-dimensional array */
	p_index = p_y * texture->width + p_x;
	
	/* set color */
	color->red   = texture->pixels[p_index].red;
	color->green = texture->pixels[p_index].green;
	color->blue  = texture->pixels[p_index].blue;
}


/************************************************************************
	texture_compute_color
		Given an object, and world space coordinate on that object, and
		normal direction (for 2d texturing), returns the texture color
		according to the object's texture and texture control params.
*************************************************************************/
static void texture_compute_color(obj_td* obj, fzrt_int face_index, fz_xyz_td* world_pt, fz_xyz_td* normal, fz_rgb_float_td* color, x_pixel_proj_data_td_ptr proj_data)
/************************************************************************/
{
	fz_xyz_td fx1, fx2, fy1, fy2, fz1, fz2;
	double	dot_cubX1, dot_cubX2, dot_cubY1, dot_cubY2, dot_cubZ1, dot_cubZ2, dot_max, sx, sy,temp;
	fz_xyz_td transform_pt;
	fz_xyz_td transform_norm_pt;
	fz_xyz_td transform_norm,vec1,vec2;
	fz_xy_td st;
	fz_mat4x4_td matX, matY, matZ, matR;
	tctl_td* tctl_ptr;
	
	/* use default project level texture map control attribute? */
	if (obj->faces[face_index].tgroup == NULL)
	{
		tctl_ptr = &proj_data->tctl;
	}
	else
	{
		tctl_ptr = &obj->faces[face_index].tgroup->tctl;
	}

	/* transform point into texture space coordinate system */
	/* translate */
	transform_pt.x = world_pt->x - tctl_ptr->origin.x;
	transform_pt.y = world_pt->y - tctl_ptr->origin.y;
	transform_pt.z = world_pt->z - tctl_ptr->origin.z;
	/* rotate */
	_math_funcs.fz_math_4x4_set_rot_mat(&matZ, -tctl_ptr->rotation.z, FZ_AXIS_Z);
	_math_funcs.fz_math_4x4_set_rot_mat(&matY, -tctl_ptr->rotation.y, FZ_AXIS_Y);
	_math_funcs.fz_math_4x4_set_rot_mat(&matX, -tctl_ptr->rotation.x, FZ_AXIS_X);
	_math_funcs.fz_math_4x4_multiply_mat_mat(&matX, &matY, &matR);
	_math_funcs.fz_math_4x4_multiply_mat_mat(&matR, &matZ, &matR);
	_math_funcs.fz_math_4x4_multiply_mat_xyz(&matR, &transform_pt);
	
	/* wrap texture to surface */
	switch (tctl_ptr->map_type)
	{
		case FZ_MAP_SPACE_FLAT :
			st.x = transform_pt.x;
			st.y = transform_pt.y;
			sx = tctl_ptr->size_x_inv;
			sy = tctl_ptr->size_x_inv;
		break;
		
		case FZ_MAP_SPACE_CYLINDER :
			if ( fabs(transform_pt.x) < FZ_MATH_TOL_LIN ) 
				 st.x = 0.0;
			else st.x = (atan2(transform_pt.y,transform_pt.x) + FZ_PI) / FZ_2PI;
			st.y = transform_pt.z;
			sx = FZ_2PI / tctl_ptr->size_x;
			sy = tctl_ptr->size_y_inv;
		break;
		
		case FZ_MAP_SPACE_SPHERE :
			if ( fabs(transform_pt.x) < FZ_MATH_TOL_LIN ) 
				 st.x = 0.0;
			else st.x = (atan2(transform_pt.y,transform_pt.x) + FZ_PI) / FZ_2PI;
			if ( fabs(transform_pt.x) < FZ_MATH_TOL_LIN && fabs(transform_pt.y) < FZ_MATH_TOL_LIN )
			{	if ( transform_pt.z > 0.0 ) st.y = 1.0;
				else						st.y = 0.0;
			}
			else
			{	vec1 = transform_pt;
				vec2 = vec1;
				vec1.z = 0.0;
				_math_funcs.fz_math_3d_normalize_vec(&vec1,&vec1);
				_math_funcs.fz_math_3d_normalize_vec(&vec2,&vec2);
				st.y = acos(FZ_MATH_3D_DOT_PROD(&vec1,&vec2));
				if ( transform_pt.z < 0.0 ) st.y = FZ_PI2 - st.y;
				else						st.y = st.y + FZ_PI2;
				st.y /= FZ_PI;
			}
			sx = FZ_2PI / tctl_ptr->size_x;
			sy = FZ_PI  / tctl_ptr->size_y;
		break;

		case FZ_MAP_SPACE_CUBIC :
			transform_norm_pt.x = normal->x + world_pt->x - tctl_ptr->origin.x;
			transform_norm_pt.y = normal->y + world_pt->y - tctl_ptr->origin.y;
			transform_norm_pt.z = normal->z + world_pt->z - tctl_ptr->origin.z;
			_math_funcs.fz_math_4x4_multiply_mat_xyz(&matR, &transform_norm_pt);
			transform_norm.x = transform_norm_pt.x - transform_pt.x;
			transform_norm.y = transform_norm_pt.y - transform_pt.y;
			transform_norm.z = transform_norm_pt.z - transform_pt.z;
	

			/* texture control cube faces for cubic mapping */
			fx1.x =  1; fx1.y =  0; fx1.z =  0;
			fx2.x = -1; fx2.y =  0; fx2.z =  0;
			fy1.x =  0; fy1.y =  1; fy1.z =  0;
			fy2.x =  0; fy2.y = -1; fy2.z =  0;
			fz1.x =  0; fz1.y =  0; fz1.z =  1;
			fz2.x =  0; fz2.y =  0; fz2.z = -1;

			dot_cubX1 = FZ_MATH_3D_DOT_PROD(&transform_norm, &fx1);
			dot_cubX2 = FZ_MATH_3D_DOT_PROD(&transform_norm, &fx2);
			dot_cubY1 = FZ_MATH_3D_DOT_PROD(&transform_norm, &fy1);
			dot_cubY2 = FZ_MATH_3D_DOT_PROD(&transform_norm, &fy2);
			dot_cubZ1 = FZ_MATH_3D_DOT_PROD(&transform_norm, &fz1);
			dot_cubZ2 = FZ_MATH_3D_DOT_PROD(&transform_norm, &fz2);

			dot_max = x_max(dot_cubX1, x_max(dot_cubX2, x_max(dot_cubY1, x_max(dot_cubY2, x_max(dot_cubZ1, dot_cubZ2)))));

			if (dot_max == dot_cubX1 || dot_max == dot_cubX2)
			{
				/* project to x plane */
				st.x = transform_pt.y;
				st.y = transform_pt.z;
			}
			else if (dot_max == dot_cubY1 || dot_max == dot_cubY2)
			{
				/* project to y plane */
				st.x = transform_pt.x;
				st.y = transform_pt.z;
			}
			else if (dot_max == dot_cubZ1 || dot_max == dot_cubZ2)
			{
				/* project to z plane */
				st.x = transform_pt.x;
				st.y = transform_pt.y;
			}
			sx = tctl_ptr->size_x_inv;
			sy = tctl_ptr->size_x_inv;
		break;
		
		default :
			st.x = 0.0;
			st.y = 0.0;
			sx = 1.0;
			sy = 1.0;
		break;
	}
	
	/* scale */
	st.x = st.x * sx * obj->faces[face_index].scale_texture_inv;
	st.y = st.y * sy * obj->faces[face_index].scale_texture_inv;
	
	/* center */
	if(tctl_ptr->cntr_horz) st.x += 0.5;
	if(tctl_ptr->cntr_vert) st.y += 0.5;
	
	/* saw tooth */			  
	st.x = st.x - (fzrt_int)st.x;
	st.y = st.y - (fzrt_int)st.y;

	/* flip */
	if(tctl_ptr->flip)
	{	temp = st.x;	st.x = st.y;	st.y = temp;
	}
	
	/* index into texture based on normalized texture space coordinates */
	texture_get_pixel(obj->faces[face_index].texture, &st, color);
}

/************************************************************************
	shade
		Determines the color of a pixel by using the object's normal at
		the intersection point, whether or not the light is visible, and
		the angle between the light, normal and view directions.

	parameters
		[in] windex		  - the active window
		[in] origin		  - the origin of the ray.
		[in] view		  - the ray's direction.
		[in] obj		  - the index of the intersected object.
		[in] face		  - the index of the intersected face on the object.
		[in] isect		  - the point along the ray where the
		                    intersection occurs.
		                    e.g. origin + view*t_isect = pt_of_intersection
		[in] x_pixel_opts - parameters that affect shading calculations
		[in] x_pixel_proj_data_ptr - pointer to project data
		[out] color		  - the color computed at this pixel
*************************************************************************/
static void shade(fzrt_int windex, fz_xyz_td* origin, fz_xyz_td* view, obj_td* obj, fzrt_int face, fz_xyz_td* isect, void* x_pixel_opts_ptr, void* x_pixel_proj_data_ptr, fz_rgb_float_td* color)
/************************************************************************/
{
	/* pixel color */
	fz_rgb_float_td	acc_color, cur_color, diffuse_color;
	/* rays */
	fz_xyz_td		light_dir, viewer_normal;
	fzrt_int 			i;
	/* diffuse */
	double 			dot_res;
	/* light dist */
	double 			vec_len_sqr, vec_len_f;
	double			dist_to_light;
	double			attn;
	/* shadows */
	obj_td*			shad_closest = NULL;
	fzrt_int			shad_closest_face;
	double			shad_t_isect;
	/* specular */
	fz_xyz_td		half;
	double			dot_res2, dot_res3;
	/* options/parameters */
	x_pixel_opts_td* x_pixel_opts;
	/* project data */
	x_pixel_proj_data_td_ptr proj_data;
	/* cone lights */
	double ang;

	/* initialize */

	proj_data = (x_pixel_proj_data_td_ptr)x_pixel_proj_data_ptr;
	x_pixel_opts  = (x_pixel_opts_td*)x_pixel_opts_ptr;
	
	/* use normal facing viewer */
	dot_res = FZ_MATH_3D_DOT_PROD(view, &obj->faces[face].normal);
	if (dot_res < 0)
	{
		viewer_normal.x = obj->faces[face].normal.x;
		viewer_normal.y = obj->faces[face].normal.y;
		viewer_normal.z = obj->faces[face].normal.z;
	}
	else
	{
		viewer_normal.x = obj->faces[face].normal_inv.x;
		viewer_normal.y = obj->faces[face].normal_inv.y;
		viewer_normal.z = obj->faces[face].normal_inv.z;
	}

	acc_color.red = 0;
	acc_color.green = 0;
	acc_color.blue = 0;
	
	/* do textures? */
	if (x_pixel_opts->do_texturing == TRUE &&
		obj->faces[face].texture != NULL &&
		obj->faces[face].texture->tmap_file_found) 
	{
		/* based on current world point intersection, */
		/* determine what the texture coordinate is, and */
		/* use the texture of this material for the color. */
		texture_compute_color(obj, face, isect, &viewer_normal, &diffuse_color, proj_data);
		/* mutliply by diffuse factor */
		diffuse_color.red   *= (float)obj->faces[face].diffuse_factor;
		diffuse_color.green *= (float)obj->faces[face].diffuse_factor;
		diffuse_color.blue  *= (float)obj->faces[face].diffuse_factor;
	}
	else /* no texturing */
	{
		/* otherwise, use the diffuse color of the fmaterial (already pre-multiplied diffuse factor). */
		diffuse_color.red   = obj->faces[face].color.red;
		diffuse_color.green = obj->faces[face].color.green;
		diffuse_color.blue  = obj->faces[face].color.blue;
	}

	/* add global ambient to accumulative color based on this material's ambient factor and reflecting diffuse color */
	acc_color.red   += (float)(proj_data->ambt_color.red  *obj->faces[face].ambient_factor*diffuse_color.red);
	acc_color.green += (float)(proj_data->ambt_color.green*obj->faces[face].ambient_factor*diffuse_color.green);
	acc_color.blue  += (float)(proj_data->ambt_color.blue *obj->faces[face].ambient_factor*diffuse_color.blue);
	
	/* iterate through all the lights */
	for (i = 0; i < proj_data->num_lights; i++)
	{
		/* this light contributes to color only if it is ON */
		if (proj_data->lights[i].is_power_on == TRUE)
		{
			
			/* point lights */
			if (_base_funcs.fzrt_UUID_is_equal( proj_data->lights[i].light_type, FZ_LIGHT_REP_TYPE_POINT))
			{
		 		/* calculate vector to the light from intersection point */
				light_dir.x = proj_data->lights[i].light_pt.x - isect->x;
				light_dir.y = proj_data->lights[i].light_pt.y - isect->y;
				light_dir.z = proj_data->lights[i].light_pt.z - isect->z;
				
				/* distance squared */
				vec_len_sqr = FZ_MATH_3D_VEC_LEN_SQUARED(&light_dir);
				
				/* attenuation */
				attn = 1.0; /* none for now. */
				
				/* calc distance to light */
				dist_to_light = sqrt(vec_len_sqr);
				/* normalize it */
				vec_len_f = 1.0 / dist_to_light;
				light_dir.x *= vec_len_f;
				light_dir.y *= vec_len_f;
				light_dir.z *= vec_len_f;
			}
			else if (_base_funcs.fzrt_UUID_is_equal( proj_data->lights[i].light_type, FZ_LIGHT_REP_TYPE_CONE))
			{
		 		/* calculate vector to the light from intersection point */
				light_dir.x = proj_data->lights[i].light_pt.x - isect->x;
				light_dir.y = proj_data->lights[i].light_pt.y - isect->y;
				light_dir.z = proj_data->lights[i].light_pt.z - isect->z;

				/* distance squared */
				vec_len_sqr = FZ_MATH_3D_VEC_LEN_SQUARED(&light_dir);

				/* calc distance to light */
				dist_to_light = sqrt(vec_len_sqr);
				/* normalize it */
				vec_len_f = 1.0 / dist_to_light;
				light_dir.x *= vec_len_f;
				light_dir.y *= vec_len_f;
				light_dir.z *= vec_len_f;
				
				/* note cone has a direction, see if vector to light */
				/* is "outside" or "inside" the light cone. */
				ang = FZ_MATH_3D_DOT_PROD(&light_dir, &proj_data->lights[i].light_dir);
				if (ang >= 0)
				{
					ang = acos(ang);
					if (ang < proj_data->lights[i].inner_ang)
					{
						/* okay to proceed */
						attn = 1.0;
					}
					else if (ang < proj_data->lights[i].outer_ang)
					{
						/* penumbra */
						attn = (proj_data->lights[i].outer_ang - ang) * (proj_data->lights[i].ang_range_inv);
					}
					else /* outside cone */
					{
						/* skip this light */
						continue;
					}
				}
				else /* wrong direction */
				{
					/* skip this light */
					continue;
				}
				
			}
			else if (_base_funcs.fzrt_UUID_is_equal( proj_data->lights[i].light_type, FZ_LIGHT_REP_TYPE_DISTANT))
			{
				/* get pre-computed direction to distant light (already normalized) */
				/* (parallel rays, independent of intersection point). */
				light_dir.x = proj_data->lights[i].light_dir.x;
				light_dir.y = proj_data->lights[i].light_dir.y;
				light_dir.z = proj_data->lights[i].light_dir.z;
				
				/* attenuation */
				attn = 1.0; /* none for now. */

				/* extremely large distance to distance lights */
				dist_to_light = 9e100;
			}
			else /* skip this light type */
			{
				continue;
			}
						
			/* in shadow? */
			if (x_pixel_opts->have_shadows == TRUE && proj_data->lights[i].do_shadows == TRUE)
				intersect(windex, isect, &light_dir, proj_data, &shad_closest, &shad_closest_face, &shad_t_isect, NULL);
			if (x_pixel_opts->have_shadows == FALSE ||			/* don't care about shadows */
				proj_data->lights[i].do_shadows == FALSE ||	  	/* don't care about shadows */
				shad_closest == NULL ||				   			/* no obstruction */
				shad_t_isect > dist_to_light)		   			/* no obstruction */
			{
				/* not in shadow... */
				
				/* diffuse calculation (use normal facing viewer) */
				dot_res = FZ_MATH_3D_DOT_PROD(&light_dir, &viewer_normal);
				if (dot_res > 0) {
					
					/* diffuse factor already pre-multiplied in color */
					
					/* cosine law for diffuse */
					cur_color.red   = (float)(diffuse_color.red   * dot_res);
					cur_color.green = (float)(diffuse_color.green * dot_res);
					cur_color.blue  = (float)(diffuse_color.blue  * dot_res);
				
					/* specular calculation */
					if (x_pixel_opts->do_specular == TRUE)
					{
						/* half angle rule for specular */
						half.x = light_dir.x - view->x;
						half.y = light_dir.y - view->y;
						half.z = light_dir.z - view->z;
						_math_funcs.fz_math_3d_normalize_vec(&half, &half);
						dot_res2 = FZ_MATH_3D_DOT_PROD(&viewer_normal, &half);
						
						if (dot_res2 > 0)
						{
							/* calc how much highlight based on shininess */
							dot_res3 = pow(dot_res2, obj->faces[face].specular_expo) * obj->faces[face].specular_factor;
							cur_color.red   += (float)dot_res3;
							cur_color.green += (float)dot_res3;
							cur_color.blue  += (float)dot_res3;
						}/* else no highlight here */
					}

					/* light color and intensity, and attenuation affects final color */
					/* (light color already has light intensity premultiplied). */
					cur_color.red   *= (float)(proj_data->lights[i].light_color.red   * attn);
					cur_color.green *= (float)(proj_data->lights[i].light_color.green * attn);
					cur_color.blue  *= (float)(proj_data->lights[i].light_color.blue  * attn);
					
					/* add to accumulative color */
					acc_color.red   += cur_color.red;
					acc_color.green += cur_color.green;
					acc_color.blue  += cur_color.blue;
				} /* else facing away from light */
			}/* else in shadow */
		}/* else light is off */
	}
	/* done iterating through lights */

	/* cap colors at 100% intensities for red, green, blue */
	if (acc_color.red   > 1)
		acc_color.red   = 1;
	if (acc_color.green > 1)
		acc_color.green = 1;
	if (acc_color.blue  > 1)
		acc_color.blue  = 1;
	
	/* return color */
	color->red   = acc_color.red;
	color->green = acc_color.green;
	color->blue  = acc_color.blue;

}





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

/************************************************************************
	x_pixel_wind_opts_defaults
		Sets default values for the user options on a window-level basis.
*************************************************************************/
static fzrt_error_td x_pixel_wind_opts_defaults(fzrt_int windex, fzrt_ptr x_pixel_opts_ptr)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_opts_td_ptr x_pixel_opts;
		
	x_pixel_opts = (x_pixel_opts_td_ptr)x_pixel_opts_ptr;
	
	/* set defaults */
	x_pixel_opts->have_shadows = TRUE;
	x_pixel_opts->do_specular = TRUE;
	x_pixel_opts->do_texturing = TRUE;

	return err;
}

/************************************************************************
	x_pixel_wind_opts_init
		Initializes the user options for this renderer (called for each
		window).
*************************************************************************/
static fzrt_error_td x_pixel_wind_opts_init(fzrt_int windex, fzrt_ptr x_pixel_opts_ptr)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;

	x_pixel_wind_opts_defaults(windex, x_pixel_opts_ptr);

	return err;
}

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


/************************************************************************
	x_pixel_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_pixel_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;

	memcpy(dst_opts_ptr, src_opts_ptr, sizeof(x_pixel_opts_td));

	return err;
}

/************************************************************************
	x_pixel_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_pixel_wind_opts_cmp(fzrt_ptr src_opts_ptr, fzrt_ptr dst_opts_ptr)
/************************************************************************/
{
	fzrt_boolean rv = TRUE;

	rv = (memcmp(dst_opts_ptr, src_opts_ptr, sizeof(x_pixel_opts_td)) == 0);

	return rv;
}


/************************************************************************
	x_pixel_wind_opts_io
		Saves user options for this renderer (called for each window).
*************************************************************************/
static fzrt_error_td x_pixel_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_pixel_opts_td_ptr	x_pixel_opts;
	fzrt_int				lval;
	
	x_pixel_opts = (x_pixel_opts_td_ptr) data;
	
	err = _iost_funcs.fz_iost_boolean(iost,&x_pixel_opts->have_shadows,1);
	if (err == FZRT_NOERR)
	{
		err = _iost_funcs.fz_iost_boolean(iost,&x_pixel_opts->do_specular,1);
		if (err == FZRT_NOERR)
		{
			/* write out a fzrt_int for do_texturing correctly using lval.  keep writing out as fzrt_int for backwards compatibility. */
			if (dir == FZ_IOST_READ)
			{
				err = _iost_funcs.fz_iost_int(iost,&lval,1);
				x_pixel_opts->do_texturing = (fzrt_boolean)lval;
			}
			else
			{
				lval = x_pixel_opts->do_texturing;
				err = _iost_funcs.fz_iost_int(iost,&lval,1);
			}
		}
	}

	return err;
}

/************************************************************************
	x_pixel_init
		Initializes renderer.
*************************************************************************/
static fzrt_error_td x_pixel_init(void)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	
	err = _mem_funcs.fz_mem_zone_dynamic_init(&_zone, 1000);
	
	return err;
}

/************************************************************************
	x_pixel_finit
		Cleans up as renderer quits.
*************************************************************************/
static fzrt_error_td x_pixel_finit(void)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	
	err = _mem_funcs.fz_mem_zone_finit(&_zone);
	
	return err;
}

/************************************************************************
	x_pixel_name
		Returns name of renderer.
*************************************************************************/
static fzrt_error_td x_pixel_name(char* str_name, fzrt_int max_len)
/************************************************************************/
{
	char str[STRING_SIZE];
	
	_base_funcs.fzrt_fzr_get_string(_rsrc_ref, X_PIXEL_STRINGS, X_PIXEL_NAME_STR, str);
	strncpy(str_name, str, max_len);

	return FZRT_NOERR;
}

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

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

	return FZRT_NOERR;
}

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


/************************************************************************
	x_pixel_info
		Returns general info about renderer.
*************************************************************************/
static fzrt_error_td x_pixel_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_PIXEL;
	*behave = FZ_RNDR_BEHAVE_STATIC;
	/* sizes of data for formZ to manage about project, window, and options */
	*proj_data_size = sizeof(x_pixel_proj_data_td);
	*wind_data_size = sizeof(x_pixel_wind_data_td);
	*wind_opts_size = sizeof(x_pixel_opts_td);

	return FZRT_NOERR;
}



/************************************************************************
	x_pixel_view_type
		Tells formZ what views this renderer supports.
*************************************************************************/
static fzrt_boolean x_pixel_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_PERSPECTIVE:
    	case FZ_VIEW_TYPE_PANORAMIC:
			rv = TRUE;
			break;

		case FZ_VIEW_TYPE_ISOMETRIC:	
		case FZ_VIEW_TYPE_OBLIQUE:
    		rv = FALSE;
    		break;
    	
    	default:
    		rv = FALSE;
    		break;
   	}

	return rv; 
}


/************************************************************************
	x_pixel_attr
		Tells formZ what rendering attributes this renderer cares about
		when rendering.
*************************************************************************/
static fzrt_boolean x_pixel_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 */
		case FZ_RNDR_ATTR_USE_TCTL_PROJ_OPTS:	/* offer the standard Texture Map Control options tab in options dialog */
		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_CAN_SS:				/* allow super-sampling */
			rv = TRUE;
			break;
		case FZ_RNDR_ATTR_STAY_IN_IACT_MODE:	/* if switching temporarily out of static rendering, stay out? */
		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_USE_SHAD_PROJ_OPTS:	/* offer smooth shading options tab in 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_pixel_disp_attr
		Tells formZ what attributes are displayed for this renderer.
		returns TRUE if attribute is present.
				FALSE otherwise.
*************************************************************************/
static fzrt_boolean x_pixel_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:
		case FZ_RNDR_DISP_ATTR_AXIS:
		case FZ_RNDR_DISP_ATTR_MARQUEE:
		case FZ_RNDR_DISP_ATTR_LITE:
		case FZ_RNDR_DISP_ATTR_ULAY:
		default:
			rv = FALSE;
			break;
	}
	return rv;
}

/************************************************************************
	x_pixel_wind_init
		Sets up when a window is opened.
*************************************************************************/
static fzrt_error_td x_pixel_wind_init(fzrt_int windex, fzrt_ptr wind_data_ptr)
/************************************************************************/
{
	fzrt_error_td 				err = FZRT_NOERR;
	x_pixel_wind_data_td_ptr	wind_data;

	wind_data = (x_pixel_wind_data_td_ptr)wind_data_ptr;

	/* set defaults */
	if (wind_data != NULL)
		wind_data->dirty = TRUE;

	return err;
}

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


/************************************************************************
	x_pixel_proj_clear_mem
		Clears memory used to store local copy of objects, faces,
		etc.
*************************************************************************/
static fzrt_error_td x_pixel_proj_clear_mem(fzrt_int windex)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_proj_data_td_ptr proj_data;
	x_pixel_wind_data_td_ptr wind_data;
	
	_rndr_funcs.fz_rndr_proj_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&proj_data);
	_rndr_funcs.fz_rndr_wind_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&wind_data);

	/* delete allocated memory */
	err = proj_data_delete(proj_data);

	/* reinitialize */
	proj_data_init(proj_data);
	if (wind_data != NULL)
		wind_data->dirty = TRUE;	

	return err;
}


/************************************************************************
	x_pixel_proj_init
		Sets up when a new project is opened/loaded.
*************************************************************************/
static fzrt_error_td x_pixel_proj_init(fzrt_int windex, fzrt_ptr proj_data_ptr)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_proj_data_td_ptr proj_data;

	proj_data = (x_pixel_proj_data_td_ptr)proj_data_ptr;

	/* intialize project data */
	proj_data_init(proj_data);

	if (err == FZRT_NOERR)
	{
		proj_retrieve_tctl(windex, proj_data);
	}

	return err;
}

/************************************************************************
	x_pixel_proj_finit
		Cleans up when a project is closed.
*************************************************************************/
static fzrt_error_td x_pixel_proj_finit(fzrt_int windex, fzrt_ptr proj_data_ptr)
/************************************************************************/
{
	fzrt_boolean err = FZRT_NOERR;

	err = proj_data_delete((x_pixel_proj_data_td_ptr)proj_data_ptr);

	return err;
}



/************************************************************************
    x_pixel_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_pixel_fuim_setup(fzrt_int windex, fz_fuim_tmpl_ptr fuim_tmpl, fzrt_ptr fuim_data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	short ci1, ci2, ci3;
	x_pixel_opts_td_ptr x_pixel_opts;
	char str[STRING_SIZE];

	err = _rndr_funcs.fz_rndr_wind_opts_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&x_pixel_opts);

	if (err == FZRT_NOERR && x_pixel_opts != NULL)
	{
		/* setup UI template to place UI components on. */
		_base_funcs.fzrt_fzr_get_string(_rsrc_ref, X_PIXEL_STRINGS, X_PIXEL_OPTS_NAME_STR, str);
		err = _fuim_funcs.fz_fuim_tmpl_init(fuim_tmpl, str, FZ_FUIM_FLAG_NONE,X_PIXEL_PLUGIN_TMPL_UUID,0);

		if (err == FZRT_NOERR)
		{
			/* check box */
			_base_funcs.fzrt_fzr_get_string(
				_rsrc_ref, X_PIXEL_STRINGS, SHADOWS_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_pixel_opts->have_shadows, TRUE);
			/* check box */
			_base_funcs.fzrt_fzr_get_string(
				_rsrc_ref, X_PIXEL_STRINGS, SPECULAR_STR, str);
			ci2 = _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, ci2, &x_pixel_opts->do_specular, TRUE);

			/* check box */
			_base_funcs.fzrt_fzr_get_string(
				_rsrc_ref, X_PIXEL_STRINGS, TEXTURING_STR, str);
			ci3 = _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, ci3, &x_pixel_opts->do_texturing, TRUE);
		}
	}

	return err;
}


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

	err = _rndr_funcs.fz_rndr_wind_opts_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&x_pixel_opts);
	
	if (err == FZRT_NOERR && data != NULL && parm_indx >= X_PIXEL_PARM_SHADOWS && parm_indx < X_PIXEL_PARM_MAX)
	{
		switch(parm_indx)
		{
			case X_PIXEL_PARM_SHADOWS:
				_type_funcs.fz_type_set_boolean(&x_pixel_opts->have_shadows, data);
				break;
			case X_PIXEL_PARM_SPECULAR:
				_type_funcs.fz_type_set_boolean(&x_pixel_opts->do_specular, data);
				break;
			case X_PIXEL_PARM_TEXTURES:
				_type_funcs.fz_type_set_boolean(&x_pixel_opts->do_texturing, 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_pixel_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_pixel_set_parm(fzrt_int windex, fzrt_int parm_indx, fz_type_td* data)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_opts_td_ptr x_pixel_opts;

	err = _rndr_funcs.fz_rndr_wind_opts_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&x_pixel_opts);
	
	if (err == FZRT_NOERR && data != NULL && parm_indx >= X_PIXEL_PARM_SHADOWS && parm_indx < X_PIXEL_PARM_MAX)
	{
		switch(parm_indx)
		{
			case X_PIXEL_PARM_SHADOWS:
				_type_funcs.fz_type_get_boolean(data, &x_pixel_opts->have_shadows);
				break;
			case X_PIXEL_PARM_SPECULAR:
				_type_funcs.fz_type_get_boolean(data, &x_pixel_opts->do_specular);
				break;
			case X_PIXEL_PARM_TEXTURES:
				_type_funcs.fz_type_get_boolean(data, &x_pixel_opts->do_texturing);
				break;
		}
		x_pixel_notf_wind(windex, FZ_NOTF_WIND_OPTS, FZ_NOTF_PROJ_NONE);
	}
	else
	{
		err = _base_funcs.fzrt_error_set(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0);
	}
	return err;
}


/************************************************************************
    x_pixel_image_prep
    	Prepares window for rendering.
*************************************************************************/
static fzrt_error_td x_pixel_image_prep(fzrt_int windex)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	msg_td* cur_msg;
	x_pixel_proj_data_td_ptr proj_data;
	obj_td* obj;
	
	_rndr_funcs.fz_rndr_proj_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&proj_data);
	
	/* devour message queue */
	
	cur_msg = proj_data->root_msg;
	
	while(err == FZRT_NOERR && cur_msg != NULL)
	{
		switch(cur_msg->msg_type)
		{
			case X_PIXEL_MSG_CREATE_OBJ:
				err = add_obj(windex, cur_msg->msg_objt, proj_data, &obj);
				break;
			case X_PIXEL_MSG_DELETE_OBJ:
				err = remove_obj(cur_msg->msg_objt, proj_data);
				break;
			case X_PIXEL_MSG_EDIT_GEOM:
				err = edit_obj(windex, cur_msg->msg_objt, proj_data, 1 /* edit geom */);
				break;
			case X_PIXEL_MSG_EDIT_ATTR:
				err = edit_obj(windex, cur_msg->msg_objt, proj_data, 2 /* edit attr */);
				break;
		}
		if (err == FZRT_NOERR)
			err = remove_msg(proj_data->root_msg, proj_data);
		
		cur_msg = proj_data->root_msg;
	}

	
	/* retrieve default project texture control as it may have changed since last retrieval. */
	
	if (err == FZRT_NOERR)
	{
		proj_retrieve_tctl(windex, proj_data);
	}

	return err;
}




/************************************************************************
	x_pixel_image_render_core
	
		handles rendering in the surface style preview window as well as
		the rendering in a project window
	
		Renders the scene.  Walks through all pixels of the window,
		shooting rays from eye point, finds intersections with objects,
		and shades each pixel according to light and angles, etc.
		
		Follows the basic raytracing algorithm:
		
		1) shoot/trace ray into the scene from the eye point.
		Iterate through all pixels, finding intersection points with
		the geometry and shading the pixels appropriately.
		2) for each ray, check if ray intersects any objects in the scene.
		3) for each pixel, after finding intersections, if any, compute
		the color based on the intersection point, the lights' positions,
		and the viewer's position.
*************************************************************************/
static fzrt_error_td x_pixel_image_render_core(
	fzrt_int				windex, 
	fzrt_error_td		prep_err, 
	fzrt_rect 			*sub_image,
	fz_fuim_tmpl_ptr	fuim_tmpl,
	unsigned char		*image_buffer,
	fzrt_int				first_scanline,
	fzrt_boolean		*was_cancelled,
	fzrt_int				*last_scanline
	)
{
	/* error catching */
	fzrt_error_td		err, free_err;
	/* use options */
	x_pixel_opts_td_ptr x_pixel_opts;
	/* clear background */
	fz_rgb_float_td		bgnd_color_float;
	/* raytracing - shoot ray */
	fz_view_ptr	cur_view;
	fz_type_td	data;
	fz_xyz_td	eye, coi, upv, view, view_norm, view_cur, left,upn;
	fz_xy_mm_td	view_area;
	double		pers_angle;
	fzrt_int		width, height, dpi;
	fzrt_int		h_width, h_height;
	fzrt_int		sub_height, sub_width;
	fzrt_int 		row, col, start_row, start_col, end_row, end_col, scanline_index;
	double		dist, norm_f;
	double		aspect, horz_dist, vert_dist, dx, dy;
	double		max_len, min_len;
	double		wdx, wdy, h_wdx, h_wdy;
	fz_xyz_td	top_left_corner_eye, cur_row_eye, cur_eye;
	fz_xyz_td	top_left_corner_coi, cur_row_coi, cur_coi;
	fz_view_type_enum view_type;
	/* intersection */
	obj_td*	closest = NULL;
	fzrt_int	closest_face = -1;
	double 	t_isect = -1;
	fz_xyz_td isect;
	/* shade */
	fz_rgb_float_td	thecolor;
	/* progress bar */
	fzrt_int	mod_num;
	fzrt_int	count_mod;
	fzrt_boolean inner_cancel = FALSE,interrupt;
	/* image buffer */
	x_pixel_wind_data_td_ptr wind_data;
	fz_rgb_float_td*		 scanline;
	/* project data */
	x_pixel_proj_data_td_ptr proj_data;
	unsigned char	*buff;
	float			val;
	obj_td			*cur_obj;
	fz_attr_visib_enum		visib;

	
	/* initialize */
	
	
	err = prep_err;
	scanline = NULL;


	_rndr_funcs.fz_rndr_wind_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&wind_data);
	_rndr_funcs.fz_rndr_proj_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&proj_data);
	
	if (err == FZRT_NOERR && wind_data != NULL && proj_data != NULL)
	{
		err = _rndr_funcs.fz_rndr_wind_opts_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&x_pixel_opts);

		if (err == FZRT_NOERR && x_pixel_opts != NULL)
		{
			/* mark objects as visisble / invisisble */
			cur_obj = proj_data->root_obj;
			while (cur_obj != NULL) /* quit when cur_obj is NULL (end of object list) */
			{
				_model_attr_funcs.fz_objt_attr_get_objt_visib(windex,cur_obj->ref,TRUE,&visib);
				if ( visib == FZ_ATTR_VISIB_VISIB ) cur_obj->visible = TRUE;
				else								cur_obj->visible = FALSE;
				cur_obj = cur_obj->next;
			}
			
			/* change to surface styles has occurred */
			if (proj_data->surf_dirty)
			{
				/* free memory for textures */
				while (err == FZRT_NOERR && proj_data->root_texture != NULL)
				{	err = remove_root_texture(proj_data);
				}
				
				/* update surface style data of objects */
				cur_obj = proj_data->root_obj;
				while (cur_obj != NULL) 
				{	if ( cur_obj->visible )
					{	update_obj_tgroups(windex, cur_obj, proj_data);
						update_obj_attr(windex, cur_obj, proj_data);
					}
					cur_obj = cur_obj->next;
				}
				proj_data->surf_dirty = FALSE;
			}
		
			/* use project background color as missed color */
			_proj_funcs.fz_proj_color_get_rgb(windex, FZ_PROJ_COLR_BACK, &bgnd_color_float);
			
			/* set up raytracing rays */
			_view_funcs.fz_view_get_active(windex, &cur_view);
			_view_funcs.fz_view_get_parm_data(windex, cur_view, FZ_VIEW_PARM_EYE, &data);
			_type_funcs.fz_type_get_xyz(&data, &eye);
			_view_funcs.fz_view_get_parm_data(windex, cur_view, FZ_VIEW_PARM_COI, &data);
			_type_funcs.fz_type_get_xyz(&data, &coi);
			_view_funcs.fz_view_get_parm_data(windex, cur_view, FZ_VIEW_PARM_UP, &data);
			_type_funcs.fz_type_get_xyz(&data, &upv);
			_view_funcs.fz_view_get_parm_data(windex, cur_view, FZ_VIEW_PARM_TYPE, &data);
			_type_funcs.fz_type_get_enum(&data, (fzrt_int*)&view_type);
			_view_funcs.fz_view_get_parm_data(windex, cur_view, FZ_VIEW_PARM_PERSPECTIVE_ANGLE, &data);
			_type_funcs.fz_type_get_double(&data, &pers_angle);
			_view_funcs.fz_view_get_parm_data(windex, cur_view, FZ_VIEW_PARM_AREA, &data);
			_type_funcs.fz_type_get_xy_mm(&data, &view_area);
					
					
			/*								*/
			/*                   ^ upn		*/
			/*                   |			*/
			/*      view_norm    |			*/
			/* +            <----+ eye		*/
			/* coi                \			*/
			/*                     \		*/
			/*                      left	*/
			/*								*/

			/* the viewer's direction (the main ray) */
			view.x = coi.x - eye.x;
			view.y = coi.y - eye.y;
			view.z = coi.z - eye.z;

			/* compute distance to coi */
			dist = sqrt(view.x*view.x +
						view.y*view.y +
						view.z*view.z);

			/* normalize view vector */
			norm_f = 1.0 / dist;
			view_norm.x = view.x*norm_f;
			view_norm.y = view.y*norm_f;
			view_norm.z = view.z*norm_f;

			/* get orthogonal, normalized left vector */
			_math_funcs.fz_math_3d_cross_prod(&upv, &view, &left);
			/* get orthogonal, normalized up vector */
			_math_funcs.fz_math_3d_cross_prod(&view, &left, &upn);
			
			/* screen pixel dimensions */
			_rndr_funcs.fz_rndr_get_image_resolution(windex, &width, &height, &dpi);
			h_width = width / 2;
			h_height = height / 2;
			
			/* calc increments based on screen dimensions and aspect ratio. */
			aspect = height / (double)width;

			horz_dist = (view_area.xmax - view_area.xmin) / 2;
			vert_dist = (view_area.ymax - view_area.ymin) / 2;
			
			max_len = x_max(horz_dist, vert_dist);
			min_len = x_min(horz_dist, vert_dist);

			if (horz_dist == max_len)
			{
				dx = dy = horz_dist / h_width;
			}
			else
			{
				dx = dy = vert_dist / h_height;
			}

			/* world space increments */
			wdx = dx;
			wdy = dy;
			h_wdx = wdx * 0.5;
			h_wdy = wdy * 0.5;
			
			/* initialize */

			view_cur.x = view_norm.x;
			view_cur.y = view_norm.y;
			view_cur.z = view_norm.z;

			mod_num = (fzrt_int)(0.05*height);
			count_mod = 0;

			thecolor.red = 0;
			thecolor.green = 0;
			thecolor.blue = 0;

			/* render only sub-image if required. */
			if (sub_image != NULL)
			{
				sub_height = sub_image->bottom - sub_image->top;
				start_row = sub_image->top;
				end_row = start_row + sub_height;
				sub_width = sub_image->right - sub_image->left;
				start_col = sub_image->left;
				end_col = start_col + sub_width;
			}
			else
			{
				sub_height = height;
				start_row = first_scanline > -1 ? first_scanline : 0;
				end_row = height;
				sub_width = width;
				start_col = 0;
				end_col = width;
			}
				
			/* allocate array of fz_rgb_float_td for single scan line (re-used for each row) */
			err = _mem_funcs.fz_mem_zone_alloc(_zone, sub_width*sizeof(fz_rgb_float_td), FALSE, (fzrt_ptr*)&scanline);

			if (err == FZRT_NOERR && scanline != NULL)
			{
				_fuim_funcs.fz_fuim_curs_wait(FZ_FUIM_CURS_WAIT_START);
				
				if (view_type == FZ_VIEW_TYPE_AXONOMETRIC)
				{
					/* For axonometric view, each pixel has a different eye point, yet */
					/* same view direction.  So, for each pixel, move eye around a large */
					/* grid plane, perpendicular to the view direction, and aligned */
					/* with the original eye point. */
					
					/* start eye with top left corner */
					top_left_corner_eye.x = eye.x + left.x*-view_area.xmin + upn.x*-view_area.ymin + h_wdx*left.x + h_wdy*upn.x;
					top_left_corner_eye.y = eye.y + left.y*-view_area.xmin + upn.y*-view_area.ymin + h_wdx*left.y + h_wdy*upn.y;
					top_left_corner_eye.z = eye.z + left.z*-view_area.xmin + upn.z*-view_area.ymin + h_wdx*left.z + h_wdy*upn.z;

					/* precompute vectors to be used as incremental factors */
					upn.x *= -wdy;
					upn.y *= -wdy;
					upn.z *= -wdy;
					left.x *= -wdx;
					left.y *= -wdx;
					left.z *= -wdx;
					
					_last_face = -1;

					/* iterate through all pixels */
					for (row = start_row; row < end_row; row++)
					{
						/* for this row, start eye at left edge */
						cur_row_eye.x = row*upn.x + top_left_corner_eye.x;
						cur_row_eye.y = row*upn.y + top_left_corner_eye.y;
						cur_row_eye.z = row*upn.z + top_left_corner_eye.z;
						
						if (_fuim_funcs.fz_fuim_key_cancel() == TRUE ||
							inner_cancel == TRUE)
							/* exit rendering loop */
							break;

						for (col = start_col; col < end_col; col++)
						{
							/* move eye to current column */
							cur_eye.x = col*left.x + cur_row_eye.x;
							cur_eye.y = col*left.y + cur_row_eye.y;
							cur_eye.z = col*left.z + cur_row_eye.z;

							_fuim_funcs.fz_fuim_curs_wait(FZ_FUIM_CURS_WAIT_TURN);

							
							/* find intersection */
							intersect(windex, &cur_eye, &view_cur, proj_data, &closest, &closest_face, &t_isect, &isect);
							scanline_index = col - start_col;
							if (closest != NULL)
							{
								/* calc color for this pixel */
								shade(windex, &cur_eye, &view_cur, closest, closest_face, &isect, x_pixel_opts, proj_data, &thecolor);
								/* set this pixel in the scanline */
								scanline[scanline_index].red   = thecolor.red;
								scanline[scanline_index].green = thecolor.green;
								scanline[scanline_index].blue  = thecolor.blue;
							}
							else /* else no intersection */
							{	
								/* draw only background color */
								scanline[scanline_index].red   = bgnd_color_float.red;
								scanline[scanline_index].green = bgnd_color_float.green;
								scanline[scanline_index].blue  = bgnd_color_float.blue;
							}

							if (_fuim_funcs.fz_fuim_key_cancel() == TRUE)
							{
								/* exit rendering loop */
								inner_cancel = TRUE;
								break;
							}

							/* done with current column's pixel, go on to next column. */
						}
						if (err == FZRT_NOERR)
						{	
							if ( fuim_tmpl == NULL )
							{
								err = _rndr_funcs.fz_rndr_ibuf_add_scanline(
														X_PIXEL_PLUGIN_UUID, 
														scanline, NULL, NULL, NULL);
							}
							else
							{	buff = image_buffer + width * row * 3;
								for (col = 0; col < width; col++)
								{
									val = scanline[col].red * 255.0;
									(*buff) = (val < 0.0) ? 0 : ((val > 255) ? 255 : val);
									buff++;
									
									val = scanline[col].green * 255.0;
									(*buff) = (val < 0.0) ? 0 : ((val > 255) ? 255 : val);
									buff++;
									
									val = scanline[col].blue * 255.0;
									(*buff) = (val < 0.0) ? 0 : ((val > 255) ? 255 : val);
									buff++;
								}
								
								_rmtl_funcs.fz_rmtl_iface_pview_update(fuim_tmpl,image_buffer,row + 1);
								
								_rmtl_funcs.fz_rmtl_iface_pview_interrupt(fuim_tmpl,&interrupt);
								if ( interrupt == TRUE ) 
								{	inner_cancel = TRUE;
									break;
								}
							}
						}
						else
							break;
						/* done with this row, go on to next row */
					}
					/* done with all rows */
				}
				else if (view_type == FZ_VIEW_TYPE_PERSPECTIVE)
				{
					/* For perspective view, each pixel has the same eye point, yet */
					/* different view vector.  So, for each pixel, alter the view */
					/* direction slightly so as to shoot rays at a grid plane */
					/* perpendicular to the original view direction, and aligned */
					/* with the center of interest. */
					
					/* start coi with top left corner */
					top_left_corner_coi.x = coi.x + left.x*-view_area.xmin + upn.x*view_area.ymax + h_wdx*left.x + h_wdy*upn.x;
					top_left_corner_coi.y = coi.y + left.y*-view_area.xmin + upn.y*view_area.ymax + h_wdx*left.y + h_wdy*upn.y;
					top_left_corner_coi.z = coi.z + left.z*-view_area.xmin + upn.z*view_area.ymax + h_wdx*left.z + h_wdy*upn.z;

					/* precompute vectors to be used as incremental factors */
					upn.x *= -wdy;
					upn.y *= -wdy;
					upn.z *= -wdy;
					left.x *= -wdx;
					left.y *= -wdx;
					left.z *= -wdx;
					
					
					/* iterate through all pixels */
					for (row = start_row; row < end_row; row++)
					{
						/* for this row, start coi at left edge */
						cur_row_coi.x = row*upn.x + top_left_corner_coi.x;
						cur_row_coi.y = row*upn.y + top_left_corner_coi.y;
						cur_row_coi.z = row*upn.z + top_left_corner_coi.z;

						if (_fuim_funcs.fz_fuim_key_cancel() == TRUE ||
							inner_cancel == TRUE)
							/* exit rendering loop */
							break;
						
						for (col = start_col; col < end_col; col++)
						{
							/* move coi to current column */
							cur_coi.x = col*left.x + cur_row_coi.x;
							cur_coi.y = col*left.y + cur_row_coi.y;
							cur_coi.z = col*left.z + cur_row_coi.z;
							
							view_cur.x = cur_coi.x - eye.x;
							view_cur.y = cur_coi.y - eye.y;
							view_cur.z = cur_coi.z - eye.z;
							_math_funcs.fz_math_3d_normalize_vec(&view_cur, &view_cur);

							_fuim_funcs.fz_fuim_curs_wait(FZ_FUIM_CURS_WAIT_TURN);


							/* find intersection */
							intersect(windex, &eye, &view_cur, proj_data, &closest, &closest_face, &t_isect, &isect);
							
							scanline_index = col - start_col;
							if (closest != NULL)
							{
								/* calc color for this pixel */
								shade(windex, &eye, &view_cur, closest, closest_face, &isect, x_pixel_opts, proj_data, &thecolor);
								/* set this pixel in the scanline */
								scanline[scanline_index].red   = thecolor.red;
								scanline[scanline_index].green = thecolor.green;
								scanline[scanline_index].blue  = thecolor.blue;
							}
							else /* else no intersection */
							{
								/* draw only background color */
								scanline[scanline_index].red   = bgnd_color_float.red;
								scanline[scanline_index].green = bgnd_color_float.green;
								scanline[scanline_index].blue  = bgnd_color_float.blue;
							}

							if (_fuim_funcs.fz_fuim_key_cancel() == TRUE)
							{
								/* exit rendering loop */
								inner_cancel = TRUE;
								break;
							}

							/* done with current column's pixel, go on to next column. */
						}
					
						if (err == FZRT_NOERR)
						{	
							if ( fuim_tmpl == NULL )
							{
								err = _rndr_funcs.fz_rndr_ibuf_add_scanline(
														X_PIXEL_PLUGIN_UUID, 
														scanline, NULL, NULL, NULL);
							}
							else
							{	buff = image_buffer + width * row * 3;
								for (col = 0; col < width; col++)
								{
									val = scanline[col].red * 255.0;
									(*buff) = (val < 0.0) ? 0 : ((val > 255) ? 255 : val);
									buff++;
									
									val = scanline[col].green * 255.0;
									(*buff) = (val < 0.0) ? 0 : ((val > 255) ? 255 : val);
									buff++;
									
									val = scanline[col].blue * 255.0;
									(*buff) = (val < 0.0) ? 0 : ((val > 255) ? 255 : val);
									buff++;
								}
								
								_rmtl_funcs.fz_rmtl_iface_pview_update(fuim_tmpl,image_buffer,row + 1);
								
								_rmtl_funcs.fz_rmtl_iface_pview_interrupt(fuim_tmpl,&interrupt);
								if ( interrupt == TRUE ) 
								{	inner_cancel = TRUE;
									break;
								}
							}
						}
						else
							break;
						/* done with this row, go on to next row */
					}
					/* done with all rows */
				} /* else no other views supported */

				/* change cursor back */
				_fuim_funcs.fz_fuim_curs_wait(FZ_FUIM_CURS_WAIT_END);

				free_err = _mem_funcs.fz_mem_zone_free(_zone, (fzrt_ptr*)&scanline);
				
				/* Raytracing has just finished, */
				/* do not re-raytrace unless we have to. */
				wind_data->dirty = FALSE;

			} /* else could not allocate memory for scanline */
			
		} /* else problem with x_pixel_opts */
		
	} /* else problem with wind_data */

	if (err == FZRT_NOERR)
	{
		err = free_err;
	}

	if (err != FZRT_NOERR)
	{
		err = _base_funcs.fzrt_error_set(X_ERROR_PIXEL_RENDER, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _x_rndr_plugin_runtime_id);
		/* do not re-raytrace unless we have to. */
		if (wind_data != NULL)
			wind_data->dirty = FALSE;
	}

	if ( was_cancelled ) *was_cancelled = inner_cancel;
	if ( last_scanline ) *last_scanline = row;
	
	
	return err;
}

/************************************************************************
	x_pixel_image_render
		Renders the scene.  Walks through all pixels of the window,
		shooting rays from eye point, finds intersections with objects,
		and shades each pixel according to light and angles, etc.
		
		Follows the basic raytracing algorithm:
		
		1) shoot/trace ray into the scene from the eye point.
		Iterate through all pixels, finding intersection points with
		the geometry and shading the pixels appropriately.
		2) for each ray, check if ray intersects any objects in the scene.
		3) for each pixel, after finding intersections, if any, compute
		the color based on the intersection point, the lights' positions,
		and the viewer's position.
*************************************************************************/
static fzrt_error_td x_pixel_image_render(fzrt_int windex, fzrt_error_td prep_err, fzrt_rect *sub_image)
/************************************************************************/
{
	fzrt_error_td	rv;
	
	rv = x_pixel_image_render_core(windex, prep_err, sub_image, NULL,NULL, -1,NULL,NULL);
	
	return(rv);
}

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

/************************************************************************
	x_pixel_image_dirty
		Tells formZ whether or not the image buffer needs to be redrawn,
		determined by the dirty flag set at other points in the renderer.
*************************************************************************/
static fzrt_boolean x_pixel_image_dirty(fzrt_int windex)
/************************************************************************/
{
	x_pixel_wind_data_td_ptr wind_data;
	
	_rndr_funcs.fz_rndr_wind_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&wind_data);
	if (wind_data != NULL)
		return (wind_data->dirty);
	else
		return FALSE;
}


/************************************************************************
	x_pixel_activate
		Invalidates the screen.  Called when this renderer is selected.
*************************************************************************/
static fzrt_error_td x_pixel_activate(fzrt_int windex)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_proj_data_td_ptr proj_data;
	fz_objt_ptr obj_next = NULL;
	obj_td* cur_obj;
	
	_rndr_funcs.fz_rndr_proj_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&proj_data);
	
	if (proj_data->is_activated == FALSE)
	{
		err = get_light_info(windex, proj_data);
		
		if (proj_data->has_been_activated == FALSE)
		{
			/* build initial list of objects */
			_model_funcs.fz_objt_get_next(windex, NULL, &obj_next);
			
			while(obj_next != NULL && err == FZRT_NOERR)
			{
				err = add_obj(windex, obj_next, proj_data, &cur_obj);
				_model_funcs.fz_objt_get_next(windex, obj_next, &obj_next);
			}
		}
		
		proj_data->is_activated = TRUE;
		proj_data->has_been_activated = TRUE;
	}
	
	
	return err;
}

/************************************************************************
	x_pixel_deactivate
		Called when this renderer has been deselected.
*************************************************************************/
static fzrt_error_td x_pixel_deactivate(fzrt_int windex)
/************************************************************************/
{
	x_pixel_proj_data_td_ptr proj_data;

	_rndr_funcs.fz_rndr_proj_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&proj_data);
	proj_data->is_activated = FALSE;

	return FZRT_NOERR;
}


/************************************************************************
	x_pixel_proj_linked_files
		Informs formZ what files are necessary for this renderer,
		in particular for distributed rendering.
*************************************************************************/
static fzrt_error_td x_pixel_proj_linked_files(fzrt_int windex)
/************************************************************************/
{
	x_pixel_proj_data_td_ptr proj_data;
	texture_td* cur_texture;
	fzrt_int texture_iter;
	char str[256];

	_rndr_funcs.fz_rndr_proj_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&proj_data);
	
	cur_texture = proj_data->root_texture;
	texture_iter = 0;
	while (cur_texture != NULL)
	{
		/* not found in project list, so try to find it with its existing file location, or ask user */
		if(_tmap_funcs.fz_tmap_find_file(windex, cur_texture->tmap, FALSE) == FZRT_NOERR)
		{
			if (cur_texture->tmap_floc == NULL)
				_file_funcs.fzrt_file_floc_init(&cur_texture->tmap_floc);
			_tmap_funcs.fz_tmap_get_floc(cur_texture->tmap, cur_texture->tmap_floc);
			sprintf(str, "Color Map %d", texture_iter);
			_rndr_funcs.fz_rndr_set_linked_file(str, cur_texture->tmap_floc);
			texture_iter++;
			cur_texture->tmap_file_found = TRUE;
		}
		else
			cur_texture->tmap_file_found = FALSE;
		
		cur_texture = cur_texture->next;
	}

	return FZRT_NOERR;
}


/************************************************************************
							Renderer Fill Callback
*************************************************************************/

/************************************************************************
	x_pixel_fill_rndr_fset
		Fills the rndr fset structure with this renderer's rndr callback
		functions.
*************************************************************************/
fzrt_error_td x_pixel_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_pixel_init;
			rndr_fset->fz_rndr_cbak_info 					= x_pixel_info;
			rndr_fset->fz_rndr_cbak_finit 					= x_pixel_finit;
			rndr_fset->fz_rndr_cbak_name 					= x_pixel_name;
			rndr_fset->fz_rndr_cbak_uuid 					= x_pixel_uuid;
			rndr_fset->fz_rndr_cbak_opts_name 				= x_pixel_opts_name;
			rndr_fset->fz_rndr_cbak_opts_uuid 				= x_pixel_opts_uuid;
			rndr_fset->fz_rndr_cbak_attr					= x_pixel_attr;
			rndr_fset->fz_rndr_cbak_disp_attr				= x_pixel_disp_attr;
			rndr_fset->fz_rndr_cbak_handle_view				= x_pixel_view_type;
			rndr_fset->fz_rndr_cbak_activate				= x_pixel_activate;
			rndr_fset->fz_rndr_cbak_deactivate				= x_pixel_deactivate;
			
			/* project management for renderer */
			rndr_fset->fz_rndr_cbak_proj_data_init			= x_pixel_proj_init;
			rndr_fset->fz_rndr_cbak_proj_data_finit			= x_pixel_proj_finit;
			rndr_fset->fz_rndr_cbak_proj_clear_mem			= x_pixel_proj_clear_mem;
			
			/* window management for renderer */
			rndr_fset->fz_rndr_cbak_wind_data_init			= x_pixel_wind_init;
			rndr_fset->fz_rndr_cbak_wind_data_finit			= x_pixel_wind_finit;
			rndr_fset->fz_rndr_cbak_wind_opts_init			= x_pixel_wind_opts_init;
			rndr_fset->fz_rndr_cbak_wind_opts_default		= x_pixel_wind_opts_defaults;
			rndr_fset->fz_rndr_cbak_wind_opts_io			= x_pixel_wind_opts_io;
			rndr_fset->fz_rndr_cbak_wind_opts_copy			= x_pixel_wind_opts_copy;
			rndr_fset->fz_rndr_cbak_wind_opts_are_equal		= x_pixel_wind_opts_cmp;
			rndr_fset->fz_rndr_cbak_wind_opts_finit			= x_pixel_wind_opts_finit;
		
			/* rendering functions */
			rndr_fset->fz_rndr_cbak_image_init				= x_pixel_image_prep;
			rndr_fset->fz_rndr_cbak_image_disp				= x_pixel_image_render;
			rndr_fset->fz_rndr_cbak_image_finit				= x_pixel_image_finit;
			rndr_fset->fz_rndr_cbak_image_inval				= NULL;
			rndr_fset->fz_rndr_cbak_image_dirty				= x_pixel_image_dirty;
			
			/* image export for renderer */
			rndr_fset->fz_rndr_cbak_expt_vect_out				= NULL;
			rndr_fset->fz_rndr_cbak_expt_vect_check_feature		= NULL;
			
			/* user interface for renderer */
			rndr_fset->fz_rndr_cbak_iface_tmpl				= x_pixel_fuim_setup;
			rndr_fset->fz_rndr_cbak_get_parm				= x_pixel_get_parm;
			rndr_fset->fz_rndr_cbak_set_parm				= x_pixel_set_parm;
			
			/* necessary resources for distributed rendering */
			rndr_fset->fz_rndr_cbak_proj_linked_files		= x_pixel_proj_linked_files;
		}
	}
	else
		err = _base_funcs.fzrt_error_set(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0);

	return err;
}

/************************************************************************
							Notification Callbacks
*************************************************************************/

/************************************************************************
	x_pixel_notf_proj
		Project notification callback.
*************************************************************************/
static fzrt_error_td x_pixel_notf_proj(
	fzrt_int				windex,
	fz_notf_proj_enum	proj_notf
	)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_wind_data_td_ptr wind_data;
	x_pixel_proj_data_td_ptr proj_data;

	_rndr_funcs.fz_rndr_wind_data_get(windex, X_PIXEL_PLUGIN_UUID,(fzrt_ptr*)&wind_data);
	_rndr_funcs.fz_rndr_proj_data_get(windex, X_PIXEL_PLUGIN_UUID,(fzrt_ptr*)&proj_data);

	switch(proj_notf)
	{
		case FZ_NOTF_PROJ_NONE:   /* No action */
		case FZ_NOTF_PROJ_NEW:    /* project new */
		case FZ_NOTF_PROJ_OPEN:   /* project open */
		case FZ_NOTF_PROJ_SAVE:   /* project save */
		case FZ_NOTF_PROJ_SAVEAS: /* project saveas */
		case FZ_NOTF_PROJ_CLOSE:  /* project close */
		case FZ_NOTF_PROJ_PICK:   /* project pick changed */
		case FZ_NOTF_PROJ_SNAPS:  /* project snap hilight changed */
		case FZ_NOTF_PROJ_SMTH:   /* project smooth shading options changed */
		case FZ_NOTF_PROJ_GEOM:   /* project geometry options changed */
			/* do nothing */
			break;
		case FZ_NOTF_PROJ_DEFT:   /* project default texture controls changed */
			proj_retrieve_tctl(windex, proj_data);
			if (wind_data != NULL)
				wind_data->dirty = TRUE;
			break;
		case FZ_NOTF_PROJ_LIGHTS: /* project lights changed */
			err = get_light_info(windex, proj_data);
			if (wind_data != NULL)
				wind_data->dirty = TRUE;
			break;
		case FZ_NOTF_PROJ_SURF:   /* project surface style changed */
			
#if 0			// now done once, before rendering
			/* free memory for textures */
			while (err == FZRT_NOERR && proj_data->root_texture != NULL)
			{
				err = remove_root_texture(proj_data);
			}
#endif				
			
			if (err == FZRT_NOERR)
			{
#if 0			// now done once, before rendering
				cur_obj = proj_data->root_obj;
				while (cur_obj != NULL)
				{
					err = update_obj_tgroups(windex, cur_obj, proj_data);
					if (err == FZRT_NOERR)
						err = update_obj_attr(windex, cur_obj, proj_data);
					cur_obj = cur_obj->next;
				}
#endif				
				if (wind_data != NULL)
				{	wind_data->dirty = TRUE;
				}
				
				proj_data->surf_dirty = TRUE;
			}
			break;
		case FZ_NOTF_PROJ_DIRTY:  /* general message when anything in window changes */
		case FZ_NOTF_PROJ_CAMERA: /* project camera changed */
		case FZ_NOTF_PROJ_COLORS: /* project colors changed */
		default:
			if (wind_data != NULL)
				wind_data->dirty = TRUE;
			break;

	}

	return err;
}

/************************************************************************
	x_pixel_notf_wind
		Window notification callback
*************************************************************************/
static fzrt_error_td x_pixel_notf_wind(
	fzrt_int			 	windex,		
	fz_notf_wind_enum 	wind_notf,
	fz_notf_proj_enum	proj_notf	
	)
/************************************************************************/
{
	x_pixel_wind_data_td_ptr wind_data;
	
	_rndr_funcs.fz_rndr_wind_data_get(windex, X_PIXEL_PLUGIN_UUID,(fzrt_ptr*)&wind_data);
	
	switch (wind_notf)
	{

		case FZ_NOTF_WIND_NONE:   /* No action */
		case FZ_NOTF_WIND_GRID:   /* window's grid changed */
		case FZ_NOTF_WIND_DEPTH:  /* window's depth changed */
		case FZ_NOTF_WIND_NEW:    /* window new */
		case FZ_NOTF_WIND_CLOSE:  /* window closed */
		case FZ_NOTF_WIND_RESIZE_START: /* window resize start */
		case FZ_NOTF_WIND_RESIZE_END:   /* window resize end */
		case FZ_NOTF_WIND_ULAY:   /* window's grid changed */
			/* do nothing */
			break;
		case FZ_NOTF_WIND_DIRTY:  /* general message when anything in window changes */
		case FZ_NOTF_WIND_RESIZE: /* window resized */
		case FZ_NOTF_WIND_ISPEC:  /* window image spec changed */
		case FZ_NOTF_WIND_VIEW:   /* window's view changed */
		case FZ_NOTF_WIND_OPTS:   /* window's options (not renderer's options) changed */
			if (wind_data != NULL)
				wind_data->dirty = TRUE;
			break;
		
		case FZ_NOTF_WIND_PROJ:   /* window's project changed */
			x_pixel_notf_proj(windex, proj_notf);
			break;

		default:
			break;
	}

	return FZRT_NOERR;
}

/************************************************************************
	x_pixel_notf_objt
		Object notification callback
*************************************************************************/
static fzrt_error_td x_pixel_notf_objt(
	fzrt_int				windex,
	fz_notf_objt_enum 	objt_notf,
	fz_objt_ptr 		objt
	)
/************************************************************************/
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_wind_data_td_ptr wind_data;
	x_pixel_proj_data_td_ptr proj_data;
	obj_td* obj;

	_rndr_funcs.fz_rndr_wind_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&wind_data);
	_rndr_funcs.fz_rndr_proj_data_get(windex, X_PIXEL_PLUGIN_UUID, (fzrt_ptr*)&proj_data);


	switch(objt_notf)
	{
		case FZ_NOTF_OBJT_NONE:		/* nothing changed */
			/* do nothing */
			break;

		case FZ_NOTF_OBJT_NEW:		/* new object */
			/* add new obj_td to this renderer's object list */
			if (proj_data->is_activated == TRUE)
			{
				err = add_obj(windex, objt, proj_data, &obj);
			}
			else
			{
				add_msg(X_PIXEL_MSG_CREATE_OBJ, objt, proj_data);
			}
			if (wind_data != NULL)
				wind_data->dirty = TRUE;
			break;
		case FZ_NOTF_OBJT_DELETE:	/* deleted object */
			/* delete obj_td from this renderer's object list */
			if (proj_data->is_activated == TRUE)
			{
				err = remove_obj(objt, proj_data);
			}
			else
			{
				add_msg(X_PIXEL_MSG_DELETE_OBJ, objt, proj_data);
			}
			if (wind_data != NULL)
				wind_data->dirty = TRUE;
			break;
		case FZ_NOTF_OBJT_EDTOP:	/* topology changed */
		case FZ_NOTF_OBJT_EDGEO:	/* geometry changed */
			/* redo faces of existing obj_td in this renderer's object list */
			if (proj_data->is_activated == TRUE)
			{
				err = edit_obj(windex, objt, proj_data, 1 /* update geom */);
			}
			else
			{
				add_msg(X_PIXEL_MSG_EDIT_GEOM, objt, proj_data);
			}
			if (wind_data != NULL)
				wind_data->dirty = TRUE;
			break;
		case FZ_NOTF_OBJT_EDATTR:	/* attributes changed */
			/* redo faces attributes of existing obj_td in this renderer's object list */
			if (proj_data->is_activated == TRUE)
			{
				err = edit_obj(windex, objt, proj_data, 2 /* update attr */);
			}
			else
			{
				add_msg(X_PIXEL_MSG_EDIT_ATTR, objt, proj_data);
			}
			if (wind_data != NULL)
				wind_data->dirty = TRUE;
			break;
		
		default:
			break;
	}

	return err;
}


/************************************************************************
						Notification Fill Callback
*************************************************************************/

/************************************************************************
	x_pixel_fill_notf_fset
		Fills the notf fset structure with this renderer's notf callback
		functions.
*************************************************************************/
fzrt_error_td x_pixel_fill_notf_fset(const fzpl_fset_def_ptr fset_def, fzpl_fset_td* const fset)
/************************************************************************/
{
	fzrt_error_td		 err = FZRT_NOERR;
	fz_notf_cbak_fset* notf_fset;

	err = _fset_glue->fzpl_fset_def_check( fset_def,
									 FZ_NOTF_CBAK_FSET_VERSION,
									 FZPL_TYPE_STRING(fz_notf_cbak_fset),
									 sizeof(fz_notf_cbak_fset),
									 FZPL_VERSION_OP_NEWER );
	if (err == FZRT_NOERR)
	{
		notf_fset = (fz_notf_cbak_fset*)fset;

		notf_fset->fz_notf_cbak_syst = NULL;
		notf_fset->fz_notf_cbak_syst_units = NULL;

		notf_fset->fz_notf_cbak_proj = x_pixel_notf_proj;
		notf_fset->fz_notf_cbak_proj_units = NULL;
	
		notf_fset->fz_notf_cbak_wind = x_pixel_notf_wind;
		notf_fset->fz_notf_cbak_wind_units = NULL;
	
		notf_fset->fz_notf_cbak_objt = x_pixel_notf_objt;
		notf_fset->fz_notf_cbak_lite = NULL;
		notf_fset->fz_notf_cbak_view = NULL;
		notf_fset->fz_notf_cbak_layr = NULL;

		notf_fset->fz_notf_cbak_pref_default = NULL;
		notf_fset->fz_notf_cbak_pref_model_type = NULL;
	}

	return err;
}


/************************************************************************
		Surface Style Representation (srep) Callbacks.
		This plugin demonstrates, how a renderer can implement
		its own surface style representation. The x pixel sample
		defines four shading paramters : ambient, diffuse, specular
		and shininess, which it uses instead of those defined by
		the RenderZone representation of the surface style. Look
		for uses of those parameters in the update_obj_attr() function.
*************************************************************************/

#define X_SREP_UUID	"\x52\xa1\x05\xa6\xb1\xc1\x4b\x01\x82\x36\xe5\xb9\x92\x70\xde\xb3"

/************************************************************************
	Name callback. Returns the (user interface) name of the surface style representation
		of the x pixel renderer.
*************************************************************************/
static fzrt_error_td	x_srep_name (
	char		*name,
	fzrt_int		max_name	
	)
{
	strcpy(name,"X Pixel");
	return(FZRT_NOERR);
}

/************************************************************************
	x_srep_uuid
		UUID callback. Returns the UUID of the surface style representation
		of the x pixel renderer. 
*************************************************************************/
static fzrt_error_td	 x_srep_uuid (
	fzrt_UUID_td			uuid
	)
{
	_base_funcs.fzrt_UUID_copy(X_SREP_UUID,uuid);
	return(FZRT_NOERR);
}

/************************************************************************
	x_srep_info
		Info callback. Returns basic information about the surface style representation
		of the x pixel renderer. 
*************************************************************************/
static fzrt_error_td	 x_srep_info (
	fzrt_int		*size,
	fzrt_int		*flags
	)
{
	*size = sizeof(x_srep_td);
	*flags = 0;
	return(FZRT_NOERR);
}

/************************************************************************
	x_srep_init
		Init callback. One time initialization of stuff for the surface style representation
		of the x pixel renderer. Here it simply captures the run time index,
		which is used later for more efficient lookup of data.
*************************************************************************/
static fzrt_error_td	 x_srep_init (
		void
	)
{
	_rmtl_funcs.fz_rmtl_get_srep_index(X_SREP_UUID,&_x_srep_indx);
	return(FZRT_NOERR);
}

/************************************************************************
	x_srep_data_io
		IO callback. Writes / reads the values of the surface style representation
		of the x pixel renderer
*************************************************************************/
static fzrt_error_td 	 x_srep_data_io (
	fzrt_int 					windex, 
	fz_iost_ptr				iost, 	
	fz_iost_dir_td_enum		dir,	
	fzpl_vers_td * const 	version, 	
	fzrt_unsigned_int 			size,	
	void					*data	
	)
{
	x_srep_td		*x_srep;
	fzrt_error_td	rv = FZRT_NOERR;
	
	x_srep = (x_srep_td*) data;

	if ( dir == FZ_IOST_WRITE ) *version = 0;

	if((rv = _iost_funcs.fz_iost_float(iost,&x_srep->ambient,1)) == FZRT_NOERR &&
	   (rv = _iost_funcs.fz_iost_float(iost,&x_srep->diffuse,1)) == FZRT_NOERR &&
	   (rv = _iost_funcs.fz_iost_float(iost,&x_srep->specular,1)) == FZRT_NOERR )
	{	rv = _iost_funcs.fz_iost_float(iost,&x_srep->specular_expo,1);
	}
	
	return(rv);
}

/************************************************************************
	x_srep_data_init
		Data init callback. Assigns default values to the surface style representation
		of the x pixel renderer
*************************************************************************/
static fzrt_error_td	 x_srep_data_init (
	fzrt_int					windex,	
	void					*data	
	)
{
	x_srep_td	*x_srep;
	
	x_srep = (x_srep_td*) data;
	
	x_srep->ambient  		= 1.0;
	x_srep->diffuse  		= 0.75;
	x_srep->specular 		= 0.25;
	x_srep->specular_expo	= 0.5;
	
	return(FZRT_NOERR);
}

/************************************************************************
	x_srep_data_iface_tmpl
		Iface template callback. Creates the interface items in the
		respective tab in the surface style parameters dialog.
*************************************************************************/
static fzrt_error_td 	 x_srep_data_iface_tmpl (	
	fzrt_int				windex,		
	fz_fuim_tmpl_ptr	fuim_tmpl, 	
	fzrt_int				tab_id,		
	fzrt_ptr			fuim_data	
	)
{
	short		align[10];
	x_srep_td	*x_srep;
	
	x_srep = (x_srep_td*) fuim_data;
	
	_fuim_funcs.fz_fuim_new_slid_edit_pcent_float(
		fuim_tmpl,
		tab_id,
		"Ambient Factor",
		FZ_FUIM_NONE,
		FZ_FUIM_NONE,
		0.0,
		1.0,
		0.0,
		100.0,
		FZ_FUIM_RANGE_MIN | FZ_FUIM_RANGE_MIN_INCL | 
		FZ_FUIM_RANGE_MAX | FZ_FUIM_RANGE_MAX_INCL,
		NULL,
		&x_srep->ambient,
		&align[0],
		NULL
	);
	
	_fuim_funcs.fz_fuim_new_slid_edit_pcent_float(
		fuim_tmpl,
		tab_id,
		"Diffuse Factor",
		FZ_FUIM_NONE,
		FZ_FUIM_NONE,
		0.0,
		1.0,
		0.0,
		100.0,
		FZ_FUIM_RANGE_MIN | FZ_FUIM_RANGE_MIN_INCL | 
		FZ_FUIM_RANGE_MAX | FZ_FUIM_RANGE_MAX_INCL,
		NULL,
		&x_srep->diffuse,
		&align[1],
		NULL
	);
	
	_fuim_funcs.fz_fuim_new_slid_edit_pcent_float(
		fuim_tmpl,
		tab_id,
		"Specular Factor",
		FZ_FUIM_NONE,
		FZ_FUIM_NONE,
		0.0,
		1.0,
		0.0,
		100.0,
		FZ_FUIM_RANGE_MIN | FZ_FUIM_RANGE_MIN_INCL | 
		FZ_FUIM_RANGE_MAX | FZ_FUIM_RANGE_MAX_INCL,
		NULL,
		&x_srep->specular,
		&align[2],
		NULL
	);
	_fuim_funcs.fz_fuim_new_slid_edit_pcent_float(
		fuim_tmpl,
		tab_id,
		"Specular Shininess",
		FZ_FUIM_NONE,
		FZ_FUIM_NONE,
		0.0,
		1.0,
		0.0,
		100.0,
		FZ_FUIM_RANGE_MIN | FZ_FUIM_RANGE_MIN_INCL | 
		FZ_FUIM_RANGE_MAX | FZ_FUIM_RANGE_MAX_INCL,
		NULL,
		&x_srep->specular_expo,
		&align[3],
		NULL
	);
	_fuim_funcs.fz_fuim_item_align(fuim_tmpl, FZ_FUIM_ALIGN_VLFT | FZ_FUIM_ALIGN_MAX, 4, align);
	
	return(FZRT_NOERR);
}

static fzrt_int	_last_scanline = 0;
/************************************************************************
	x_srep_data_iface_pview
		iface template callback. Renderes a preview of the surface style
		using the scene described in the preview_windex project passed in
*************************************************************************/
static fzrt_error_td 	 x_srep_data_iface_pview (	
	fzrt_int				windex,					
	fzrt_int				pview_windex,			
	fzrt_int				action,					
	fzrt_boolean		dirty,					
	fz_fuim_tmpl_ptr	fuim_tmpl, 				
	fzrt_ptr			fuim_data,				
	unsigned char		*image_buffer,			
	fzrt_boolean		*complete				
	)
{
	fzrt_int						first_scanline,scan_line;
	fzrt_boolean				cancelled;
	x_pixel_proj_data_td_ptr	proj_data;
	
	switch(action)
	{
		case 0 :		break;
		
		
		case 1 :
			cancelled = FALSE;
			/* previously cancelled rendering starts back at the last scanline rendered */
			if ( dirty == 0 ) first_scanline = _last_scanline+1;
			/* new rendering starts at scanline 0 */
			else			  first_scanline = 0;
			
			/* MAKE SURE NEW SURFACE STYLE IS USED */
			if ( dirty )
			{	_rndr_funcs.fz_rndr_proj_data_get(pview_windex, X_PIXEL_PLUGIN_UUID,(fzrt_ptr*)&proj_data);
				proj_data->surf_dirty = TRUE;
			}
			
			/* render the scene using the preview windex */
			x_pixel_activate(pview_windex);
			x_pixel_image_prep(pview_windex);
			x_pixel_image_render_core(pview_windex,FZRT_NOERR,NULL,fuim_tmpl,image_buffer,first_scanline,&cancelled,&scan_line);
			x_pixel_image_finit(pview_windex);
			x_pixel_deactivate(pview_windex);
			
			/* return cancel status */
			*complete = cancelled ? FALSE : TRUE;
			
			/* store last rendered scanline */
			if ( cancelled ) _last_scanline = scan_line;
			else			 _last_scanline = 0;
		break;
		
		
		case 2 :		break;
	
	
	}

	return(FZRT_NOERR);
}

/************************************************************************
	x_pixel_fill_srep_fset
		Fills the srep fset structure with this renderer's surface style representation callback
		functions.
*************************************************************************/
fzrt_error_td x_pixel_fill_srep_fset(const fzpl_fset_def_ptr fset_def, fzpl_fset_td* const fset)
{
	fzrt_error_td		 err = FZRT_NOERR;
	fz_srep_cbak_fset	*srep_fset;

	err = _fset_glue->fzpl_fset_def_check( fset_def,
									 FZ_SREP_CBAK_FSET_VERSION,
									 FZPL_TYPE_STRING(fz_srep_cbak_fset),
									 sizeof(fz_srep_cbak_fset),
									 FZPL_VERSION_OP_NEWER );
	if (err == FZRT_NOERR)
	{
		srep_fset = (fz_srep_cbak_fset*)fset;

		
		srep_fset->fz_srep_cbak_name 				= x_srep_name;
		srep_fset->fz_srep_cbak_uuid 				= x_srep_uuid;
		srep_fset->fz_srep_cbak_info 				= x_srep_info;
		srep_fset->fz_srep_cbak_init 				= x_srep_init;
		
		srep_fset->fz_srep_cbak_data_io 			= x_srep_data_io;
		srep_fset->fz_srep_cbak_data_init 			= x_srep_data_init;
		srep_fset->fz_srep_cbak_data_finit			= NULL;
		srep_fset->fz_srep_cbak_data_copy			= NULL;
		srep_fset->fz_srep_cbak_data_are_equal		= NULL;
		srep_fset->fz_srep_cbak_data_iface_tmpl		= x_srep_data_iface_tmpl;
		srep_fset->fz_srep_cbak_data_iface_pview	= x_srep_data_iface_pview;
	}

	return err;
}

/************************************************************************
	x_pixel_lrep_name
		Returns the name of the light representation to FormZ.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_name( char *name, fzrt_int max_name )
{
	strcpy( name, "X-Pixel" );
	return FZRT_NOERR;
}

/************************************************************************
	x_pixel_lrep_uuid
		Returns the uuid of the light representation to FormZ.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_uuid( fzrt_UUID_td uuid )
{
	_base_funcs.fzrt_UUID_copy( X_PIXEL_LREP_UUID, uuid );
	return FZRT_NOERR;
}

/************************************************************************
	x_pixel_lrep_info
		Returns some basic information about the light represenatation
		such as lrep structure size and any flags that need to be toggled
		(for light rep, most likely none).
*************************************************************************/
static fzrt_error_td x_pixel_lrep_info( fzrt_int* size, fzrt_int* flags )
{
	*size = sizeof( x_pixel_lrep_td );
	*flags = 0;
	return FZRT_NOERR;
}

/************************************************************************
	x_pixel_lrep_init
		This function is called to initialize any data or allocate any
		memory that is needed by the plugin.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_init( void )
{
	return FZRT_NOERR;
}

/************************************************************************
	x_pixel_lrep_finit
		This function is called to free any memory that is needed 
		by the plugin.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_finit( void )
{
	return FZRT_NOERR;
}

/************************************************************************
	x_pixel_lrep_data_io
		Write out data to a save file so that it can be loaded by
		FormZ. The data_io reads and writes, you can check which direction
		it is going in by checking the fz_iost_dir_td_enum variable. the last
		parameter of the fz_iost_[type] functions take the number of variables
		to write (for arrays).
*************************************************************************/
static fzrt_error_td x_pixel_lrep_data_io( fzrt_int	windex, fz_iost_ptr iost, fz_iost_dir_td_enum dir, fzpl_vers_td *const version,
	fzrt_unsigned_int size, fz_lite_ptr light, void *light_rep_data )
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_lrep_ptr lrep;

	lrep = (x_pixel_lrep_ptr)light_rep_data;

	if( dir == FZ_IOST_WRITE )
		*version = 1;

	err = _iost_funcs.fz_iost_double( iost, &lrep->intensity_override, 1 );


	return err;
}

/************************************************************************
	x_pixel_lrep_data_init
		Initializes any data to default values.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_data_init( fzrt_int windex, fz_lite_ptr light, void* light_rep_data )
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_lrep_ptr lrep;

	lrep = (x_pixel_lrep_ptr)light_rep_data;

	lrep->intensity_override = 1.0;

	return err;
}

/************************************************************************
	x_pixel_lrep_data_finit
		Called to clean up any memory incase data_init allocated memory
*************************************************************************/
static fzrt_error_td x_pixel_lrep_data_finit( fzrt_int windex, fz_lite_ptr light, void *light_rep_data )
{
	fzrt_error_td err = FZRT_NOERR;

	memset( light_rep_data, 0, sizeof(x_pixel_lrep_td) );

	return err;
}

/************************************************************************
	x_pixel_lrep_data_copy
		Copies light rep data between two light reps of the same kind
*************************************************************************/
static fzrt_error_td x_pixel_lrep_data_copy( fzrt_int src_windex, fz_lite_ptr src_light, void *src_data, fzrt_int dst_windex,
	fz_lite_ptr dst_light, void *dst_data )
{
	fzrt_error_td err = FZRT_NOERR;

	memcpy( dst_data, src_data, sizeof(x_pixel_lrep_td) );

	return err;
}

/************************************************************************
	x_pixel_lrep_data_are_equal
		Checks if two light reps are identical.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_data_are_equal( fzrt_int windex1, fz_lite_ptr lite1,	void *light_rep_data1,
	fzrt_int	windex2, fz_lite_ptr lite2,	void *light_rep_data2, 
	fzrt_boolean *are_equal )
{
	fzrt_error_td err = FZRT_NOERR;

	*are_equal = !memcmp( light_rep_data1, light_rep_data2, sizeof(x_pixel_lrep_td) );

	return err;
}

/************************************************************************
	x_pixel_lrep_type_support
		Tells FormZ which light types are supported. If there are custom
		light types, they need to be included (see XPIXEL_LIGHT).
*************************************************************************/
static fzrt_error_td x_pixel_lrep_type_support( fzrt_UUID_td	light_type, fzrt_boolean* support )
{
	fzrt_error_td err = FZRT_NOERR;

	if( _base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_AMBIENT )		||
		_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_DISTANT )		||
		_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_POINT ) 		||
		_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_CONE )			||
		_base_funcs.fzrt_UUID_is_equal(light_type, X_PIXEL_LREP_XPIXEL_LIGHT ) )
		*support = true;
	else
		*support = false;

	return err;
}

/************************************************************************
	x_pixel_lrep_parm_support
		Tells FormZ which default parameters are supported and displayed
		or unsupported and hidden, or supported and yet hidden. You can
		specify this per light type and per parameter.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_parm_support( fzrt_UUID_td light_type, fz_light_rep_parm_enum parm_id, fz_light_rep_parm_support_enum	*support )
{
	fzrt_error_td err = FZRT_NOERR;

	if (parm_id >= FZ_LIGHT_REP_PARM_COLOR && parm_id <= FZ_LIGHT_REP_PARM_SHADOWS_ON)
	{
		// by default, this plugin supports and displays most light parameters
		*support = FZ_LREP_PARM_SUPPORT_AND_DISPLAY;
		switch(parm_id)
		{
			case FZ_LIGHT_REP_PARM_COLOR:
				if (_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_ENVIRONMENT))
					*support = FZ_LREP_PARM_SUPPORT_NOT; // does not support
				break;
		
			case FZ_LIGHT_REP_PARM_INTENS_BRIGHT:
					*support = FZ_LREP_PARM_SUPPORT_NO_DISPLAY; // supports, but does not display
		
			case FZ_LIGHT_REP_PARM_LOC_EYE:
				if (_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_AMBIENT) )
					*support = FZ_LREP_PARM_SUPPORT_NO_DISPLAY;
				break;
		
			case FZ_LIGHT_REP_PARM_LOC_COI:
				if (_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_POINT) ||
					_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_AREA) ||
					_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_AMBIENT) )
				{
					*support = FZ_LREP_PARM_SUPPORT_NOT;
				}
				break;
		
			case FZ_LIGHT_REP_PARM_SHADOWS_ON:
				*support = FZ_LREP_PARM_SUPPORT_NOT;
				break;
				
			default:
				break;
		}
	}
	else
	{
		*support = FZ_LREP_PARM_SUPPORT_NOT;
		switch(parm_id)
		{
			case FZ_LIGHT_REP_PARM_RADIUS:
				if (_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_POINT) ||
					_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_AREA) )
				{
					*support = FZ_LREP_PARM_SUPPORT_AND_DISPLAY;
				}
				break;
		
			case FZ_LIGHT_REP_PARM_OUTER_ANGLE:
				if (_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_CONE) )
				{
					*support = FZ_LREP_PARM_SUPPORT_AND_DISPLAY;
				}
				break;
		
			case FZ_LIGHT_REP_PARM_INNER_ANGLE:
				if (_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_CONE))
				{
					*support = FZ_LREP_PARM_SUPPORT_AND_DISPLAY;
				}
				break;
			case FZ_LIGHT_REP_PARM_USE_OBJ:
				if (_base_funcs.fzrt_UUID_is_equal(light_type, FZ_LIGHT_REP_TYPE_AREA) )
				{
					*support = FZ_LREP_PARM_SUPPORT_AND_DISPLAY;
				}
				break;
			case FZ_LIGHT_REP_PARM_OBJ_COLOR:
					*support = FZ_LREP_PARM_SUPPORT_AND_DISPLAY;
				break;
			
			default:
				break;
		}
	}
	

	return err;
}

/************************************************************************
	x_pixel_lrep_custom_count
		Tells FormZ how many lights this plugin is adding.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_custom_count( fzrt_int *count )
{
	fzrt_error_td err = FZRT_NOERR;

	*count = 1;

	return err;
}

/************************************************************************
	x_pixel_lrep_custom_info
		Gives some basic information about each custom light
*************************************************************************/
static fzrt_error_td x_pixel_lrep_custom_info( fzrt_int custom_index, fz_string_td light_name, fzrt_UUID_td light_type, fzrt_floc_ptr palette_pic_floc )
{
	fzrt_error_td err = FZRT_NOERR;

	switch( custom_index )
	{
	case 0:
		strcpy( light_name, "X Pixel Light" );
		_base_funcs.fzrt_UUID_copy( X_PIXEL_LREP_XPIXEL_LIGHT, light_type );
	}

	return err;
}

/************************************************************************
	x_pixel_lrep_custom_parm_count
		The number of parameters this plugin's lights has, this can be
		specified by type or generically for all lights.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_custom_parm_count( fzrt_int windex, void *light_rep_data, fzrt_UUID_td	light_type, fzrt_int *count	)
{
	fzrt_error_td err = FZRT_NOERR;

	*count = 1;

	return err;
}

/************************************************************************
	x_pixel_lrep_custom_parm_info
		Returns information to FormZ about each of this plugin's custom
		light parameters: UUID, name, type, default value, category. 
		UI items can be included here.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_custom_parm_info( fzrt_int windex, void *light_rep_data, fzrt_UUID_td light_type, fzrt_int	parm_indx,
	fz_light_rep_custom_parm_info_enum parm_info, fz_type_td *data )
{
	fzrt_error_td err = FZRT_NOERR;
	fzrt_UUID_td	uuid;
	fz_type_enum	data_type;
	fzrt_int		lval;
	fzrt_boolean	bval;

	switch( parm_indx )
	{
		case 0: // "X Pixel Light"
			switch( parm_info )
			{
				case FZ_LIGHT_REP_CUSTOM_PARM_INFO_UUID:
					_base_funcs.fzrt_UUID_copy( X_PIXEL_LREP_XPIXEL_LIGHT, uuid );
					_type_funcs.fz_type_set_uuid( uuid, data );
					break;
				case FZ_LIGHT_REP_CUSTOM_PARM_INFO_NAME:
					_type_funcs.fz_type_set_string( "X Pixel Light", data );
					break;
				case FZ_LIGHT_REP_CUSTOM_PARM_INFO_TYPE:
					data_type = FZ_TYPE_BOOL;
					_type_funcs.fz_type_set_enum((fzrt_int*)&data_type, data);
					break;
				case FZ_LIGHT_REP_CUSTOM_PARM_INFO_FLAGS:
					lval = 0;
					_type_funcs.fz_type_set_int(&lval, data);
					break;
				case FZ_LIGHT_REP_CUSTOM_PARM_INFO_DEFAULT:
					bval = FALSE;
					_type_funcs.fz_type_set_boolean(&bval, data);
					break;
				case FZ_LIGHT_REP_CUSTOM_PARM_INFO_CATEGORY:
					lval = FZ_LIGHT_REP_CATEGORY_PARAMS;
					_type_funcs.fz_type_set_enum(&lval, data);
					break;
					
				default:
					break;
			}
			break;
			
		default:
			break;
	}

	return err;
}

/************************************************************************
	x_pixel_lrep_custom_parm_get
		This is called by FormZ when get_plugin_parm is called later in
		the program.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_custom_parm_get( fzrt_int windex, fz_lite_ptr lite, void *light_rep_data, fzrt_UUID_td	parm_uuid,
	fz_type_td *data, fzrt_boolean *found	)
{
	fzrt_error_td err = FZRT_NOERR;
	x_pixel_lrep_ptr lrep;

	lrep = (x_pixel_lrep_ptr)light_rep_data;

	if( _base_funcs.fzrt_UUID_is_equal( parm_uuid, X_PIXEL_LREP_PARM_INTENS ) )
	{
		err = _type_funcs.fz_type_set_double( &lrep->intensity_override, data );
		*found = true;
	}
	else
		*found = false;

	return err;
}

/************************************************************************
	x_pixel_lrep_custom_parm_get
		Provides an approximate light type for use in FormZ shaded full.
*************************************************************************/
static fzrt_error_td x_pixel_lrep_custom_approx( fzrt_UUID_td light_type_org, fzrt_UUID_td light_type_approx	)
{
	if( _base_funcs.fzrt_UUID_is_equal( light_type_org, X_PIXEL_LREP_XPIXEL_LIGHT ) )
		_base_funcs.fzrt_UUID_copy( FZ_LIGHT_REP_TYPE_POINT, light_type_approx );

	return FZRT_NOERR;
}

/************************************************************************
	x_pixel_lrep_custom_parm_get
		Intensity UI tab
*************************************************************************/
static fzrt_error_td x_pixel_lrep_intensity_iface( fz_fuim_tmpl_ptr fuim_tmpl, short	parent, fzrt_int windex, fz_lite_ptr light,
	void *light_rep_data, fzrt_UUID_td light_type	)
{
	short				g1;
	short				ci1;
	x_pixel_lrep_ptr	lrep;
	fz_tag_td			l_tag;
	fzrt_error_td		err=FZRT_NOERR;
	//dbase_units_td		*units;

	if( _base_funcs.fzrt_UUID_is_equal( light_type, FZ_LIGHT_REP_TYPE_AMBIENT ) == FALSE )
	{
		lrep = (x_pixel_lrep_ptr)light_rep_data;

		_lite_funcs.fz_lite_ptr_to_tag( windex, light, &l_tag );

		/* "Intensity" GRP */
		g1 = _fuim_funcs.fz_fuim_new_group( fuim_tmpl, parent, FZ_FUIM_NONE, FZ_FUIM_FLAG_HORZ, NULL );
		{
			/* "Intensity" STR */
			ci1 = _fuim_funcs.fz_fuim_new_text_static( fuim_tmpl, g1, FZ_FUIM_NONE, FZ_FUIM_FLAG_NONE, "Intensity", NULL, NULL );

			ci1 = _fuim_funcs.fz_fuim_new_slider( fuim_tmpl, g1, FZ_FUIM_NONE, FZ_FUIM_FLAG_NONE, NULL, NULL, NULL );
			_fuim_funcs.fz_fuim_item_range_double( fuim_tmpl, ci1, &lrep->intensity_override, 0.0, 1.0, FZ_FUIM_FORMAT_FLOAT_DEFAULT,
				FZ_FUIM_RANGE_MIN | FZ_FUIM_RANGE_MAX | FZ_FUIM_RANGE_MAX_INCL | FZ_FUIM_RANGE_MIN_INCL );
		}
	}

	return err;
}

/************************************************************************
	x_pixel_lrep_custom_parm_get
		This fills out the light rep function set (same as srep).
*************************************************************************/
fzrt_error_td x_pixel_fill_lrep_fset(const fzpl_fset_def_ptr fset_def, fzpl_fset_td* const fset)
{
	fzrt_error_td err = FZRT_NOERR;
	fz_light_rep_cbak_fset *lrep_fset;

	err = _fset_glue->fzpl_fset_def_check( fset_def,
		FZ_LIGHT_REP_CBAK_FSET_VERSION,
		FZPL_TYPE_STRING(fz_light_rep_cbak_fset),
		sizeof(fz_light_rep_cbak_fset),
		FZPL_VERSION_OP_NEWER );

	if( err == FZRT_NOERR )
	{
		lrep_fset = (fz_light_rep_cbak_fset*)fset;

		lrep_fset->fz_light_rep_cbak_name = x_pixel_lrep_name;
		lrep_fset->fz_light_rep_cbak_uuid = x_pixel_lrep_uuid;
		lrep_fset->fz_light_rep_cbak_info = x_pixel_lrep_info;

		lrep_fset->fz_light_rep_cbak_init = x_pixel_lrep_init;
		lrep_fset->fz_light_rep_cbak_finit = x_pixel_lrep_finit;

		lrep_fset->fz_light_rep_cbak_data_io = x_pixel_lrep_data_io;
		lrep_fset->fz_light_rep_cbak_data_init = x_pixel_lrep_data_init;
		lrep_fset->fz_light_rep_cbak_data_finit = x_pixel_lrep_data_finit;
		lrep_fset->fz_light_rep_cbak_data_copy = x_pixel_lrep_data_copy;
		lrep_fset->fz_light_rep_cbak_data_are_equal = x_pixel_lrep_data_are_equal;

		lrep_fset->fz_light_rep_cbak_type_support = x_pixel_lrep_type_support;
		lrep_fset->fz_light_rep_cbak_parm_support = x_pixel_lrep_parm_support;
		lrep_fset->fz_light_rep_cbak_custom_count = x_pixel_lrep_custom_count;
		lrep_fset->fz_light_rep_cbak_custom_info = x_pixel_lrep_custom_info;

		lrep_fset->fz_light_rep_cbak_custom_parm_count = x_pixel_lrep_custom_parm_count;
		lrep_fset->fz_light_rep_cbak_custom_parm_info = x_pixel_lrep_custom_parm_info;
		lrep_fset->fz_light_rep_cbak_custom_parm_get = x_pixel_lrep_custom_parm_get;

		lrep_fset->fz_light_rep_cbak_intensity_iface = x_pixel_lrep_intensity_iface;
		lrep_fset->fz_light_rep_cbak_custom_approximate = x_pixel_lrep_custom_approx;

	}

	return err;
}
