/*----------------------------------------------------------------------
 *			 TMS340 Graphics Library
 *       Copyright (c) 1988-1990 Texas Instruments Incorporated.
 *                         All Rights Reserved
 *----------------------------------------------------------------------
 * Demonstration Program
 *
 *   Test the fill_convex, page_flip and page_busy functions.  Draw a
 *   stack of cubes rotating in 3D space.  A better-quality realtime
 *   animation is achieved by running this demo in a double-buffered
 *   graphics mode.  The demo can be run in a mode with a single page
 *   (or frame buffer), but the image may flicker.  The stack of cubes
 *   continually moves toward or away from the view.  The number of
 *   layers of cubes in the x, y and z dimensions of the stack varies
 *   from time to time.  A special-purpose solution to the hidden
 *   surface problem is used.  The viewer's position is transformed to
 *   cube-relative coordinates; that is, to the space in which the cubes
 *   are aligned with the x, y and z axes.  This space is conceptually
 *   partitioned into an infinite 3D grid of cubes, one of which
 *   contains the viewpoint.  The cubes closest to the viewpoint must be
 *   drawn last according to the painter's algorithm.  The cubes are
 *   drawn in outside-in order, beginning with the most distant.
 *   Fixed-point arithmetic is required in several places.  The notation
 *   FIXn is used in the comments to describe a 32-bit fixed-point value
 *   with an n-bit fraction.
 *----------------------------------------------------------------------
 * Revision History:
 *   03/20/88...Original version written..................Jerry Van Aken
 *   05/15/90...Converted to free-running demo............Jerry Van Aken
 *----------------------------------------------------------------------
 */
#include <gsptypes.h>
#include "colors.h"       /* for color displays */
#include "patterns.h"     /* for black-and-white displays */

/* If MODE not defined at preprocessor command line, set to default. */
#ifndef  MODE
#define  MODE	  0	  /* default graphics mode */
#endif

#define max(x,y) (x>y?x:y)

/* 3D parameters for cubes and viewpoint */
#define NSHFT      5           /* angular increment = 1/2**NSHFT rad */
#define STEPSIZE   (8<<8)      /* viewer movement increment in FIX8 */
#define YFAR       (222*STEPSIZE)  /* most distant cubes position */
#define ONE        0x40000000  /* constant 1.0 in FIX30 format */
#define BIAS       (1<<LOGSIDE+7)
#define SIDE       48          /* length of side of cube */
#define LOGSIDE    6           /* log2(ceil(side of cube)) */
#define MAXCUBES   27	       /* max. number of cubes */
#define NFRAMES    80          /* frame count between cubes changes */

/* Rotation commands */
#define TURN_LEFT   0       /* turn left */
#define TURN_RIGHT  1       /* turn right */
#define TURN_UP     2       /* turn up */
#define TURN_DOWN   3       /* turn down */

typedef struct { short x, y; } POINT;

/* Stack of cubes dimensions and position relative to viewer */
int i, istart, ipos, imin, imax;      /* x indices */
int j, jstart, jpos, jmin, jmax;      /* y indices */
int k, kstart, kpos, kmin, kmax;      /* z indices */

/* Cube database:  Define faces 0 through 5 of cube. */
short cubefaces[6][4] = {
  /*  right       left       back      front       top       bottom  */
    {0,1,3,2}, {6,7,5,4}, {4,5,1,0}, {2,3,7,6}, {0,2,6,4}, {1,5,7,3}
};
short cubecolors[6] = {
  /* right   left    back       front      top         bottom  */
     YELLOW, YELLOW, LIGHT_RED, LIGHT_RED, LIGHT_GRAY, LIGHT_GRAY
};

/* Three orthogonal unit vectors give orientation of stack of cubes. */
typedef struct { long u[3], v[3], w[3]; } UNITVEC;    /* FIX30 */
UNITVEC vec;


/* Viewpoint position in object space */
long ypos;     /* viewer's distance in y from cubes */
long vy;       /* cubes' velocity in y */

/* Display parameters */
static CONFIG cfg;       /* hardware configuration info */
static int hres, vres;   /* horizontal, vertical screen dimensions */
static short drawpage;   /* drawing page number (0 or 1) */


main()
{
    static POINT ptlist[100];        /* perspected 2D coords for face */
    static POINT vert[100];          /* perspected 2D coords for face */
    char seeside;                    /* visibility of cube sides 0..5 */
    short ijk[3];                    /* cube xyz translation indices */
    long xyzcube[3*8];               /* cube xyz coordinates in FIX8 */
    long xyzview[3];                 /* view xyz coordinates in FIX8 */
    int m;                           /* outer loop index */
    int n;                           /* inner loop index */
    int visible;                     /* polygon visibility status */
    long r;                          /* cubes' enclosing radius, FIX8 */
    int cmd;                         /* rotation command */


    /* Initialize graphics environment. */
    if (!set_config(MODE, 1))
	exit(1);		 /* invalid graphics mode number */
    clear_screen(-1);
    get_config(&cfg);
    hres = cfg.mode.disp_hres;	 /* horizontal resolution */
    vres = cfg.mode.disp_vres;	 /* vertical resolution */

    /* Viewpoint initial position coordinates in object space */
    ypos = -YFAR/2;	   /* initial distance in y from cubes */
    vy = STEPSIZE;         /* Initial velocity is in forward dir. */

    /* Initial parameters for stack of cubes */
    vec.u[1] = vec.u[2] = 0;
    vec.u[0] = ONE;         /* initialize unit vector to +x direction */
    vec.v[0] = vec.v[2] = 0;
    vec.v[1] = ONE;         /* initialize unit vector to +y direction */
    vec.w[0] = vec.w[1] = 0;
    vec.w[2] = ONE;         /* initialize unit vector to +z direction */
    imax = jmax = kmax = 1;
    imin = jmin = kmin = 0;

    /*
     * Infinite loop below updates size, position and orientation
     * of 3-dimensional stack of cubes, and draws its image.
     */
    cmd = 15;
    drawpage = 0;
    for ( ; ; ) {
        for (m = NFRAMES ; m != 0; m--) {
	    page_flip(!drawpage, drawpage);
            drawpage ^= 1;

            /* Turn up or turn down? */
            if (cmd & 1 << TURN_UP)
                turn(0, &vec.u[1]);
            else if (cmd & 1 << TURN_DOWN)
                turn(1, &vec.u[1]);

            /* Turn left or turn right? */
            if (cmd & 1 << TURN_LEFT)
                turn(0, &vec.u[0]);
            else if (cmd & 1 << TURN_RIGHT)
                turn(1, &vec.u[0]);

            /* Rotate model cube about its center. */
            cube_coords(xyzcube);

            /* Calculate viewer's xyz position coordinates
             * in cube-relative coordinate space.
             */
            xyzview[0] = ypos * (vec.u[1] >> 20) >> 10;
            xyzview[1] = ypos * (vec.v[1] >> 20) >> 10;
            xyzview[2] = ypos * (vec.w[1] >> 20) >> 10;

            /* Is viewer's position in x to left or right of cubes? */
            ipos = xyzview[0] + BIAS >> LOGSIDE+8;
            if (ipos <= imin) {
                ipos = imin;
                istart = imax;
            } else {
                istart = imin;
                if (ipos > imax)
                    ipos = imax;
            }

            /* Is viewer's position in y in front of or behind cubes? */
            jpos = xyzview[1] + BIAS >> LOGSIDE+8;
            if (jpos <= jmin) {
                jpos = jmin;
                jstart = jmax;
            } else {
                jstart = jmin;
                if (jpos > jmax)
                    jpos = jmax;
            }

            /* Is viewer's position in z above or below cubes? */
            kpos = xyzview[2] + BIAS >> LOGSIDE+8;
            if (kpos <= kmin) {
                kpos = kmin;
                kstart = kmax;
            } else {
                kstart = kmin;
                if (kpos > kmax)
                    kpos = kmax;
            }

           /* Check cubes' position against "near" and "far" limits. */
            ypos += vy;
            r = 3*((max(max(imax, jmax), kmax) << LOGSIDE) + SIDE)/2;
	    if (r >= (-ypos>>8) || ypos < -YFAR) {
		ypos -= 2*vy;
                vy = -vy;                  /* reverse direction */
		/* Randomly alter rotation of stack of cubes. */
		if (!(cmd = rand() & 15))
		    cmd = 1;
            }

            /* Wait until drawing page is no longer being displayed. */
	    while (page_busy())
                ;

            /* Draw earth, sky and horizon. */
	    if (cfg.mode.disp_psize >= 4) {   /* color display */
		set_fcolor(BLUE);
		fill_rect(hres, vres/2, 0, 0);
		set_fcolor(GREEN);
		fill_rect(hres, vres/2, 0, vres/2);
	    } else {
		current_patn.data = (PTR)&patnbits[17];
		set_patn(&current_patn);
		set_colors(2, 3);
		patnfill_rect(hres, vres/2, 0, 0);
		set_colors(3, 2);
		patnfill_rect(hres, vres/2, 0, vres/2);
		current_patn.data = (PTR)&patnbits[18];
		set_patn(&current_patn);
	    }

            /*
             * Painter's algorithm:  Draw cubes in outside-in order
             * about the viewer's position in cube-relative xyz space.
             */
            seeside = 0;
            for (i = istart; ; ) {
                ijk[0] = i;
                seeside &= ~0x03;
                if (xyzview[0] > (i << LOGSIDE+8)+(SIDE<<7))
                    seeside |= 1;
                if (xyzview[0] < (i << LOGSIDE+8)-(SIDE<<7))
                    seeside |= 2;
                for (j = jstart; ; ) {
                    ijk[1] = j;
                    seeside &= ~0x0C;
                    if (xyzview[1] > (j << LOGSIDE+8)+(SIDE<<7))
                        seeside |= 4;
                    if (xyzview[1] < (j << LOGSIDE+8)-(SIDE<<7))
                        seeside |= 8;
                    for (k = kstart; ; ) {
                        ijk[2] = k;
                        seeside &= ~0x30;
                        if (xyzview[2] > (k << LOGSIDE+8)+(SIDE<<7))
                            seeside |= 16;
                        if (xyzview[2] < (k << LOGSIDE+8)-(SIDE<<7))
                            seeside |= 32;
                        /* Transform, perspect and draw next cube. */
                        xfrmcube(xyzcube, ptlist, -ypos, ijk, &vec);
                        for (n = 0; n <= 5; ++n) {    /* 6 faces */
                            if ((seeside & (1<<n)) == 0)
                                continue;
                            vert[0] = ptlist[cubefaces[n][0]];
                            vert[1] = ptlist[cubefaces[n][1]];
                            vert[2] = ptlist[cubefaces[n][2]];
                            vert[3] = ptlist[cubefaces[n][3]];
			    if (cfg.mode.disp_psize >= 4) {  /* color */
				set_fcolor(cubecolors[n]);
				fill_convex(4, vert);
			    } else {			     /* B&W */
				set_colors(n/2, ~n/2);
				if (n & 4)  /* pattern-fill 2 faces */
				    patnfill_convex(4, vert);
				else	    /* solid-fill 4 faces */
				    fill_convex(4, vert);
			    }
                        }
                        /* Update z index into stack of cubes. */
                        if (k > kpos)
                            --k;
                        else if (k == kpos)
                            break;
                        else if (++k == kpos)
                            k = kmax;
                    }
                    /* Update y index into stack of cubes. */
                    if (j > jpos)
                        --j;
                    else if (j == jpos)
                        break;
                    else if (++j == jpos)
                        j = jmax;
                }
                /* Update x index into stack of cubes. */
                if (i > ipos)
                    --i;
                else if (i == ipos)
                    break;
                else if (++i == ipos)
                    i = imax;
            }
        }

        /* Randomly alter thickness of stack of cubes in x, y and z. */
	if (ypos < -YFAR/2)
	    switch (rand() & 7) {
	    case 0:
	    case 1:    /* Add layer of cubes in x dimension. */
	       if (MAXCUBES >= (imax-imin+2)*(jmax-jmin+1)*(kmax-kmin+1))
		{
		    if (imax == -imin)
			++imax;
		    else
			--imin;
		}
		break;

	    case 2:    /* Subtract layer of cubes in x dimension. */
		if (imin < imax) {
		    if (imax == -imin)
			++imin;
		    else
			--imax;
		}
		break;

	    case 3:    /* Add layer of cubes in y dimension. */
	       if (MAXCUBES >= (imax-imin+1)*(jmax-jmin+2)*(kmax-kmin+1))
		{
		    if (jmax == -jmin)
			++jmax;
		    else
			--jmin;
		}
		break;

	    case 4:    /* Subtract layer of cubes in y dimension. */
		if (jmin < jmax) {
		    if (jmax == -jmin)
			++jmin;
		    else
			--jmax;
		}
		break;

	    case 5:    /* Add layer of cubes in z dimension. */
	       if (MAXCUBES >= (imax-imin+1)*(jmax-jmin+1)*(kmax-kmin+2))
		{
		    if (kmax == -kmin)
			++kmax;
		    else
			--kmin;
		}
		break;

	    case 6:    /* Subtract layer of cubes in z dimension. */
		if (kmin < kmax) {
		    if (kmax == -kmin)
			++kmin;
		    else
			--kmax;
		}
		break;

	    default:
		if (imax - imin > 3) {
		    if (imax == -imin)
			++imin;
		    else
			--imax;
		} else if (jmax - jmin > 3) {
		    if (jmax == -jmin)
			++jmin;
		    else
			--jmax;
		} else if (kmax - kmin > 3) {
		    if (kmax == -kmin)
			++kmin;
		    else
			--kmax;
		}
		break;
	    }

    }
}


/*----------------------------------------------------------------------
 * Calculate coordinates for the 8 vertices of a cube rotated about its
 * center.  The cube's orientation is expressed by unit vectors u, v, w.
 *----------------------------------------------------------------------
 */

cube_coords(p)
long *p;
{
    int i, j, k, n;
    long u[3], v[3], w[3], *q1, *q2, *q3;

    for (i = 0, q1 = u, q2 = v, q3 = w; i <= 2; ++i) {
        *q1++ = (SIDE/2)*(vec.u[i] >> 10) >> 12;
        *q2++ = (SIDE/2)*(vec.v[i] >> 10) >> 12;
        *q3++ = (SIDE/2)*(vec.w[i] >> 10) >> 12;
    }

    for (i = 0; i <= 1; ++i) {
        for (j = 0; j <= 1; ++j) {
            for (k = 0; k <= 1; ++k) {
                q1 = u;
                q2 = v;
                q3 = w;
                for (n = 0; n <= 2; ++n) {
                    *p++ = (*q1++) + (*q2++) + (*q3);
                    *q3++ = -(*q3);
                }
            }
            for (k = 0, q2 = v; k <= 2; ++k)
                *q2++ = -(*q2);
        }
        for (j = 0, q1 = u; j <= 2; ++j)
            *q1++ = -(*q1);
    }
}


/*----------------------------------------------------------------------
 *  Translate and perspect a cube in 3D.  The cube has been previously
 *  rotated about its center.  This function translates the cube to its
 *  proper position in the stack of cubes, and projects it onto the
 *  screen with perspective.  Input array xyzin contains the xyz
 *  coordinates of the 8 vertices in 3D space; each coordinate is in
 *  FIX8 format.  Output array xyout contains the integer xy coordinates
 *  of the 8 vertices projected onto the 2D surface of the display.
 *----------------------------------------------------------------------
 */
xfrmcube(xyzin, xyout, dist, ijk, uvec)
long *xyzin;    /* list of FIX8 input coordinates in 3D space */
short *xyout;   /* projection of points onto 2D surface */
long dist;      /* viewer's distance from screen for perspective */
short ijk[3];   /* xyz translation indices from center of rotation */
UNITVEC *uvec;  /* pointer to orthogonal unit vectors u, v and w */
{
    int n;
    long x, y, z, dx, dy, dz;

    dx =   ijk[0]*(uvec->u[0]>>22-LOGSIDE)
         + ijk[1]*(uvec->v[0]>>22-LOGSIDE)
         + ijk[2]*(uvec->w[0]>>22-LOGSIDE);

    dy = dist
         + ijk[0]*(uvec->u[1]>>22-LOGSIDE)
         + ijk[1]*(uvec->v[1]>>22-LOGSIDE)
         + ijk[2]*(uvec->w[1]>>22-LOGSIDE);

    dz =   ijk[0]*(uvec->u[2]>>22-LOGSIDE)
         + ijk[1]*(uvec->v[2]>>22-LOGSIDE)
         + ijk[2]*(uvec->w[2]>>22-LOGSIDE);

    for (n = 8; n > 0; --n) {
        x =   *xyzin++ + dx;
        z =   *xyzin++ + dy;        /* FIX8 */
        y = -(*xyzin++ + dz);
        *xyout++ = (x*hres)/z + hres/2;     /* integer */
        *xyout++ = (y*hres)/z + vres/2;
    }
}


/*----------------------------------------------------------------------
 *  Rotate vector in "xy" plane:  vec[0] = x, vec[1] = y in FIX30 format
 *----------------------------------------------------------------------
 */
turn(dir, vec)
int dir;          /* 0 = clockwise, 1 = counterclockwise */
long *vec;        /* 3 unit vectors in FIX30 format */
{
    int i;
    long x1, y1, x2, y2, w;   /* FIX30 format */

    for (i = 1; i <= 3; ++i) {
        if (dir) {
            x2 = -(y1 = vec[1]);
            y2 =  (x1 = vec[0]);
        } else {
            x2 =  (y1 = vec[1]);
            y2 = -(x1 = vec[0]);
        }
        x2 -= (w = x2 >> 2*NSHFT+3);
        x2 -= (w >>= 2*NSHFT+4);
        x2 -= w >> 2*NSHFT+3;
        x2 -= x1 >> NSHFT+1;

        y2 -= (w = y2 >> 2*NSHFT+3);
        y2 -= (w >>= 2*NSHFT+4);
        y2 -= w >> 2*NSHFT+3;
        y2 -= y1 >> NSHFT+1;

        x1 += x2 >> NSHFT;
        x2 -= x1 >> NSHFT;
        y1 += y2 >> NSHFT;
        y2 -= y1 >> NSHFT;
        vec[0] = x1;
        vec[1] = y1;
        vec = &vec[3];
    }
}

