/*
 *  Program:    Dodecahedron.java
 *  Purpose:    Solid 3D Objects
 *  Author:     George Aroush
 *  Date:       1/1/1998
 *  Change Log: None
 *
 *  Full description of Program:
 *      Use of "painter's algorithm" to display filled in dodecahedron
 */

import java.applet.*;
import java.awt.*;

public class Dodecahedron extends Applet
{
    final int   NP = 21;                    /* number of points */
    final int   NF = 12;                    /* number of faces */
    final int   MAX_V = 5;                  /* largest number of vertices in a face */
    final int   ARRAYSIZE = (5 + MAX_V);    /* number of values in a face array */
    final int   CX = 320;                   /* center of screen */
    final int   CY = 200;
                                        /* edges of screen */
    final int   MAXX = 640;
    final int   MAXY = 400;
                                        /* index into our 3D array */
    final int   X = 0;
    final int   Y = 1;
    final int   Z = 2;
                                        /* positions (or offsets) in face array */
    final int   QTY = 0;                    /* number of vertices in face */
    final int   COLOR_R = 1;                /* color of face - Red */
    final int   COLOR_G = 2;                /* color of face - Green */
    final int   COLOR_B = 3;                /* color of face - Blue */
    final int   FAR_Z = 4;                  /* most distant vertex in face */
    final int   VERT = 5;                   /* start of vertex numbers */

    public void paint(Graphics g)
    {
            /* coordinates of 20 vertices of dodecahedron */
        float   oCoord[][] = {{-30,  78,  0}, { 30,  78,  0},  {48,  48,  48}, {  0,  30,  78}, 
                              {-48,  48, 48}, { 48,  48, -48}, { 0,  30, -78}, {-48,  48, -48},
                              {-78,   0, 30}, {-78,   0, -30}, {78,   0,  30}, { 78,   0, -30},
                              {-48, -48, 48}, {  0, -30,  78}, {48, -48,  48}, {-48, -48, -48},
                              {-30, -78,  0}, { 48, -48, -48}, { 0, -30, -78}, { 30, -78,   0},
                              {  0,   0,  0}};  /* 21st vertex added for center of dodecahedron */
        
            /* to hold temporary values of vertices */
        float   coord[][] = new float[NP][3];

            /* The twelve faces */
        int     faces[][] = {
                               {5,              /* number of vertices in face */
                                0, 0, 255,      /* color of face */
                                0,			    /* place for farthest z */
                                0, 1, 5, 6, 7}, /* vertices */


                               {5,
                                0, 255, 255,
                                0,
                                1, 0, 4, 3, 2},

                               {5,
                                64, 64, 64,
                                0,
                                16, 15, 18, 17, 19},

                               {5,
                                128, 128, 128,
                                0,
                                16, 19, 14, 13, 12},

                               {5,
                                0, 255, 0,
                                0,
                                9, 8, 4, 0, 7},

                               {5,
                                192, 192, 192,
                                0,
                                10, 11, 5, 1, 2},

                               {5,
                                255, 0, 255,
                                0,
                                8, 9, 15, 16, 12},

                               {5,
                                255, 200, 0,
                                0,
                                11, 10, 14, 19, 17},

                               {5,
                                255, 175, 175,
                                0,
                                18, 6, 5, 11, 17},

                               {5,
                                255, 0, 0,
                                0,
                                7, 6, 18, 15, 9},

                               {5,
                                255, 255, 255,
                                0,
                                3, 4, 8, 12, 13},

                               {5,
                                255, 255, 0,
                                0,
                                3, 13, 14, 10, 2}
                            };

            /* the location of the object in 3D space */
        float   trans[] = {0, 0, 900};

            /* velocity along X, Y, & Z axes*/
        float   vel[] = {20, 20, 20};

            /* the size of the object */
        float   scal[] = {2, 2, 2};

            /* current angles of rotation of object */
        float   angle[] = {0, 0, 0};

            /* rates of rotation of object */
        float   ang_inc[] = {0.08f, 0.05f, 0.0722f};

            /* minimum and maximum X Y & Z values for vertex 21 */
        float   limits[][] =  { {-500,  500},
                                {-500,  500},
                                { 700, 5000} };

            /* holds the projected (2D) object */
        int     proj[][] = new int[NP][2];

            /* screen distance */
        float   sd = 300;

            /* for looping */
        int     i, j;


            /*
             * Copy dodecahedron from unchanging, original arrays, 
             * scale and rotate around three axes move it, project 
             * vertices, determine farthest Z-coordinate for each 
             * face, sort the faces from back to front, & draw them 
             * in that order
             */

        for (j = 0; j < 10000; j++)
        {
            Copy3D(oCoord, coord, NP);

            Scale3D(coord, scal, NP);

            Rotate3D(coord, Y, Z, NP, angle[X]);
            Rotate3D(coord, Z, X, NP, angle[Y]);
            Rotate3D(coord, X, Y, NP, angle[Z]); 

            Translate3D(coord, trans, NP);

                /* 
                 * Test center of icosahedron and reverse direction 
                 * of velocity if it has crossed a limit 
                 */
            for (i = X; i <= Z; i++)
            {
                if (coord[NP - 1][i] < limits[i][0] || coord[NP - 1][i] > limits[i][1])
                    vel[i] *= -1.0;

                trans[i] += vel[i];     /* change location of object */

                angle[i] += ang_inc[i]; /* change angles of rotation */

                if (angle[i] > 2 * Math.PI)
                    angle[i] -= 2 * Math.PI;
            }

            Project3D(coord, proj, NP, sd);

                /* Find farthest z of each face, store in FAR_Z position in its array */
            FarthestZ(coord, faces, NF);

                /* Sort faces from back to front, using BubbleSort() algorithm */
            BubbleSort(faces, NF, ARRAYSIZE);

            FDisplay3D(proj, faces, NF, g);

            Delay(100);

            g.clearRect(0, 0, 640, 480);
        }
    }


    /*
     *	void	FarthestZ()
     *
     *	Determines farthest z-coordinate in each face
     */
    void    FarthestZ(float v[][], int f[][], int nf)
    {
        int     i, j;

        for (i = 0; i < nf; i++)
        {
            f[i][FAR_Z] = 0;    /* assume it's zero */

            for (j = 0; j < f[i][QTY]; j++)
            {
                    /* 
                     * Vertex numbers start at f[i][VERT], so j is added to
                     * get desired vertex number.  This vertex number is
                     * plugged into v[][Z] to get the actual Z-coordinate
                     */

                    /* 
                     * Compare each Z-coordinate to current far_z. If a
                     * Z-coordinate is farther, use its value for far_z
                     */

                if (f[i][FAR_Z] < (int) v[f[i][VERT + j]][Z])
                    f[i][FAR_Z] = (int) v[f[i][VERT + j]][Z];
            }
        }
    }


    /*
     *	void	FDisplay()
     *
     *	Displays filled faces of object. Uses values in faces[][] to set 
     *	color and fill x[] and y[] with projected values to be filled with area().
     */
    void	FDisplay3D(int proj[][], int faces[][], int nf, Graphics g)
    {
        boolean on;
        int     x[] = new int[MAX_V];
        int     y[] = new int[MAX_V];
        int     f, v;

            /* faces should have been sorted from front to back */
        for (f = 0; f < nf; f++)
        {
            on = true;

                /* 
                 * This loop continues while v is less than # of vertices,
                 * and on = 1, meaning all the points are on the screen
                 */
            for (v = 0; v < faces[f][QTY]; v++)
            {
                    /* 
                     * vertex numbers start at faces[f][VERT], so v is added
                     * to get desired vertex number. This vertex number is
                     * plugged into proj[][] to get projected X and Y-coord
                     */

                x[v] = proj[ faces[f][ VERT + v] ][X];
                y[v] = proj[ faces[f][ VERT + v] ][Y];

                    /* make sure this point is on screen */
                if (x[v] < 0 || x[v] > MAXX || y[v] < 0 || y[v] > MAXY)
                    on = false;
            } 

            if (on == true)
            {
                Color   c = new Color(faces[f][COLOR_R], faces[f][COLOR_G], faces[f][COLOR_B]);

                g.setColor(c);                      /* get color from face array */
                g.fillPolygon(x, y, faces[f][QTY]); /* fill face */
                
                /* Delay(1000); */  /* uncomment this line to see the drawing in action */
            }
        }
    }


    /*
     *  void    BubbleSort()
     *
     *  Sorts an array using the Bubble Sort algorithm
     */
    void        BubbleSort(int faces[][], int nf, int faceSize)
    {
        int     faceDataA[] = new int[faceSize], faceDataB[] = new int[faceSize];
        int     facesIndexA, facesIndexB;
        int     nDiff;
        int     i, j;

        while (nf > 1)
        {
            bIsDone = true;
            facesIndexA = 0;

            for (i = 0; i < nf - 1; i++)    /* walk throuh faces and move things up/down as needed */
            {
                    /* get a face off the list */
                for (j = 0; j < faceSize; j++)
                    faceDataA[j] = faces[facesIndexA][j];

                facesIndexB = facesIndexA + 1;

                    /* get the second face */
                for (j = 0; j < faceSize; j++)
                    faceDataB[j] = faces[facesIndexB][j];

                    /* check the 'far-z' of those faces */
                if (faceDataA[FAR_Z] == faceDataB[FAR_Z])
                    nDiff = 0;
                else
                {
                    if (faceDataA[FAR_Z] > faceDataB[FAR_Z])
                        nDiff = 1;
                    else
                        nDiff = -1;
                }

                    /* if they are not equal the we must swap them */
                if (nDiff != 0)
                {
                    if (nDiff < 0)
                    {
                            /* we will swap now */
                        for (j = 0; j < faceSize; j++)
                            faces[facesIndexA][j] = faceDataB[j];

                        for (j = 0; j < faceSize; j++)
                            faces[facesIndexB][j] = faceDataA[j];
                    }
                }

                facesIndexA++;      /* get the next element in the array */
            }

            nf--;
        }
    }


    /*
     *  void    Project3D()
     *
     *  Projects coordinate array onto screen at given screen distance 
     *  storing result as x and y coordinates in proj[][2] array.
     */
    void    Project3D(float coord[][], int proj[][], int np, float sd)
    {
        int     i;

            /*
             * scale each x and y by the ratio of the the screen 
             * distance to the Z-coordinate -- add coordinates of 
             * center of screen to shift origin to center of screen
             */

        for (i = 0; i < np; i++)
        {	    
            if (coord[i][Z] != 0.0f)
            {
                proj[i][X] = CX + (int) (coord[i][X] * sd / coord[i][Z]);
                proj[i][Y] = CY + (int) (coord[i][Y] * sd / coord[i][Z]);
            }
            else
                proj[i][X] = proj[i][Y] = 0;
        }
    }


    /*
     *  void    Translate3D()
     *	
     *  Add three values of trans[3] to each vertex in coord[][3] thus 
     *  moving vertices along all three axes
     */
    void    Translate3D(float coord[][], float trans[], int np)
    {
        int	i, j;

        for (i = 0; i < np; i++)    /* loop in the array */
        {
            for (j = 0; j < 3; j++)     /* loop in the vertex */
                coord[i][j] += trans[j];
        }
    }


    /*
     *	void    Copy3D()
     *
     *	Copy X, Y & Z coordinates from original arrays to temp arrays
     */
    void	Copy3D(float orig[][], float coord[][], int np)
    {
        int     i, j;

        for (i = 0; i < np; i++)
        {
            for (j = 0; j < 3; j++)
                coord[i][j] = orig[i][j];
        }
    }


    /*
     *	void	Scale3D()
     *
     *	Multiplies each X, Y & Z coordinate by corresponding element in the array s[]
     */
    void	Scale3D(float coord[][], float s[], int np)
    {
        int     i, j;

        for (i = 0; i < np; i++)
        {
            for (j = 0; j < 3; j++)
                coord[i][j] *= s[j];
        }
    }


    /*
     *  void    Rotate3D()
     *
     *  Rotate vertices around an axis by number of radians in angle:
     *      for X axis rotation:    c1 = Y and c2 = Z
     *      for Y axis rotation:    c1 = Z and c2 = X
     *      for Z axis rotation:    c1 = X and c2 = Y
     */		
    void	Rotate3D(float coord[][], int c1, int c2, int np, float angle)
    {
        int     i;
        float   t, s, c;

        s = (float) Math.sin(angle);
        c = (float) Math.cos(angle);

        for (i = 0; i < np; i++)
        {
                /* temporarily store new value of coordinate 1 */
            t            = c * coord[i][c1] + s * coord[i][c2];
            coord[i][c2] = c * coord[i][c2] - s * coord[i][c1];
            coord[i][c1] = t;
        }
    }


    /*
     *	void	Display3D()
     *	
     *	Displays wire frame of object.  Uses values in edge[][2] to find starting and 
     *	ending vertex for each edge
     */
    void	Display3D(int proj[][], int edge[][], int ne, Graphics g)
    {
        int     e;
        int     s, f;
        int     x1, y1, x2, y2;

        for (e = 0; e < ne; e++)
        {
            s = edge[e][0];     /* s -- is the starting vertex */
            f = edge[e][1];     /* f -- is the ending vertex */

            x1 = proj[s][X];    /* stating point of a line */
            y1 = proj[s][Y];

            x2 = proj[f][X];    /* ending point of a line */
            y2 = proj[f][Y];

            g.drawLine(x1, y1, x2, y2);
        }  
    }


    /*
     *  void    Delay()
     *
     *  Simply pauses for some time
     */
    public void Delay(int delayTime)
    {
        try
        {
            Thread.sleep(delayTime);    /* call Java's sleep method */
        }
        catch (InterruptedException e)
        {
            /* when the sleep() call above is over, Java will */
            /* be interuppted and we fall into this block of code */
            /* because our intention is simply slow down things */
            /* wed do nothing in our exception code and just get out */
        }
    }
}


