/* ReadTDDD.c - read a TDDD (Three Dimension Data Decription) file
 *            - written by Glenn M. Lewis - 9/4/91
 *            - Added HINGE support - Rob Hounsell - 01/01/94
 */

static char rcs_id[] = "$Id: readtddd.c,v 1.32 1994/01/20 22:14:52 glewis Exp $";

#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include "t3dlib.h"
#ifdef __STDC__
#include <stdlib.h>
#include <strings.h>
#include "readtddd_protos.h"
#endif
#define MAXLINE 132
#ifndef PI
#define PI (3.14159265)
#endif

int verbose_flag=0;

static void process_DESC();
static OBJECT *process_EXTR();
static void process_INFO();
static void process_OBJ();
void insert_into_sorted_list();

/* Here are a few necessary utilities */

static void get_name(name, size, world)
register char  *name;
register int size;
WORLD *world;
{
	while (size--) *name++ = fgetc(world->inp);
	*name = '\0';
}

static BYTE get_BYTE(world)
WORLD *world;
{
	return((BYTE)fgetc(world->inp));
}

static UBYTE get_UBYTE(world)
WORLD *world;
{
	return((UBYTE)fgetc(world->inp));
}

static WORD get_WORD(world)
WORLD *world;
{
	WORD tmp = (WORD)get_UBYTE(world)<<8;
	return((WORD)(tmp|get_UBYTE(world)));
}

static UWORD get_UWORD(world)
WORLD *world;
{
	UWORD tmp = (UWORD)get_UBYTE(world)<<8;
	return((UWORD)(tmp|get_UBYTE(world)));
}

static ULONG get_ULONG(world)
WORLD *world;
{
	ULONG tmp = (ULONG)get_UWORD(world)<<16;
	return(tmp|get_UWORD(world));
}

static double get_FRACT(world)	/* This sets the precision to 10^(-3) */
WORLD *world;
{
	register double whole, fract;

	whole =  (double)get_WORD(world);
	fract = ((double)get_UWORD(world))/(double)65536.0;
	return((double)(whole+fract));
}

static void stuff_XYZ(st, world)
XYZ_st *st;
WORLD *world;
{
	st->x = get_FRACT(world);
	st->y = get_FRACT(world);
	st->z = get_FRACT(world);
}

static void stuff_RGB(st, world)
RGB_st *st;
WORLD *world;
{
	get_UBYTE(world);	/* pad byte */
	st->r = get_UBYTE(world);
	st->g = get_UBYTE(world);
	st->b = get_UBYTE(world);
}

static void output_warning(world, area, name, size)
WORLD *world;
char *area, *name;
ULONG size;
{
	ULONG i;
	int j;
	unsigned char tmpstr[20];

	fprintf(stderr, "WARNING: Unknown %ssub-chunk: '%s', size %lu...\n",
		area, name, size);
	for (i=0; i<size; i+=16) {
		fprintf(stderr, "%04lx:", i);
		for (j=0; j<16 && i+j<size; j++)
			fprintf(stderr, " %02x", tmpstr[j] = (fgetc(world->inp)&0xff));
		while (j++ < 16) fputs("   ", stderr);
		fputs("    ", stderr);
		/* Print out the ASCII equivalent */
		for (j=0; j<16 && i+j<size; j++) {
			tmpstr[j] &= 0x7F;   /* Strip off upper bit */
			if (isprint(tmpstr[j]))
				fputc(tmpstr[j], stderr);
			else fputc('.', stderr);
		}
		fputs("\n", stderr);
	}
}

/********************/
/* The MAIN section */
/********************/

int already_read_header = 0;
unsigned char header_storage[13];

WORLD *read_World(file)
FILE *file;
{
	int i;
	for (i=0; i<4; i++) header_storage[i] = fgetc(file);
	already_read_header = 4;
	if (strncmp(header_storage, "FORM", 4)==0) {
		while (i<12) header_storage[i++] = fgetc(file);
		already_read_header = 12;
		if (strncmp(&header_storage[8], "TDDD", 4)==0) return(read_TDDD(file));
#if 0
		if (strncmp(&header_storage[8], "LWOB", 4)==0) return(read_LWOB(file));
#endif
		if (strncmp(&header_storage[8], "ISTG", 4)==0) return(read_ISTG(file));
		/* BAD IFF */
		header_storage[12] = 0;
		fprintf(stderr, "ERROR! Unknown FORM: '%s'\n", &header_storage[8]);
		return(0);
	} else {
		return(read_TTDDD(file));
	}
}

WORLD *read_TDDD(file)
FILE *file;
{
	register ULONG i, len, size;
	WORLD *world;
	char name[5];

	if (!file) return(0L);	/* File not open */

	if (!(world = (WORLD*)malloc(sizeof(WORLD)))) { OUT_MEM("WORLD"); }
	bzero((char*)world, sizeof(WORLD));
	world->inp = file;

	if (!already_read_header) {
		/* Parse the IFF TDDD file */
		get_name(name, 4, world);
		if (strcmp(name, "FORM") != 0) {
			fprintf(stderr, "ERROR: Input is not an IFF file.\n"); exit(-1); }
		if (!(len = get_ULONG(world))) {
			fprintf(stderr, "ERROR: Bad FORM length in IFF file.\n"); exit(-1); }
		get_name(name, 4, world);
		if (strcmp(name, "TDDD") != 0) {
			fprintf(stderr, "ERROR: IFF file is not a TDDD file.\n"); exit(-1); }
	} else {
		len =	(header_storage[4]<<24) | (header_storage[5]<<16) |
				(header_storage[6]<< 8) | (header_storage[7]    );
		already_read_header = 0;
	}
	len -= 4;

/* Here is the main loop: */
	while (len>0) {
		get_name(name, 4, world);
		size = get_ULONG(world);
		len -= 8;
		if (strcmp(name, "INFO")==0) process_INFO(size, world);
		else
		if (strcmp(name, "OBJ ")==0) process_OBJ(size, world);
		else {
			if (verbose_flag) output_warning(world, "", name, size);
			else {
				for (i=size; i--; ) fgetc(world->inp);	/* Skip this section */
			}
		}
		if (size&1) { fgetc(world->inp); len--; }	/* Skip odd byte */
		len -= size;
	}

/* All done. */
	return(world);
}

static void process_INFO(len, world)
register ULONG len;
WORLD *world;
{
	register INFO *info;
	register ULONG i, size;
	char name[5];

	if (world->info) {
		fputs("ERROR: More than one INFO chunk!\n", stderr); exit(-1); }
	if (!(info = world->info = (INFO*)malloc(sizeof(INFO)))) { OUT_MEM("INFO"); }
	bzero((char*)world->info, sizeof(INFO));

	if (verbose_flag>1) fprintf(stderr, "INFO chunk.\n");
	while (len>0) {
		get_name(name, 4, world);
		size = get_ULONG(world);
		len -= 8;
		if (verbose_flag>1) fprintf(stderr, "%s chunk.\n", name);
		if (strcmp(name, "BRSH")==0) {
			i = get_UWORD(world);
			if (i<0 || i>7) { i=0; fputs("BRSH error.\n", stderr); }
			get_name(info->brsh[i], 80, world);
		} else
		if (strcmp(name, "STNC")==0) {
			i = get_UWORD(world);
			if (i<0 || i>7) { i=0; fputs("STNC error.\n", stderr); }
			get_name(info->stnc[i], 80, world);
		} else
		if (strcmp(name, "TXTR")==0) {
			i = get_UWORD(world);
			if (i<0 || i>7) { i=0; fputs("TXTR error.\n", stderr); }
			get_name(info->txtr[i], 80, world);
		} else
		if (strcmp(name, "OBSV")==0) {
			if (!info->obsv) info->obsv=(OBSV*)malloc(sizeof(OBSV));
			if (!info->obsv) { OUT_MEM("OBSV"); }
			stuff_XYZ(&info->obsv->came, world);
			stuff_XYZ(&info->obsv->rota, world);
			info->obsv->foca = get_FRACT(world);
		} else
		if (strcmp(name, "OTRK")==0) {
			get_name(info->otrk, 18, world);
		} else
		if (strcmp(name, "OSTR")==0) {
			if (!info->ostr) info->ostr=(STRY*)malloc(sizeof(STRY));
			if (!info->ostr) { OUT_MEM("OSTR"); }
			get_name(info->ostr->path, 18, world);
			stuff_XYZ(&info->ostr->tran, world);
			stuff_XYZ(&info->ostr->rota, world);
			stuff_XYZ(&info->ostr->scal, world);
			info->ostr->info = get_UWORD(world);
		} else
		if (strcmp(name, "FADE")==0) {
			if (!info->fade) info->fade=(FADE*)malloc(sizeof(FADE));
			if (!info->fade) { OUT_MEM("FADE"); }
			info->fade->at = get_FRACT(world);
			info->fade->by = get_FRACT(world);
			stuff_RGB(&info->fade->to, world);
		} else
		if (strcmp(name, "SKYC")==0) {
			if (!info->skyc) info->skyc=(SKYC*)malloc(sizeof(SKYC));
			if (!info->skyc) { OUT_MEM("SKYC"); }
			stuff_RGB(&info->skyc->hori, world);
			stuff_RGB(&info->skyc->zeni, world);
		} else
		if (strcmp(name, "AMBI")==0) {
			if (!info->ambi) info->ambi=(RGB_st*)malloc(sizeof(RGB_st));
			if (!info->ambi) { OUT_MEM("AMBI"); }
			stuff_RGB(info->ambi, world);
		} else
		if (strcmp(name, "GLB0")==0) {
			if (!info->glb0) info->glb0=(BYTE*)malloc(8*sizeof(BYTE));
			if (!info->glb0) { OUT_MEM("GLB0"); }
			for (i=0; i<8; i++)
				info->glb0[i] = get_UBYTE(world);
		} else {
			if (verbose_flag) output_warning(world, "INFO ", name, size);
			else {
				for (i=size; i--; ) fgetc(world->inp);	/* Skip this section */
			}
		}
		if (size&1) { fgetc(world->inp); len--; }	/* Skip odd byte */
		len -= size;
	}
}

static void process_OBJ(len, world)
register ULONG len;
WORLD *world;
{
	register ULONG i, size;
	register OBJECT *p;
	char name[5];
	int depth=0;

	if (verbose_flag>1) fprintf(stderr, "OBJ chunk.\n");
	while (len>0) {
		get_name(name, 4, world);
		size = get_ULONG(world);
		len -= 8;
		if (verbose_flag>1) fprintf(stderr, "%s chunk.\n", name);
		if (strcmp(name, "EXTR")==0) {
			p = process_EXTR(size, create_object(), world);
			if (world->curobj) {
				world->curobj->next = p;
				p->parent = world->curobj->parent;
			} else {
				world->object = p;
				p->parent = 0;
			}
			world->curobj = p;
		} else if (strcmp(name, "DESC")==0) {
			p = create_object();
			if (world->num_DESC > world->num_TOBJ+depth) {  /* This is a child */
				depth++;    /* Down one in the hierarchy */
				world->curobj->child = p;
				p->parent = world->curobj;
			} else {
				if (world->curobj) {
					world->curobj->next = p;
					p->parent = world->curobj->parent;
				} else {
					world->object = p;
					p->parent = 0;
				}
			}
			world->curobj = p;
			process_DESC(size, &p->desc, world);
		} else if (strcmp(name, "TOBJ")==0) {
			world->num_TOBJ++;
			if (world->num_TOBJ > world->num_DESC) {
				fprintf(stderr, "Warning: TOBJ without DESC.  Ignored.\n");
			}
			if (world->num_TOBJ+depth > world->num_DESC) {  /* Up a level */
				depth--;
				world->curobj=world->curobj->parent;
			}
		} else {
			if (verbose_flag) output_warning(world, "OBJ ", name, size);
			else {
				for (i=size; i--; ) fgetc(world->inp);	/* Skip this section */
			}
		}
		if (size&1) { fgetc(world->inp); len--; }	/* Skip odd byte */
		len -= size;
	}
}

OBJECT *load_external_object(filename, mtrx)
char *filename;
MTRX *mtrx;
{
	WORLD *new;
	FILE *newinp;
	register OBJECT *p;

	/* Now, load in the external object */
TRY_EXTR_AGAIN:
	if (!(newinp=fopen(filename, "r"))) {
		fprintf(stderr, "Can't load in EXTR object: '%s'.\n",
			filename);
		fprintf(stderr, "Enter filneame (or CR to abort): "); fflush(stderr);
		fgets(filename, 512, stdin);
		if (!filename[0] || filename[0]=='\n') return(0L);
		goto TRY_EXTR_AGAIN;
	}
	new = read_World(newinp);
	fclose(newinp);
	/* scale, rotate, and translate new object hierarchy */
	for (p=new->object; p; p=p->next)
		move_extr(p, mtrx);

	p = new->object;
	free((char*)new);
	return(p);
}

static OBJECT *process_EXTR(len, obj, world)
register ULONG len;
OBJECT *obj;
WORLD *world;
{
	register ULONG i, size;
	register EXTR *extr;
	char name[5];
	MTRX *mtrx;

	if (!(extr = obj->extr = (EXTR*)malloc(sizeof(EXTR))))
		{ OUT_MEM("EXTR"); }
	bzero((char*)extr, sizeof(EXTR));
	mtrx = &extr->mtrx;
	/* Initialize structure */
	mtrx->tran.x  = mtrx->tran.y  = mtrx->tran.z  = 0.0;
	mtrx->scal.x  = mtrx->scal.y  = mtrx->scal.z  = 1.0;
	mtrx->rota1.y = mtrx->rota1.z = 0.0;
	mtrx->rota2.x = mtrx->rota2.z = 0.0;
	mtrx->rota3.x = mtrx->rota3.y = 0.0;
	mtrx->rota1.x = mtrx->rota2.y = mtrx->rota3.z = 1.0;

	if (verbose_flag>1) fprintf(stderr, "EXTR chunk.\n");
	while (len>0) {
		get_name(name, 4, world);
		size = get_ULONG(world);
		len -= 8;
		if (verbose_flag>1) fprintf(stderr, "%s chunk.\n", name);
		if (strcmp(name, "MTRX")==0) {
			stuff_XYZ(&mtrx->tran, world);
			stuff_XYZ(&mtrx->scal, world);
			stuff_XYZ(&mtrx->rota1, world);
			stuff_XYZ(&mtrx->rota2, world);
			stuff_XYZ(&mtrx->rota3, world);
		} else
		if (strcmp(name, "LOAD")==0) {
			get_name(extr->filename, 80, world);
		} else {
			if (verbose_flag) output_warning(world, "EXTR ", name, size);
			else {
				for (i=size; i--; ) fgetc(world->inp);	/* Skip this section */
			}
		}
		if (size&1) { fgetc(world->inp); len--; }	/* Skip odd byte */
		len -= size;
	}

	/* Free up unused memory */
	free((char*)obj->extr);
	free((char*)obj);

	obj = load_external_object(extr->filename, mtrx);
	return(obj);
}

static UBYTE defclst[3], defrlst[3], deftlst[3], defspc1[3];

static void malloc_arrays(i, desc)
register int i;
register DESC *desc;
{
	if (!desc->fcount) {
		desc->fcount = i;
if (!(desc->face=(UWORD*)malloc(3*desc->fcount*sizeof(UWORD))))OUT_MEM((char*)0);
if (!(desc->clst=(UBYTE*)malloc(3*desc->fcount*sizeof(UBYTE))))OUT_MEM((char*)0);
if (!(desc->rlst=(UBYTE*)malloc(3*desc->fcount*sizeof(UBYTE))))OUT_MEM((char*)0);
if (!(desc->tlst=(UBYTE*)malloc(3*desc->fcount*sizeof(UBYTE))))OUT_MEM((char*)0);
		/* Initialize arrays */
		for (i=0; i<3*desc->fcount; i+=3) {
			desc->face[i]   = desc->face[i+1] = desc->face[i+2] = 0;
			desc->clst[i]   = defclst[0];
			desc->clst[i+1] = defclst[1];
			desc->clst[i+2] = defclst[2];
			desc->rlst[i]   = defrlst[0];
			desc->rlst[i+1] = defrlst[1];
			desc->rlst[i+2] = defrlst[2];
			desc->tlst[i]   = deftlst[0];
			desc->tlst[i+1] = deftlst[1];
			desc->tlst[i+2] = deftlst[2];
		}
	} else if (i != desc->fcount) {
		fprintf(stderr, "ERROR: FACE and [C|R|T]LST 'Count' values inconsistant.\n");
		OUT_MEM((char*)0);
	}
}

static void process_DESC(len, orig, world)
register ULONG len;
DESC **orig;
WORLD *world;
{
	register ULONG size;
	register DESC *desc;
	register int i, j;
	FGRP *fgrp;
	char name[5];
	ULONG ccsize;

	if (!(desc = *orig = (DESC*)malloc(sizeof(DESC))))
		{ OUT_MEM("DESC"); }
	bzero((char*)desc, sizeof(DESC));

	/* Set up defaults: */
	defclst[0] = defclst[1] = defclst[2] = 240; /* TS default */
	defrlst[0] = defrlst[1] = defrlst[2] = 0;
	deftlst[0] = deftlst[1] = deftlst[2] = 0;
	defspc1[0] = defspc1[1] = defspc1[2] = 0;

	world->num_DESC++;

	if (verbose_flag>1) fprintf(stderr, "DESC chunk.\n");
	while (len>0) {
		get_name(name, 4, world);
		size = get_ULONG(world);
		len -= 8;
		if (verbose_flag>1) fprintf(stderr, "%s chunk.\n", name);
		if (strcmp(name, "NAME")==0) {
			get_name(desc->name, 18, world);
		} else
		if (strcmp(name, "SHAP")==0) {
			if (!desc->shap) {
				if (!(desc->shap=(WORD*)malloc(2*sizeof(WORD))))
					{ OUT_MEM("SHAP"); }
				desc->shap[0] = 2;  /* TS defaults */
				desc->shap[1] = 0;
			}
			desc->shap[0] = get_UWORD(world);
			desc->shap[1] = get_UWORD(world);
		} else
		if (strcmp(name, "POSI")==0) {
			if (!desc->posi) {
				if (!(desc->posi=(XYZ_st*)malloc(sizeof(XYZ_st))))
					{ OUT_MEM("POSI"); }
			}
			stuff_XYZ(desc->posi, world);
		} else
		if (strcmp(name, "AXIS")==0) {
			if (!desc->axis) {
				if (!(desc->axis=(AXIS*)malloc(sizeof(AXIS))))
					{ OUT_MEM("AXIS"); }
				bzero((char*)desc->axis, sizeof(AXIS));
				desc->axis->xaxi.x = 1;
				desc->axis->yaxi.y = 1;
				desc->axis->zaxi.z = 1;
			}
			stuff_XYZ(&desc->axis->xaxi, world);
			stuff_XYZ(&desc->axis->yaxi, world);
			stuff_XYZ(&desc->axis->zaxi, world);
		} else
		if (strcmp(name, "SIZE")==0) {
			if (!desc->size) {
				if (!(desc->size=(XYZ_st*)malloc(sizeof(XYZ_st))))
					{ OUT_MEM("SIZE"); }
			}
			stuff_XYZ(desc->size, world);
		} else
		if (strcmp(name, "PNTS")==0) {
			i = desc->pcount = get_UWORD(world);	/* Number of points */
			if (!(desc->pnts = (XYZ_st*)malloc(desc->pcount*sizeof(XYZ_st))))
				OUT_MEM("PNTS");
			for (j=0; j<i; j++)
				{ stuff_XYZ(&desc->pnts[j], world); }
		} else
		if (strcmp(name, "EDGE")==0) {
			i = desc->ecount = get_UWORD(world);	/* Number of edges */
			if (!(desc->edge = (UWORD*)malloc(2*desc->ecount*sizeof(UWORD))))
				OUT_MEM("EDGE");
			for (j=0; j<2*i; j++)
				{ desc->edge[j] = get_UWORD(world); }
		} else
		if (strcmp(name, "EFLG")==0) {		/* An undocumented chunk */
			if (!(desc->eflg = (EFLG*)malloc(sizeof(EFLG))))
				OUT_MEM("EFLG");
			i = desc->eflg->num = get_UWORD(world);	/* Number of edge flags */
			if (!(desc->eflg->eflg=(UBYTE*)malloc(desc->eflg->num*sizeof(UBYTE))))
				OUT_MEM("EFLG");
			for (j=0; j<i; j++)
				{ desc->eflg->eflg[j] = get_UBYTE(world); }
		} else
		if (strcmp(name, "FACE")==0) {
			i = get_UWORD(world);				/* Number of faces */
			malloc_arrays(i, desc);
			for (j=0; j<i; j++) {
				desc->face[3*j]   = get_UWORD(world);
				desc->face[3*j+1] = get_UWORD(world);
				desc->face[3*j+2] = get_UWORD(world);
			}
		} else
		if (strcmp(name, "BRS3")==0) {
			fprintf(stderr, "BRS3:\n");
			for (i=0; i<21; i++) {
				fprintf(stderr, "%.12g\n", get_FRACT(world));
			}
			get_UWORD(world);
			i = get_UBYTE(world);
			while (i--) {
				fputc(get_UBYTE(world), stderr);
			}
		} else
		if (strcmp(name, "TXT2")==0) {
			for (i=0; i<4 && desc->txt2[i]; i++) ;
			if (i==4) {		/* ERROR! */
				fprintf(stderr, "WARNING! More than 4 TXT2 chunks!\n");
				i--;
			}
			if (!(desc->txt2[i] = (TXT2*)malloc(sizeof(TXT2)))) OUT_MEM("TXT2");
			desc->txt2[i]->Flags = get_UWORD(world);
			ccsize = 2;
			stuff_XYZ(&desc->txt2[i]->TAxis.tran, world);
			stuff_XYZ(&desc->txt2[i]->TAxis.rota1, world);
			stuff_XYZ(&desc->txt2[i]->TAxis.rota2, world);
			stuff_XYZ(&desc->txt2[i]->TAxis.rota3, world);
			stuff_XYZ(&desc->txt2[i]->TAxis.scal, world);
			ccsize = ccsize + 60;
			for (j = 0; j < 16; j++) desc->txt2[i]->Params[j] = get_FRACT(world);
			ccsize = ccsize + 64;
			for (j = 0; j < 16; j++) desc->txt2[i]->PFlags[j] = get_UBYTE(world);
			ccsize = ccsize + 16;
			ccsize = ccsize + 17;
			get_name(desc->txt2[i]->SubName, 17, world);
			j = get_UWORD(world);
			ccsize = ccsize + 2 + j;
			desc->txt2[i]->Length = j;
			get_name(desc->txt2[i]->Name, j, world);
			for (j=ccsize; j < size; j++) get_UBYTE(world);
		} else
#if 0
		if (strcmp(name, "FOR2")==0) {		/* Another undocumented chunk */
			newsize = size;
			i = get_UWORD(world);	/* NumC */
			newsize -= 2;
			NumC = i;
			fprintf(stderr, "Encountered 'FOR2' chunk...size=%lu\n  NumC=%u\n", size, i);
			i = get_UWORD(world);	/* NumF */
			newsize -= 2;
			NumF = i;
			fprintf(stderr, "  NumF=%u\n", i);
			i = get_UWORD(world);	/* Flags */
			newsize -= 2;
			fprintf(stderr, "  Flags=%u\n", i);
			i = get_UWORD(world);	/* pad */
			newsize -= 2;
			fprintf(stderr, "  pad=%u\n", i);
			for (j=0; j<9; j++) fprintf(stderr, "  MTRX[%d]=%.12g\n", j, get_FRACT(world));
			newsize -= 36;
			fprintf(stderr, "  ShiftX=%.12g ", get_FRACT(world));
			fprintf(stderr, "ShiftY=%.12g ", get_FRACT(world));
			fprintf(stderr, "ShiftZ=%.12g\n", get_FRACT(world));
			newsize -= 12;
			for (j=0; j<NumC+4*NumF; j++) {
				fprintf(stderr, "  Point[%d]: X=%.12g ", j, get_FRACT(world));
				fprintf(stderr, "Y=%.12g ", get_FRACT(world));
				fprintf(stderr, "Z=%.12g\n", get_FRACT(world));
				newsize -= 12;
			}
			output_warning(world, "DESC ", "FOR2", newsize);
		} else
#endif
		if (strcmp(name, "FGRP")==0) {		/* Another undocumented chunk */
			if (!(fgrp = (FGRP*)malloc(sizeof(FGRP))))
				OUT_MEM("FGRP");
			i = fgrp->num = get_UWORD(world);	/* Number of faces in group */
			if (!(fgrp->face = (UWORD*)malloc(i*sizeof(UWORD))))
				OUT_MEM("FGRP");
			get_name(fgrp->name, 18, world);
			for (j=0; j<i; j++)
				fgrp->face[j] = get_UWORD(world);
			/* Link this new FGRP into list */
			fgrp->next = desc->fgrp;
			desc->fgrp = fgrp;
		} else
		if (strcmp(name, "COLR")==0) {
			if (!desc->colr) {
				if (!(desc->colr = (RGB_st*)malloc(sizeof(RGB_st))))
					{ OUT_MEM("COLR"); }
			}
			stuff_RGB((RGB_st*)&defclst[0], world);
			desc->colr->r = defclst[0];
			desc->colr->g = defclst[1];
			desc->colr->b = defclst[2];
		} else
		if (strcmp(name, "REFL")==0) {
			if (!desc->refl) {
				if (!(desc->refl = (RGB_st*)malloc(sizeof(RGB_st))))
					{ OUT_MEM("REFL"); }
			}
			stuff_RGB((RGB_st*)&defrlst[0], world);
			desc->refl->r = defrlst[0];
			desc->refl->g = defrlst[1];
			desc->refl->b = defrlst[2];
		} else
		if (strcmp(name, "TRAN")==0) {
			if (!desc->tran) {
				if (!(desc->tran = (RGB_st*)malloc(sizeof(RGB_st))))
					{ OUT_MEM("TRAN"); }
			}
			stuff_RGB((RGB_st*)&deftlst[0], world);
			desc->tran->r = deftlst[0];
			desc->tran->g = deftlst[1];
			desc->tran->b = deftlst[2];
		} else
		if (strcmp(name, "SPC1")==0) {
			if (!desc->spc1) {
				if (!(desc->spc1 = (RGB_st*)malloc(sizeof(RGB_st))))
					{ OUT_MEM("SPC1"); }
			}
			stuff_RGB((RGB_st*)&defspc1[0], world);
			desc->spc1->r = defspc1[0];
			desc->spc1->g = defspc1[1];
			desc->spc1->b = defspc1[2];
		} else
		if (strcmp(name, "CLST")==0) {
			i = get_UWORD(world);		/* Number of faces */
			malloc_arrays(i, desc);
			for (j=0; j<i; j++) {
				desc->clst[3*j+0] = get_UBYTE(world);
				desc->clst[3*j+1] = get_UBYTE(world);
				desc->clst[3*j+2] = get_UBYTE(world);
			}
		} else
		if (strcmp(name, "RLST")==0) {
			i = get_UWORD(world);		/* Number of faces */
			malloc_arrays(i, desc);
			for (j=0; j<i; j++) {
				desc->rlst[3*j+0] = get_UBYTE(world);
				desc->rlst[3*j+1] = get_UBYTE(world);
				desc->rlst[3*j+2] = get_UBYTE(world);
			}
		} else
		if (strcmp(name, "TLST")==0) {
			i = get_UWORD(world);		/* Number of faces */
			malloc_arrays(i, desc);
			for (j=0; j<i; j++) {
				desc->tlst[3*j+0] = get_UBYTE(world);
				desc->tlst[3*j+1] = get_UBYTE(world);
				desc->tlst[3*j+2] = get_UBYTE(world);
			}
		} else
		if (strcmp(name, "TPAR")==0) {
			if (!desc->tpar) {
				if (!(desc->tpar=(double*)malloc(16*sizeof(double))))
					{ OUT_MEM("TPAR"); }
				bzero((char*)desc->tpar, 16*sizeof(double));
			}
			for (i=0; i<16; i++)
				desc->tpar[i] = get_FRACT(world);
		} else
		if (strcmp(name, "SURF")==0) {
			if (!desc->surf) {
				if (!(desc->surf=(UBYTE*)malloc(5*sizeof(UBYTE))))
					{ OUT_MEM("SURF"); }
				bzero((char*)desc->surf, 5*sizeof(UBYTE));
			}
			for (i=0; i<5; i++)
				desc->surf[i] = get_BYTE(world);
		} else
		if (strcmp(name, "MTTR")==0) {
			if (!desc->mttr) {
				if (!(desc->mttr=(MTTR*)malloc(sizeof(MTTR))))
					{ OUT_MEM("MTTR"); }
				bzero((char*)desc->mttr, sizeof(MTTR));
			}
			desc->mttr->type = get_UBYTE(world);
			desc->mttr->indx = (get_UBYTE(world)/100.0)+1.0;
		} else
		if (strcmp(name, "SPEC")==0) {
			if (!desc->spec) {
				if (!(desc->spec=(UBYTE*)malloc(2*sizeof(UBYTE))))
					{ OUT_MEM("SPEC"); }
				bzero((char*)desc->spec, 2*sizeof(UBYTE));
			}
			desc->spec[0] = get_UBYTE(world);
			desc->spec[1] = get_UBYTE(world);
		} else
		if (strcmp(name, "PRP0")==0) {
			if (!desc->prp0) {
				if (!(desc->prp0=(UBYTE*)malloc(6*sizeof(UBYTE))))
					{ OUT_MEM("PRP0"); }
				bzero((char*)desc->prp0, 6*sizeof(UBYTE));
			}
			for (i=0; i<6; i++)
				desc->prp0[i] = get_UBYTE(world);
		} else
		if (strcmp(name, "PRP1")==0) {
			if (!desc->prp1) {
				if (!(desc->prp1=(UBYTE*)malloc(8*sizeof(UBYTE))))
					{ OUT_MEM("PRP1"); }
				bzero((char*)desc->prp1, 8*sizeof(UBYTE));
			}
			for (i=0; i<8; i++)
				desc->prp1[i] = get_UBYTE(world);
		} else
		if (strcmp(name, "INTS")==0) {
			if (!desc->ints) {
				if (!(desc->ints=(double*)malloc(sizeof(double))))
					{ OUT_MEM("INTS"); }
			}
			*desc->ints = get_FRACT(world);
		} else
		if (strcmp(name, "INT1")==0) {
			if (!desc->int1) desc->int1=(XYZ_st*)malloc(sizeof(XYZ_st));
			if (!desc->int1) { OUT_MEM("INT1"); }
			stuff_XYZ(desc->int1, world);
		} else
		if (strcmp(name, "STRY")==0) {
			get_name(desc->stry->path, 18, world);
			stuff_XYZ(&desc->stry->tran, world);
			stuff_XYZ(&desc->stry->rota, world);
			stuff_XYZ(&desc->stry->scal, world);
			desc->stry->info = get_UWORD(world);
		} else {
			if (verbose_flag) output_warning(world, "DESC ", name, size);
			else {
				for (i=size; i--; ) fgetc(world->inp);	/* Skip this section */
			}
		}
		if (size&1) { fgetc(world->inp); len--; }	/* Skip odd byte */
		len -= size;
	}
}

/* readistg.c - read an Imagine Stage file
 *            - written by Glenn M. Lewis - 8/11/92
 *            - Hinge update by Rob Hounsell - 1/01/94
 */

static void process_OSIZ();
static void process_POSN();
static void process_ALGN();
static void process_PALN();
static void process_TALN();
static void process_HING();
static void process_PTH2();
static void process_GLB2();
static void process_AXIS();
static void process_LITE();
static void process_FILE();
static void process_SOBJ();

WORLD *read_ISTG(file)
FILE *file;
{
	register ULONG i, len, size;
	WORLD *world;
	char name[5];

	if (!file) return(0L);	/* File not open */

	if (!(world = (WORLD*)malloc(sizeof(WORLD)))) { OUT_MEM("WORLD"); }
	bzero((char*)world, sizeof(WORLD));
	world->inp = file;

	if (!already_read_header) {
		/* Parse the IFF ISTG file */
		get_name(name, 4, world);
		if (strcmp(name, "FORM") != 0) {
			fprintf(stderr, "ERROR: Input is not an IFF file.\n"); exit(-1); }
		if (!(len = get_ULONG(world))) {
			fprintf(stderr, "ERROR: Bad FORM length in IFF file.\n"); exit(-1); }
		get_name(name, 4, world);
		if (strcmp(name, "ISTG") != 0) {
			fprintf(stderr, "ERROR: IFF file is not a ISTG file.\n"); exit(-1); }
	} else {
		len =	(header_storage[4]<<24) | (header_storage[5]<<16) |
				(header_storage[6]<< 8) | (header_storage[7]    );
		already_read_header = 0;
	}
	len -= 4;

	/* Allocate the ISTG structure */
	if (!(world->istg = (ISTG*)malloc(sizeof(ISTG)))) { OUT_MEM("ISTG"); }
	bzero((char*)world->istg, sizeof(ISTG));

/* Here is the main loop: */
	while (len>0) {
		get_name(name, 4, world);
		size = get_ULONG(world);
		len -= 8;
		if (strcmp(name, "MAXF")==0) world->istg->maxf = get_UWORD(world);
		else
		if (strcmp(name, "SOBJ")==0) process_SOBJ(size, world);
		else {
			if (verbose_flag) output_warning(world, "ISTG ", name, size);
			else {
				for (i=size; i--; ) fgetc(world->inp);	/* Skip this section */
			}
		}
		if (size&1) { fgetc(world->inp); len--; }	/* Skip odd byte */
		len -= size;
	}

/* All done. */
	return(world);
}

SOBJ *add_SOBJ(istg)	/* to tail of ISTG's SOBJ list */
ISTG *istg;
{
	SOBJ *new;

	if (!(new = (SOBJ*)malloc(sizeof(SOBJ)))) OUT_MEM("SOBJ");
	bzero((char*)new, sizeof(SOBJ));

	if (!istg->head) {
		istg->head = istg->tail = new;
		return(new);
	}
	/* Add this SOBJ to the tail */
	new->prev = istg->tail;
	istg->tail->next = new;
	istg->tail = new;
	return(new);
}

static void process_SOBJ(len, world)
register ULONG len;
WORLD *world;
{
	register ULONG i, size;
	char name[5];
	SOBJ *sobj;

	if (verbose_flag>1) fprintf(stderr, "SOBJ chunk.\n");
	/* Add another SOBJ to the current ISTG list */
	sobj = add_SOBJ(world->istg);
	while (len>0) {
		get_name(name, 4, world);
		size = get_ULONG(world);
		len -= 8;
		if (verbose_flag>1) fprintf(stderr, "%s chunk.\n", name);
		if (strcmp(name, "NAME")==0) { get_name(sobj->name, (int)size, world);
		} else if (strcmp(name, "STGF")==0) { sobj->stgf = get_UWORD(world);
		} else if (strcmp(name, "OSIZ")==0) { process_OSIZ(size, sobj, world);
		} else if (strcmp(name, "POSN")==0) { process_POSN(size, sobj, world);
		} else if (strcmp(name, "ALGN")==0) { process_ALGN(size, sobj, world);
		} else if (strcmp(name, "PALN")==0) { process_PALN(size, sobj, world);
		} else if (strcmp(name, "TALN")==0) { process_TALN(size, sobj, world);
		} else if (strcmp(name, "HING")==0) { process_HING(size, sobj, world);
		} else if (strcmp(name, "PTH2")==0) { process_PTH2(size, sobj, world);
		} else if (strcmp(name, "GLB2")==0) { process_GLB2(size, sobj, world);
		} else if (strcmp(name, "AXIS")==0) { process_AXIS(size, sobj, world);
		} else if (strcmp(name, "LITE")==0) { process_LITE(size, sobj, world);
		} else if (strcmp(name, "FILE")==0) { process_FILE(size, sobj, world);
		} else {
			if (verbose_flag) output_warning(world, "SOBJ ", name, size);
			else {
				for (i=size; i--; ) fgetc(world->inp);	/* Skip this section */
			}
		}
		if (size&1) { fgetc(world->inp); len--; }	/* Skip odd byte */
		len -= size;
	}
}

static void process_OSIZ(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register OSIZ *osiz;

	if (!(osiz = (OSIZ*)malloc(sizeof(OSIZ)))) OUT_MEM("OSIZ");
	bzero((char*)osiz, sizeof(OSIZ));
	osiz->flags = get_UWORD(world);
	osiz->start = get_UWORD(world);
	osiz->stop  = get_UWORD(world);
	stuff_XYZ(&osiz->size, world);
	insert_into_sorted_list((PALN**)&sobj->osiz, (PALN*)osiz);
}

static void process_POSN(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register POSN *posn;

	if (!(posn = (POSN*)malloc(sizeof(POSN)))) OUT_MEM("POSN");
	bzero((char*)posn, sizeof(POSN));
	posn->flags = get_UWORD(world);
	posn->start = get_UWORD(world);
	posn->stop  = get_UWORD(world);
	stuff_XYZ(&posn->posn, world);
	insert_into_sorted_list((PALN**)&sobj->posn, (PALN*)posn);
}

static void process_ALGN(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register ALGN *algn;

	if (!(algn = (ALGN*)malloc(sizeof(ALGN)))) OUT_MEM("ALGN");
	bzero((char*)algn, sizeof(ALGN));
	algn->flags = get_UWORD(world);
	algn->start = get_UWORD(world);
	algn->stop  = get_UWORD(world);
	stuff_XYZ(&algn->algn, world);
	insert_into_sorted_list((PALN**)&sobj->algn, (PALN*)algn);
}

static void process_PALN(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register PALN *paln;

	if (!(paln = (PALN*)malloc(sizeof(PALN)))) OUT_MEM("PALN");
	bzero((char*)paln, sizeof(PALN));
	paln->flags = get_UWORD(world);
	paln->start = get_UWORD(world);
	paln->stop  = get_UWORD(world);
	insert_into_sorted_list(&sobj->paln, paln);
}

static void process_TALN(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register TALN *taln;

	if (!(taln = (TALN*)malloc(sizeof(TALN)))) OUT_MEM("TALN");
	bzero((char*)taln, sizeof(TALN));
	taln->flags = get_UWORD(world);
	taln->start = get_UWORD(world);
	taln->stop  = get_UWORD(world);
	taln->initial_y = get_FRACT(world);
	taln->final_y   = get_FRACT(world);
	size -= 15L;
	/* BCPL string... byte followed by string, followed by zero */
	get_UBYTE(world);
	if (size)
		get_name(taln->trackobj, (int)size, world);
	insert_into_sorted_list((PALN**)&sobj->taln, (PALN*)taln);
}

static void process_HING(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register HING *hing;

	if (!(hing = (HING*)malloc(sizeof(HING)))) OUT_MEM("HING");
	bzero((char*)hing, sizeof(HING));
	hing->flags = get_UWORD(world);
	hing->start = get_UWORD(world);
	hing->stop  = get_UWORD(world);
	size -= 7L;
	/* BCPL string... byte followed by string, followed by zero */
	get_UBYTE(world);
	if (size)
		get_name(hing->hingeobj, (int)size, world);
	insert_into_sorted_list((PALN**)&sobj->hing, (PALN*)hing);
}

static void process_PTH2(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register PTH2 *pth2;

	if (!(pth2 = (PTH2*)malloc(sizeof(PTH2)))) OUT_MEM("PTH2");
	bzero((char*)pth2, sizeof(PTH2));
	pth2->flags = get_UWORD(world);
	pth2->start = get_UWORD(world);
	pth2->stop  = get_UWORD(world);
	pth2->acceleration_frames = get_ULONG(world);
	pth2->start_speed = get_FRACT(world);
	pth2->deacceleration_frames = get_ULONG(world);
	pth2->end_speed = get_FRACT(world);
	size -= 23L;
	/* BCPL string... byte followed by string, followed by zero */
	get_UBYTE(world);
	if (size)
		get_name(pth2->path, (int)size, world);
	insert_into_sorted_list((PALN**)&sobj->pth2, (PALN*)pth2);
}

static void process_GLB2(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register GLB2 *glb2;

	if (!(glb2 = (GLB2*)malloc(sizeof(GLB2)))) OUT_MEM("GLB2");
	bzero((char*)glb2, sizeof(GLB2));
	glb2->flags = get_UWORD(world);
	glb2->start = get_UWORD(world);
	glb2->stop  = get_UWORD(world);
	glb2->sky_blending = get_ULONG(world);
	glb2->starfield = get_FRACT(world);
	glb2->transition = get_ULONG(world);
	/* The following are FRACTional colors */
	stuff_XYZ(&glb2->ambient, world);
	stuff_XYZ(&glb2->horizon, world);
	stuff_XYZ(&glb2->zenith1, world);
	stuff_XYZ(&glb2->zenith2, world);
	stuff_XYZ(&glb2->fog_color, world);
	glb2->fog_bottom = get_FRACT(world);
	glb2->fog_top    = get_FRACT(world);
	glb2->fog_length = get_FRACT(world);
	glb2->brush_seq  = get_ULONG(world);
	glb2->backdrop_seq = get_ULONG(world);
	get_name(glb2->backdrop, 256, world);		/* Fixed size */
	size -= 355L;
	/* BCPL string... byte followed by string, followed by zero */
	get_UBYTE(world);
	if (size)
		get_name(glb2->globalbrush, (int)size, world);	/* Var. size */
	insert_into_sorted_list((PALN**)&sobj->glb2, (PALN*)glb2);
}

static void process_AXIS(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register SAXIS *axis;

	if (!(axis = (SAXIS*)malloc(sizeof(SAXIS)))) OUT_MEM("SAXIS");
	bzero((char*)axis, sizeof(SAXIS));
	axis->flags = get_UWORD(world);
	axis->start = get_UWORD(world);
	axis->stop  = get_UWORD(world);
	insert_into_sorted_list((PALN**)&sobj->axis, (PALN*)axis);
}

static void process_LITE(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register LITE *lite;

	if (!(lite = (LITE*)malloc(sizeof(LITE)))) OUT_MEM("LITE");
	bzero((char*)lite, sizeof(LITE));
	lite->flags = get_UWORD(world);
	lite->start = get_UWORD(world);
	lite->stop  = get_UWORD(world);
	/* The following is a FRACTional color */
	stuff_XYZ(&lite->color, world);
	lite->transition = get_ULONG(world);
	insert_into_sorted_list((PALN**)&sobj->lite, (PALN*)lite);
}

static void process_FILE(size, sobj, world)
ULONG size;
SOBJ *sobj;
WORLD *world;
{
	register SFILE *file;

	if (!(file = (SFILE*)malloc(sizeof(SFILE)))) OUT_MEM("SFILE");
	bzero((char*)file, sizeof(SFILE));
	file->flags = get_UWORD(world);
	file->start = get_UWORD(world);
	file->stop  = get_UWORD(world);
	file->cycles_to_perform = get_FRACT(world);
	file->initial_cycle_phase = get_FRACT(world);
	file->transition = get_ULONG(world);
	size -= 19L;
	/* BCPL string... byte followed by string, followed by zero */
	get_UBYTE(world);
	if (size)
		get_name(file->object_description, (int)size, world);
	insert_into_sorted_list((PALN**)&sobj->file, (PALN*)file);
}

/* We use "PALN" here, even though this routine inserts all SOBJ sub-types
 * into their proper list, because all be need is "START" and "STOP"
 * information, and "PALN" is the minimal structure that provides this
 */
void insert_into_sorted_list(ppaln, paln)
PALN **ppaln, *paln;
{
	register PALN *p;

	if (!*ppaln) { *ppaln = paln; return; }		/* First in list */
	/* Now search through the list and insert in the proper place */
	for (p= *ppaln; p; p=p->next) {
		if (paln->start == p->start ||
			paln->start == p->stop  ||
			paln->stop  == p->start ||
			paln->stop  == p->stop) {
			fprintf(stderr, "WARNING!!! start and stop times collide! (%u-%u) vs (%u-%u)\n",
				paln->start, paln->stop, p->start, p->stop);
		}
		if (paln->stop < p->start) {	/* Insert the item before this one */
			paln->prev = p->prev;
			paln->next = p;
			p->prev = paln;
			if (p == *ppaln) { *ppaln = paln; }	/* New head of list */
			return;
		}
		if (!p->next) {	/* Insert after this item */
			p->next = paln;
			paln->prev = p;
			return;
		}
	}
	/* Never will reach here! */
}

/* Find the last existing key frame that would be applicable for "frame" */
void delete_unused_frames(orig, frame)
PALN **orig;
{
	register PALN *p;
	PALN *p2;

	for (p= *orig; p; ) {
		if (frame>=p->start && frame<=p->stop) return;		/* It falls within this keyframe */
		if (frame>=p->stop && (!p->next || (p->next && frame<p->next->start))) return;		/* This is the one! */
		/* Otherwise, delete it */
		if (p->next) p->next->prev = p->prev;
		if (p->prev) p->prev->next = p->next;
		if (p== *orig) *orig = p->next;
		p2 = p->next;
		free(p);
		p = p2;
	}
}

static void mtrx_ident(mtrx)
register MTRX *mtrx;
{
	mtrx->tran.x  = mtrx->tran.y  = mtrx->tran.z  = 0.0;
	mtrx->scal.x  = mtrx->scal.y  = mtrx->scal.z  = 1.0;
	mtrx->rota1.y = mtrx->rota1.z = 0.0;
	mtrx->rota2.x = mtrx->rota2.z = 0.0;
	mtrx->rota3.x = mtrx->rota3.y = 0.0;
	mtrx->rota1.x = mtrx->rota2.y = mtrx->rota3.z = 1.0;
}

static void mmultiply(s, d)
register double *s;
register double *d;
{
	register int j;
	double tmp[9];
	tmp[0] = s[0]*d[0] + s[1]*d[3] + s[2]*d[6];
	tmp[1] = s[0]*d[1] + s[1]*d[4] + s[2]*d[7];
	tmp[2] = s[0]*d[2] + s[1]*d[5] + s[2]*d[8];
	tmp[3] = s[3]*d[0] + s[4]*d[3] + s[5]*d[6];
	tmp[4] = s[3]*d[1] + s[4]*d[4] + s[5]*d[7];
	tmp[5] = s[3]*d[2] + s[4]*d[5] + s[5]*d[8];
	tmp[6] = s[6]*d[0] + s[7]*d[3] + s[8]*d[6];
	tmp[7] = s[6]*d[1] + s[7]*d[4] + s[8]*d[7];
	tmp[8] = s[6]*d[2] + s[7]*d[5] + s[8]*d[8];
	for (j = 9; j--; ) d[j] = tmp[j];
}

/*
Matrix Inversion
by Richard Carling
from "Graphics Gems", Academic Press, 1990
tweaked heavily by Glenn M. Lewis to not require "GraphicsGems.h" and
to work only on 3x3 matrices
*/


#define SMALL_NUMBER	1.e-8
#define det2x2(a,b,c,d)	((a)*(d)-(b)*(c))
#define det3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3)	\
	((a1) * det2x2((b2), (b3), (c2), (c3))	\
	-(b1) * det2x2((a2), (a3), (c2), (c3))	\
	+(c1) * det2x2((a2), (a3), (b2), (b3)))

/* 
 *   inverse(original_matrix, inverse_matrix)
 * 
 *    calculate the inverse of a 3x3 matrix
 *
 *     -1     
 *     A  = ___1__ adjoint A
 *         det A
 */

void inverse(in, out)
register double *in, *out;
{
    int i;
    double det;
	void adjoint();

    /* calculate the adjoint matrix */
    adjoint(in, out);
    /*  calculate the 3x3 determinent
     *  if the determinent is zero, 
     *  then the inverse matrix is not unique.
     */
    det = det3x3(
		in[0], in[1], in[2],
		in[3], in[4], in[5],
		in[6], in[7], in[8]
	);
    if (fabs(det) < SMALL_NUMBER) return;	/* non-singular matrix */
    /* scale the adjoint matrix to get the inverse */
	det = 1.0/det;
    for (i=0; i<9; i++) out[i] *= det;
}

/* 
 *   adjoint(original_matrix, inverse_matrix)
 * 
 *     calculate the adjoint of a 4x4 matrix
 *
 *      Let  a   denote the minor determinant of matrix A obtained by
 *           ij
 *
 *      deleting the ith row and jth column from A.
 *
 *                    i+j
 *     Let  b   = (-1)    a
 *          ij            ji
 *
 *    The matrix B = (b  ) is the adjoint of A
 *                     ij
 */

void adjoint(in, out)
register double *in, *out;
{
    /* row column labeling reversed since we transpose rows & columns */
    out[0]  =   det2x2(in[4], in[7], in[5], in[8]);
    out[4]  = - det2x2(in[3], in[6], in[5], in[8]);
    out[6]  =   det2x2(in[3], in[6], in[4], in[7]);

    out[1]  = - det2x2(in[1], in[7], in[2], in[8]);
    out[4]  =   det2x2(in[0], in[6], in[2], in[8]);
    out[7]  = - det2x2(in[0], in[6], in[1], in[7]);

    out[2]  =   det2x2(in[1], in[4], in[2], in[5]);
    out[5]  = - det2x2(in[0], in[3], in[2], in[5]);
    out[8]  =   det2x2(in[0], in[3], in[1], in[4]);
}

OBJECT *load_staging_object(filename, mtrx)
char *filename;
MTRX *mtrx;
{
	WORLD *new;
	FILE *newinp;
	register OBJECT *p;

	/* Now, load in the external object */
TRY_EXTR_AGAIN:
	if (!(newinp=fopen(filename, "r"))) {
		fprintf(stderr, "Can't load in EXTR object: '%s'.\n",
			filename);
		fprintf(stderr, "Enter filneame (or CR to abort): "); fflush(stderr);
		fgets(filename, 512, stdin);
		if (!filename[0] || filename[0]=='\n') return(0L);
		goto TRY_EXTR_AGAIN;
	}
	new = read_World(newinp);
	fclose(newinp);

	/* scale, rotate, and translate new object hierarchy */
	/* First, take a look at the root object's size an orientation,
	 * and scale all subobjects appropriately
	 */

	p = new->object;
	if (p) {
		if (p->desc->size) {
			if (p->desc->size->x != 0.0)
				mtrx->scal.x /= p->desc->size->x;
			if (p->desc->size->y != 0.0)
				mtrx->scal.y /= p->desc->size->y;
			if (p->desc->size->z != 0.0)
				mtrx->scal.z /= p->desc->size->z;
		}
		if (p->desc->axis) {
/*			inverse(&p->desc->axis->xaxi.x, &newmtrx.rota1.x);	*/
/*			mmultiply(&newmtrx.rota1.x, &mtrx->rota1.x);			*/
			mmultiply(&p->desc->axis->xaxi.x, &mtrx->rota1.x);
		}
	}
	for (p=new->object; p; p=p->next)
		move_extr(p, mtrx);

	p = new->object;
	free((char*)new);
	return(p);
}

void load_staging_frame_objects(world, frame)
WORLD *world;
int frame;
{
	register SOBJ *sobj;
	MTRX mtrx, tmpmtrx;
	OBJECT *obj;
	double angle;

	if (!world) return;
	if (!world->istg) return;
	if (frame<1 || frame>world->istg->maxf) {
		fprintf(stderr, "ERROR: desired frame (%d) is larger than MAXF (%d)\n",
			frame, world->istg->maxf);
		return;
	}
	/* Search through all of the "SOBJ" chunks and load in objects that
	 * are active during the specified frame
	 */
	for (sobj=world->istg->head; sobj; sobj=sobj->next) {
		/* Easiest method is to simply delete all the information in the SOBJ
		 * sub-chunks that does not fall within the specified frame time.
		 */
		delete_unused_frames((PALN**)&sobj->osiz, frame);		/* Size */
		delete_unused_frames((PALN**)&sobj->posn, frame);		/* Position */
		delete_unused_frames((PALN**)&sobj->algn, frame);		/* Alignment */
		delete_unused_frames((PALN**)&sobj->paln, frame);		/* Path Alignment */
		delete_unused_frames((PALN**)&sobj->taln, frame);		/* Track Alignment */
		delete_unused_frames((PALN**)&sobj->pth2, frame);		/* Follow Path */
		delete_unused_frames((PALN**)&sobj->glb2, frame);		/* Globals */
		delete_unused_frames((PALN**)&sobj->axis, frame);		/* Stagefile AXIS */
		delete_unused_frames((PALN**)&sobj->lite, frame);		/* Light */
		delete_unused_frames((PALN**)&sobj->file, frame);		/* Stage file */

		/* Now, we have a list of SOBJ chunks with information for this frame */
		if (!sobj->file) continue;	/* No filename information */

		/* Initialize transformation matrix structure */
		mtrx_ident(&mtrx);

		/* Now modify matrix based upon requested transformations */
		if (sobj->posn) { bcopy((char*)&sobj->posn->posn.x, (char*)&mtrx.tran.x, sizeof(XYZ_st)); }
		if (sobj->osiz) { bcopy((char*)&sobj->osiz->size.x, (char*)&mtrx.scal.x, sizeof(XYZ_st)); }
		if (sobj->algn) {
			/* First Z rotation, then X, then Y */
			if (sobj->algn->algn.z != 0.0) {
				angle = sobj->algn->algn.z * PI / 180.0;
				mtrx_ident(&tmpmtrx);	/* Initialize it */
				tmpmtrx.rota1.x = tmpmtrx.rota2.y = cos(angle);
				tmpmtrx.rota2.x = sin(angle);
				tmpmtrx.rota1.y = -tmpmtrx.rota2.x;
				mmultiply(&tmpmtrx.rota1.x, &mtrx.rota1.x);
			} else if (sobj->algn->algn.x != 0.0) {
				angle = sobj->algn->algn.x * PI / 180.0;
				mtrx_ident(&tmpmtrx);	/* Initialize it */
				tmpmtrx.rota2.y = tmpmtrx.rota3.z = cos(angle);
				tmpmtrx.rota3.y = sin(angle);
				tmpmtrx.rota2.z = -tmpmtrx.rota3.y;
				mmultiply(&tmpmtrx.rota1.x, &mtrx.rota1.x);
			} else if (sobj->algn->algn.y != 0.0) {
				angle = sobj->algn->algn.y * PI / 180.0;
				mtrx_ident(&tmpmtrx);	/* Initialize it */
				tmpmtrx.rota1.x = tmpmtrx.rota3.z = cos(angle);
				tmpmtrx.rota1.z = sin(angle);
				tmpmtrx.rota3.x = -tmpmtrx.rota1.z;
				mmultiply(&tmpmtrx.rota1.x, &mtrx.rota1.x);
			}
		}
		/* Okeydokey... let's load in that object! */
		obj = load_staging_object(sobj->file->object_description, &mtrx);
		if (world->curobj) {
			world->curobj->next = obj;
			obj->parent = world->curobj->parent;
			while (obj->next) {
				obj->next->parent = obj->parent;
				obj = obj->next;
				world->curobj = obj;
			}
		} else {
			world->object = obj;
			obj->parent = 0;
		}
		world->curobj = obj;
	}
}
