/*****************************************************************************
  Athor:	 Carlos Sarria	
  Name :     DragonFly
  Date :     March 1999
  Platform : Dreamcast Set5 (CLX2 + SH4) 
  
  Description : PowerVR 3DStudio MAX plug-in example.

  Copyright : 1999 by VideoLogic Limited. All rights reserved.
******************************************************************************/
#include <math.h>

#include "KMTools.h"
#include "KMShell.h"

/* Textures have been loaded with Data.c */
extern const unsigned char Back[];
extern const unsigned char Wing[];
extern const unsigned char Flash[];
extern const unsigned char Reflect[];

/* 3D data */
#include "DragonFly.h"

/*************************************************/
/*	Globals                                      */
/*************************************************/

#define MAX_NUM_SHOTS 100

/* texture surface */
KMSURFACEDESC		Tex[256];		
		
/* Vertex context */
KMVERTEXCONTEXT		VertexContext[256];		

/* Vertex pointers*/
KMVERTEX_03			*KmVert[256];		

/* SET4 / SET5 memory start*/
int MemPtr = 0x0c400000;

int ScreenWidth=640, ScreenHeight=480;

km_vector CameraFrom		= { 0.0f, 0.0f, -280.0f};
km_vector CameraTo			= { 0.0f, 0.0f, 1000.0f};
km_vector LightDirection	= { 0.0f, -1.0f, 1.0f};

float   nFrame = 0; 
float	fXAng, fYAng, fZAng;

BOOL    bStart = FALSE, bNoAB = FALSE, bFire = FALSE;

float   fPosX=0.0f, fPosZ=0.0, fAngleY = 0.5f, fAngleX=0.0f, fHeight = 0.0f;
float	AnimCount = 0, Incr = 0.5f;

typedef struct Shot_struct_tag {
	int Active;
	int Time;
	float fPosX; 
	float fPosZ; 
	float fAngleY; 
	float fHeight;
} SHOT;

SHOT Shot[100];


/**************************************************/
/* Prototypes                                     */
/**************************************************/

void	InitMeshes			(void);
void    InitMaterials       (void);
void	RenderObject		(int Object, KMVERTEXCONTEXT *VertexContext);
void	SetAnimM			(MATRIX *pMatrix, int i, unsigned Count);
void	CreateShot			(void);
void	DrawShots			(void);

/************************************************************************/
/* KMShell functions:                                                   */
/*    KMInitApplication                                                 */
/*    KMQuitApplication                                                 */
/*    KMRenderScene                                                     */
/*    KMPadControl                                                      * 
/*                                                                      */
/* WARNING : These functions must be created always because are called  */
/*           by KMShell.											    */
/************************************************************************/
/*----------------------------------------------------------------------*
 *  Function Name   :  InitApplication                              
 *  Inputs          :  None                                               
 *  Outputs         :  None                                                 
 *  Returns         :                                              
 *  Globals Used    :                                                
 *  Description     :   KMInitApplication() is called by KMShell to enable
 *						user to initialise the application                                           
 *----------------------------------------------------------------------*/
void InitApplication (void)
{
register i;

	KMShellSetVideo ( FALSE	);	/* Trans PreSort   */
					 

	KMShellSetBuffers (	50, 		/* % Opaque        */
						0,			/* % ModVol Opaque */
						50,			/* % Translucent   */
						0,			/* % ModVol Trans  */
						0);	

	KMABCTableInit (TRUE);

	InitMaterials ();

	InitMeshes();

	for(i=0; i<100; i++) Shot[i].Active = FALSE;

}
/*----------------------------------------------------------------------*
 *  Function Name   :  QuitApplication                                 
 *  Inputs          :  None                                               
 *  Outputs         :  None                                                 
 *  Returns         :                                            
 *  Globals Used    :                                                 
 *  Description     :                                                  
 *----------------------------------------------------------------------*/
void QuitApplication (void)
{
	KMABCTableRelease ();

	/* FREE TEXTURES */
	kmFreeTexture(&Tex[0]);
	kmFreeTexture(&Tex[1]);
	kmFreeTexture(&Tex[2]);
	kmFreeTexture(&Tex[10]);
}
/*----------------------------------------------------------------------*
 *  Function Name   :  RenderScene                                      
 *  Inputs          :  None                                               
 *  Outputs         :  None                                                 
 *  Returns         :  FALSE to finish                                               
 *  Globals Used    :                                                  
 *  Description     :  Draws everything in the scene.                                                 
 *----------------------------------------------------------------------*/
int RenderScene (void)
{
int				i, j;
MATRIX			Matrx;
static float	Angle = 0.0f;


	/*
	 * Transform the scene
	 */
  
	/* Slow movemement of the dragonfly up and down */
	fHeight += 0.2f * sin ((float)nFrame/50.0f);

	/* Slow rectification of the dragonfly angle */
	if(fAngleX>0.0f) fAngleX -= 0.01f;
	if(fAngleX<0.0f) fAngleX += 0.01f;

	for (i=0; i<NUM_MESHES;i++) 
	{ 
		/* Don't transform these objects */
		if (i==M_FLASH || i==M_BACKGROUND) continue;

		/* Set the transformation matrix for animation. (Must be done before any tranformation) */
		SetAnimM (&Matrx, i,  (int)AnimCount);

		/* Special movement for legs */
		if (i>=M_LEG1 && i<=M_LEG06)
		   KMMatrixRotate(&Matrx, 0.0f, 0, PI/64 * sin((float)(nFrame)/(5.0f+i*2.0f)));
			  
		/* Special movement for wings */
		if (i==M_WING1)
		   KMMatrixRotate(&Matrx, 0.0f, 0.0f, -PI/5.5 + PI/5.5 * cos((float)(nFrame)/1.0f));

		if (i==M_WING2)
			KMMatrixRotate(&Matrx, 0.0f, 0.0f,  PI/5.5 - PI/5.5 * cos((float)(nFrame)/1.0f));

		/* Rotate and translate the dragonfly */
		KMMatrixRotate(&Matrx, fAngleX, fAngleY, 0); 
				
		KMMatrixTranslate(&Matrx, fPosX, fHeight, 500.0f + fPosZ); 
	
		
		/* Transform all the vertices */
		KMTransTransformProjectPerspective (&Matrx, Mesh[i].nNumVertex, Mesh[i].pVertex, 
											KmVert[i], ScreenWidth, ScreenHeight,
											CameraFrom, CameraTo, 0.0f, 1.0f, 1000000.0f, 30.0f*(PI/180.0f));
		/*
		 * Environment map
		 */
		if (i!=M_WING1 && i!=M_WING2)
		{
			/* Reduce displacement to avoid 'crazy' env-map */
			Matrx._41 *= 0.005f; 
			Matrx._42 *= 0.005f; 
			Matrx._43  = 0.005f; 

			KMTransEnvironmentMap (&Matrx, Mesh[i].nNumVertex, Mesh[i].pNormals, 
											KmVert[i], 	CameraFrom, CameraTo, 0.0f, 
											1.0f, 1000000.0f, 30.0f*(PI/180.0f));
		}

	}

	/*
	 * Render the scene
	 */

	/* Draw the background */
	KMShellBackground (&VertexContext[10]);

	/* Draw the dragonfly */
	for (i=0; i<NUM_MESHES;i++) 
	{ 
		/* Don't transform these objects */
		if (i==M_FLASH || i==M_BACKGROUND) continue;

		if (i==M_WING1 || i==M_WING2)	RenderObject (i, &VertexContext[0]);
		else							RenderObject (i, &VertexContext[1]);	
	}

	/* Draw all the shots in the current frame */
	DrawShots();   
		
	/* Draw text */
	KMABCTablePrint3D (10.0f, 20.0f, 1.0f, 0xFFFFFF00, "DragonFly");
	KMABCTablePrint3D (10.0f, 50.0f, 0.5f, 0xFFFFFFFF, "CURSOR to move the dragonfly.");
	KMABCTablePrint3D (10.0f, 65.0f, 0.5f, 0xFFFFFFFF, "TRIGGERS to go forward.");
	KMABCTablePrint3D (10.0f, 80.0f, 0.5f, 0xFFFFFFFF, "ENTER to reset.");
	KMABCTablePrint3D (10.0f, 95.0f, 0.5f, 0xFFFFFFFF, "(A) to fire.");

	/*
	 * Animation frame-counter.
	 * There are two different animations, 
	 * one is the slow repose movement (from frame 0 to 100) 
	 * the other one is the 'fire' movement (from frame 100 to 150)
	 */
	if (!bFire)
	{
		AnimCount += Incr;
		if(AnimCount==0 || AnimCount==100) { Incr=-Incr;}
	}
	else
	{
		AnimCount++;
		if (AnimCount==150) {AnimCount=0; Incr=0.5f; bFire=FALSE;}
		if (AnimCount==108 || AnimCount==114 || AnimCount==120) CreateShot ();
	}

	nFrame++;     

	/* RenderScene OK */
	return TRUE;

}
/*-------------------------------------------------------------------------------------* 
 *  Function Name   : PadControl()                                                         
 *  Inputs          : None                                                               
 *  Outputs         : None                                                               
 *  Returns         : None                                                               
 *  Globals Used    :                                   
 *  Note            : This function is called by KMShell.c                           
 *-------------------------------------------------------------------------------------*/
void PadControl (void)
{	
static	int	Delay = 0;

	Delay++;

	/* START */
	if (IsSTARTKey())
	{
		fPosX=0.0f; fPosZ=0.0; fAngleY = 0.5f; fAngleX=0.0f; fHeight = 0.0f;
		if (Delay>10) bStart = !bStart;
		Delay = 0;
	}
	
	/* No controler until START is pressed */
	/*if (bStart == FALSE) return;*/
	
	/* UP */
	if (IsUPKey())
	{
		fHeight += 5.0f;
		fAngleX= 0.1f;
	}

	/* DOWN */
	if (IsDOWNKey())
	{
		fHeight -= 5.0f;
		fAngleX = -0.1f;
	}

	/* LEFT */
	if (IsLEFTKey())
	{
		fAngleY  += 0.05f;
	}

	/* RIGHT */
	if (IsRIGHTKey())
	{
		fAngleY  -= 0.05f;
	}


	/* A button */
	if (IsAKey())
	{
		if (AnimCount<100) {AnimCount=100;Incr=0.5f;}
		bFire = TRUE;
		fAngleX = -0.1f;
	}
	
	/* B button */
	if (IsBKey())
	{

	}
		
	/* X button */
	if (IsXKey())
	{
	}

	/* Y button */
	if (IsYKey())
	{
	}

	/* LEFT finger button */
	if (IsLFKey())
	{
		fPosX -= 10.0f * sin(fAngleY);
		fPosZ -= 10.0f * cos(fAngleY);

		fAngleX= 0.1f;
	}

	/* RIGHT finger button */
	if (IsRFKey())
	{
		fPosX -= 10.0f * sin(fAngleY);
		fPosZ -= 10.0f * cos(fAngleY);

		fAngleX= 0.1f;
	}

}
/*-------------------------------------------------------------------------------------*
 *  Function Name   : InitMeshes
 *  Inputs          : 
 *  Outputs         : 
 *  Returns         : 
 *  Globals Used    : 
 *  Description     : Initialitation for meshes
 *-------------------------------------------------------------------------------------*/
void InitMeshes (void)
{
register unsigned i,j, k=0;
float fTempVert[3], fModule;
unsigned int Color;

	/* Memory allocation for KmVert */
	for (i=0; i<NUM_MESHES; i++) 
	{
		KmVert[i] = (KMVERTEX_03 *)MemPtr; MemPtr += Mesh[i].nNumVertex*sizeof(KMVERTEX_03);
	}

	/* Setting UV values and colors for all the meshes reading materials or vertex colors*/
    for (i=0; i<NUM_MESHES;i++)
	{
		/* Default color */
		Color = 0xFFFFFFFF;
		
		/* Diffuse color from the material (if it exists) */
#ifdef STRUCT_MATERIALS_DEFINED 
		if (Mesh[i].nMaterial!=-1) Color = Material[Mesh[i].nMaterial].nMatDiffuseColor;
#endif 

		for (j=0; j<Mesh[i].nNumVertex; j++)
		{
			/* UVs */
			if(Mesh[i].pUV != NULL)
			{
				(KmVert[i]+j)->fU	= *(Mesh[i].pUV+j*2+0);
				(KmVert[i]+j)->fV	= *(Mesh[i].pUV+j*2+1);
			}
			else
			{
				(KmVert[i]+j)->fU	= 0.0f;
				(KmVert[i]+j)->fV	= 0.0f;
			}

			/* Colors */
			if (Mesh[i].pVertexColor!=NULL)
			{
				/* Vertex colors from 3DStudio MAX */
				(KmVert[i]+j)->uBaseRGB.dwPacked = *(Mesh[i].pVertexColor+j);
			}
			else
			{
				/* Material color (or default) */
				(KmVert[i]+j)->uBaseRGB.dwPacked = Color;
			}

			/* No specular color */
			(KmVert[i]+j)->uOffsetRGB.dwPacked	= 0x00000000;
		
		}     
	}

	/* Recalculating normals for a good env-mapping. 
	 * These are the media between the normal and the normalized vector
	 * from the origin to the vertex.
	 */
    for (i=0; i<NUM_MESHES;i++)
	{
		
		for (j=0; j<Mesh[i].nNumVertex; j++)
		{
          fTempVert[0] = *(Mesh[i].pVertex+j*3+0) - Mesh[i].fCenter[0];
		  fTempVert[1] = *(Mesh[i].pVertex+j*3+1) - Mesh[i].fCenter[1];
		  fTempVert[2] = *(Mesh[i].pVertex+j*3+2) - Mesh[i].fCenter[2];

		  if (fTempVert[0] || fTempVert[1] || fTempVert[2])
		  {
			fModule = 1.0f / sqrt (fTempVert[0]*fTempVert[0]+ fTempVert[1]*fTempVert[1] +fTempVert[2]*fTempVert[2]);
			fTempVert[0] *= fModule;
			fTempVert[1] *= fModule;
			fTempVert[2] *= fModule;
		  }
	
		  *(Mesh[i].pNormals+j*3+0) = (fTempVert[0]+*(Mesh[i].pNormals+j*3+0)) * 0.5f;
		  *(Mesh[i].pNormals+j*3+1) = (fTempVert[1]+*(Mesh[i].pNormals+j*3+1)) * 0.5f;
		  *(Mesh[i].pNormals+j*3+2) = (fTempVert[2]+*(Mesh[i].pNormals+j*3+2)) * 0.5f;
		
		}     
	}
 
  
}
/*-------------------------------------------------------------------------------------* 
 *  Function Name   : InitMaterials                                                         
 *  Inputs          : None                                                               
 *  Outputs         : None                                                               
 *  Returns         : None                                                               
 *  Globals Used    :                                   
 *  Description     : Loads textures and sets vertex contexts                           
 *-------------------------------------------------------------------------------------*/
void InitMaterials (void)
{
 
	/* Background */ 
	KMShellLoadTexture (&Tex[10],(unsigned long *)Back, 256,   KM_TEXTURE_TWIDDLED | KM_TEXTURE_RGB565);
	KMShellInitVertexContext (&VertexContext[10], &Tex[10], VC_OPAQUE);

	/* Wings */
	KMShellLoadTexture (&Tex[0],(unsigned long *)Wing, 256,   KM_TEXTURE_TWIDDLED | KM_TEXTURE_ARGB4444);
	KMShellInitVertexContext (&VertexContext[0], &Tex[0], VC_TRANS);

	/* Reflection */
	KMShellLoadTexture (&Tex[1],(unsigned long *)Reflect, 128,   KM_TEXTURE_TWIDDLED_MM | KM_TEXTURE_RGB565);
	KMShellInitVertexContext (&VertexContext[1], &Tex[1], VC_OPAQUE);

	/* Flash */
	KMShellLoadTexture (&Tex[2],(unsigned long *)Flash, 128,   KM_TEXTURE_TWIDDLED | KM_TEXTURE_RGB565);
	KMShellInitVertexContext (&VertexContext[2], &Tex[2], VC_ADDITIVE);

}
/*-------------------------------------------------------------------------------------
 *  Function Name   : RenderObject                                                    
 *  Inputs          : Object                                                           
 *  Outputs         : None                                                             
 *  Returns         : None                                                          
 *  Globals Used    :                                                                 
 *  Description     : Sending a set of triangles to render.                            
 *-------------------------------------------------------------------------------------*/
void RenderObject (int Object, KMVERTEXCONTEXT *pVertexContext)
{
	KMShellStrips (pVertexContext, Mesh[Object].nNumStrips, Mesh[Object].pStripLength,
		          Mesh[Object].pStrips, KmVert[Object]);

}
/*-------------------------------------------------------------------------------------
 *  Function Name   : SetAnimM                                                    
 *  Inputs          :                                                            
 *  Outputs         :                                                             
 *  Returns         :                                                           
 *  Globals Used    :                                                                 
 *  Description     : To set a animation from 3DStudio MAX, feed the transformation matrix 
 *					  with the values exported by the PVRexp plug-in.
 *-------------------------------------------------------------------------------------*/
void SetAnimM (MATRIX *pMatrix, int i, unsigned Count)
{

#ifdef STRUCT_ANIMATION_DEFINED

	/* If the frame is out of range return the identity matrix */
	if (Count<0 || Count > Animation[i].nNumFrames) 
	{
		KMMatrixReset (pMatrix);
		return;
	}

	/*
	 *  Every chunk has 12 floats so, for example, the data for frame 32 of the object 2 starts at
	 *  (Animation[2].pData + 32*12 + 0) and carrys on for 12 floats.
	 *  Note: M_14 = 0, M_24 = 0, M_34 = 0 and M_44 =1 are fixed values.
	 */
	Count = Count*12;

    pMatrix->_11 = *(Animation[i].pData + Count + 0);
	pMatrix->_12 = *(Animation[i].pData + Count + 1);
	pMatrix->_13 = *(Animation[i].pData + Count + 2);
	pMatrix->_14 = 0.0f;

	pMatrix->_21 = *(Animation[i].pData + Count + 3);
	pMatrix->_22 = *(Animation[i].pData + Count + 4);
	pMatrix->_23 = *(Animation[i].pData + Count + 5);
	pMatrix->_24 = 0.0f;

	pMatrix->_31 = *(Animation[i].pData + Count + 6);
	pMatrix->_32 = *(Animation[i].pData + Count + 7); 
	pMatrix->_33 = *(Animation[i].pData + Count + 8);
	pMatrix->_34 = 0.0f;

	pMatrix->_41 = *(Animation[i].pData + Count + 9); 
	pMatrix->_42 = *(Animation[i].pData + Count + 10);
	pMatrix->_43 = *(Animation[i].pData + Count + 11);
	pMatrix->_44 = 1.0f;

#else
	KMMatrixReset (pMatrix);
#endif

}
/*-------------------------------------------------------------------------------------
 *  Function Name   : DrawShots                                                    
 *  Inputs          :                                                            
 *  Outputs         :                                                             
 *  Returns         :                                                           
 *  Globals Used    :                                                                 
 *  Description     : If a shot is on, move and draw it.
 *-------------------------------------------------------------------------------------*/
void DrawShots (void)
{
register i;
float fX, fY;
MATRIX Matrix;

	for (i=0; i<MAX_NUM_SHOTS; i++)
	{
		/* If the shot is not active, don't draw it */
		if (Shot[i].Active==FALSE) continue;

		/* Limited time for displaying the same shot */
		Shot[i].Time++; 
		if(Shot[i].Time > 200) Shot[i].Active = FALSE;

		/* Update the position */
		Shot[i].fPosX -= 20.0f * sin(Shot[i].fAngleY);
		Shot[i].fPosZ -= 20.0f * cos(Shot[i].fAngleY); 
		Shot[i].fHeight -= 1.0f;

		/* Transform */
		KMMatrixReset (&Matrix);
		KMMatrixTranslate(&Matrix, Shot[i].fPosX, Shot[i].fHeight, 500.0f +Shot[i].fPosZ);
	 
		KMTransTransformProjectPerspective (&Matrix, Mesh[M_FLASH].nNumVertex, Mesh[M_FLASH].pVertex, 
											KmVert[M_FLASH], ScreenWidth, ScreenHeight,
											CameraFrom, CameraTo, 0.0f, 1.0f, 1000000.0f, 30.0f*(PI/180.0f));
	
		/* Draw */
		RenderObject (M_FLASH, &VertexContext[2]);

	}
}
/*-------------------------------------------------------------------------------------
 *  Function Name   : DrawShots                                                    
 *  Inputs          :                                                            
 *  Outputs         :                                                             
 *  Returns         :                                                           
 *  Globals Used    :                                                                 
 *  Description     : This function is called just once to initialize the data.                     
 *-------------------------------------------------------------------------------------*/
void CreateShot (void)
{
register i;
float fX, fY;
MATRIX Matrix;

	for (i=0; i<MAX_NUM_SHOTS; i++)
	{ 
		/* Select the first one that is free */
		if (Shot[i].Active==TRUE) continue;

		/* Feed the structure with the current position data */
		Shot[i].Active	= TRUE;
		Shot[i].Time	= 0; 
		Shot[i].fAngleY = fAngleY;	
		Shot[i].fPosX	= fPosX - 35.0f * sin(Shot[i].fAngleY);
		Shot[i].fPosZ	= fPosZ - 35.0f * cos(Shot[i].fAngleY);
		Shot[i].fHeight = fHeight + 20.0f;

		/* done */
		break;
	}
}