/*

    3DKIT   version   1.3
    High speed 3D graphics and rendering library for Linux.

    1996  Paul Sheer   psheer@icon.co.za

    This file is an example program demonstrating the use of the
    3dkit library. It is not part of the library and is not copyright.

    The author takes no responsibility, for the results
    of compilation, execution or other usage of this program.

*/


/*
File: planukit.c

comments or suggestions welcome, send to:  psheer@icon.co.za

Demo of 3D graphics tool for drawing shaded 3D surfaces with a light source.
This demo sets up the surfaces (in a crude fashion) and the function
drawobject from 3dkit.c draws them at the specified angle of azimuth,
rotation and elevation. The surfaces are sorted from furthest to closest
and drawn from their furthest corner forward toward their second furthest
corner. So any object made up of reasonable surfaces will be drawn solid
with hidden surfaces properly removed. Backfaced triangles are not drawn
to improve speed.

This demo draws a turbo-prop aeroplane (done originally for a 3rd year
aeronautical engineering design project).

see the handle_key function below for what all the keys do.

*/

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>		/*for stderr */
#include <math.h>
#include <string.h>

#include <vga.h>
#include <vgagl.h>
#include "3dkit.h"
#include "3dinit.h"
#include "plane.h"

#ifdef WORLD_VIEW

#define PL_TDOPTION_ROTATE_OBJECT 0
#define PL_TDOPTION_LIGHT_SOURCE_CAM 0

#else

#define PL_TDOPTION_ROTATE_OBJECT TDOPTION_ROTATE_OBJECT

	/*Lighting vector follows camera: */
#define PL_TDOPTION_LIGHT_SOURCE_CAM TDOPTION_LIGHT_SOURCE_CAM

#endif


/*closer to 1.25 on my screen: */
#define PL_SCREEN_ASPECT 1.333

/*Number of surfaces in ths plane */
#ifdef WORLD_VIEW
#define PL_NUMSURFACES 62
#else
#define PL_NUMSURFACES 52
/*52 */
#endif

/*maximum width or length of a surface (for malloc) */
#define PL_SURF_SIZE 20



/*globals used for initialisation of surfaces */

/*width and breadth of body surfaces (in grid points) */
int DENS = 2;

/*width of wing surfaces (in grid points). */
int DENS2 = 2;

/* length of wing surfaces is inherent in the following 
   made-up aerofoil: */


int gmode;
int PL_screen_width;
int PL_screen_height;


/*A trivial example of how to initialise a surface: */
void initplate (TD_Surface * surf, float xstart, float ystart, float zstart, float x, float y, int w, int l)
{
    int i, k, j;

/*setup width and length */
    surf->w = w + 1;
    surf->l = l + 1;

/*initialise a 6 meter square plate with its centre at the origin */
    for (k = 0; k < w + 1; k++)
	for (i = 0; i < l + 1; i++) {
	    j = l - i;
	    surf->point[i * (w + 1) + k].x = (float) PL_METER *(xstart + (float) x * k / w);
	    surf->point[i * (w + 1) + k].y = (float) PL_METER *(ystart + (float) y * j / l);
	    surf->point[i * (w + 1) + k].z = (float) PL_METER *zstart;
	}
}


/*exchanges the x and y values of a surface, making y negative */
/* This is a patch to get the coords aligned with flight-dynamic's
   axes. */
void xchgxy (TD_Surface * surf)
{
    int j;
    int t;

    for (j = 0; j < surf->l * surf->w; j++) {
	t = surf->point[j].x;
	surf->point[j].x = surf->point[j].y;
	surf->point[j].y = -t;
    }
}

/*
void gl_triangle (int x1, int y1, int z1, int x2, int y2, int z2,
		  int x3, int y3, int z3, int bf);
void gl_striangle (int x1, int y1, int x2, int y2, int x3, int y3,
		   int c, int bf);
*/

/*returns 0 on error */
TD_Solid *PL_init_solid (void)
{
    TD_Solid *plane_demo;
    int i;
    int n = PL_NUMSURFACES;

    if ((plane_demo = malloc (sizeof (TD_Solid))) == NULL)
	return 0;
    memset (plane_demo, 0, sizeof (TD_Solid));

    plane_demo->num_surfaces = n;

    if ((plane_demo->surf = calloc (n , sizeof (TD_Surface))) == NULL)
	return 0;

    for (i = 0; i < n; i++) {
	if ((plane_demo->surf[i].point
	     = malloc (PL_SURF_SIZE * PL_SURF_SIZE * sizeof (TD_Point))) == NULL)
	    return 0;
	/*      plane_demo->surf[i].render = TD_MESH_AND_SOLID; *//*can leave out and set option ALL_SAME_RENDER */
	plane_demo->surf[i].shadow = TD_DEFAULT_COLOR + TD_DEFAULT_SHADOW;
	plane_demo->surf[i].maxcolor = TD_DEFAULT_COLOR + TD_DEFAULT_MAXCOLOR;
	plane_demo->surf[i].mesh_color = 191;	/*navy blue in from the palette set */
	plane_demo->surf[i].backfacing = 1;	/*don't draw any of surface that faces away */
	plane_demo->surf[i].depth_per_color = 6;	/*2^6 = 64 colors in the grey scale */
    }

    plane_demo->alpha = 0;	/* begin all at zero (flight dynamics */
    plane_demo->beta = 0;	/* says plane is level */
    plane_demo->gamma = 0;

    plane_demo->xlight = -147;	/* lighting out of the screen,... */
    plane_demo->ylight = -147;	/* ...to the right,... */
    plane_demo->zlight = 147;	/* ...and from the top. */

    plane_demo->distance = PL_METER * 35;	/* distance of the camera from the */
    /* origin, PL_METER * meters. */

/*if PL_TDOPTION_ROTATE_OBJECT is set to zero then we need to
   define the full camera position instead: */
    plane_demo->x_cam = PL_METER * 35;
    plane_demo->y_cam = PL_METER * 0;
    plane_demo->z_cam = PL_METER * 0;

/* These two are scale factors for the screen: */
/* xscale is now calculated so that the maximum volume (-2^15 to 2^15 or
   -2^31 to 2^31) will just fit inside the screen width at this distance: */
    plane_demo->xscale = (int) plane_demo->distance * PL_screen_width / (32768 * 2);
    plane_demo->yscale = (float) plane_demo->xscale * PL_SCREEN_ASPECT
	* PL_screen_height / PL_screen_width;	/*to get display aspect square */

/*The above gives an average (not to telescopic, and not to wide angle) view */

/*use any triangle or linedrawing routine: */
    plane_demo->draw_triangle = gl_triangle;
    plane_demo->draw_striangle = gl_striangle;
    plane_demo->draw_line = gl_line;

/* very important to set TDOPTION_INIT_ROTATION_MATRIX if you don't
   calculate the rotation matrix yourself. */

    plane_demo->option_flags = TDOPTION_INIT_ROTATION_MATRIX
	| TDOPTION_ALL_SAME_RENDER | TDOPTION_SORT_SURFACES
	| PL_TDOPTION_ROTATE_OBJECT | PL_TDOPTION_LIGHT_SOURCE_CAM;

    plane_demo->render = TD_MESH_AND_SOLID;	/*how we want to render it */

    return plane_demo;
}

void PL_init_surfaces (TD_Solid * plane)
{
    int i;

/* To see what an example of the ellipsoid initialisation: */
/*
   TD_initsellipsoid (plane, 0, 0, 0, 0, 
   PL_METER * 8, PL_METER * 4, PL_METER * 4, 3);
   for(i=0;i<6;i++)
   TD_initcolor (&plane->surf[i], -256); 
   return;
 */

    for (i = 0; i < 4; i++) {
	initfus (&plane->surf[i], i * TD_PI / 2);
    }

    for (i = 0; i < 4; i++) {
	initfus1 (&plane->surf[i + 4], i * TD_PI / 2);
    }

    for (i = 0; i < 4; i++) {
	initfus2 (&plane->surf[i + 8], i * TD_PI / 2);
    }

    initwing (&plane->surf[12], 1, 1, 0);
    initwing (&plane->surf[13], -1, 1, 0);
    initwing (&plane->surf[14], 1, -1, 0);
    initwing (&plane->surf[15], -1, -1, 0);
    initwing (&plane->surf[16], 1, 1, 1);
    initwing (&plane->surf[17], -1, 1, 1);
    initwing (&plane->surf[18], 1, -1, 1);
    initwing (&plane->surf[19], -1, -1, 1);
    initwing (&plane->surf[20], 1, 1, 2);
    initwing (&plane->surf[21], -1, 1, 2);
    initwing (&plane->surf[22], 1, -1, 2);
    initwing (&plane->surf[23], -1, -1, 2);
    initwing (&plane->surf[24], 1, 1, 3);
    initwing (&plane->surf[25], -1, 1, 3);
    initwing (&plane->surf[26], 1, -1, 3);
    initwing (&plane->surf[27], -1, -1, 3);
    initstab (&plane->surf[28], 1, 1);
    initstab (&plane->surf[29], -1, 1);
    initstab (&plane->surf[30], 1, -1);
    initstab (&plane->surf[31], -1, -1);
    initfin (&plane->surf[32], 1);
    initfin (&plane->surf[33], -1);

    for (i = 0; i < 4; i++) {
	initfus3 (&plane->surf[i + 34], i * TD_PI / 2);
    }

    for (i = 0; i < 4; i++) {
	initnacelle (&plane->surf[i + 38], i * TD_PI / 2, -1);
    }

    for (i = 0; i < 4; i++) {
	initnacelle (&plane->surf[i + 42], i * TD_PI / 2, 1);
    }

    for (i = 0; i < 2; i++) {
	initnacelle2 (&plane->surf[i + 46], i * TD_PI / 2, -1);
    }

    for (i = 0; i < 2; i++) {
	initnacelle2 (&plane->surf[i + 48], i * TD_PI / 2, 1);
    }

    inittips (&plane->surf[50], 1);
    inittips (&plane->surf[51], -1);

#ifdef WORLD_VIEW
    for (i = 0; i < 10; i++)
	initplate (&plane->surf[i + 52], -20 + (float) i * 4.44, -20, -3.5, 0.4, 40, 1, 10);
#endif

    for (i = 0; i < PL_NUMSURFACES; i++) {
	xchgxy (&plane->surf[i]);

	TD_initcolor (&plane->surf[i], -256);
	/*initialises the color vector (vector normal to each point) */
    }
}


/*returns 1 on error */

int PL_init_plane (TD_Solid ** plane)
{
    if (!(*plane = PL_init_solid ()))
	return 1;
    PL_init_surfaces (*plane);
    return 0;
}


void PL_init_palette (void)
{
/*  Here the depth_per_color is 5 (for 64 colors).
   256 / 64 gives 4 colors so TD_Surface->color 
   can be 0, 64, 128, OR 192 */

    int i;
    unsigned char palette[768];

    for (i = 0; i < 64; i++) {
	palette[i * 3] = i;
	palette[i * 3 + 1] = i;
	palette[i * 3 + 2] = 16 + i / 2;
    }

    for (i = 0; i < 64; i++) {
	palette[(i + 64) * 3 + 0] = i;
	palette[(i + 64) * 3 + 1] = 0;
	palette[(i + 64) * 3 + 2] = 0;
    }

    for (i = 0; i < 64; i++) {
	palette[(i + 128) * 3 + 0] = 0;
	palette[(i + 128) * 3 + 1] = i;
	palette[(i + 128) * 3 + 2] = 0;
    }

    for (i = 0; i < 64; i++) {
	palette[(i + 192) * 3 + 0] = 0;
	palette[(i + 192) * 3 + 1] = 0;
	palette[(i + 192) * 3 + 2] = i;
    }

    gl_setpalette (&palette);
}




/*returns 1 if exit key is pressed */
int PL_handle_key (TD_Solid * plane)
{
    static float incr = 0.1047198;
    int finished = 0;
    int c;

/*plane->gamma += incr;
   plane->beta = -0.5;
   plane->render = TD_SOLID; 
   return 0; *//*---> a screen saver*/


    switch (c = getchar ()) {
    case 'q':
	plane->alpha += incr;
	break;
    case 'a':
	plane->alpha -= incr;
	break;
    case 'o':
	plane->beta += incr;
	break;
    case 'p':
	plane->beta -= incr;
	break;
    case 'z':
	plane->gamma += incr;
	break;
    case 'x':
	plane->gamma -= incr;
	break;
    case 't':
	plane->z_cam += PL_METER;
	break;
    case 'v':
	plane->z_cam -= PL_METER;
	break;
    case 'g':
	plane->x_cam += PL_METER;
	break;
    case 'f':
	plane->x_cam -= PL_METER;
	break;
    case 'w':
	plane->distance += PL_METER;
	plane->y_cam += PL_METER;
	break;
    case 's':
	plane->distance -= PL_METER;
	plane->y_cam -= PL_METER;
	break;
    case 'c':
	finished = 1;
	break;
    case 'i':
	plane->gamma = 0;
	plane->alpha = 0;
	plane->beta = 0;
	break;
    case ' ':
	switch (plane->render) {
	case TD_MESH:
	    plane->render = TD_MESH_AND_SOLID;
	    break;
	case TD_MESH_AND_SOLID:
	    plane->render = TD_SOLID;
	    break;
	case TD_SOLID:
	    plane->render = TD_EDGES_ONLY;
	    break;
	case TD_EDGES_ONLY:
	    plane->render = TD_MESH;
	    break;
	}
	break;
    case 'r':
	if (plane->option_flags & TDOPTION_FLAT_TRIANGLE)
	    plane->option_flags &= 0xFFFFFFFF - TDOPTION_FLAT_TRIANGLE;
	else
	    plane->option_flags |= TDOPTION_FLAT_TRIANGLE;
	break;
    case '1':
	incr += .01047198;
	break;
    case '2':
	incr -= .01047198;
	break;
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
	DENS = c - '2';
	PL_init_surfaces (plane);
    }

    if (incr < 0)
	incr = 0;

    return (finished);
}


/*WRITE-PAGE FLIPPING*/

/*
  The following routines redirect the setpage functions to take advantage
  of the vga memory: writing graphics functions to one half of the memory
  while viewing
  the other. This saves us a copyscreen, while costing the extra
  time it takes to draw to vga memory instead of linear (i.e. the
  virtual screen) memory. vga.c should have the minor modification
  that would allow this to be done more simply. The following was the
  only way that seemed to work without altering vga.c. We will
  call the method "Write-page Flipping", as apposed to "Page Flipping"
  where pages are flipped, but writing is done to a virtual screen
  which is then copied to the vga memory not being viewed. Write-page
  Flipping writes directly to the vga memory not being viewed.

  The method even works on my TVGA8900CL/D in 320x200 (though it's not
  supposed to), and doesn't work in 640x480 (where it is supposed to)
  so I have given both options at startup. Note that Write-page flipping
  can only work on linear or paged modes (320x200, 640x480, 800x600,
  1024x768) since graphics functions to write directly to planar 
  modes are not supported by svgalib.
*/

GraphicsContext physcr, virscr;
int winflipping, vgawindow = 0;
int Startpage[2];
int gmode, chipset;

void PL_redraw (TD_Solid * plane)
{
    gl_clearscreen (64);
    TD_draw_solid (plane);
}


void PL_cleanup (TD_Solid * plane)
{
/*this function should free all allocated memory*/
    return;
}


void winpointto (int win)
{
    if (chipset == TVGA8900 && gmode == G320x200x256) {
	/*trident has 4 bpp in this mode */
	vga_ext_set(VGA_EXT_PAGE_OFFSET, (Startpage[win] * 4) >> 16);
    } else {
	vga_ext_set(VGA_EXT_PAGE_OFFSET, Startpage[win] >> 16);
    }

    vga_setpage (0);
}


void winview (int win)
{
    vga_waitretrace ();
    vga_setdisplaystart (Startpage[win] * win);
}

void winflip (void)
{
    winview (vgawindow);
    vgawindow = 1 - vgawindow;
    winpointto (vgawindow);
}


void PL_animate (TD_Solid * plane, void (*PL_redraw_callback) (TD_Solid *),
	    int (*PL_key_callback) (TD_Solid *))
{
    do {
	PL_redraw_callback (plane);
	if(winflipping) {
	    winflip ();
	} else {
	    gl_setscreenoffset( HEIGHT * WIDTH * currentcontext.flippage );
	    gl_copyscreen (&physcr);
	}
    } while (!(int *) PL_key_callback (plane));
}

int pl_getchar (void)
{
    int c = 0;
    while (c == 0 || c == '\n') {
	c = vga_getkey ();
    }
    if (c >= 'a' && c <= 'z')
	c += 'A' - 'a';
    return c;
}


int main (void)
{
    int mode[7] =
    {5, 6, 7, 8, 10, 11, 12};
    int Winflipping[7] =
    {1, 0, 0, 0, 1, 1, 1};
    int Winflippages[7] =
    {65536, 0, 0, 0, 8 * 65536, 8 * 65536, 16 * 256};
    int c, c2;
    vga_modeinfo *ginfo;
    TD_Solid *plane;

/* Note that in this demo, graphics are written to all modes as
   virtual modes, so that the triangle routine optimisations will
   operate all the time (see triangle.c). */

    vga_init ();
    if (!(vga_ext_set(VGA_EXT_AVAILABLE, VGA_AVAIL_SET) & (1 << VGA_EXT_PAGE_OFFSET))) {
	puts("You need at least svgalib 1.2.10 to run this program!\n");
	exit(1);
    }
    do {
	printf ("\n256 color modes:\n\n1: 320x200\n2: 320x240\n3: 320x400\n");
	printf ("4: 360x480\n5: 640x480\n6: 800x600\n7: 1024x768\n");
	printf ("\nWhich? ");
	c = pl_getchar () - '1';
	printf ("\n");
    } while (c < 0 || c > 6);

    printf("Want (W)rite-page flipping, normal (P)age flipping\n");
    printf("using copyscreen, or (N)o page flipping (W/F/N)\n");
    printf("(W is faster but may not work, N will always work\n");
    printf("but sometimes looks tacky) ?\n");

    c2 = pl_getchar();

    printf ("\n");

    gmode = mode[c];
    winflipping = Winflipping[c];

    if (!vga_hasmode (gmode)) {
	fprintf (stderr, "Mode not available.\n");
	exit (-1);
    }

    vga_setmode (gmode);
    gl_setcontextvga (gmode);

    ginfo = vga_getmodeinfo (gmode);

    PL_screen_width = ginfo->width;
    PL_screen_height = ginfo->height;

    if (PL_init_plane (&plane)) {
	fprintf (stderr, "Unable to intialise data structures.\n");
    }

    plane->posx = PL_screen_width / 2;	/*Where origin will be printed */
    plane->posy = PL_screen_height / 2;

    PL_init_palette ();

/* to see what the palette looks like: */
/* for(i=0;i<256;i++) gl_line(0,i,PL_screen_width,i,i);   getchar(); */


    /* Allow write flipping 
	on 320x200 even though ginfo doesn't report more 
	than 64k of memory:*/
    if ((PL_screen_width * PL_screen_height * 2 > ginfo->maxpixels
		    && gmode != G320x200x256) || c2 != 'W')
	winflipping = 0;

    if (winflipping) {
	printf("Using Write-page Flipping.\n");
	Startpage[0] = 0;  /*define pages offsets into memory*/
	Startpage[1] = Winflippages[c];

	winflip ();
    } else {
	gl_getcontext (&physcr);
	gl_setcontextvgavirtual (gmode);
	gl_getcontext (&virscr);
	if(c2 != 'N') {
	    if(gl_enablepageflipping (&physcr))
		printf("Using Page Flipping.\n"); 
	}
    }

    gl_enableclipping ();
    PL_animate (plane, PL_redraw, PL_handle_key);

    PL_cleanup (plane);
    vga_setmode (TEXT);
    return 0;
}
