/*
   Copyright 2008 Jeffrey Lee
   This file is part of WOUM.
   WOUM is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
   WOUM is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   You should have received a copy of the GNU General Public License
   along with WOUM.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#include "kernel.h"
#include "swis.h"

#include "spandraw.h"
#include "vec.h"
#include "tmat.h"

#include "screen.h"
#include "WoumInclude:lib/gp/screen.h"

#include "timer.h"

#define X .525731112119133606
#define Z .850650808352039932

float vdata[12][3] = {
	{-X, 0, Z}, {X, 0, Z}, {-X, 0, -Z}, {X, 0, -Z},
	{0, Z, X}, {0, Z, -X}, {0, -Z, X}, {0, -Z, -X},
	{Z, X, 0}, {-Z, X, 0}, {Z, -X, 0}, {-Z, -X, 0}
};

#undef X
#undef Z

int tindicies[20][3] = {
	{0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},
	{8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},
	{7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6},
	{6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11} };

typedef struct {
	int v1,v2,v3; /* Vertex indicies */
	int col; /* colour */
} tri;

vec16 *verts; /* the verticies */
tri *tris; /* The triangles */
vec16 *tverts; /* The transformed verticies */

int ntri,nvert;

int getvertex(vec16 *v)
{
	int i;
	i=0;
	do {
		if ((verts[i].x == 0) && (verts[i].y == 0) && (verts[i].z == 0))
		{
			verts[i].x = v->x;
			verts[i].y = v->y;
			verts[i].z = v->z;
			if (i > nvert)
				nvert = i+1;
			return i;
		}
		else if ((verts[i].x == v->x) && (verts[i].y == v->y) && (verts[i].z == v->z))
			return i;
		else
			i++;
	} while (1);
}

void drawtriangle(vec16 *v1,vec16 *v2,vec16 *v3)
{
	int r,g,b,col;
	tris[ntri].v1 = getvertex(v1);
	tris[ntri].v2 = getvertex(v2);
	tris[ntri].v3 = getvertex(v3);
	r = ((v1->x+v2->x+v3->x)/3+0x10000) >> 12;
	g = ((v1->y+v2->y+v3->y)/3+0x10000) >> 12;
	b = ((v1->z+v2->z+v3->z)/3+0x10000) >> 12;
	col = r+(g << 5)+(b << 10);
	tris[ntri++].col = col*0x10001;
}

void subdivide(vec16 *v1, vec16 *v2, vec16 *v3, int depth)
{
	vec16 v12, v23, v31;
	if (depth == 0) {
		drawtriangle(v1,v2,v3);
		return;
	}
	v12.x = v1->x+v2->x; v12.y = v1->y+v2->y; v12.z = v1->z+v2->z;
	v23.x = v2->x+v3->x; v23.y = v2->y+v3->y; v23.z = v2->z+v3->z;
	v31.x = v3->x+v1->x; v31.y = v3->y+v1->y; v31.z = v3->z+v1->z;
	vec16_normalize(&v12);
	vec16_normalize(&v23);
	vec16_normalize(&v31);
	subdivide(v1,&v12,&v31,depth-1);
	subdivide(v2,&v23,&v12,depth-1);
	subdivide(v3,&v31,&v23,depth-1);
	subdivide(&v12,&v23,&v31,depth-1);
}

void setuptris(int depth)
{
	int polys,i;
	vec16 tvec[3];
	if (verts)
		free(verts);
	if (tris)
		free(tris);
	if (tverts)
		free(tverts);
	polys = 20*((int) pow(4,(double) depth));
	verts = malloc(sizeof(vec16)*polys); /* Rough estimate */
	memset(verts,0,sizeof(vec16)*polys); /* Zero out */
	tris = malloc(sizeof(tri)*polys);
	ntri = 0;
	nvert = 0;
	for (i=0;i<20;i++)
	{
		tvec[0].x = f1616_FromFloat(vdata[tindicies[i][0]][0]);
		tvec[0].y = f1616_FromFloat(vdata[tindicies[i][0]][1]);
		tvec[0].z = f1616_FromFloat(vdata[tindicies[i][0]][2]);
		tvec[1].x = f1616_FromFloat(vdata[tindicies[i][1]][0]);
		tvec[1].y = f1616_FromFloat(vdata[tindicies[i][1]][1]);
		tvec[1].z = f1616_FromFloat(vdata[tindicies[i][1]][2]);
		tvec[2].x = f1616_FromFloat(vdata[tindicies[i][2]][0]);
		tvec[2].y = f1616_FromFloat(vdata[tindicies[i][2]][1]);
		tvec[2].z = f1616_FromFloat(vdata[tindicies[i][2]][2]);
		subdivide(&tvec[0],&tvec[1],&tvec[2],depth);
	}
	tverts = malloc(sizeof(vec16)*nvert);
}

tmat16 mat1,mat2;

int main(int argc,char **argv)
{
	int i,depth,frames;
	void *framea,*frameb,*framec;
	f1616 rotx,roty,rotz;
	int stop;
	vec16 tvec[4];
	float fps;
	_kernel_swi_regs regs;
	f1616 dist;
	vec16 *ttri[3];
	dist = f1616_FromInt(3);
/*	screen_oldmode(28);*/
	screen_rpcmode(640,480,SCRN_16B);
	screen_createbanks(2);
	screen_setvdu(1);
	screen_setdisplay(2);
/*	screen_setdisplay(1);*/
	screen_getdata();
	span_lines = screen.height+1;
	span_width = screen.width+1;
	span_ps = 1;
	frames=0;
	depth=0;
	framea=frameb=framec=0;
	rotx=roty=rotz=0;
/*	rotx=0x00ce407e;
	roty=0x00672038;
	rotz=0x00339014;*/
	stop=0;
	setuptris(depth);
	do {
/*		fprintf(stderr,"%08x %08x %08x\n",rotx,roty,rotz);*/
		timer_start(0);
	/*	screen_vsync();*/
		screen_swap();
		screen_getdata();
/*		_kernel_swi(0x100+12,&regs,&regs);*/ /* Clear screen */
		_kernel_swi(0x100+31,&regs,&regs); /* Reposition cursor */
		_kernel_swi(0x100,&regs,&regs);
		_kernel_swi(0x100,&regs,&regs);
		span_buf = (void *) screen.vdu;
		span_init(0);
		/* Set up matrix */
		tmat16_ident(&mat1);
		tvec[0].x = tvec[0].y = tvec[0].z = 0;
		tmat16_translate(&mat1,&mat2,&tvec[0]);
		tmat16_rotx(&mat2,&mat1,rotx);
		tmat16_roty(&mat1,&mat2,roty);
		tmat16_rotz(&mat2,&mat1,rotz);
		tvec[0].x = tvec[0].y = 0;
		tvec[0].z = dist;
		tmat16_translate(&mat1,&mat2,&tvec[0]);
		tvec[0].x = tvec[0].z = 0x10000;
		tvec[0].y = f1616_div(f1616_FromInt(span_width),f1616_FromInt(span_lines));
		tmat16_scale(&mat2,&mat1,&tvec[0]); /* Account for aspect ratio */
		for (i=0;i<nvert;i++)
			tmat16_dotprod(&mat1,&verts[i],&tverts[i]);
		for (i=0;i<ntri;i++)
		{
			ttri[0] = &tverts[tris[i].v1];
			ttri[1] = &tverts[tris[i].v2];
			ttri[2] = &tverts[tris[i].v3];
			span_addpoly(3,ttri,tris[i].col,0);
		}
	/*	span_do2(HorzLine16);*/
		framea = span_do3(gp_screen_hline1,framea);
		/* Swap a and b */
		framec = frameb;
		frameb = framea;
		framea = framec;
		span_free(0);
		timer_stop(0);
		frames++;
		fps=(float) (100*frames)/(timer_report(0) ? : 1);
		printf("frames %d; average %.2ffps %d verticies, %d polys, %dpps    \n",frames,fps,nvert,ntri,(int) (fps*ntri));
		if (!stop)
		{
			rotx += f1616_FromFloat(50/fps);
			roty += f1616_FromFloat(25/fps);
			rotz += f1616_FromFloat(12.5/fps);
		}
		_kernel_swi(OS_Mouse,&regs,&regs);
		if (regs.r[2] & 4)
			dist -= f1616_FromFloat(6.25/fps);
		if (regs.r[2] & 1)
			dist += f1616_FromFloat(6.25/fps);
		if ((regs.r[2] & 2) && (frames > 10))
		{
			depth = (depth+1) & 3;
			setuptris(depth);
			timer_reset(0);
			frames=0;
		}
	} while (1);
	return 0;
}
