/* Compile:
   gcc -o cubbi-cube cubbi-cube.c -lvgagl -lvga
Exit: Ctrl-C

   (c) 1999 Sergey Zubkov
 */
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <vga.h>
#include <vgagl.h>

/* the mode I will use */
#define VGAMODE G320x200x256
/* try this one for 640x480 */
// #define VGAMODE G640x480x256
/* note that the cube rotates slower in high-res not because of lower FPS, but
 * because the rotation is done by frame change, not by time. If you want to
 * run it faster in 640x480 - increase a,b,c in rotatecube() or calculate them
 * using current time */

/* fps-related variables */
int framecount;
clock_t frameclock;
float framerate;

/* we need two contexts - one for actual output */
GraphicsContext *physicalscreen;
/* and one to draw our lines and letters in */
GraphicsContext *backscreen;
/* If you want to combine several layers in your picture, like background and
 * foreground - you will need more contexts */

/* Not really *cube* - just 8 3D-points */
struct {
	struct {float x; float y; float z;} c1;
	struct {float x; float y; float z;} c2;
	struct {float x; float y; float z;} c3;
	struct {float x; float y; float z;} c4;
	struct {float x; float y; float z;} c5;
	struct {float x; float y; float z;} c6;
	struct {float x; float y; float z;} c7;
	struct {float x; float y; float z;} c8;
} cube;

/* draw the cube, X-Y projection */
void drawcube(void)
{
	/* actually here I just draw 12 lines connecting our 8 dots, with a
	 * WIDTH/2,HEIGHT/2 bias to put them closer to the center of the screen
	 */
	gl_line(WIDTH/2+cube.c1.x, HEIGHT/2+cube.c1.y,
			WIDTH/2+cube.c2.x, HEIGHT/2+cube.c2.y, 1);
	gl_line(WIDTH/2+cube.c2.x, HEIGHT/2+cube.c2.y,
			WIDTH/2+cube.c3.x, HEIGHT/2+cube.c3.y, 1);
	gl_line(WIDTH/2+cube.c3.x, HEIGHT/2+cube.c3.y,
			WIDTH/2+cube.c4.x, HEIGHT/2+cube.c4.y, 1);
	gl_line(WIDTH/2+cube.c4.x, HEIGHT/2+cube.c4.y,
			WIDTH/2+cube.c1.x, HEIGHT/2+cube.c1.y, 1);
	gl_line(WIDTH/2+cube.c1.x, HEIGHT/2+cube.c1.y,
			WIDTH/2+cube.c7.x, HEIGHT/2+cube.c7.y, 2);
	gl_line(WIDTH/2+cube.c2.x, HEIGHT/2+cube.c2.y,
			WIDTH/2+cube.c8.x, HEIGHT/2+cube.c8.y, 2);
	gl_line(WIDTH/2+cube.c3.x, HEIGHT/2+cube.c3.y,
			WIDTH/2+cube.c5.x, HEIGHT/2+cube.c5.y, 2);
	gl_line(WIDTH/2+cube.c4.x, HEIGHT/2+cube.c4.y,
			WIDTH/2+cube.c6.x, HEIGHT/2+cube.c6.y, 2);
	gl_line(WIDTH/2+cube.c5.x, HEIGHT/2+cube.c5.y,
			WIDTH/2+cube.c6.x, HEIGHT/2+cube.c6.y, 3);
	gl_line(WIDTH/2+cube.c6.x, HEIGHT/2+cube.c6.y,
			WIDTH/2+cube.c7.x, HEIGHT/2+cube.c7.y, 3);
	gl_line(WIDTH/2+cube.c7.x, HEIGHT/2+cube.c7.y,
			WIDTH/2+cube.c8.x, HEIGHT/2+cube.c8.y, 3);
	gl_line(WIDTH/2+cube.c8.x, HEIGHT/2+cube.c8.y,
			WIDTH/2+cube.c5.x, HEIGHT/2+cube.c5.y, 3);
}
/* rotate one 3D point */
void rotatedot(float *x,float *y,float *z,float a,float b,float c)
{
	float tmp_x,tmp_y,tmp_z; /* stupid imperative C */
	tmp_x=*x; tmp_y=*y; tmp_z=*z;
/* This is NOT Euler 3D rotation transform - this is the version of it
   used in air and space: a is the Tilt angle, b is the Tangage angle,
   c is the Slide angle (not sure about english names for those words)
   But you can take a math textbook and use Euler just as well.
 */
	*x=cos(b)*cos(c)*tmp_x
		-cos(b)*sin(c)*tmp_y
		+sin(b)*tmp_z;
	*y=(sin(a)*sin(b)*sin(c)+cos(a)*sin(c))*tmp_x
		+(-sin(a)*sin(b)*sin(c)+cos(a)*cos(c))*tmp_y
		-sin(a)*cos(b)*tmp_z;
	*z=(-cos(a)*sin(b)*cos(c)+sin(a)*sin(c))*tmp_x
		+(cos(a)*sin(b)*sin(c)+sin(a)*cos(c))*tmp_y
		+cos(a)*cos(b)*tmp_z;
}
/* rotate the cube */
void rotatecube(void)
{
	float a,b,c; /* rotation angles */
	/* every frame the cube is rotated by : */
	a=0.007;  /* 0.007 radian around x axis */
	b=0.003;  /* 0.003 radian around y axis */
	c=0.005;  /* 0.005 radian around z axis */
	/* CHANGE these to change the speed of rotation as well as the
	 * directions. It would be fun to make each of these angular speeds
	 * change with time, getting faster and slower. */

	/* apply rotation rules to every vertix separately */
	rotatedot(&cube.c1.x,&cube.c1.y,&cube.c1.z,a,b,c);
	rotatedot(&cube.c2.x,&cube.c2.y,&cube.c2.z,a,b,c);
	rotatedot(&cube.c3.x,&cube.c3.y,&cube.c3.z,a,b,c);
	rotatedot(&cube.c4.x,&cube.c4.y,&cube.c4.z,a,b,c);
	rotatedot(&cube.c5.x,&cube.c5.y,&cube.c5.z,a,b,c);
	rotatedot(&cube.c6.x,&cube.c6.y,&cube.c6.z,a,b,c);
	rotatedot(&cube.c7.x,&cube.c7.y,&cube.c7.z,a,b,c);
	rotatedot(&cube.c8.x,&cube.c8.y,&cube.c8.z,a,b,c);
}

/* create and draw next frame on the screen */
void drawscreen(void)
{
	int i;
	char s[50];
	/* From this command all gl_* functions will draw the objects in
	 * backscreen. */
	gl_setcontext(backscreen);

	/* clear frame */
	gl_clearscreen(0);
	/* change the cube's coordinates by rotation */
	rotatecube();
	/* output the current cube into our "backscreen" buffer */
	drawcube();

	/* Output current frame and last second's FPS into backscreen */
	sprintf(s, "Frame: %3d FPS: %g",framecount,framerate);
	gl_setwritemode(WRITEMODE_OVERWRITE);
	gl_write(0, HEIGHT-8, s);

	/* Copy backscreen to physical screen (this is actual output!) */
	gl_copyscreen(physicalscreen);
}
int main(void)
{
	void *font;
	/* init and switch to graphics */
	vga_init();
	vga_setmode(VGAMODE);

	/* set up the screen buffer */
	gl_setcontextvga(VGAMODE);
	physicalscreen = gl_allocatecontext();
	gl_getcontext(physicalscreen);

	/* allocate and set up font for display of frame rate */
	font = (void*)malloc(256 * 8 * 8 * BYTESPERPIXEL);
	gl_expandfont(8, 8, 1, gl_font8x8, font);
	gl_setfont(8, 8, font);

	/* Initial cube position and size is set here */
	/* It doesn't really *have* to be a cube - it's simply 8 dots in space,
	 * connected with lines - try changing any of the following coordinates
	 * at will. Remember that rotation will be aroung the 0,0,0 dot in
	 * these coordinates (try setting all positive) */
	cube.c1.x=cube.c1.y=cube.c1.z=-10;
	cube.c2.y=cube.c2.z=-10; cube.c2.x=20;
	cube.c3.x=cube.c3.y=20; cube.c3.z=-10;
	cube.c4.x=cube.c4.z=-10; cube.c4.y=20;
	cube.c5.x=cube.c5.y=cube.c5.z=20;
	cube.c6.y=cube.c6.z=20; cube.c6.x=-10;
	cube.c7.x=cube.c7.y=-10; cube.c7.z=20;
	cube.c8.x=cube.c8.z=20; cube.c8.y=-10;

	/* set up the virtual buffer for drawing in */
	gl_setcontextvgavirtual(VGAMODE);
	backscreen = gl_allocatecontext();
	gl_getcontext(backscreen);

	/* main display loop */
	framerate = 0;
	framecount = 0;
	frameclock = clock();
	for (;;) {
		/* draw next frame */
		drawscreen();
		/* update frame counter */
		framecount++;
		/* if a second passed - update frame rate (see man clock) */
		if (clock()-frameclock >= CLOCKS_PER_SEC) {
			framerate = (float)framecount*CLOCKS_PER_SEC
				/ (float)(clock()-frameclock);
			framecount = 0; frameclock = clock();
		}
	}
	/* exit (never reached in this program) */
	vga_setmode(TEXT);
	return 0;
}

