// **************************************************************************
// *				Sporting Clays for the Nintendo 64						*
// *					   (C) 1999 Charles Doty                          	*
// *	 	   3D Scene Source file contains 3D related code				*
// **************************************************************************

#include "display.h"
#include "main.h"
#include "scene.h"
#include "scene3d.h"
#include "static.h"
#include "objects\clay.h"

#define SCREENZ     1024                    // screen projection
#define ZSTART		-250					// Start of the Z buffer

Object3D				Objects[MAXOBJECTS];
dynamic_stuff			*check;
u64 					DramStack[SP_DRAM_STACK_SIZE64];

extern dynamic_stuff   	dynamic[3];
extern dynamic_stuff	*generate;
extern OSTask          	task[3];
extern WORD 			orange[];
extern WORD 			white[];
extern WORD 			grey[];
extern char   			*staticSegment;
extern WORD    			RenderBuffer[SCREENWIDTH * SCREENHEIGHT];
extern RECT				Mouse;
extern OSMesgQueue		rdpMessageQ;
extern OSMesgQueue		rspMessageQ;

DWORD Scene3DInitialize()
{
	DWORD 	loop;

	for (loop = 0; loop < MAXOBJECTS; loop++)
	{
		Objects[loop].Used 			= FALSE;
		Objects[loop].CurrentFrame	= 0;
	}

	return ERR_NOERROR;
}

DWORD AddObject(long color, DWORD *ObjectID, POS *position, ROT *rotation, SCALE *scale)
{
    DWORD	loop    = 0;

    if (NULL == ObjectID || NULL == position || NULL == rotation || NULL == scale)
    {
#ifdef DEBUG
		printf("Bad Parameters to AddObject\n");
#endif
    	*ObjectID 	= 0xffff;

    	return ERR_BADPARAMS;
    }

    for (loop = 0; loop < MAXOBJECTS; loop++)
    {
    	if (FALSE == Objects[loop].Used)
    	{
    		break;
    	}
    }

    if (loop >= MAXOBJECTS)
    {
#ifdef DEBUG
        printf("No object available in AddObject\n");
#endif
    	return ERR_NONEAVAILABLE;
    }

    Objects[loop].Position.x       		= position->x;
    Objects[loop].Position.y       		= position->y;
    Objects[loop].Position.z       		= position->z + ZSTART;
    Objects[loop].Rotation.x       		= rotation->x;
    Objects[loop].Rotation.y       		= rotation->y;
    Objects[loop].Rotation.z       		= rotation->z;
    Objects[loop].Scale.x				= scale->x;
    Objects[loop].Scale.y          		= scale->y;
    Objects[loop].Scale.z          		= scale->z;
	Objects[loop].CurrentFrame			= 0;
    Objects[loop].Color					= color;
    Objects[loop].Used 					= TRUE;

	*ObjectID 							= loop;

	return ERR_NOERROR;
}

DWORD RemoveObject(DWORD ObjectID)
{
	if (ObjectID > MAXOBJECTS || FALSE == Objects[ObjectID].Used)
	{
#ifdef DEBUG
		printf("Bad Parameters to RemoveObject\n");
#endif

		return ERR_BADPARAMS;
	}

	Objects[ObjectID].Used = FALSE;

	return ERR_NOERROR;
}

DWORD MoveObject(DWORD ObjectID, POS *position)
{
	if (ObjectID > MAXOBJECTS || FALSE == Objects[ObjectID].Used)
	{
#ifdef DEBUG
		printf("Bad Parameters to MoveObject\n");
#endif
		return ERR_BADPARAMS;
	}

    Objects[ObjectID].Position.x	= position->x;
    Objects[ObjectID].Position.y 	= position->y;
    Objects[ObjectID].Position.z 	= position->z + ZSTART;

    return ERR_NOERROR;
}

DWORD RotateObject(DWORD ObjectID, ROT *rotation)
{
	if (ObjectID > MAXOBJECTS || FALSE == Objects[ObjectID].Used)
	{
#ifdef DEBUG
		printf("Bad Parameters to RotateObject\n");
#endif
		return ERR_BADPARAMS;
	}

    Objects[ObjectID].Rotation.x	= rotation->x;
    Objects[ObjectID].Rotation.y 	= rotation->y;
    Objects[ObjectID].Rotation.z 	= rotation->z;

    return ERR_NOERROR;
}

DWORD SetObjectFrame(DWORD ObjectID, long frame)
{
	if (ObjectID > MAXOBJECTS || FALSE == Objects[ObjectID].Used)
	{
#ifdef DEBUG
		printf("Bad Parameters to SetObjectFrame\n");
#endif
		return FALSE;
	}

	Objects[ObjectID].CurrentFrame = frame;

	return ERR_NOERROR;
}

BOOL CheckIntersection(DWORD ObjectID, long x, long y, long range, long *distance, long *xdir,
	long *ydir)
{
    long	outerloop;
    long	innerloop;
	long 	NextObject 	= 0;
    long	frame;
	long	total1;
	long	total2;
	long	total3;
	long	total4;
  	long	mx			= Mouse.x + MOUSESIZE / 2;
  	long	my			= Mouse.y + MOUSESIZE / 2;
  	WORD	perspnorm;
    OSTask	*gentask;
    Gfx		*GfxList0;
	Gfx		*GfxList;
	Gfx		*DisplayList;
	WORD	*buffer;

	if (ObjectID > MAXOBJECTS || FALSE == Objects[ObjectID].Used || NULL == distance || NULL == xdir
		|| NULL == ydir)
	{
#ifdef DEBUG
		printf("Bad Parameters to Check Intersection\n");
#endif

		return FALSE;
	}

  	check 		= &(dynamic[2]);
  	GfxList0  	= &(check->glist[0]);
  	GfxList   	= GfxList0;

  	// Tell RCP where each segment is
  	gSPSegment(GfxList++, 0, 0);
  	gSPSegment(GfxList++, STATIC_SEGMENT, osVirtualToPhysical(staticSegment));

  	// Select surface to write to
  	gDPSetColorImage(GfxList++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREENWIDTH,
  		OS_K0_TO_PHYSICAL(RenderBuffer));

  	// Clear Render Buffer
  	gSPDisplayList(GfxList++, ClearRenderBuffer);

  	// Initialize RSP and RDP state
    gSPDisplayList(GfxList++, CheckInit);

    gDPSetScissor(GfxList++, G_SC_NON_INTERLACE, mx - RANGE, my - RANGE,
        mx + RANGE, my + RANGE);
	gDPSetCycleType(GfxList++, G_CYC_1CYCLE);
  	gSPSetGeometryMode(GfxList++, G_CULL_BACK);
    gDPSetCombineMode(GfxList++, G_CC_PRIMITIVE, G_CC_PRIMITIVE);
	gDPSetRenderMode(GfxList++,G_RM_OPA_SURF, G_RM_OPA_SURF);
	gDPSetColorDither(GfxList++, G_CD_DISABLE);

   	gSPTexture(GfxList++, 0, 0, 0, 0, G_OFF);

	switch (Objects[ObjectID].Color)
	{
		case CC_ORANGE:
            gDPSetPrimColor(GfxList++, 0x0100, 0x0100, 250, 81, 0, 1);

			break;

		case CC_WHITE:
            gDPSetPrimColor(GfxList++, 0x0100, 0x0100, 0xFF, 0xFF, 0xFF, 1);

			break;

		case CC_GREY:
            gDPSetPrimColor(GfxList++, 0x0100, 0x0100, 0x7F, 0x7F, 0x7F, 1);

			break;
	}

	frame       = Objects[ObjectID].CurrentFrame;

	// Set up the perspective.
    guPerspective(&(check->projection), &perspnorm, 45.0, 320.0 / 240.0, 150.0, 3000.0, 1.0);
	gSPPerspNormalize(GfxList++, perspnorm);

	guRotate(&(check->modeling_rotate[NextObject][0]), Objects[ObjectID].Rotation.x, 1.0, 0.0, 0.0);
	guRotate(&(check->modeling_rotate[NextObject][1]), Objects[ObjectID].Rotation.y, 0.0, 1.0, 0.0);
	guRotate(&(check->modeling_rotate[NextObject][2]), Objects[ObjectID].Rotation.z, 0.0, 0.0, 1.0);

	guTranslate(&(check->modeling_translate[NextObject]), Objects[ObjectID].Position.x,
		Objects[ObjectID].Position.y, Objects[ObjectID].Position.z);

	// Set pointers in drawling list
	gSPMatrix(GfxList++, OS_K0_TO_PHYSICAL(&(check->projection)), G_MTX_PROJECTION | G_MTX_LOAD |
		G_MTX_NOPUSH);

	gSPMatrix(GfxList++, OS_K0_TO_PHYSICAL(&(check->modeling_translate[NextObject])),
		G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);

	gSPMatrix(GfxList++, OS_K0_TO_PHYSICAL(&(check->modeling_rotate[NextObject][0])),
		G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);

	gSPMatrix(GfxList++, OS_K0_TO_PHYSICAL(&(check->modeling_rotate[NextObject][2])),
		G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);

	gSPMatrix(GfxList++, OS_K0_TO_PHYSICAL(&(check->modeling_rotate[NextObject][1])),
		G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);

	gSPDisplayList(GfxList++, Clay[frame]);

	gDPPipeSync(GfxList++);
	gDPFullSync(GfxList++);
  	gSPEndDisplayList(GfxList++);

  	// Build graphics task to execute the display list we just built
  	gentask 					= &(task[2]);
  	gentask->t.type            	= M_GFXTASK;
  	gentask->t.flags           	= OS_TASK_DP_WAIT;
  	gentask->t.ucode_boot      	= (u64 *)rspbootTextStart;
  	gentask->t.ucode_boot_size 	= ((int)rspbootTextEnd - (int)rspbootTextStart);
  	gentask->t.ucode           	= (u64 *)gspFast3DTextStart;
  	gentask->t.ucode_data      	= (u64 *)gspFast3DDataStart;
  	gentask->t.ucode_size      	= 4096;
  	gentask->t.ucode_data_size 	= 2048;
  	gentask->t.dram_stack      	= (u64 *)DramStack;
  	gentask->t.dram_stack_size 	= 1024;
  	gentask->t.output_buff     	= (u64 *)NULL;
  	gentask->t.output_buff_size	= (u64 *)NULL;
  	gentask->t.yield_data_ptr  	= (u64 *)NULL;
  	gentask->t.yield_data_size 	= 0x0;
  	gentask->t.data_ptr        	= (u64 *)GfxList0;
  	gentask->t.data_size       	= ((int)GfxList - (int)GfxList0);

  	// Write back dirty cache lines that need to be read by the RCP
  	osWritebackDCache(check, sizeof(dynamic_stuff));

  	// Fire up SP task
  	osSpTaskStart(gentask);

  	// wait for SP completion
  	osRecvMesg(&rspMessageQ, NULL, OS_MESG_BLOCK);
  	osRecvMesg(&rdpMessageQ, NULL, OS_MESG_BLOCK);

	buffer = &RenderBuffer[(my - INNERRANGE) * SCREENWIDTH + (mx - INNERRANGE)];

	for (outerloop = 0; outerloop < INNERRANGE * 2; outerloop++)
	{
		for (innerloop = 0; innerloop < INNERRANGE * 2; innerloop++)
		{
			if (*buffer != 0x0001)
			{
				*distance = INNERRANGE - 1;

				return TRUE;
			}

			buffer++;
		}

		buffer += SCREENWIDTH - INNERRANGE * 2;
	}

	total1 = 0;
	total2 = 0;
	total3 = 0;
	total4 = 0;

	buffer = &RenderBuffer[(my - RANGE) * SCREENWIDTH + (mx - RANGE)];

	for (outerloop = 0; outerloop < RANGE; outerloop++)
	{
		for (innerloop = 0; innerloop < RANGE; innerloop++)
		{
			if (*buffer != 0x0001)
			{
				total1++;
			}

			if (*(buffer + RANGE) != 0x0001)
			{
				total2++;
			}

			if (*(buffer + SCREENWIDTH * RANGE) != 0x0001)
			{
				total3++;
			}

			if (*(buffer + SCREENWIDTH * RANGE + RANGE) != 0x0001)
			{
				total4++;
			}

			buffer++;
		}

		buffer += SCREENWIDTH - RANGE;
	}

	if (0 == total1 && 0 == total2 && 0 == total3 && 0 == total4)
	{
		return FALSE;
	}

	if (total1 >= total2 && total1 >= total3 && total1 >= total4)
	{
		*distance 	= INNERRANGE + 1;
		*xdir		= -1;
		*ydir		= 1;

		return TRUE;
	}

	else if (total2 >= total1 && total2 >= total3 && total2 >= total4)
	{
		*distance 	= INNERRANGE + 1;
		*xdir		= 1;
		*ydir		= 1;

		return TRUE;
	}

	else if (total3 >= total1 && total3 >= total2 && total3 >= total4)
	{
		*distance 	= INNERRANGE + 1;
		*xdir		= -1;
		*ydir		= -1;

		return TRUE;
	}

	else if (total4 >= total1 && total4 >= total2 && total4 >= total3)
	{
		*distance 	= INNERRANGE + 1;
		*xdir		= 1;
		*ydir		= -1;

		return TRUE;
	}

	return FALSE;
}

BOOL CheckFloor(DWORD ObjectID, long y)
{
	long y1;
	long y2;

	if (ObjectID > MAXOBJECTS || FALSE == Objects[ObjectID].Used)
	{
#ifdef DEBUG
		printf("Bad Parameters to Check Floor\n");
#endif
		return FALSE;
	}

	y1 	= ((-Objects[ObjectID].Position.z * 415) / 1000);
	y2	= (y - 120) * y1 / (SCREENHEIGHT - 120);

	if (-Objects[ObjectID].Position.y >= y2)
	{
		return TRUE;
	}

	return FALSE;
}

BOOL CheckScreen(DWORD ObjectID)
{
	long 	x1;
	long 	y1;
	long 	x2;
	long 	y2;
	long	x;
	long	y;

	if (ObjectID > MAXOBJECTS || FALSE == Objects[ObjectID].Used)
	{
#ifdef DEBUG
		printf("Bad Parameters to Check Screen\n");
#endif
		return FALSE;
	}

	x	= Objects[ObjectID].Position.x;
	y	= Objects[ObjectID].Position.y;

	x2 = ((-Objects[ObjectID].Position.z * 560) / 1000);
	x1 = -x2;
	y1 = ((-Objects[ObjectID].Position.z * 415) / 1000);
	y2 = -y1;

	if (x < x1 || x > x2 || y > y1 || y < y2)
	{
		return TRUE;
	}

	return FALSE;
}

DWORD Render(Gfx **GfxList)
{
    DWORD       loop;
  	WORD		perspnorm;
    long		frame;
	long 		NextObject = 0;

	gDPSetCycleType((*GfxList)++, G_CYC_1CYCLE);
  	gSPSetGeometryMode((*GfxList)++, G_SHADE | G_SHADING_SMOOTH | G_ZBUFFER | G_LIGHTING |
        G_CULL_BACK | G_TEXTURE_GEN);
    gDPSetCombineMode((*GfxList)++, G_CC_DECALRGB, G_CC_DECALRGB);
	gDPSetRenderMode((*GfxList)++,G_RM_AA_ZB_OPA_SURF, G_RM_AA_ZB_OPA_SURF);
	gDPSetColorDither((*GfxList)++, G_CD_DISABLE);

    for (loop = 0; loop < MAXOBJECTS; loop++)
    {
        if (FALSE == Objects[loop].Used)
        {
            continue;
        }

        frame       = Objects[loop].CurrentFrame;

    	gSPTexture((*GfxList)++, 0x8000, 0x8000, 0, 0, G_ON);

		switch (Objects[loop].Color)
		{
			case CC_ORANGE:
                gDPSetPrimColor((*GfxList)++, 0x0100, 0x0100, 250, 81, 0, 1);

    			gDPLoadTextureBlock((*GfxList)++, orange, G_IM_FMT_RGBA, G_IM_SIZ_16b, 32, 32, 0,
    				G_TX_WRAP | G_TX_NOMIRROR, G_TX_WRAP | G_TX_NOMIRROR, 5, 5, G_TX_NOLOD,
    				G_TX_NOLOD);

				break;

			case CC_WHITE:
                gDPSetPrimColor((*GfxList)++, 0x0100, 0x0100, 0xFF, 0xFF, 0xFF, 1);

    			gDPLoadTextureBlock((*GfxList)++, white, G_IM_FMT_RGBA, G_IM_SIZ_16b, 32, 32, 0,
    				G_TX_WRAP | G_TX_NOMIRROR, G_TX_WRAP | G_TX_NOMIRROR, 5, 5, G_TX_NOLOD,
    				G_TX_NOLOD);

  				break;

			case CC_GREY:
                gDPSetPrimColor((*GfxList)++, 0x0100, 0x0100, 0x80, 0x80, 0x80, 1);

    			gDPLoadTextureBlock((*GfxList)++, grey, G_IM_FMT_RGBA, G_IM_SIZ_16b, 32, 32, 0,
    				G_TX_WRAP | G_TX_NOMIRROR, G_TX_WRAP | G_TX_NOMIRROR, 5, 5, G_TX_NOLOD,
    				G_TX_NOLOD);

				break;
		}

  		// Set up the perspective.
  		guPerspective(&(generate->projection), &perspnorm, 45.0, 320.0 / 240.0, 150.0, 3000.0, 1.0);
		gSPPerspNormalize((*GfxList)++, perspnorm);

  		guRotate(&(generate->modeling_rotate[NextObject][0]), Objects[loop].Rotation.x, 1.0, 0.0, 0.0);
  		guRotate(&(generate->modeling_rotate[NextObject][1]), Objects[loop].Rotation.y, 0.0, 1.0, 0.0);
  		guRotate(&(generate->modeling_rotate[NextObject][2]), Objects[loop].Rotation.z, 0.0, 0.0, 1.0);

  		guTranslate(&(generate->modeling_translate[NextObject]), Objects[loop].Position.x,
  			Objects[loop].Position.y, Objects[loop].Position.z);

  		// Set pointers in drawing list
  		gSPMatrix((*GfxList)++, OS_K0_TO_PHYSICAL(&(generate->projection)), G_MTX_PROJECTION |
  			G_MTX_LOAD | G_MTX_NOPUSH);

  		gSPMatrix((*GfxList)++, OS_K0_TO_PHYSICAL(&(generate->modeling_translate[NextObject])),
  			G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);

	  	gSPMatrix((*GfxList)++, OS_K0_TO_PHYSICAL(&(generate->modeling_rotate[NextObject][0])),
	  		G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);

  		gSPMatrix((*GfxList)++, OS_K0_TO_PHYSICAL(&(generate->modeling_rotate[NextObject][2])),
  			G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);

  		gSPMatrix((*GfxList)++, OS_K0_TO_PHYSICAL(&(generate->modeling_rotate[NextObject][1])),
  			G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);

  		gSPDisplayList((*GfxList)++, Clay[frame]);

		NextObject++;
    }

  	gDPPipeSync((*GfxList)++);

	return ERR_NOERROR;
}

