home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Virtual Reality Zone
/
VRZONE.ISO
/
mac
/
PC
/
VDF
/
VDF.ZIP
/
VDFREAD.C
< prev
next >
Wrap
C/C++ Source or Header
|
1994-11-21
|
43KB
|
1,418 lines
/* Parser for VDF -- the Virtual world Description Format */
/*
* This implementation written by:
* Bernie Roehl (broehl@uwaterloo.ca)
* November, 1994
* Redistribute at will.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include "vdf.h"
#define color_zero(v) ((v)->red = (v)->green = (v)->blue = 0.0)
#define vector_zero(v) ((v)->x = (v)->y = (v)->z = 0.0)
typedef enum
{
VDF_TAG_IGNORE = 0, VDF_TAG_UNKNOWN,
VDF_TAG_LEFT_BRACKET, VDF_TAG_RIGHT_BRACKET, VDF_TAG_STRING,
VDF_TAG_INCLUDE, VDF_TAG_COUNT, VDF_TAG_IDENTIFIER, VDF_TAG_NAME,
VDF_TAG_APPLICATION_HANDLE, VDF_TAG_FILENAME, VDF_TAG_COLOR,
VDF_TAG_NORMAL3D, VDF_TAG_INDEX,
VDF_TAG_PALETTE, VDF_TAG_COLOR_TABLE, VDF_TAG_HUE_TABLE,
VDF_TAG_COLOR_ENTRY, VDF_TAG_HUE_ENTRY,
VDF_TAG_MAP,
VDF_TAG_MATERIAL, VDF_TAG_RENDERING_MODE, VDF_TAG_HUE, VDF_TAG_ALBEDO,
VDF_TAG_DIFFUSE_COLOR, VDF_TAG_AMBIENT_COLOR, VDF_TAG_TRANSPARENCY_COLOR,
VDF_TAG_SPECULAR_COLOR, VDF_TAG_SPECULAR_EXPONENT,
VDF_TAG_REFRACTIVE_INDEX, VDF_TAG_TEXTURE_MAP, VDF_TAG_BUMP_MAP,
VDF_TAG_OPACITY_MAP, VDF_TAG_REFLECTION_MAP, VDF_TAG_REFLECTION_BLUR,
VDF_TAG_TRANSPARENCY_FALLOFF,
VDF_TAG_MATERIAL_TABLE, VDF_TAG_MATERIAL_REFERENCE,
VDF_TAG_SHAPE, VDF_TAG_LOD_SIZE, VDF_TAG_LOD_REPLACES, VDF_TAG_IS_CONVEX,
VDF_TAG_BOUNDING_BOX, VDF_TAG_USES_MATERIAL_TABLE, VDF_TAG_VERTEX_LIST,
VDF_TAG_FACET_LIST, VDF_TAG_FACET, VDF_TAG_VERTEX, VDF_TAG_POINT3D,
VDF_TAG_VERTEX_DATA, VDF_TAG_VERTEX_INFO, VDF_TAG_IS_DOUBLESIDED,
VDF_TAG_IS_INTERIOR, VDF_TAG_BASE_FACET,
VDF_TAG_FRONT_MATERIAL, VDF_TAG_BACK_MATERIAL,
VDF_TAG_OBJECT, VDF_TAG_INSTANCE_OF_SHAPE, VDF_TAG_SCALED_BY,
VDF_TAG_LOCATION, VDF_TAG_ROTATION,
VDF_TAG_ATTACHED_TO, VDF_TAG_CONTAINED_WITHIN, VDF_TAG_IS_INVISIBLE,
VDF_TAG_LAYER, VDF_TAG_TEXT, VDF_TAG_FACET_BEHIND,
VDF_TAG_CAMERA, VDF_TAG_FIELD_OF_VIEW, VDF_TAG_ASPECT_RATIO,
VDF_TAG_ASSOCIATED_WITH, VDF_TAG_PROJECTION_TYPE,
VDF_TAG_LIGHT, VDF_TAG_LIGHT_TYPE, VDF_TAG_HOTSPOT, VDF_TAG_FALLOFF,
VDF_TAG_IS_ON, VDF_TAG_CASTS_SHADOWS,
VDF_TAG_SOUND, VDF_TAG_VOLUME, VDF_TAG_SAMPLE_NAME,
VDF_TAG_WORLD_ATTRIBUTES, VDF_TAG_GRAVITY_VECTOR, VDF_TAG_AMBIENT_LIGHT,
VDF_TAG_HAS_HORIZON, VDF_TAG_SKY_COLOR, VDF_TAG_GROUND_COLOR,
VDF_TAG_FOG_COLOR, VDF_TAG_SCALE,
VDF_TAG_WORLD_INFO, VDF_TAG_CREATED_BY, VDF_TAG_MODIFIED_BY,
VDF_TAG_DATE, VDF_TAG_PERSON, VDF_TAG_COPYRIGHT_MESSAGE,
VDF_TAG_USAGE_RESTRICTIONS, VDF_TAG_COMMENT, VDF_TAG_TITLE
} VDF_TAG;
static char *tagnames[] =
{
"", "", "{", "}", "",
"INCLUDE", "COUNT", "IDENTIFIER", "NAME",
"APPLICATION_HANDLE", "FILENAME", "COLOR",
"NORMAL3D", "INDEX",
"PALETTE", "COLOR_TABLE", "HUE_TABLE",
"COLOR_ENTRY", "HUE_ENTRY",
"MAP",
"MATERIAL", "RENDERING_MODE", "HUE", "ALBEDO",
"DIFFUSE_COLOR", "AMBIENT_COLOR", "TRANSPARENCY_COLOR",
"SPECULAR_COLOR", "SPECULAR_EXPONENT",
"REFRACTIVE_INDEX", "TEXTURE_MAP", "BUMP_MAP",
"OPACITY_MAP", "REFLECTION_MAP", "REFLECTION_BLUR",
"TRANSPARENCY_FALLOFF",
"MATERIAL_TABLE", "MATERIAL_REFERENCE",
"SHAPE", "LOD_SIZE", "LOD_REPLACES", "IS_CONVEX",
"BOUNDING_BOX", "USES_MATERIAL_TABLE", "VERTEX_LIST",
"FACET_LIST", "FACET", "VERTEX", "POINT3D",
"VERTEX_DATA", "VERTEX_INFO", "IS_DOUBLESIDED",
"IS_INTERIOR", "BASE_FACET",
"FRONT_MATERIAL", "BACK_MATERIAL",
"OBJECT", "INSTANCE_OF_SHAPE", "SCALED_BY",
"LOCATION", "ROTATION",
"ATTACHED_TO", "CONTAINED_WITHIN", "IS_INVISIBLE",
"LAYER", "TEXT", "FACET_BEHIND",
"CAMERA", "FIELD_OF_VIEW", "ASPECT_RATIO",
"ASSOCIATED_WITH", "PROJECTION_TYPE",
"LIGHT", "TYPE", "HOTSPOT", "FALLOFF",
"IS_ON", "CASTS_SHADOWS",
"SOUND", "VOLUME", "SAMPLE_NAME",
"WORLD_ATTRIBUTES", "GRAVITY_VECTOR", "AMBIENT_LIGHT",
"HAS_HORIZON", "SKY_COLOR", "GROUND_COLOR",
"FOG_COLOR", "SCALE",
"WORLD_INFORMATION", "CREATED_BY", "MODIFIED_BY",
"DATE", "PERSON", "COPYRIGHT_MESSAGE",
"USAGE_RESTRICTIONS", "COMMENT", "TITLE",
NULL
};
#define START_STRING -2 /* returned for opening double-quotes */
#define END_STRING -3 /* returned for closing double-quotes */
static char *earlyEOF = "encountered unexpected EOF";
/* these "end" variables point to the last entry in each of the singly-linked
lists; that way new items can be added onto the end of the list easily */
static VDF_PALETTE *end_palettes;
static VDF_MAP *end_maps;
static VDF_MATERIAL *end_materials;
static VDF_MATERIAL_TABLE *end_material_tables;
static VDF_SHAPE *end_shapes;
static VDF_OBJECT *end_objects;
static VDF_CAMERA *end_cameras;
static VDF_LIGHT *end_lights;
static VDF_SOUND *end_sounds;
static VDF_WORLD_ATTRIBUTES *end_world_attributes;
static char *render_mode_names[] =
{
"WIREFRAME", "UNLIT", "FLAT", "GOURAUD", "PHONG", NULL
};
static char *light_type_names[] =
{
"DIRECTIONAL", "POINT", "SPOT", NULL
};
static char *projection_type_names[] =
{
"PERSPECTIVE", "PARALLEL", NULL
};
/* Open include files are kept in a stack (implemented as a linked list) */
typedef struct _file_info FILEINFO;
struct _file_info
{
char *name; /* name of file at top of inclusion stack */
FILE *file; /* pointer to open file */
int line; /* current line in file at top of inclusion stack */
FILEINFO *next; /* stack is a linked list */
};
FILEINFO *filestack = NULL;
/**** error-handling routines ****/
static jmp_buf error_jmp;
static char emsg[500];
char *vdf_get_read_error(void)
{
return emsg;
}
static void error_abort(char *str)
{
strcpy(emsg, str);
longjmp(error_jmp, 1);
}
static void syntax(char *str)
{
if (filestack)
sprintf(emsg, "Error encountered on line %d of file '%s':\n\t%s\n",
filestack->line, filestack->name, str);
else
sprintf(emsg, "Early EOF detected!\n");
longjmp(error_jmp, 1);
}
/**** error-handling versions of various routines ****/
static void *emalloc(unsigned int nbytes)
{
void *ptr = vdf_malloc(nbytes);
if (ptr) return ptr;
error_abort("out of memory");
return NULL; /* should never get here */
}
static char *estrdup(char *string)
{
char *ptr = emalloc(strlen(string)+1);
strcpy(ptr, string);
return ptr;
}
/**** list-handling routines ****/
/* used to map an identifier into a pointer to something */
typedef struct _listnode LISTNODE;
struct _listnode
{
VDF_IDENTIFIER id;
void *ptr;
LISTNODE *next;
};
static void add_to_list(LISTNODE **list, VDF_IDENTIFIER id, void *ptr)
{
LISTNODE *p = emalloc(sizeof(LISTNODE));
p->id = id; p->ptr = ptr;
p->next = *list; *list = p;
}
static void *find_on_list(LISTNODE *list, VDF_IDENTIFIER id)
{
while (list)
if (list->id == id) return list->ptr;
else list = list->next;
return NULL;
}
/**** file-handling routines ****/
static void open_file(char *filename)
{
FILEINFO *inf = emalloc(sizeof(FILEINFO));
inf->file = fopen(filename, "r");
if (inf->file == NULL)
{
char buff[200];
sprintf(buff, "could not open '%s'", filename);
error_abort(buff);
}
inf->name = estrdup(filename);
inf->line = 0;
inf->next = filestack; filestack = inf;
}
static int check_filestack_empty = 1;
static void close_file(void)
{
FILEINFO *f = filestack;
if (filestack == NULL) return;
if (filestack->file) fclose(filestack->file);
if (filestack->name) vdf_free(filestack->name);
filestack = filestack->next;
vdf_free(f);
if (check_filestack_empty)
if (filestack == NULL)
syntax(earlyEOF);
}
static void close_all(void)
{
check_filestack_empty = 0;
while (filestack)
close_file();
check_filestack_empty = 1;
}
static int egetc(void)
{
static int hold_char = 0, instring = 0;
int c;
if (hold_char)
{
c = hold_char;
hold_char = 0;
}
else
c = getc(filestack->file);
switch (c)
{
case '\n': ++filestack->line;
case '\t':
case ' ': return ' ';
case EOF: return EOF;
case '"':
if (instring)
{
instring = 0;
return END_STRING;
}
instring = 1;
return START_STRING;
case '\\':
switch (c = getc(filestack->file))
{
case '\n': ++filestack->line; break;
case 'n': return '\n';
case 'r': return '\r';
case 'b': return '\b';
case 't': return '\t';
default: break;
}
return c;
case '/':
c = getc(filestack->file);
if (c == '/')
{
do c = getc(filestack->file); while (c != EOF && c != '\n');
if (c == EOF) return EOF;
++filestack->line;
return ' ';
}
hold_char = c;
return '/';
default:
if (!isprint(c))
syntax("non-printable character");
break;
}
return c;
}
static VDF_TAG lookup_tag(char *name)
{
int i;
for (i = 0; tagnames[i]; ++i)
if (!stricmp(name, tagnames[i]))
return i;
return 0;
}
/* the need_left_bracket() and need_right_bracket() are called when we know
that a particular kind of bracket is the only acceptable next token */
static char nb_buff[1000];
static void need_left_bracket(void)
{
VDF_TAG get_token(char *buff, int len);
if (get_token(nb_buff, sizeof(nb_buff)) != VDF_TAG_LEFT_BRACKET)
syntax("missing {");
}
static void need_right_bracket(void)
{
VDF_TAG get_token(char *buff, int len);
if (get_token(nb_buff, sizeof(nb_buff)) != VDF_TAG_RIGHT_BRACKET)
syntax("missing }");
}
/* pulls a token from the input stream, storing it in the buffer and
returning a VDF_TAG identifying which tag (if any) was encountered */
static VDF_TAG get_token(char *buff, int len)
{
char *p = buff;
int c;
VDF_TAG tag;
--len; /* allow for null byte at end */
do
{
switch (c = egetc())
{
case '{': return VDF_TAG_LEFT_BRACKET;
case '}': return VDF_TAG_RIGHT_BRACKET;
case START_STRING: break;
default: break;
}
} while (isspace(c));
if (c == START_STRING) /* start of a doubly-quoted string */
{
c = egetc();
while (c != END_STRING && len > 0)
{
if (c == EOF) return EOF;
*p++ = c;
--len;
c = egetc();
}
*p = '\0';
return VDF_TAG_STRING;
}
while (!isspace(c) && len > 0) /* copy characters until whitespace */
{
if (c == EOF) return EOF;
*p++ = c;
--len;
c = egetc();
}
*p = '\0';
tag = lookup_tag(buff); /* find the tag */
if (tag == VDF_TAG_INCLUDE) /* process included files */
{
char filename[100];
need_left_bracket();
get_token(filename, sizeof(filename));
need_right_bracket();
open_file(filename);
return VDF_TAG_IGNORE;
}
return tag;
}
/* skip_parms() skips a leading {, a bunch of stuff, and a closing } */
static char skipbuff[1000];
static void skip_parms(void)
{
int nbrackets = 1;
need_left_bracket();
while (nbrackets > 0)
switch (get_token(skipbuff, sizeof(skipbuff)))
{
case EOF: close_file(); break;
case VDF_TAG_LEFT_BRACKET: ++nbrackets; break;
case VDF_TAG_RIGHT_BRACKET: --nbrackets; break;
default: break;
}
}
/**** higher-level input functions ****/
/* these each pull a known data type from the input stream */
static void get_real(VDF_REAL *p)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
need_right_bracket();
sscanf(buff, "%f", p);
}
static void get_angle(VDF_ANGLE *p)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
need_right_bracket();
sscanf(buff, "%f", p);
}
static void get_boolean(VDF_BOOLEAN *p)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
need_right_bracket();
if (!stricmp(buff, "TRUE")) *p = 1;
else if (!stricmp(buff, "FALSE")) *p = 0;
else syntax("value must be TRUE or FALSE");
}
static void get_count(VDF_COUNT *p)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
need_right_bracket();
*p = strtoul(buff, NULL, 0);
}
static void get_index(VDF_INDEX *p)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
need_right_bracket();
*p = strtoul(buff, NULL, 0);
}
static void get_handle(VDF_HANDLE *p)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
need_right_bracket();
*p = strtoul(buff, NULL, 0);
}
static void get_identifier(VDF_IDENTIFIER *p)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
need_right_bracket();
*p = strtoul(buff, NULL, 0);
}
static void get_color(VDF_COLOR *p)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
sscanf(buff, "%f", &p->red);
get_token(buff, sizeof(buff));
sscanf(buff, "%f", &p->green);
get_token(buff, sizeof(buff));
sscanf(buff, "%f", &p->blue);
need_right_bracket();
}
static void get_vector(VDF_VECTOR *p)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
sscanf(buff, "%f", &p->x);
get_token(buff, sizeof(buff));
sscanf(buff, "%f", &p->y);
get_token(buff, sizeof(buff));
sscanf(buff, "%f", &p->z);
need_right_bracket();
}
void get_string(char **str)
{
char buff[100];
need_left_bracket();
get_token(buff, sizeof(buff));
need_right_bracket();
*str = estrdup(buff);
}
static int get_enum(char *values[])
{
char buff[100];
int i;
need_left_bracket();
get_token(buff, sizeof(buff));
need_right_bracket();
for (i = 0; values[i]; ++i)
if (!stricmp(buff, values[i])) return i;
syntax("unrecognized enumerated value");
return 0;
}
/**** file processing ****/
static char tmpbuff[1000]; /* re-used repeatedly */
static LISTNODE
*object_list = NULL, *material_list = NULL, *material_tables_list = NULL,
*shape_list = NULL, *facet_list = NULL;
static VDF_DATA *world_ptr = NULL;
static char junkbuff[1000]; /* used to load tokens we don't care about */
/* this next routine gets called whenever we encounter a Color_table tag */
static void process_color_table(VDF_PALETTE *palette)
{
VDF_COUNT n_entries = 0;
VDF_TAG tag;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_COUNT:
get_count(&palette->ncolors);
palette->colors = emalloc(palette->ncolors * sizeof(VDF_COLOR));
n_entries = 0;
break;
case VDF_TAG_COLOR_ENTRY:
if (palette->ncolors == 0)
{
palette->ncolors = 1;
palette->colors = emalloc(sizeof(VDF_COLOR));
n_entries = 0;
}
if (n_entries < palette->ncolors)
get_color(&palette->colors[n_entries++]);
else
skip_parms();
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Hue_table tag */
static void process_hue_table(VDF_PALETTE *palette)
{
VDF_COUNT n_entries = 0;
VDF_TAG tag;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_COUNT:
get_count(&palette->nhues);
palette->hues = emalloc(palette->nhues * sizeof(VDF_HUE_DESCRIPTOR));
n_entries = 0;
break;
case VDF_TAG_HUE_ENTRY:
if (palette->nhues == 0)
{
palette->nhues = 1;
palette->hues = emalloc(sizeof(VDF_HUE_DESCRIPTOR));
n_entries = 0;
}
if (n_entries < palette->nhues)
{
char tbuff[100];
need_left_bracket();
get_token(tbuff, sizeof(tbuff));
palette->hues[n_entries].darkest = atof(tbuff);
get_token(tbuff, sizeof(tbuff));
need_right_bracket();
palette->hues[n_entries].lightest = atof(tbuff);
++n_entries;
}
else
skip_parms();
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Palette tag */
static void process_palette(void)
{
VDF_PALETTE *palette = emalloc(sizeof(VDF_PALETTE));
VDF_TAG tag;
palette->ncolors = 0; palette->colors = NULL;
palette->nhues = 0; palette->hues = NULL;
world_ptr->palette = palette;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_COLOR_TABLE: process_color_table(palette); break;
case VDF_TAG_HUE_TABLE: process_hue_table(palette); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Map tag */
static void process_map(void)
{
VDF_MAP *map = emalloc(sizeof(VDF_MAP));
VDF_TAG tag;
map->name = map->filename = NULL;
if (end_maps == NULL)
world_ptr->maps = end_maps = map;
else
end_maps->next = map;
end_maps = map; map->next = NULL;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_NAME: get_string(&map->name); break;
case VDF_TAG_FILENAME: get_string(&map->filename); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Texture tag */
static void get_tmap(char **name)
{
VDF_TAG tag;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_NAME: get_string(name); break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Material tag */
static void process_material(void)
{
VDF_MATERIAL *material = emalloc(sizeof(VDF_MATERIAL));
VDF_TAG tag;
material->name = NULL; material->rendering_mode = VDF_RENDER_FLAT;
material->hue = -1; material->specular_exponent = 0;
material->id = 0;
material->albedo = 0.5; /* reflects half the light that falls on it */
color_zero(&material->diffuse_color);
color_zero(&material->ambient_color);
color_zero(&material->transparency_color);
color_zero(&material->specular_color);
material->refractive_index = 0.00;
material->reflection_blur = 0.00;
material->transparency_falloff = 0.00;
material->texture_map = material->bump_map = NULL;
material->opacity_map = material->reflection_map = NULL;
if (end_materials == NULL)
world_ptr->materials = end_materials = material;
else
end_materials->next = material;
end_materials = material; material->next = NULL;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_NAME: get_string(&material->name); break;
case VDF_TAG_RENDERING_MODE: material->rendering_mode = get_enum(render_mode_names); break;
case VDF_TAG_HUE: get_index(&material->hue); break;
case VDF_TAG_DIFFUSE_COLOR: get_color(&material->diffuse_color); break;
case VDF_TAG_AMBIENT_COLOR: get_color(&material->ambient_color); break;
case VDF_TAG_TRANSPARENCY_COLOR: get_color(&material->transparency_color); break;
case VDF_TAG_SPECULAR_COLOR: get_color(&material->specular_color); break;
case VDF_TAG_SPECULAR_EXPONENT: get_real(&material->specular_exponent); break;
case VDF_TAG_REFRACTIVE_INDEX: get_real(&material->refractive_index); break;
case VDF_TAG_REFLECTION_BLUR: get_real(&material->reflection_blur); break;
case VDF_TAG_TRANSPARENCY_FALLOFF: get_real(&material->transparency_falloff); break;
case VDF_TAG_TEXTURE_MAP: get_tmap(&material->texture_map); break;
case VDF_TAG_BUMP_MAP: get_tmap(&material->bump_map); break;
case VDF_TAG_OPACITY_MAP: get_tmap(&material->opacity_map); break;
case VDF_TAG_REFLECTION_MAP: get_tmap(&material->reflection_map); break;
case VDF_TAG_ALBEDO: get_real(&material->albedo); break;
case VDF_TAG_IDENTIFIER:
get_identifier(&material->id);
add_to_list(&material_list, material->id, material);
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Material tag */
static void process_material_table(void)
{
VDF_MATERIAL_TABLE *table = emalloc(sizeof(VDF_MATERIAL_TABLE));
VDF_TAG tag;
VDF_COUNT n_entries = 0;
table->name = NULL; table->application_handle = 0;
table->count = 0; table->material_references = NULL;
table->n_used = 0; table->id = 0;
if (end_material_tables == NULL)
world_ptr->material_tables = end_material_tables = table;
else
end_material_tables->next = table;
end_material_tables = table; table->next = NULL;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_NAME: get_string(&table->name); break;
case VDF_TAG_APPLICATION_HANDLE: get_handle(&table->application_handle); break;
case VDF_TAG_COUNT:
get_count(&table->count);
if (table->count > 0)
{
table->material_references = calloc(table->count, sizeof(VDF_MATERIAL *));
n_entries = 0;
}
break;
case VDF_TAG_MATERIAL_REFERENCE:
if (table->count == 0)
{
table->count = 1;
table->material_references = emalloc(sizeof(VDF_MATERIAL *));
n_entries = 0;
}
if (n_entries < table->count)
{
VDF_IDENTIFIER id;
get_identifier(&id);
table->material_references[n_entries++] = find_on_list(material_list, id);
}
else
skip_parms();
break;
case VDF_TAG_IDENTIFIER:
get_identifier(&table->id);
add_to_list(&material_tables_list, table->id, table);
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Vertex tag */
static void process_vertex(VDF_VERTEX *vertex)
{
VDF_TAG tag;
vector_zero(&vertex->vector); vector_zero(&vertex->normal);
color_zero(&vertex->color);
vertex->application_handle = 0;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_POINT3D: get_vector(&vertex->vector); break;
case VDF_TAG_NORMAL3D: get_vector(&vertex->normal); break;
case VDF_TAG_COLOR: get_color(&vertex->color); break;
case VDF_TAG_APPLICATION_HANDLE: get_handle(&vertex->application_handle); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Vertex_info tag */
static void process_vertex_info(VDF_FACET *facet, VDF_INDEX ind)
{
VDF_TAG tag;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_INDEX: get_index(&facet->points[ind].vertnum); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Vertex_data tag */
static void process_facet_points(VDF_FACET *facet)
{
VDF_TAG tag;
VDF_COUNT n_entries = 0;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_COUNT:
get_count(&facet->npoints);
facet->points = emalloc(facet->npoints * sizeof(VDF_INDEX));
n_entries = 0;
break;
case VDF_TAG_VERTEX_INFO:
if (facet->npoints == 0)
{
facet->npoints = 1;
facet->points = emalloc(sizeof(VDF_INDEX));
n_entries = 0;
}
if (n_entries < facet->npoints)
process_vertex_info(facet, n_entries++);
else
skip_parms();
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Facet tag */
static void process_facet(VDF_FACET *facet)
{
VDF_TAG tag;
facet->is_doublesided = facet->is_interior = 0;
facet->base_facet = NULL; facet->n_used = 0;
facet->front_material = facet->back_material = -1;
vector_zero(&facet->normal); facet->application_handle = 0;
facet->npoints = 0; facet->points = NULL;
facet->id = 0;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_IS_DOUBLESIDED: get_boolean(&facet->is_doublesided); break;
case VDF_TAG_IS_INTERIOR: get_boolean(&facet->is_interior); break;
case VDF_TAG_APPLICATION_HANDLE: get_handle(&facet->application_handle); break;
case VDF_TAG_BASE_FACET:
{
VDF_IDENTIFIER id;
get_identifier(&id);
facet->base_facet = find_on_list(facet_list, id);
if (facet->base_facet)
++facet->base_facet->n_used;
}
break;
case VDF_TAG_FRONT_MATERIAL: get_index(&facet->front_material); break;
case VDF_TAG_BACK_MATERIAL: get_index(&facet->back_material); break;
case VDF_TAG_NORMAL3D: get_vector(&facet->normal); break;
case VDF_TAG_VERTEX_DATA: process_facet_points(facet); break;
case VDF_TAG_IDENTIFIER:
get_identifier(&facet->id);
add_to_list(&facet_list, facet->id, facet);
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
if (facet->front_material == -1)
facet->front_material = 0;
if (facet->back_material == -1)
facet->back_material = facet->front_material;
}
/* this next routine gets called whenever we encounter a Vertex_list tag */
static void process_vertices(VDF_SHAPE *shape)
{
VDF_COUNT n_entries = 0;
VDF_TAG tag;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_COUNT:
get_count(&shape->nvertices);
shape->vertices = emalloc(shape->nvertices * sizeof(VDF_VERTEX));
break;
case VDF_TAG_VERTEX:
if (shape->nvertices == 0)
{
shape->nvertices = 1;
shape->vertices = emalloc(sizeof(VDF_VERTEX));
n_entries = 0;
}
if (n_entries < shape->nvertices)
process_vertex(&shape->vertices[n_entries++]);
else
skip_parms();
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
shape->nvertices = n_entries;
}
/* this next routine gets called whenever we encounter a Facets_list tag */
static void process_facets(VDF_SHAPE *shape)
{
VDF_COUNT n_entries = 0;
VDF_TAG tag;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_COUNT:
get_count(&shape->nfacets);
shape->facets = emalloc(shape->nfacets * sizeof(VDF_FACET));
break;
case VDF_TAG_FACET:
if (shape->nfacets == 0)
{
shape->nfacets = 1;
shape->facets = emalloc(sizeof(VDF_FACET));
n_entries = 0;
}
if (n_entries < shape->nfacets)
process_facet(&shape->facets[n_entries++]);
else
skip_parms();
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
shape->nfacets = n_entries;
}
/* this next routine gets called whenever we encounter a Shape tag */
static void process_shape(void)
{
VDF_SHAPE *shape = emalloc(sizeof(VDF_SHAPE));
VDF_TAG tag;
shape->n_used = 0; shape->id = 0;
shape->name = NULL; shape->application_handle = 0;
shape->lod_size = 0; shape->replaces = NULL;
shape->is_convex = 0; shape->material_table = NULL;
vector_zero(&shape->bound_min); vector_zero(&shape->bound_max);
shape->nvertices = 0; shape->vertices = NULL;
shape->nfacets = 0; shape->facets = NULL;
if (end_shapes == NULL)
world_ptr->shapes = end_shapes = shape;
else
end_shapes->next = shape;
end_shapes = shape; shape->next = NULL;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_LOD_SIZE: get_real(&shape->lod_size); break;
case VDF_TAG_LOD_REPLACES:
{
VDF_IDENTIFIER id;
get_identifier(&id);
shape->replaces = find_on_list(shape_list, id);
/* note: do not increment n_used */
}
break;
case VDF_TAG_NAME: get_string(&shape->name); break;
case VDF_TAG_APPLICATION_HANDLE: get_handle(&shape->application_handle); break;
case VDF_TAG_USES_MATERIAL_TABLE:
{
VDF_IDENTIFIER id;
get_identifier(&id);
shape->material_table = find_on_list(material_tables_list, id);
if (shape->material_table)
++shape->material_table->n_used;
}
break;
case VDF_TAG_BOUNDING_BOX:
need_left_bracket();
get_token(junkbuff, sizeof(junkbuff));
shape->bound_min.x = atof(junkbuff);
get_token(junkbuff, sizeof(junkbuff));
shape->bound_min.y = atof(junkbuff);
get_token(junkbuff, sizeof(junkbuff));
shape->bound_min.z = atof(junkbuff);
get_token(junkbuff, sizeof(junkbuff));
shape->bound_max.x = atof(junkbuff);
get_token(junkbuff, sizeof(junkbuff));
shape->bound_max.y = atof(junkbuff);
get_token(junkbuff, sizeof(junkbuff));
shape->bound_max.z = atof(junkbuff);
need_right_bracket();
break;
case VDF_TAG_IS_CONVEX: get_boolean(&shape->is_convex); break;
case VDF_TAG_VERTEX_LIST: process_vertices(shape); break;
case VDF_TAG_FACET_LIST: process_facets(shape); break;
case VDF_TAG_IDENTIFIER:
get_identifier(&shape->id);
add_to_list(&shape_list, shape->id, shape);
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter an Object tag */
static void process_object(void)
{
VDF_OBJECT *object = emalloc(sizeof(VDF_OBJECT));
VDF_TAG tag;
object->name = NULL; object->application_handle = 0;
object->shape = NULL; object->id = 0;
object->scaled_by.x = object->scaled_by.y = object->scaled_by.z = 1.00;
object->material_table = NULL; vector_zero(&object->location);
object->rotx = object->roty = object->rotz = 0;
object->parent = NULL;
object->container = NULL; object->is_invisible = 0;
object->layer = 0; object->text = NULL;
object->facet_behind = NULL;
if (end_objects == NULL)
world_ptr->objects = end_objects = object;
else
end_objects->next = object;
end_objects = object; object->next = NULL;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_NAME: get_string(&object->name); break;
case VDF_TAG_APPLICATION_HANDLE: get_handle(&object->application_handle); break;
case VDF_TAG_INSTANCE_OF_SHAPE:
{
VDF_IDENTIFIER id;
get_identifier(&id);
object->shape = find_on_list(shape_list, id);
if (object->shape)
++object->shape->n_used;
}
break;
case VDF_TAG_SCALED_BY: get_vector(&object->scaled_by); break;
case VDF_TAG_USES_MATERIAL_TABLE:
{
VDF_IDENTIFIER id;
get_identifier(&id);
object->material_table = find_on_list(material_tables_list, id);
if (object->material_table)
++object->material_table->n_used;
}
break;
case VDF_TAG_LOCATION: get_vector(&object->location); break;
case VDF_TAG_ROTATION:
need_left_bracket();
get_token(junkbuff, sizeof(junkbuff));
object->rotx = atof(junkbuff);
get_token(junkbuff, sizeof(junkbuff));
object->roty = atof(junkbuff);
get_token(junkbuff, sizeof(junkbuff));
object->rotz = atof(junkbuff);
need_right_bracket();
break;
case VDF_TAG_ATTACHED_TO:
{
VDF_IDENTIFIER id;
get_identifier(&id);
object->parent = find_on_list(object_list, id);
if (object->parent)
++object->parent->n_used;
}
break;
case VDF_TAG_CONTAINED_WITHIN:
{
VDF_IDENTIFIER id;
get_identifier(&id);
object->container = find_on_list(object_list, id);
if (object->container)
++object->container->n_used;
}
break;
case VDF_TAG_IS_INVISIBLE: get_boolean(&object->is_invisible); break;
case VDF_TAG_LAYER: get_index(&object->layer); break;
case VDF_TAG_TEXT: get_string(&object->text); break;
case VDF_TAG_FACET_BEHIND:
{
VDF_IDENTIFIER id;
get_identifier(&id);
object->facet_behind = find_on_list(facet_list, id);
if (object->facet_behind)
++object->facet_behind->n_used;
}
break;
case VDF_TAG_IDENTIFIER:
get_identifier(&object->id);
add_to_list(&object_list, object->id, object);
break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Light tag */
static void process_light(void)
{
VDF_LIGHT *light = emalloc(sizeof(VDF_LIGHT));
VDF_TAG tag;
light->name = NULL; light->application_handle = 0;
light->associated_with = NULL; light->is_on = 1;
light->type = VDF_LIGHT_DIRECTIONAL;
color_zero(&light->color);
light->hotspot = 0; light->falloff = 0; light->casts_shadows = 0;
if (end_lights == NULL)
world_ptr->lights = end_lights = light;
else
end_lights->next = light;
end_lights = light; light->next = NULL;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_NAME: get_string(&light->name); break;
case VDF_TAG_APPLICATION_HANDLE: get_handle(&light->application_handle); break;
case VDF_TAG_ASSOCIATED_WITH:
{
VDF_IDENTIFIER id;
get_identifier(&id);
light->associated_with = find_on_list(object_list, id);
if (light->associated_with)
++light->associated_with->n_used;
}
break;
case VDF_TAG_LIGHT_TYPE: light->type = get_enum(light_type_names); break;
case VDF_TAG_COLOR: get_color(&light->color); break;
case VDF_TAG_HOTSPOT: get_angle(&light->hotspot); break;
case VDF_TAG_FALLOFF: get_angle(&light->falloff); break;
case VDF_TAG_IS_ON: get_boolean(&light->is_on); break;
case VDF_TAG_CASTS_SHADOWS: get_boolean(&light->casts_shadows); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Camera tag */
static void process_camera(void)
{
VDF_CAMERA *camera = emalloc(sizeof(VDF_CAMERA));
VDF_TAG tag;
camera->name = NULL; camera->application_handle = 0;
camera->associated_with = NULL; camera->field_of_view = 45.00;
camera->aspect_ratio = 1.33;
camera->projection_type = VDF_PROJ_PERSPECTIVE;
if (end_cameras == NULL)
world_ptr->cameras = end_cameras = camera;
else
end_cameras->next = camera;
end_cameras = camera; camera->next = NULL;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_NAME: get_string(&camera->name); break;
case VDF_TAG_APPLICATION_HANDLE: get_handle(&camera->application_handle); break;
case VDF_TAG_ASSOCIATED_WITH:
{
VDF_IDENTIFIER id;
get_identifier(&id);
camera->associated_with = find_on_list(object_list, id);
if (camera->associated_with)
++camera->associated_with->n_used;
}
break;
case VDF_TAG_FIELD_OF_VIEW: get_real(&camera->field_of_view); break;
case VDF_TAG_ASPECT_RATIO: get_real(&camera->aspect_ratio); break;
case VDF_TAG_PROJECTION_TYPE: camera->projection_type = get_enum(projection_type_names); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a Sound tag */
static void process_sound(void)
{
VDF_SOUND *sound = emalloc(sizeof(VDF_SOUND));
VDF_TAG tag;
sound->name = NULL; sound->application_handle = 0;
sound->associated_with = NULL; sound->is_on = 1;
sound->volume = 1.00; sound->sample_name = NULL;
if (end_sounds == NULL)
world_ptr->sounds = end_sounds = sound;
else
end_sounds->next = sound;
end_sounds = sound; sound->next = NULL;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_NAME: get_string(&sound->name); break;
case VDF_TAG_APPLICATION_HANDLE: get_handle(&sound->application_handle); break;
case VDF_TAG_ASSOCIATED_WITH:
{
VDF_IDENTIFIER id;
get_identifier(&id);
sound->associated_with = find_on_list(object_list, id);
if (sound->associated_with)
++sound->associated_with->n_used;
}
break;
case VDF_TAG_IS_ON: get_boolean(&sound->is_on); break;
case VDF_TAG_VOLUME: get_real(&sound->volume); break;
case VDF_TAG_SAMPLE_NAME: get_string(&sound->sample_name); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a World_attributes tag */
static void process_world_attributes(void)
{
VDF_WORLD_ATTRIBUTES *ptr = world_ptr->world_attributes;
VDF_TAG tag;
if (world_ptr->world_attributes == NULL)
{
ptr = world_ptr->world_attributes = emalloc(sizeof(VDF_WORLD_ATTRIBUTES));
vector_zero(&ptr->gravity_vector);
color_zero(&ptr->ambient_light); ptr->has_horizon = 1;
color_zero(&ptr->sky_color); color_zero(&ptr->ground_color);
color_zero(&ptr->fog_color); ptr->scale = 1.00;
}
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_GRAVITY_VECTOR: get_vector(&ptr->gravity_vector); break;
case VDF_TAG_AMBIENT_LIGHT: get_color(&ptr->ambient_light); break;
case VDF_TAG_HAS_HORIZON: get_boolean(&ptr->has_horizon); break;
case VDF_TAG_SKY_COLOR: get_color(&ptr->sky_color); break;
case VDF_TAG_GROUND_COLOR: get_color(&ptr->ground_color); break;
case VDF_TAG_FOG_COLOR: get_color(&ptr->fog_color); break;
case VDF_TAG_SCALE: get_real(&ptr->scale); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter either a
Created_by or Modified_by tag */
static void process_human_data(VDF_HUMAN_DATA **data)
{
VDF_TAG tag;
VDF_HUMAN_DATA *h = emalloc(sizeof(VDF_HUMAN_DATA));
h->person = NULL; h->date = NULL; h->time = NULL; h->next = NULL;
h->comment = NULL;
if (*data == NULL)
*data = h;
else
{
VDF_HUMAN_DATA *p;
for (p = *data; p; p = p->next)
if (p->next == NULL)
{
p->next = h;
break;
}
}
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_PERSON: get_string(&h->person); break;
case VDF_TAG_DATE:
{
need_left_bracket();
get_token(tmpbuff, sizeof(tmpbuff));
h->date = estrdup(tmpbuff);
get_token(tmpbuff, sizeof(tmpbuff));
h->time = estrdup(tmpbuff);
need_right_bracket();
}
break;
case VDF_TAG_COMMENT: get_string(&h->comment); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this next routine gets called whenever we encounter a World_information tag */
static void process_world_info(void)
{
VDF_TAG tag;
need_left_bracket();
while ((tag = get_token(junkbuff, sizeof(junkbuff))) != VDF_TAG_RIGHT_BRACKET)
switch (tag)
{
case EOF: close_file(); break;
case VDF_TAG_COPYRIGHT_MESSAGE: get_string(&world_ptr->copyright_message); break;
case VDF_TAG_USAGE_RESTRICTIONS: get_string(&world_ptr->usage_restrictions); break;
case VDF_TAG_CREATED_BY: process_human_data(&world_ptr->created); break;
case VDF_TAG_MODIFIED_BY: process_human_data(&world_ptr->modified); break;
case VDF_TAG_TITLE: get_string(&world_ptr->title); break;
case VDF_TAG_IGNORE: break;
default: skip_parms(); break;
}
}
/* this routine handles all the top-level tags */
static void process_file(void)
{
VDF_TAG tag;
while ((tag = get_token(tmpbuff, sizeof(tmpbuff))) != EOF)
switch (tag)
{
case VDF_TAG_PALETTE: process_palette(); break;
case VDF_TAG_MAP: process_map(); break;
case VDF_TAG_MATERIAL: process_material(); break;
case VDF_TAG_MATERIAL_TABLE: process_material_table(); break;
case VDF_TAG_SHAPE: process_shape(); break;
case VDF_TAG_OBJECT: process_object(); break;
case VDF_TAG_CAMERA: process_camera(); break;
case VDF_TAG_LIGHT: process_light(); break;
case VDF_TAG_SOUND: process_sound(); break;
case VDF_TAG_WORLD_INFO: process_world_info(); break;
case VDF_TAG_WORLD_ATTRIBUTES: process_world_attributes(); break;
case VDF_TAG_IGNORE: break;
default:
printf("Unknown tag '%s'\n", tmpbuff);
skip_parms();
break;
}
}
/**** main file-reading routine ****/
VDF_DATA *vdf_readfile(char *filename)
{
strcpy(emsg, "No read error");
if (setjmp(error_jmp))
return NULL; /* return NULL on error */
world_ptr = emalloc(sizeof(VDF_DATA));
world_ptr->palette = NULL; world_ptr->maps = NULL;
world_ptr->materials = NULL;
world_ptr->material_tables = NULL; world_ptr->shapes = NULL;
world_ptr->objects = NULL; world_ptr->cameras = NULL;
world_ptr->lights = NULL; world_ptr->sounds = NULL;
world_ptr->world_attributes = NULL;
world_ptr->created = NULL; world_ptr->modified = NULL;
world_ptr->copyright_message = NULL; world_ptr->usage_restrictions = NULL;
world_ptr->title = NULL;
end_palettes = NULL; end_maps = NULL;
end_materials = NULL;
end_material_tables = NULL; end_shapes = NULL;
end_objects = NULL; end_cameras = NULL;
end_lights = NULL; end_sounds = NULL;
end_world_attributes = NULL;
open_file(filename);
process_file();
close_all();
return world_ptr;
}
VDF_MATERIAL *vdf_map_material(VDF_OBJECT *object, VDF_INDEX material_index)
{
if (object->material_table) /* if the object has a material table */
return object->material_table->material_references[material_index]; /* use it */
if (object->shape) /* if the object has a shape */
if (object->shape->material_table) /* and the shape has a material table */
return object->shape->material_table->material_references[material_index]; /* use it */
return NULL; /* couldn't map the material */
}