[net.sources] OFF subroutine library sources

rost@granite.UUCP (01/27/87)

This is a shar file that includes the source code for a library
of routines to read/write/create Object File Format (OFF) files.
These routines are useful for creating/maintaining a library
of OFF graphical object databases.  A number of object in this
format were posted to the net in December, 1986.


# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by granite!rost on Tue Jan 27 12:09:43 PST 1987
# Contents: AddProp.c CreateObj.c DestroyObj.c FreeProp.c ReadGeneric.c ReadIndPoly.c ReadObj.c RemoveProp.c WriteGeneric.c WriteIndPoly.c WriteObj.c makefile off.h off.ms
echo x - AddProp.c
sed 's/^@//' > "AddProp.c" <<'@//E*O*F AddProp.c//'


/*
 *
 * Description
 *	Add property structure to OFF property list.
 *
 * Output
 *	OFFAddProperty returns a pointer to the newly-created and
 *	initialized property structure.
 *
 * Input
 *	Obj		Pointer to object to which property is to be added.
 *
 * Diagnostics
 *	Returns NULL if unsuccessful malloc'ing the property structure.
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 */

#include <stdio.h>
#include "off.h"

OFFProperty *OFFAddProperty(Obj)
    OFFObjDesc	*Obj;	/* Pointer to object */

    {
    OFFProperty	**ppProp;
    OFFProperty	*newProp;

    ppProp = &(Obj->FirstProp);
    while (*ppProp != NULL) ppProp = &((*ppProp)->NextProp);

    newProp = (OFFProperty *) malloc(sizeof(OFFProperty));

    if (newProp == NULL)
	{
	fprintf(stderr, "OFFAddProperty: malloc failed\n");
	return(NULL);
	}

    *ppProp = newProp;
    newProp->PropName[0] = '\0';
    newProp->PropType = OFF_UNKNOWN_TYPE_DATA;
    newProp->PropFileName[0] = '\0';
    newProp->DataFormat[0] = '\0';
    newProp->PropCount = 0;
    newProp->PropData = NULL;
    newProp->NextProp = NULL;

    return(newProp);
    }
@//E*O*F AddProp.c//
chmod u=rw,g=r,o= AddProp.c
echo x - CreateObj.c
sed 's/^@//' > "CreateObj.c" <<'@//E*O*F CreateObj.c//'

/*
 *
 * Description
 *	Allocate and initialize an OFF object description structure.
 *
 * Output
 *	OFFCreateObj returns a pointer to the newly-created and
 *	initialized object structure.
 *
 * Input
 *
 * Diagnostics
 *	Returns NULL if unsuccessful malloc'ing memory for object structure
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */

#include <stdio.h>
#include "off.h"

OFFObjDesc *OFFCreateObj()

    {
    OFFObjDesc	*Obj;

    Obj = (OFFObjDesc *) malloc(sizeof(OFFObjDesc));

    if (Obj == NULL)
	{
	fprintf(stderr, "OFFCreateObj: malloc failed\n");
	return(NULL);
	}

    Obj->FirstProp = NULL;
    return(Obj);
    }
@//E*O*F CreateObj.c//
chmod u=rw,g=r,o= CreateObj.c
echo x - DestroyObj.c
sed 's/^@//' > "DestroyObj.c" <<'@//E*O*F DestroyObj.c//'

/*
 *
 * Description
 *	Destroy the specified object and deallocate all memory resources
 *	associated with it.
 *
 * Output
 *
 * Input
 *	Obj		Pointer to object structure to be deallocated.
 *
 * Diagnostics
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */

#include <stdio.h>
#include "off.h"

OFFDestroyObj(Obj)
    OFFObjDesc	*Obj;

    {
    OFFProperty	*pProp, *nextProp;

    pProp = Obj->FirstProp;
    while (pProp != NULL)
	{
	nextProp = pProp->NextProp;
	OFFFreeProperty(pProp);
	pProp = nextProp;
	}
    free(Obj);
    }

@//E*O*F DestroyObj.c//
chmod u=rw,g=r,o= DestroyObj.c
echo x - FreeProp.c
sed 's/^@//' > "FreeProp.c" <<'@//E*O*F FreeProp.c//'

/*
 *
 * Description
 *	Free a property structure and associated memory resources.
 *
 * Output
 *
 * Input
 *	pProp		Pointer to the property structure to be deallocated.
 *
 * Diagnostics
 *	Returns 0 if successful, -1 if unsuccessful (unknown data type).
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */

#include <stdio.h>
#include "off.h"


OFFFreeProperty(pProp)
    OFFProperty	*pProp;

    {
    OFFProperty	*newprop;
    int		i;
    int		nostrings = 1;

    if (strcmp(pProp->PropName, "comment") == 0)
	{
	free(pProp->PropData);
	free(pProp);
	return(0);
	}

    if (strcmp(pProp->PropName, "nl") == 0)
	{
	free(pProp);
	return(0);
	}

    if (strcmp(pProp->PropName, "name") == 0 ||
	strcmp(pProp->PropName, "author") == 0 ||
	strcmp(pProp->PropName, "type") == 0 ||
	strcmp(pProp->PropName, "description") == 0 ||
	strcmp(pProp->PropName, "copyright") == 0)
	{
	free(pProp->PropData);
	free(pProp);
	return(0);
	}

    else
	{
	nostrings;
	for (i = 0; i < strlen(pProp->DataFormat); i++)
	    if (pProp->DataFormat[i] == 's') nostrings = 0;
	switch(newprop->PropType)
	    {
	    case OFF_DEFAULT_DATA:
		if (!nostrings)
		    FreeStrings(pProp->PropData, pProp->DataFormat, 1);
		break;

	    case OFF_GENERIC_DATA:
		if (!nostrings)
		    FreeStrings(pProp->PropData + sizeof(long),
			pProp->DataFormat, pProp->PropCount);
		break;

	    case OFF_INDEXED_POLY_DATA:
		if (nostrings)
		    FreeStrings(pProp->PropData + 3 * sizeof(long),
			pProp->DataFormat, pProp->PropCount);
		break;

	    default:
		return(-1);
		break;
	    } /* switch */

	free(pProp->PropData);
	free(pProp);
	}

    return(0);
    }



static int FreeStrings(dataptr, format)
    char	*dataptr;
    char	*format;

    {
    int		i;
    char	*ptr;

    ptr = dataptr;
    for (i = 0; i < strlen(format); i++)
	{
	switch(format[i])
	    {
	    case 'i':
		 /* Make sure we're aligned on word boundary */
		 ptr += (((int) ptr % 4) == 0) ? 0 : 4 - (int) ptr % 4;
		 ptr += sizeof(long);
		 break;
	    case 'b':
		 ptr++;
		 break;
	    case 'd':
		 /* Make sure we're aligned on word boundary */
		 ptr += (((int) ptr % 4) == 0) ? 0 : 4 - (int) ptr % 4;
		 ptr += sizeof(double);
		 break;
	    case 'h':
		 /* Make sure we're aligned on halfword boundary */
		 ptr += (((int) ptr % 2) == 0) ? 0 : 1;
		 ptr += sizeof(short);
		 break;
	    case 'f':
		 /* Make sure we're aligned on word boundary */
		 ptr += (((int) ptr % 4) == 0) ? 0 : 4 - (int) ptr % 4;
		 ptr += sizeof(float);
		 break;
	    case 's':
		 /* Make sure we're aligned on word boundary */
		 ptr += (((int) ptr % 4) == 0) ? 0 : 4 - (int) ptr % 4;
		 free(*((char **) ptr));
		 ptr += sizeof(char *);
		 break;
	    default:
		 return(-1);
	    }
	}
    }
@//E*O*F FreeProp.c//
chmod u=rw,g=r,o= FreeProp.c
echo x - ReadGeneric.c
sed 's/^@//' > "ReadGeneric.c" <<'@//E*O*F ReadGeneric.c//'

/*
 *
 * Description
 *	Read a generic data file.
 *
 * Output
 *
 * Input
 *	pProp		Pointer to property structure in which to store data
 *	fname		Full path/file name of file to be read.
 *
 * Diagnostics
 *	Returns 0 if successful, -1 if unsuccessful for any reason.
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */


#include <stdio.h>
#include <sys/file.h>
#include "off.h"

#define MAX_DATA_ITEMS		30

OFFReadGeneric(pProp, fname)
    OFFProperty	*pProp;
    char	*fname;

    {
    FILE	*ascfd;
    int		binfd;
    long	code;
    char	*ptr;
    int		i, j;
    long	npts;
    long	*lptr;
    char	format[MAX_DATA_ITEMS][10];
    int		padding[MAX_DATA_ITEMS];
    int		size[MAX_DATA_ITEMS];
    int		datasize = 0;
    int		type = OFF_BINARY;
    char	ch;
    char	bigstr[OFF_BIGSTR];
    int		nostrings = 1;
    int		strlength;
    int		endpad;
    long	junk;


/*  Try opening the file as if it were binary first  */
    binfd = open(fname, O_RDONLY, 0);

/*  If error opening file, punt  */
    if (binfd < 0)
	{
	fprintf(stderr, "OFFReadGeneric: cannot open data file %s\n",
		fname);
	return(-1);
	}

/*  Read first word of file to determine file type  */
    read(binfd, &code, sizeof(long));

    if (code != OFF_GENERIC_MAGIC)
	{
    /*  Close the file  */
	close(binfd);

    /*  Try to open it as an ascii data file  */
	ascfd = fopen(fname, "r");

    /*  If error opening file, punt  */
	if (ascfd == NULL)
	    {
	    fprintf(stderr, "OFFReadGeneric: cannot open data file %s\n",
		    fname);
	    return(-1);
	    }
	type = OFF_ASCII;
	}

/*  Read in the number of data items for the list  */
    if (type == OFF_ASCII)
	fscanf(ascfd,"%d\n", &npts);
    else
	read(binfd, &npts, sizeof(long));
    pProp->PropCount = npts;

/*  Compute data size  */
    for (i = 0; i < strlen(pProp->DataFormat); i++)
	{
	switch (pProp->DataFormat[i])
	    {
	    case 'i': size[i] = sizeof(long);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%d");
		      break;
	    case 'f': size[i] = sizeof(float);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%f");
		      break;
	    case 'd': size[i] = sizeof(double);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%F");
		      break;
	    case 'h': size[i] = sizeof(short);
		      padding[i] = ((datasize % 2) == 0) ? 0 : 1;
		      strcpy(format[i], "%hd");
		      break;
	    case 'b': size[i] = sizeof(char);
		      padding[i] = 0;
		      strcpy(format[i], "%d");
		      break;
	    case 's': size[i] = sizeof(char *);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%s");
		      nostrings = 0;
		      break;
	    default:  fprintf(stderr, "OFFReadGeneric: data format not ");
		      fprintf(stderr, "valid for generic data type\n");
		      return (-1);
	    }
	datasize += padding[i] + size[i];
	}

    endpad = ((datasize % 4) == 0) ? 0 : 4 - datasize % 4;
    datasize += endpad;

/*  Allocate memory for the points and vertices  */
    pProp->PropData = (char *) malloc(sizeof(long) + datasize * npts);

    ptr = pProp->PropData;
    lptr = (long *) ptr;
    *lptr = npts;
    ptr += sizeof(long);

    if (type == OFF_ASCII)	/* Read info from the ascii file */
	{
    /*  Read in all the data  */
	for(i = 0; i < npts; i++)
	    {
	    for(j = 0; j < strlen(pProp->DataFormat); j++)
		{
		ptr += padding[j];
		if (pProp->DataFormat[j] == 's')
		    {
		    fscanf(ascfd, format[j], bigstr);
		    lptr = (long *) ptr;
		    *lptr = (long) malloc(strlen(bigstr) + 1);
		    strcpy(*lptr, bigstr);
		    }
		else if (pProp->DataFormat[j] == 'b')
		    { fscanf(ascfd, format[j], &ch); *ptr = ch; }
		else
		    fscanf(ascfd, format[j], ptr);

		ptr += size[j];
		}

	    ptr += endpad;
	    }
	}

    else	/* Read info from the binary file */

	{
	if (nostrings)
	    read(binfd, ptr, datasize * npts);	 /* Read object vertex array */
	else
	    {
	    for(i = 0; i < npts; i++)
		{
		for(j = 0; j < strlen(pProp->DataFormat); j++)
		    {
		    ptr += padding[j];
		    if (padding[j] != 0) read(binfd, ptr, padding[j]);
		    if (strcmp(format[j], "%s") != 0)
			read(binfd, ptr, size[j]);
		    else
			{
			read(binfd, &strlength, sizeof(char *));
			lptr = (long *) ptr;
			*lptr = (long) malloc(strlength);
			read(binfd, bigstr, strlength);
			strcpy(*lptr, bigstr);
			if ((strlength % 4) != 0)
			    read(binfd, &junk, 4 - strlength % 4);
			}
		    ptr += size[j];
		    }
		if (endpad != 0) read(binfd, ptr, endpad);
		ptr += endpad;
		}
	    }
	}

/*  Close the data file  */
    if (type == OFF_ASCII)
	fclose(ascfd);
    else
	close(binfd);

    return(0);
    }

@//E*O*F ReadGeneric.c//
chmod u=rw,g=r,o= ReadGeneric.c
echo x - ReadIndPoly.c
sed 's/^@//' > "ReadIndPoly.c" <<'@//E*O*F ReadIndPoly.c//'

/*
 *
 * Description
 *	Read an indexed_poly data file.
 *
 * Output
 *
 * Input
 *	pProp		Pointer to property structure in which to store data
 *	fname		Full path/file name of file to be read.
 *
 * Diagnostics
 *	Returns 0 if successful, -1 if unsuccessful for any reason.
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */


#include <stdio.h>
#include <sys/file.h>
#include "off.h"

#define MAX_DATA_ITEMS		30

OFFReadIndexedPoly(pProp, fname)
    OFFProperty	*pProp;
    char	*fname;

    {
    FILE	*ascfd;
    int		binfd;
    int		code;
    char	*ptr, *ptr2;
    int		i, j;
    long	npts, npolys, nconnects;
    long	*lptr;
    char	format[MAX_DATA_ITEMS][10];
    int		padding[MAX_DATA_ITEMS];
    int		size[MAX_DATA_ITEMS];
    int		datasize = 0;
    int		type = OFF_BINARY;
    char	ch;
    char	bigstr[OFF_BIGSTR];
    int		nostrings = 1;
    int		strlength;
    int		endpad;
    long	junk;


/*  Try opening the file as if it were binary first  */
    binfd = open(fname, O_RDONLY, 0);

/*  If error opening file, punt  */
    if (binfd < 0)
	{
	fprintf(stderr, "OFFReadIndexedPoly: cannot open data file %s\n",
		fname);
	return(-1);
	}

/*  Read first word of file to determine file type  */
    read(binfd, &code, sizeof(long));

    if (code != OFF_INDEXED_POLY_MAGIC)
	{
    /*  Close the file  */
	close(binfd);

    /*  Try to open it as an ascii data file  */
	ascfd = fopen(fname, "r");

    /*  If error opening file, punt  */
	if (ascfd == NULL)
	    {
	    fprintf(stderr, "OFFReadIndexedPoly: cannot open data file %s\n",
		    fname);
	    return(-1);
	    }
	type = OFF_ASCII;
	}

/*  Read in the number of points and polygons for the object  */
    if (type == OFF_ASCII)
	fscanf(ascfd,"%d %d %d\n", &npts, &npolys, &nconnects);
    else
	{
	read(binfd, &npts, sizeof(long));
	read(binfd, &npolys, sizeof(long));
	read(binfd, &nconnects, sizeof(long));
	}
    pProp->PropCount = npts;

/*  Compute data size  */
    for (i = 0; i < strlen(pProp->DataFormat); i++)
	{
	switch (pProp->DataFormat[i])
	    {
	    case 'i': size[i] = sizeof(long);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%d");
		      break;
	    case 'f': size[i] = sizeof(float);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%f");
		      break;
	    case 'd': size[i] = sizeof(double);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%F");
		      break;
	    case 'h': size[i] = sizeof(short);
		      padding[i] = ((datasize % 2) == 0) ? 0 : 1;
		      strcpy(format[i], "%hd");
		      break;
	    case 'b': size[i] = sizeof(char);
		      padding[i] = 0;
		      strcpy(format[i], "%d");
		      break;
	    case 's': size[i] = sizeof(char *);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%s");
		      nostrings = 0;
		      break;
	    default:  fprintf(stderr, "OFFReadIndexedPoly: data format not ");
		      fprintf(stderr, "valid for indexed poly type\n");
		      return (-1);
	    }
	datasize += padding[i] + size[i];
	}

    endpad = ((datasize % 4) == 0) ? 0 : 4 - datasize % 4;
    datasize += endpad;

/*  Allocate memory for the points and vertices  */
    pProp->PropData = (char *) malloc(sizeof(long) * 3
		+ datasize * npts
		+ sizeof(short) * npolys 
		+ sizeof(short) * nconnects);

    ptr = pProp->PropData;
    lptr = (long *) ptr;
    *lptr++ = npts;
    *lptr++ = npolys;
    *lptr++ = nconnects;
    ptr = (char *) lptr;

    if (type == OFF_ASCII)	/* Read info from the ascii file */
	{
    /*  Read in all the points  */
	for(i = 0; i < npts; i++)
	    {
	    for(j = 0; j < strlen(pProp->DataFormat); j++)
		{
		ptr += padding[j];
		if (pProp->DataFormat[j] == 's')
		    {
		    fscanf(ascfd, format[j], bigstr);
		    lptr = (long *) ptr;
		    *lptr = (long) malloc(strlen(bigstr) + 1);
		    strcpy(*lptr, bigstr);
		    }
		else if (pProp->DataFormat[j] == 'b')
		    { fscanf(ascfd, format[j], &ch); *ptr = ch; }
		else
		    fscanf(ascfd, format[j], ptr);

		ptr += size[j];
		}

	    ptr += endpad;
	    }

    /*  Read in all the polygon counts and connectivity info */
	ptr2 = ptr + sizeof(short) * npolys;
	for(i = 0; i < npolys; i++)
	    {
	    fscanf(ascfd,"%hd", (short *) ptr);
	    for (j = 0; j < *((short *) ptr); j++)
		{ fscanf(ascfd,"%hd", (short *) ptr2); ptr2+= sizeof(short); }
	    ptr += sizeof(short);
	    }

	}

    else	/* Read info from the binary file */

	{
	if (nostrings)
	    {
	    read(binfd, ptr, datasize * npts);	 /* Read object vertex array */
	    ptr += datasize * npts;
	    }
	else
	    {
	    for(i = 0; i < npts; i++)
		{
		for(j = 0; j < strlen(pProp->DataFormat); j++)
		    {
		    ptr += padding[j];
		    if (padding[j] != 0) read(binfd, ptr, padding[j]);
		    if (strcmp(format[j], "%s") != 0)
			read(binfd, ptr, size[j]);
		    else
			{
			read(binfd, &strlength, sizeof(char *));
			lptr = (long *) ptr;
			*lptr = (long) malloc(strlength);
			read(binfd, bigstr, strlength);
			strcpy(*lptr, bigstr);
			if ((strlength % 4) != 0)
			    read(binfd, &junk, 4 - strlength % 4);
			}
		    ptr += size[j];
		    }
		if (endpad != 0) read(binfd, ptr, endpad);
		ptr += endpad;
		}
	    }

    /*  Read poly count array */
	read(binfd, ptr, sizeof(short) * (npolys + nconnects));
	}

/*  Close the data file  */
    if (type == OFF_ASCII)
	fclose(ascfd);
    else
	close(binfd);

    return(0);
    }

@//E*O*F ReadIndPoly.c//
chmod u=rw,g=r,o= ReadIndPoly.c
echo x - ReadObj.c
sed 's/^@//' > "ReadObj.c" <<'@//E*O*F ReadObj.c//'

/*
 *
 * Description
 *	Read an OFF object data file.
 *
 * Output
 *
 * Input
 *	Obj		Pointer to object structure in which to store data.
 *	FileName	Name of file to be read.
 *
 * Diagnostics
 *	Returns 0 if successful, -1 if unsuccessful for any reason.
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */

#include <stdio.h>
#include "off.h"

#define	MAX_OBJ_DIRS		10
#define DEFAULT_DATA_BLK	100

static	char		ObjDir[MAX_OBJ_DIRS][OFF_BIGSTR];
static	int		NumObjDirs = 0;
static	int		firsttime = 1;
static	char		*strptr;
static	OFFProperty	**pProp;

OFFReadObj(Obj, FileName)
    OFFObjDesc	 *Obj;			/* Object data structure to fill out */
    char	 *FileName;		/* File to be opened and read */

    {
    FILE	*ObjFile;
    char	Line[OFF_BIGSTR];
    char	Key[OFF_BIGSTR];
    char	Remainder[OFF_BIGSTR];
    char	Directory[OFF_BIGSTR];
    char	Path[OFF_BIGSTR];
    char	TmpString[OFF_BIGSTR];
    char	*index(), *getenv();
    int		i;
    int		status = 0;


/*  If this is the first time, parse the object search path  */
    if (firsttime)
	status = ParseObjPath();

/*  Punt if too many directories in OBJ_PATH  */
    if (status < 0)
	{
	fprintf(stderr,"OFFReadObj: too many (> %d) directories in OBJ_PATH\n",
		MAX_OBJ_DIRS);
	return(-1);
	}

/*  See if filename has a leading pathname component  */
    Directory[0] = '\0';
    for (i = strlen(FileName) - 1; i >= 0; i--)
	if (FileName[i] == '/') break;
    if (i >= 0)
	{
	strncpy(Directory, FileName, i + 1);
	Directory[i+1] = '\0';
	}

/*  First try opening the file as passed to us  */
    ObjFile = fopen(FileName,"r");

/*  If that doesn't work, try each directory in search path  */
    if (ObjFile == NULL)
	{ 
	for (i = 0; i < NumObjDirs; i++)
	    {
	    strcpy(Path, ObjDir[i]);
	    strcat(Path, FileName);
	    ObjFile = fopen(Path, "r");
	    if (ObjFile != NULL)
		{
		strcpy(TmpString, ObjDir[i]);
		strcat(TmpString, Directory);
		strcpy(Directory, TmpString);
		break;
		}
	    }
	}

/*  If the file isn't found in any of the search directories, punt  */
    if (ObjFile == NULL)
	{
	fprintf(stderr, "OFFReadObj:  %s not found\n", FileName);
	return(-1);
	}

/*  Initialize fields in object structure  */
    pProp = &(Obj->FirstProp);
    Obj->FirstProp = NULL;

/*  Read lines from the header file  */
    while((fgets(Line, OFF_BIGSTR - 1, ObjFile)) != NULL) 
	{
    /*  Get the first token  */
	if (Line[0] == '\n')
	    status = AddProperty("nl", NULL, NULL);
	else if (Line[0] == '#')
	    status = AddProperty("comment", Line, NULL);
	else
	    {
	    SplitLine(Line, Key, Remainder);
	    status = AddProperty(Key, Remainder, Directory);
	    }
	if (status != 0)
	    {
	    fprintf(stderr, "OFFReadObj: problem parsing line\n");
	    fprintf(stderr, "\t>>>%s\n", Line);
	    return(-1);
	    }
	}

    fclose(ObjFile);
    return(0);
    }



static SplitLine(str, part1, remainder)
    char *str, *part1, *remainder;

    {
    int    i, p1, p2;

/*  If first character is '#' for a comment, we're done  */
    if (str[0] == '#')
	{
	strcpy(part1, "#");
	strcpy(remainder, &(str[1]));
	return;
	}

/*  Position p1 to first non-separator character  */
/*  Blanks, tabs, commas are separators */
    p1 = 0;	
    while ((str[p1] == ' ' ) || (str[p1] == '\t') || (str[p1] == ',')) p1++;

/*  Position p2 to first separator character after p1  */
    p2 = p1;
    while ((str[p2] != ' ') && (str[p2] != '\t') && (str[p2] != '\0') &&
	   (str[p2] != ',') && (str[p2] != '\n')) p2++;

/*  Copy what's between p1 and p2 to part1  */
    for (i = p1; i < p2; i++)
	part1[i - p1] = str[i];
    part1[i - p1] = '\0';

/*  Position p2 to next non-separator character  */
    while ((str[p2] == ' ' ) || (str[p2] == '\t') || (str[p2] == ',')) p2++;
    i = 0;
    while ((str[p2] != '\0') && (str[p2] != '\n') && (str[p2] != '\0'))
	remainder[i++] = str[p2++];
    remainder[i] = '\0';
    }

#ifdef caca
OFFPrintObjPath()

    {
    int		i;

/*  If this is the first time, parse the object search path  */
    if (firsttime)
	i = ParseObjPath();

/*  Punt if too many directories in OBJ_PATH  */
    if (i < 0)
	{
	fprintf(stderr,
	    "PrintObjPath: too many (> %d) directories in OBJ_PATH\n",
		MAX_OBJ_DIRS);
	return(-1);
	}

/*  Print out all directories in the object search path  */
    for (i = 0; i < NumObjDirs; i++)
	printf("%s ", ObjDir[i]);
    printf("\n");

    return(0);
    }
#endif

static ParseObjPath()

    {
    char	*str;
    int		i, j;

    firsttime = 0;
    str = getenv("OBJ_PATH");
    for (i = 0; i < strlen(str); i++)
	{
	while ((str[i] == ' ') || (str[i] == '\t')) i++;
	j = i;
	while ((str[j] != ' ') && (str[j] != '\t') && (j < strlen(str)))
	    j++;
	strncpy(ObjDir[NumObjDirs], &(str[i]), j - i);
	ObjDir[NumObjDirs][j - i] = '/';
	ObjDir[NumObjDirs][j - i + 1] = '\0';
	if (strcmp(ObjDir[NumObjDirs], "./") != 0)
	    glob(ObjDir[NumObjDirs++]);  /* ignore directory "." in path */
	i = j;
	if (NumObjDirs > MAX_OBJ_DIRS)
	    return(-1);
	}

    return(0);
    }


#include <pwd.h>
#include <ctype.h>

static glob(string)
    char	*string;

    {
    struct	passwd	*p, *getpwname();
    char	str1[160], str2[160];
    char	*pstr;
    char	*getenv();

   
/*  If first character is '~', expand it  */
    if (string[0] == '~')

	{

	strcpy(str1, string);

    /*  ~/... means use home directory  */
	if (string[1] == '/')
	    {
	    strcpy(string, getenv("HOME"));
	    strcat(string, &(str1[1]));
	    }

    /*  ~whatever/... means use whatever's home directory  */
	else
	    {
	    pstr = index(str1, '/');
	    strncpy(str2, &(string[1]), pstr - str1 - 1);
	    str2[pstr - str1 - 1] = 0;
	    p = getpwnam(str2);
	    strcpy(string, p->pw_dir);
	    strcat(string, pstr);
	    }

	}

    }


static int AddProperty(PropName, String, Directory)
    char	*PropName;
    char	*String;
    char	*Directory;

    {
    OFFProperty	*newprop;
    char	str[OFF_BIGSTR], junk[OFF_BIGSTR], remainder[OFF_BIGSTR];
    char	datatype[OFF_BIGSTR];
    char	filename[OFF_BIGSTR];
    long	*iptr;
    short	*hptr;
    float	*fptr;
    double	*dptr;
    char	*ptr;
    int		i, j, k;

    newprop = (OFFProperty *) malloc(sizeof(OFFProperty));
    *pProp = newprop;
    pProp = &(newprop->NextProp);
    newprop->NextProp = NULL;
    newprop->PropData = NULL;
    newprop->PropFileName[0] = '\0';
    newprop->DataFormat[0] = '\0';
    newprop->PropCount = 0;
    PropName[OFF_SMSTR] = '\0';
    strcpy(newprop->PropName, PropName);

    if (strcmp(PropName, "comment") == 0)
	{
	String[strlen(String) - 1] = '\0';
	newprop->PropData = (char *) malloc(strlen(String));
	strcpy(newprop->PropData, String);
	newprop->PropType = OFF_COMMENT_DATA;
	return(0);
	}

    if (strcmp(PropName, "nl") == 0)
	{
	newprop->PropData = (char *) malloc(OFF_BIGSTR);
	newprop->PropType = OFF_COMMENT_DATA;
	return(0);
	}

    if (strcmp(PropName, "name") == 0 || strcmp(PropName, "author") == 0 ||
	strcmp(PropName, "type") == 0 || strcmp(PropName, "description") == 0
	|| strcmp(PropName, "copyright") == 0)
	{
	newprop->PropData = (char *) malloc(OFF_BIGSTR);
	strcpy(newprop->PropData, String);
	newprop->PropType = OFF_STANDARD_DATA;
	return(0);
	}

    else
	{
	SplitLine(String, datatype, remainder);
	if (strcmp(datatype, "default") == 0)
		newprop->PropType = OFF_DEFAULT_DATA;
	else if (strcmp(datatype, "generic") == 0)
		newprop->PropType = OFF_GENERIC_DATA;
	else if (strcmp(datatype, "indexed_poly") == 0)
		newprop->PropType = OFF_INDEXED_POLY_DATA;
	else
		newprop->PropType = OFF_UNKNOWN_TYPE_DATA;
	strcpy(str, remainder);
	SplitLine(str, newprop->DataFormat, remainder);

	remainder[OFF_SMSTR] = '\0';
	if (newprop->PropType != OFF_DEFAULT_DATA)
	    strcpy(newprop->PropFileName, remainder);

	switch(newprop->PropType)
	    {
	    case OFF_DEFAULT_DATA:
		newprop->PropCount = 1;
		newprop->PropData = (char *) malloc(DEFAULT_DATA_BLK);
		ptr = newprop->PropData;
		for (i = 0; i < strlen(newprop->DataFormat); i++)
		    {
		    switch (newprop->DataFormat[i])
			{
			case 'i':
			     /* Make sure we're aligned on word boundary */
			     ptr += (((int) ptr % 4) == 0) ?
				0 : 4 - (int) ptr % 4;
			     iptr = (long *) ptr;
			     sscanf(remainder, "%d", iptr);
			     ptr += sizeof(long);
			     strcpy(str, remainder);
			     SplitLine(str, junk, remainder);
			     break;
			case 'b':
			     sscanf(remainder, "%d", &j);
			     *ptr++ = (unsigned char) j;
			     strcpy(str, remainder);
			     SplitLine(str, junk, remainder);
			     break;
			case 'd':
			     /* Make sure we're aligned on word boundary */
			     ptr += (((int) ptr % 4) == 0) ?
				0 : 4 - (int) ptr % 4;
			     dptr = (double *) ptr;
			     sscanf(remainder, "%F", dptr);
			     ptr += sizeof(double);
			     strcpy(str, remainder);
			     SplitLine(str, junk, remainder);
			     break;
			case 'h':
			     /* Make sure we're aligned on halfword boundary */
			     ptr += (((int) ptr % 2) == 0) ? 0 : 1;
			     hptr = (short *) ptr;
			     sscanf(remainder, "%hd", hptr);
			     ptr += sizeof(short);
			     strcpy(str, remainder);
			     SplitLine(str, junk, remainder);
			     break;
			case 'f':
			     /* Make sure we're aligned on word boundary */
			     ptr += (((int) ptr % 4) == 0) ?
				0 : 4 - (int) ptr % 4;
			     fptr = (float *) ptr;
			     sscanf(remainder, "%f", fptr);
			     ptr += sizeof(float);
			     strcpy(str, remainder);
			     SplitLine(str, junk, remainder);
			     break;
			case 's':
			     j = 0; k = 0;
			     while (remainder[j] != ' ' && remainder[j] != '\t'
				    && j < strlen(remainder))
				 junk[k++] = remainder[j++];
			     junk[k] = '\0';
			     *((char **) ptr) = (char *) malloc(k + 1);
			     strcpy(*((char **) ptr), junk);
			     ptr += sizeof(char *);
			     strcpy(junk, &(remainder[j]));
			     strcpy(remainder, junk);
			     break;
			default:
			     return(-1);
			} /* switch */
		    } /* for */
		break;

	    case OFF_GENERIC_DATA:
		strcpy(filename, Directory);
		strcat(filename, newprop->PropFileName);
		if ((i = OFFReadGeneric(newprop, filename)) != 0)
		    return(-1);
		break;

	    case OFF_INDEXED_POLY_DATA:
		strcpy(filename, Directory);
		strcat(filename, newprop->PropFileName);
		if (OFFReadIndexedPoly(newprop, filename) != 0)
		    return(-1);
		break;

	    default:
		return(-1);
		break;
	    } /* switch */
	}

    return(0);
    }
@//E*O*F ReadObj.c//
chmod u=rw,g=r,o= ReadObj.c
echo x - RemoveProp.c
sed 's/^@//' > "RemoveProp.c" <<'@//E*O*F RemoveProp.c//'

/*
 *
 * Description
 *	Remove the named property from a property list.
 *
 * Output
 *
 * Input
 *	Obj		Pointer to object structure from which to remove prop.
 *	PropName	Name of property to be removed.
 *
 * Diagnostics
 *	Returns 0 if successful, -1 if named property is not found.
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */

#include <stdio.h>
#include "off.h"

OFFRemoveProperty(Obj, PropName)
    OFFObjDesc	*Obj;		/* Pointer to object */
    char	*PropName;	/* Name of property to be deleted */

    {
    OFFProperty	**ppProp;
    OFFProperty	*newProp;
    OFFProperty	*nextProp;

    ppProp = &(Obj->FirstProp);
    while (*ppProp != NULL)
	{
	if (strcmp(PropName, (*ppProp)->PropName) != 0)
	    {
	    nextProp = (*ppProp)->NextProp;
	    OFFFreeProperty(*ppProp);
	    (*ppProp) = nextProp;
	    return(0);
	    }
	ppProp = &((*ppProp)->NextProp);
	}

    fprintf(stderr,
	"OFFRemoveProperty: specified property not in property list\n");
    return(-1);

    }
@//E*O*F RemoveProp.c//
chmod u=rw,g=r,o= RemoveProp.c
echo x - WriteGeneric.c
sed 's/^@//' > "WriteGeneric.c" <<'@//E*O*F WriteGeneric.c//'

/*
 *
 * Description
 *	Write a generic data file.
 *
 * Output
 *
 * Input
 *	pProp		Pointer to property structure from which to get data
 *	fname		Full path/file name of file to be written
 *	type		File type (OFF_ASCII or OFF_BINARY)
 *
 * Diagnostics
 *	Returns 0 if successful, -1 if unsuccessful for any reason.
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */


#include <stdio.h>
#include <sys/file.h>
#include "off.h"

#define MAX_DATA_ITEMS	30

OFFWriteGeneric(pProp, fname, type)
    OFFProperty	*pProp;
    char	*fname;
    int		type;

    {
    FILE	*ascfd;
    int		binfd;
    long	npts;
    char	*ptr;
    int		i, j, k;
    long	*lptr;
    char	format[MAX_DATA_ITEMS][10];
    int		padding[MAX_DATA_ITEMS];
    int		size[MAX_DATA_ITEMS];
    int		datasize = 0;
    char	ch;
    char	bigstr[OFF_BIGSTR];
    int		nostrings = 1;
    long	strlength;
    long	code;
    long	zeros = 0;
    int		endpad;
    char	dstring[80];


/*  Open the file, punt if file open fails  */
    if (type == OFF_ASCII)
	{
	ascfd = fopen(fname, "w");
	if (ascfd == NULL)
	    {
	    fprintf(stderr, "OFFWriteGeneric: cannot open data file %s\n",
		    fname);
	    return(-1);
	    }
	}
    else
	{
	binfd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 0666);
	if (binfd < 0)
	    {
	    fprintf(stderr, "OFFWriteGeneric: cannot open data file %s\n",
		    fname);
	    return(-1);
	    }
	code = OFF_GENERIC_MAGIC;
	write(binfd, &code, sizeof(long));
	}


/*  Write out the number of data items for the list  */
    ptr = pProp->PropData;
    npts = *((long *) ptr);
    if (type == OFF_ASCII)
	fprintf(ascfd,"%d\n", npts);
    else
	write(binfd, &npts, sizeof(long));
    ptr += sizeof(long);

/*  Compute data size  */
    for (i = 0; i < strlen(pProp->DataFormat); i++)
	{
	switch (pProp->DataFormat[i])
	    {
	    case 'i': size[i] = sizeof(long);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%d\t");
		      break;
	    case 'f': size[i] = sizeof(float);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%g\t");
		      break;
	    case 'd': size[i] = sizeof(double);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%*.*g\t");
		      break;
	    case 'h': size[i] = sizeof(short);
		      padding[i] = ((datasize % 2) == 0) ? 0 : 1;
		      strcpy(format[i], "%d\t");
		      break;
	    case 'b': size[i] = sizeof(char);
		      padding[i] = 0;
		      strcpy(format[i], "%d\t");
		      break;
	    case 's': size[i] = sizeof(char *);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%s\t");
		      nostrings = 0;
		      break;
	    default:  fprintf(stderr, "OFFWriteGeneric: data format not ");
		      fprintf(stderr, "valid for generic data type\n");
		      return (-1);
	    }
	datasize += padding[i] + size[i];
	}

    endpad = ((datasize % 4) == 0) ? 0 : 4 - datasize % 4;
    datasize += endpad;


    if (type == OFF_ASCII)	/* Write info to the ascii file */
	{
	for(i = 0; i < npts; i++)
	    {
	    for(j = 0; j < strlen(pProp->DataFormat); j++)
		{
		ptr += padding[j];
		if (pProp->DataFormat[j] == 'f')
		    fprintf(ascfd, format[j], *((float *) ptr));
		else if (pProp->DataFormat[j] == 'i')
		    fprintf(ascfd, format[j], *((long *) ptr));
		else if (pProp->DataFormat[j] == 'd')
		    { /* I wouldn't have to do this if %g format worked right*/
		    sprintf(dstring, "%-30.30g", *((double *) ptr));
		    k = 0;
		    while (dstring[k] != ' ') k++;
		    fprintf(ascfd, format[j], k, k, *((double *) ptr));
		    }
		else if (pProp->DataFormat[j] == 'h')
		    fprintf(ascfd, format[j], *((short *) ptr));
		else if (pProp->DataFormat[j] == 's')
		    fprintf(ascfd, format[j], *((char **) ptr));
		else if (pProp->DataFormat[j] == 'b')
		    fprintf(ascfd, format[j], *((unsigned char *) ptr));

		ptr += size[j];
		}

	    fprintf(ascfd, "\n");
	    ptr += endpad;
	    }
	}

    else	/* Write info to the binary file */

	{
	if (nostrings)
	    write(binfd, ptr, datasize * npts); /* Write object vertex array */
	else
	    {
	    for(i = 0; i < npts; i++)
		{
		for(j = 0; j < strlen(pProp->DataFormat); j++)
		    {
		    ptr += padding[j];
		    if (padding[j] != 0)  write(binfd, &zeros, padding[j]);
		    if (format[j][1] != 's')
			write(binfd, ptr, size[j]);
		    else
			{
			strlength = strlen(*((char **) ptr)) + 1;
			write(binfd, &strlength, sizeof(long));
			write(binfd, *((char **) ptr), strlength);
			strlength = ((strlength % 4) == 0) ?
				0 : 4 - strlength % 4;
			write(binfd, &zeros, strlength);
			}
		    ptr += size[j];
		    }
		if (endpad != 0)  write(binfd, &zeros, endpad);
		ptr += endpad;
		}
	    }
	}

/*  Close the data file  */
    if (type == OFF_ASCII)
	fclose(ascfd);
    else
	close(binfd);

    return(0);
    }

@//E*O*F WriteGeneric.c//
chmod u=rw,g=r,o= WriteGeneric.c
echo x - WriteIndPoly.c
sed 's/^@//' > "WriteIndPoly.c" <<'@//E*O*F WriteIndPoly.c//'

/*
 *
 * Description
 *	Write an indexed_poly data file.
 *
 * Output
 *
 * Input
 *	pProp		Pointer to property structure from which to get data
 *	fname		Full path/file name of file to be written
 *	type		File type (OFF_ASCII or OFF_BINARY)
 *
 * Diagnostics
 *	Returns 0 if successful, -1 if unsuccessful for any reason.
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */


#include <stdio.h>
#include <sys/file.h>
#include "off.h"

#define MAX_DATA_ITEMS	30

OFFWriteIndexedPoly(pProp, fname, type)
    OFFProperty	*pProp;
    char	*fname;
    int		type;

    {
    FILE	*ascfd;
    int		binfd;
    long	npts, npolys, nconnects;
    char	*ptr, *ptr2;
    int		i, j, k;
    long	*lptr;
    char	format[MAX_DATA_ITEMS][10];
    int		padding[MAX_DATA_ITEMS];
    int		size[MAX_DATA_ITEMS];
    int		datasize = 0;
    char	ch;
    char	bigstr[OFF_BIGSTR];
    int		nostrings = 1;
    long	strlength;
    long	code;
    long	zeros = 0;
    int		endpad;
    char	dstring[80];
    long	wsize;


/*  Open the file, punt if file open fails  */
    if (type == OFF_ASCII)
	{
	ascfd = fopen(fname, "w");
	if (ascfd == NULL)
	    {
	    fprintf(stderr, "OFFWriteIndexedPoly: cannot open data file %s\n",
		    fname);
	    return(-1);
	    }
	}
    else
	{
	binfd = open(fname, O_CREAT | O_WRONLY | O_TRUNC, 0666);
	if (binfd < 0)
	    {
	    fprintf(stderr, "OFFWriteIndexedPoly: cannot open data file %s\n",
		    fname);
	    return(-1);
	    }
	code = OFF_INDEXED_POLY_MAGIC;
	write(binfd, &code, sizeof(long));
	}


/*  Write out the number of data items for the list  */
    ptr = pProp->PropData;
    lptr = (long *) ptr;
    npts = *lptr++;
    npolys = *lptr++;
    nconnects = *lptr++;
    ptr = (char *) lptr;
    if (type == OFF_ASCII)
	fprintf(ascfd,"%d\t%d\t%d\n", npts, npolys, nconnects);
    else
	{
	write(binfd, &npts, sizeof(long));
	write(binfd, &npolys, sizeof(long));
	write(binfd, &nconnects, sizeof(long));
	}

/*  Compute data size  */
    for (i = 0; i < strlen(pProp->DataFormat); i++)
	{
	switch (pProp->DataFormat[i])
	    {
	    case 'i': size[i] = sizeof(long);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%d\t");
		      break;
	    case 'f': size[i] = sizeof(float);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%g\t");
		      break;
	    case 'd': size[i] = sizeof(double);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%*.*g\t");
		      break;
	    case 'h': size[i] = sizeof(short);
		      padding[i] = ((datasize % 2) == 0) ? 0 : 1;
		      strcpy(format[i], "%d\t");
		      break;
	    case 'b': size[i] = sizeof(char);
		      padding[i] = 0;
		      strcpy(format[i], "%d\t");
		      break;
	    case 's': size[i] = sizeof(char *);
		      padding[i] = ((datasize % 4) == 0) ?
				0 : 4 - datasize % 4;
		      strcpy(format[i], "%s\t");
		      nostrings = 0;
		      break;
	    default:  fprintf(stderr, "OFFWriteIndexedPoly: data format not ");
		      fprintf(stderr, "valid for indexed_poly data type\n");
		      return (-1);
	    }
	datasize += padding[i] + size[i];
	}

    endpad = ((datasize % 4) == 0) ? 0 : 4 - datasize % 4;
    datasize += endpad;


    if (type == OFF_ASCII)	/* Write info to the ascii file */
	{
	for(i = 0; i < npts; i++)
	    {
	    for(j = 0; j < strlen(pProp->DataFormat); j++)
		{
		ptr += padding[j];
		if (pProp->DataFormat[j] == 'f')
		    fprintf(ascfd, format[j], *((float *) ptr));
		else if (pProp->DataFormat[j] == 'i')
		    fprintf(ascfd, format[j], *((long *) ptr));
		else if (pProp->DataFormat[j] == 'd')
		    { /* I wouldn't have to do this if %g format worked right*/
		    sprintf(dstring, "%-30.30g", *((double *) ptr));
		    k = 0;
		    while (dstring[k] != ' ') k++;
		    fprintf(ascfd, format[j], k, k, *((double *) ptr));
		    }
		else if (pProp->DataFormat[j] == 'h')
		    fprintf(ascfd, format[j], *((short *) ptr));
		else if (pProp->DataFormat[j] == 's')
		    fprintf(ascfd, format[j], *((char **) ptr));
		else if (pProp->DataFormat[j] == 'b')
		    fprintf(ascfd, format[j], *((unsigned char *) ptr));

		ptr += size[j];
		}

	    fprintf(ascfd, "\n");
	    ptr += endpad;
	    }
	ptr2 = ptr + sizeof(short) * npolys;
	for (i = 0; i < npolys; i++)
	    {
	    fprintf(ascfd, "%hd\t", *((short *) ptr));
	    for (j = 0; j < *((short *) ptr); j++)
		{
		fprintf(ascfd, "%hd    ", *((short *) ptr2));
		ptr2 += sizeof(short);
		}
	    fprintf(ascfd, "\n");
	    ptr += sizeof(short);
	    }
	}

    else	/* Write info to the binary file */

	{
	wsize = datasize * npts + sizeof(short) * (npolys + nconnects);
	if (nostrings)
	    write(binfd, ptr, wsize); /* Write object vertex array */
	else
	    {
	    for(i = 0; i < npts; i++)
		{
		for(j = 0; j < strlen(pProp->DataFormat); j++)
		    {
		    ptr += padding[j];
		    if (padding[j] != 0)  write(binfd, &zeros, padding[j]);
		    if (format[j][1] != 's')
			write(binfd, ptr, size[j]);
		    else
			{
			strlength = strlen(*((char **) ptr)) + 1;
			write(binfd, &strlength, sizeof(long));
			write(binfd, *((char **) ptr), strlength);
			strlength = ((strlength % 4) == 0) ?
				0 : 4 - strlength % 4;
			write(binfd, &zeros, strlength);
			}
		    ptr += size[j];
		    }
		if (endpad != 0)  write(binfd, &zeros, endpad);
		ptr += endpad;
		}
	    write(binfd, ptr, sizeof(short) * (npolys + nconnects));
	    }
	}

/*  Close the data file  */
    if (type == OFF_ASCII)
	fclose(ascfd);
    else
	close(binfd);

    return(0);
    }

@//E*O*F WriteIndPoly.c//
chmod u=rw,g=r,o= WriteIndPoly.c
echo x - WriteObj.c
sed 's/^@//' > "WriteObj.c" <<'@//E*O*F WriteObj.c//'

/*
 *
 * Description
 *	Write an OFF object data file.
 *
 * Output
 *
 * Input
 *	Obj		Pointer to object to be written
 *	FileName	Name of header file for object
 *	Directory	Name of directory in which to write file(s)
 *	FileType	File type (OFF_ASCII or OFF_BINARY)
 *
 * Diagnostics
 *	Returns 0 if successful, -1 if unsuccessful for any reason.
 *
 * Author
 *	Randi J. Rost
 *	Digital Equipment Corp.
 *	Workstation Systems Engineering
 *	Palo Alto, CA
 *
 * History
 *	17-Nov-86	Created
 *
 */

#include <stdio.h>
#include "off.h"

OFFWriteObj(Obj, FileName, Directory, FileType)
    OFFObjDesc	*Obj;			/* Object data structure to write */
    char	*FileName;		/* Name of file to be written */
    char	*Directory;		/* Directory in which to write */
    int		FileType;		/* Type of file (bin/ascii) to write */

    {
    FILE	*ObjFile;
    char	Name[OFF_BIGSTR];
    OFFProperty	*pProp;
    int		status;
    char	Dir[OFF_BIGSTR];



/*  Open the file for writing  */
    strcpy(Dir, Directory);
    writeglob(Dir);
    strcat(Dir, "/");
    strcpy(Name, Dir);
    strcat(Name, FileName);
    ObjFile = fopen(Name,"w");

/*  If that doesn't work, punt  */
    if (ObjFile == NULL)
	{ 
	fprintf(stderr, "OFFWriteObj:  cannot open %s\n", Name);
	return(-1);
	}

/*  Initialize fields in object structure  */
    pProp = Obj->FirstProp;

/*  Write lines to the header file  */
    while(pProp != NULL)
	{
	status = WriteProperty(ObjFile, pProp, Dir, FileType);
	if (status != 0)
	    {
	    fprintf(stderr, "OFFWriteObj: problem writing property %s\n",
		pProp->PropName);
	    return(-1);
	    }
	pProp = pProp->NextProp;
	}

    fclose(ObjFile);
    return(0);
    }




static int WriteProperty(ObjFile, pProp, Directory, FileType)
    FILE	*ObjFile;
    OFFProperty	*pProp;
    char	*Directory;
    int		FileType;

    {
    long	*iptr;
    short	*hptr;
    float	*fptr;
    double	*dptr;
    char	*ptr;
    int		i, j;
    int		ntabs;
    char	filename[OFF_BIGSTR];


    if (strcmp(pProp->PropName, "comment") == 0)
	{
	fprintf(ObjFile, "%s\n", pProp->PropData);
	return(0);
	}

    if (strcmp(pProp->PropName, "nl") == 0)
	{
	fprintf(ObjFile, "\n");
	return(0);
	}

    if (strcmp(pProp->PropName, "name") == 0 ||
	strcmp(pProp->PropName, "author") == 0 ||
	strcmp(pProp->PropName, "type") == 0)
	{
	fprintf(ObjFile, "%s\t\t%s\n", pProp->PropName, pProp->PropData);
	return(0);
	}

    if (strcmp(pProp->PropName, "description") == 0 ||
	strcmp(pProp->PropName, "copyright") == 0)
	{
	fprintf(ObjFile, "%s\t%s\n", pProp->PropName, pProp->PropData);
	return(0);
	}

    else
	{
	fprintf(ObjFile, "%s", pProp->PropName);
	ntabs = (strlen(pProp->PropName) < 8) ? 2 : 1;
	for (i = 0; i < ntabs; i++) fprintf(ObjFile, "\t");
	switch (pProp->PropType)
	    {
	    case OFF_DEFAULT_DATA:
		fprintf(ObjFile, "default\t\t"); break;
	    case OFF_INDEXED_POLY_DATA:
		fprintf(ObjFile, "indexed_poly\t"); break;
	    case OFF_GENERIC_DATA:
		fprintf(ObjFile, "generic\t\t"); break;
	    default:
		fprintf(ObjFile, "unknown\t\t"); break;
	    }
	fprintf(ObjFile, "%s", pProp->DataFormat);
	ntabs = (strlen(pProp->DataFormat) < 8) ? 2 : 1;
	for (i = 0; i < ntabs; i++) fprintf(ObjFile, "\t");
	switch(pProp->PropType)
	    {
	    case OFF_DEFAULT_DATA:
		ptr = pProp->PropData;
		for (i = 0; i < strlen(pProp->DataFormat); i++)
		    {
		    switch (pProp->DataFormat[i])
			{
			case 'i':
			     /* Make sure we're aligned on word boundary */
			     ptr += (((int) ptr % 4) == 0) ?
				0 : 4 - (int) ptr % 4;
			     iptr = (long *) ptr;
			     fprintf(ObjFile, "%d ", *iptr);
			     ptr += sizeof(long);
			     break;
			case 'b':
			     fprintf(ObjFile, "%ud ", (unsigned char) *ptr);
			     ptr++; 
			     break;
			case 'd':
			     /* Make sure we're aligned on word boundary */
			     ptr += (((int) ptr % 4) == 0) ?
				0 : 4 - (int) ptr % 4;
			     dptr = (double *) ptr;
			     fprintf(ObjFile, "%g ", *dptr);
			     ptr += sizeof(double);
			     break;
			case 'h':
			     /* Make sure we're aligned on halfword boundary */
			     ptr += (((int) ptr % 2) == 0) ? 0 : 1;
			     hptr = (short *) ptr;
			     fprintf(ObjFile, "%d ", *hptr);
			     ptr += sizeof(short);
			     break;
			case 'f':
			     /* Make sure we're aligned on word boundary */
			     ptr += (((int) ptr % 4) == 0) ?
				0 : 4 - (int) ptr % 4;
			     fptr = (float *) ptr;
			     fprintf(ObjFile, "%g ", *fptr);
			     ptr += sizeof(float);
			     break;
			case 's':
			     j = 0;
			     fprintf(ObjFile, "%s ", *((char **) ptr));
			     ptr += strlen(ptr) + 1;
			     break;
			default:
			     return(-1);
			} /* switch */
		    } /* for */
		fprintf(ObjFile, "\n");
		break;

	    case OFF_GENERIC_DATA:
		fprintf(ObjFile, "%s\n", pProp->PropFileName);
		strcpy(filename, Directory);
		strcat(filename, pProp->PropFileName);
		if ((i = OFFWriteGeneric(pProp, filename, FileType)) != 0)
		    return(-1);
		break;

	    case OFF_INDEXED_POLY_DATA:
		fprintf(ObjFile, "%s\n", pProp->PropFileName);
		strcpy(filename, Directory);
		strcat(filename, pProp->PropFileName);
		if (i = OFFWriteIndexedPoly(pProp, filename, FileType) != 0)
		    return(-1);
		break;

	    default:
		return(-1);
		break;
	    } /* switch */
	}

    return(0);
    }


#include <pwd.h>
#include <ctype.h>

static writeglob(string)
    char	*string;

    {
    struct	passwd	*p, *getpwname();
    char	str1[160], str2[160];
    char	*pstr;
    char	*index(), *getenv();

   
/*  If first character is '~', expand it  */
    if (string[0] == '~')

	{

	strcpy(str1, string);

    /*  ~/... means use home directory  */
	if (string[1] == '/')
	    {
	    strcpy(string, getenv("HOME"));
	    strcat(string, &(str1[1]));
	    }

    /*  ~whatever/... means use whatever's home directory  */
	else
	    {
	    pstr = index(str1, '/');
	    strncpy(str2, &(string[1]), pstr - str1 - 1);
	    str2[pstr - str1 - 1] = 0;
	    p = getpwnam(str2);
	    strcpy(string, p->pw_dir);
	    strcat(string, pstr);
	    }

	}

/*  if directory is '/' (root) return null so concatenation works right  */
    else if (strcmp(string, "/") == 0)
	strcpy(string, "");

/*  if directory is null, return "." so concatenation works right */
    else if (strlen(string) == 0)
	strcpy(string, ".");
    }


@//E*O*F WriteObj.c//
chmod u=rw,g=r,o= WriteObj.c
echo x - makefile
sed 's/^@//' > "makefile" <<'@//E*O*F makefile//'

#-------------#
# Definitions #
#-------------#

# Debug flags

CDEBUGFLAGS=


# Project name

GLIB_NAME=	liboff.a


# Distribution paths

GINC_PATH=	.


# Global library members

GLIB_MEMS=	AddProp.o CreateObj.o DestroyObj.o FreeProp.o \
		ReadGeneric.o ReadIndPoly.o ReadObj.o RemoveProp.o \
		WriteGeneric.o WriteIndPoly.o WriteObj.o


# Global include files

GINCS=		$(GINC_PATH)/off.h


# Local include files

LINCS=


# Compilation flags

CFLAGS=		-I$(GINC_PATH)



#--------------------#
# Installation Rules #
#--------------------#

project:	$(GLIB_MEMS)
	ar qv $(GLIB_NAME) $(GLIB_MEMS)
	ranlib $(GLIB_NAME)

$(GLIB_MEMS):	$(GINCS) $(LINCS)



#------------------------#
# Installation Utilities #
#------------------------#

clean:
	rm -f *.o

print:
	pr $(LDOCS) $(LINCS) *.c makefile | lpr
@//E*O*F makefile//
chmod u=rw,g=r,o= makefile
echo x - off.h
sed 's/^@//' > "off.h" <<'@//E*O*F off.h//'


#define OFF_INDEXED_POLY_MAGIC	0xFEEDFEED
#define OFF_GENERIC_MAGIC	0xBEEFBEEF

#define OFF_BIGSTR		256
#define OFF_SMSTR		40

#define	OFF_ASCII		0
#define	OFF_BINARY		1


/* Types of data for object properties  */

#define OFF_UNKNOWN_TYPE_DATA	0
#define OFF_STANDARD_DATA	1
#define OFF_COMMENT_DATA	2
#define OFF_DEFAULT_DATA	3
#define OFF_GENERIC_DATA	4
#define OFF_INDEXED_POLY_DATA	5


typedef struct _OFFProp
    {
    char	PropName[OFF_SMSTR];	/* Name of property (or attribute)   */
    int		PropType;		/* Type of data for property         */
    char	PropFileName[OFF_BIGSTR];/* Name of file that has prop data */
    char	DataFormat[OFF_SMSTR];	/* Pointer to property data format   */
    int		PropCount;		/* Number of data items for property */
    char	*PropData;		/* Pointer to property data	     */
    struct _OFFProp *NextProp;		/* Pointer to next property in list  */
    } OFFProperty;

typedef struct
    { 
    OFFProperty	*FirstProp;		/* Pointer to first property in list */
    } OFFObjDesc;

OFFObjDesc *OFFCreateObj();
OFFProperty *OFFAddProperty();
@//E*O*F off.h//
chmod u=rw,g=r,o= off.h
echo x - off.ms
sed 's/^@//' > "off.ms" <<'@//E*O*F off.ms//'
.nr VS 14
.nr PO 1.25i
.nr LL 6.0i
.nr HM 1.25i
.nr FM 1.25i
.TL
OFF - A 3D Object File Format
.AU
Randi J. Rost
6-November-1986
.AI
Digital Equipment Corporation
Workstation Systems Engineering
100 Hamilton Ave.
Palo Alto, Ca. 94301
.DA
.AB no
This document describes the data format developed by WSE for the
interchange and archiving of three-dimensional objects.
This format, called OFF (for Object File Format), is general,
flexible, and extensible.  It supports ASCII text versions of
objects for the purpose of interchange, and binary versions for
efficiency of reading and writing.  It is assumed that applications
will develop their own, more efficient format for internal storage
and operation on three-dimensional objects.
.AE
.PP
.NH 1
Introduction
.PP
One of the most time-consuming tasks in computer animation projects
is designing the 3D models that will be used.  Many computer animation
houses have found that owning a large number of databases makes it
easier for them to take on new projects at a lower cost (time and $$$).
The cost of initially creating an object can be amortized over the
number of times it can be re-used.
It is our intention to promote the use of OFF files within (and perhaps
even outside of) Digital in an effort to build up our collection of useful
3D models.
.PP
The file format itself is not limiting:  OFF files can be used for
a wide variety of object types.  None of the "policy decisions" are
hard-wired in the design of the file format, or in the support library
routines that allow reading and writing of OFF files.
Rather, the policy decisions have been left
up to the designers since the format supports "generic" object definitions.
We have developed specific conventions for objects that are defined
by polygons for use within WSE, and we'd encourage others to adopt
these conventions as
well in order to promote the interchange of useful object data bases.
The XModel application (also
developed by WSE) is an example of an application that permits
reading and writing of OFF files.
.PP
This paper describes the Object File Format itself, the conventions
we've adopted at WSE, and the library of support routines that can
be used to read and write OFF files.
.PP
.NH 1
Design Goals and Non-Goals
.LP
Design goals for the Object File Format include:
.IP 1)
\fBSimple.\fP  Simple cases should be simple.  It should be possible
to type in all the data required for a simple object (such as a cube)
by hand.
.IP 2)
\fBPowerful.\fP  The Object File Format should be capable of
accomodating complicated objects (many vertices, polygons, and
a wide range of attributes).
.IP 3)
\fBPortable.\fP  ASCII text file representation required for portability
across operating systems and hardware.  This also allows
operations on OFF files by familiar system utilities
(text editors and the like).
.IP 4)
\fBEfficient.\fP  Binary text file representation required to allow
efficient reading and writing of OFF files.  It is assumed that reading/writing
an object is a costly operation, but reading and writing ASCII data is just
\fItoo\fP slow.
.IP 5)
\fBGeneral.\fP  The format should address the general case of the
three-dimensional object, not a single particular case.
.IP 6)
\fBExtensibile.\fP  Make sure the format can be easily extended to
eventually support other primitives such as bezier patches.
.IP 7)
\fBNo Favors.\fP  Avoid hard-wiring policy decisions.  Rather, provide
generic building blocks capable of supporting several styles and document
a set of strongly encouraged conventions that we have adopted.
.LP
There are also things that were specifically non-goals in the design
of the Object File Format.
.IP 1)
\fBInternal Format.\fP
The Object File Format is not intended to be forced upon applications
as an internal format that must be used.  Rather, it should be considered
a means for inputting and outputting object descriptions in a 
device-, language-, and operating system-independent format.
Applications should feel free to develop and maintain the most useful/efficient
data format they can, and only convert to/from OFF when input or output
of a standardized object is desired.
.IP 2)
\fBObject Conventions.\fP
OFF conventions are documented only for objects in polygonal form at
this point.  It is anticipated
that the Object File Format can be easily extended to handle bezier
surface patches and other primitives in the future.
.NH 1
Objects
.PP
For the purposes of the Object File Format, we'll adopt a very general
definition of an \fIobject\fP.
An \fIobject\fP is simply a list of properties
(name, description, author, copyright information, geometry data, colors,
etc.)
.PP
The most important information about the object can be found in the
\fIheader file\fP for the object.  The header file is always an ASCII text file
that, by convention, is named \fIname\fP.off where \fIname\fP is the
object name.  See Appendix B for an example of an OFF object header file.
.PP
A few of these properties (name, description, author, copyright, type)
are common to every type of 3D object and are considered standard
properties.  The standard properties are built into the routines
that manipulate 3D objects.  The rest of the properties may vary
with the type of object, and so are defined by convention only.
.PP
The \fIname\fP of an object is used to concisely describe the object
itself.  For example, we have objects named "x29", "banana" and "vw".
By convention, this name also becomes the prefix for OFF data filenames
when an object is read or written, so it is best to keep it fairly short.
.PP
The \fIdescription\fP is used to more fully describe the object itself.
It may contain the time and date of creation or more prose describing
the object.
.PP
The \fIauthor\fP should be the name of the person (or company, or utility) that
created the object.  We should always try to give credit where credit is due.
This field tells you who to thank for spiffy objects or whose cage to rattle
when a problem with an OFF file is discovered.
.PP
The \fIcopyright\fP field contains information dealing with the distribution
of the object data.  Some object databases will be regarded
by a company as proprietary.  These objects should not be copied or
distributed without consent.  Other objects (vw, x29) were developed
by companies or individuals and can be copied or used as long as the
copyright notice appears and proper credit is given.  Still other objects
(cube, sphere, etc.) have been placed in the public domain.  We have
tried to be as careful as possible in preserving copyright and author
information for the objects we have collected, but sometimes the information
was lost or unavailable.  Be sure and honor copyright notices.  If you don't,
you (or your company) could end up in big trouble.
.PP
The \fItype\fP field contains the type of the object.  For now, only
two types of objects are supported: polyline objects and polygon objects.
It is anticipated that surface patch-type objects will be supported in
the future as well.
.PP
Also contained in the object header file are lines that describe
the various properties of the object.  Each line in the object header
file that describes an attribute of the object other than a standard
attribute must contain the
property name, the property type, the data format
and either a file name or a string containing default data,
depending on the property type.  Each of these four items is
an ASCII string, separated by white space.
.PP
The \fIproperty name\fP uniquely describes the property.  Property
names for which we have defined conventions (see Appendix A) include
geometry, polygon_colors, vertex_colors, back_faces, vertex_order,
normals, diffuse_coef, specular_coef, and specular_power.
.PP
OFF currently supports three \fIproperty types\fP:
\fIdefault\fP, \fIgeneric\fP,
and \fIindexed_poly\fP.  If a property is indicated to be of type
\fIdefault\fP, the part of the line after the data format is assumed
to contain some default data that will be applied to the entire object.
For instance, it may make sense to give the entire object a default color
and default diffuse and specular coefficients.
.PP
If the property type is either generic or indexed_poly (described more
fully below), the remainder of the line is taken to be a file name that
can be opened and read to obtain the information for the property.
.PP
The data format indicates what type of data will be found on the remainder
of the line if the property type is default, otherwise what kind of
data will be found in the specified file.  The data format is a
string of characters (no spaces) that indicate the order and type of
the data.  Supported primitive data types are:
.IP "f -"
A number stored internally as a 32-bit floating point number
.IP "d -"
A number stored internally as a 64-bit double-precision floating point number
.IP "i -"
A number stored internally as a 32-bit integer value
.IP "h -"
A number stored internally as a 16-bit integer value
.IP "b -"
A number stored internally as an 8-bit integer value
.IP "s -"
A 32-bit pointer to a null-terminated string of characters
.PP
If, for instance, you were interested in using 32-bit floating values
for r, g, and b default color values, you might have a line in the
object header file that reads
.DS
polygon_colors   default   fff   1.0   0.8   0.0
.DE
.PP
It is important to understand that in all cases, the "string" (s) data
primitive will indicate a pointer to a string
that is stored internally in the data block for an object and not
the string itself.
.NH 1
ASCII Property Files
.PP
OFF supports ASCII text files as a way of providing for language-, hardware-,
and operating system-independent object data files.  The two types of
data files currently supported by OFF are generic and indexed_poly.
.NH 2
Generic Files
.PP
Generic files contain only a \fIcount\fP value followed by \fIcount\fP
data items of the
type specified by the data format.
Each data item can be comprised of some combination of the primitive data
types described in Section 3.
Generic data files are useful for storing attributes
which are unique at every vertex or polygon (such as color).
.PP
String data items in ASCII generic files may not contain spaces or
other white space.  8-bit integers must be listed in the range 0-255.
.PP
See Appendix D for an example of an ASCII generic data file.
.PP
.NH 2
Indexed_Poly Files
.PP
Indexed_poly files take advantage of a connectivity list to reduce
the amount of information needed
to store a list of polylines, polygons, or normals.
The unique geometry items (vertices or normals) are listed in the first
part of the file.  Following this list is a connectivity list.  Each line
in the connectivity list contains a \fIcount\fP value
followed by \fIcount\fP indices (pointers)
to information in the geometry list.  (Items in the geometry list are
indexed starting from 1, not 0.)
.PP
The first line of an indexed_poly data file contains three integers,
separated by white space.  The first number on this line indicates
the number of data items (vertices/normals) that follow, the second number
indicates the number of polylines/polygons that follow the
data list, and the
third indicates the total number of edges that are contained
in the polyline/polygon connectivity list.
.PP
String data items in ASCII indexed_poly files may not contain spaces or
other white space.  8-bit integers must be listed in the range 0-255.
.PP
See Appendix C for an example of an ASCII indexed_poly file.
.PP
.NH 1
Binary OFF Files
.PP
The same two types of data files described above are also supported
in binary format.  There are a few minor differences.
.PP
.NH 2
Generic Files
.PP
Binary generic files begin with the first 32-bit word equal to 
OFF_GENERIC_MAGIC as defined in the include file \fIoff.h\fP.
The second word in the file is the \fIcount\fP (number of data items in
the file).  Following the \fIcount\fP is the data itself.
.PP
The data format in the header file describes the primitives that
make up each data item in the list.
Floats, doubles,
32-bit integers, and string pointers will all begin on a word boundary.
16-bit integers will all begin on a half-word boundary.  Strings begin
with a 32-bit \fIcount\fP followed by \fIcount\fP characters followed by a null
character.  The string is null-padded so it will end on a word boundary.
.PP
(It is assumed that for strings, the length will be read and then
the necessary memory will be allocated and the string read in.  This eliminates
a problem with having variable-length data in the data files.  Anyway,
strings in files are really only there for symmetry with default values,
where strings are really useful.  The performance implications for files
containing strings will probably be enough to prevent people from using
them.)
.PP
In addition, each data item will begin on a word boundary.  Thus, if
your data format for the data items in a generic data file is "bbb"
(three byte values), each data item will be stored as three bytes followed
by a null byte so that each data item wil begin on a word boundary.
.PP
.NH 2
Indexed_Poly Files
.PP
Binary indexed_poly files begin with the first 32-bit word equal to 
OFF_INDEXED_POLY_MAGIC as defined in the include file off.h.
The second word is the number of data items
in the vertex/normal list (\fInpts\fP),
the third word is the number of polylines/polygons
in the list (\fInpolys\fP), and
the fourth word is the number of edges contained in
the connectivity list (\fInconnects\fP).
.PP
Starting at the fifth word in the file
is a list of \fInpts\fP data items, followed by \fInpolys\fP short integers
containing polyline/polygon vertex counts, followed by \fInconnects\fP
short integers which are indices into the array of data items.  (This
arrangement is slightly different than that used for indexed_poly files
in ASCII format for efficiency reasons.)
.PP
The same restrictions that
apply to the data types for generic binary files apply to indexed_poly
binary files as well.  In addition, the vertex count array which
follows the geometry data in an indexed_poly file will always begin
on a word boundary.  The connectivity array that follows the vertex
count array will not necessarily start on a word boundary, but will
always begin \fInpolys * sizeof(short)\fP bytes after the start of the
vertex count array.
.PP
.NH 1
Liboff.a and Objects.h
.PP
An include file and a library of routines has been provided for UNIX/C
programmers to more easily manipulate OFF files.  The basic concepts
of "reading" and "writing" OFF files are supported in this library
of routines.  The library is a software layer on top of
the operating system file I/O interface, with special knowledge of
OFF files.  This subroutine library provides a mechanism for accessing
the syntactical elements of an object file, but makes no attempt to understand
the semantics.  Higher level interfaces can be layered on top.
.PP
The subroutine library refers to an object as a pointer to an
\fIOFFObjDesc\fP.  This structure contains
a pointer to the first property in the
property list.  It is defined as follows:
.DS
typedef struct
    { 
    OFFProperty	*FirstProp;	/* Pointer to first property in list */
    } OFFObjDesc;
.DE
.PP 
The information that describes the object is contained in a linked
list of property structures.  The first such structure in the list
is pointed at by an \fIOFFObjDesc\fP structure.  The property structures
have the form:
.DS
typedef struct _OFFProp
    {
    char	PropName[40];
    int		PropType;
    char	PropFileName[256];
    int		PropCount;
    char	DataFormat[40];
    char	*PropData;
    struct _OFFProp *NextProp;
    } OFFProperty;
.DE
.PP
\fIPropName\fP contains a string defining one of the property types
for which a convention has been defined.
This includes the property names
"name", "author", "description", "copyright", "comment",
"geometry", "polygon_color", "normals", etc.  For a complete
list of property names, see Appendix A.
(The special attribute type "comment" is 
supported so that blank lines and comment lines can be preserved if 
an object file is read and then written.)
.PP
The \fIPropType\fP field contains a value equal to \fIOFF_DEFAULT_DATA\fP,
\fIOFF_GENERIC_DATA\fP, or \fIOFF_INDEXED_POLY_DATA\fP which defines
the basic type for the property.
.PP
The \fIPropFileName\fP is required if \fIPropType\fP is something
other than \fIOFF_DEFAULT_DATA\fP.  It contains a string representing the name
of the file to be read/written for this attribute.  This file name
should \fInot\fP contain a path leading up to the file itself, only
the actual file name.  The object search path mechanism (see Section
7) should be used instead.
.PP
The \fIPropCount\fP indicates the actual number of data items associated
with this particular attribute.  After reading in an object, properties
of type \fIOFF_DEFAULT_DATA\fP will have a \fIPropCount\fP of one, properties
of type \fIOFF_GENERIC_DATA\fP will have a \fIPropCount\fP equal to the number
of generic data items in the list, and properties of type
\fIOFF_INDEXED_POLY_DATA\fP
will have a \fIPropCount\fP equal to the number of data items in the
geometry list.
.PP
The \fIDataFormat\fP field contains a string of characters corresponding
to primitive data items.  The composite type of the data for this property
can then be deduced by looking at this field and applying the rules for
padding to word and half-word boundaries.
.PP
The \fIPropData\fP field contains a pointer to a block of memory containing
the actual data for this property.  This data will have the same
data alignment restrictions as a binary file has, with the exception
of strings.  As strings are read in, memory is malloc'ed to hold them
and a pointer to the string is stored in the appropriate field in the
data list.  This means that all primitive data types will have a fixed
size and lengths and alignments can be computed more easily.
.PP
The \fINextProp\fP field contains a pointer to the next property structure
in the property list.
.PP
The routines contained in the subroutine library are defined below.
.sp 1
.LP
.nf
\fB#include "off.h"\fP
.sp 1
int \fBOFFReadObj\fP(Obj, FileName)
	OFFObjDesc *Obj;
	char *FileName;
.sp 1
int \fBOFFWriteObj\fP(Obj, FileName, Directory, FileType);
	OFFObjDesc *Obj;
	char *FileName;
	char *Directory;
	int FileType;
.sp 1
int \fBOFFReadGeneric\fP(Property, FileName)
	OFFProperty *Property;
	char *FileName;
.sp 1
int \fBOFFWriteGeneric\fP(Property, FileName, FileType)
	OFFProperty *Property;
	char *FileName;
	int FileType;
.sp 1
int \fBOFFReadIndexedPoly\fP(Property, FileName)
	OFFProperty *Property;
	char *FileName;
.sp 1
int \fBOFFWriteIndexedPoly\fP(Property, FileName, FileType)
	OFFProperty *Property;
	char *FileName;
	int FileType;
.sp 1
OFFObjDesc *\fBOFFCreateObj\fP()
.sp 1
int \fBOFFDestroyObj\fP(Obj)
	OFFObjDesc *Obj;
.sp 1
OFFProperty *\fBOFFAddProperty\fP(Obj)
	OFFObjDesc *Obj;
.sp 1
int \fBOFFRemoveProperty\fP(Obj, PropertyName)
	OFFObjDesc *Obj;
	char *PropertyName;
.sp 1
int \fBOFFFreeProperty\fP(Property)
	OFFProperty *Property;
.sp 1
.PP
\fIOFFReadObj\fP will attempt to open the object header file named
\fIFileName\fP
and read the object data it contains.  A pointer to the constructed
object structure will be returned in \fIObj\fP when the object has been read.
An attempt will be made to open the specified file first as given,
then concatenated in turn with each of
the directories specified by the environment
search path variable \fIOBJ_PATH\fP.
The property list for the object is built as the file is
read.  Upon return, the client need only traverse the
property list and select the data it needs.  This routine
calls \fIOFFReadGeneric\fP and \fIOFFReadIndexedPoly\fP 
in order to read associated data files.
\fIOFFReadObj\fP will return 0 if the read operation was successful,
-1 otherwise.
.PP
\fIOFFWriteObj\fP will attempt to write the object pointed at by
\fIObj\fP using the filename specified by \fIFileName\fP.  The
file will be written in the directory indicated by \fIDirectory\fP.
If \fIFileType\fP is \fIOFF_ASCII\fP, the file will be written as an ASCII
text OFF file.  If \fIFileType\fP is \fIOFF_BINARY\fP, the file will
be written as a binary OFF file.
The property list for the object is traversed and each
property of the object is written out in turn.
This routine
calls \fIOFFWriteGeneric\fP and \fIOFFWriteIndexedPoly\fP 
in order to write associated data files.
\fIOFFWriteObj\fP will return 0 if the write operation was successful,
-1 otherwise.
.PP
\fIOFFReadGeneric\fP will read the generic data file named \fIFileName\fP
(here \fIFileName\fP contains the full path name) into the
property structure pointed at by \fIProperty\fP.
This routine will allocate the space it needs in order
to read in the data.  A pointer to this allocated data space
will be stored in the \fIPropData\fP field of the specified
\fIproperty\fP as described earlier.
The entire object, including all
allocated memory resources
can later be deallocated by calling \fIOFFDestroyObj\fP.
This routine will not typically be called directly by applications.
\fIOFFReadGeneric\fP will return 0 if the read operation was successful,
-1 otherwise.
.PP
\fIOFFWriteGeneric\fP will write the generic data associated with
\fIProperty\fP into the file \fIFileName\fP (here \fIFileName\fP
contains the full path name of the file to be written).  
If \fIFileType\fP is \fIOFF_ASCII\fP, the file will be written as an ASCII
text generic data file.  If \fIFileType\fP is \fIOFF_BINARY\fP, the file will
be written as a binary generic data file.
This routine will not typically be called directly by applications.
\fIOFFWriteGeneric\fP will return 0 if the write operation was successful,
-1 otherwise.
.PP
\fIOFFReadIndexedPoly\fP will read the indexed_poly data file named
\fIFileName\fP (here \fIFileName\fP contains the full path name) into the
property structure pointed at by \fIProperty\fP.
This routine will allocate the space it needs in order
to read in the data.  A pointer to this allocated data space
will be stored in the \fIPropData\fP field of the specified
\fIproperty\fP as described earlier.
The entire object, including all
allocated memory resources
can later be deallocated by calling \fIOFFDestroyObj\fP.
This routine will not typically be called directly by applications.
\fIOFFReadIndexedPoly\fP will return 0 if the read operation was successful,
-1 otherwise.
.PP
\fIOFFWriteIndexedPoly\fP will write the indexed_poly data associated with
\fIProperty\fP into the file \fIFileName\fP (here \fIFileName\fP contains
the full path name of the file to be written).  
If \fIFileType\fP is \fIOFF_ASCII\fP, the file will be written as an ASCII
text indexed_poly data file.
If \fIFileType\fP is \fIOFF_BINARY\fP, the file will
be written as a binary indexed_poly data file.
This routine will not typically be called directly by applications.
\fIOFFWriteIndexedPoly\fP will return 0 if the write operation was successful,
-1 otherwise.
.PP
\fIOFFCreateObj\fP allocates and initializes an \fIOFFObjDesc\fP structure
A pointer to the newly-created structure is returned.  The null pointer is
returned if the operation was unsuccessful.
.PP
\fIOFFDestroyObj\fP deallocates all memory resources associated with
the object pointed at by \fIObj\fP.  It works by calling
\fIOFFFreeProperty\fP for each property in the property list for
the specified object.
.PP
\fIOFFAddProperty\fP adds a property structure to the property list
associated with the object pointed at by \fIObj\fP, initializes it,
and returns a pointer to it.
The null pointer is returned if the operation was unsuccessful.
.PP
\fIOFFRemoveProperty\fP deletes the named property 
from the object pointed at by \fIObj\fP.  This routine returns
-1 if the named property is not found in the property list for the
specified object.
.PP
\fIOFFFreeProperty\fP frees all the memory resources allocated to the
property structure specified by \fIProperty\fP as well as the property
structure itself.  This routine will not typically be called directly
by applications.
.NH 1
Object Search Path
.PP
It is important to avoid embedding path names in object files.
When an object is transported to another system, chances are slim that the same
directory structure will exist.  The \fIOFFReadObj\fP routine in
libobj.a knows about an environment variable named \fIOBJ_PATH\fP
that is used to overcome this problem.
.PP
When an object is read, an attempt is first made to open it in the
current working directory.  If that attempt fails, the directories
specified in the \fIOBJ_PATH\fP variable are tried in turn until
the file is successfully opened or the directory list is exhausted.
.PP
The name of the directory where a successful open operation occurred
is used for opening associated data files as well.  This means that
all of the data files for a particular object must reside in the same
directory.
.PP
It is hoped that in this way, users will be able to draw on one or
more collections of "standard" objects in addition to their own private
collections of objects.
.bp
.sp 6
.NH 1
Appendix A: Conventions for Polygonal Objects
.PP
This list contains the conventions we have adopted for describing
3D polygonal objects which are defined in some three-dimensional
model coordinate system.  Items in regular type are string
literal, printed as they would appear in an OFF file, and
item in italics are indicate data values that will vary from
object to object.
.PP
.TS
box;
c|c|c|c|c|c
l|l|l|l|l|l.
Property	Type	Format	Defaults	ASCII filename	Binary Filename
=
name	*****	*****	\fIobjname\fP	*****	*****
author	*****	*****	\fIauthor\fP	*****	*****
description	*****	*****	\fIdescription\fP	*****	*****
copyright	*****	*****	\fIcopyright\fP	*****	*****
type	*****	*****	polyline	*****	*****
	*****	*****	polygon	*****	*****
geometry	indexed_poly	fff	*****	\fIname\fP.geom	\fIname\fP.bgeom
polygon_colors	generic	fff	*****	\fIname\fP.pcol	\fIname\fP.bpcol
vertex_colors	generic	fff	*****	\fIname\fP.vcol	\fIname\fP.bvcol
back_faces	default	s	cull	*****	*****	
			display	*****	*****	
			reverse	*****	*****	
vertex_order	default	s	clockwise	*****	*****	
			counter-clockwise	*****	*****	
normals	indexed_poly	fff	*****	\fIname\fP.norm	\fIname\fP.bnorm
diffuse_coef	default	f	\fIvalue\fP	*****	*****
specular_coef	default	f	\fIvalue\fP	*****	*****
specular_power	default	f	\fIvalue\fP	*****	*****
bounding_box	default	ffffff	\fIvalue\fP	*****	*****
.TE
.bp
.NH 1
Appendix B: OFF Header File For a Cube (cube.off)
.PP
.sp 6
.TS
;
l l.
name	cube
author	Randi J. Rost
description	cube with sides of red, green, blue, cyan, yellow, magenta
copyright	public domain
type	polygon
.TE
.TS
;
l c c c
l c c c
l l l l.
# Prop.	data type	format	filename or default data
#_______	_________	______	________________________
.sp 1
geometry	indexed_poly	fff	cube.geom
vertex_order	default	s	clockwise
polygon_colors	generic	fff	cube.pcol
back_faces	default	s	cull
.TE
.bp
.sp 6
.NH 1
Appendix C: Listing of cube.geom
.PP
.sp 6
.TS
;
nw(0.5i) nw(0.5i) nw(0.5i) nw(0.5i) nw(0.5i).
8	6	24
-1.0	-1.0	1.0
-1.0	1.0	1.0
1.0	1.0	1.0
1.0	-1.0	1.0
-1.0	-1.0	-1.0
-1.0	1.0	-1.0
1.0	1.0	-1.0
1.0	-1.0	-1.0
4	1	2	3	4
4	5	6	2	1
4	3	2	6	7
4	3	7	8	4
4	1	4	8	5
4	8	7	6	5
.TE
.bp
.sp 6
.NH 1
Appendix D: Listing of cube.pcol
.PP
.sp 6
.TS
;
l s s
nw(0.5i) nw(0.5i) nw(0.5i).
6		
1.0        0.0        0.0
0.0        1.0        0.0
0.0        0.0        1.0
0.0        1.0        1.0
1.0        1.0        0.0
1.0        0.0        1.0
.TE
.bp
.sp 6
.NH 1
Appendix E: Listing of off.h
.LP
.sp 2
.nf
\f8#define OFF_INDEXED_POLY_MAGIC  0xFEEDFEED
#define OFF_GENERIC_MAGIC       0xBEEFBEEF

#define OFF_BIGSTR              256
#define OFF_SMSTR               40

#define	OFF_ASCII               0
#define	OFF_BINARY              1


/* Types of data for object properties  */

#define OFF_UNKNOWN_TYPE_DATA   0
#define OFF_STANDARD_DATA       1
#define OFF_COMMENT_DATA        2
#define OFF_DEFAULT_DATA        3
#define OFF_GENERIC_DATA        4
#define OFF_INDEXED_POLY_DATA   5


typedef struct _OFFProp
    {
    char        PropName[OFF_SMSTR];     /* Name of property (or attribute)  */
    int         PropType;                /* Type of data for property        */
    char        PropFileName[OFF_BIGSTR];/* Name of file that has prop data  */
    char        DataFormat[OFF_SMSTR];   /* Pointer to property data format  */
    int         PropCount;               /* Number of data items for property*/
    char        *PropData;               /* Pointer to property data         */
    struct _OFFProp *NextProp;           /* Pointer to next property in list */
    } OFFProperty;

typedef struct
    { 
    OFFProperty *FirstProp;              /* Pointer to first property in list*/
    } OFFObjDesc;\fP

.fi
.bp
.sp 6
.NH 1
Appendix F: Data Structure Format
.PP
The following diagram depicts some of the data structures for the
object \fIcube.off\fP after being read by \fIOFFReadObj()\fP (or just prior
to being written by \fIOFFWriteObj()\fP).
.PS
.ps 8
move to 0.0, 14.5
Object:
	[
	boxht = 0.3i; boxwid = 2.0i
	moveht = 0.3i; movewid = 0.0i
	   down
	A: box "FirstProp"
	   move up 0.3i; move right 2.0i
	   down
	B: box
	]

move to 1.0, 13.0
Prop1:
	[
	boxht = 0.3i; boxwid = 1.5i
	moveht = 0.3i; movewid = 0.0i
	   down
	A: box "PropName"
	   down
	B: box "PropType"
	   down
	C: box "PropFileName"
	   down
	D: box "DataFormat"
	   down
	E: box "PropCount"
	   down
	F: box "PropData"
	   down
	H: box "NextProp"
	   move up 2.1i;
	   move right 2.25i;
	   boxht = 0.3i; boxwid = 3.0i
	   moveht = 0.3i; movewid = 0.0i
	   down
	I: box "name"
	   down
	J: box "OFF_STANDARD_DATA"
	   down
	K: box "\fInull string\fP"
	   down
	L: box "\fInull string\fP"
	   down
	M: box "0"
	   down
	N: box
	   down
	O: box
	]

move to 1.0, 10.0
Prop2:
	[
	boxht = 0.3i; boxwid = 1.5i
	moveht = 0.3i; movewid = 0.0i
	   down
	A: box "PropName"
	   down
	B: box "PropType"
	   down
	C: box "PropFileName"
	   down
	D: box "DataFormat"
	   down
	E: box "PropCount"
	   down
	F: box "PropData"
	   down
	H: box "NextProp"
	   move up 2.1i;
	   move right 2.25i;
	   boxht = 0.3i; boxwid = 3.0i
	   moveht = 0.3i; movewid = 0.0i
	   down
	I: box "author"
	   down
	J: box "OFF_STANDARD_DATA"
	   down
	K: box "\fInull string\fP"
	   down
	L: box "\fInull string\fP"
	   down
	M: box "0"
	   down
	N: box
	   down
	O: box
	]

move to 1.0, 7.0
Prop3:
	[
	boxht = 0.3i; boxwid = 1.5i
	moveht = 0.3i; movewid = 0.0i
	   down
	A: box "PropName"
	   down
	B: box "PropType"
	   down
	C: box "PropFileName"
	   down
	D: box "DataFormat"
	   down
	E: box "PropCount"
	   down
	F: box "PropData"
	   down
	H: box "NextProp"
	   move up 2.1i;
	   move right 2.25i;
	   boxht = 0.3i; boxwid = 3.0i
	   moveht = 0.3i; movewid = 0.0i
	   down
	I: box "geometry"
	   down
	J: box "OFF_INDEXED_POLY_DATA"
	   down
	K: box "cube.geom"
	   down
	L: box "fff"
	   down
	M: box "6"
	   down
	N: box
	   down
	O: box
	]

move to 1.0, 4.0
Prop4:
	[
	boxht = 0.3i; boxwid = 1.5i
	moveht = 0.3i; movewid = 0.0i
	   down
	A: box "PropName"
	   down
	B: box "PropType"
	   down
	C: box "PropFileName"
	   down
	D: box "DataFormat"
	   down
	E: box "PropCount"
	   down
	F: box "PropData"
	   down
	H: box "NextProp"
	   move up 2.1i;
	   move right 2.25i;
	   boxht = 0.3i; boxwid = 3.0i
	   moveht = 0.3i; movewid = 0.0i
	   down
	I: box "polygon_colors"
	   down
	J: box "OFF_GENERIC_DATA"
	   down
	K: box "cube.pcol"
	   down
	L: box "fff"
	   down
	M: box "6"
	   down
	N: box
	   down
	O: box
	]

move to 1.0, 1.0
Prop5:
	[
	boxht = 0.3i; boxwid = 1.5i
	moveht = 0.3i; movewid = 0.0i
	   down
	A: box "PropName"
	   down
	B: box "PropType"
	   down
	C: box "PropFileName"
	   down
	D: box "DataFormat"
	   down
	E: box "PropCount"
	   down
	F: box "PropData"
	   down
	H: box "NextProp"
	   move up 2.1i;
	   move right 2.25i;
	   boxht = 0.3i; boxwid = 3.0i
	   moveht = 0.3i; movewid = 0.0i
	   down
	I: box "back_faces"
	   down
	J: box "OFF_DEFAULT_DATA"
	   down
	K: box "\fInull string\fP"
	   down
	L: box "s"
	   down
	M: box "0"
	   down
	N: box
	   down
	O: box "\fInull pointer\fP"
	]

line from Prop1.O.c to Prop1.O.c.x, (Prop1.s.y + Prop2.n.y) / 2.0
line to 0.5, (Prop1.s.y + Prop2.n.y) / 2.0
line to 0.5, Prop2.A.c.y
arrow to Prop2.A.w

line from Prop2.O.c to Prop2.O.c.x, (Prop2.s.y + Prop3.n.y) / 2.0
line to 0.5, (Prop2.s.y + Prop3.n.y) / 2.0
line dashed to 0.5, Prop3.A.c.y
arrow to Prop3.A.w

line from Prop3.O.c to Prop3.O.c.x, (Prop3.s.y + Prop4.n.y) / 2.0
line to 0.5, (Prop3.s.y + Prop4.n.y) / 2.0
line dashed to 0.5, Prop4.A.c.y
arrow to Prop4.A.w

line from Prop4.O.c to Prop4.O.c.x, (Prop4.s.y + Prop5.n.y) / 2.0
line to 0.5, (Prop4.s.y + Prop5.n.y) / 2.0
line to 0.5, Prop5.A.c.y
arrow to Prop5.A.w

line from Object.B.c to Object.B.c.x, (Object.s.y + Prop1.n.y) / 2.0
line to 0.5, (Object.s.y + Prop1.n.y) / 2.0
line to 0.5, Prop1.A.c.y
arrow to Prop1.A.w

boxht = 0.3i; boxwid = 2.0i
P1: box "cube" at Prop1.N.e.x + 1.5i, Prop1.N.e.y
arrow from Prop1.N.c to P1.w

P2: box "Randi J. Rost" at Prop2.N.e.x + 1.5i, Prop2.N.e.y
arrow from Prop2.N.c to P2.w

P3: box "  8    6    24 -1.0 -1.0" at Prop3.N.e.x + 1.5i, Prop3.N.e.y
P4: box " 1.0 -1.0  1.0  1.0  1.0" with .nw at P3.sw
P5: box " 1.0  1.0  1.0 -1.0  1.0" with .nw at P4.sw
P6: box "-1.0 -1.0 -1.0 -1.0  1.0" with .nw at P5.sw
P7: box "-1.0  1.0  1.0 -1.0  1.0" with .nw at P6.sw
P8: box "-1.0 -1.0 4  4  4 4  4  4" with .nw at P7.sw
P9: box "1  2  3  4  5  6  2  1" with .nw at P8.sw
Pa: box "3  2  6  7  3  7  8  4" with .nw at P9.sw
Pb: box "1  4  8  5  8  7  6  5" with .nw at Pa.sw
arrow from Prop3.N.c to P3.w

Pc: box "6    1.0  0.0  0.0  0.0" at Prop4.N.e.x + 1.5i, Prop4.N.e.y
Pd: box "1.0  0.0  0.0  0.0  1.0" with .nw at Pc.sw
Pe: box "0.0  1.0  1.0  1.0  1.0" with .nw at Pd.sw
Pf: box "0.0  1.0  0.0  1.0     " with .nw at Pe.sw
arrow from Prop4.N.c to Pc.w

boxht = 0.3i; boxwid = 0.5i
Pg: box at Prop5.N.e.x + 1.0i, Prop5.N.e.y
Ph: box "cull" at Pg.e.x + 1.0i, Prop5.N.e.y
arrow from Pg.c to Ph.w
arrow from Prop5.N.c to Pg.w

"Object" at Object.A.n above
.ps 12
.PE
.bp
.NH 1
Acknowledgements
.PP
Special thanks to Allen Akin of WSE for helpful ideas and suggestions.  
Thanks also to Jeff Friedberg of Digital's High-Performance Workstation (HPWS)
group and Shaun Ho of WSE who also contributed to the design.
@//E*O*F off.ms//
chmod u=rw,g=r,o= off.ms

exit 0