/* 
	name: getworld.c

	Descrition interpreter
	----------------------
	
	This part will read and interpret a scene description file.


    This source-code is part of the RayLab 1.1 package, and it is provided
    for your compiling pleasure.  You may use it, change it, re-compile it
    etc., as long as nobody else but you receive the changes/compilations
    that you have made!

    You may not use any part(s) of this source-code for your own productions
    without the permission from the author of RayLab. Please read the legal
    information found in the users documentation for RayLab for more details.

*/


#include  <stdlib.h>
#include  <string.h>

#include  "defs.h"
#include  "extern.h"


	extern int	RunMagic;

	long		LineCntr,NewLineCntr;

	TEXTURE		DefaultTexture;
	TRANSFORM	DefaultTransform;

	char Keywords[][20] = {
		{"PLANE:"}, {"SPHERE:"}, {"ELLIPSOID:"},	/* Top level keywords */
		{"TRIANGLE:"}, {"TRIANGLELIST:"}, {"BOX:"},
		{"DISC:"}, {"CYLINDER:"}, {"CONE:"},
		{"TORUS:"}, {"PEAK:"}, {"DEOFLASKA:"},
		{"CSG:"}, {"GLOBALS:"}, {"LIGHT:"},
		{"CAMERA:"}, {"DEFTEXTURE:"},
		{"DEFTRANSFORM:"},
		{"TEXTURE:"}, {"TRANSFORM:"}, {"INVERT"},	/* General object */
		{"NOSHADOW"}, {"SHADOW"}, {"BOUNDING"},
		{":END"},
		{"NORMAL"}, {"OFFSET"},				/* Plane     */
		{"CENTRE"}, {"RADIUS"},				/* Sphere    */
		{"CENTRE"}, {"RADIUS"},				/* Ellipsoid */
		{"CORNERS"},					/* Triangle  */
		{"CORNERS"},					/* TriangleList */
		{"CORNERS"},					/* Box       */
		{"NORMAL"}, {"CENTRE"}, {"RADIUS"},		/* Disc      */
		{"HEIGHT"}, {"RADIUS"}, {"START"}, {"END"},	/* Cylinder  */
		{"HEIGHT"}, {"START"}, {"END"}, {"R1"}, {"R2"},	/* Cone      */
		{"RADIUS"},
		{"R0"}, {"R1"}, {"RADIUS"}, {"MAJOR"},		/* Torus     */
		{"MINOR"},
		{"Dummy"},					/* Peak     */
		{"HEIGHT"}, {"RADIUS"}, {"VARIATION"},		/* Deoflaska */
		{"PLANE:"}, {"SPHERE:"}, {"ELLIPSOID:"},	/* Csg       */
		{"BOX:"}, {"CYLINDER:"}, {"CSG:"},
		{"AND"}, {"OR"}, {"MINUS"}, {"PLUS"},
		{"COLOR"}, {"COLORMAP"}, {"PATTERN"},		/* Texture */
		{"NONE"}, {"CHECKER"},  {"CIRCLES"}, {"RINGS"},
		{"SPOTS"}, {"GRADIENT"}, {"MARBLE"},
		{"SOFTMARBLE"}, {"SQUARES"}, {"BLURB"},
		{"MANDEL"}, {"WOOD"}, {"ANGULAR"},
		{"TURBULENCE"}, {"REFLECT"}, {"FILTER"},
		{"IOR"}, {"AMBIENT"}, {"DIFFUSE"}, {"PHONG"},
		{"PHONGSIZE"}, {"IMAGE:"}, {"DEFAULT"},
		{"TRANSFORM:"},	{":END"},
		{"TGA"}, {"IFF"}, {"PPM"}, {"MAPTYPE"},		/* Image  */
		{"INTERPOLATE"}, {"NOINTERPOLATE"}, {":END"},
		{"MOVE"}, {"SCALE"}, {"ROTATE"}, {"WHIRL"},	/* Transform */
		{"TWIST"}, {"NONE"}, {":END"},
		{"LOCATION"}, {"COLOR"}, {"SOFTNESS"},		/* Light */
		{"SIZE"}, {"JITTER"}, {":END"},
		{"LOCATION"}, {"VIEWPOINT"}, {"ASPECT"},	/* Camera  */
		{":END"},
		{"BACKGROUNDCOLOR"}, {"RECDEPTH"},		/* Globals */
		{"PICWIDTH"}, {"PICHEIGHT"}, {"ANTIALIASREC"},
		{"ANTIALIASTHRESHOLD"}, {"ANTIALIASJITTER"},
		{"DISPLAY"}, {"FORMAT"}, {"IFF"}, {"TGA"},
		{"PPM"}, {"NONE"}, {"QUICKSCAN"},
		{"RENDERMETHOD"}, {"TRACE"}, {"QUICK"},
		{"FOGCOLOR"}, {"FOGDISTANCE"},
		{":END"},
		{"END"}
	     };


#define  KW_TOPLEVEL     0
#define  KW_OBJECT       KW_TOPLEVEL+18
#define  KW_PLANE        KW_OBJECT + 7
#define  KW_SPHERE       KW_PLANE + 2
#define  KW_ELLIPSOID    KW_SPHERE + 2
#define  KW_TRIANGLE     KW_ELLIPSOID + 2
#define  KW_TRIANGLELIST KW_TRIANGLE + 1
#define  KW_BOX          KW_TRIANGLELIST + 1
#define  KW_DISC         KW_BOX + 1
#define  KW_CYLINDER     KW_DISC + 3
#define  KW_CONE         KW_CYLINDER + 4
#define  KW_TORUS        KW_CONE + 5
#define  KW_PEAK         KW_TORUS + 5
#define  KW_DEOFLASKA    KW_PEAK + 1
#define  KW_CSG          KW_DEOFLASKA + 3
#define  KW_TEXTURE      KW_CSG + 10
#define  KW_IMAGE        KW_TEXTURE + 28
#define  KW_TRANSFORM    KW_IMAGE + 7
#define  KW_LIGHT        KW_TRANSFORM + 7
#define  KW_CAMERA       KW_LIGHT + 6
#define  KW_GLOBALS      KW_CAMERA + 4
#define  KW_END          KW_GLOBALS + 20



/*****************************************************************
 *
 *      Setup default globals
 *
 *****************************************************************/

void SetupDefaults(void)
{
	RecDepth=10;	/* Was 3 in RayLab 1.0, which generally isn't enough for transparency */
	PicWidth=200; PicHeight=150;
	BackgroundColor.r=0.0; BackgroundColor.g=0.0; BackgroundColor.b=0.0;
	FogColor.r=0.8; FogColor.g=0.8; FogColor.b=0.8;
	FogDistance=0.0;	/* No fog */
	AntiAliasingRec=0L; AntiAliasingThreshold=0.3; AntiAliasingJitter=0.05; 
	ReqDisplayType=0L; OutputFormat=FORMAT_DEFAULT;
	RenderMethod=RENDER_TRACE;
	
	Camera.Location.x=0.0; Camera.Location.y=-10.0; Camera.Location.z=1.0;
	Camera.ViewPoint.x=0.0; Camera.ViewPoint.y=0.0; Camera.ViewPoint.z=0.0; 
	Camera.Aspect.x=4.0; Camera.Aspect.y=3.0; Camera.Aspect.z=5.0;
	CreateCamera(&Camera, &Camera.Location, &Camera.ViewPoint, &Camera.Aspect);
}


/*****************************************************************
 *
 *      This is the main input routine
 *
 *****************************************************************/


int CreateWorld(char *filename)
{
	int	RetOK=TRUE,EndReached=FALSE;
	char	strtmp[100];
	FILE	*parfile;

	LineCntr=NewLineCntr=1;
	NumObjects=NumLights=Num24Images=Num8Images=NumPrimitives=0;

	CreateDefTexture(&DefaultTexture);
	ClearTransform(&DefaultTransform);

	if((parfile=fopen(filename,"r"))!=OK) {
	    fprintf(textoutput,"Building scene:\n");
	    while((EndReached==FALSE)&&(RetOK==TRUE)&&(getnextkeyword(parfile,strtmp)==TRUE)) {
		if(strcmp(strtmp,(char *)"PLANE:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_PLANE);
		else if(strcmp(strtmp,(char *)"SPHERE:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_SPHERE);
		else if(strcmp(strtmp,(char *)"ELLIPSOID:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_ELLIPSOID);
		else if(strcmp(strtmp,(char *)"TRIANGLE:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_TRIANGLE);
		else if(strcmp(strtmp,(char *)"TRIANGLELIST:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_TRIANGLELIST);
		else if(strcmp(strtmp,(char *)"BOX:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_BOX);
		else if(strcmp(strtmp,(char *)"DISC:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_DISC);
		else if(strcmp(strtmp,(char *)"CYLINDER:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_CYLINDER);
		else if(strcmp(strtmp,(char *)"CONE:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_CONE);
		else if(strcmp(strtmp,(char *)"TORUS:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_TORUS);
		else if(strcmp(strtmp,(char *)"PEAK:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_PEAK);
		else if(strcmp(strtmp,(char *)"DEOFLASKA:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_DEOFLASKA);
		else if(strcmp(strtmp,(char *)"CSG:")==OK)
		    RetOK=CreateNewListObject(parfile,(short)SHAPE_CSG);
		else if(strcmp(strtmp,(char *)"LIGHT:")==OK)
		    RetOK=InitNewLight(parfile);
		else if(strcmp(strtmp,(char *)"CAMERA:")==OK)
		    RetOK=InitCamera(parfile);
		else if(strcmp(strtmp,(char *)"GLOBALS:")==OK)
		    RetOK=InitGlobals(parfile);
		else if(strcmp(strtmp,(char *)"DEFTEXTURE:")==OK)
		    RetOK=InitTexture(&DefaultTexture,parfile);
		else if(strcmp(strtmp,(char *)"DEFTRANSFORM:")==OK)
		    RetOK=InitTransform(&DefaultTransform,parfile);
		else if(strcmp(strtmp,(char *)"END")==OK) {
		    EndReached=TRUE;
		}
		else {
		    ParseError();
		    fprintf(textoutput,"Unknown keyword \"%s\"",strtmp);
		    RetOK=FALSE;
		}
	    }
	    fprintf(textoutput,"\n\n");
	    if(NumObjects<=0) {
	    	fprintf(textoutput,"No objects were declared!\n");
	    	RetOK=FALSE;
	    }
	    if(NumLights<=0) {
	    	fprintf(textoutput,"No lights were declared!\n");
	    	RetOK=FALSE;
	    }
	    if(RetOK==FALSE) fprintf(textoutput,"*** Failed!\n\n");
	    fclose(parfile);
	}
	else {
		fprintf(textoutput,"Error: Could not open description file \"%s\"\n",filename);
		RetOK=FALSE;
	}

	return(RetOK);
}



/*****************************************************************
 *
 *      Object creator(s)
 *
 *****************************************************************/


int CreateNewListObject(FILE *parfile, short ShapeType)
{
	int	RetOK;

	if(NumObjects<maxobjects) {
	    if((ObjectArray[NumObjects]=CreateNewObject(parfile,(short)ShapeType))!=NULL) {
		NumObjects++;
		RetOK=TRUE;
	    }
	    else {
		RetOK=FALSE;
	    }
	}
	else {
		ParseError();
		fprintf(textoutput,"Maximum amount of objects exceeded!\n");
		RetOK=FALSE;
	}

	return(RetOK);
}


OBJECT *CreateNewObject(FILE *parfile, short ShapeType)
{
	void	*Shape;
	CSG	*Csg;
	int	RetOK, ObjEndReached;
	char	strtmp[100];
	TEXTURE	TmpTexture;
	TRANSFORM TmpTransform;
	OBJECT	*TmpObject;

	fprintf(textoutput,".");

	TmpObject=(OBJECT *)malloc(sizeof(OBJECT));
	if(TmpObject!=NULL) {
	    TmpObject->BoundType=BOUND_NONE;
	    TmpObject->Bound=NULL;
	    TmpObject->ShapeType=ShapeType;
	    TmpObject->Shape=NULL;
	    TmpObject->Invert=INVERT_FALSE;
	    TmpObject->Shadow=CASTSHADOW_TRUE;
	    CopyTexture(&TmpTexture,&DefaultTexture);
	    CopyTransform(&TmpTransform,&DefaultTransform);
	    switch(ShapeType) {
		case SHAPE_PLANE:
		    Shape=(void *)InitNewPlane(parfile,strtmp);
		    break;
		case SHAPE_SPHERE:
		    Shape=(void *)InitNewSphere(parfile,strtmp);
		    break;
		case SHAPE_ELLIPSOID:
		    Shape=(void *)InitNewEllipsoid(parfile,strtmp);
		    break;
		case SHAPE_BOX:
		    Shape=(void *)InitNewBox(parfile,strtmp);
		    break;
		case SHAPE_TRIANGLE:
		    Shape=(void *)InitNewTriangle(parfile,strtmp);
		    break;
		case SHAPE_TRIANGLELIST:
		    Shape=(void *)InitNewTriangleList(parfile,strtmp);
		    break;
		case SHAPE_DISC:
		    Shape=(void *)InitNewDisc(parfile,strtmp);
		    break;
		case SHAPE_CYLINDER:
		    Shape=(void *)InitNewCylinder(parfile,strtmp,&TmpTransform);
		    break;
		case SHAPE_CONE:
		    Shape=(void *)InitNewCone(parfile,strtmp,&TmpTransform);
		    break;
		case SHAPE_TORUS:
		    Shape=(void *)InitNewTorus(parfile,strtmp);
		    break;
		case SHAPE_PEAK:
		    Shape=(void *)InitNewPeak(parfile,strtmp);
		    break;
		case SHAPE_DEOFLASKA:
		    Shape=(void *)InitNewDeoflaska(parfile,strtmp);
		    break;
		case SHAPE_CSG:
		    Shape=(void *)InitNewCsg(parfile,strtmp);
		    break;
		default:
		    ParseError();
		    fprintf(textoutput,"Onknown object-type: %d (?!)\n",ShapeType);
		    Shape=NULL;
		    break;
	    }
	    if(Shape!=NULL) {
		RetOK=TRUE;

		if(ShapeType != SHAPE_CSG) NumPrimitives++;

		ObjEndReached=FALSE;
		while((ObjEndReached==FALSE)&&(RetOK==TRUE)) {
		    if(strcmp(strtmp,(char *)"TEXTURE:")==OK) {
			if(ShapeType != SHAPE_CSG) {
			    RetOK=InitTexture(&TmpTexture,parfile);
			}
			else {
			    ParseError();
			    fprintf(textoutput,"Texture-declarations are not allowed in CSG's\n");
			    RetOK=FALSE;
			}
		    }
		    else if(strcmp(strtmp,(char *)"TRANSFORM:")==OK) {
			RetOK=InitTransform(&TmpTransform,parfile);
			AddTransform(&TmpTexture.Transform,&TmpTransform);
			if(ShapeType == SHAPE_CSG) {
			    ChangeAllCSGTextureTransforms(((CSG *)Shape)->Object1,((CSG *)Shape)->Object2,&TmpTransform);
			}
			TransformBounding(TmpObject,&TmpTransform);
		    }
		    else if(strcmp(strtmp,(char *)"INVERT")==OK) {
			if(TmpObject->Invert==INVERT_TRUE)
			    TmpObject->Invert=INVERT_FALSE;
			else
			    TmpObject->Invert=INVERT_TRUE;
		    }
		    else if(strcmp(strtmp,(char *)"NOSHADOW")==OK) {
			TmpObject->Shadow=CASTSHADOW_FALSE;
			if(ShapeType == SHAPE_CSG) {
			    ChangeAllCSGNoshadow(((CSG *)Shape)->Object1,((CSG *)Shape)->Object2);
			}
		    }
		    else if(strcmp(strtmp,(char *)"SHADOW")==OK) {
			TmpObject->Shadow=CASTSHADOW_TRUE;
			if(ShapeType == SHAPE_CSG) {
			    ChangeAllCSGShadow(((CSG *)Shape)->Object1,((CSG *)Shape)->Object2);
			}
		    }
		    else if(strcmp(strtmp,(char *)"BOUNDING")==OK) {
			if((ObjEndReached==FALSE)&&(getnextkeyword(parfile,strtmp)==TRUE)) {	/* Get next keyword */
			    RetOK=TRUE;
			    if(strcmp(strtmp,(char *)"BOX:")==OK) {
				TmpObject->BoundType=BOUND_BOX;
				TmpObject->Bound=(void *)InitNewBox(parfile,strtmp);
			    }
			    else if(strcmp(strtmp,(char *)"SPHERE:")==OK) {
				TmpObject->BoundType=BOUND_SPHERE;
				TmpObject->Bound=(void *)InitNewSphere(parfile,strtmp);
			    }
			    else {
				ParseError();
				fprintf(textoutput,"Expected box or sphere. \"%s\" is not a valid bounding shape\n",strtmp);
				RetOK=FALSE;
			    }
			    if((TmpObject->Bound!=NULL)&&(strcmp(strtmp,(char *)":END")==OK))  RetOK=TRUE; 
			    else {
				if((RetOK==TRUE)&&(strcmp(strtmp,(char *)":END")!=OK)) {
				    ParseError();
				    fprintf(textoutput,"Not allowed in bounding: \"%s\"\n",strtmp);
				}
				RetOK=FALSE;
			    }
			} else RetOK=FALSE;
		    }
		    else if(strcmp(strtmp,(char *)":END")==OK) {
			ObjEndReached=TRUE;
		    }
		    else {
			ParseError();
			fprintf(textoutput,"Unknown object-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		    }
		    if((RetOK==TRUE)&&(ObjEndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		    }
		}

		if(RetOK==TRUE) {
		    TmpObject->Shape=Shape;
		    TmpObject->ShapeType=ShapeType;
		    CopyTexture(&TmpObject->Texture,&TmpTexture);
		    CopyTransform(&TmpObject->Transform,&TmpTransform);
		}
		else {
		    switch(ShapeType) {
			case SHAPE_PLANE:
			    free((PLANE *)Shape);
			    break;
			case SHAPE_SPHERE:
			    free((SPHERE *)Shape);
			    break;
			case SHAPE_ELLIPSOID:
			    free((ELLIPSOID *)Shape);
			    break;
			case SHAPE_BOX:
			    free((BOX *)Shape);
			    break;
			case SHAPE_TRIANGLE:
			    free((TRIANGLE *)Shape);
			    break;
			case SHAPE_TRIANGLELIST:
			    free((TRIANGLELIST *)Shape);
			    break;
			case SHAPE_DISC:
			    free((DISC *)Shape);
			    break;
			case SHAPE_CYLINDER:
			    free((CYLINDER *)Shape);
			    break;
			case SHAPE_CONE:
			    free((CONE *)Shape);
			    break;
			case SHAPE_TORUS:
			    free((TORUS *)Shape);
			    break;
			case SHAPE_PEAK:
			    free((PEAK *)Shape);
			    break;
			case SHAPE_DEOFLASKA:
			    free((DEOFLASKA *)Shape);
			    break;
			case SHAPE_CSG:
			    Csg=(CSG *)Shape;
			    FreeObjectMemory(Csg->Object1); FreeObjectMemory(Csg->Object2);
			    free((CSG *)Shape);
			    break;
			default:
			    ParseError();
			    fprintf(textoutput,"Onknown object-type: %d (?!)\n",ShapeType);
			    break;
		    }
		    RetOK=FALSE;
		}
	    }
	    else RetOK=FALSE;

	    if(RetOK==FALSE) {
		free(TmpObject);
		TmpObject=NULL;
	    }
	}
	else {
	    ParseError();
	    fprintf(textoutput,"Could not allocate memory for new object\n");
	    TmpObject=NULL;
	    RetOK=FALSE;
	}

	return(TmpObject);
}



/*****************************************************************
 *
 *      Initialization routines
 *
 *****************************************************************/


PLANE *InitNewPlane(FILE *parfile, char *strtmp)
{
	PLANE	TmpPlane, *NewPlane;
	int	RetOK=TRUE, EndReached=FALSE;

	TmpPlane.Normal.x=0.0;	/* Set default values */
	TmpPlane.Normal.y=0.0;
	TmpPlane.Normal.z=1.0;
	TmpPlane.a=0.0;

	NewPlane=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((RetOK==TRUE)&&(EndReached==FALSE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"NORMAL")==OK) {
			RetOK=getnextvector(&TmpPlane.Normal,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"OFFSET")==OK) {
			RetOK=getnextdvalue(&TmpPlane.a,parfile,mincoord,maxcoord,strtmp);
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown plane-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewPlane=(PLANE *)malloc(sizeof(PLANE));
	    if(NewPlane!=NULL) {
		CopyVector(&NewPlane->Normal,&TmpPlane.Normal);
		NewPlane->a=TmpPlane.a;
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new plane\n");
		NewPlane=NULL;
	    }
	}

	return(NewPlane);
}


SPHERE *InitNewSphere(FILE *parfile, char *strtmp)
{
	SPHERE	TmpSphere, *NewSphere;
	int	RetOK=TRUE, EndReached=FALSE;

	TmpSphere.Centre.x=0.0;	/* Set default values */
	TmpSphere.Centre.y=0.0;
	TmpSphere.Centre.z=0.0;
	TmpSphere.r=1.0;

	NewSphere=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((RetOK==TRUE)&&(EndReached==FALSE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"CENTRE")==OK) {
			RetOK=getnextvector((VECTOR *)&TmpSphere.Centre,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"RADIUS")==OK) {
			RetOK=getnextdvalue(&TmpSphere.r,parfile,mincoord,maxcoord,strtmp);
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown sphere-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewSphere=(SPHERE *)malloc(sizeof(SPHERE));
	    if(NewSphere!=NULL) {
		CopyPoint(&NewSphere->Centre,&TmpSphere.Centre);
		NewSphere->r=TmpSphere.r;
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new sphere\n");
		NewSphere=NULL;
	    }
	}

	return(NewSphere);
}



ELLIPSOID *InitNewEllipsoid(FILE *parfile, char *strtmp)
{
	ELLIPSOID	TmpEllipsoid, *NewEllipsoid;
	int	RetOK=TRUE, EndReached=FALSE;

	TmpEllipsoid.Centre.x=0.0;	/* Set default values */
	TmpEllipsoid.Centre.y=0.0;
	TmpEllipsoid.Centre.z=0.0;
	TmpEllipsoid.Radius.x=1.0;
	TmpEllipsoid.Radius.y=1.0;
	TmpEllipsoid.Radius.z=1.0;

	NewEllipsoid=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((RetOK==TRUE)&&(EndReached==FALSE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"CENTRE")==OK) {
			RetOK=getnextvector((VECTOR *)&TmpEllipsoid.Centre,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"RADIUS")==OK) {
			RetOK=getnextvector((VECTOR *)&TmpEllipsoid.Radius,parfile,mincoord,maxcoord,strtmp);
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown ellipsoid-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewEllipsoid=(ELLIPSOID *)malloc(sizeof(ELLIPSOID));
	    if(NewEllipsoid!=NULL) {
		CopyPoint(&NewEllipsoid->Centre,&TmpEllipsoid.Centre);
		CopyVector(&NewEllipsoid->Radius,&TmpEllipsoid.Radius);
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new ellipsoid\n");
		NewEllipsoid=NULL;
	    }
	}

	return(NewEllipsoid);
}



TRIANGLE *InitNewTriangle(FILE *parfile, char *strtmp)
{
	double	maxn,projection;
	TRIANGLE  *NewTriangle;
	POINT	Corners[3];
	VECTOR	v1,v2,n;
	int	RetOK=TRUE, EndReached=FALSE, i;

	Corners[0].x=0.0;			/* Set default values */
	Corners[0].y=0.0;
	Corners[0].z=0.0;
	Corners[1].x=1.0;
	Corners[1].y=1.0;
	Corners[1].z=1.0;
	Corners[2].x=-1.0;
	Corners[2].y=1.0;
	Corners[2].z=1.0;

	NewTriangle=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((RetOK==TRUE)&&(EndReached==FALSE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"CORNERS")==OK) {
		    i=0;
		    while((i<3)&&(RetOK==TRUE)) {
			RetOK=getnextvector((VECTOR *)&Corners[i],parfile,mincoord,maxcoord,strtmp);
			i++;
		    }
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown triangle-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewTriangle=(TRIANGLE *)malloc(sizeof(TRIANGLE));
	    if(NewTriangle!=NULL) {
		v1.x=Corners[1].x-Corners[0].x;
		v1.y=Corners[1].y-Corners[0].y;
		v1.z=Corners[1].z-Corners[0].z;
		v2.x=Corners[2].x-Corners[0].x;
		v2.y=Corners[2].y-Corners[0].y;
		v2.z=Corners[2].z-Corners[0].z;
		CrossProduct(&n,&v1,&v2);
		CopyVector(&NewTriangle->Normal,&n);
		NewTriangle->Offset=Corners[0].x*n.x+Corners[0].y*n.y+Corners[0].z*n.z;

		maxn=fabs(n.x);
		projection=PROJ_YZ;
		if(fabs(n.y)>maxn) {
			maxn=fabs(n.y);
			projection=PROJ_XZ;
		}
		if(fabs(n.z)>maxn) {
			projection=PROJ_XY;
		}
		NewTriangle->Projection=projection;
		switch((int)projection) {
		    case PROJ_XY:
			for(i=0;i<3;i++) {
				NewTriangle->Corners[i].u=Corners[i].x;
				NewTriangle->Corners[i].v=Corners[i].y;
			}
			break;
		    case PROJ_XZ:
			for(i=0;i<3;i++) {
				NewTriangle->Corners[i].u=Corners[i].x;
				NewTriangle->Corners[i].v=Corners[i].z;
			}
			break;
		    case PROJ_YZ:
			for(i=0;i<3;i++) {
				NewTriangle->Corners[i].u=Corners[i].y;
				NewTriangle->Corners[i].v=Corners[i].z;
			}
			break;
		}
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new triangle\n");
		NewTriangle=NULL;
	    }
	}

	return(NewTriangle);
}



TRIANGLELIST *InitNewTriangleList(FILE *parfile, char *strtmp)
{
	double	maxn,projection;
	TRIANGLELIST  *FirstTriangle, *OldTriangle, *NextTriangle;
	POINT	Corners[3];
	VECTOR	v1,v2,n;
	int	i, ThisIsFirstTriangle=TRUE, RetOK=TRUE, EndReached=FALSE;

	Corners[0].x=0.0;			/* Set default values */
	Corners[0].y=0.0;
	Corners[0].z=0.0;
	Corners[1].x=1.0;
	Corners[1].y=1.0;
	Corners[1].z=1.0;
	Corners[2].x=-1.0;
	Corners[2].y=1.0;
	Corners[2].z=1.0;

	FirstTriangle=OldTriangle=NULL;

	RetOK=getnextkeyword(parfile,strtmp);	/* Get first keyword */
	while((RetOK==TRUE)&&(EndReached==FALSE)&&(IsObjectKW(strtmp)==FALSE)) {
	    if(strcmp(strtmp,(char *)"CORNERS")==OK) {
		i=0;
		while((i<3)&&(RetOK==TRUE)) {
		    RetOK=getnextvector((VECTOR *)&Corners[i],parfile,mincoord,maxcoord,strtmp);
		    i++;
		}
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Unknown trianglelist-keyword \"%s\"\n",strtmp);
		RetOK=FALSE;
	    }

	    NextTriangle=NULL;
	    if(RetOK==TRUE) {
	      NextTriangle=(TRIANGLELIST *)malloc(sizeof(TRIANGLELIST));
	      if(NextTriangle!=NULL) {
		v1.x=Corners[1].x-Corners[0].x;
		v1.y=Corners[1].y-Corners[0].y;
		v1.z=Corners[1].z-Corners[0].z;
		v2.x=Corners[2].x-Corners[0].x;
		v2.y=Corners[2].y-Corners[0].y;
		v2.z=Corners[2].z-Corners[0].z;
		CrossProduct(&n,&v1,&v2);
		CopyVector(&NextTriangle->Normal,&n);
		NextTriangle->Offset=Corners[0].x*n.x+Corners[0].y*n.y+Corners[0].z*n.z;

		maxn=fabs(n.x);
		projection=PROJ_YZ;
		if(fabs(n.y)>maxn) {
			maxn=fabs(n.y);
			projection=PROJ_XZ;
		}
		if(fabs(n.z)>maxn) {
			projection=PROJ_XY;
		}
		NextTriangle->Projection=projection;
		switch((int)projection) {
		    case PROJ_XY:
			for(i=0;i<3;i++) {
				NextTriangle->Corners[i].u=Corners[i].x;
				NextTriangle->Corners[i].v=Corners[i].y;
			}
			break;
		    case PROJ_XZ:
			for(i=0;i<3;i++) {
				NextTriangle->Corners[i].u=Corners[i].x;
				NextTriangle->Corners[i].v=Corners[i].z;
			}
			break;
		    case PROJ_YZ:
			for(i=0;i<3;i++) {
				NextTriangle->Corners[i].u=Corners[i].y;
				NextTriangle->Corners[i].v=Corners[i].z;
			}
			break;
		}

		NextTriangle->NextTriangle=NULL;
		if(ThisIsFirstTriangle==TRUE) {
			FirstTriangle=NextTriangle;
			ThisIsFirstTriangle=FALSE;
		}
		else {
			OldTriangle->NextTriangle=NextTriangle;
		}
		OldTriangle=NextTriangle;
	      }
	      else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new triangle\n");
		FreeTriangleList(FirstTriangle);
		FirstTriangle=NULL;
		RetOK=FALSE;
	      }

	      if((RetOK==TRUE)&&(EndReached==FALSE))
		RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
	    }
	}

	return(FirstTriangle);
}



BOX *InitNewBox(FILE *parfile, char *strtmp)
{
	double	maxx,maxy,maxz,minx,miny,minz;
	BOX	*NewBox;
	POINT	Corners[2];
	int	RetOK=TRUE, EndReached=FALSE, i;

	Corners[0].x=0.0;			/* Set default values */
	Corners[0].y=0.0;
	Corners[0].z=0.0;
	Corners[1].x=1.0;
	Corners[1].y=1.0;
	Corners[1].z=1.0;

	NewBox=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((RetOK==TRUE)&&(EndReached==FALSE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"CORNERS")==OK) {
		    i=0;
		    while((i<2)&&(RetOK==TRUE)) {
			RetOK=getnextvector((VECTOR *)&Corners[i],parfile,mincoord,maxcoord,strtmp);
			i++;
		    }
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown box-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewBox=(BOX *)malloc(sizeof(BOX));
	    if(NewBox!=NULL) {
		minx=maxx=Corners[0].x;				/* Calculate min/max boundaries for x,y,z...    */
		miny=maxy=Corners[0].y;				/* This is so that you can give the coordinates */
		minz=maxz=Corners[0].z;				/* in any order.                                */
		if(Corners[1].x>maxx) maxx=Corners[1].x;
		else if(Corners[1].x<minx) minx=Corners[1].x;
		if(Corners[1].y>maxy) maxy=Corners[1].y;
		else if(Corners[1].y<miny) miny=Corners[1].y;
		if(Corners[1].z>maxz) maxz=Corners[1].z;
		else if(Corners[1].z<minz) minz=Corners[1].z;
		CreatePoint(&NewBox->Corners[0],minx,miny,minz);
		CreatePoint(&NewBox->Corners[1],maxx,maxy,maxz);
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new box\n");
		NewBox=NULL;
	    }
	}

	return(NewBox);
}


DISC *InitNewDisc(FILE *parfile, char *strtmp)
{
	DISC	TmpDisc, *NewDisc;
	int	RetOK=TRUE, EndReached=FALSE;

	TmpDisc.Plane.Normal.x=0.0;	/* Set default values */
	TmpDisc.Plane.Normal.y=0.0;
	TmpDisc.Plane.Normal.z=1.0;
	TmpDisc.Plane.a=0.0;
	TmpDisc.Centre.x=0.0;		/* Set default values */
	TmpDisc.Centre.y=0.0;
	TmpDisc.Centre.z=0.0;
	TmpDisc.r=1.0;

	NewDisc=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((EndReached==FALSE)&&(RetOK==TRUE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"NORMAL")==OK) {
			RetOK=getnextvector(&TmpDisc.Plane.Normal,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"CENTRE")==OK) {
			RetOK=getnextvector((VECTOR *)&TmpDisc.Centre,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"RADIUS")==OK) {
			RetOK=getnextdvalue(&TmpDisc.r,parfile,0.0,maxcoord,strtmp);
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown disc-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewDisc=(DISC *)malloc(sizeof(DISC));
	    if(NewDisc!=NULL) {
		CopyPoint(&NewDisc->Centre,&TmpDisc.Centre);
		NewDisc->r=TmpDisc.r;
		CopyVector(&NewDisc->Plane.Normal,&TmpDisc.Plane.Normal);
		NewDisc->Plane.a=(TmpDisc.Plane.Normal.x*TmpDisc.Centre.x+TmpDisc.Plane.Normal.y*TmpDisc.Centre.y+TmpDisc.Plane.Normal.z*TmpDisc.Centre.z);
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new disc\n");
		NewDisc=NULL;
	    }
	}

	return(NewDisc);
}



CYLINDER *InitNewCylinder(FILE *parfile, char *strtmp, TRANSFORM *ObjTrans)
{
	double	a;
	POINT	CylStart,CylEnd;
	VECTOR	DirV,RotV;
	TRANSFORM TmpTrans;
	CYLINDER	TmpCyl, *NewCyl;
	int	RetOK=TRUE, EndReached=FALSE;

	CreatePoint(&CylStart,0.0,0.0,0.0);
	CreatePoint(&CylEnd,0.0,0.0,1.0);
	TmpCyl.h=TmpCyl.r=1.0;

	NewCyl=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((EndReached==FALSE)&&(RetOK==TRUE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"HEIGHT")==OK) {
			RetOK=getnextdvalue(&CylEnd.z,parfile,0.0,maxcoord,strtmp);
			CylStart.x=CylStart.y=CylStart.z=CylEnd.x=CylEnd.y=0.0;
		}
		else if(strcmp(strtmp,(char *)"RADIUS")==OK) {
			RetOK=getnextdvalue(&TmpCyl.r,parfile,0.0,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"START")==OK) {
			RetOK=getnextvector((VECTOR *)&CylStart,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"END")==OK) {
			RetOK=getnextvector((VECTOR *)&CylEnd,parfile,mincoord,maxcoord,strtmp);
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown cylinder-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewCyl=(CYLINDER *)malloc(sizeof(CYLINDER));
	    if(NewCyl!=NULL) {
		NewCyl->r=TmpCyl.r;
		SubVector(&DirV,(VECTOR *)&CylEnd,(VECTOR *)&CylStart);
		NewCyl->h=VectorLength(&DirV);
	/* Transform the cylinder to its place in space */
		ClearTransform(&TmpTrans);
		a=sqrt(DirV.x*DirV.x+DirV.y*DirV.y);
		RotV.x=0.0;
		RotV.y=asin(a/NewCyl->h);
		if(DirV.z<0.0) RotV.y=PI-RotV.y;
		if(a>EPSILON) {
			RotV.z=acos(DirV.x/a);
			if(DirV.y<0.0) RotV.z=PIM2-RotV.z;
		}
		else RotV.z=0.0;
		if(VectorLength(&RotV)>EPSILON) {
			TmpTrans.Entry[TmpTrans.NumTransforms].Type=TRANSFORM_ROTATE;
			CopyVector(&TmpTrans.Entry[TmpTrans.NumTransforms].Values,&RotV);
			TmpTrans.NumTransforms++;
		}
		if(VectorLength((VECTOR *)&CylStart)>EPSILON) {
			TmpTrans.Entry[TmpTrans.NumTransforms].Type=TRANSFORM_MOVE;
			CopyVector(&TmpTrans.Entry[TmpTrans.NumTransforms].Values,(VECTOR *)&CylStart);
			TmpTrans.NumTransforms++;
		}
		AddTransform(&TmpTrans,ObjTrans);
		CopyTransform(ObjTrans,&TmpTrans);
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new cylinder\n");
		NewCyl=NULL;
	    }
	}

	return(NewCyl);
}


CONE *InitNewCone(FILE *parfile, char *strtmp, TRANSFORM *ObjTrans)
{
	double	a;
	POINT	ConeStart,ConeEnd;
	VECTOR	DirV,RotV;
	TRANSFORM TmpTrans;
	CONE	TmpCone, *NewCone;
	char	strtmp2[100];
	int	RetOK=TRUE, EndReached=FALSE;

	CreatePoint(&ConeStart,0.0,0.0,0.0);
	CreatePoint(&ConeEnd,0.0,0.0,1.0);
	TmpCone.h=TmpCone.r1=1.0;
	TmpCone.r2=0.0;

	NewCone=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((EndReached==FALSE)&&(RetOK==TRUE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"HEIGHT")==OK) {
		    RetOK=getnextdvalue(&ConeEnd.z,parfile,0.0,maxcoord,strtmp);
		    ConeStart.x=ConeStart.y=ConeStart.z=ConeEnd.x=ConeEnd.y=0.0;
		}
		else if(strcmp(strtmp,(char *)"R1")==OK) {
		    RetOK=getnextdvalue(&TmpCone.r1,parfile,0.0,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"R2")==OK) {
		    RetOK=getnextdvalue(&TmpCone.r2,parfile,0.0,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"START")==OK) {
		    RetOK=getnextvector((VECTOR *)&ConeStart,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"END")==OK) {
		    RetOK=getnextvector((VECTOR *)&ConeEnd,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"RADIUS")==OK) {
		    if(getnextkeyword(parfile,strtmp2)==TRUE) {
			if(strcmp(strtmp2,(char *)"START")==OK) {
			    RetOK=getnextdvalue(&TmpCone.r1,parfile,0.0,maxcoord,strtmp);
			}
			else {
			    ParseError();
			    fprintf(textoutput,"Expected START, found \"%s\"\n",strtmp2);
			    RetOK=FALSE;
			}
		    }
		    else RetOK=FALSE;
		    if((RetOK==TRUE)&&(getnextkeyword(parfile,strtmp2)==TRUE)) {
			if(strcmp(strtmp2,(char *)"END")==OK) {
			    RetOK=getnextdvalue(&TmpCone.r2,parfile,0.0,maxcoord,strtmp);
			}
			else {
			    ParseError();
			    fprintf(textoutput,"Expected END, found \"%s\"\n",strtmp2);
			    RetOK=FALSE;
			}
		    }
		    else RetOK=FALSE;
		}
		else {
		    ParseError();
		    fprintf(textoutput,"Unknown cone-keyword \"%s\"\n",strtmp);
		    RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
		    RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewCone=(CONE *)malloc(sizeof(CONE));
	    if(NewCone!=NULL) {
		NewCone->r1=TmpCone.r1;
		NewCone->r2=TmpCone.r2;
		SubVector(&DirV,(VECTOR *)&ConeEnd,(VECTOR *)&ConeStart);
		NewCone->h=VectorLength(&DirV);
	/* Transform the cone to its place in space */
		ClearTransform(&TmpTrans);
		a=sqrt(DirV.x*DirV.x+DirV.y*DirV.y);
		RotV.x=0.0;
		RotV.y=asin(a/NewCone->h);
		if(DirV.z<0.0) RotV.y=PI-RotV.y;
		if(a>EPSILON) {
			RotV.z=acos(DirV.x/a);
			if(DirV.y<0.0) RotV.z=PIM2-RotV.z;
		}
		else RotV.z=0.0;
		if(VectorLength(&RotV)>EPSILON) {
			TmpTrans.Entry[TmpTrans.NumTransforms].Type=TRANSFORM_ROTATE;
			CopyVector(&TmpTrans.Entry[TmpTrans.NumTransforms].Values,&RotV);
			TmpTrans.NumTransforms++;
		}
		if(VectorLength((VECTOR *)&ConeStart)>EPSILON) {
			TmpTrans.Entry[TmpTrans.NumTransforms].Type=TRANSFORM_MOVE;
			CopyVector(&TmpTrans.Entry[TmpTrans.NumTransforms].Values,(VECTOR *)&ConeStart);
			TmpTrans.NumTransforms++;
		}
		AddTransform(&TmpTrans,ObjTrans);
		CopyTransform(ObjTrans,&TmpTrans);
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new cone\n");
		NewCone=NULL;
	    }
	}

	return(NewCone);
}


TORUS *InitNewTorus(FILE *parfile, char *strtmp)
{
	TORUS	TmpTorus, *NewTorus;
	char	strtmp2[100];
	int	RetOK=TRUE, EndReached=FALSE;

	TmpTorus.r0=1.0;	/* Set default values */
	TmpTorus.r1=0.5;

	NewTorus=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((RetOK==TRUE)&&(EndReached==FALSE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"R0")==OK) {
		    RetOK=getnextdvalue(&TmpTorus.r0,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"R1")==OK) {
		    RetOK=getnextdvalue(&TmpTorus.r1,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"RADIUS")==OK) {
		    if(getnextkeyword(parfile,strtmp2)==TRUE) {
			if(strcmp(strtmp2,(char *)"MAJOR")==OK) {
			    RetOK=getnextdvalue(&TmpTorus.r0,parfile,0.0,maxcoord,strtmp);
			}
			else {
			    ParseError();
			    fprintf(textoutput,"Expected MAJOR, found \"%s\"\n",strtmp2);
			    RetOK=FALSE;
			}
		    }
		    else RetOK=FALSE;
		    if((RetOK==TRUE)&&(getnextkeyword(parfile,strtmp2)==TRUE)) {
			if(strcmp(strtmp2,(char *)"MINOR")==OK) {
			    RetOK=getnextdvalue(&TmpTorus.r1,parfile,0.0,maxcoord,strtmp);
			}
			else {
			    ParseError();
			    fprintf(textoutput,"Expected MINOR, found \"%s\"\n",strtmp2);
			    RetOK=FALSE;
			}
		    }
		    else RetOK=FALSE;
		}
		else {
		    ParseError();
		    fprintf(textoutput,"Unknown torus-keyword \"%s\"\n",strtmp);
		    RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
		    RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewTorus=(TORUS *)malloc(sizeof(TORUS));
	    if(NewTorus!=NULL) {
		*NewTorus=TmpTorus;
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new torus\n");
		NewTorus=NULL;
	    }
	}

	return(NewTorus);
}


PEAK *InitNewPeak(FILE *parfile, char *strtmp)
{
	PEAK	*NewPeak, TmpPeak;
	int	RetOK=TRUE, EndReached=FALSE;

	NewPeak=NULL;

	TmpPeak.dummy=0.0;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((RetOK==TRUE)&&(EndReached==FALSE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"DUMMY")==OK) {
		    RetOK=getnextdvalue(&TmpPeak.dummy,parfile,mincoord,maxcoord,strtmp);
		}
		else {
		    ParseError();
		    fprintf(textoutput,"Unknown peak-keyword \"%s\"\n",strtmp);
		    RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
		    RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewPeak=(PEAK *)malloc(sizeof(PEAK));
	    if(NewPeak!=NULL) {
		NewPeak->dummy=TmpPeak.dummy;
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new peak\n");
		NewPeak=NULL;
	    }
	}

	return(NewPeak);
}


DEOFLASKA *InitNewDeoflaska(FILE *parfile, char *strtmp)
{
	DEOFLASKA	TmpDeoflaska, *NewDeoflaska;
	int	RetOK=TRUE, EndReached=FALSE;

	TmpDeoflaska.h=5.0;	/* Set default values */
	TmpDeoflaska.r0=1.0;
	TmpDeoflaska.dr=0.5;

	NewDeoflaska=NULL;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((RetOK==TRUE)&&(EndReached==FALSE)&&(IsObjectKW(strtmp)==FALSE)) {
		if(strcmp(strtmp,(char *)"HEIGHT")==OK) {
			RetOK=getnextdvalue(&TmpDeoflaska.h,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"RADIUS")==OK) {
			RetOK=getnextdvalue(&TmpDeoflaska.r0,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"VARIATION")==OK) {
			RetOK=getnextdvalue(&TmpDeoflaska.dr,parfile,mincoord,maxcoord,strtmp);
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown deoflaska-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	if(RetOK==TRUE) {
	    NewDeoflaska=(DEOFLASKA *)malloc(sizeof(DEOFLASKA));
	    if(NewDeoflaska!=NULL) {
		NewDeoflaska->h=TmpDeoflaska.h;
		NewDeoflaska->r0=TmpDeoflaska.r0;
		NewDeoflaska->dr=TmpDeoflaska.dr;
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new deoflaska\n");
		NewDeoflaska=NULL;
	    }
	}

	return(NewDeoflaska);
}


CSG *InitNewCsg(FILE *parfile, char *strtmp)
{
	CSG	*NewCsg, TmpCsg;
	OBJECT	*TmpObject;
	int	Operator, NumOperators;
	int	ContinueCSG=FALSE, RetOK=TRUE;

	NewCsg=NULL;
	NumOperators=0;

	if(getnextkeyword(parfile,strtmp)==TRUE) {		/* Get first keyword */
	    if(strcmp(strtmp,(char *)"PLANE:")==OK)
		TmpCsg.Object1=CreateNewObject(parfile,(short)SHAPE_PLANE);
	    else if(strcmp(strtmp,(char *)"SPHERE:")==OK)
		TmpCsg.Object1=CreateNewObject(parfile,(short)SHAPE_SPHERE);
	    else if(strcmp(strtmp,(char *)"ELLIPSOID:")==OK)
		TmpCsg.Object1=CreateNewObject(parfile,(short)SHAPE_ELLIPSOID);
	    else if(strcmp(strtmp,(char *)"BOX:")==OK)
		TmpCsg.Object1=CreateNewObject(parfile,(short)SHAPE_BOX);
	    else if(strcmp(strtmp,(char *)"CYLINDER:")==OK)
		TmpCsg.Object1=CreateNewObject(parfile,(short)SHAPE_CYLINDER);
	    else if(strcmp(strtmp,(char *)"CONE:")==OK)
		TmpCsg.Object1=CreateNewObject(parfile,(short)SHAPE_CONE);
	    else if(strcmp(strtmp,(char *)"TORUS:")==OK)
		TmpCsg.Object1=CreateNewObject(parfile,(short)SHAPE_TORUS);
	    else if(strcmp(strtmp,(char *)"PEAK:")==OK)
		TmpCsg.Object1=CreateNewObject(parfile,(short)SHAPE_PEAK);
	    else if(strcmp(strtmp,(char *)"CSG:")==OK)
		TmpCsg.Object1=CreateNewObject(parfile,(short)SHAPE_CSG);
	    else if(strcmp(strtmp,(char *)"DISC:")==OK) {
		ParseError();
		fprintf(textoutput,"Can not include discs in CSG's!\n");
		RetOK=FALSE;
	    }
	    else if((strcmp(strtmp,(char *)"TRIANGLE:")==OK)||(strcmp(strtmp,(char *)"TRIANGLELIST:")==OK)) {
		ParseError();
		fprintf(textoutput,"Can not include triangles in CSG's!\n");
		RetOK=FALSE;
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Expected an object description, but found \"%s\" instead\n",strtmp);
		RetOK=FALSE;
	    }
	    if(TmpCsg.Object1==NULL)
		RetOK=FALSE;
	} else RetOK=FALSE;

	if(RetOK==TRUE)
	    RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */

	while((RetOK==TRUE)&&(IsObjectKW(strtmp)==FALSE)) {
	    if(strcmp(strtmp,(char *)"OR")==OK)
		Operator=CSG_OP_OR;
	    else if(strcmp(strtmp,(char *)"AND")==OK)
		Operator=CSG_OP_AND;
	    else if(strcmp(strtmp,(char *)"MINUS")==OK)
		Operator=CSG_OP_MINUS;
	    else if(strcmp(strtmp,(char *)"PLUS")==OK)
		Operator=CSG_OP_PLUS;
	    else {
		ParseError();
		fprintf(textoutput,"Expected a valid CSG operator, but found \"%s\" instead\n",strtmp);
		RetOK=FALSE;
	    }

	    if((RetOK==TRUE)&&(ContinueCSG==TRUE)) {
		if((Operator==TmpCsg.Operator)||((Operator==CSG_OP_MINUS)&&(TmpCsg.Operator==CSG_OP_AND))) {	/* Treat MINUS and AND the same */
		/* Create new object, which contains the previous CSG with its two */
		/* objects. The new object will be the first object in a new CSG.  */
		    TmpObject=NULL;
		    TmpObject=(OBJECT *)malloc(sizeof(OBJECT));
		    if(TmpObject!=NULL) {
			TmpObject->BoundType=BOUND_NONE;
			TmpObject->Bound=NULL;
			TmpObject->ShapeType=SHAPE_CSG;
			TmpObject->Shape=NewCsg;
			TmpObject->Invert=INVERT_FALSE;
			TmpObject->Shadow=CASTSHADOW_TRUE;
			CreateDefTexture(&TmpObject->Texture);
			ClearTransform(&TmpObject->Transform);
			TmpCsg.Object1=TmpObject;
			NewCsg=NULL; TmpObject=TmpCsg.Object2=NULL;	/* (Done using them) */
		    }
		    else {
			ParseError();
			fprintf(textoutput,"Could not allocate memory for next CSG object\n");
			RetOK=FALSE;
		    }
		}
		else {
		    ParseError();
		    fprintf(textoutput,"You must use the same operator for all objects\n");
		    fprintf(textoutput,"in a single CSG section.\n");
		    RetOK=FALSE;
		}
	    }

	    if(RetOK==TRUE) {
		TmpCsg.Operator=Operator; NumOperators++;
		RetOK=getnextkeyword(parfile,strtmp);				/* Get next keyword */
	    }
	    if(RetOK==TRUE) {
		if(strcmp(strtmp,(char *)"PLANE:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_PLANE);
		else if(strcmp(strtmp,(char *)"SPHERE:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_SPHERE);
		else if(strcmp(strtmp,(char *)"ELLIPSOID:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_ELLIPSOID);
		else if(strcmp(strtmp,(char *)"BOX:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_BOX);
		else if(strcmp(strtmp,(char *)"CYLINDER:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_CYLINDER);
		else if(strcmp(strtmp,(char *)"CONE:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_CONE);
		else if(strcmp(strtmp,(char *)"TORUS:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_TORUS);
		else if(strcmp(strtmp,(char *)"PEAK:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_PEAK);
		else if(strcmp(strtmp,(char *)"DEOFLASKA:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_DEOFLASKA);
		else if(strcmp(strtmp,(char *)"CSG:")==OK)
			TmpCsg.Object2=CreateNewObject(parfile,(short)SHAPE_CSG);
		else if(strcmp(strtmp,(char *)"DISC:")==OK) {
			ParseError();
			fprintf(textoutput,"Can not include discs in CSG's!\n");
			RetOK=FALSE;
		}
		else if((strcmp(strtmp,(char *)"TRIANGLE:")==OK)||(strcmp(strtmp,(char *)"TRIANGLELIST:")==OK)) {
			ParseError();
			fprintf(textoutput,"Can not include triangles in CSG's!\n");
			RetOK=FALSE;
		}
		else {
			ParseError();
			fprintf(textoutput,"Expected an object description, but found \"%s\" instead\n",strtmp);
			RetOK=FALSE;
		}
		if(TmpCsg.Object2==NULL)
			RetOK=FALSE;
	    } else RetOK=FALSE;

	    if(RetOK==TRUE) {
		if(TmpCsg.Operator==CSG_OP_MINUS) {
			TmpCsg.Operator=CSG_OP_AND;
			if(TmpCsg.Object2->Invert==INVERT_FALSE) TmpCsg.Object2->Invert=INVERT_TRUE;
			else TmpCsg.Object2->Invert=INVERT_FALSE;
		}
		RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
	    }

	    if(RetOK==TRUE) {
		NewCsg=(CSG *)malloc(sizeof(CSG));
		if(NewCsg!=NULL) {
		    NewCsg->Operator=TmpCsg.Operator;
		    NewCsg->Object1=TmpCsg.Object1;
		    NewCsg->Object2=TmpCsg.Object2;
		}
		else {
		    ParseError();
		    fprintf(textoutput,"Could not allocate memory for new CSG section\n");
		    if(TmpCsg.Object1!=NULL)  FreeObjectMemory(TmpCsg.Object1);
		    if(TmpCsg.Object2!=NULL)  FreeObjectMemory(TmpCsg.Object2);
		    TmpCsg.Object1=TmpCsg.Object2=NULL; NewCsg=NULL;
		    RetOK=FALSE;
		}
	    }
	    ContinueCSG=TRUE;	/* Indicate that two objects are done already */
	}

	if(NumOperators<1) {
	    ParseError();
	    fprintf(textoutput,"Incomplete CSG\n");
	    if(TmpCsg.Object1!=NULL)  FreeObjectMemory(TmpCsg.Object1);
	    if(TmpCsg.Object2!=NULL)  FreeObjectMemory(TmpCsg.Object2);
	    TmpCsg.Object1=TmpCsg.Object2=NULL; NewCsg=NULL;
	}

	return(NewCsg);
}



int InitNewLight(FILE *parfile)
{
	char	strtmp[100];
	LIGHT	TmpLight, *NewLight;
	int	RetOK=TRUE, EndReached=FALSE;

	fprintf(textoutput,".");

	if(NumLights>=maxlights) {
		ParseError();
		fprintf(textoutput,"Maximum amount of lights exceeded!\n");
		RetOK=FALSE;
	}

	else {
	  TmpLight.Location.x=10.0;		/* Set default values */
	  TmpLight.Location.y=-10.0;
	  TmpLight.Location.z=10.0;
	  TmpLight.Color.r=TmpLight.Color.g=TmpLight.Color.b=1.0;
	  TmpLight.SoftRec=0L; TmpLight.Size=0.5; TmpLight.Jitter=0.1;

	  if(getnextkeyword(parfile,strtmp)==TRUE) {		/* Get first keyword */
	    while((EndReached==FALSE)&&(RetOK==TRUE)) {
		if(strcmp(strtmp,(char *)"LOCATION")==OK) {
			RetOK=getnextvector((VECTOR *)&TmpLight.Location,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"COLOR")==OK) {
			RetOK=getnextvector((VECTOR *)&TmpLight.Color,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"SIZE")==OK) {
			RetOK=getnextdvalue(&TmpLight.Size,parfile,0.0,100.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"SOFTNESS")==OK) {
			RetOK=getnextsvalue(&TmpLight.SoftRec,parfile,(short)0,(short)25,strtmp);
		}
		else if(strcmp(strtmp,(char *)"JITTER")==OK) {
			RetOK=getnextdvalue(&TmpLight.Jitter,parfile,0.0,1.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)":END")==OK) {
			EndReached=TRUE;
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown light-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	  }

	  if(RetOK==TRUE) {
	    NewLight=(LIGHT *)malloc(sizeof(LIGHT));
	    if(NewLight!=NULL) {
		CopyPoint(&NewLight->Location,&TmpLight.Location);
		NewLight->Color.r=TmpLight.Color.r;
		NewLight->Color.g=TmpLight.Color.g;
		NewLight->Color.b=TmpLight.Color.b;
		NewLight->SoftRec=TmpLight.SoftRec;
		NewLight->Size=TmpLight.Size;
		NewLight->Jitter=TmpLight.Jitter;
		LightArray[NumLights++]=NewLight;
	    }
	    else {
		ParseError();
		fprintf(textoutput,"Could not allocate memory for new light\n");
		RetOK=FALSE;
	    }
	  }
	}

	return(RetOK);
}



int InitCamera(FILE *parfile)
{
	char	strtmp[100];
	int	RetOK=TRUE, EndReached=FALSE;

	fprintf(textoutput,".");

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((EndReached==FALSE)&&(RetOK==TRUE)) {
		if(strcmp(strtmp,(char *)"LOCATION")==OK) {
			RetOK=getnextvector((VECTOR *)&Camera.Location,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"VIEWPOINT")==OK) {
			RetOK=getnextvector((VECTOR *)&Camera.ViewPoint,parfile,mincoord,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"ASPECT")==OK) {
			RetOK=getnextvector((VECTOR *)&Camera.Aspect,parfile,0.0,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)":END")==OK) {
			EndReached=TRUE;
		}
		else {
			ParseError();
			fprintf(textoutput,"Unknown camera-keyword \"%s\"\n",strtmp);
			RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
			RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}
	if(RetOK==TRUE)
		CreateCamera(&Camera, &Camera.Location, &Camera.ViewPoint, &Camera.Aspect);

	return(RetOK);
}



int InitTexture(TEXTURE *Texture, FILE *parfile)
{
	char	strtmp[100],strtmp2[100];
	int	RetOK=TRUE, EndReached=FALSE;
	short	lastbound,i;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((EndReached==FALSE)&&(RetOK==TRUE)) {
		if(strcmp(strtmp,(char *)"COLOR")==OK) {
		    RetOK=getnextvector((VECTOR *)&Texture->CMap.Colors[0],parfile,0.0,1.0,strtmp);
		    Texture->CMap.Colors[1].r=Texture->CMap.Colors[1].g=Texture->CMap.Colors[1].b=0.0;
		    Texture->CMap.Bounds[0]=0.0;
		    Texture->CMap.Bounds[1]=1.0;
		    Texture->CMap.LastBound=1;
		}
		else if(strcmp(strtmp,(char *)"COLORMAP")==OK) {
		    RetOK=getnextsvalue(&lastbound,parfile,(short)1,(short)10,strtmp);
		    lastbound--;
		    i=0;
		    while((i<=lastbound)&&(RetOK==TRUE)) {
			RetOK=getnextdvalue(&Texture->CMap.Bounds[i],parfile,0.0,1.0,strtmp);
			if(RetOK==TRUE) RetOK=getnextvector((VECTOR *)&Texture->CMap.Colors[i],parfile,0.0,1.0,strtmp);
			i++;
		    }
		    if(RetOK==TRUE) Texture->CMap.LastBound=i;
		}
		else if(strcmp(strtmp,(char *)"PATTERN")==OK) {
		    if(getnextkeyword(parfile,strtmp2)==TRUE) {
			if(strcmp(strtmp2,(char *)"NONE")==OK)
			    Texture->Pattern=PATTERN_NONE;
			else if(strcmp(strtmp2,(char *)"CHECKER")==OK)
			    Texture->Pattern=PATTERN_CHECKER;
			else if(strcmp(strtmp2,(char *)"CIRCLES")==OK)
			    Texture->Pattern=PATTERN_CIRCLES;
			else if(strcmp(strtmp2,(char *)"RINGS")==OK)
			    Texture->Pattern=PATTERN_RINGS;
			else if(strcmp(strtmp2,(char *)"SPOTS")==OK)
			    Texture->Pattern=PATTERN_SPOTS;
			else if(strcmp(strtmp2,(char *)"GRADIENT")==OK)
			    Texture->Pattern=PATTERN_GRADIENT;
			else if(strcmp(strtmp2,(char *)"MARBLE")==OK) {
			    Texture->Pattern=PATTERN_MARBLE;
			    NeedNoise=TRUE;
			}
			else if(strcmp(strtmp2,(char *)"SOFTMARBLE")==OK) {
			    Texture->Pattern=PATTERN_SOFTMARBLE;
			    NeedNoise=TRUE;
			}
			else if(strcmp(strtmp2,(char *)"SQUARES")==OK)
			    Texture->Pattern=PATTERN_SQUARES;
			else if(strcmp(strtmp2,(char *)"BLURB")==OK) {
			    Texture->Pattern=PATTERN_BLURB;
			    NeedNoise=TRUE;
			}
			else if(strcmp(strtmp2,(char *)"MANDEL")==OK)
			    Texture->Pattern=PATTERN_MANDEL;
			else if(strcmp(strtmp2,(char *)"WOOD")==OK)
			    Texture->Pattern=PATTERN_WOOD;
			else if(strcmp(strtmp2,(char *)"ANGULAR")==OK)
			    Texture->Pattern=PATTERN_ANGULAR;
			else {
			    ParseError();
			    fprintf(textoutput,"Unknown pattern \"%s\"\n",strtmp2);
			    RetOK=FALSE;
			}
		    }
		    else EndReached=TRUE;
		}
		else if(strcmp(strtmp,(char *)"TURBULENCE")==OK) {
		    RetOK=getnextdvalue(&Texture->Turbulence,parfile,0.0,50000.0,strtmp);
		    NeedNoise=TRUE;
		}
		else if(strcmp(strtmp,(char *)"REFLECT")==OK) {
		    RetOK=getnextvector((VECTOR *)&Texture->Reflect,parfile,0.0,1.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"FILTER")==OK) {
		    RetOK=getnextvector((VECTOR *)&Texture->Filter,parfile,0.0,1.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"IOR")==OK) {
		    RetOK=getnextdvalue(&Texture->Ior,parfile,1.0,3.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"AMBIENT")==OK) {
		    RetOK=getnextdvalue(&Texture->Ambient,parfile,0.0,1.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"DIFFUSE")==OK) {
		    RetOK=getnextdvalue(&Texture->Diffuse,parfile,0.0,1.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"PHONG")==OK) {
		    RetOK=getnextdvalue(&Texture->Phong,parfile,0.0,1.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"PHONGSIZE")==OK) {
		    RetOK=getnextdvalue(&Texture->PhongSize,parfile,0.0,500.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"IMAGE:")==OK) {
		    Texture->ImageType=InitImage(&Texture->ImageNum,parfile);
		    if(Texture->ImageType==IMG_NONE) RetOK=FALSE;
		}
		else if(strcmp(strtmp,(char *)"DEFAULT")==OK) {
		    CreateDefTexture(Texture);
		}
		else if(strcmp(strtmp,(char *)"TRANSFORM:")==OK) {
		    RetOK=InitTransform(&Texture->Transform,parfile);
		}
		else if(strcmp(strtmp,(char *)":END")==OK) {
		    EndReached=TRUE;
		}
		else {
		    ParseError();
		    fprintf(textoutput,"Unknown texture-keyword \"%s\"\n",strtmp);
		    RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
		    RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	return(RetOK);
}



short InitImage(int *ImageNum, FILE *parfile)
{
	char	strtmp[100],strtmp2[100];
	long	XSize,YSize;
	short	Interpolate,Maptype,ImageType,Tile;
	int	ImgNum, ImgSpecified;
	FILE	*ImgFile;
	int	RetOK=TRUE, EndReached=FALSE;

	XSize=YSize=-1;
	Maptype=MAP_PLANAR;
	Interpolate=INTPOL_NONE;
	Tile=TILE_TRUE;
	ImageType=IMG_NONE;
	ImgSpecified=FALSE;
	*ImageNum=0;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((EndReached==FALSE)&&(RetOK==TRUE)) {
		if(strcmp(strtmp,(char *)"IFF")==OK) {
		    if(getnextstring(parfile,strtmp2)==TRUE) {
			ImgSpecified=TRUE;
			ReadPicHeader(strtmp2,FORMAT_IFF,&ImgFile,&XSize,&YSize);
			if((XSize>0)&&(YSize>0)) {
			    ImageType=ReadImage(ImageNum,FORMAT_IFF,ImgFile,XSize,YSize);
			    if(ImageType==IMG_NONE) RetOK=FALSE;
			    fclose(ImgFile);
			}
			else RetOK=FALSE;
		    }
		    else RetOK=FALSE;
		}
		else if(strcmp(strtmp,(char *)"TGA")==OK) {
		    if(getnextstring(parfile,strtmp2)==TRUE) {
			ImgSpecified=TRUE;
			ReadPicHeader(strtmp2,FORMAT_TGA,&ImgFile,&XSize,&YSize);
			if((XSize>0)&&(YSize>0)) {
			    ImageType=ReadImage(ImageNum,FORMAT_TGA,ImgFile,XSize,YSize);
			    if(ImageType==IMG_NONE) RetOK=FALSE;
			    fclose(ImgFile);
			}
			else RetOK=FALSE;
		    }
		    else RetOK=FALSE;
		}
		else if(strcmp(strtmp,(char *)"PPM")==OK) {
		    if(getnextstring(parfile,strtmp2)==TRUE) {
			ImgSpecified=TRUE;
			ReadPicHeader(strtmp2,FORMAT_PPM,&ImgFile,&XSize,&YSize);
			if((XSize>0)&&(YSize>0)) {
			    ImageType=ReadImage(ImageNum,FORMAT_PPM,ImgFile,XSize,YSize);
			    if(ImageType==IMG_NONE) RetOK=FALSE;
			    fclose(ImgFile);
			}
			else RetOK=FALSE;
		    }
		    else RetOK=FALSE;
		}
		else if(strcmp(strtmp,(char *)"MAPTYPE")==OK) {
		    if(getnextkeyword(parfile,strtmp2)==TRUE) {
			if(strcmp(strtmp2,(char *)"PLANAR")==OK)
			    Maptype=MAP_PLANAR;
			else if(strcmp(strtmp2,(char *)"SPHERICAL")==OK)
			    Maptype=MAP_SPHERICAL;
			else if(strcmp(strtmp2,(char *)"CYLINDRICAL")==OK)
			    Maptype=MAP_CYLINDRICAL;
			else {
			    ParseError();
			    fprintf(textoutput,"Unknown maptype \"%s\"\n",strtmp2);
			    RetOK=FALSE;
			}
		    }
		    else RetOK=FALSE;
		}
		else if(strcmp(strtmp,(char *)"INTERPOLATE")==OK) {
		    Interpolate=INTPOL_LINEAR;
		}
		else if(strcmp(strtmp,(char *)"NOINTERPOLATE")==OK) {
		    Interpolate=INTPOL_NONE;
		}
		else if(strcmp(strtmp,(char *)"TILE")==OK) {
		    Tile=TILE_TRUE;
		}
		else if(strcmp(strtmp,(char *)"NOTILE")==OK) {
		    Tile=TILE_FALSE;
		}
		else if(strcmp(strtmp,(char *)"TRANSFORM:")==OK) {
		    switch(ImageType) {
			case IMG_24BIT:
			    RetOK=InitTransform(&Img24Array[*ImageNum]->Transform,parfile);
			    break;
			case IMG_8BIT:
			    RetOK=InitTransform(&Img8Array[*ImageNum]->Transform,parfile);
			    break;
			case IMG_NONE:
			default:
			    ParseError();
			    fprintf(textoutput,"You must specify an image before the transformation\n");
			    RetOK=FALSE;
			    break;
		    }
		}
		else if(strcmp(strtmp,(char *)":END")==OK) {
		    EndReached=TRUE;
		}
		else {
		    ParseError();
		    fprintf(textoutput,"Unknown image-keyword \"%s\"\n",strtmp);
		    RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
		    RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	ImgNum=*ImageNum;
	if((RetOK==TRUE)&&(XSize>0)&&(XSize>0)&&(ImageType!=IMG_NONE)) {
	    switch(ImageType) {
		case IMG_24BIT:
		    Img24Array[ImgNum]->Interpolate=Interpolate;
		    Img24Array[ImgNum]->Maptype=Maptype;
		    Img24Array[ImgNum]->Tile=Tile;
		    break;
		case IMG_8BIT:
		    Img8Array[ImgNum]->Interpolate=Interpolate;
		    Img8Array[ImgNum]->Maptype=Maptype;
		    Img8Array[ImgNum]->Tile=Tile;
		    break;
		default:
		    ParseError();
		    fprintf(textoutput,"Unknown image type %d (?!)\n",ImageType);
		    RetOK=FALSE;
		    break;
	    }
	}
	else {
	    if(ImageType!=IMG_NONE) {
		switch(ImageType) {
		    case IMG_24BIT:
			free(Img24Array[ImgNum]->Body);
			free(Img24Array[ImgNum]);
			Img24Array[ImgNum]=NULL;
			break;
		    case IMG_8BIT:
			free(Img8Array[ImgNum]->Body);
			free(Img8Array[ImgNum]);
			Img8Array[ImgNum]=NULL;
			break;
		    default:
			break;
		}
	    }
	    else if(ImgSpecified==FALSE) {
		ParseError();
		fprintf(textoutput,"No image specified\n");
	    }
	    RetOK=FALSE;
	}

	if(RetOK==FALSE) {
	    ImageType=IMG_NONE;
	    *ImageNum=0;
	}

	return(ImageType);
}



int InitTransform(TRANSFORM *TmpTransform, FILE *parfile)
{
	char	strtmp[100];
	int	RetOK=TRUE, EndReached=FALSE;
	short	numtransforms;
	double	d;

	numtransforms=TmpTransform->NumTransforms;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((EndReached==FALSE)&&(RetOK==TRUE)) {
		if(numtransforms>=10) {
		    ParseError();
		    fprintf(textoutput,"Maximum amount of transforms exceeded\n");
		    RetOK=FALSE;
		}
		else if(strcmp(strtmp,(char *)"SCALE")==OK) {
		    TmpTransform->Entry[numtransforms].Type=TRANSFORM_SCALE;
		    RetOK=getnextvector((VECTOR *)&TmpTransform->Entry[numtransforms].Values,parfile,mincoord,maxcoord,strtmp);
		    if((RetOK==TRUE)&&((TmpTransform->Entry[numtransforms].Values.x!=1.0)||(TmpTransform->Entry[numtransforms].Values.y!=1.0)||(TmpTransform->Entry[numtransforms].Values.z!=1.0))) {
			if(TmpTransform->Entry[numtransforms].Values.x==0.0) {
			    ParseWarning();
			    fprintf(textoutput,"Scale value is 0, which is not allowed; changed to 1.0\n");
			    TmpTransform->Entry[numtransforms].Values.x=1.0;
			}
			if(TmpTransform->Entry[numtransforms].Values.y==0.0) {
			    ParseWarning();
			    fprintf(textoutput,"Scale value is 0, which is not allowed; changed to 1.0\n");
			    TmpTransform->Entry[numtransforms].Values.y=1.0;
			}
			if(TmpTransform->Entry[numtransforms].Values.z==0.0) {
			    ParseWarning();
			    fprintf(textoutput,"Scale value is 0, which is not allowed; changed to 1.0\n");
			    TmpTransform->Entry[numtransforms].Values.z=1.0;
			}
			numtransforms++;
		    }
		}
		else if(strcmp(strtmp,(char *)"MOVE")==OK) {
		    TmpTransform->Entry[numtransforms].Type=TRANSFORM_MOVE;
		    RetOK=getnextvector((VECTOR *)&TmpTransform->Entry[numtransforms].Values,parfile,mincoord,maxcoord,strtmp);
		    if((RetOK==TRUE)&&((TmpTransform->Entry[numtransforms].Values.x!=0.0)||(TmpTransform->Entry[numtransforms].Values.y!=0.0)||(TmpTransform->Entry[numtransforms].Values.z!=0.0)))
			numtransforms++;
		}
		else if(strcmp(strtmp,(char *)"ROTATE")==OK) {
		    TmpTransform->Entry[numtransforms].Type=TRANSFORM_ROTATE;
		    RetOK=getnextvector((VECTOR *)&TmpTransform->Entry[numtransforms].Values,parfile,mincoord,maxcoord,strtmp);
		    if((RetOK==TRUE)&&((TmpTransform->Entry[numtransforms].Values.x!=0.0)||(TmpTransform->Entry[numtransforms].Values.y!=0.0)||(TmpTransform->Entry[numtransforms].Values.z!=0.0))) {
			ScaleVector(&TmpTransform->Entry[numtransforms].Values,RADPDEG,&TmpTransform->Entry[numtransforms].Values);
			numtransforms++;
		    }
		}
		else if(strcmp(strtmp,(char *)"WHIRL")==OK) {
		    TmpTransform->Entry[numtransforms].Type=TRANSFORM_WHIRL;
		    RetOK=getnextdvalue(&TmpTransform->Entry[numtransforms].Values.x,parfile,mincoord,maxcoord,strtmp);
		    if(RetOK==TRUE) {
			RetOK=getnextdvalue(&TmpTransform->Entry[numtransforms].Values.y,parfile,mincoord,maxcoord,strtmp);
			if((RetOK==TRUE)&&((TmpTransform->Entry[numtransforms].Values.x!=0.0)||(TmpTransform->Entry[numtransforms].Values.y!=0.0)))
			    numtransforms++;
		    }
		}
		else if(strcmp(strtmp,(char *)"TWIST")==OK) {
		    TmpTransform->Entry[numtransforms].Type=TRANSFORM_TWIST;
		    RetOK=getnextdvalue(&d,parfile,mincoord,maxcoord,strtmp);
		    if((RetOK==TRUE)&&(d!=0.0)) {
			TmpTransform->Entry[numtransforms].Values.x=PIM2/d;
			numtransforms++;
		    }
		}
		else if(strcmp(strtmp,(char *)"NONE")==OK) {
		    ClearTransform(TmpTransform);
		}
		else if(strcmp(strtmp,(char *)":END")==OK) {
		    EndReached=TRUE;
		}
		else {
		    ParseError();
		    fprintf(textoutput,"Unknown transform-keyword \"%s\"\n",strtmp);
		    RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
		    RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	    if((RetOK==TRUE)&&(EndReached==TRUE)) {
		TmpTransform->NumTransforms=numtransforms;
	    }
	}

	return(RetOK);
}



int InitGlobals(FILE *parfile)
{
	char	strtmp[100], strtmp2[100];
	int	RetOK=TRUE, EndReached=FALSE;

	if(getnextkeyword(parfile,strtmp)==TRUE) {	/* Get first keyword */
	    while((EndReached==FALSE)&&(RetOK==TRUE)) {
		if(strcmp(strtmp,(char *)"BACKGROUNDCOLOR")==OK) {
		    RetOK=getnextvector((VECTOR *)&BackgroundColor,parfile,0.0,1.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"FOGCOLOR")==OK) {
		    RetOK=getnextvector((VECTOR *)&FogColor,parfile,0.0,1.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"FOGDISTANCE")==OK) {
		    RetOK=getnextdvalue(&FogDistance,parfile,0.0,maxcoord,strtmp);
		}
		else if(strcmp(strtmp,(char *)"RECDEPTH")==OK) {
		    RetOK=getnextlvalue(&RecDepth,parfile,1L,50L,strtmp);
		}
		else if(strcmp(strtmp,(char *)"PICWIDTH")==OK) {
		    RetOK=getnextlvalue(&PicWidth,parfile,1L,5000L,strtmp);
		}
		else if(strcmp(strtmp,(char *)"PICHEIGHT")==OK) {
		    RetOK=getnextlvalue(&PicHeight,parfile,1L,5000L,strtmp);
		}
		else if(strcmp(strtmp,(char *)"ANTIALIASREC")==OK) {
		    RetOK=getnextlvalue(&AntiAliasingRec,parfile,0L,4L,strtmp);
		}
		else if(strcmp(strtmp,(char *)"ANTIALIASTHRESHOLD")==OK) {
		    RetOK=getnextdvalue(&AntiAliasingThreshold,parfile,0.0,3.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"ANTIALIASJITTER")==OK) {
		    RetOK=getnextdvalue(&AntiAliasingJitter,parfile,0.0,1.0,strtmp);
		}
		else if(strcmp(strtmp,(char *)"DISPLAY")==OK) {
		    RetOK=getnextlvalue(&ReqDisplayType,parfile,0L,9999L,strtmp);
		}
		else if(strcmp(strtmp,(char *)"FORMAT")==OK) {
		    if(getnextkeyword(parfile,strtmp2)==TRUE) {
			if(strcmp(strtmp2,(char *)"TGA")==OK)
			    OutputFormat=FORMAT_TGA;
			else if(strcmp(strtmp2,(char *)"IFF")==OK)
			    OutputFormat=FORMAT_IFF;
			else if(strcmp(strtmp2,(char *)"PPM")==OK)
			    OutputFormat=FORMAT_PPM;
			else if(strcmp(strtmp2,(char *)"NONE")==OK)
			    OutputFormat=FORMAT_NONE;
			else {
			    ParseWarning();
			    fprintf(textoutput,"Unknown image format %s, using default format\n",strtmp2);
			    OutputFormat=FORMAT_DEFAULT;
			}
		    }
		    else RetOK=FALSE;
		}
		else if(strcmp(strtmp,(char *)"RENDERMETHOD")==OK) {
		    if(getnextkeyword(parfile,strtmp2)==TRUE) {
			if(strcmp(strtmp2,(char *)"TRACE")==OK)
			    RenderMethod=RENDER_TRACE;
			else if(strcmp(strtmp2,(char *)"QUICKSCAN")==OK)
			    RenderMethod=RENDER_QUICKSCAN;
			else if(strcmp(strtmp2,(char *)"QUICK")==OK)
			    RenderMethod=RENDER_QUICKSCAN;
			else {
			    ParseWarning();
			    fprintf(textoutput,"Unknown rendering method %s, using default (trace)\n",strtmp2);
			    RenderMethod=RENDER_TRACE;
			}
		    }
		    else RetOK=FALSE;
		}
		else if(strcmp(strtmp,(char *)"QUICKSCAN")==OK) {
		    RenderMethod=RENDER_QUICKSCAN;
		}
		else if(strcmp(strtmp,(char *)"TRACE")==OK) {
		    RenderMethod=RENDER_TRACE;
		}
		else if(strcmp(strtmp,(char *)":END")==OK) {
		    EndReached=TRUE;
		}
		else {
		    ParseError();
		    fprintf(textoutput,"Unknown globals keyword \"%s\"\n",strtmp);
		    RetOK=FALSE;
		}
		if((RetOK==TRUE)&&(EndReached==FALSE)) {
		    RetOK=getnextkeyword(parfile,strtmp);	/* Get next keyword */
		}
	    }
	}

	return(RetOK);
}



/************************************************************************
 *
 *	SetupMagic() : Generate a harcoded default scene
 *
 ************************************************************************/

int SetupMagic(void)
{
	OBJECT	*NewObj;
	SPHERE	*NewSphere;
	LIGHT	*NewLight;
	int	RetOK;

	RunMagic=TRUE;
	RetOK=TRUE;

	NumObjects=NumLights=Num24Images=Num8Images=NumPrimitives=0L;

 /* Sphere 1 */

	if((NewObj=(OBJECT *)malloc(sizeof(OBJECT)))!=NULL) {
	    if((NewSphere=(SPHERE *)malloc(sizeof(SPHERE)))!=NULL) {
		CreateVector((VECTOR *)&NewSphere->Centre,-1.7,-1.2,1.0);
		NewSphere->r=0.8;
		NewObj->BoundType=BOUND_NONE;
		NewObj->Bound=NULL;
		NewObj->Shadow=CASTSHADOW_TRUE;
		NewObj->ShapeType=SHAPE_SPHERE;
		NewObj->Shape=NewSphere;
		CreateDefTexture(&NewObj->Texture);
		ClearTransform(&NewObj->Transform);
		NewObj->Texture.Phong=0.7;
		NewObj->Texture.PhongSize=20.0;
		ObjectArray[NumObjects]=NewObj;
		NumObjects++; NumPrimitives++;
	    }
	    else {
		free(NewObj);
		fprintf(textoutput,"Error (magic): Could not allocate memory for sphere 1\n");
		RetOK=FALSE;
	    }
	}
	else {
	    fprintf(textoutput,"Error (magic): Could not allocate memory for object 1\n");
	    RetOK=FALSE;
	}

 /* Sphere 2 */

	if((NewObj=(OBJECT *)malloc(sizeof(OBJECT)))!=NULL) {
	    if((NewSphere=(SPHERE *)malloc(sizeof(SPHERE)))!=NULL) {
		CreateVector((VECTOR *)&NewSphere->Centre,0.0,1.0,0.0);
		NewSphere->r=1.5;
		NewObj->BoundType=BOUND_NONE;
		NewObj->Bound=NULL;
		NewObj->Invert=INVERT_FALSE; NewObj->Shadow=CASTSHADOW_TRUE;
		NewObj->ShapeType=SHAPE_SPHERE;
		NewObj->Shape=NewSphere;
		CreateDefTexture(&NewObj->Texture);
		 CreateVector((VECTOR *)&NewObj->Texture.CMap.Colors[0],0.2,0.2,1.0);
		 CreateVector((VECTOR *)&NewObj->Texture.Reflect,0.8,0.8,0.8);
		 NewObj->Texture.Pattern=PATTERN_NONE;
		 NewObj->Texture.Phong=0.7;
		 NewObj->Texture.PhongSize=20.0;
		ClearTransform(&NewObj->Transform);
		ObjectArray[NumObjects]=NewObj;
		NumObjects++; NumPrimitives++;
	    }
	    else {
		free(NewObj);
		fprintf(textoutput,"Error (magic): Could not allocate memory for sphere 2\n");
		RetOK=FALSE;
	    }
	}
	else {
	    fprintf(textoutput,"Error (magic): Could not allocate memory for new object 2\n");
	    RetOK=FALSE;
	}

 /* Light 1 */

	if((NewLight=(LIGHT *)malloc(sizeof(LIGHT)))!=NULL) {
	    NewLight->Color.r=1.0; NewLight->Color.g=1.0; NewLight->Color.b=1.0;
	    CreateVector((VECTOR *)&NewLight->Location,-8.0,-20.0,8.0);
	    NewLight->SoftRec=0; NewLight->Jitter=0.3; NewLight->Size=0.5;
	    LightArray[NumLights]=NewLight;
	    NumLights++;
	}
	else {
	    FreeObjectMemory(NewObj);
	    fprintf(textoutput,"Error (magic): Could not allocate memory for new light\n");
	    RetOK=FALSE;
	}

	if(RetOK==TRUE)
	    fprintf(textoutput,"  *** Magic Default Scene ***\n\n");

	return(RetOK);
}



/*****************************************************************
 *
 *      Miscellanous subroutines
 *
 *****************************************************************/


void ParseError(void)
{
	fprintf(textoutput,"\nError, line %ld: ",LineCntr);
}

void ParseWarning(void)
{
	fprintf(textoutput,"\nWarning, line %ld: ",LineCntr);
}


void ChangeAllCSGTextureTransforms(OBJECT *Obj1, OBJECT *Obj2, TRANSFORM *Trans)
{
	CSG	*TmpCsg;

	if(Obj1->ShapeType == SHAPE_CSG) {
	    TmpCsg = (CSG *)Obj1->Shape;
	    ChangeAllCSGTextureTransforms(TmpCsg->Object1, TmpCsg->Object2, Trans);
	}
	else {
	    AddTransform(&Obj1->Texture.Transform, Trans);
	}

	if(Obj2->ShapeType == SHAPE_CSG) {
	    TmpCsg = (CSG *)Obj2->Shape;
	    ChangeAllCSGTextureTransforms(TmpCsg->Object1, TmpCsg->Object2, Trans);
	}
	else {
	    AddTransform(&Obj2->Texture.Transform, Trans);
	}
}


void ChangeAllCSGNoshadow(OBJECT *Obj1, OBJECT *Obj2)
{
	CSG	*TmpCsg;

	if(Obj1->ShapeType == SHAPE_CSG) {
	    TmpCsg = (CSG *)Obj1->Shape;
	    ChangeAllCSGNoshadow(TmpCsg->Object1, TmpCsg->Object2);
	}
	Obj1->Shadow=CASTSHADOW_FALSE;

	if(Obj2->ShapeType == SHAPE_CSG) {
	    TmpCsg = (CSG *)Obj2->Shape;
	    ChangeAllCSGNoshadow(TmpCsg->Object1, TmpCsg->Object2);
	}
	Obj2->Shadow=CASTSHADOW_FALSE;
}


void ChangeAllCSGShadow(OBJECT *Obj1, OBJECT *Obj2)
{
	CSG	*TmpCsg;

	if(Obj1->ShapeType == SHAPE_CSG) {
	    TmpCsg = (CSG *)Obj1->Shape;
	    ChangeAllCSGShadow(TmpCsg->Object1, TmpCsg->Object2);
	}
	Obj1->Shadow=CASTSHADOW_TRUE;

	if(Obj2->ShapeType == SHAPE_CSG) {
	    TmpCsg = (CSG *)Obj2->Shape;
	    ChangeAllCSGShadow(TmpCsg->Object1, TmpCsg->Object2);
	}
	Obj2->Shadow=CASTSHADOW_TRUE;
}


/*
 *                          ----------
 *                         *-- NOTE --*
 *                          ----------
 *
 * These routines may have to be changed if your system uses a
 * non-ascii standard. Sorry about that, but I guess most systems
 * ARE ascii today...
 *
 */


int uppercase(int a)
{
	if((a>=(int)'a')&&(a<=(int)'z')) a=a-(int)'a'+(int)'A'	; /* Make uppercase */
	return(a);
}

int ischar(int a)
{
	int	b;

	b=FALSE;
	if((a>=33)&&(a<=126)) b=TRUE;		/* chr(a) = "!..~" (all chars) */
	return(b);
}

int isletter(int *a)
{
	int	b;

	b=FALSE;
	*a=uppercase(*a);
	if(((*a>=(int)'A')&&(*a<=(int)'Z'))||(*a==(int)':')) b=TRUE;	/* chr(a) = "A..Z" or ":" */

	return(b);
}

/* int isnumber(int a)
{
	int	b;

	b=FALSE;
	if(((a>=(int)'0')&&(a<=(int)'9'))||(a==(int)'-')||(a==(int)'.')) b=TRUE;
	return(b);
} */

int isnumber(int a)
{
	int	b;

	b=FALSE;
	if(((a>=(int)'0')&&(a<=(int)'9'))||(a==(int)'.')) b=TRUE;	/* chr(a) = "0..9" or "." */
	return(b);
}

int iscomment(int a)
{
	int	b;

	b=FALSE;
	if((a==(int)'#')||(a==(int)';')||(a==(int)'*')) b=TRUE;
	return(b);
}


int isnewline(int a)
{
	int	b;

	b=FALSE;
	if(a==(int)10) b=TRUE;				/*  Unix/Amiga only uses 0x0A at end of line, IBM-PC uses both 0x0A and 0x0D */
  /*	if((a==(int)10)||(a==(int)13)) b=TRUE; */	/*  I know of no system that use only 0x0D, so <- this line is unnecessary   */
	return(b);					/*  (Note: I need to keep track of _one_ EOL char to be able to count lines) */
}


int getnextstring(FILE *f, char *string)
{
	int	itmp,i;

	i=0;
	string[0]=(char) 0;
	itmp=fgetc(f);
	while(((itmp!=EOF)&&(ischar(itmp)==FALSE))||(iscomment(itmp)==TRUE)) {
		if(iscomment(itmp)==TRUE) {
		    while((itmp!=EOF)&&(isnewline(itmp)==FALSE)) {
			itmp=fgetc(f);
		    }
		    NewLineCntr++;
		}
		else if(isnewline(itmp)==TRUE)  NewLineCntr++;
		itmp=fgetc(f);
	}
	LineCntr=NewLineCntr;
	while((itmp!=EOF)&&(ischar(itmp)==TRUE)&(i<99)) {
		string[i]=(char)itmp;
		itmp=fgetc(f);
		i++;
	}
	if(itmp==EOF) return(FALSE);
	else {
		if(isnewline(itmp)==TRUE)  NewLineCntr++;
		string[i]=(char)0;
		return(TRUE);
	}
}


int getnextkeyword(FILE *f, char *keyword)
{
	int	itmp,i;

	i=0;
	keyword[0]=(char) 0;
	itmp=fgetc(f);
	while(((itmp!=EOF)&&(ischar(itmp)==FALSE))||(iscomment(itmp)==TRUE)) {
		if(iscomment(itmp)==TRUE) {
		    while((itmp!=EOF)&&(isnewline(itmp)==FALSE)) {
			itmp=fgetc(f);
		    }
		    NewLineCntr++;
		}
		else if(isnewline(itmp)==TRUE)  NewLineCntr++;
		itmp=fgetc(f);
	}
	LineCntr=NewLineCntr;
	while((itmp!=EOF)&&(ischar(itmp)==TRUE)&(i<99)) {
		keyword[i]=(char)uppercase(itmp);
		itmp=fgetc(f);
		i++;
	}
	if(itmp==EOF) { return(FALSE); }
	else {
		if(isnewline(itmp)==TRUE)  NewLineCntr++;
		keyword[i]=(char)0;
		return(TRUE);
	}
}


int getnextdvalue(double *dvalue, FILE *f, double min, double max, char *string)
{
	int	RetOK;
	double	d;

	if(getnextnumber(f,&d)==TRUE) {
		set_dvalue(dvalue,d,min,max,string);
		RetOK=TRUE;
	}
	else RetOK=FALSE;

	return(RetOK);
}


int getnextlvalue(long *ivalue, FILE *f, long min, long max, char *string)
{
	int	RetOK;
	double	d;

	if(getnextnumber(f,&d)==TRUE) {
		set_lvalue(ivalue,(long)floor(d),min,max,string);
		RetOK=TRUE;
	}
	else RetOK=FALSE;

	return(RetOK);
}


int getnextsvalue(short *ivalue, FILE *f, short min, short max, char *string)
{
	int	RetOK;
	double	d;

	if(getnextnumber(f,&d)==TRUE) {
		set_svalue(ivalue,(short)floor(d),min,max,string);
		RetOK=TRUE;
	}
	else RetOK=FALSE;

	return(RetOK);
}


int getnextvector(VECTOR *v, FILE *f, double min, double max, char *string)
{
	int	RetOK;
	double	d;

	RetOK=FALSE;
	if(getnextnumber(f,&d)==TRUE) {
	    set_dvalue(&v->x,d,min,max,string);
	    if(getnextnumber(f,&d)==TRUE) {
		set_dvalue(&v->y,d,min,max,string);
		if(getnextnumber(f,&d)==TRUE) {
		    set_dvalue(&v->z,d,min,max,string);
		    RetOK=TRUE;
		}
	    }
	}

	return(RetOK);
}

int getnextnumber(FILE *f, double *number)
{
	int	itmp,i;
	char	string[100];

	i=0;
	string[0]=(char)"0"; string[1]=(char)0;
	itmp=fgetc(f);
	while(((itmp!=EOF)&&(ischar(itmp)==FALSE))||(iscomment(itmp)==TRUE)) {
		if(iscomment(itmp)==TRUE) {
		    while((itmp!=EOF)&&(isnewline(itmp)==FALSE)) {
			itmp=fgetc(f);
		    }
		    NewLineCntr++;
		}
		else if(isnewline(itmp)==TRUE)  NewLineCntr++;
		itmp=fgetc(f);
	}
	LineCntr=NewLineCntr;
	while((itmp!=EOF)&&(ischar(itmp)==TRUE)&&(i<99)) {
		string[i]=(char)itmp;
		itmp=fgetc(f);
		i++;
	}
	if(itmp==EOF) return(FALSE);
	else {
		if(isnewline(itmp)==TRUE)  NewLineCntr++;
		string[i]=(char)0;
		return(evaulateexpression(number,string));
	}
}


double extractnumber(char *string, int *counter)
{
	char	number[30];
	int	stringlen, i;

	number[0]=(char)'0'; number[1]=(char)0;
	stringlen=strlen(string);
	i=0;
	while((isnumber(string[i])==TRUE)&&(i<stringlen)) {
		number[i]=string[i];
		i++;
	}
	number[i]=(char)0;
	*counter+=i;
	return(atof(number));
}


int evaulateexpression(double *ans, char *expression)
{
	char	subexpression[100];
	int	explen,i,Ok;
	double	num;

	Ok=TRUE;
	subexpression[0]=(char)0;
	explen=strlen(expression);
	i=0; num=0.0;
	while((i<explen)&&(Ok==TRUE)) {
		if(isnumber((int)expression[i])==TRUE) {
		    num+=extractnumber(&expression[i],&i);
		}
		else if(expression[i]==(char)'+') {
		    i++;
		    num+=extractnumber(&expression[i],&i);
		}
		else if(expression[i]==(char)'-') {
		    i++;
		    num-=extractnumber(&expression[i],&i);
		}
		else {
		    Ok=FALSE;
		}
	}
	if(Ok==TRUE) {
		*ans=num;
	/*	fprintf(textoutput,"\nEvaluated expression: %s=%f\n",expression,*ans); */
	}
	else {
		ParseError();
		fprintf(textoutput,"Invalid expression \"%s\"\n",expression);
		*ans=0.0;
	}
	return(Ok);
}



void set_lvalue(long *variable, long value, long min, long max, char *string)
{
	if((value>=min)&&(value<=max)) *variable=value;
	else {
		ParseWarning();
		fprintf(textoutput,"Invalid value for %s: %ld\n",string,value);
		if(value>max) *variable=max;
		if(value<min) *variable=min;
		fprintf(textoutput,"Set to: %ld\n",*variable);
	}
}


void set_svalue(short *variable, short value, short min, short max, char *string)
{
	if((value>=min)&&(value<=max)) *variable=value;
	else {
		ParseWarning();
		fprintf(textoutput,"Invalid value for %s: %d\n",string,value);
		if(value>max) *variable=max;
		if(value<min) *variable=min;
		fprintf(textoutput,"Set to: %d\n",*variable);
	}
}


void set_dvalue(double *variable, double value, double min, double max, char *string)
{
	if((value>=min)&&(value<=max)) *variable=value;
	else {
		ParseWarning();
		fprintf(textoutput,"Invalid value for %s: %f\n",string,value);
		if(value>max) *variable=max;
		if(value<min) *variable=min;
		fprintf(textoutput,"Set to: %f\n",*variable);
	}
}


/*     Here are some routines for handling the keyword table       */

int GetKeywordTableIndex(char *keyword)
{
	int	i, j;

	i=-1; j=0;
	while((j<=KW_END)&&(i<0)) {
	    if(strcmp(keyword,Keywords[j])==OK)  i=j;
	    j++;
	}

	return(i);
}


int IsRayLabKW(char *keyword)
{
	if(GetKeywordTableIndex(keyword)>=0)  return(TRUE);
	else  return(FALSE);
}


int IsTopLevelKW(char *keyword)
{
	int	index;

	index=GetKeywordTableIndex(keyword);
	if((index>=KW_TOPLEVEL)&&(index<KW_OBJECT))  return(TRUE);
	else  return(FALSE);

}

int IsObjectKW(char *keyword)
{
	int	index;

	index=GetKeywordTableIndex(keyword);
	if((index>=KW_OBJECT)&&(index<KW_PLANE))  return(TRUE);
	else  return(FALSE);
}

int IsCsgOperator(char *keyword)
{
	int	index;

	index=GetKeywordTableIndex(keyword);
	if((index>=(KW_CSG+6))&&(index<(KW_CSG+10)))  return(TRUE);
	else  return(FALSE);
}

