/*
	name:	raylab.c

	Main program
	------------

*/


#include  <stdio.h>
#include  <stdlib.h>
#include  <time.h>

#include  "defs.h"
#include  "getworld.h"
#include  "tga.h"



/* Global variables (most mentioned in extern.h) */

	long	PicWidth, PicHeight, RecDepth, NumObjects, NumLights;
	CAMERA	Camera;
	COLOR	BackgroundColor;
	long	AntiAliasingRec;
	double	AntiAliasingThreshold, AntiAliasingJitter;
	long	ReqDisplayType, GotDisplayType;

	OBJECT	*ObjectArray[maxobjects];
	LIGHT	*LightArray[maxlights];

	unsigned char	pixlearray[parraysize];

	long	picturerendering;

	long	PlaneIntersectionAttempts,PlaneIntersections;
	long	SphereIntersectionAttempts,SphereIntersections;
	long	EllipsoidIntersectionAttempts,EllipsoidIntersections;
	long	TriangleIntersectionAttempts,TriangleIntersections;
	long	BoxIntersectionAttempts,BoxIntersections;
	long	DiscIntersectionAttempts,DiscIntersections;
	long	CylinderIntersectionAttempts,CylinderIntersections;
	long	ShadowAttempts,ShadowHits,ReflectedRays;

	FILE	*inputfile,*outputfile;

	long	outfileopen,objectalloc,aalinbufalloc,displayopen;


/* Declaration of entries in the anti-aliasing table(s) */

typedef struct AAEntry_Struct AAENTRY;
struct AAEntry_Struct {
	COLOR	Color;
	unsigned char	Done;
};

	AAENTRY	aatable[aamaxlevel][aamaxlevel]; /* 9x9 matrix <=> max rec.-level: 4 */
	AAENTRY *aalinebuffer;			 /* Line-buffer for major speedup during antialiasing */

/**********************************************************************
 *
 *	main()
 *
 **********************************************************************/


void main(int argc, char *argv[])
{
	time_t	starttrace, endtrace;
	int	spenttime, spent_h, spent_m, spent_s;


	outfileopen=objectalloc=aalinbufalloc=displayopen=picturerendering=0;

	fprintf(stderr,"RayLab v1.0 (c)1995-1996 by Marcus Geelnard\n");
	fprintf(stderr,"-------------------------------------------\n");

	if(argc<3) {
		fprintf(stderr,"\n*** Not enough parameters!\n");
		fprintf(stderr,"Usage: %s <input file> <output file>\n",argv[0]);
		exit(0);
	}

	if(CreateWorld(argv[1])!=0) {		/* Do some world-declarations */
		exit(1);
	}
	objectalloc=1;

	if((outputfile=fopen(argv[2],"wb"))==NULL) {
		fprintf(stderr,"\n*** Could not open output file!\n");
		cleanup();
		exit(1);
	}
	else outfileopen=1;

	if(AntiAliasingRec>=1L) {
	    if((aalinebuffer=(AAENTRY *)calloc((size_t)(PicWidth*aamaxlevel),sizeof(AAENTRY)))==NULL) {
		fprintf(stderr,"\n*** Could not allocate memory for antialiasing line buffer!\n");
		cleanup();
		exit(1);
	    }
	    else {
		fprintf(stderr,"(Allocated %ld KB of memory for antialiasing line buffer)\n\n",(long)(PicWidth*aamaxlevel*(long)sizeof(AAENTRY))/(long)1024);
		aalinbufalloc=1;
	    }
	}

	fprintf(stderr,"Image size:  %ldx%ld\n",PicWidth,PicHeight);
	WriteTgaHeader(outputfile,PicWidth,PicHeight);

	fprintf(stderr,"\nLights:     %ld\n",NumLights);
	fprintf(stderr,"Primitives: %ld\n\n",NumObjects);

	PlaneIntersectionAttempts = PlaneIntersections = 0;
	SphereIntersectionAttempts = SphereIntersections = 0;
	EllipsoidIntersectionAttempts = EllipsoidIntersections = 0;
	DiscIntersectionAttempts = DiscIntersections = 0;
	CylinderIntersectionAttempts = CylinderIntersections = 0;
	ShadowAttempts=ShadowHits = 0;
	ReflectedRays = 0;

	GotDisplayType = OpenDisplay(ReqDisplayType);	/* Open potential display */
	displayopen=1;

	time(&starttrace);

	picturerendering=1;

	ScanWorld();				/* Make the actual ray-tracing */

	picturerendering=0;

	time(&endtrace);

	spenttime=(int)difftime(endtrace,starttrace);
	spent_h=spenttime/(60*60);
	spent_m=(spenttime%(60*60))/60;
	spent_s=spenttime%60;

	fprintf(stderr,"\nDone!\n\n");
	fprintf(stderr,"Shape              Tests        Hits\n");
	fprintf(stderr,"-----------------------------------------------\n");
	if(PlaneIntersectionAttempts>0)
		fprintf(stderr,"Plane         %10ld  %10ld  (%3.2lf%%)\n",PlaneIntersectionAttempts,PlaneIntersections,((double)PlaneIntersections/(double)PlaneIntersectionAttempts)*100.0);
	if(SphereIntersectionAttempts>0)
		fprintf(stderr,"Sphere        %10ld  %10ld  (%3.2lf%%)\n",SphereIntersectionAttempts,SphereIntersections,((double)SphereIntersections/(double)SphereIntersectionAttempts)*100.0);
	if(EllipsoidIntersectionAttempts>0)
		fprintf(stderr,"Ellipsoid     %10ld  %10ld  (%3.2lf%%)\n",EllipsoidIntersectionAttempts,EllipsoidIntersections,((double)EllipsoidIntersections/(double)EllipsoidIntersectionAttempts)*100.0);
	if(TriangleIntersectionAttempts>0)
		fprintf(stderr,"Triangle      %10ld  %10ld  (%3.2lf%%)\n",TriangleIntersectionAttempts,TriangleIntersections,((double)TriangleIntersections/(double)TriangleIntersectionAttempts)*100.0);
	if(BoxIntersectionAttempts>0)
		fprintf(stderr,"Box           %10ld  %10ld  (%3.2lf%%)\n",BoxIntersectionAttempts,BoxIntersections,((double)BoxIntersections/(double)BoxIntersectionAttempts)*100.0);
	if(DiscIntersectionAttempts>0)
		fprintf(stderr,"Disc          %10ld  %10ld  (%3.2lf%%)\n",DiscIntersectionAttempts,DiscIntersections,((double)DiscIntersections/(double)DiscIntersectionAttempts)*100.0);
	if(CylinderIntersectionAttempts>0)
		fprintf(stderr,"Cylinder      %10ld  %10ld  (%3.2lf%%)\n",CylinderIntersectionAttempts,CylinderIntersections,((double)CylinderIntersections/(double)CylinderIntersectionAttempts)*100.0);
	fprintf(stderr,"\nShadow rays:  tested: %ld   blocked: %ld  (%2.2lf%%)\n",ShadowAttempts,ShadowHits,100.0*(double)ShadowHits/(double)ShadowAttempts);
	fprintf(stderr,"Reflected rays:  %ld\n",ReflectedRays);
	fprintf(stderr,"\nTime: %dh %dmin %ds\n\n",spent_h,spent_m,spent_s);


/*	if(AntiAliasingRec>=1L) {
		free(aalinebuffer);
	}

	FreeAllObjectMemory();

	fclose(outputfile);
 */

	cleanup();

	exit(0);
}



/**********************************************************************
 *
 *	cleanup() cleans up the system, closes down things etc...
 *
 **********************************************************************/

void cleanup(void) {
	if(outfileopen==1) {
		fclose(outputfile);
		outfileopen=0;
	}
	if(aalinbufalloc==1) {
		free(aalinebuffer);
		aalinbufalloc=0;
	}
	if(displayopen==1) {
		CloseDisplay();		/* Close display (if any was opened) */
		displayopen=0;
	}
	if(objectalloc==1) {
		FreeAllObjectMemory();	/* Free memory that was allocated for objects */
		objectalloc=0;
	}
}


/**********************************************************************
 *
 *	ScanWorld() is the main tracing routine
 *
 **********************************************************************/

void ScanWorld(void)
{
	long	column,line;
	COLOR	ReturnColor;
	LINE	ScanLine;
	POINT	CameraLocation,ScreenLocation,OldScreenLocation;
	VECTOR	vtemp,vxstep,vystep;
	int	i,j;
	unsigned char	RGBColor[3];
	AAENTRY	*currentlinebufentry,*currentlinebufentry_tmp;

	CreatePoint(&CameraLocation,Camera.Location.x,Camera.Location.y,Camera.Location.z);
	CopyPoint(&ScreenLocation,&CameraLocation);
	ScaleVector(&vtemp,Camera.Aspect.z,&(Camera.Direction));
	ScreenLocation.x+=vtemp.x; ScreenLocation.y+=vtemp.y; ScreenLocation.z+=vtemp.z;
	ScaleVector(&vtemp,(Camera.Aspect.y)/(double)2,&(Camera.Up));
	ScreenLocation.x+=vtemp.x; ScreenLocation.y+=vtemp.y; ScreenLocation.z+=vtemp.z;
	ScaleVector(&vtemp,(Camera.Aspect.x)/(double)2,&(Camera.Right));
	ScreenLocation.x-=vtemp.x; ScreenLocation.y-=vtemp.y; ScreenLocation.z-=vtemp.z;

	ScaleVector(&vxstep,(Camera.Aspect.x)/(double)PicWidth,&(Camera.Right));
	ScaleVector(&vystep,-(Camera.Aspect.y)/(double)PicHeight,&(Camera.Up));

	if(AntiAliasingRec>=1L) {
	    currentlinebufentry=aalinebuffer;
	    for(column=0;column<PicWidth;column++) {	/* Clear antialiasing line-buffer */
		for(i=0;i<aamaxlevel;i++) {
		    currentlinebufentry->Done=0;
		    currentlinebufentry++;
		}
	    }
	}

	for(line=1;line<=PicHeight;line++) {
	    if(AntiAliasingRec>=1L) {
		for(i=0;i<aamaxlevel;i++) {
		    for(j=0;j<aamaxlevel;j++) {
			aatable[i][j].Done=0;	/* Clear whole aatable */
		    }
		}
		currentlinebufentry=aalinebuffer;
	    }
	    CopyPoint(&OldScreenLocation,&ScreenLocation);
	    fprintf(stderr,"\rRendering line %ld of %ld (%3.1lf%%)",line,PicHeight,100.0*(double)line/(double)PicHeight);

	    for(column=1;column<=PicWidth;column++) {
		if(AntiAliasingRec<1L) {
			MakeLine(&ScanLine,&CameraLocation,&ScreenLocation);
			(void) TraceRay(&ReturnColor,&ScanLine,RecDepth);
		}
		else {
		    for(j=0;j<aamaxlevel;j++) {				/* Copy right column of last scan  to left column of new scan! */
			if((aatable[0][j].Done=aatable[aamaxlevel-1][j].Done)!=0)
			    CopyColor(&aatable[0][j].Color,&aatable[aamaxlevel-1][j].Color);
		    }
		    currentlinebufentry_tmp=currentlinebufentry;
		    for(i=0;i<aamaxlevel;i++) {				/* Copy top row from previous line */
			if((aatable[i][0].Done=currentlinebufentry->Done)!=0)
			    CopyColor(&aatable[i][0].Color,&currentlinebufentry->Color);
			currentlinebufentry++;
		    }
		    for(i=1;i<aamaxlevel;i++) {
		    	for(j=1;j<aamaxlevel;j++) {
			    aatable[i][j].Done=0;			/* Clear rest of aatable */
			}
		    }
		    ScanOnePoint(&ReturnColor,&CameraLocation,&ScreenLocation,&vxstep,&vystep,0,aamaxlevel-1,0,aamaxlevel-1,AntiAliasingRec);
		    currentlinebufentry=currentlinebufentry_tmp;
		    for(i=0;i<aamaxlevel;i++) {				/* Save bottom row for next line */
			if((currentlinebufentry->Done=aatable[i][aamaxlevel-1].Done)!=0)
			    CopyColor(&currentlinebufentry->Color,&aatable[i][aamaxlevel-1].Color);
			currentlinebufentry++;
		    }
		}

		RGBColor[0] = (unsigned char)(255*ReturnColor.r);
		RGBColor[1] = (unsigned char)(255*ReturnColor.g);
		RGBColor[2] = (unsigned char)(255*ReturnColor.b);

		pixlearray[(column-1)*3+0] = RGBColor[0];
		pixlearray[(column-1)*3+1] = RGBColor[1];
		pixlearray[(column-1)*3+2] = RGBColor[2];

		if(GotDisplayType!=0L)
			DisplayPlot((int)(column-1),(int)(line-1), RGBColor);

		ScreenLocation.x+=vxstep.x;
		ScreenLocation.y+=vxstep.y;
		ScreenLocation.z+=vxstep.z;
	    }
	    WriteTgaLine(outputfile,PicWidth);
	    CopyPoint(&ScreenLocation,&OldScreenLocation);
	    ScreenLocation.x+=vystep.x;
	    ScreenLocation.y+=vystep.y;
	    ScreenLocation.z+=vystep.z;
	}
}

void ScanOnePoint(COLOR *Color, POINT *CameraLocation, POINT *ScreenLocation, VECTOR *dx, VECTOR *dy, int ColumnFirst, int ColumnLast, int RowFirst, int RowLast, long AARec)
{
	LINE	ScanLine;
	POINT	ScreenLocation2;
	VECTOR	dx2,dy2;
	COLOR	ReColor[4],MinColor,MaxColor;
	double	ColorDiff;
	int	i, DeltaColumn, DeltaRow;

	if(aatable[ColumnFirst][RowFirst].Done==0) {
		MakeLine(&ScanLine,CameraLocation,ScreenLocation);
		(void) TraceRay(&ReColor[0],&ScanLine,RecDepth);
		aatable[ColumnFirst][RowFirst].Done=1;
		CopyColor(&aatable[ColumnFirst][RowFirst].Color,&ReColor[0]);
	}
	else {
		CopyColor(&ReColor[0],&aatable[ColumnFirst][RowFirst].Color);
	}

	AddVector((VECTOR *)&ScreenLocation2,(VECTOR *)ScreenLocation,dx);
	if(aatable[ColumnLast][RowFirst].Done==0) {
		MakeLine(&ScanLine,CameraLocation,&ScreenLocation2);
		(void) TraceRay(&ReColor[1],&ScanLine,RecDepth);
		aatable[ColumnLast][RowFirst].Done=1;
		CopyColor(&aatable[ColumnLast][RowFirst].Color,&ReColor[1]);
	}
	else {
		CopyColor(&ReColor[1],&aatable[ColumnLast][RowFirst].Color);
	}

	AddVector((VECTOR *)&ScreenLocation2,(VECTOR *)&ScreenLocation2,dy);
	if(aatable[ColumnLast][RowLast].Done==0) {
		MakeLine(&ScanLine,CameraLocation,&ScreenLocation2);
		(void) TraceRay(&ReColor[2],&ScanLine,RecDepth);
		aatable[ColumnLast][RowLast].Done=1;
		CopyColor(&aatable[ColumnLast][RowLast].Color,&ReColor[2]);
	}
	else {
		CopyColor(&ReColor[2],&aatable[ColumnLast][RowLast].Color);
	}

	SubVector((VECTOR *)&ScreenLocation2,(VECTOR *)&ScreenLocation2,dx);
	if(aatable[ColumnFirst][RowLast].Done==0) {
		MakeLine(&ScanLine,CameraLocation,&ScreenLocation2);
		(void) TraceRay(&ReColor[3],&ScanLine,RecDepth);
		aatable[ColumnFirst][RowLast].Done=1;
		CopyColor(&aatable[ColumnFirst][RowLast].Color,&ReColor[3]);
	}
	else {
		CopyColor(&ReColor[3],&aatable[ColumnFirst][RowLast].Color);
	}

	if(AARec>1L) {
	    CopyColor(&MinColor,&ReColor[0]);	/* Determine min/max values for r,g,b */
	    CopyColor(&MaxColor,&ReColor[0]);
	    for(i=1;i<4;i++) {
		if(ReColor[i].r<MinColor.r) MinColor.r=ReColor[i].r;
		else if(ReColor[i].r>MaxColor.r) MaxColor.r=ReColor[i].r;
		if(ReColor[i].g<MinColor.g) MinColor.g=ReColor[i].g;
		else if(ReColor[i].g>MaxColor.g) MaxColor.g=ReColor[i].g;
		if(ReColor[i].b<MinColor.b) MinColor.b=ReColor[i].b;
		else if(ReColor[i].b>MaxColor.b) MaxColor.b=ReColor[i].b;
	    }

	    ColorDiff=MaxColor.r-MinColor.r+MaxColor.g-MinColor.g+MaxColor.b-MinColor.b;

	    if(ColorDiff>AntiAliasingThreshold) {
		ScaleVector(&dx2,0.5,dx);
		ScaleVector(&dy2,0.5,dy);
		DeltaColumn=(ColumnLast-ColumnFirst)>>1;
		DeltaRow=(RowLast-RowFirst)>>1;

		ScanOnePoint(&ReColor[0],CameraLocation,ScreenLocation,&dx2,&dy2,ColumnFirst,ColumnFirst+DeltaColumn,RowFirst,RowFirst+DeltaRow,AARec-1);

		AddVector((VECTOR *)&ScreenLocation2,(VECTOR *)ScreenLocation,&dx2);
		ScanOnePoint(&ReColor[1],CameraLocation,&ScreenLocation2,&dx2,&dy2,ColumnFirst+DeltaColumn,ColumnLast,RowFirst,RowFirst+DeltaRow,AARec-1);

		AddVector((VECTOR *)&ScreenLocation2,(VECTOR *)&ScreenLocation2,&dy2);
		ScanOnePoint(&ReColor[2],CameraLocation,&ScreenLocation2,&dx2,&dy2,ColumnFirst+DeltaColumn,ColumnLast,RowFirst+DeltaRow,RowLast,AARec-1);

		SubVector((VECTOR *)&ScreenLocation2,(VECTOR *)&ScreenLocation2,&dx2);
		ScanOnePoint(&ReColor[3],CameraLocation,&ScreenLocation2,&dx2,&dy2,ColumnFirst,ColumnFirst+DeltaColumn,RowFirst+DeltaRow,RowLast,AARec-1);
	    }
	}

	CopyColor(Color,&ReColor[0]);
	for(i=1;i<4;i++) {
		Color->r+=ReColor[i].r;
		Color->g+=ReColor[i].g;
		Color->b+=ReColor[i].b;
	}
	Color->r*=0.25; Color->g*=0.25; Color->b*=0.25;
}


/**********************************************************************
 *
 *	TraceRay() will trace a given ray and return a resulting color
 *
 **********************************************************************/

long TraceRay(COLOR *Color, LINE *RayLine, long RecurseLevel)
{
	double	t,tnew,angle,dintense,pintense;
	LINE	LightLine,ReflectLine;
	POINT	IPoint;
	VECTOR	surfnormal,reflectvect;
	COLOR	surfcolor,ReflectedColor;
	long	i,ObjNum=-1;


    /* Find closest hit */
    if(RecurseLevel>0) {
	t=-1;
	for(i=0;i<NumObjects;i++) {
	    tnew=Intersect_Object(RayLine, ObjectArray[i]);
	    if((tnew>EPSILON)&&((tnew<t)||(t<=EPSILON))) {
		ObjNum=i;
		t=tnew;
	    }
	}

    /* Calculate color in intersected point */
	if(ObjNum>=0) {
	    IPoint.x=RayLine->Origin.x+t*RayLine->Direction.x;
	    IPoint.y=RayLine->Origin.y+t*RayLine->Direction.y;
	    IPoint.z=RayLine->Origin.z+t*RayLine->Direction.z;
	    if(fabs(IPoint.x+IPoint.y+IPoint.z)>maxsumcoord) {
		ObjNum=-1;
		Color->r=BackgroundColor.r;
		Color->g=BackgroundColor.g;
		Color->b=BackgroundColor.b;
	    }
	    else {
		GetSurfaceColor(&surfcolor, &ObjectArray[ObjNum]->Texture, &IPoint);
		GetSurfaceNormal(&surfnormal, &IPoint, ObjectArray[ObjNum]);
		angle=VectorsAngle(&surfnormal,&(RayLine->Direction));
		if(angle<PID2) NegVector(&surfnormal,&surfnormal);	/* So that the surface-normal always points towards us */

		ReflectVector(&reflectvect,&(RayLine->Direction),&surfnormal);

		Color->r=Color->g=Color->b=0.0;

		for(i=0;i<NumLights;i++) {
		    dintense=0.0;
		    pintense=0.0;
		    MakeLine(&LightLine,&IPoint,&(LightArray[i]->Location));
		    if(TestShadowRay(&LightLine,ObjNum)<0) {
			angle=VectorsAngle(&surfnormal,&(LightLine.Direction));
			if(angle<PID2) {
			    dintense=ObjectArray[ObjNum]->Texture.Diffuse*cos(angle);

			    angle=VectorsAngle(&reflectvect,&(LightLine.Direction));
			    if(angle<PID2) {
				pintense=ObjectArray[ObjNum]->Texture.Phong*pow(cos(angle),ObjectArray[ObjNum]->Texture.PhongSize);
			    }
			}
		    }
		    dintense = (dintense > 1.0 ? 1.0 : dintense);
		    pintense = (pintense > 1.0 ? 1.0 : pintense);

		    Color->r+=((dintense*surfcolor.r+pintense)*LightArray[i]->Color.r);
		    Color->g+=((dintense*surfcolor.g+pintense)*LightArray[i]->Color.g);
		    Color->b+=((dintense*surfcolor.b+pintense)*LightArray[i]->Color.b);
		}
		Color->r+=surfcolor.r*ObjectArray[ObjNum]->Texture.Ambient;
		Color->g+=surfcolor.g*ObjectArray[ObjNum]->Texture.Ambient;
		Color->b+=surfcolor.b*ObjectArray[ObjNum]->Texture.Ambient;

		if((ObjectArray[ObjNum]->Texture.Reflect.r+ObjectArray[ObjNum]->Texture.Reflect.g+ObjectArray[ObjNum]->Texture.Reflect.b)>EPSILON) {
			ReflectedRays++;
			CopyVector(&(ReflectLine.Direction),&reflectvect);
			CopyPoint(&(ReflectLine.Origin),&IPoint);
			TraceRay(&ReflectedColor, &ReflectLine, RecurseLevel-1L);
			Color->r+=(ObjectArray[ObjNum]->Texture.Reflect.r*ReflectedColor.r);
			Color->g+=(ObjectArray[ObjNum]->Texture.Reflect.g*ReflectedColor.g);
			Color->b+=(ObjectArray[ObjNum]->Texture.Reflect.b*ReflectedColor.b);
		}

		Color->r = (Color->r > 1.0 ? 1.0 : Color->r);
		Color->g = (Color->g > 1.0 ? 1.0 : Color->g);
		Color->b = (Color->b > 1.0 ? 1.0 : Color->b);
	    }
	}
	else {
		Color->r=BackgroundColor.r;
		Color->g=BackgroundColor.g;
		Color->b=BackgroundColor.b;
	}
    }
    else {
	Color->r=0.0;	/* Return no color (= color black) if recursion- */
	Color->g=0.0;   /* level <= 0                                    */
	Color->b=0.0;
    }

    return(ObjNum);
}



/**********************************************************************
 *
 *	TestShadowRay() will trace a given ray and look for object-hits
 *
 **********************************************************************/


long TestShadowRay(LINE *RayLine, long CurrentObject)
{
	long	i,hit=-1;
	double	t,told=-1;

	ShadowAttempts++;
	for(i=0;i<NumObjects;i++) {
	    if(i!=CurrentObject) {
		t=Intersect_Object(RayLine, ObjectArray[i]);
		if((t>EPSILON)&&((t<told)||(told<=EPSILON))) {
			hit=i;
			told=t;
		}
	    }
	}
	t=told;

	if(t>1.0) hit=-1;		/* Object was beyond light */
	if(hit>=0) ShadowHits++;

	return(hit);
}
