/*

DS glOrtho Test
Relminator 2010 
Richard Eric M. Lope BSN RN

http://rel.betterwebber.com

Just a proof of concept of using the 3d core to render 2d stuff


Notes:
*/


#include <nds.h>
#include <stdio.h>

#include "texture_2x2_16.h"
#include "nds_opt.h"



typedef enum
{

	GL_FLIP_NONE 	= 1 << 0,
	GL_FLIP_V 		= 1 << 1,
	GL_FLIP_H 		= 1 << 2,
	
} GL_FLIP_MODE;


typedef struct glImage
{

	int		width;
	int 	height;
	int 	u_off;
	int 	v_off;
	int		textureID;

} glImage;


/******************************************************
	Direct copy of glVertex3v16()
	I made this since VideoGL don'y have a
	glVertexni() wrappers
	
******************************************************/
static inline void glVertex3i(v16 x, v16 y, v16 z)
{
	GFX_VERTEX16 = (y << 16) | (x & 0xFFFF);
	GFX_VERTEX16 = ((uint32)(uint16)z);
}

/******************************************************
	Again no glVertex2i() in the videoGL header
	This is used for optimizing vertex calls
	
******************************************************/
static inline void glVertex2i(v16 x, v16 y)
{
	GFX_VERTEX_XY = (y << 16) | (x & 0xFFFF);	
}

/******************************************************

	Almost a direct copy of TEXTURE_PACK except that
	UV coords are shifted left by 4 bits.
	U and V are shifted left by 4 bits
	since GFX_TEX_COORD expects 12.4 Fixed point values
	
******************************************************/
static inline void glTexcoord2i(t16 u, t16 v)
{
	GFX_TEX_COORD = (v << 20) | ( (u << 4) & 0xFFFF );
}



static inline void glScale(s32 x, s32 y, s32 z)
{
	MATRIX_SCALE = x;
	MATRIX_SCALE = y;
	MATRIX_SCALE = z;
}

// Our static global variable used for
// Depth values since we cannot disable 
// depth testing in the DS hardware
// This value is incremented for every draw call
static v16 g_depth = 0;



/******************************************************************************
	Initializes GL in 3d mode 
	I initialized in 3d mode so that we could combine 2d and 3d later
	Almost a direct copy from the example files

******************************************************************************/
void glInitScreen()
{
		
	// initialize gl
	glInit();
	
	//enable textures
	glEnable(GL_TEXTURE_2D);
	
	// enable antialiasing
	glEnable(GL_ANTIALIAS);
		
	// setup the rear plane
	glClearColor(0,0,0,31); // BG must be opaque for AA to work
	glClearPolyID(63); // BG must have a unique polygon ID for AA to work
	
	glClearDepth(GL_MAX_DEPTH);

	//this should work the same as the normal gl call
	glViewport(0,0,255,191);
	
	
	//any floating point gl call is being converted to fixed prior to being implemented
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(70, 256.0 / 192.0, 1, 200);
	
	gluLookAt(	0.0, 0.0, 1.0,		//camera possition 
				0.0, 0.0, 0.0,		//look at
				0.0, 1.0, 0.0);		//up

	glMaterialf(GL_AMBIENT, RGB15(31,31,31));
	glMaterialf(GL_DIFFUSE, RGB15(31,31,31));
	glMaterialf(GL_SPECULAR, BIT(15) | RGB15(31,31,31));
	glMaterialf(GL_EMISSION, RGB15(31,31,31));

	//ds uses a table for shinyness..this generates a half-ass one
	glMaterialShinyness();

	//not a real gl function and will likely change
	glPolyFmt(POLY_ALPHA(31) | POLY_CULL_BACK);
	
}

/******************************************************
	Set orthographic projection
	at 1:1 correspondence to screen coords
	glOrtho expects f32 values but if we use the
	standard f32 values, we need to rescale either
	every vert or the modelview matrix by the same
	amount to make it work.  That's gonna give us
	lots of overflows and headaches.  So we "scale down"
	and use an all integer value.
******************************************************/
void glSetOrtho()		
{

	glMatrixMode(GL_PROJECTION);     // set matrixmode to projection
	glLoadIdentity();				 // reset
	glOrthof32(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1 << 12, 1 << 12);  // downscale projection matrix
	
}


/******************************************************


******************************************************/
void glStart2D()
{

	//what?!! No glDisable(GL_DEPTH_TEST)?!!!!!!
	
	// save 3d perpective projection matrix
	glMatrixMode(GL_PROJECTION);   
    glPushMatrix();
	
	// save 3d modelview matrix for safety
	glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
	
	// What?!! No glDisable(GL_DEPTH_TEST)?!!!!!!
	
	glEnable(GL_BLEND);
	glEnable(GL_TEXTURE_2D);
	glDisable(GL_ANTIALIAS);		// disable AA
	glDisable(GL_OUTLINE);			// disable edge-marking

	glColor(0x7FFF); 				// max color
	
	glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE);  // no culling

	glSetOrtho();
	
	glMatrixMode(GL_TEXTURE);      	// reset texture matrix just in case we did some funky stuff with it
	glLoadIdentity();
	
	glMatrixMode(GL_MODELVIEW);		// reset modelview matrix. No need to scale up by << 12
	glLoadIdentity();

	
	g_depth = 0; 	// set depth to 0. We need this var since we cannot disable depth testing

}


/******************************************************

	Issue this after drawing 2d so that we don't mess the matrix stack

******************************************************/
void glEnd2D()
{

	// restore 3d matrices and set current matrix to modelview
	glMatrixMode(GL_PROJECTION);    
    glPopMatrix(1);
	glMatrixMode(GL_MODELVIEW);
    glPopMatrix(1);

}

/******************************************************************************

	draws a line

******************************************************************************/
void glLine(int x1, int y1, int x2, int y2, int color)
{

	glBindTexture(0,0);
	glColor(color);
	glBegin(GL_TRIANGLES);
		glVertex3i(x1,y1,g_depth);
		glVertex2i(x2,y2);
		glVertex2i(x2,y2);
	glEnd();
	glColor(0x7FFF);
	g_depth++;
	
}


/******************************************************************************
    draws a box

******************************************************************************/
void glBox(int x1, int y1, int x2, int y2, int color)
{
	
	glBindTexture(0,0);
	glColor(color);
	glBegin(GL_TRIANGLES);
	
		glVertex3i(x1,y1,g_depth);
		glVertex2i(x2,y1);
		glVertex2i(x2,y1);

		glVertex2i(x2,y1);
		glVertex2i(x2,y2);
		glVertex2i(x2,y2);

		glVertex2i(++x2,y2);  // bug fix for lower-right corner disappearing pixel
		glVertex2i(x1,y2);
		glVertex2i(x1,y2);

		glVertex2i(x1,y2);
		glVertex2i(x1,y1);
		glVertex2i(x1,y1);
		
	glEnd();
	glColor(0x7FFF);
	g_depth++;

}

/******************************************************************************
   draws a filled box

******************************************************************************/
void glBoxFilled(int x1, int y1, int x2, int y2, int color)
{

	glBindTexture(0,0);
	glColor(color);
	glBegin(GL_QUADS);
		glVertex3i(x1,y1,g_depth);		// use 3i for first vertex so that we increment HW depth
		glVertex2i(x1,y2);				// no need for 3 vertices as 2i would share last depth call
		glVertex2i(x2,y2);
		glVertex2i(x2,y1);
	glEnd();
	glColor(0x7FFF);
	g_depth++;

}


/******************************************************************************
   draws a filled box

******************************************************************************/
void glBoxFilledGradient(int x1, int y1, int x2, int y2, int color1, int color2, int color3, int color4)
{

	glBindTexture(0,0);
	glBegin(GL_QUADS);
		glColor(color1); glVertex3i(x1,y1,g_depth);		// use 3i for first vertex so that we increment HW depth
		glColor(color2); glVertex2i(x1,y2);				// no need for 3 vertices as 2i would share last depth call
		glColor(color3); glVertex2i(x2,y2);
		glColor(color4); glVertex2i(x2,y1);
	glEnd();
	glColor(0x7FFF);
	g_depth++;

}



/******************************************************************************
    draws a Triangle

******************************************************************************/
void glTriangle(int x1, int y1, int x2, int y2, int x3, int y3, int color)
{
	
	glBindTexture(0,0);
	glColor(color);
	glBegin(GL_TRIANGLES);
	
		glVertex3i(x1,y1,g_depth);
		glVertex2i(x2,y2);
		glVertex2i(x2,y2);

		glVertex2i(x2,y2);
		glVertex2i(x3,y3);
		glVertex2i(x3,y3);

		glVertex2i(x3,y3);  
		glVertex2i(x1,y1);
		glVertex2i(x1,y1);
		
	glEnd();
	glColor(0x7FFF);
	g_depth++;

}

/******************************************************************************
   draws a filled triangle

******************************************************************************/
void glTriangleFilled(int x1, int y1, int x2, int y2, int x3, int y3, int color)
{

	glBindTexture(0,0);
	glColor(color);
	glBegin(GL_TRIANGLES);
		glVertex3i(x1,y1,g_depth);		// use 3i for first vertex so that we increment HW depth
		glVertex2i(x2,y2);				// no need for 3 vertices as 2i would share last depth call
		glVertex2i(x3,y3);
	glEnd();
	glColor(0x7FFF);
	g_depth++;

}


/******************************************************************************
   draws a filled Triangle

******************************************************************************/
void glTriangleFilledGradient(int x1, int y1, int x2, int y2, int x3, int y3, int color1, int color2, int color3)
{

	glBindTexture(0,0);
	glBegin(GL_TRIANGLES);
		glColor(color1); glVertex3i(x1,y1,g_depth);		// use 3i for first vertex so that we increment HW depth
		glColor(color2); glVertex2i(x2,y2);				// no need for 3 vertices as 2i would share last depth call
		glColor(color3); glVertex2i(x3,y3);
	glEnd();
	glColor(0x7FFF);
	g_depth++;

}


/******************************************************************************


******************************************************************************/
void glSprite(int x, int y, GL_FLIP_MODE flipmode, glImage *spr)
{
	int x1 = x;
	int y1 = y;
	int x2 = x + spr->width;
	int y2 = y + spr->height;

	int	u1 = (flipmode  & GL_FLIP_H) ? spr->u_off + spr->width - 1  : spr->u_off;
 	int	u2 = (flipmode  & GL_FLIP_H) ? spr->u_off 			        : spr->u_off + spr->width;
	int v1 = (flipmode  & GL_FLIP_V) ? spr->v_off + spr->height - 1 : spr->v_off;
 	int v2 = (flipmode  & GL_FLIP_V) ? spr->v_off 				    : spr->v_off + spr->height;
 	
 
	glBindTexture(0, spr->textureID);	
	glBegin(GL_QUADS);
		
		glTexcoord2i(u1, v1); glVertex3i(x1, y1, g_depth);	
		glTexcoord2i(u1, v2); glVertex2i(x1, y2);
		glTexcoord2i(u2, v2); glVertex2i(x2, y2);
		glTexcoord2i(u2, v1); glVertex2i(x2, y1);
		
	glEnd();
	
	g_depth++;

}

/******************************************************************************


******************************************************************************/
void glSpriteRotate(int x, int y, s32 angle, GL_FLIP_MODE flipmode, glImage *spr)
{
	
	int s_half_x = spr->width/2;
	int	s_half_y = spr->height/2;
	
	int x1 =  -s_half_x;
	int y1 =  -s_half_y;
	
	int x2 =  s_half_x;
	int y2 =  s_half_y;
	
	
	int	u1 = (flipmode  & GL_FLIP_H) ? spr->u_off + spr->width - 1  : spr->u_off;
 	int	u2 = (flipmode  & GL_FLIP_H) ? spr->u_off 			        : spr->u_off + spr->width - 1;
	int v1 = (flipmode  & GL_FLIP_V) ? spr->v_off + spr->height - 1 : spr->v_off;
 	int v2 = (flipmode  & GL_FLIP_V) ? spr->v_off 				    : spr->v_off + spr->height - 1;
 	
	glBindTexture(0, spr->textureID);
	
	glPushMatrix();
	
		glTranslate3f32(x, y, 0);
		glRotateZi(angle);
		
		
	glBegin(GL_QUADS);
		
		glTexcoord2i(u1, v1); glVertex3i(x1, y1, g_depth);	
		glTexcoord2i(u1, v2); glVertex2i(x1, y2);
		glTexcoord2i(u2, v2); glVertex2i(x2, y2);
		glTexcoord2i(u2, v1); glVertex2i(x2, y1);
		
	glEnd();
	
	glPopMatrix(1);
		
	g_depth++;

}


/******************************************************************************


******************************************************************************/
void glSpriteRotateScale(int x, int y, s32 angle, s32 scale, GL_FLIP_MODE flipmode, glImage *spr)
{
	
	int s_half_x = spr->width/2;
	int	s_half_y = spr->height/2;
	
	int x1 =  -s_half_x;
	int y1 =  -s_half_y;
	
	int x2 =  s_half_x;
	int y2 =  s_half_y;
	
	int	u1 = (flipmode  & GL_FLIP_H) ? spr->u_off + spr->width - 1  : spr->u_off;
 	int	u2 = (flipmode  & GL_FLIP_H) ? spr->u_off 			        : spr->u_off + spr->width- 1;
	int v1 = (flipmode  & GL_FLIP_V) ? spr->v_off + spr->height - 1 : spr->v_off;
 	int v2 = (flipmode  & GL_FLIP_V) ? spr->v_off 				    : spr->v_off + spr->height - 1;
 	
	glBindTexture(0, spr->textureID);
	
	glPushMatrix();
	
		glTranslate3f32(x, y, 0);
		glScale(scale, scale, 1 << 12);
		glRotateZi(angle);
		
		
	glBegin(GL_QUADS);
		
		glTexcoord2i(u1, v1); glVertex3i(x1, y1, g_depth);	
		glTexcoord2i(u1, v2); glVertex2i(x1, y2);
		glTexcoord2i(u2, v2); glVertex2i(x2, y2);
		glTexcoord2i(u2, v1); glVertex2i(x2, y1);
		
	glEnd();
	
	glPopMatrix(1);
		
	g_depth++;

}


int g_textureID, g_textureID_2;

glImage  images[10];

glImage  option[4];

int main()
{

	videoSetMode(MODE_5_3D);
	
	
	consoleDemoInit();
	
	iprintf("\x1b[1;1HDS Ortho Test");
	iprintf("\x1b[2;1HUsing the 3d core");
	iprintf("\x1b[3;1HTo draw 2d primitives.");
		
	iprintf("\x1b[6;1HRelminator");
	iprintf("\x1b[7;1Hhttp://rel.betterwebber.com");
	
	
	glInitScreen();
	
	
	vramSetBankA(VRAM_A_TEXTURE);
	
	// load 16 bit bitmap
	glGenTextures(1, &g_textureID);
	glBindTexture(0, g_textureID);
	glTexImage2D( 0, 0, GL_RGB, TEXTURE_SIZE_128 , TEXTURE_SIZE_128, 0, GL_TEXTURE_WRAP_S|GL_TEXTURE_WRAP_T|TEXGEN_OFF, (u8*)texture_2x2_16Bitmap );


	// load paletted 8 bit bitmap with color index 0 as the transparent color
	glGenTextures(1, &g_textureID_2);
	glBindTexture(0, g_textureID_2);
	glTexImage2D( 0, 0, GL_RGB256, TEXTURE_SIZE_16 , TEXTURE_SIZE_64, 0, GL_TEXTURE_WRAP_S|GL_TEXTURE_WRAP_T|TEXGEN_OFF|GL_TEXTURE_COLOR0_TRANSPARENT, (u8*)nds_optBitmap );

	// load the palette for our 256 color BMP
	gluTexLoadPal(nds_optPal, 512,GL_RGB256);

	
	// set up our images
	images[0].width = 64;
	images[0].height = 64;				// subtract 1 to height since bottom lines are not rendered by default
	images[0].u_off = 0;
	images[0].v_off = 0;
	images[0].textureID = g_textureID;
	
	images[1].width = 64;
	images[1].height = 64;
	images[1].u_off = 64;
	images[1].v_off = 64;
	images[1].textureID = g_textureID;
	
	images[2].width = 64;
	images[2].height = 64;
	images[2].u_off = 0;
	images[2].v_off = 64;
	images[2].textureID = g_textureID;
	
	images[3].width = 64;
	images[3].height = 64;
	images[3].u_off = 64;
	images[3].v_off = 0;
	images[3].textureID = g_textureID;
	
	// full image
	images[4].width = 128;
	images[4].height = 128;
	images[4].u_off = 0;
	images[4].v_off = 0;
	images[4].textureID = g_textureID;
	
	// option images
	int i;
	for (i = 0; i < 4; i++)
	{
	
		option[i].width = 16;
		option[i].height = 16;
		option[i].u_off = 0;
		option[i].v_off = i * 16;
		option[i].textureID = g_textureID_2;
		
	}
	s32 x = 10;
	s32 y = 10;
	
	s32 x1,y1,x2,y2,x3,y3;
	u8 r,g,b;
	
	int option_frame = 0;
	int frame = 0;
	int rotation = 0;
	
	while(1)
	{
	
		frame++;
		if ( (frame & 7) == 0 )
		{
			option_frame = (option_frame + 1) & 3;
		}
		rotation = (rotation + 128) & 32767;
		
		x1 = rand() & 255;
		y1 = rand() & 255;
		x2 = rand() & 255;
		y2 = rand() & 255;
		x3 = rand() & 255;
		y3 = rand() & 255;
		
		r = rand() & 31;
		g = rand() & 31;
		b = rand() & 31;
		
		glStart2D();
	
		//glEnable(GL_OUTLINE);
		
			// needs to be called before texturing
			// or sprite gets blended by last glcolor call
			glColor(ARGB16(1,31,31,31)); 
	
			glSprite(10, 10, GL_FLIP_NONE, &images[4]);
			glSprite(10, 10, GL_FLIP_H, &images[1]);
			//glSprite(10+64, 10, GL_FLIP_H, &images[2]);
			
			
			x = 90;
			y = 90;
			
			glBoxFilled(x, y, x + 60, y + 40, ARGB16(1,0,31,31));
			
			x = 128;
			y = 100;
			
			glBoxFilled(x, y, x + 10, y+ 80, ARGB16(1,31,31,31));
			
			x = 10;
			y = 120;
			
			glBoxFilledGradient(x, y, x + 200, y+ 30, ARGB16(1,31,0,0), ARGB16(1,31,31,0), ARGB16(1,0,0,31), ARGB16(1,31,0,31));
			
			
			
			glLine(x1,y1,x2,y2,ARGB16(1,r,g,b));
			glBox(x1,y1,x2,y2,ARGB16(1,r,g,b));
			
			glTriangleFilledGradient(100,100,200,200,150,20,ARGB16(1,22,2,31),ARGB16(1,31,0,20),ARGB16(1,15,31,0));
			
			glTriangle(x1,y1,x2,y2,x3,y3,ARGB16(1,r,g,b));
			
			// do some funky frame based movement
			x = 128 + ((cosLerp(frame)+sinLerp(32767/2+rotation) * 70) >> 12);
			y = 96 + ((cosLerp(frame)+cosLerp(-rotation) * 50) >> 12);
			
			// needs to be called before texturing
			// or sprite gets blended by last glcolor call
			glColor(ARGB16(1,31,31,31)); 
			
			glSpriteRotate(x, 	   y,    rotation,      GL_FLIP_NONE,          &images[0]);
			glSpriteRotate(255-x, 191-y, rotation * 4,  GL_FLIP_H,             &images[1]);
			glSpriteRotate(255-x, y,     -rotation,     GL_FLIP_V,             &images[2]);
			glSpriteRotate(x,     191-y, -rotation * 3, GL_FLIP_H | GL_FLIP_V, &images[3]);
			
			
			// do some scaling and rotation
			// translucency is achieved by setting POLY_ALPHA and giving a unique POLY_ID
			s32 scale = sinLerp(rotation) * 3; 
			glPolyFmt(POLY_ALPHA(16) | POLY_CULL_NONE | POLY_ID(1));
			glSpriteRotateScale(128,96,rotation, scale, GL_FLIP_NONE, &images[3]);
			
			
			// try some flipped and animated stuff
			glSprite(180, 10, GL_FLIP_NONE, &images[1]);
			
			glPolyFmt(POLY_ALPHA(31) | POLY_CULL_NONE); 
			
			glSprite(10, 10, GL_FLIP_NONE, &option[option_frame]);
			
			glSprite(10, 40, GL_FLIP_H, &option[option_frame]);
			
			glSprite(10, 70, GL_FLIP_V, &option[option_frame]);
			
			glSprite(10, 100, GL_FLIP_V | GL_FLIP_H , &option[option_frame]);
			
		glEnd2D();
		
		
		glFlush(0);

		swiWaitForVBlank();
	}

	return 0;
	
}
