/*************************************************************************
	File:	wave_in.c

	FormZ data model translator sample.  Imports and exports Wavefront OBJ files.

	COPYRIGHT (C) 2001.  All Rights Reserved.
	auto*des*sys, Inc., Columbus, Ohio, USA.
*************************************************************************/


#include "wave_prvt.h"


fzrt_int _obj_prog_update_incr = 1;


/************************************************************************
					Face Material Table
						
	Creates and maintains a table of face materials.  The face material table
	keeps track of surface styles assigned to faces.  Each entry in the table
	corresponds to one imported face.  This table is reset for each imported
	object.

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

/************************************************************************
	Allocates and initializes a face material table.
*************************************************************************/
static fzrt_int wave_facemltab_begin(wave_facemtltab_td **facemltab)
{
	fzrt_int err = FZRT_NOERR;

	if ((facemltab != NULL) && (*facemltab == NULL))
	{
		if (_fzrt_mem_funcs.fzrt_new_ptr_clear((fzrt_size)sizeof(wave_facemtltab_td), (fzrt_ptr*)facemltab) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1001);
		}
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1002);
	}

	return err;
}

/************************************************************************
	Frees a face material table.
*************************************************************************/
static fzrt_int wave_facemltab_end(wave_facemtltab_td **facemltab)
{
	fzrt_int err = FZRT_NOERR;

	if (*facemltab != NULL)
	{
		if ((*facemltab)->tab != NULL)		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)(*facemltab)->tab);
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)*facemltab);
		*facemltab = NULL;
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1003);
	}

	return err;
}

/************************************************************************
	Adds an entry to a face material table.
*************************************************************************/
static fzrt_int wave_facemltab_add(wave_facemtltab_td *facemltab, fz_tag_td *color)
{
	fzrt_int err = FZRT_NOERR;

	/* Allocate memory for the face material array 256 at a time. */
	if ((facemltab->num % 256) == 0)
	{
		if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr *)&facemltab->tab, &facemltab->size, sizeof(fz_tag_td), (fzrt_int)256) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1004);
		}
	}
	if (err == FZRT_NOERR)
	{
		facemltab->tab[facemltab->num] = *color;
		++facemltab->num;
	}

	return err;
}

/************************************************************************
	Emptys a face material table without freeing the memory.
*************************************************************************/
static void wave_facemltab_reset(wave_facemtltab_td *facemltab)
{
	facemltab->num = 0;
}

/************************************************************************
					Segment Link Table
						
	Creates and maintains a table of edge links.
	
	TODO: NEED MORE INFO

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


/************************************************************************
	Allocates and initializes an segment link table.
*************************************************************************/
static fzrt_int wave_seglinktab_begin(wave_seglinktab_td **seglinktab)
{
	fzrt_int err = FZRT_NOERR;

	if ((seglinktab != NULL) && (*seglinktab == NULL))
	{
		if (_fzrt_mem_funcs.fzrt_new_ptr_clear((fzrt_size)sizeof(wave_seglinktab_td), (fzrt_ptr*)seglinktab) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1005);
		}
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1006);
	}

	return err;
}

/************************************************************************
	Frees an segment link table.
*************************************************************************/
static fzrt_int wave_seglinktab_end(wave_seglinktab_td **seglinktab)
{
	fzrt_int err = FZRT_NOERR;

	if (*seglinktab != NULL)
	{
		if ((*seglinktab)->tab != NULL)		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)(*seglinktab)->tab);
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)*seglinktab);
		*seglinktab = NULL;
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1007);
	}

	return err;
}

/************************************************************************
	Adds an entry to an segment link table.
*************************************************************************/
static fzrt_int wave_seglinktab_add(wave_seglinktab_td *seglinktab, fzrt_int seg)
{
	fzrt_int err = FZRT_NOERR;

	/* Allocate memory for the segment link array 1024 at a time. */
	if ((seglinktab->num % 1024) == 0)
	{
		if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr *)&seglinktab->tab, &seglinktab->size, sizeof(fzrt_int), (fzrt_int)1024) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1008);
		}
	}
	if (err == FZRT_NOERR)
	{
		seglinktab->tab[seglinktab->num] = seg;
		++seglinktab->num;
	}

	return err;
}

/************************************************************************
	Emptys an edge link table without freeing the memory.
*************************************************************************/
static void wave_seglinktab_reset(wave_seglinktab_td *seglinktab)
{
	seglinktab->num = 0;
}

/************************************************************************
					Face Marker Array
						
	Creates and maintains an array of face markers.  This marker array
	keeps track of which faces have texture uv coordinates.  Each entry
	in the array corresponds to one imported face.  If the face mark is
	TRUE, the face contains texture uv coordinates.

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


/************************************************************************
	Allocates and initializes a face marker array.
*************************************************************************/
static fzrt_int wave_facemarktab_begin(wave_facemarktab_td **facemarktab)
{
	fzrt_int err = FZRT_NOERR;

	if ((facemarktab != NULL) && (*facemarktab == NULL))
	{
		if (_fzrt_mem_funcs.fzrt_new_ptr_clear((fzrt_size)sizeof(wave_facemarktab_td), (fzrt_ptr*)facemarktab) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1009);
		}
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1010);
	}

	return err;
}

/************************************************************************
	Frees a face marker array.
*************************************************************************/
static fzrt_int wave_facemarktab_end(wave_facemarktab_td **facemarktab)
{
	fzrt_int err = FZRT_NOERR;

	if (*facemarktab != NULL)
	{
		if ((*facemarktab)->tab != NULL)		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)(*facemarktab)->tab);
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)*facemarktab);
		*facemarktab = NULL;
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1011);
	}

	return err;
}

/************************************************************************
	Adds an entry to a face marker array.
*************************************************************************/
static fzrt_int wave_facemarktab_add(wave_facemarktab_td *facemarktab, fzrt_int mark)
{
	fzrt_int err = FZRT_NOERR;

	/* Allocate memory for the face marker array 256 at a time. */
	if ((facemarktab->num % 256) == 0)
	{
		if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr *)&facemarktab->tab, &facemarktab->size, sizeof(char), (fzrt_int)256) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1012);
		}
	}
	if (err == FZRT_NOERR)
	{
		facemarktab->tab[facemarktab->num] = mark;
		++facemarktab->num;
	}

	return err;
}

/************************************************************************
	Emptys a face marker array without freeing the memory.
*************************************************************************/
static void wave_facemarktab_reset(wave_facemarktab_td *facemarktab)
{
	facemarktab->num = 0;
}

/************************************************************************
					Texture Coordinate Table
						
	Creates and maintains a look up table of texture coordinates.  Each
	entry in this table corresponds to a texture uv coordinate in the obj file.

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


/************************************************************************
	Allocates the texture coordinate table.
*************************************************************************/
static fzrt_int wave_uvtab_begin(wave_uvtab_td **uvtab)
{
	fzrt_int err = FZRT_NOERR;

	if ((uvtab != NULL) && (*uvtab == NULL))
	{
		if (_fzrt_mem_funcs.fzrt_new_ptr_clear((fzrt_size)sizeof(wave_uvtab_td), (fzrt_ptr*)uvtab) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1013);
		}
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1014);
	}

	return err;
}

/************************************************************************
	Frees the texture coordinate table.
*************************************************************************/
static fzrt_int wave_uvtab_end(wave_uvtab_td **uvtab)
{
	fzrt_int err = FZRT_NOERR;

	if (*uvtab != NULL)
	{
		if ((*uvtab)->tab != NULL)		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)(*uvtab)->tab);
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)*uvtab);
		*uvtab = NULL;
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1015);
	}

	return err;
}

/************************************************************************
	Add a texture coordinate to the texture coordinate table table.
*************************************************************************/
static fzrt_int wave_uvtab_add(wave_uvtab_td *uvtab, double u, double v, double w)
{
	fzrt_int err = FZRT_NOERR;

	/* Allocate memory for the texture coordinate array 256 at a time. */
	if ((uvtab->num % 256) == 0)
	{
		if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr *)&uvtab->tab, &uvtab->size, sizeof(fz_xy_td), (fzrt_int)256))
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1016);
		}
	}
	if (err == FZRT_NOERR)
	{
		uvtab->tab[uvtab->num].x = u;
		uvtab->tab[uvtab->num].y = v;
		/* ignore w */
		++uvtab->num;
	}

	return err;
}

/************************************************************************
	initialize wavefront texture map options to default values.
*************************************************************************/
static void wave_map_init(wave_map_td *map)
{
	map->name[0] = 0;
	map->flags = 0;
	map->o.x = map->o.y = map->o.z = 0.0; /* offset */
	map->s.x = map->s.y = map->s.z = 1.0; /* scale */
	map->t.x = map->t.y = map->t.z = 0.0; /* turbulance */
	map->factor = 1.0;
	map->base = 0.0;
	map->gain = 1.0;
	map->textres = 1.0;
}

/************************************************************************
	Initialize wavefrom material definition to default values.
*************************************************************************/
static void wave_mtl_init(wave_mtl_td *mtl)
{
	fzrt_int i;

	/* material name statement */
	mtl->name[0] = 0;

	/* color and illumination statements */
	mtl->Ka.x = mtl->Ka.y = mtl->Ka.z = 0.5;
	mtl->Kd.x = mtl->Kd.y = mtl->Kd.z = 0.5;
	mtl->Ks.x = mtl->Ks.y = mtl->Ks.z = 0.0;
	mtl->Tf.x = mtl->Tf.y = mtl->Tf.z = 0.0; /* opaque */
	mtl->illum = 2;
	mtl->d = 1.0;
	mtl->Ns = 1.0;
	mtl->sharpness = 60.0;
	mtl->Ni = 1.0;

	/* texturemap statements */
	wave_map_init(&mtl->map_Ka);
	wave_map_init(&mtl->map_Kd);
	wave_map_init(&mtl->map_Ks);
	wave_map_init(&mtl->map_Ns);
	wave_map_init(&mtl->map_d);
	wave_map_init(&mtl->decal);
	wave_map_init(&mtl->bump);
	mtl->bm = 1.0;
	mtl->map_aat = 0;

	/* reflection map statements */
	mtl->refl = 0;
	for (i = 0; i < 6; ++i)
	{
		wave_map_init(&mtl->refl_map[i]);
	}
}

/************************************************************************
	Convert a texture map file as specified by the import texture map options.
*************************************************************************/
static fzrt_error_td wave_convert_tmap(wave_in_td *wave, char *src_name, fzrt_floc_ptr dst_floc, fz_ffmt_ref_td *dst_ffmt_id)
{
	fzrt_error_td 					err = FZRT_NOERR;
	fzrt_int							file_id;
	fz_ffmt_tmap_file_ptr			file_item;
	fz_ffmt_tmap_filetab_ptr		filetab;

	/* Get the formZ implemented texture file table. */
	err = _ffmt_3d_funcs.fz_ffmt_data_model_tmap_filetab(wave->ffmt_id, &filetab);

	/* Add the texture map to the table.  formZ will perform the options specified
		texture file conversion at this time. */
	err = _ffmt_3d_funcs.fz_ffmt_data_model_tmap_read_filetab_add(wave->windex, wave->ffmt_id, filetab, src_name, &file_id);

	/* Get the newly added table entry. */
	if(_ffmt_3d_funcs.fz_ffmt_data_model_tmap_filetab_entry(filetab, file_id, &file_item))
	{
		/* Get the converted texture map file's file location and format reference id. */
		_ffmt_3d_funcs.fz_ffmt_data_model_tmap_filetab_dst_floc(file_item, dst_floc);
		_ffmt_3d_funcs.fz_ffmt_data_model_tmap_filetab_dst_ffmt_id(file_item, dst_ffmt_id);
	}

	return err;
}

/************************************************************************
	Gets the current surface style from the surfact style palette.
	If the palette is empty, or no surface style is current, a new
	default 50% gray color surface style is created.
*************************************************************************/
static fzrt_error_td wave_get_default_surf(wave_in_td *wave, fz_tag_td *ctag)
{
	fzrt_error_td		err = FZRT_NOERR;
	fz_tag_td			col_tag;
	fz_rmtl_ptr			surf;
	fz_rgb_float_td		col;
	
	/* Get the current surface style. */
	_rmtl_funcs.fz_rmtl_get_current(wave->windex, &surf);
	if(surf != NULL)
	{
		/* Get the surface style's tag. */
		_rmtl_funcs.fz_rmtl_ptr_to_tag(wave->windex, surf, &col_tag);
	}
	else
	{
		/* If there is no current surface style, the style palette is empty.
			So, create a new surface style. */
		err = _rmtl_funcs.fz_rmtl_create_new(wave->windex, &surf);
		if(err == FZRT_NOERR && surf != NULL)
		{
			/* Set the surface style's color to 50% gray. */
			col.red = col.green = col.blue = 0.5;
			err = _rmtl_funcs.fz_rmtl_set_generic_parm(wave->windex, surf,
										&col, NULL, NULL, 1.0,
										0.75, 0.0, 0.0, 0.0,
										0.0, 0.0, NULL, NULL, NULL, NULL, NULL, 0.0);

			if(err == FZRT_NOERR)
			{
				
				/* Generate the surface style's preview for the surface style palette. */
				_rmtl_funcs.fz_rmtl_gen_preview(wave->windex, surf);

				/* Get the surface style's tag. */
				err = _rmtl_funcs.fz_rmtl_ptr_to_tag(wave->windex, surf, &col_tag);
			}
		}
	}
	if(ctag != NULL)	*ctag = col_tag;
	
	return(err);
}

/************************************************************************
	Converts a Wavefront material definition to a formZ surface style.
*************************************************************************/
static fzrt_error_td wave_mtl_to_surf(wave_in_td *wave, char *library, wave_mtl_td *mtl, fz_tag_td *stag)
{
	fzrt_error_td		err = FZRT_NOERR;
	double				ambient = 0.75, diffuse = 1.0, specular = 0.0, spec_expo = 1.0,
						refl = 0.0, transp = 0.0, refr = 1.0;
	fzrt_boolean		color_valid = FALSE, trans_valid = FALSE, bump_valid = FALSE;
	char				color_name[256], trans_name[256], bump_name[256];
	double				bmp_amp = 0.0;
	fz_ffmt_ref_td		color_ffmt_id, trans_ffmt_id, bump_ffmt_id;
	fz_rmtl_ptr			surf;
	fz_rgb_float_td		diff_col, spec_col;
	fzrt_floc_ptr 		color_floc;
	fzrt_floc_ptr		trans_floc;
	fzrt_floc_ptr		bump_floc;
	fz_tag_td			ctag;

	/* Initialize the file locations for the color, transparency, and bump texture map files. */
	_fzrt_file_funcs.fzrt_file_floc_init(&color_floc);
	_fzrt_file_funcs.fzrt_file_floc_init(&trans_floc);
	_fzrt_file_funcs.fzrt_file_floc_init(&bump_floc);

	/* Ambient reflection parameter */
	ambient = 1.0;

	/* Diffuse reflection parameter */
	diffuse = 1.0;

	/* Specular reflection parameter */
	specular = 1.0;

	/* Specular reflection exponent */
	spec_expo = mtl->Ns;

	/* Reflectivity */
	refl = 0.0F;

	/* Transparency */
	transp = (mtl->Tf.x + mtl->Tf.y + mtl->Tf.z) / 3.0;

	/* Index of refraction */
	refr = mtl->Ni;

	/* Specular color */
	spec_col.red = mtl->Ks.x;
	spec_col.green = mtl->Ks.y;
	spec_col.blue = mtl->Ks.z;

	/* Diffuse color */
	diff_col.red = mtl->Kd.x;
	diff_col.green = mtl->Kd.y;
	diff_col.blue = mtl->Kd.z;

	if(wave->do_tmaps)
	{
		/* If there is a color texture map convert it and get its name. */
		if ((color_valid = (mtl->map_Kd.name[0]?TRUE:FALSE)) != 0)
		{
			err = wave_convert_tmap(wave, mtl->map_Kd.name, color_floc, &color_ffmt_id);
			_fzrt_file_funcs.fzrt_file_floc_get_name(color_floc, color_name, 256);
		}

		/* If there is a transparency texture map convert it and get its name. */
		if ((trans_valid = (mtl->decal.name[0]?TRUE:FALSE)) != 0)
		{
			err = wave_convert_tmap(wave, mtl->decal.name, trans_floc, &trans_ffmt_id);
			_fzrt_file_funcs.fzrt_file_floc_get_name(trans_floc, trans_name, 256);
		}

		/* If there is a bump map convert it and get its name. */
		if ((bump_valid = (mtl->bump.name[0]?TRUE:FALSE)) != 0)
		{
			err = wave_convert_tmap(wave, mtl->bump.name, bump_floc, &bump_ffmt_id);
			_fzrt_file_funcs.fzrt_file_floc_get_name(bump_floc, bump_name, 256);

			/* Bump amplitude */
			bmp_amp = (float)mtl->bm;
		}
		else bmp_amp = 0.0;
	}

	if (err == FZRT_NOERR)
	{
		/* Create a new surface style. */
		err = _rmtl_funcs.fz_rmtl_create_new(wave->windex, &surf);
		if(err == FZRT_NOERR && surf != NULL)
		{
			/* set the surface style parameters. formZ will create the appropriate shader type from these parameters. */
			err = _rmtl_funcs.fz_rmtl_set_generic_parm(
										wave->windex,
										surf,
										(!color_valid?&diff_col:NULL),
										(color_valid?color_name:NULL),
										(color_valid?color_floc:NULL),
										ambient,
										diffuse,
										specular,
										spec_expo,
										refl,
										transp,
										refr,
										&spec_col,
										(trans_valid?trans_name:NULL),
										(trans_valid?trans_floc:NULL),
										(bump_valid?bump_name:NULL),
										(bump_valid?bump_floc:NULL),
										bmp_amp
										);

			/* Generate the surface style's preview for the surface style palette. */
			_rmtl_funcs.fz_rmtl_gen_preview(wave->windex, surf);

			/* Get the surface style's tag. */
			_rmtl_funcs.fz_rmtl_ptr_to_tag(wave->windex, surf, &ctag);
			*stag = ctag;
		}
	}

	/* Uninitialize the file locations for the color, transparency, and bump texture map files. */
	_fzrt_file_funcs.fzrt_file_floc_finit(&bump_floc);
	_fzrt_file_funcs.fzrt_file_floc_finit(&trans_floc);
	_fzrt_file_funcs.fzrt_file_floc_finit(&color_floc);

	return err;
}



/************************************************************************
						Material Link Table
						
	Creates and maintains a look up from Wavefront material name
	to its corresponding formZ surface style tag.
*************************************************************************/


/************************************************************************
	Allocates the material link table.
*************************************************************************/
static fzrt_int wave_mtllinktab_begin(wave_mtllinktab_td **mtllinktab)
{
	fzrt_int err = FZRT_NOERR;

	if ((mtllinktab != NULL) && (*mtllinktab == NULL))
	{
		if (_fzrt_mem_funcs.fzrt_new_ptr_clear((fzrt_size)sizeof(wave_mtllinktab_td), (fzrt_ptr*)mtllinktab) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1017);
		}
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1018);
	}

	return err;
}

/************************************************************************
	Frees the material link table.
*************************************************************************/
static fzrt_int wave_mtllinktab_end(wave_mtllinktab_td **mtllinktab)
{
	fzrt_int err = FZRT_NOERR;

	if (*mtllinktab != NULL)
	{
		if ((*mtllinktab)->tab != NULL) _fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)(*mtllinktab)->tab);
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)*mtllinktab);
		*mtllinktab = NULL;
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_BAD_PARAM_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1019);
	}

	return err;
}

/************************************************************************
	Find a material in the material link table.
	Returns the formZ surf style tag or -1 if not found, for the search
	uses only the wavefront material name.
*************************************************************************/
static fz_tag_td *wave_mtllinktab_find(wave_mtllinktab_td *mtllinktab, char *library, char *name)
{
	fzrt_int 		i;
	fz_tag_td	*stag = NULL;

	for (i = 0; i < mtllinktab->num; ++i)
	{
		if (((library != NULL) && !strcmp(mtllinktab->tab[i].libname, library) && !strcmp(mtllinktab->tab[i].matname, name))
				|| !strcmp(mtllinktab->tab[i].matname, name))
		{
			stag = &mtllinktab->tab[i].stag;
			break;
		}
	}
	return stag;
}

/************************************************************************
	Add a material to the material link table.
*************************************************************************/
static fzrt_error_td wave_mtllinktab_add(wave_in_td *wave, char *library, wave_mtl_td *mtl)
{
	fzrt_error_td	err = FZRT_NOERR;
	fz_tag_td		stag;

	/* if not in material link table, add material */
	if (wave_mtllinktab_find(wave->mtllinktab, library, mtl->name) == NULL)
	{
		if ((wave->mtllinktab->num % 64) == 0)
		{
			if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr *)&wave->mtllinktab->tab, &wave->mtllinktab->size, sizeof(wave_mtllink_td), (fzrt_int)64) != FZRT_NOERR)
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1020);
			}
		}
		if (err == FZRT_NOERR)
		{
			if ((err = wave_mtl_to_surf(wave, library, mtl, &stag)) == FZRT_NOERR)
			{
				strcpy(wave->mtllinktab->tab[wave->mtllinktab->num].libname, library);
				strcpy(wave->mtllinktab->tab[wave->mtllinktab->num].matname, mtl->name);
				wave->mtllinktab->tab[wave->mtllinktab->num].active = 0;
				wave->mtllinktab->tab[wave->mtllinktab->num].stag = stag;
				++wave->mtllinktab->num;
			}
		}
	}
	/* else do nothing */

	return err;
}

/************************************************************************
	If a file read fails, checks to see if the failure was due to reaching
	the end of the file.
*************************************************************************/
static fzrt_error_td wave_check_eof(FILE *fp)
{
	fzrt_error_td		err = FZRT_NOERR;

	if ( ferror(fp) == 0 )
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZ_FFMT_UNEXPECTED_EOF_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_APP, FZ_FFMT_CONTEXT_ID, 1021);
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(ferror(fp), FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1022);
		errno = 0;
	}
	
	return(err);
}

/************************************************************************
	Has the end of a line been reached.
*************************************************************************/
static fzrt_boolean wave_eoln(FILE *fp)
{
	char			c;
	fzrt_boolean	found = FALSE;

	if ((c = fgetc(fp)) == '\r')
	{
		found = TRUE;
	}
	else if (c == '\n')
	{
		found = TRUE;
	}
	ungetc(c, fp);

	return found;
}

/************************************************************************
	has the end of the file been reached.
*************************************************************************/
static fzrt_boolean wave_eof(FILE *fp)
{
	char			c;
	fzrt_boolean	found = FALSE;

	if ((c = fgetc(fp)) == EOF) found = TRUE;
	ungetc(c, fp);

	return found;
}

/************************************************************************
	Skips a line.
	Each call to "wave_skip_line" skips a run of any characters
	including line continuation patterns ("\\\n") up to the first
	"newline" ('\n') INCLUSIVE.
*************************************************************************/
static fzrt_error_td wave_skip_line(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_boolean	get_continuation_line = FALSE;
	char			c = EOF;
										/*----skip a comment----*/
	do {
		get_continuation_line = FALSE;
		while ((c = fgetc(fp)) != EOF)
		{
			if (c == '\\')
			{
				get_continuation_line = TRUE;
			}
			else if (c == '\r')
			{
				if ((c = fgetc(fp)) != '\n') ungetc(c, fp);
				break;
			}
			else if (c == '\n')
				break;
			else
				get_continuation_line = FALSE;
		}
	} while ((c != EOF) && get_continuation_line);

	return err;
}

/************************************************************************
	Skips a comment line
	Each call to "wave_skip_comment" skips everything up to:
	<End of line> or <End of file>
*************************************************************************/
static fzrt_error_td wave_skip_comment(FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_boolean	found = FALSE;
	char			c;

	do {
		switch (c = fgetc(fp))
		{
			/* EOF, EOLN */
			case EOF:
			case '\r':
			case '\n':
				found = TRUE;
				ungetc(c, fp);
				break;

			/* other */
			default:
				break;
		}
	} while (!found);

	return err;
}

/************************************************************************
	Skips to the next token
	Each call to "wave_skipto_token" skips:
	{[ \t] or \<End of line>}
*************************************************************************/
static fzrt_error_td wave_skipto_token(FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_boolean	found = FALSE;
	char			c1, c2, c3;

	do {
		switch (c1 = fgetc(fp))
		{
			/* space */
			case ' ':
			case '\t':
				break;

			/* line continuation pattern */
			case '\\':
				switch (c2 = fgetc(fp))
				{
					case '\r':
						if ((c3 = fgetc(fp)) != '\n') ungetc(c3, fp);
						break;
					case '\n':
						break;
					default:
						found = TRUE;
						ungetc(c2, fp);
						ungetc(c1, fp);
						break;
				}
				break;

			/* other */
			default:
				found = TRUE;
				ungetc(c1, fp);
				break;
		}
	} while (!found);

	return err;
}

/************************************************************************
	Skips to the next keyword.
	Each call to "wave_skipto_keyword" skips:
	{([ \t] or \<end of line>) or [#!].<end of line> or <end of line>}
*************************************************************************/
static fzrt_error_td wave_skipto_keyword(FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_boolean	found = FALSE;
	char			c1, c2;

	do {
		switch (c1 = fgetc(fp))
		{
			/* space, line continuation paterns */
			case ' ':
			case '\t':
				err = wave_skipto_token(fp);
				break;

			/* comments */
			case '#':
			case '!':
				err = wave_skip_comment(fp);
				break;

			/* Eld of line */
			case '\r':
				if ((c2 = fgetc(fp)) != '\n') ungetc(c2, fp);
				break;
			case '\n':
				break;

			/* other */
			default:
				found = TRUE;
				ungetc(c1, fp);
				break;
		}
	} while ((err == FZRT_NOERR) && !found);

	return err;
}

/************************************************************************
	Reads a wavefront keyword
*************************************************************************/
static fzrt_error_td wave_read_keyword(
			FILE			*fp,
			char			*keyword,
			fzrt_boolean	read_to_eol)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_int			pos = 0L;
	fzrt_int			i;
	char			c;

	if ((err = wave_skipto_token(fp)) == FZRT_NOERR)
	{
		if ((pos = ftell(fp)) >= 0)
		{
			for (i = 0; i < 256; )
			{
				c = fgetc(fp);
				if (isalnum(c) || (c == '_') || (read_to_eol && c != '\n' && c != '\r'))
				{
					if (i == 255)
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_STRING_TOO_LONG, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1023);
						break;
					}
					else keyword[i++] = c;
				}
				else
				{
					if (i == 0)
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNEXPECTED_CHARACTER, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1024);
					}
					else ungetc(c, fp);
					break;
				}
			}
			if (err == FZRT_NOERR)
			{
				keyword[i] = 0;
			}
			else fseek(fp, pos, SEEK_SET);
		}
		else
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1025);
		}
	}

	return err;
}

/************************************************************************
	Skips a run of "space" (' ') or
	"tab" ('\t') characters including line continuation patterns ("\\\n")
	up to the last (non inclusive) character of the line (ie '\n').
*************************************************************************/
static fzrt_error_td wave_skip_blank(wave_in_td *wave, FILE *fp)
/************************************************************************/
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_boolean	get_continuation_line = FALSE;
	char			c = EOF;

	do {
		get_continuation_line = FALSE;
		while (((c = fgetc(fp)) == ' ') || (c == '\t')) ;
		if (c == EOF)
			break;
		else if (c == '\\')
		{
			if ((c = fgetc(fp)) == '\r')
			{
				get_continuation_line = TRUE;
				if ((c = fgetc(fp)) != '\n') ungetc(c, fp);
			}
			else if (c == '\n')
			{
				get_continuation_line = TRUE;
			}
			else
			{
				ungetc(c, fp);
				ungetc('\\', fp);
			}
		}
		else ungetc(c, fp);
	} while ((c != EOF) && get_continuation_line);

	return err;
}

/************************************************************************
	Skips white space then reads everything up to the end of line.
*************************************************************************/
static fzrt_error_td wave_read_line(
		FILE	*fp,
		char	*line)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_int			pos = 0L;
	fzrt_int			i;
	char			c;

	if ((err = wave_skipto_token(fp)) == FZRT_NOERR)
	{
		if ((pos = ftell(fp)) >= 0)
		{
			for (i = 0; i < 256; )
			{
				if (!wave_eof(fp) && !wave_eoln(fp))
				{
					c = fgetc(fp);
					if (i == 255)
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_STRING_TOO_LONG, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1026);
						break;
					}
					else line[i++] = c;
				}
				else break;
			}
			if (err == FZRT_NOERR)
			{
				line[i] = 0;
			}
			else fseek(fp, pos, SEEK_SET);
		}
		else
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1027);
		}
	}

	return err;
}

/************************************************************************
	Reads an option tag (prefixed with a '-').
*************************************************************************/
static fzrt_error_td wave_read_option(
	FILE	*fp,
	char	*keyword)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_int			pos = 0L;
	fzrt_int			i;
	char			c;

	if ((err = wave_skipto_token(fp)) == FZRT_NOERR)
	{
		if ((pos = ftell(fp)) >= 0)
		{
			if ((c = fgetc(fp)) == '-')
			{
				keyword[0] = c;
				for (i = 1; i < 256; )
				{
					c = fgetc(fp);
					if (isalnum(c) || (c == '_'))
					{
						if (i == 255)
						{
							err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_STRING_TOO_LONG, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1028);
							break;
						}
						else keyword[i++] = c;
					}
					else
					{
						if (i == 0)
						{
							err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNEXPECTED_CHARACTER, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1029);
						}
						else ungetc(c, fp);
						break;
					}
				}
				if (err == FZRT_NOERR) keyword[i] = 0;
			}
			else
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNEXPECTED_CHARACTER, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, _wave_plugin_runtime_id, 1030);
			}

			if (err != FZRT_NOERR) fseek(fp, pos, SEEK_SET);
		}
		else
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1031);
		}
	}

	return err;
}

/************************************************************************
	Reads a floating point number.
*************************************************************************/
static fzrt_error_td wave_read_real(FILE *fp, double *val)
{
	fzrt_error_td	 err = FZRT_NOERR;

	if ((err = wave_skipto_token(fp)) == FZRT_NOERR)
	{
		if (fscanf(fp, "%lg", val) < 1)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1032);
		}
	}

	return err;
}

/************************************************************************
	Reads a vector of the form u [v w]
	if v, w are not read then they are set to u
*************************************************************************/
static fzrt_error_td wave_read_uvw(FILE *fp, fz_xyz_td *uvw)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_int			pos = 0L;

	if ((err = wave_read_real(fp, &uvw->x)) == FZRT_NOERR)
	{
		/* remember the file pos, in case that we do not read the optional arguments */
		if ((pos = ftell(fp)) >= 0)
		{
			if ((err = wave_read_real(fp, &uvw->y)) == FZRT_NOERR)
			{
				err = wave_read_real(fp, &uvw->z);
			}
			else
			{
				if (!fseek(fp, pos, SEEK_SET))
				{
					err = FZRT_NOERR;
					uvw->y = uvw->z = uvw->x;
				}
				else
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1033);
				}
			}
		}
		else
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1034);
		}
	}

	return err;
}

/************************************************************************
	Reads a filename from the source file that has at most 255 chars,
	including a period and a three letter extension.
*************************************************************************/
static fzrt_error_td wave_read_filename(
	FILE	*fp,	
	char	*filename,	
	char	*name,	
	char	*extension)	
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_int			len = 0L, pos;
	char			token[256], *cp;
	char			c = EOF;
	fzrt_int			i, j;


	if ((err = wave_skipto_token(fp)) == FZRT_NOERR)
	{
		if ((pos = ftell(fp)) >= 0)
		{
			/* read the name */
			for (i = 0; i < 256; )
			{
				c = fgetc(fp);
				if (isalnum(c) || (c == '_') || (c == '/') || (c == '\\') || (c == ':')  || (c == ' ') || (c =='-') || (c =='.'))
				{
					if (i == 255)
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_STRING_TOO_LONG, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1035);
						break;
					}
					else token[i++] = c;
				}
				else
				{
					if (i == 0)
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNEXPECTED_CHARACTER, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1036);
					}
					else ungetc(c, fp);
					break;
				}
			}
			if (err == FZRT_NOERR)
			{
				token[i] = 0;
				if (filename != NULL)
				{
					cp = strrchr(token, '/');
					if(cp == NULL)	cp = strrchr(token, '\\');
					if(cp == NULL)	cp = strrchr(token, ':');
					if(cp == NULL)	cp = token;
					else			cp++;
					strcpy(filename, cp);
				}
				if (name != NULL)
				{
					strcpy(name, filename);
					for (j = 0; j < strlen(name); ++j)
					{
						name[j] = tolower(name[j]);
					}

					cp = strrchr(name, '.');
					if(cp != NULL)
					{
						*cp = '\0';
						strcpy(extension, ++cp);
					}
				}
				else if(extension)
				{
					cp = strrchr(filename, '.');
					if(cp != NULL)
					{
						strcpy(extension, ++cp);
					}
				}
				len = strlen(token);
			}

			if (err != FZRT_NOERR) fseek(fp, pos, SEEK_SET);
		}
		else
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1040);
		}
	}

	return err;
}

/************************************************************************
	Get wavefront bump map option type.
*************************************************************************/
static fzrt_int wave_isBumpOption(char *keyword)
{
	fzrt_int is = WAVE_BUMP_OPT_NONE;

	if		(!strcmp("-bm",			keyword)) is = WAVE_BUMP_OPT_BM;

	return is;
}

/************************************************************************
	Get wavefront channel option type.
*************************************************************************/
static fzrt_int wave_isChannelOption(char *keyword)
{
	fzrt_int is = WAVE_CHAN_OPT_NONE;

	if		(!strcmp("-imfchan",	keyword)) is = WAVE_CHAN_OPT_IMFCHAN;

	return is;
}

/************************************************************************
	Get wavefront texture map option type.
*************************************************************************/
static fzrt_int wave_isMapOption(char *keyword)
{
	fzrt_int is = WAVE_TXTR_OPT_NONE;

	if		(!strcmp("-blendu",		keyword)) is = WAVE_TXTR_OPT_BLENDU;
	else if (!strcmp("-blendv",		keyword)) is = WAVE_TXTR_OPT_BLENDV;
	else if (!strcmp("-clamp",		keyword)) is = WAVE_TXTR_OPT_CLAMP;
	else if (!strcmp("-mm",			keyword)) is = WAVE_TXTR_OPT_MM;
	else if (!strcmp("-o",			keyword)) is = WAVE_TXTR_OPT_OFFSET;
	else if (!strcmp("-s",			keyword)) is = WAVE_TXTR_OPT_SCALE;
	else if (!strcmp("-t",			keyword)) is = WAVE_TXTR_OPT_TURB;
	else if (!strcmp("-textres",	keyword)) is = WAVE_TXTR_OPT_RES;

	return is;
}

/************************************************************************
	Get color correction type.
*************************************************************************/
static fzrt_int wave_isColorCorectionOption(char *keyword)
{
	fzrt_int is = WAVE_CC_OPT_NONE;

	if (!strcmp("-cc",				keyword)) is = WAVE_CC_OPT_CC;

	return is;
}

/************************************************************************
	Is this a material name keyword.
*************************************************************************/
static fzrt_int wave_isMaterialNameStatement(char *keyword)
{
	fzrt_int is = 0;

	if (!strcmp("newmtl",			keyword)) is = 1;

	return is;
}

/************************************************************************
	Get wavefront color and illumination type.
*************************************************************************/
static fzrt_int wave_isMaterialColorAndIlluminationStatement(char *keyword)
{
	fzrt_int is = WAVE_CI_NONE;

	if		(!strcmp("Ka",			keyword)) is = WAVE_CI_AMB;
	else if (!strcmp("Kd",			keyword)) is = WAVE_CI_DIFF;
	else if (!strcmp("Ks",			keyword)) is = WAVE_CI_SPEC;
	else if (!strcmp("Tf",			keyword)) is = WAVE_CI_TRANS;
	else if (!strcmp("illum",		keyword)) is = WAVE_CI_ILUM;
	else if (!strcmp("d",			keyword)) is = WAVE_CI_DSOLV;
	else if (!strcmp("Ns",			keyword)) is = WAVE_CI_SPEXP;
	else if (!strcmp("sharpness",	keyword)) is = WAVE_CI_SHRP;
	else if (!strcmp("Ni",			keyword)) is = WAVE_CI_OPDNS;

	return is;
}

/************************************************************************
	Get wavefront texture map type.
*************************************************************************/
static fzrt_int wave_isTextureMapStatement(char *keyword)
{
	fzrt_int is = WAVE_TXTR_TYPE_NONE;

	if		(!strcmp("map_Ka",	keyword)) is = WAVE_TXTR_TYPE_AMB;
	else if (!strcmp("map_Kd",	keyword)) is = WAVE_TXTR_TYPE_DIFF;
	else if (!strcmp("map_Ks",	keyword)) is = WAVE_TXTR_TYPE_SPEC;
	else if (!strcmp("map_Ns",	keyword)) is = WAVE_TXTR_TYPE_SPEXP;
	else if (!strcmp("map_d",	keyword)) is = WAVE_TXTR_TYPE_DSOLV;
	else if (!strcmp("map_aat",	keyword)) is = WAVE_TXTR_TYPE_AA;
	else if (!strcmp("decal",	keyword)) is = WAVE_TXTR_TYPE_DCAL;
	else if (!strcmp("bump",	keyword)) is = WAVE_TXTR_TYPE_BUMP;

	return is;
}

/************************************************************************
	Is this a reflection map keyword.
*************************************************************************/
static fzrt_int wave_isReflectionMapStatement(char *keyword)
{
	fzrt_int is = 0;

	if		(!strcmp("refl",	keyword)) is = 1;
	return is;
}

/************************************************************************
	Read bump map options.
*************************************************************************/
static fzrt_int wave_read_BumpOption(FILE *fp, fzrt_int type, double *bumpfactor)
{
	fzrt_int err = FZRT_NOERR;

	switch (type) {
		case WAVE_BUMP_OPT_BM: /* -bm <BumpFactor> */
			err = wave_read_real(fp, /* & */ bumpfactor);
		break;
		default:
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNKNOWNOPTIONTYPE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1041);
		break;
	}

	return err;
}

/************************************************************************
	Read texture map channel options.
*************************************************************************/
static fzrt_int wave_read_ChannelOption(FILE *fp, fzrt_int type)
{
	fzrt_int err = FZRT_NOERR;
	char keyword[256];

	switch (type) {
		case WAVE_CHAN_OPT_IMFCHAN: /* -imfchan (r|g|b|m|l|z) */
			if ((err = wave_read_keyword(fp, keyword, FALSE)) == FZRT_NOERR)
			{
				if (strcmp("r", keyword) &&
					strcmp("g", keyword) &&
					strcmp("b", keyword) &&
					strcmp("m", keyword) &&
					strcmp("l", keyword) &&
					strcmp("z", keyword))
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_RGBMLZEXPECTED_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1042);
				}
			}
		break;
		default:
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNKNOWNOPTIONTYPE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1043);
		break;
	}

	return err;
}

/************************************************************************
	Read various texture map options.
*************************************************************************/
static fzrt_int wave_read_MapOption(FILE *fp, fzrt_int type)
{
	fzrt_int err = FZRT_NOERR, pos = 0L;
	char keyword[256];
	double base, gain, resolution;
	fz_xyz_td uvw;

	switch (type)
	{
		case WAVE_TXTR_OPT_BLENDU:		/* -blendu (on|off) - horizontal (u) texture blending */
		case WAVE_TXTR_OPT_BLENDV:		/* -blendv (on|off) - vertical (v) texture blending */
		case WAVE_TXTR_OPT_CLAMP:		/* -clamp (on|off) - uv range clamping */
			if ((err = wave_read_keyword(fp, keyword, FALSE)) == FZRT_NOERR)
			{
				if (!strcmp("on", keyword) && !strcmp("off", keyword))
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_ONOFFEXPECTED_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1044);
				}
			}
		break;
		case WAVE_TXTR_OPT_MM:			/* -mm - Color base and gain */
			if ((err = wave_read_real(fp, &base)) == FZRT_NOERR)
			{
				err = wave_read_real(fp, &gain);
			}
		break;
		case WAVE_TXTR_OPT_OFFSET:		/* -o - texture position offset */
		case WAVE_TXTR_OPT_SCALE:		/* -s - texture scale */
		case WAVE_TXTR_OPT_TURB:		/* -t - Add turbulence to the texture */
			err = wave_read_uvw(fp, &uvw);
		break;
		case WAVE_TXTR_OPT_RES:			/* - texture resolution */
			err = wave_read_real(fp, &resolution);
		break;
		default:
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNKNOWNOPTIONTYPE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1045);
		break;
	}

	return err;
}

/************************************************************************
	Read a texture map's color correction
*************************************************************************/
static fzrt_int wave_read_ColorCorectionOption(FILE *fp, fzrt_int type)
{
	fzrt_int err = FZRT_NOERR;
	char keyword[256];

	switch (type)
	{
		case WAVE_CC_OPT_CC: /*Color correction on or off */
			if ((err = wave_read_keyword(fp, keyword, FALSE)) == FZRT_NOERR)
			{
				if (!strcmp("on", keyword) && !strcmp("off", keyword))
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_ONOFFEXPECTED_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1046);
				}
			}
		break;
		default:
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNKNOWNOPTIONTYPE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1047);
		break;
	}

	return err;
}

/************************************************************************
	Reads a material's color.
*************************************************************************/
static fzrt_int wave_read_Color(FILE *fp, fz_xyz_td *rgb)
{
	fzrt_int err = FZRT_NOERR, pos = 0L;
	char keyword[256], extension[256];
	double spectral;

	/* Remember the file pos, in case that we do not read RGB */
	if ((pos = ftell(fp)) >= 0)
	{
		/* Read RGB */
		if ((err = wave_read_uvw(fp, rgb)) != FZRT_NOERR)
		{
			/* Read of RGB failed, try something else. */
			if (!fseek(fp, pos, SEEK_SET))
			{
				err = FZRT_NOERR;
				if ((err = wave_read_keyword(fp, keyword, FALSE)) == FZRT_NOERR)
				{
					/* Reference to a Spectral Curve ".rfl" file */
					if (strcmp("spectral", keyword) == 0)
					{
						if ((err = wave_read_filename(fp, keyword, NULL, extension)) == FZRT_NOERR)
						{
							if (strcmp("rfl", extension) == 0)
							{
								rgb->x = rgb->y = rgb->z = 0;
								if (!wave_eof(fp) && !wave_eoln(fp))
									err = wave_read_real(fp, &spectral);
							}

							/* Unrecognized file extension */
							else
							{
								err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_RFLEXTEXPECTED_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1048);
							}
						}
						/* else "wave_read_filename" failed */
					}

					/* CIEXYZ color */
					else if (strcmp("xyz", keyword) == 0)
					{
						err = wave_read_uvw(fp, rgb);
					}

					/* Unrecognized keyword */
					else
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNEXPECTEDKEYWORD_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1049);
					}
				}
				/* else "wave_read_keyword" failed */
			}
			else
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1050);
			}
		}
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1051);
	}

	return err;
}

/************************************************************************
	Reads a material's name.
*************************************************************************/
static fzrt_int wave_read_MaterialNameStatement(FILE *fp, fzrt_int type, wave_mtl_td *mtl)
{
	fzrt_int err = FZRT_NOERR;
	char keyword[256];

	switch (type) {
		case 1: /* newmtl <MaterialName> EOL */
			if ((err = wave_read_keyword(fp, keyword, TRUE)) == FZRT_NOERR)
			{
				strcpy(mtl->name, keyword);
			}
		break;
		default:
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNKNOWNNAMETYPE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1052);
		break;
	}

	return err;
}

/************************************************************************
	Imports a color and illumination material.
*************************************************************************/
static fzrt_int wave_read_MaterialColorAndIlluminationStatement(FILE *fp, fzrt_int type, wave_mtl_td *mtl)
{
	fzrt_int err = FZRT_NOERR;

	switch (type)
	{
		case WAVE_CI_AMB:		/* Ambient */
			err = wave_read_Color(fp, &mtl->Ka);
			break;
		case WAVE_CI_DIFF: 		/* Diffuse */
			err = wave_read_Color(fp, &mtl->Kd);
			break;
		case WAVE_CI_SPEC: 		/* Specular */
			err = wave_read_Color(fp, &mtl->Ks);
			break;
		case WAVE_CI_TRANS: 	/* Transmission */
			err = wave_read_Color(fp, &mtl->Tf);
			break;
		case WAVE_CI_ILUM: 		/* Illumination */
			err = wave_skip_comment(fp);
			break;
		case WAVE_CI_DSOLV: 	/* Dissolve */
			err = wave_skip_comment(fp);
			break;
		case WAVE_CI_SPEXP: 	/* Specular Exponent */
			err = wave_read_real(fp, &mtl->Ns);
			break;
		case WAVE_CI_SHRP: 		/* Sharpness */
			err = wave_skip_comment(fp);
			break;
		case WAVE_CI_OPDNS: 	/* Optical Density (Index of Refraction) */
			err = wave_read_real(fp, &mtl->Ni);
			break;
		default:
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNKNOWNCOLILLUMTYPE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1053);
			break;
	}

	return err;
}

/************************************************************************
	Imports a texture map material.
*************************************************************************/
static fzrt_int wave_read_TextureMapStatement(FILE *fp, fzrt_int type, wave_mtl_td *mtl)
{
	fzrt_int err = FZRT_NOERR;
	char keyword[256], extension[256];

	switch (type)
	{
		case WAVE_TXTR_TYPE_AMB: 		/* Ambient */
			err = wave_skip_comment(fp);
		break;
		case WAVE_TXTR_TYPE_DIFF:		/* Diffuse */
			while (err == FZRT_NOERR) {
				if ((err = wave_read_option(fp, keyword)) == FZRT_NOERR) 
				{
					if ((type = wave_isMapOption(keyword)) != WAVE_TXTR_OPT_NONE)
					{
						err = wave_read_MapOption(fp, type);
					}
					else if ((type = wave_isColorCorectionOption(keyword)) != WAVE_CC_OPT_NONE)
					{
						err = wave_read_ColorCorectionOption(fp, type);
					}
					else
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNEXPECTEDOPTION_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1054);
					}
				}
			}
			if ((err = wave_read_filename(fp, keyword, NULL, extension)) == FZRT_NOERR)
			{
				strcpy(mtl->map_Kd.name, keyword);
			}
		break;

		case WAVE_TXTR_TYPE_SPEC: 		/* Specular */
			err = wave_skip_comment(fp);
		break;

		case WAVE_TXTR_TYPE_SPEXP: 		/* Specular Exponent */
			err = wave_skip_comment(fp);
		break;

		case WAVE_TXTR_TYPE_DSOLV: 		/* Dissolve */
			err = wave_skip_comment(fp);
		break;

		case WAVE_TXTR_TYPE_AA: 		/* Anti-Aliasing */
			err = wave_skip_comment(fp);
		break;

		case WAVE_TXTR_TYPE_DCAL: 		/* Decal */
			while (err == FZRT_NOERR) {
				if ((err = wave_read_option(fp, keyword)) == FZRT_NOERR)
				{
					if ((type = wave_isChannelOption(keyword)) != WAVE_CHAN_OPT_NONE)
					{
						err = wave_read_ChannelOption(fp, type);
					}
					else if ((type = wave_isMapOption(keyword)) != WAVE_TXTR_OPT_NONE)
					{
						err = wave_read_MapOption(fp, type);
					}
					else
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNEXPECTEDOPTION_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1055);
					}
				}
			}
			if ((err = wave_read_filename(fp, keyword, NULL, extension)) == FZRT_NOERR)
			{
				strcpy(mtl->decal.name, keyword);
			}
		break;

		case WAVE_TXTR_TYPE_BUMP: 		/* Bump Map */
			while (err == FZRT_NOERR) {
				if ((err = wave_read_option(fp, keyword)) == FZRT_NOERR)
				{
					if ((type = wave_isMapOption(keyword)) != WAVE_TXTR_OPT_NONE)
					{
						err = wave_read_MapOption(fp, type);
					}
					else if ((type = wave_isBumpOption(keyword)) != WAVE_BUMP_OPT_NONE)
					{
						err = wave_read_BumpOption(fp, type, &mtl->bm);
					}
					else if ((type = wave_isChannelOption(keyword)) != WAVE_CHAN_OPT_NONE)
					{
						err = wave_read_ChannelOption(fp, type);
					}
					else
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNEXPECTEDOPTION_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1056);
					}
				}
			}
			if ((err = wave_read_filename(fp, keyword, NULL, extension)) == FZRT_NOERR)
			{
				strcpy(mtl->bump.name, keyword);
			}
		break;

		default:
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNKNOWNTMAPTYPE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1057);
		break;
	}

	return err;
}

/************************************************************************
	Place holder for importing a reflection map material.
*************************************************************************/
static fzrt_int wave_read_ReflectionMapStatement(FILE *fp, fzrt_int type, wave_mtl_td *mtl)
{
	fzrt_int err = FZRT_NOERR;

	switch (type)
	{
		/*
			refl -type sphere {<MapOption> | <ColorCorectionOption>} <MapFile> EOL
			refl -type cube_top {<MapOption> | <ColorCorectionOption>} <MapFile> EOL
			refl -type cube_bottom {<MapOption> | <ColorCorectionOption>} <MapFile> EOL
			refl -type cube_front {<MapOption> | <ColorCorectionOption>} <MapFile> EOL
			refl -type cube_back {<MapOption> | <ColorCorectionOption>} <MapFile> EOL
			refl -type cube_left {<MapOption> | <ColorCorectionOption>} <MapFile> EOL
			refl -type cube_right {<MapOption> | <ColorCorectionOption>} <MapFile> EOL
		*/
		case 1:
			err = wave_skip_comment(fp);
		break;

		default:
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNKNOWNRMAPTYPE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1058);
		break;
	}

	return err;
}

/************************************************************************
	Read a material definition from a material library file.
	Failure to read a material  definition is not considered an error condition.
	The failure will simply result in objects which reference the material
	getting assigned a default material.
*************************************************************************/
static fzrt_int wave_read_MaterialDefinition(FILE *fp, wave_mtl_td *mtl, wave_in_td *wave)
{
	fzrt_int err = FZRT_NOERR, pos = 0L;
	fzrt_int type = 0;
	char keyword[256];

	/* Starter keyword/ */
	if ((err = wave_read_keyword(fp, keyword, FALSE)) == FZRT_NOERR)
	{
		/* MUST start with a Material Name Statement */
		if ((type = wave_isMaterialNameStatement(keyword)) != 0)
		{
			if ((err = wave_read_MaterialNameStatement(fp, type, mtl)) == FZRT_NOERR)
			{

				/* Followed by zero or more Color, Texture, and Reflection statements */
				while (err == FZRT_NOERR)
				{
					if ((err = wave_skipto_keyword(fp)) == FZRT_NOERR)
					{
						if (wave_eof(fp)) break;
						if ((pos = ftell(fp)) >= 0)
						{
							if ((err = wave_read_keyword(fp, keyword, FALSE)) == FZRT_NOERR)
							{

								/* Material Color And Illumination Statement */
								if ((type = wave_isMaterialColorAndIlluminationStatement(keyword)) != WAVE_CI_NONE)
								{
									if ((err = wave_read_MaterialColorAndIlluminationStatement(fp, type, mtl)) != FZRT_NOERR)
										break;
								}

								/* Texture Map Statement */
								else if ((type = wave_isTextureMapStatement(keyword)) != WAVE_TXTR_TYPE_NONE)
								{
									if ((err = wave_read_TextureMapStatement(fp, type, mtl)) != FZRT_NOERR)
										break;
								}

								/* Reflection Map Statement */
								else if ((type = wave_isReflectionMapStatement(keyword)) != 0)
								{
									if ((err = wave_read_ReflectionMapStatement(fp, type, mtl)) != FZRT_NOERR)
										break;
								}

								/* New material, return data to input stream and exit this level */
								else if(strncmp(keyword, "newmtl", 6) == 0)
								{
									if (fseek(fp, pos, SEEK_SET))
									{
										err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1059);
									}
									break; /* could be err == FZRT_NOERR */
								}
								/* Unrecognized keyword, skip this line */
								else
								{
									wave_skip_line(NULL, fp);
								}
							}
							/* else "wave_read_keyword" failed */
						}

						/* "ftell" failed */
						else
						{
							err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1060);
						}
					}
					/* else "wave_skipto_keyword" failed */
				}
			}
			/* else "wave_read_MaterialNameStatement" failed */
		}

		/* "newmtl" keyword expected */
		else
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_NEWMTLEXPECTED_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1061);
		}
	}
	/* else "wave_read_keyword" failed */

	return err;
}

/************************************************************************
	Imports a material library, ".mtl", file.  	Failure to read a material 
	definition is not considered an error condition.  The failure will simply
	result in objects which reference the material getting assigned a default
	material.  Imported materials are added to a link table.  formZ surface styles
	are created when materials are added to the link table.
*************************************************************************/
static fzrt_int wave_load_mtllib(wave_in_td *wave, char *filename)
{
	fzrt_int err = FZRT_NOERR;
	FILE *fp = NULL;
	wave_mtl_td *mtl = NULL;

	/* Allocate working storage for the materials. */
	if (_fzrt_mem_funcs.fzrt_new_ptr_clear((fzrt_size)sizeof(wave_mtl_td), (fzrt_ptr*)&mtl) == FZRT_NOERR)
	{
		/* Open the material library */
		if ((fp = fopen(filename, "rb")) != NULL)
		{
			if ((err = wave_skipto_keyword(fp)) == FZRT_NOERR)
			{

				/* Read one material definition */
				wave_mtl_init(mtl);
				if ((err = wave_read_MaterialDefinition(fp, mtl, wave)) == FZRT_NOERR)
				{
					if ((err = wave_mtllinktab_add(wave, filename, mtl)) == FZRT_NOERR)
					{

						/* Read the rest material definitions */
						while (err == FZRT_NOERR)
						{
							if ((err = wave_skipto_keyword(fp)) == FZRT_NOERR)
							{
								if (wave_eof(fp)) break;
								wave_mtl_init(mtl);
								if ((err = wave_read_MaterialDefinition(fp, mtl, wave)) == FZRT_NOERR)
								{
									err = wave_mtllinktab_add(wave, filename, mtl);
								}
								/* else "wave_read_MaterialDefinition" failed */
							}
							/* else "wave_skipto_keyword" failed */
						}
					}
					/* else "wave_mtllinktab_add" failed */
				}
				/* else "wave_read_MaterialDefinition" failed */
				/* or the loop failed */
			}
			/* else "wave_skipto_keyword" failed */

			/* Close the material library file */
			fclose(fp);
		}
		else
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1062);
		}

		/* Free working storage for the materials. */
		if (mtl != NULL)
		{
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)mtl);
			mtl = NULL;
		}
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1063);
	}

	return err;
}

/************************************************************************
	Process a material library "mtllib" keyword.  This function will
	also open and import the materials in the specified library.
	Failure to read a material library is not considered an error
	condition.  The failure will simply result in objects which reference the
	materials in the file getting assigned a default material.
*************************************************************************/
static fzrt_int wave_read_mtllib(wave_in_td *wave, FILE *fp)
{
	fzrt_int	err = FZRT_NOERR;
	char	filename[256], name[256], extension[256];

	/* Read the first material library filename. */
	if ((err = wave_read_filename(fp, filename, name, extension)) == FZRT_NOERR)
	{
		if (strcmp("mtl", extension) == 0)
		{

			/* Import the material library. */
			if ((err = wave_load_mtllib(wave, filename)) == FZRT_NOERR)
			{
				/* One by one read the rest of the material library filenames. */
				while (TRUE)
				{
					/* Test EOF/EOLN termination conditions. */
					if (wave_eof(fp)) break;
					if (wave_eoln(fp)) break;

					/* Read a filename. */
					if ((err = wave_read_filename(fp, filename, name, extension)) == FZRT_NOERR)
					{
						if (strcmp("mtl", extension) == 0)
						{
							/* Import the material library */
							if ((err = wave_load_mtllib(wave, filename)) != FZRT_NOERR) break;
						}

						/* Improper file extension for material file. */
						else
						{
							err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_MTLEXTEXPECTED_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1064);
						}
					}

					/* else "wave_read_filename" failed */
					else break;
				}
			}
			else	err = FZRT_NOERR;
			/* else "wave_load_mtllib" failed */
			/* or the "while(TRUE)" loop failed */
		}

		/* improper file extension for material file */
		else
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_MTLEXTEXPECTED_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1065);
		}
	}
	/* else "wave_read_filename" failed */

	return err;
}

/************************************************************************
	Processes a material "usemtl" keyword.  This sets the material
	to apply to all subsequent imported objects.
*************************************************************************/
static fzrt_int wave_read_usemtl(wave_in_td *wave, FILE *fp)
{
	fzrt_int err = FZRT_NOERR;
	fz_tag_td *stag;
	char name[256];

	/* read material name */
	if ((err = wave_read_keyword(fp, name, TRUE)) == FZRT_NOERR)
	{
		/* See if this material has been imported and get the formZ surface style tag associated with it. */

		if ((stag = wave_mtllinktab_find(wave->mtllinktab, NULL, name)) != NULL)
		{
			wave->stag = *stag;
		}
		else
		{
			_type_funcs.fz_tag_set_NULL(&wave->stag);
		}
	}

	return err;
}

/************************************************************************
	Process a texture vertex "vt" keyword.
	wave_read_vt: Adds a new texture vertex (u v [w]) to "wave_uvtab"
*************************************************************************/
static fzrt_error_td wave_read_vt(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	double			u = 0.0, v = 0.0, w = 1.0;

	if (wave->do_tmaps)
	{
		/* Read a uv pair */
		if ((err = wave_read_real(fp, &u)) == FZRT_NOERR)
		{
			if ((err = wave_read_real(fp, &v)) == FZRT_NOERR)
			{
				/* check for w */
				if ((err = wave_skipto_token(fp)) == FZRT_NOERR)
				{
					if (!wave_eof(fp) && !wave_eoln(fp))	err = wave_read_real(fp, &w);
					else									w = 1.0;
					if (err == FZRT_NOERR)					err = wave_uvtab_add(wave->uvtab, u, v, w);
				}
			}
		}
	}
	else err = wave_skip_line(wave, fp);

	return err;
}

/************************************************************************
	Process a vertex "v" keyword.
	Each call to "wave_get_v" adds a new
	vertex to the _vertices database and adds an invalid link
	(-1) to the _vertices_links database.
	Vertices may be 3D or 4D vectors and they are recognized automatically
	by the reader.
	When a new group is defined with the "g", "s", "mg", or "o" keyword,
	(according to the current import options), or EOF is encountered
	then the geometry of the object is copied from _vertices to _obj->cord
	array, and the current object is added to the formZ
	project, provided that at least one face has been defined from the
	time the object was created. Immediately following a new formZ object
	is created.
*************************************************************************/
static fzrt_error_td wave_get_v(wave_in_td *wave, FILE *fp)
/************************************************************************/
{
	fzrt_error_td		err = FZRT_NOERR;
	fzrt_int				i, dummy = 0;
	short				get_continuation_line = FALSE;
	char				c = EOF;
	double				x = 0.0, y = 0.0, z = 0.0, w = 1.0;

	/* Read vertex location. */
	err = wave_skip_blank(wave, fp);
	if(err == FZRT_NOERR)
	{
		if (fscanf(fp, "%lg", &x) < 1)
		{
			err = wave_check_eof(fp);
		}
	}
	if(err == FZRT_NOERR)
	{
		err = wave_skip_blank(wave, fp);
		if(err == FZRT_NOERR)
		{
			if (fscanf(fp, "%lg", &y) < 1)
			{
				err = wave_check_eof(fp);
			}
		}
	}
	if(err == FZRT_NOERR)
	{
		err = wave_skip_blank(wave, fp);
		if(err == FZRT_NOERR)
		{
			if (fscanf(fp, "%lg", &z) < 1)
			{
				err = wave_check_eof(fp);
			}
		}
	}
	if(err == FZRT_NOERR)
	{
		err = wave_skip_blank(wave, fp);
		
		/* Check for w. */
		if (((c = fgetc(fp)) != '\n') && (c != '\r'))
		{
			ungetc(c, fp);
			if (fscanf(fp, "%lg", &w) < 1)
			{
				err = wave_check_eof(fp);
			}

			err = wave_skip_blank(wave, fp);
			if(err == FZRT_NOERR)
			{
				if (((c = fgetc(fp)) != '\n') && (c != '\r'))
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_SYNTAX_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1066);
				}
			}
		}
		else w = 1.0;
	}

	if(err == FZRT_NOERR)
	{
		/* Store vertex. */
		++wave->n_vertices;
		if (((wave->n_vertices - 1)%256) == 0)
		{
			/* Allocate vertices and vertex links 256 at a time. */
			dummy = wave->s_vertices;
			if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&wave->vertices, &wave->s_vertices, sizeof(fz_xyzw_td), (fzrt_int)256) != FZRT_NOERR)
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1067);
			}
			if(err == FZRT_NOERR)
			{
				if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&wave->vertices_links, &dummy, sizeof(fzrt_int), (fzrt_int)256) != FZRT_NOERR)
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1068);
				}
				if(err == FZRT_NOERR)
				{
					for (i = 0; i < 256; ++i)
					{
						wave->vertices_links[wave->n_vertices - 1 + i] = -1;
					}
				}
			}
		}
		if(err == FZRT_NOERR)
		{
			wave->vertices[wave->n_vertices - 1].x = x;
			wave->vertices[wave->n_vertices - 1].y = y;
			wave->vertices[wave->n_vertices - 1].z = z;
			wave->vertices[wave->n_vertices - 1].w = w;
		}
	}

	return err;
}

/************************************************************************
	Process a curve/surface type "cstype" keyword.
	Each call to "wave_get_cstype" resets the current curve/surface type.
	"rat" argument is optional, if it is not present the curve/surface is
	assumed to be non rational.
	allowed curve/surface types are: "bmatrix", "bezier", "bspline",
	"cardinal", and "taylor".
*************************************************************************/
static fzrt_error_td wave_get_cstype(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR, len = 0L;
	fzrt_int			i;
	char			c = EOF, str[256];

	err = wave_skip_blank(wave, fp);
	if(err == FZRT_NOERR)
	{
		/* Read type parameter strings. */
		if (fscanf(fp, "%s", str) < 1)
		{
			err = wave_check_eof(fp);
		}	
		if(err == FZRT_NOERR)
		{
			for (i = 0, len = strlen(str); i < len; ++i)
			{
				str[i] = tolower(str[i]);
			}
			wave->rational = FALSE;
			/* Check for rational parameter. */
			if (strcmp(str, "rat") == 0)
			{
				wave->rational = TRUE;

				err = wave_skip_blank(wave, fp);
				if(err == FZRT_NOERR)
				{
					if (fscanf(fp, "%s", str) < 1)
					{
						err = wave_check_eof(fp);
					}
					if(err == FZRT_NOERR)
					{
						for (i = 0, len = strlen(str); i < len; ++i)
						{
							str[i] = tolower(str[i]);
						}
					}
				}
			}
			
			if(err == FZRT_NOERR)
			{
				/* Map the type string to an internal enumerated type. */
				if		(!strcmp(str, "bmatrix"))	wave->cstype = WAVE_CSTYPE_BMATRIX;
				else if (!strcmp(str, "bezier"))	wave->cstype = WAVE_CSTYPE_BEZIER;
				else if (!strcmp(str, "bspline"))	wave->cstype = WAVE_CSTYPE_BSPLINE;
				else if (!strcmp(str, "cardinal"))	wave->cstype = WAVE_CSTYPE_CARDINAL;
				else if (!strcmp(str, "taylor"))	wave->cstype = WAVE_CSTYPE_TAYLOR;
				else
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_SYNTAX_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1069);
				}
				if(err == FZRT_NOERR)
				{
					err = wave_skip_blank(wave, fp);
					if(err == FZRT_NOERR)
					{
						if (((c = fgetc(fp)) != '\n') && (c != '\r'))
						{
							err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_SYNTAX_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1070);
						}
					}
				}
			}
		}
	}

	return err;
}

/************************************************************************
	Process polynumial degree "deg" keyword.
	Each call to "wave_get_deg" resets the current curve/surface degree
	in u/v direction. If the degree in v direction is not specified then
	an invalid degree of -1 is assumed.
*************************************************************************/
static fzrt_error_td wave_get_deg(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	char			c = EOF;

	err = wave_skip_blank(wave, fp);
	if(err == FZRT_NOERR)
	{
		/* Read the u degree. */
		if (fscanf(fp, "%d", &wave->u_degree) < 1)
		{
			err = wave_check_eof(fp);
		}
		if(err == FZRT_NOERR)
		{
			err = wave_skip_blank(wave, fp);
			
			if(err == FZRT_NOERR)
			{
				/* Check for a degree in V direction. */
				wave->v_degree = -1;
				if (((c = fgetc(fp)) != '\n') && (c != '\r'))
				{
					ungetc(c, fp);
					
					/* Read the v degree. */
					if (fscanf(fp, "%d", &wave->v_degree) < 1)
					{
						err = wave_check_eof(fp);
					}
					if(err == FZRT_NOERR)
					{
						err = wave_skip_blank(wave, fp);
						if(err == FZRT_NOERR)
						{
							if (((c = fgetc(fp)) != '\n') && (c != '\r'))
							{
								err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_SYNTAX_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1071);
							}
						}
					}
				}
			}
		}
	}

	return err;
}

/************************************************************************
	Process a point "p" keyword.
	Each call to "wave_get_p" constructs a formZ point object for each referenced point.
*************************************************************************/
static fzrt_error_td wave_get_p(wave_in_td *wave, FILE *fp)
/************************************************************************/
{
	fzrt_error_td		err = FZRT_NOERR;
	char				c = EOF;
	fzrt_int				pindx, tmp;
	fz_xyz_td			cord;
	fz_objt_ptr			obj;
	short				create = TRUE;

	err = wave_skip_blank(wave, fp);
	if(err == FZRT_NOERR)
	{
		do {
			/* Read the vertex indices for the point. */
			if (fscanf(fp, "%d", &pindx) < 1)
			{
				err = wave_check_eof(fp);
			}
			if(err == FZRT_NOERR)
			{
				err = wave_skip_blank(wave, fp);
				if(err == FZRT_NOERR)
				{
					if ((c = fgetc(fp)) == '/')
					{
						c = fgetc(fp); ungetc(c, fp);
						if ((c == '.') || (c == '+') || (c == '-') || isdigit(c))
							if (fscanf(fp, "%d", &tmp) < 1)
							{
								err = wave_check_eof(fp);
							}
					}
					else
					{
						ungetc(c, fp);
					}
					
					if(err == FZRT_NOERR && wave->do_points)
					{
						/* Create a formZ point object. */
						if(pindx < 0)	pindx = wave->n_vertices + pindx;
						else			pindx--;
						cord.x = wave->vertices[pindx].x;
						cord.y = wave->vertices[pindx].y;
						cord.z = wave->vertices[pindx].z;
						_model_funcs.fz_objt_cnstr_point(wave->windex, &cord, &obj);
						if(err == FZRT_NOERR)
						{
							_ffmt_3d_funcs.fz_ffmt_data_model_objt_complete(wave->windex, obj);
						}
					}
				}
			}

		} while ((c != EOF) && (c != '\n') && (c != '\r') && err == FZRT_NOERR);
	}

	return err;
}

/************************************************************************
	Process a polyline "l" keyword.
	Each call to "wave_get_l" constructs a formZ polyline object.
*************************************************************************/
static fzrt_error_td wave_get_l(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	char			c = EOF;
	fzrt_int			i, seg, tmp, j;
	fzrt_boolean	is_in_segt;
	fzrt_int			n_cord = 0, s_cord = 0, n_segt = 0, s_segt = 0, *segt = NULL;
	fz_xyz_td		*cord = NULL;
	fzrt_boolean	create = TRUE;
	fz_objt_ptr		obj;

	err = wave_skip_blank(wave, fp);
	if(err == FZRT_NOERR)
	{
		i = 0;
		n_segt = 0;
		do {
			/* Read the vertex indices for the poly line. */
			if (fscanf(fp, "%d", &seg) < 1)
			{
				err = wave_check_eof(fp);
			}
			if(err == FZRT_NOERR)
			{
				if ((c = fgetc(fp)) == '/')
				{
					c = fgetc(fp); ungetc(c, fp);
					if ((c == '.') || (c == '+') || (c == '-') || isdigit(c))
					{
						if (fscanf(fp, "%d", &tmp) < 1)
						{
							err = wave_check_eof(fp);
						}
					}
				}
				else ungetc(c, fp);
				if ((c = fgetc(fp)) == '/')
				{
					c = fgetc(fp); ungetc(c, fp);
					if ((c == '.') || (c == '+') || (c == '-') || isdigit(c))
					{
						if (fscanf(fp, "%d", &tmp) < 1)
						{
							err = wave_check_eof(fp);
						}
					}
				}
				else ungetc(c, fp);
			}

			if(err == FZRT_NOERR)
			{
				if ((err = wave_skip_blank(wave, fp)) == FZRT_NOERR)
				{
					++n_segt;
					if ((i%256) == 0)
					{
						if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&segt, &s_segt, sizeof(fzrt_int), (fzrt_int)256) != FZRT_NOERR)
						{
							err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1072);
						}
					}
			
					if(err == FZRT_NOERR)
					{
						segt[n_segt - 1] = (seg > 0) ? (seg - 1) : (wave->n_vertices + seg);
						if ((segt[n_segt - 1] < 0) || (segt[n_segt - 1] > wave->n_vertices - 1))
						{
							err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_NO_SUCH_VERTEX_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1073);
						}
						if(err == FZRT_NOERR)
						{
							if (((c = fgetc(fp)) != EOF) && (c != '\n') && (c != '\r'))
							{
								ungetc(c, fp);
							}
							++i;
						}
					}
				}
			}
		} while ((c != EOF) && (c != '\n') && (c != '\r') && err == FZRT_NOERR);

		if(err == FZRT_NOERR)
		{
			/* If any geometry point is used by an already
				open object, don't create the object. */
			for (i = 0, create = TRUE; i < n_segt; ++i)
			{
				if (wave->vertices_links[segt[i]] != -1)
				{
					create = FALSE;
				}
			}

			if (create && (wave->obj == NULL)) 
			{
				/* Adjust entries to wave->vertices_links. */
				for (i = 0, n_cord = 0; i < n_segt; ++i)
				{
					tmp = segt[i];
					if (wave->vertices_links[tmp] == -1)
					{
						wave->vertices_links[tmp] = n_cord;
						++n_cord;
					}
				}
				
				/* Allocate a local array for polyline vertices.*/
					if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&cord, &s_cord, sizeof(fz_xyz_td), (fzrt_int)n_cord) != FZRT_NOERR)
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1074);
				}
				
				if(err == FZRT_NOERR)
				{
					/* Copy geometry to local vertex array. */
					for (i = 0; i < wave->n_vertices; ++i)
					{
						if (wave->vertices_links[i] != -1)
						{
							is_in_segt = FALSE;
							for (j = 0; j < n_segt; ++j)
							{
								if (segt[j] == i)
								{
									is_in_segt = TRUE;
									break;
								}
							}
							if (is_in_segt)
							{
								cord[wave->vertices_links[i]].x = wave->vertices[i].x;
								cord[wave->vertices_links[i]].y = wave->vertices[i].y;
								cord[wave->vertices_links[i]].z = wave->vertices[i].z;
								wave->vertices_links[i] = -1;
							}
						}
					}
				
					/* Create a formZ vline object. */
					err = _model_funcs.fz_objt_cnstr_vline(wave->windex, cord, n_cord, FALSE, &obj);
					if(err == FZRT_NOERR)
					{
						_ffmt_3d_funcs.fz_ffmt_data_model_objt_complete(wave->windex, obj);
					}
				}
			}
		}
	}

	if (segt != NULL)
	{
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)segt);
		segt = NULL;
	}
	if (cord != NULL)
	{
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)cord);
		cord = NULL;
	}

	return err;
}

/************************************************************************
	Processes curve "curv" keyword.
*************************************************************************/
static fzrt_error_td wave_get_curv(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	char			c = EOF;
	fzrt_int			ival = 0, tmp;
	double			val = 0.0;
	fzrt_boolean	done = FALSE;

	/* if a curve or surface is already being processed the obj file is considered corrupt. */
	if (wave->begin_curvsurf != WAVE_NONE)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_STRUCTURE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1075);
	}

	if(err == FZRT_NOERR)
	{
		wave->begin_curvsurf = WAVE_CURV;

		/* read the surface data. */
		err = wave_skip_blank(wave, fp);
		if(err == FZRT_NOERR)
		{
			if (fscanf(fp, "%lg", &val) < 1)
			{
				err = wave_check_eof(fp);
			}
		}

		if(err == FZRT_NOERR)
		{
			err = wave_skip_blank(wave, fp);
			if(err == FZRT_NOERR)
			{
				if (fscanf(fp, "%lg", &val) < 1)
				{
					err = wave_check_eof(fp);
				}
			}
		}
		if(err == FZRT_NOERR)
		{
			/* Free the old control geometry array. */
			if (wave->control_geom != NULL)
			{
				_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->control_geom);
				wave->control_geom = NULL;
				wave->n_control_geom = wave->s_control_geom = 0;
			}

			/* Read the control geometry. */
			do {
				err = wave_skip_blank(wave, fp);
				if(err == FZRT_NOERR)
				{
					if (((c = fgetc(fp)) == '\n') || (c == '\r') || (c == EOF))
					{
						done = TRUE;
					}
					if(!done)
					{
						ungetc(c, fp);

						if (fscanf(fp, "%d", &ival) < 1)
						{
							err = wave_check_eof(fp);
						}
						if(err == FZRT_NOERR)
						{
							if ((c = fgetc(fp)) == '/')
							{
								c = fgetc(fp); ungetc(c, fp);
								if ((c == '.') || (c == '+') || (c == '-') || isdigit(c))
								{
									if (fscanf(fp, "%d", &tmp) < 1)
									{
										err = wave_check_eof(fp);
									}
								}
							}
							else
							{
								ungetc(c, fp);
							}

							if(err == FZRT_NOERR)
							{
								/* Allocate control geometry 16 at a time. */
								if ((wave->n_control_geom % 16) == 0)
								{
									if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&wave->control_geom, &wave->s_control_geom, sizeof(fzrt_int), (fzrt_int)16) != FZRT_NOERR)
									{
										err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1076);
									}
								}
								if(err == FZRT_NOERR)
								{
									wave->control_geom[wave->n_control_geom] = (ival > 0)?(ival - 1):(wave->n_vertices + ival);
									if ((wave->control_geom[wave->n_control_geom] < 0) || (wave->control_geom[wave->n_control_geom] > wave->n_vertices - 1) != FZRT_NOERR)
									{
										err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_NO_SUCH_VERTEX_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1077);
									}
									if(err == FZRT_NOERR)	++wave->n_control_geom;
								}
							}
						}
					}
				}
			} while (!done && err == FZRT_NOERR);
		}
	}

	if (err != FZRT_NOERR) wave->begin_curvsurf = WAVE_NONE;

	return err;
}

/************************************************************************
	Place holder for processing a curve on surface "curv2" keyword.
*************************************************************************/
static fzrt_error_td wave_get_curv2(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;

	/* if a curve or surface is already being processed the obj file is considered corrupt. */
	if (wave->begin_curvsurf != WAVE_NONE)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_STRUCTURE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1078);
	}
	else
	{
		wave->begin_curvsurf = WAVE_CURV2;

		err = wave_skip_line(wave, fp);
	}


	if (err != FZRT_NOERR) wave->begin_curvsurf = WAVE_NONE;

	return err;
}

/************************************************************************
	Processes surface "surf" keyword.
*************************************************************************/
static fzrt_error_td wave_get_surf(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	char			c = EOF;
	fzrt_int			ival = 0, tmp;
	double			val = (double)0;
	fzrt_int			seg_uv;
	fzrt_boolean	first_seg = TRUE, face_has_uvs = FALSE, got_seg_uv;
	fzrt_boolean	done = FALSE;

	/* if a curve or surface is already being processed the obj file is considered corrupt. */
	if (wave->begin_curvsurf != WAVE_NONE)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_STRUCTURE_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1079);
	}

	if(err == FZRT_NOERR)
	{
		wave->begin_curvsurf = WAVE_SURF;

		/* read the surface data. */
		err = wave_skip_blank(wave, fp);
		if(err == FZRT_NOERR)
		{
			if (fscanf(fp, "%lg", &val) < 1) 
			{
				err = wave_check_eof(fp);
			}
		}
		if(err == FZRT_NOERR)
		{
			err = wave_skip_blank(wave, fp);
			if(err == FZRT_NOERR)
			{
				if (fscanf(fp, "%lg", &val) < 1)
				{
					err = wave_check_eof(fp);
				}
			}
		}
		if(err == FZRT_NOERR)
		{
			err = wave_skip_blank(wave, fp);
			if(err == FZRT_NOERR)
			{
				if (fscanf(fp, "%lg", &val) < 1)
				{
					err = wave_check_eof(fp);
				}
			}
		}
		if(err == FZRT_NOERR)
		{
			err = wave_skip_blank(wave, fp);
			if(err == FZRT_NOERR)
			{
				if (fscanf(fp, "%lg", &val) < 1)
				{
					err = wave_check_eof(fp);
				}
			}
		}

		if(err == FZRT_NOERR)
		{
			/* Free the old control geometry array. */
			if (wave->control_geom != NULL)
			{
				_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->control_geom);
				wave->control_geom = NULL;
				wave->n_control_geom = wave->s_control_geom = 0;
			}

			/* Read the control geometry. */
			do {
				err = wave_skip_blank(wave, fp);
				if(err == FZRT_NOERR)
				{
					if (((c = fgetc(fp)) == '\n') || (c == '\r') || (c == EOF))
					{
						done = TRUE;
					}
					if(!done)
					{
						ungetc(c, fp);

						if (fscanf(fp, "%d", &ival) < 1)
						{
							err = wave_check_eof(fp);
						}

						if(err == FZRT_NOERR)
						{
							got_seg_uv = FALSE;

							if ((c = fgetc(fp)) == '/')
							{
								if ((c = fgetc(fp)) == '/')
								{
									c = fgetc(fp); ungetc(c, fp);
									if ((c == '.') || (c == '+') || (c == '-') || isdigit(c))
									{
										if (fscanf(fp, "%d", &tmp) < 1)
										{
											err = wave_check_eof(fp);
										}
									}
								}
								else
								{
									ungetc(c, fp);

									got_seg_uv = TRUE;
									if (fscanf(fp, "%d", &seg_uv) < 1)
									{
										err = wave_check_eof(fp);
									}

									if(err == FZRT_NOERR)
									{
										if ((c = fgetc(fp)) == '/')
										{
											c = fgetc(fp); ungetc(c, fp);
											if ((c == '.') || (c == '+') || (c == '-') || isdigit(c))
											{
												if (fscanf(fp, "%d", &tmp) < 1)
												{
													err = wave_check_eof(fp);
												}
											}
										}
										else
										{
											ungetc(c, fp);
										}
									}
								}
							}
							else
							{
								ungetc(c, fp);
							}

							if (wave->do_tmaps)
							{
								if (first_seg)
								{
									first_seg = FALSE;
									face_has_uvs = got_seg_uv;
								}
								if (face_has_uvs && got_seg_uv)
								{
									seg_uv = (seg_uv > 0)?(seg_uv - 1):(wave->uvtab->num + seg_uv);
									err = wave_seglinktab_add(wave->seglinktab, seg_uv);
								}
								else
								{
									err = wave_seglinktab_add(wave->seglinktab, -1);
								}
							}

							if(err == FZRT_NOERR)
							{
								/* Allocate control geometry 16 at a time. */
								if ((wave->n_control_geom % 16) == 0)
								{
									if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&wave->control_geom, &wave->s_control_geom, sizeof(fzrt_int), (fzrt_int)16) != FZRT_NOERR)
									{
										err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1080);
									}
								}
								if(err == FZRT_NOERR)
								{
									wave->control_geom[wave->n_control_geom] = (ival > 0)?(ival - 1):(wave->n_vertices + ival);
									if ((wave->control_geom[wave->n_control_geom] < 0) || (wave->control_geom[wave->n_control_geom] > wave->n_vertices - 1))
									{
										err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_NO_SUCH_VERTEX_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1081);
									}
									if(err == FZRT_NOERR)	++wave->n_control_geom;
								}
							}
						}
					}
				}
			} while (!done && err == FZRT_NOERR);

			if (wave->do_tmaps)
			{
				err = wave_facemarktab_add(wave->facemarktab, face_has_uvs);
			}
		}
	}

	if (err != FZRT_NOERR) wave->begin_curvsurf = WAVE_NONE;

	return err;
}

/************************************************************************
	Processes curve/surface parameters. ("parm" keyword)
	Each succesful call to "wave_get_parm" clears the current knot vector
	in the specified (u/v) direction and fills it with new data.
*************************************************************************/
static fzrt_error_td wave_get_parm(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_int			len = 0L;
	fzrt_int			i;
	fzrt_boolean	u;
	char			str[256], c = EOF;
	double			val = 0.0;
	fzrt_boolean 	done = FALSE;

	/* Skip to next parameter */
	err = wave_skip_blank(wave, fp);
	if(err == FZRT_NOERR)
	{
		if (fscanf(fp, "%s", str) < 1)
		{
			err = wave_check_eof(fp);
		}
		
		if(err == FZRT_NOERR)
		{
			for (i = 0, len = strlen(str); i < len; ++i)
			{
				str[i] = tolower(str[i]);
			}
			
			/* u or v direction. */
			if (strcmp(str, "u") == 0)
			{
				u = TRUE;
			}
			else if (strcmp(str, "v") == 0)
			{
				u = FALSE;
			}
			else
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_SYNTAX_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1082);
			}
			
			if(err == FZRT_NOERR)
			{
				/* clear old knot vector. */
				if (u)
				{
					if (wave->u_knot_vec != NULL)
					{
						_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->u_knot_vec);
						wave->u_knot_vec = NULL;
						wave->n_u_knots = wave->s_u_knots = 0;
					}
				}
				else
				{
					if (wave->v_knot_vec != NULL)
					{
						_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->v_knot_vec);
						wave->v_knot_vec = NULL;
						wave->n_v_knots = wave->s_v_knots = 0;
					}
				}
				
				/* Read knot vector. */
				do {
					err = wave_skip_blank(wave, fp);
					if(err == FZRT_NOERR)
					{
						if (((c = fgetc(fp)) == '\n') || (c == '\r') || (c == EOF))
						{
							done = TRUE;
						}
						if(!done)
						{
							ungetc(c, fp);
							if (fscanf(fp, "%lg", &val) < 1)
							{
								err = wave_check_eof(fp);
							}
							else if (u)
							{
								/* Allocate knots 16 at a time. */
								if ((wave->n_u_knots % 16) == 0)
								{
									if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&wave->u_knot_vec, &wave->s_u_knots, sizeof(double), (fzrt_int)16) != FZRT_NOERR)
									{
										err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1083);
									}
								}
								if(err == FZRT_NOERR)
								{
									/* Assign the knot value. */
									wave->u_knot_vec[wave->n_u_knots] = val;
									++wave->n_u_knots;
								}
							}
							else
							{
								/* Allocate knots 16 at a time. */
								if ((wave->n_v_knots % 16) == 0)
								{
									if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&wave->v_knot_vec, &wave->s_v_knots, sizeof(double), (fzrt_int)16) != FZRT_NOERR)
									{
										err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1084);
									}
								}
								if(err == FZRT_NOERR)
								{
									/* Assign the knot value. */
									wave->v_knot_vec[wave->n_v_knots] = val;
									++wave->n_v_knots;
								}
							}
						}
					}
				} while (!done && err == FZRT_NOERR);
			}
		}
	}

	return err;
}

/************************************************************************
	Place holder function for Basis Matrix curve.
*************************************************************************/
static fzrt_error_td wave_construct_bmatrix_curv(wave_in_td *wave)
{
	fzrt_error_td	err = FZRT_NOERR;

	return err;
}

/************************************************************************
	Creates a formZ nurbs curve from imported Bezier data.
*************************************************************************/
static fzrt_error_td wave_construct_bezier_curv(wave_in_td *wave)
{
	fzrt_error_td	err = FZRT_NOERR;
	short			closedU;
	fzrt_int			i, nU;
	double			*weight = NULL,
					*knot = NULL;
	fz_xyz_td		*cord = NULL;
	fz_objt_ptr		obj = NULL;

	/* Check that the curve parameters are valid. */
	if (err == FZRT_NOERR && wave->control_geom == NULL)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1085);
	}
	if (err == FZRT_NOERR && wave->u_knot_vec == NULL)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1086);
	}
	if (err == FZRT_NOERR && wave->u_degree < 1)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1087);
	}
	if(err == FZRT_NOERR)
	{
		nU = (wave->n_u_knots - 1) * wave->u_degree;
		closedU = (wave->u_knot_vec[0] == wave->u_knot_vec[1])?FALSE:TRUE;
	}
	if (err == FZRT_NOERR && wave->u_degree >= nU)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1088);
	}
	if (err == FZRT_NOERR && wave->n_control_geom != nU)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1089);
	}

	if(err == FZRT_NOERR)
	{
		/* Allocate memory for array of knots. */
		if (_fzrt_mem_funcs.fzrt_new_ptr((fzrt_int)((nU + wave->u_degree + 1) * sizeof(double)), (fzrt_ptr*)&knot) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1090);
		}
		else
		{
			/* Set values in the knot array. */
			for (i = 0; i < wave->u_degree + 1; ++i)
			{
				knot[i] = 0.0;
			}
			for ( ; i < nU; ++i)
			{
				knot[i] = (double)(i - wave->u_degree);
			}
			for ( ; i < nU + wave->u_degree + 1; ++i)
			{
				knot[i] = (double)(nU - wave->u_degree);
			}
		}
	}

	if(err == FZRT_NOERR)
	{
		/* Allocate memory for a local copy of the control points. */
		if (_fzrt_mem_funcs.fzrt_new_ptr_clear(nU * sizeof(fz_xyz_td), (fzrt_ptr*)&cord) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1091);
		}
		else
		{
			/* Copy control points to local array. */
			for (i = 0; i < nU; ++i)
			{
				cord[i].x = wave->vertices[wave->control_geom[i]].x;
				cord[i].y = wave->vertices[wave->control_geom[i]].y;
				cord[i].z = wave->vertices[wave->control_geom[i]].z;
			}
		}
	}

	if(err == FZRT_NOERR)
	{
		/* Allocate memory for a local copy of the weights. */
		if (_fzrt_mem_funcs.fzrt_new_ptr_clear(nU * sizeof(double), (fzrt_ptr*)&weight) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1092);
		}
		else
		{
			/* Copy weights to local array if rational otherwise set weights to 1.0. */
			if (wave->rational)
			{
				for (i = 0; i < nU; i++)
				{
					weight[i] = wave->vertices[wave->control_geom[i]].w;
				}
			}
			else
			{
				for (i = 0; i < nU; i++)
				{
					weight[i] = 1.0;
				}
			}

			/* Create formZ nurbs curve. */
			if ((err = _model_funcs.fz_objt_cnstr_nrbz_curve(wave->windex, cord, (fzrt_int)wave->n_control_geom, wave->u_degree, FALSE, wave->do_smooth, knot, weight, NULL, &obj)) != FZRT_NOERR)
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1093);
			}
			else
			{
				_ffmt_3d_funcs.fz_ffmt_data_model_objt_complete(wave->windex, obj);

				/* TODO: NEED TO GET A REAL RMTL TAG */
			
				_objt_attr_funcs.fz_objt_attr_set_face_rmtl(wave->windex, obj, 0, 0);
			}
		}
	}

										/*----release temp memory----*/
	if (cord != NULL)
	{
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)cord);
		cord = NULL;
	}
	if (weight != NULL)
	{
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)weight);
		weight = NULL;
	}
	if (knot != NULL)
	{
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)knot);
		knot = NULL;
	}

	return err;
}

/************************************************************************
	Creates a formZ nurbs curve from imported B-Spline data.
*************************************************************************/
static fzrt_error_td wave_construct_bspline_curv(wave_in_td *wave)
{
	fzrt_error_td	err = FZRT_NOERR;
	short			closedU;
	fzrt_int			i, nU;
	double			*weight = NULL;
	fz_xyz_td		*cord = NULL;
	fz_objt_ptr		obj = NULL;
	fz_tag_td		col_tag;

	/* Check that the curve parameters are valid. */
	if (wave->control_geom == NULL)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1094);
	}
	if (err == FZRT_NOERR && wave->u_knot_vec == NULL)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1095);
	}
	if (err == FZRT_NOERR && wave->u_degree < 1)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1096);
	}
	if(err == FZRT_NOERR)
	{
		nU = wave->n_u_knots - wave->u_degree - 1;
		closedU = (wave->u_knot_vec[0] == wave->u_knot_vec[1])?FALSE:TRUE;
	}
	if (err == FZRT_NOERR && wave->u_degree >= nU)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1097);
	}
	if (err == FZRT_NOERR && wave->n_control_geom != nU)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1098);
	}

	if(err == FZRT_NOERR)
	{
		/* Allocate memory for a local copy of the control points. */
		if (_fzrt_mem_funcs.fzrt_new_ptr_clear(nU * sizeof(fz_xyz_td), (fzrt_ptr*)&cord) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1099);
		}
		else
		{
			/* Copy control points to local array. */
			for (i = 0; i < nU; ++i)
			{
				cord[i].x = wave->vertices[wave->control_geom[i]].x;
				cord[i].y = wave->vertices[wave->control_geom[i]].y;
				cord[i].z = wave->vertices[wave->control_geom[i]].z;
			}
		}
	}

	if(err == FZRT_NOERR)
	{
		/* Allocate memory for a local copy of the weights. */
		if (_fzrt_mem_funcs.fzrt_new_ptr_clear(nU * sizeof(double), (fzrt_ptr*)&weight) != FZRT_NOERR)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1100);
		}
		else
		{
			/* Copy weights to local array if rational otherwise set weights to 1.0. */
			if (wave->rational)
			{
				for (i = 0; i < nU; i++)
				{
					weight[i] = wave->vertices[wave->control_geom[i]].w;
				}
			}
			else
			{
				for (i = 0; i < nU; i++)
				{
					weight[i] = 1.0;
				}
			}

			/* Create formZ nurbs curve. */
			if ((err = _model_funcs.fz_objt_cnstr_nrbz_curve(wave->windex, cord, (fzrt_int)wave->n_control_geom, wave->u_degree, FALSE, wave->do_smooth, wave->u_knot_vec, weight, NULL, &obj)) != FZRT_NOERR)
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1101);
			}
			else
			{
				_ffmt_3d_funcs.fz_ffmt_data_model_objt_complete(wave->windex, obj);

				if(!_type_funcs.fz_tag_is_NULL(&wave->stag))
				{
					col_tag = wave->stag;
				}
				else
				{
					err = wave_get_default_surf(wave, &col_tag);
				}
			}
		}
	}

	/* Free local control point and weight arrays. */
	if (cord != NULL)
	{
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)cord);
		cord = NULL;
	}
	if (weight != NULL)
	{
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)weight);
		weight = NULL;
	}

	return err;
}

/************************************************************************
	Place holder function for Cardinal curve.
*************************************************************************/
static fzrt_error_td wave_construct_cardinal_curv(wave_in_td *wave)
{
	fzrt_error_td	err = FZRT_NOERR;

	return err;
}

/************************************************************************
	Place holder function for Taylor curve.
*************************************************************************/
static fzrt_error_td wave_construct_taylor_curv(wave_in_td *wave)
{
	fzrt_error_td	err = FZRT_NOERR;

	return err;
}

/************************************************************************
	Assign name, color and texture attributes to a formZ nurbs surface.
*************************************************************************/
static fzrt_error_td wave_attribute_surf(
							wave_in_td	*wave,
							fz_objt_ptr	obj)
{
	fzrt_error_td		err = FZRT_NOERR;
	double				sizex, sizey, solid_size;
	short				map_type = FZ_MAP_SPACE_CUBIC;
	fzrt_boolean		h_center, v_center, h_mirror, v_mirror, flip = 0;
	fzrt_int				id = 0;
	fz_type_td			fz_type;
	fz_tag_td			col_tag;

	/* if applicable set object's name */
	if (wave->obj_name[0] != '\0')
	{
		_objt_attr_funcs.fz_objt_attr_set_objt_name(wave->windex, obj, wave->obj_name);
		wave->obj_name[0] = '\0';
	}

	/* If a surface style was set by the translator use it. */
	if(!_type_funcs.fz_tag_is_NULL(&wave->stag))
	{
		col_tag = wave->stag;
	}
	else
	{
		err = wave_get_default_surf(wave, &col_tag);
	}
//	if(col_indx > 0)
//	{
		/* Assign the surface style tag to the formZ object. */
		_objt_attr_funcs.fz_objt_attr_set_objt_rmtl(wave->windex, obj, &col_tag);
//	}

	if (wave->do_tmaps)
	{
		map_type = FZ_MAP_SPACE_PARAMETRIC;
		sizex = 1.0;
		sizey = 1.0;
		solid_size = 1.0;
		h_center = TRUE;
		v_center = TRUE;
		h_mirror = FALSE;
		v_mirror = FALSE;
		flip = FALSE;
		err = _objt_attr_funcs.fz_objt_attr_create_objt_tctl(wave->windex, obj);
		if(err == FZRT_NOERR)
		{
			/* set the texture map control map type to parametric. */
			_type_funcs.fz_type_set_short(&map_type, &fz_type);
			_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, obj, -1, FZ_ATTR_TCTL_PARM_MAP_TYPE, &fz_type);

			/* Set the texture map control's x size parameter to 1.0. */
			_type_funcs.fz_type_set_double(&sizex, &fz_type);
			_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, obj, -1, FZ_ATTR_TCTL_PARM_SIZE_X, &fz_type);

			/* Set the texture map control's y size parameter to 1.0. */
			_type_funcs.fz_type_set_double(&sizey, &fz_type);
			_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, obj, -1, FZ_ATTR_TCTL_PARM_SIZE_Y, &fz_type);

			/* Set the texture map control's solid size parameter to 1.0. */
			_type_funcs.fz_type_set_double(&solid_size, &fz_type);
			_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, obj, -1, FZ_ATTR_TCTL_PARM_SOLID_SIZE, &fz_type);

			/* Set the texture map control's horizontal center parameter to TRUE. */
			_type_funcs.fz_type_set_boolean(&h_center, &fz_type);
			_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, obj, -1, FZ_ATTR_TCTL_PARM_CENTER_HORIZ, &fz_type);

			/* Set the texture map control's vertical center parameter to TRUE. */
			_type_funcs.fz_type_set_boolean(&v_center, &fz_type);
			_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, obj, -1, FZ_ATTR_TCTL_PARM_CENTER_VERT, &fz_type);

			/* Set the texture map control's horizontal mirror parameter to FALSE. */
			_type_funcs.fz_type_set_boolean(&h_mirror, &fz_type);
			_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, obj, -1, FZ_ATTR_TCTL_PARM_MIRROR_HORIZ, &fz_type);

			/* Set the texture map control's vertical mirror parameter to FALSE. */
			_type_funcs.fz_type_set_boolean(&v_mirror, &fz_type);
			_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, obj, -1, FZ_ATTR_TCTL_PARM_MIRROR_VERT, &fz_type);

			/* Set the texture map control's flip parameter to FALSE. */
			_type_funcs.fz_type_set_boolean(&flip, &fz_type);
			_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, obj, -1, FZ_ATTR_TCTL_PARM_FLIP, &fz_type);
		}
	}

	return err;
}

/************************************************************************
	Creates a formZ nurbs surface from imported data.
*************************************************************************/
static fzrt_error_td wave_make_surf(wave_in_td *wave)
{
	fzrt_error_td					err = FZRT_NOERR;
	fzrt_unsigned_int					i, j, nU, nV, size2 = 0L;
	fzrt_int							closedU, closedV;
	fz_xyz_td						*cpts = NULL;
	double							*weights = NULL;
	fz_objt_ptr						obj = NULL;
	fz_grup_ptr						grup = NULL;

	/* do some consistency checks and calculations */
	if ((wave->control_geom != NULL) &&
		(wave->u_knot_vec != NULL) &&
		(wave->v_knot_vec != NULL) &&
		(wave->u_degree >= 1) &&
		(wave->v_degree >= 1) &&
		((nU = (wave->n_u_knots - wave->u_degree - 1)) > 0) &&
		((nV = (wave->n_v_knots - wave->v_degree - 1)) > 0) &&
		(wave->n_control_geom == (nU * nV)))
	{
		/* allocate surface control points */
		if ((_fzrt_mem_funcs.fzrt_new_ptr(nU * nV* sizeof(fz_xyz_td), (fzrt_ptr*)&cpts) == FZRT_NOERR) &&
			(_fzrt_mem_funcs.fzrt_new_ptr(nU * nV * sizeof(double), (fzrt_ptr*)&weights) == FZRT_NOERR))
		{
			/* collect mesh's surface control points and weights */
			for (i = 0L; i < nU; i++)
			{
				for (j = 0L; j < nV; j++)
				{
					cpts[i*nV+j].x = wave->vertices[wave->control_geom[j * nU + i]].x;
					cpts[i*nV+j].y = wave->vertices[wave->control_geom[j * nU + i]].y;
					cpts[i*nV+j].z = wave->vertices[wave->control_geom[j * nU + i]].z;
					if(wave->rational)
					{
						weights[i*nV+j] = (wave->rational?wave->vertices[wave->control_geom[j * nU + i]].w:(double)1.0);
					}
					else
					{
						weights[i*nV+j] = 1.0;
					}
				}
			}

			if (err == FZRT_NOERR)
			{
				closedU = (wave->u_knot_vec[0] == wave->u_knot_vec[1])?FALSE:TRUE;
				closedV = (wave->v_knot_vec[0] == wave->v_knot_vec[1])?FALSE:TRUE;
//				if (FZ_CHKBIT(in_opts_flags, FZ_FFMT_DATA_MODEL_READ_OPTS_PRESERVENURBS_BIT))
//				{
					/* Create a nurbz surface object. */
					err = _model_funcs.fz_objt_cnstr_nrbz_from_cpts(wave->windex, cpts, weights,
													nU, wave->u_degree, closedU, wave->u_knot_vec,
													nV, wave->v_degree, closedV, wave->v_knot_vec,
													NULL, NULL, &obj);
					if(err == FZRT_NOERR)
					{
						if(!wave->do_smooth)	 _model_funcs.fz_objt_edit_drop_smooth(wave->windex, obj);
						_ffmt_3d_funcs.fz_ffmt_data_model_objt_complete(wave->windex, obj);
					}
//				}
//				else
//				{
//					/* TODO : NEED TO CONSTRUCT CMESH */
//				}
			}

			/* free surface control points and weights */
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)cpts); cpts = NULL;
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)weights); weights = NULL;

			/* FORMZ CLEAN-UP */
			if (err == FZRT_NOERR) err = wave_attribute_surf(wave, obj);
		}
		else
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1102);
		}
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1103);
	}

	return err;
}

/************************************************************************
	Place holder function for Basis matrix surface.
*************************************************************************/
static fzrt_error_td wave_construct_bmatrix_surf(wave_in_td *wave)
{
	fzrt_error_td	err = FZRT_NOERR;

	return err;
}

/************************************************************************
	Process a Bezier surface.
*************************************************************************/
static fzrt_error_td wave_construct_bezier_surf(wave_in_td *wave)
{
	return wave_make_surf(wave);
}

/************************************************************************
	Process a B-Spline surface.
*************************************************************************/
static fzrt_error_td wave_construct_bspline_surf(wave_in_td *wave)
{
	return wave_make_surf(wave);
}

/************************************************************************
	Place holder function for Cardinal surface.
*************************************************************************/
static fzrt_error_td wave_construct_cardinal_surf(wave_in_td *wave)
{
	fzrt_error_td	err = FZRT_NOERR;

	return err;
}

/************************************************************************
	Place holder function for Taylor surface.
*************************************************************************/
static fzrt_error_td wave_construct_taylor_surf(wave_in_td *wave)
{
	fzrt_error_td	err = FZRT_NOERR;

	return err;
}

/************************************************************************
	Processes the end of a curve or surface. (keyword "end")
	Each successful call to "wave_get_end" 
	constructs a formZ object, except in the case of a trimming
	curv or hole, that are parsed only for the integrity of the file.
	It is assumed that a valid curve/surface opening-keyword has
	been encounterd in the file and all pertinent information has been
	set correctly, otherwise an error is set.
	In all cases except the BASIS MATRIX, CARDINAL or TAYLOR curve/surface,
	where a formZ corresponding representation does not yet exist, a formZ
	nurbs surface or nurbs curve is consructed.
	If a formZ non controled object was already being constructed,
	the information is preserved up to the point that an object
	terminating keyword signals the end of object in the file.
*************************************************************************/
static fzrt_error_td wave_get_end(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td	err = FZRT_NOERR;
	fzrt_boolean	ignore = FALSE;
	char			line[512];
	fzrt_int			i;

	if(!wave_eoln(fp))
	{
		wave_read_line(fp, line);
		for(i = 0; i <= strlen(line); i++)	line[i] = tolower(line[i]);
		if(strcmp(line, "of file") == 0)
		{
			ignore = TRUE;
		}
	}
	if(!ignore)
	{
		if (!wave->begin_curvsurf)
		{
			err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_CONSISTENCY_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1104);
		}
		else
		{
			if (wave->begin_curvsurf == WAVE_CURV)
			{
				switch (wave->cstype)
				{
					case WAVE_CSTYPE_BMATRIX:
						err = wave_construct_bmatrix_curv(wave);
					break;
						
					case WAVE_CSTYPE_BEZIER:
						err = wave_construct_bezier_curv(wave);
					break;
						
					case WAVE_CSTYPE_BSPLINE:
						err = wave_construct_bspline_curv(wave);
					break;
						
					case WAVE_CSTYPE_CARDINAL:
						err = wave_construct_cardinal_curv(wave);
					break;
						
					case WAVE_CSTYPE_TAYLOR:
						err = wave_construct_taylor_curv(wave);
					break;
						
					default:
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_UNKNOWN_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1105);
					break;
				}
			}
			else if (wave->begin_curvsurf == WAVE_CURV2)
			{

			}
			else if (wave->begin_curvsurf == WAVE_SURF)
			{
				switch (wave->cstype)
				{
					case WAVE_CSTYPE_BMATRIX:
						err = wave_construct_bmatrix_surf(wave);
					break;
						
					case WAVE_CSTYPE_BEZIER:
						err = wave_construct_bezier_surf(wave);
					break;
						
					case WAVE_CSTYPE_BSPLINE:
						err = wave_construct_bspline_surf(wave);
					break;
						
					case WAVE_CSTYPE_CARDINAL:
						err = wave_construct_cardinal_surf(wave);
					break;
						
					case WAVE_CSTYPE_TAYLOR:
						err = wave_construct_taylor_surf(wave);
					break;
						
					default:
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_UNKNOWN_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1106);
					break;
				}
			}
			else
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_UNKNOWN_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1107);
			}
			if(err == FZRT_NOERR)
			{
				err = wave_skip_line(wave, fp);
			}
		}

		wave->begin_curvsurf = WAVE_NONE;
	}

	return err;
}

/************************************************************************
	Begin a new object import.
	Each sucessful call to "wave_begin_object" allocates a formZ object.
*************************************************************************/
static fzrt_int wave_begin_object(wave_in_td *wave)
{
	fzrt_int	err = FZRT_NOERR;

	/* Create a new formZ object. */
	err = _model_funcs.fz_objt_cnstr_objt_new(wave->windex, &wave->obj);
	if(err == FZRT_NOERR)
	{
		_ffmt_3d_funcs.fz_ffmt_data_model_objt_complete(wave->windex, wave->obj);
	}

	/* Clear translator data for last object */
	wave_facemltab_reset(wave->facemltab);
	if (wave->do_tmaps)
	{
		wave_seglinktab_reset(wave->seglinktab);
		wave_facemarktab_reset(wave->facemarktab);
	}

	return err;
}

/************************************************************************
	End the current object import.  This fills a formZ object with
	data collected during import of the Wavefront Obj file.
	It is assumed that the formZ object has been previously allocated
	by a "wave_begin_object" call.
*************************************************************************/
static fzrt_error_td wave_end_object(wave_in_td *wave)
{
	fzrt_error_td		err = FZRT_NOERR;
	fzrt_int				i;
	short				map_type = FZ_MAP_SPACE_UV_CORDS;
	double				sizex = 1.0;
	double				sizey = 1.0;
	double				solid_size = 1.0;
	fzrt_boolean		h_center = FALSE;
	fzrt_boolean		v_center = FALSE;
	fzrt_boolean		h_mirror = FALSE;
	fzrt_boolean		v_mirror = FALSE;
	fzrt_boolean		flip = FALSE;
	fz_xy_td			*seg_uvs = NULL;
	fzrt_int				*faces_ptr = NULL;
	fzrt_int				obj_lface, obj_lsegt;
	fz_type_td			fz_type;
	fz_xyz_td			*cords;
	fz_tag_td			col_tag;

	if (wave->obj == NULL)
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_NULL_OBJECT_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1108);
	}

	if(err == FZRT_NOERR)
	{
		if (wave->obj_name[0] != '\0')
		{
			/* Set the name of the formZ object. */
			_objt_attr_funcs.fz_objt_attr_set_objt_name(wave->windex, wave->obj, wave->obj_name);
			wave->obj_name[0] = '\0';
		}

		/* Create a facetted object */
		if (wave->n_faces > 0)
		{
			fzrt_int k, nsegs;
			for (k = 0, nsegs = 0, faces_ptr = wave->faces; k < wave->n_faces; ++k, ++faces_ptr) nsegs += *faces_ptr;
			/* Tell the formZ object how many faces, edges, and vertices will be added. */
			if (_model_funcs.fz_objt_fact_allocate(wave->windex, wave->obj, wave->n_faces, wave->n_faces, nsegs, wave->ncord) != FZRT_NOERR)
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1109);
			}
			if (err == FZRT_NOERR)
			{
				for (k = 0, nsegs = 0; k < wave->n_faces; ++k)
				{
					/* Create a face. */
					_model_funcs.fz_objt_fact_create_face(wave->windex, wave->obj, &wave->segt[nsegs], wave->faces[k], &obj_lface);
					nsegs += wave->faces[k];
					
					/* If a material is assigned to the face, set the face's surface style. */
					if(wave->facemltab->tab != NULL && wave->facemltab->num != 0)		_objt_attr_funcs.fz_objt_attr_set_face_rmtl(wave->windex, wave->obj, obj_lface, &wave->facemltab->tab[obj_lface]);	/* TAH 10/28/02 */	/* TAH 12/09/03 */	/* TAH 12/15/03 */	/* TAH 12/24/03 */
				}
//				_model_funcs.fz_objt_fact_link_faces(wave->windex, wave->obj);
			}
			wave->n_faces = 0;
			wave->n_segt = 0;
		}
		/* Create a point cloud object */
		else if(wave->ncord != 0)
		{
			/* Tell the formZ object how many vertices will be added. */
			if(_model_funcs.fz_objt_fact_allocate(wave->windex, wave->obj, 0, 0, 0, wave->ncord) != FZRT_NOERR)
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1110);
			}
		}
		
		if(err == FZRT_NOERR)
		{
			/* Copy the objects vertex coordinates into a local array. */
			err = _fzrt_mem_funcs.fzrt_new_ptr(wave->n_vertices * sizeof(fz_xyz_td), (fzrt_ptr*)&cords);
			if(cords != NULL)
			{
				for (i = 0; i < wave->n_vertices; ++i)
				{
					if (wave->vertices_links[i] != -1)
					{
						cords[wave->vertices_links[i]].x = wave->vertices[i].x;
						cords[wave->vertices_links[i]].y = wave->vertices[i].y;
						cords[wave->vertices_links[i]].z = wave->vertices[i].z;
						wave->vertices_links[i] = -1;
					}
				}
				
				/* Set the formZ object's vertex coordinates. */
				_model_funcs.fz_objt_fact_add_pnts(wave->windex, wave->obj, cords, wave->ncord);
				_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)cords);
			}
			else
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1111);
			}

			if(err == FZRT_NOERR)
			{
				/* If a surface style was set by the translator use it. */
				if(!_type_funcs.fz_tag_is_NULL(&wave->stag))
				{
					col_tag = wave->stag;
				}
				else
				{
					err = wave_get_default_surf(wave, &col_tag);
				}
//				if(col_indx > 0)
//				{
					/* Assign the surface style tag to the formZ object. */
					_objt_attr_funcs.fz_objt_attr_set_objt_rmtl(wave->windex, wave->obj, &col_tag);
//				}

				/* set uv controls and color to the object */
				if (wave->do_tmaps)
				{
					/* if we read any controls and the object has segs, prepare the segment uv table */
					_model_funcs.fz_objt_get_segt_count(wave->windex, wave->obj, FZ_OBJT_MODEL_TYPE_FACT, &obj_lsegt);
					if ((wave->seglinktab->num > 0) && (obj_lsegt > 0))
					{
						if (_fzrt_mem_funcs.fzrt_new_ptr_clear((fzrt_size)sizeof(fz_xy_td) * (obj_lsegt + 1), (fzrt_ptr*)&seg_uvs) == FZRT_NOERR)
						{
							for (i = 0; i < WAVE_MIN(wave->seglinktab->num, (obj_lsegt + 1)); ++i)
							{
								if (wave->seglinktab->tab[i] > -1)
								{
									seg_uvs[i] = wave->uvtab->tab[wave->seglinktab->tab[i]];
								}
							}

							/* Create the object's texture map control. */
							err = _objt_attr_funcs.fz_objt_attr_create_objt_tctl(wave->windex, wave->obj);
							if(err == FZRT_NOERR)
							{
								/* Set the texture map control's map type parameter to uv coordinates. */
								_type_funcs.fz_type_set_short(&map_type, &fz_type);
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, wave->obj, -1, FZ_ATTR_TCTL_PARM_MAP_TYPE, &fz_type);

								/* Set the texture map control's x size parameter to 1.0. */
								_type_funcs.fz_type_set_double(&sizex, &fz_type);
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, wave->obj, -1, FZ_ATTR_TCTL_PARM_SIZE_X, &fz_type);

								/* Set the texture map control's y size parameter to 1.0. */
								_type_funcs.fz_type_set_double(&sizey, &fz_type);
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, wave->obj, -1, FZ_ATTR_TCTL_PARM_SIZE_Y, &fz_type);

								/* Set the texture map control's solid size parameter to 1.0. */
								_type_funcs.fz_type_set_double(&solid_size, &fz_type);
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, wave->obj, -1, FZ_ATTR_TCTL_PARM_SOLID_SIZE, &fz_type);

								/* Set the texture map control's horizontal center parameter to FALSE. */
								_type_funcs.fz_type_set_boolean(&h_center, &fz_type);
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, wave->obj, -1, FZ_ATTR_TCTL_PARM_CENTER_HORIZ, &fz_type);

								/* Set the texture map control's vertical center parameter to FALSE. */
								_type_funcs.fz_type_set_boolean(&v_center, &fz_type);
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, wave->obj, -1, FZ_ATTR_TCTL_PARM_CENTER_VERT, &fz_type);

								/* Set the texture map control's horizontal mirror parameter to FALSE. */
								_type_funcs.fz_type_set_boolean(&h_mirror, &fz_type);
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, wave->obj, -1, FZ_ATTR_TCTL_PARM_MIRROR_HORIZ, &fz_type);

								/* Set the texture map control's vertical mirror parameter to FALSE. */
								_type_funcs.fz_type_set_boolean(&v_mirror, &fz_type);
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, wave->obj, -1, FZ_ATTR_TCTL_PARM_MIRROR_VERT, &fz_type);

								/* Set the texture map control's flip parameter to FALSE. */
								_type_funcs.fz_type_set_boolean(&flip, &fz_type);
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_parm(wave->windex, wave->obj, -1, FZ_ATTR_TCTL_PARM_FLIP, &fz_type);

								/* Set the texture map control's uv coordinates. */
								_objt_attr_funcs.fz_objt_attr_set_objt_tctl_uv(wave->windex, wave->obj, seg_uvs);
							}

							/* "expr_rfile_2_tcntrl" makes an internal copy of "vert_uvs" */
							_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)seg_uvs); seg_uvs = NULL;
						}
						else
						{
							err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1112);
						}
					}
				}
				else
				{
					err = wave_get_default_surf(wave, &col_tag);
					if(err != FZRT_NOERR)
					{
						/* Assign the surface style tag to the formZ object. */
						_objt_attr_funcs.fz_objt_attr_set_objt_rmtl(wave->windex, wave->obj, &col_tag);
					}
				}
			}
		}
		_ffmt_3d_funcs.fz_ffmt_data_model_cleanup_objt_geom(wave->windex, wave->obj);
	}

	wave->obj = NULL;
	wave->ncord = 0;

	return err;
}

/************************************************************************
	Process face "f" keyword.
	Each call to "wave_get_f" adds a new face to the _faces database.
	When a new group is defined with the "g", "s", "mg", or "o" keyword,
	(according to the current import options), or EOF is encountered
	then the geometry of the object is copied from wave->vertices to _obj->cord
	array, and the current object is added to the formZ
	project, provided that at least one face has been defined from the
	time the object was opened. Immediately following a new formZ object
	is opened.
*************************************************************************/
static fzrt_error_td wave_get_f(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td		err = FZRT_NOERR;
	char				c = EOF;
	fzrt_int				i, seg, tmp, nsegs;
	fz_tag_td			col_tag;
	fzrt_int				seg_uv;
	fzrt_boolean		first_seg = TRUE, face_has_uvs = FALSE, got_seg_uv = FALSE;

	if (wave->obj == NULL)
	{
		err = wave_begin_object(wave);
	}

	if(err == FZRT_NOERR)
	{
			/* Read a face list */
		if ((err = wave_skip_blank(wave, fp)) == FZRT_NOERR)
		{
			i = 0;
			nsegs = 0;
			do {
				/*Get a number nnn[/[nnn]/[nnn]] */
				if (fscanf(fp, "%d", &seg) < 1)
				{
					err = wave_check_eof(fp);
				}
				else
				{
					got_seg_uv = 0;

					if ((c = fgetc(fp)) == '/')
					{
						if ((c = fgetc(fp)) == '/')
						{
							c = fgetc(fp); ungetc(c, fp);
							if ((c == '.') || (c == '+') || (c == '-') || isdigit(c))
							{
								if (fscanf(fp, "%d", &tmp) < 1)
								{
									err = wave_check_eof(fp);
								}
							}
						}
						else
						{
							ungetc(c, fp);

							got_seg_uv = 1;
							if (fscanf(fp, "%d", &seg_uv) < 1)
							{
								err = wave_check_eof(fp);
							}
							else
							{
								if ((c = fgetc(fp)) == '/')
								{
									c = fgetc(fp); ungetc(c, fp);
									if ((c == '.') || (c == '+') || (c == '-') || isdigit(c))
									{
										if (fscanf(fp, "%d", &tmp) < 1)
										{
											err = wave_check_eof(fp);
										}
									}
								}
								else ungetc(c, fp);
							}
						}
					}
					else ungetc(c, fp);

					if(err == FZRT_NOERR)
					{
						if (wave->do_tmaps)
						{
							if (first_seg)
							{
								first_seg = 0;
								face_has_uvs = (got_seg_uv?1:0);
							}
							if (face_has_uvs && got_seg_uv)
							{
								seg_uv = (seg_uv > 0) ? (seg_uv - 1) : (wave->uvtab->num + seg_uv);
								err = wave_seglinktab_add(wave->seglinktab, seg_uv);
							}
							else
							{
								err = wave_seglinktab_add(wave->seglinktab, -1);
							}
						}

						if(err == FZRT_NOERR)
						{
							if ((err = wave_skip_blank(wave, fp)) == FZRT_NOERR)
							{
								++wave->n_segt;
								++nsegs;
								if (((wave->n_segt-1)%256) == 0)
								{
									if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&wave->segt, &wave->s_segt, sizeof(fzrt_int), (fzrt_int)256) != FZRT_NOERR)
									{
										err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1113);
									}
								}

								if(err == FZRT_NOERR)
								{
									wave->segt[wave->n_segt - 1] = (seg > 0)?(seg - 1):(wave->n_vertices + seg);
									if ((wave->segt[wave->n_segt - 1] < 0) || (wave->segt[wave->n_segt - 1] > wave->n_vertices - 1))
									{
										err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_NO_SUCH_VERTEX_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1114);
									}
									else
									{
										if (((c = fgetc(fp)) != EOF) && (c != '\n') && (c != '\r')) ungetc(c, fp);
										++i;
									}
								}
							}
						}
					}
				}
			} while ((c != EOF) && (c != '\n') && (c != '\r'));

			if(err == FZRT_NOERR)
			{
				/* Adjust entries to wave->vertices_links */
				for (i = wave->n_segt - nsegs; i < wave->n_segt; ++i)
				{
					tmp = wave->segt[i];
					if (wave->vertices_links[tmp] == -1)
					{
						wave->vertices_links[tmp] = wave->ncord;
						++wave->ncord;
					}
					wave->segt[i] = wave->vertices_links[tmp];
				}

				if ((wave->n_faces%256) == 0)
				{
					if (_fzrt_mem_funcs.fz_mem_zone_incr_ptr(NULL, (fzrt_ptr*)&wave->faces, &wave->s_faces, sizeof(fzrt_int), (fzrt_int)256) != FZRT_NOERR)
					{
						err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1115);
					}
				}

				if(err == FZRT_NOERR)
				{
					wave->faces[wave->n_faces++] = nsegs;

					/* If a surface style was set by the translator use it. */
					if(!_type_funcs.fz_tag_is_NULL(&wave->stag))
					{
						col_tag = wave->stag;
					}
					else
					{
						err = wave_get_default_surf(wave, &col_tag);
					}
//					if(col_indx > 0)
//					{
						/* Remember this face's surface style. */
						err = wave_facemltab_add(wave->facemltab, &col_tag);
//					}

					if(err == FZRT_NOERR)	err = wave_facemarktab_add(wave->facemarktab, face_has_uvs);
				}
			}
		}
	}

	return err;
}

/************************************************************************
	Process group "g" keyword.
	Each call to "wave_get_g" generates a new
	form¥Z object, provided that faces exist in the _faces database and
	the current import grouping option is set to "group".
*************************************************************************/
static fzrt_error_td wave_get_g(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td			err = FZRT_NOERR;
	fzrt_int					err_code;
	fzrt_error_context_enum	err_cont;
	fzrt_int					cont_id;

	/* if object separator is "g" */
	if (wave->group_sign == WAVE_SIGN_IS_G)
	{
		if ((err = wave_end_object(wave)) != FZRT_NOERR)
		{
			/* ignore NULL object errors. */
			_fzrt_base_funcs.fzrt_error_get_info(err, NULL, 0, &err_code, NULL, &err_cont, &cont_id, NULL, 0, NULL);
			if((err_code == WAVE_NULL_OBJECT_ERR) &&
			   (err_cont == FZRT_ERROR_CONTEXT_PLUGIN) &&
			   (cont_id == _wave_plugin_runtime_id))
			{
				err = FZRT_NOERR;
			}
		}
		if (err == FZRT_NOERR)
		{
			/* check for name */
			if ((err = wave_read_line(fp, wave->obj_name)) == FZRT_NOERR)
			{
				err = wave_skipto_keyword(fp);
			}
		}
	}

	/* else skip the line */
	else
	{
		if ((err = wave_skip_comment(fp)) == FZRT_NOERR)
		{
			err = wave_skipto_keyword(fp);
		}
	}

	return err;
}

/************************************************************************
	Process smoothing group "s" keyword.
	Each call to "wave_get_s" generates a new
	formZ object, provided that faces exist in the _faces database and
	the current import grouping option is set to "smoothing group".
*************************************************************************/
static fzrt_error_td wave_get_s(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td			err = FZRT_NOERR;
	fzrt_int					err_code;
	fzrt_error_context_enum	err_cont;
	fzrt_int					cont_id;

	if ((err = wave_skip_line(wave, fp)) == FZRT_NOERR)
	{
		if (wave->group_sign == WAVE_SIGN_IS_S)
		{
			if ((err = wave_end_object(wave)) != FZRT_NOERR)
			{
				/* ignore NULL object errors. */
				_fzrt_base_funcs.fzrt_error_get_info(err, NULL, 0, &err_code, NULL, &err_cont, &cont_id, NULL, 0, NULL);
				if((err_code == WAVE_NULL_OBJECT_ERR) &&
				   (err_cont == FZRT_ERROR_CONTEXT_PLUGIN) &&
				   (cont_id == _wave_plugin_runtime_id))
				{
					err = FZRT_NOERR;
				}
			}
		}
	}

	return err;
}

/************************************************************************
	Process merging group "mg" keyword.
	Each call to "wave_get_mg" generates a new
	formZ object, provided that faces exist in the _faces database and
	the current import grouping option is set to "merging group".
*************************************************************************/
static fzrt_error_td wave_get_mg(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td			err = FZRT_NOERR;
	fzrt_int					err_code;
	fzrt_error_context_enum	err_cont;
	fzrt_int					cont_id;

	if ((err = wave_skip_line(wave, fp)) == FZRT_NOERR)
	{		
		if (wave->group_sign == WAVE_SIGN_IS_MG)
		{
			if ((err = wave_end_object(wave)) != FZRT_NOERR)
			{
				/* ignore NULL object errors. */
				_fzrt_base_funcs.fzrt_error_get_info(err, NULL, 0, &err_code, NULL, &err_cont, &cont_id, NULL, 0, NULL);
				if((err_code == WAVE_NULL_OBJECT_ERR) &&
				   (err_cont == FZRT_ERROR_CONTEXT_PLUGIN) &&
				   (cont_id == _wave_plugin_runtime_id))
				{
					err = FZRT_NOERR;
				}
			}
		}
	}

	return err;
}

/************************************************************************
	Process tht object "o" keyword.
	Each call to "wave_get_o"  generates a new
	formZ object, provided that faces exist in the _faces database and
	the current import grouping option is set to "object".
*************************************************************************/
static fzrt_error_td wave_get_o(wave_in_td *wave, FILE *fp)
{
	fzrt_error_td			err = FZRT_NOERR;
	fzrt_int					err_code;
	fzrt_error_context_enum	err_cont;
	fzrt_int					cont_id;

	/* if object separator is "o" */
	if (wave->group_sign == WAVE_SIGN_IS_O)
	{
		if((err = wave_end_object(wave)) != FZRT_NOERR)
		{
			/* ignore NULL object errors. */
			_fzrt_base_funcs.fzrt_error_get_info(err, NULL, 0, &err_code, NULL, &err_cont, &cont_id, NULL, 0, NULL);
			if((err_code == WAVE_NULL_OBJECT_ERR) &&
			   (err_cont == FZRT_ERROR_CONTEXT_PLUGIN) &&
			   (cont_id == _wave_plugin_runtime_id))
			{
				err = FZRT_NOERR;
			}
		}

		if (err == FZRT_NOERR)
		{
			/* check for name */
			if ((err = wave_read_line(fp, wave->obj_name)) == FZRT_NOERR)
			{
				err = wave_skipto_keyword(fp);
			}
		}
	}

	/* else skip the line */
	else
	{
		if ((err = wave_skip_comment(fp)) == FZRT_NOERR)
		{
			err = wave_skipto_keyword(fp);
		}
	}

	return err;
}

/************************************************************************
	Assigns a pointer to a handler function for the each known WaveFront
	keyword.
*************************************************************************/
static fzrt_error_td wave_keywordfunctab_begin(wave_table_entry_td **keywordfunctab)
{
	fzrt_error_td err = FZRT_NOERR;

	if (_fzrt_mem_funcs.fzrt_new_ptr((fzrt_size)(41 /*40*/ /*39*/ * sizeof(wave_table_entry_td)), (fzrt_ptr*)keywordfunctab) == FZRT_NOERR) { /* VAS 05/16/96 */ /* VAS 02/26/96 */
										/* skip utilities */
		(*keywordfunctab)[ 0].text = "#";			(*keywordfunctab)[ 0].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[ 1].text = "!";			(*keywordfunctab)[ 1].func = (long_func_wave_lookup *)wave_skip_line;
										/* vertex data */
		(*keywordfunctab)[ 2].text = "v";			(*keywordfunctab)[ 2].func = (long_func_wave_lookup *)wave_get_v;
		(*keywordfunctab)[ 3].text = "vt";			(*keywordfunctab)[ 3].func = (long_func_wave_lookup *)wave_read_vt;
		(*keywordfunctab)[ 4].text = "vn";			(*keywordfunctab)[ 4].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[ 5].text = "vp";			(*keywordfunctab)[ 5].func = (long_func_wave_lookup *)wave_skip_line;
										/* free form curve/surface attributes */
		(*keywordfunctab)[ 6].text = "cstype";		(*keywordfunctab)[ 6].func = (long_func_wave_lookup *)wave_get_cstype;
		(*keywordfunctab)[ 7].text = "deg";			(*keywordfunctab)[ 7].func = (long_func_wave_lookup *)wave_get_deg;
		(*keywordfunctab)[ 8].text = "bmat";		(*keywordfunctab)[ 8].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[ 9].text = "step";		(*keywordfunctab)[ 9].func = (long_func_wave_lookup *)wave_skip_line;
										/* elements */
		(*keywordfunctab)[10].text = "p";			(*keywordfunctab)[10].func = (long_func_wave_lookup *)wave_get_p;
		(*keywordfunctab)[11].text = "l";			(*keywordfunctab)[11].func = (long_func_wave_lookup *)wave_get_l;
		(*keywordfunctab)[12].text = "f";			(*keywordfunctab)[12].func = (long_func_wave_lookup *)wave_get_f;
		(*keywordfunctab)[13].text = "fo";			(*keywordfunctab)[13].func = (long_func_wave_lookup *)wave_get_f;
		(*keywordfunctab)[14].text = "curv";		(*keywordfunctab)[14].func = (long_func_wave_lookup *)wave_get_curv;
		(*keywordfunctab)[15].text = "curv2";		(*keywordfunctab)[15].func = (long_func_wave_lookup *)wave_get_curv2;
		(*keywordfunctab)[16].text = "surf";		(*keywordfunctab)[16].func = (long_func_wave_lookup *)wave_get_surf;
										/* free form curve/surface body statements */
		(*keywordfunctab)[17].text = "parm";		(*keywordfunctab)[17].func = (long_func_wave_lookup *)wave_get_parm;
		(*keywordfunctab)[18].text = "trim";		(*keywordfunctab)[18].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[19].text = "hole";		(*keywordfunctab)[19].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[20].text = "scrv";		(*keywordfunctab)[20].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[21].text = "sp";			(*keywordfunctab)[21].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[22].text = "end";			(*keywordfunctab)[22].func = (long_func_wave_lookup *)wave_get_end;
										/* connectivity between free-form surfaces */
		(*keywordfunctab)[23].text = "con";			(*keywordfunctab)[23].func = (long_func_wave_lookup *)wave_skip_line;
										/* grouping */
		(*keywordfunctab)[24].text = "g";			(*keywordfunctab)[24].func = (long_func_wave_lookup *)wave_get_g;
		(*keywordfunctab)[25].text = "s";			(*keywordfunctab)[25].func = (long_func_wave_lookup *)wave_get_s;
		(*keywordfunctab)[26].text = "mg";			(*keywordfunctab)[26].func = (long_func_wave_lookup *)wave_get_mg;
		(*keywordfunctab)[27].text = "o";			(*keywordfunctab)[27].func = (long_func_wave_lookup *)wave_get_o;
										/* display/rendering attributes */
		(*keywordfunctab)[28].text = "bevel";		(*keywordfunctab)[28].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[29].text = "c_interp";	(*keywordfunctab)[29].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[30].text = "d_interp";	(*keywordfunctab)[30].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[31].text = "lod";			(*keywordfunctab)[31].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[32].text = "usemtl";		(*keywordfunctab)[32].func = (long_func_wave_lookup *)wave_read_usemtl;
		(*keywordfunctab)[33].text = "mtllib";		(*keywordfunctab)[33].func = (long_func_wave_lookup *)wave_read_mtllib;
		(*keywordfunctab)[34].text = "shadow_obj";	(*keywordfunctab)[34].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[35].text = "trace_obj";	(*keywordfunctab)[35].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[36].text = "ctech";		(*keywordfunctab)[36].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[37].text = "stech";		(*keywordfunctab)[37].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[38].text = "usemap";		(*keywordfunctab)[38].func = (long_func_wave_lookup *)wave_skip_line;
		(*keywordfunctab)[39].text = "maplib";		(*keywordfunctab)[39].func = (long_func_wave_lookup *)wave_skip_line;
										/* end of table */
		(*keywordfunctab)[40].text = NULL;			(*keywordfunctab)[40].func = NULL;
	}
	else
	{
		err = _fzrt_base_funcs.fzrt_error_set_with_detail(FZRT_MALLOC_ERROR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_FZRT, 0, 1116);
	}

	return err;
}

/************************************************************************
	Returns a pointer to a handler function for the given WaveFront
	keyword, or NULL if the keyword was not found.
*************************************************************************/
static long_func_wave_lookup *wave_lookup_table(wave_table_entry_td	*keywordfunctab, char *str)
{
	wave_table_entry_td		*p;
	long_func_wave_lookup	*rv = NULL;

	for (p = keywordfunctab; p->text != NULL; ++p)
	{
		if (!strcmp(str, p->text))
		{
			rv = p->func;
		}
	}

	return rv;
}


/************************************************************************
	Initialize the read working data.
*************************************************************************/
static fzrt_error_td wave_read_begin(
	fzrt_int				windex,
	fzrt_floc_ptr		floc,
	fz_ffmt_ref_td		ffmt_id,
	wave_in_td			**wave_ptr)
/************************************************************************/
{
	fzrt_error_td					err = FZRT_NOERR;
	fzrt_int							steps;
	wave_in_td						*wave = NULL;
	fz_type_td						fz_type;
	fzrt_int							in_opts_flags;
	char							fmat[256];
	char							prmt_str[256];
	char							tmp_str[256];

	/* Get the common import options. */
	/* Get the import options bit flags. */
	_ffmt_3d_funcs.fz_ffmt_data_model_read_opts_parm_get(ffmt_id, FZ_FFMT_DATA_MODEL_READ_OPTS_PARM_FLAGS, &fz_type);
	_type_funcs.fz_type_get_int(&fz_type, &in_opts_flags);

	/* Allocate working data. */
	if (_fzrt_mem_funcs.fzrt_new_ptr_clear((fzrt_size)sizeof(wave_in_td), (fzrt_ptr*)wave_ptr) == FZRT_NOERR)
	{
		wave = *wave_ptr;

		/* Initialize working data. */
		wave->windex = windex;
		_fzrt_file_funcs.fzrt_file_floc_get_name(floc, tmp_str, 256);
		_fzrt_file_funcs.fzrt_file_cnvt_name(tmp_str, wave->fname, 256);
		_fzrt_file_funcs.fzrt_file_floc_init(&wave->floc);
		_fzrt_file_funcs.fzrt_file_floc_copy(floc, wave->floc);
		wave->fp = NULL;
		wave->token[0] = '\0';
		wave->ffmt_id = ffmt_id;
		wave->message[0] = 0;
		wave->pos = 0L;
		wave->max_pos = 0L;
		wave->lookup_table = NULL;
		wave->group_sign = ((_read_flags & WAVE_READ_USE_SIGNG_FLAG)?WAVE_SIGN_IS_G:WAVE_SIGN_IS_O);
		wave->vertices = NULL;
		wave->s_vertices = 0;
		wave->n_vertices = 0;
		wave->vertices_links = NULL;
		wave->obj_name[0] = '\0';
		wave->obj = NULL;
		wave->ncord = 0;
		wave->rational = 0;
		wave->cstype = 0;
		wave->begin_curvsurf = 0;
		wave->u_degree = -1;
		wave->v_degree = -1;
		wave->n_u_knots = 0;
		wave->s_u_knots = 0;
		wave->n_v_knots = 0;
		wave->s_v_knots = 0;
		wave->n_control_geom = 0;
		wave->s_control_geom = 0;
		wave->control_geom = NULL;
		wave->u_knot_vec = NULL;
		wave->v_knot_vec = NULL;

		wave->n_segt = 0;
		wave->s_segt = 0;
		wave->segt = NULL;
		wave->n_faces = 0;
		wave->s_faces = 0;
		wave->faces = NULL;
		wave->facemltab = NULL;

		wave->seglinktab = NULL;
		wave->facemarktab = NULL;
		wave->uvtab = NULL;
		wave->mtllinktab = NULL;
		_type_funcs.fz_tag_set_NULL(&wave->stag);

		wave->do_points = FZ_CHKBIT(in_opts_flags, FZ_FFMT_DATA_MODEL_READ_OPTS_SKIP_POINTS_BIT) ? FALSE : TRUE;
		wave->do_smooth = FZ_CHKBIT(in_opts_flags, FZ_FFMT_DATA_MODEL_READ_OPTS_PRESERVECONTROLS_BIT) ? TRUE : FALSE;

		/* Initialize the token/function lookup table. */
		if ((err = wave_keywordfunctab_begin(&wave->lookup_table)) == FZRT_NOERR)
		{
			/* Check the import options bit flags to see if textures should be imported. */
			wave->do_tmaps = ((FZ_CHKBIT(in_opts_flags, FZ_FFMT_DATA_MODEL_READ_OPTS_TEXTUREMAPS_BIT))?1:0);
			/* Initialize material lookup tables. */
			if ((err = wave_mtllinktab_begin(&wave->mtllinktab)) == FZRT_NOERR)
			{
				if ((err = wave_uvtab_begin(&wave->uvtab)) == FZRT_NOERR)
				{
					if ((err = wave_seglinktab_begin(&wave->seglinktab)) == FZRT_NOERR)
					{
						if ((err = wave_facemltab_begin(&wave->facemltab)) == FZRT_NOERR)
						{
							err = wave_facemarktab_begin(&wave->facemarktab);
						}
					}
				}
			}
		}

		if (err == FZRT_NOERR)
		{
			/* Open the file to import. */
			_fzrt_file_funcs.fzrt_file_floc_set_crnt(floc);
			if ((wave->fp = fopen(wave->fname, "rb")) != NULL)
			{
				if (!setvbuf(wave->fp, NULL, _IOFBF, 0x00008000L))
				{						
					/* Set the wait cursor. */
					_fuim_funcs.fz_fuim_curs_wait(FZ_FUIM_CURS_WAIT_START);
					
					/* Seek to the end of the file and get the file size. */
					fseek(wave->fp,0,SEEK_END);
					wave->max_pos = ftell(wave->fp);
					wave->pos = 0;
					rewind(wave->fp);
					
					/* Compute the number of steps to display on the progress bar. */
					steps = 1;
					if ((FZ_CHKBIT(in_opts_flags, FZ_FFMT_DATA_MODEL_READ_OPTS_MERGESOLIDS_BIT))?1:0) ++steps;
					if ((FZ_CHKBIT(in_opts_flags, FZ_FFMT_DATA_MODEL_READ_OPTS_REMOVECOPLANAR_BIT))?1:0) ++steps;

					/* Initialize the progress bar. */
					_fzrt_base_funcs.fzrt_fzr_get_string(_fz_rsrc_ref, WAVE_PROG_MSGS, OPENING_WAVE_FILE, fmat);
					sprintf(wave->message, fmat, tmp_str);
					_fuim_funcs.fz_fuim_prog_init((short)steps, FZ_FUIM_PROG_KIND_NORMAL, TRUE);
					_fuim_funcs.fz_fuim_prog_set_delay(0L);
					_fuim_funcs.fz_fuim_prog_stage_init(wave->message, 0L, wave->max_pos);
					_obj_prog_update_incr = 1;
					if(wave->max_pos > 500)
					{
						_obj_prog_update_incr = 10;
					}
					if(wave->max_pos > 2000)
					{
						_obj_prog_update_incr = 100;
					}
					if(wave->max_pos > 10000)
					{
						_obj_prog_update_incr = 1000; 
					}
					if(wave->max_pos > 50000)
					{
						_obj_prog_update_incr = 5000; 
					}
					sprintf(prmt_str,"%ld\0",wave->pos);
					_fzrt_base_funcs.fzrt_fzr_get_string(_fz_rsrc_ref, WAVE_PROG_MSGS, NUM_BYTES_READ, wave->message);
					_fuim_funcs.fz_fuim_prog_stage_set_strings(wave->message, prmt_str);
					_fuim_funcs.fz_fuim_prog_stage_set_current(wave->pos);
				}
				else
				{
					err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1117);
				}
			}
			else
			{
				err = _fzrt_base_funcs.fzrt_error_set_with_detail(errno, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_ANSI, 0, 1118);
			}
		}
	}

	return err;
}

/************************************************************************
	Uninitialize the read working data.
*************************************************************************/
static void wave_read_end(wave_in_td **wave_ptr, fzrt_int err)
/************************************************************************/
{
	wave_in_td						*wave = NULL;

	if (*wave_ptr != NULL && *wave_ptr != NULL)
	{
		wave = *wave_ptr;

		/* Close the file. */
		if (wave->fp != NULL)
		{
			fclose(wave->fp);
			wave->fp = NULL;
		}

		/* Cleanup/complete the imported objects. */
		err = _ffmt_3d_funcs.fz_ffmt_data_model_read_finish(wave->windex, wave->ffmt_id, err, NULL, NULL);

		/* Terminate the progress bar. */
		_fuim_funcs.fz_fuim_prog_stage_finit();
		_fuim_funcs.fz_fuim_prog_finit();

		/* Free allocated memory. */
		if (wave->lookup_table != NULL)
		{
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->lookup_table);
			wave->lookup_table = NULL;
		}
		if (wave->vertices != NULL)
		{
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->vertices);
			wave->vertices = NULL;
		}
		if (wave->vertices_links != NULL)
		{
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->vertices_links);
			wave->vertices_links = NULL;
		}
		if (wave->control_geom != NULL)
		{
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->control_geom);
			wave->control_geom = NULL;
		}
		if (wave->u_knot_vec != NULL)
		{
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->u_knot_vec);
			wave->u_knot_vec = NULL;
		}
		if (wave->v_knot_vec != NULL)
		{
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->v_knot_vec);
			wave->v_knot_vec = NULL;
		}

		/* Cleanup material data and tables. */
		if (wave->segt != NULL)
		{
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->segt);
			wave->segt = NULL;
		}
		if (wave->faces != NULL)
		{
			_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave->faces);
			wave->faces = NULL;
		}
		wave_facemltab_end(&wave->facemltab);
		wave_seglinktab_end(&wave->seglinktab);
		wave_facemarktab_end(&wave->facemarktab);
		wave_uvtab_end(&wave->uvtab);
		wave_mtllinktab_end(&wave->mtllinktab);

		/* Free the working data. */
		_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)wave);
		wave = *wave_ptr = NULL;
	}

	return;
}

/************************************************************************
	wave_read: main routine to import Wavefront's OBJ
	Loop unitl the all the file is read, the user cancels, or an error occurrs.
	Tokens are read from the file.  Each Token is mapped to a function
	which process that token.
*************************************************************************/
fzrt_error_td wave_read(fzrt_int windex, fz_ffmt_ref_td ffmt_id, fzrt_floc_ptr floc)
{
	fzrt_error_td			err = FZRT_NOERR;
	fzrt_int					len = 0L;
	fzrt_int					i;
	char					c = EOF;
	long_func_wave_lookup	*func = NULL;
	wave_in_td				*wave = NULL;
	FILE					*fp = NULL;
	fzrt_int					err_code;
	fzrt_error_context_enum	err_cont;
	fzrt_int					cont_id;
	char					fmat[256], err_str[256];
	char					fname[256];
	char					prmt_str[256];
	fzrt_boolean			done = FALSE;
	fzrt_int					loop_cnt = 0;

//_fzrt_base_funcs.fzrt_error_set_alrt_on_error(TRUE);

	/* Initialize the read working data. */
	if ((err = wave_read_begin(windex, floc, ffmt_id, &wave)) == FZRT_NOERR)
	{
		fp = wave->fp;

		/* Read data loop. */
		do {
			/* Update the progress bar. */
			wave->pos = ftell(fp);
			sprintf(prmt_str,"%ld\0",wave->pos);
			if(loop_cnt % _obj_prog_update_incr == 0)
			{
				if(loop_cnt % 10 == 0)	_fuim_funcs.fz_fuim_prog_stage_set_strings(NULL, prmt_str);
				_fuim_funcs.fz_fuim_prog_stage_set_current(wave->pos);
			}
			
			/* Update the wait cursor. */
			_fuim_funcs.fz_fuim_curs_wait(FZ_FUIM_CURS_WAIT_TURN);
			
			/* Check for a cancel from the user. */
			if (_fuim_funcs.fz_fuim_key_cancel())
			{
				err = FZ_USER_CANCEL;
				done = TRUE;
			}

			if(!done)
			{
				/* Skip any blank lines. */
				wave->token[0] = '\0';
				if ((err = wave_skip_blank(wave, fp)) != FZRT_NOERR)
				{
					done = TRUE;
				}
				if ((c = fgetc(fp)) == EOF)
				{
					done = TRUE;
				}
				else if ((c == '\n') || (c == '\r')) continue;
				else ungetc(c, fp);
				if(!done)
				{
					i = 0;
				
					/* Get the next token. */
					while (((c = fgetc(fp)) != EOF) && (c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
					{
						wave->token[i++] = c;
					}
					wave->token[i] = '\0';
					ungetc(c, fp);

					/* Skip comments */
					if ((wave->token[0] == '!') || (wave->token[0] == '#'))
					{
						if ((err = wave_skip_line(wave, wave->fp)) != FZRT_NOERR) done = TRUE;
					}
					else
					{
						/* map token to lower case. */
						len = strlen(wave->token);
						for (i = 0; i < len; i++)
						{
							wave->token[i] = tolower(wave->token[i]);
						}
						
						/* Get the tokens processing function. */
						if ((func = wave_lookup_table(wave->lookup_table, wave->token)) == NULL)
						{
//							if(wave->token[0] != '\0')	err = _fzrt_base_funcs.fzrt_error_set_with_detail(WAVE_UNKNOWN_KEYWORD_ERR, FZRT_ERROR_SEVERITY_ERROR, FZRT_ERROR_CONTEXT_PLUGIN, _wave_plugin_runtime_id, 1120);
//							done = TRUE;
						}
						
						/* process the token. */
						if ((func != NULL) && ((err = func(wave, wave->fp)) != FZRT_NOERR)) done = TRUE;
					}
				}
				loop_cnt++;
			}
		} while (!done);

		if (err == FZRT_NOERR)
		{
			/* Finish off the last imported object. */
			err = wave_end_object(wave);
			
			/* Don't report null object error's to the user. */
			_fzrt_base_funcs.fzrt_error_get_info(err, NULL, 0, &err_code, NULL, &err_cont, &cont_id, NULL, 0, NULL);
			if((err_code == WAVE_NULL_OBJECT_ERR) &&
			   (err_cont == FZRT_ERROR_CONTEXT_PLUGIN) &&
			   (cont_id == _wave_plugin_runtime_id))	
			{
				err = FZRT_NOERR;
			}
		}
	}

	/* Uninitialize the read working data. */
	wave_read_end(&wave, err);

//_fzrt_base_funcs.fzrt_error_set_alrt_on_error(FALSE);

	/* If an error occurred, display it to the user. */
	if ( err != FZRT_NOERR && err != FZ_USER_CANCEL )
	{
		_fzrt_base_funcs.fzrt_fzr_get_string(_fz_rsrc_ref, WAVE_STRINGS, WAVE_READ_ERR_STR, fmat);
		_fzrt_file_funcs.fzrt_file_floc_get_name(floc, fname, 256);
		sprintf(err_str, fmat, fname);
		_fuim_funcs.fz_fuim_alrt_std_error(err, WAVE_READ_ERR_ID, err_str);
	}

	return (err);
}

/************************************************************************
	Determine whether an ASCII file "could" be a Wavefront file.
	This function will parse a file up to the first valid Wavefront keyword.
	As soon as an improper sequence is deteced or EOF is reached an error
	is raised.
	Otherwise TRUE is returned when any Wavefront keyword is detected.
	Once a file is characterized as Wavefront (which still may be incorrect),
	it is the import translator's responsibility to guarantee the integrity
	of the rest of the file.
	The method used here has good chances to succeed. However there is no secure
	method to identify a Wavefront file because:
		a: there is no header in the file and
		b: wavefront keywords are so short, it is quite possible to parse a valid
		   first keyword in a non Wavefront file).
*************************************************************************/
fzrt_boolean wave_is_wave(fzrt_floc_ptr floc, const fzrt_ptr buff, fzrt_int buff_size)
/************************************************************************/
{
	fzrt_error_td			err = FZRT_NOERR;
	fzrt_int					c;
	fzrt_boolean			is = FALSE;
	fzrt_boolean			done, done2, comment;
	fzrt_int					i;
	char					token[256];
	wave_table_entry_td		*keywordfunctab = NULL;
	FILE					*fp;
	char					fname[256];
	char					tmp_str[256];

	/* Get the file name from the floc and open the file. */
	_fzrt_file_funcs.fzrt_file_floc_get_name(floc, tmp_str, 256);
	/* Need to convert this file name to a character set which works with ansi file functions. */
	_fzrt_file_funcs.fzrt_file_cnvt_name(tmp_str, fname, 256);
	if((fp = fopen(fname, "r")) != NULL)
	{
		done = FALSE;
		comment = FALSE;
		token[0] = 0x00;
		/* Search the file for a valid keyword. */
		while (!done)
		{
 			if (feof(fp))
 			{
 				done = TRUE;
 			}
 			else if (ferror(fp))
 			{
 				done = TRUE;
 			}
 			else
 			{
 				c = fgetc(fp);
				switch (c)
				{
					case 0x09:
					case 0x20:
					break;

					case '#':
					case '!':
						comment = TRUE;
					break;

					case 0x0d:
					case 0x0a:
						comment = FALSE;
					break;

					default:
						if ((c < 0x20) || (c > 0x7f)) done = 1;
						else if (!comment && isalpha(c))
						{
							i = 0;
							token[i++] = c;
							done2 = FALSE;
							while (!done2)
							{
 								if (i > 255)
 								{
 									done2 = TRUE;
 								}
 								else if (feof(fp))
 								{
 									done2 = TRUE;
 								}
 								else if (ferror(fp))
 								{
 									done2 = TRUE;
 								}
								else
								{
									c = fgetc(fp);
									if (isalnum(c) || (c == '_'))
									{
										token[i++] = c;
									}
									else done2 = TRUE;
								}
							}
							token[i++] = 0x00;
							if ((err = wave_keywordfunctab_begin(&keywordfunctab)) == FZRT_NOERR)
							{
								if (wave_lookup_table(keywordfunctab, token) != NULL)
								{
									is = TRUE;
									done = TRUE;
								}
								else done = TRUE;
								
								if (keywordfunctab)
								{
									_fzrt_mem_funcs.fzrt_dispose_ptr((fzrt_ptr)keywordfunctab);
								}
							}
							else done = TRUE;
						}
						else if (!comment)
						{
							done = TRUE;
						}
					break;
				}
			}
		}
		fclose(fp);
	}

	return is;
}
