package com.mwc.cosc610.project6;

import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.applet.*;
import java.util.*;

import com.mwc.util.*;

/**
 * A simple 3D animation applet.
 * 
 * This is based off of my readings of the 
 * following books and web sites:
 * 
 * 1) Elementary Linear Algebra (Applications Version)
 *    Anton, Rorres
 *    Chapter 11.11, Page 626
 *
 * 2) Build Your Own Flight Sim in C++ 
 *    Michael Radtke, Chris Lampton and Andr Lamothe
 * 
 * 3) http://www.javaworld.com/javaworld/jw-07-1997/jw-07-howto-p2.html
 * 
 * @author Matthew W. Coan (10:04 PM 12/3/2002)
 */
public class Project6 extends Applet implements ImageObserver, Runnable {
   class Prj6KeyListner implements KeyListener {
      public void keyPressed(KeyEvent e) {
      }
            
      public void keyReleased(KeyEvent e) {
         switch(Character.toLowerCase(e.getKeyChar())) {
         case 'x':
            synchronized(_xRot) {
               _xOn = !_xOn;
            }
            break;
         case 'y':
            synchronized(_yRot) {
               _yOn = !_yOn;
            }
            break;
         case 'z':
            synchronized(_zRot) {
               _zOn = !_zOn;
            }
            break;
         }
      }
            
      public void keyTyped(KeyEvent e) {
      }
   }

   public Matrix getCentroid(Polygon ts) {      return new Matrix(new double[][]{                        {(_vertexArray[ts.vertexArray[0]].get(0,0) 
                        + _vertexArray[ts.vertexArray[1]].get(0,0)
                        + _vertexArray[ts.vertexArray[2]].get(0,0)) / 3.0},                        {(_vertexArray[ts.vertexArray[0]].get(1,0) 
                        + _vertexArray[ts.vertexArray[1]].get(1,0)
                        + _vertexArray[ts.vertexArray[2]].get(1,0)) / 3.0},                        {(_vertexArray[ts.vertexArray[0]].get(2,0) 
                        + _vertexArray[ts.vertexArray[1]].get(2,0)
                        + _vertexArray[ts.vertexArray[2]].get(2,0)) / 3.0},                        }, 3, 1);
   }

   public Matrix getNormal(Polygon ts) {      Matrix coords = new Matrix(3,1);      coords.set(0,0, ((_vertexArray[ts.vertexArray[0]].get(1,0) - _vertexArray[ts.vertexArray[1]].get(1,0))
                       * (_vertexArray[ts.vertexArray[0]].get(2,0) + _vertexArray[ts.vertexArray[1]].get(2,0)))
                      +
                      ((_vertexArray[ts.vertexArray[1]].get(1,0) - _vertexArray[ts.vertexArray[2]].get(1,0))
                       * (_vertexArray[ts.vertexArray[1]].get(2,0) + _vertexArray[ts.vertexArray[2]].get(2,0)))
                      +
                      ((_vertexArray[ts.vertexArray[2]].get(1,0) - _vertexArray[ts.vertexArray[0]].get(1,0))
                       * (_vertexArray[ts.vertexArray[2]].get(2,0) + _vertexArray[ts.vertexArray[0]].get(2,0))));
                                  coords.set(1,0, ((_vertexArray[ts.vertexArray[0]].get(2,0) - _vertexArray[ts.vertexArray[1]].get(0,0))
                       * (_vertexArray[ts.vertexArray[1]].get(2,0) + _vertexArray[ts.vertexArray[2]].get(2,0)))
                      +
                      ((_vertexArray[ts.vertexArray[1]].get(2,0) - _vertexArray[ts.vertexArray[2]].get(0,0))
                       * (_vertexArray[ts.vertexArray[1]].get(0,0) + _vertexArray[ts.vertexArray[2]].get(0,0)))
                      +
                      ((_vertexArray[ts.vertexArray[2]].get(2,0) - _vertexArray[ts.vertexArray[0]].get(2,0))
                       * (_vertexArray[ts.vertexArray[1]].get(0,0) + _vertexArray[ts.vertexArray[0]].get(0,0))));
                  coords.set(2,0, ((_vertexArray[ts.vertexArray[0]].get(0,0) - _vertexArray[ts.vertexArray[1]].get(0,0))
                       * (_vertexArray[ts.vertexArray[0]].get(1,0) + _vertexArray[ts.vertexArray[1]].get(1,0)))
                      +
                      ((_vertexArray[ts.vertexArray[1]].get(0,0) - _vertexArray[ts.vertexArray[2]].get(0,0))
                       * (_vertexArray[ts.vertexArray[1]].get(1,0) + _vertexArray[ts.vertexArray[2]].get(1,0)))
                      +
                      ((_vertexArray[ts.vertexArray[2]].get(0,0) - _vertexArray[ts.vertexArray[0]].get(0,0))
                       * (_vertexArray[ts.vertexArray[2]].get(1,0) + _vertexArray[ts.vertexArray[0]].get(1,0))));
                  double length = Math.sqrt((coords.get(0,0) * coords.get(0,0)) 
                                + (coords.get(1,0) * coords.get(1,0)) 
                                + (coords.get(2,0) * coords.get(2,0)));
            
      coords.set(0,0, coords.get(0,0) / length);
      coords.set(1,0, coords.get(1,0) / length);
      coords.set(2,0, coords.get(2,0) / length);                  return coords;   }

   public Matrix getLine(Matrix coords1, Matrix coords2) {      Matrix coords = new Matrix(new double [][]{
                                 {coords1.get(0,0) - coords2.get(0,0)},                                 {coords1.get(1,0) - coords2.get(1,0)},                                 {coords1.get(2,0) - coords2.get(2,0)}}, 3, 1);
      double length = Math.sqrt((coords.get(0,0) * coords.get(0,0)) 
                                + (coords.get(1,0) * coords.get(1,0)) 
                                + (coords.get(2,0) * coords.get(2,0)));
            
      coords.set(0,0, coords.get(0,0) / length);
      coords.set(1,0, coords.get(1,0) / length);
      coords.set(2,0, coords.get(2,0) / length);
         
      return coords;   }

   public double dot(Matrix coords1, Matrix coords2) {
      return coords1.get(0,0) * coords2.get(0,0) +
         coords1.get(1,0) * coords2.get(1,0) +
         coords1.get(2,0) * coords2.get(2,0);
   }
   
   /**
    * The width of the applet.
    */
   public static final int WIDTH = 600;
   
   /**
    * The height of the applet.
    */
   public static final int HEIGHT = 400;
   
   /**
    * Middle of the applet on the horizontal.
    */
   public static final int XORIGIN = 300;
   
   /**
    * Middle of the applet on the vertical.
    */
   public static final int YORIGIN = 200;
   
   
   // light source
   private Matrix _light = new Matrix(new double[][]{{-180.0},{180.0},{0.0}}, 3, 1);
   
   // rotation flags
   private boolean _xOn = true;
   private boolean _yOn = true;
   private boolean _zOn = false;
   
   // X rotation
   private Matrix _xRot;
   
   // Y rotation 
   private Matrix _yRot;
   
   // Z rotation
   private Matrix _zRot;
   
   // List of unique vertex colum vectors
   private Matrix _vertexArray[];
   
   // List of graphical objects (each graphical object 
   // composed of an array of polygons)
   private Polygon _objectArray[][];
   
   // Stoping capability
   private Object _stopLock;
   private boolean _stopFlag;
   
   // My polygon class (Over-rides the java.awt.Polygon 
   // class in this class scope)
   class Polygon {
      int vertexArray[];
      
      Polygon(int vertexArray[]) {
         this.vertexArray = vertexArray;
      }
      
      boolean isBackface(Matrix vvertexArray[]) {
         Matrix v0 = vvertexArray[vertexArray[0]];
         Matrix v1 = vvertexArray[vertexArray[1]];
         Matrix v2 = vvertexArray[vertexArray[2]];
         // cross product
         double z = (v1.get(0,0) - v0.get(0,0)) * (v2.get(1,0) - v0.get(1,0))
                    - (v1.get(1,0) - v0.get(1,0)) * (v2.get(0,0) - v0.get(0,0));
         z = -z;
         return z >= 0.0;
      }
   }      
   
   public boolean polygonComp(Polygon p1, Polygon p2) {      double z1 = (_vertexArray[p1.vertexArray[0]].get(2,0)
                   + _vertexArray[p1.vertexArray[1]].get(2,0)
                   + _vertexArray[p1.vertexArray[2]].get(2,0)) / 3.0;      double z2 = (_vertexArray[p2.vertexArray[0]].get(2,0)
                   + _vertexArray[p2.vertexArray[1]].get(2,0)
                   + _vertexArray[p2.vertexArray[2]].get(2,0)) / 3.0;
      return z1 < z2;               
   }

   public void sort(Polygon [] rgo, int nLow0, int nHigh0) {
      int nLow = nLow0;
      int nHigh = nHigh0;

      Polygon oMid;
      if (nHigh0 > nLow0) {
         oMid = rgo[ (nLow0 + nHigh0) / 2 ];

         while(nLow <= nHigh) {
            while((nLow < nHigh0) && polygonComp(rgo[nLow], oMid))
               ++nLow;

            while((nLow0 < nHigh) && polygonComp(oMid, rgo[nHigh]))
               --nHigh;

            if(nLow <= nHigh) 
               swap(rgo, nLow++, nHigh--);
         }

         if(nLow0 < nHigh)             sort(rgo, nLow0, nHigh);

         if(nLow < nHigh0)             sort(rgo, nLow, nHigh0);
      }
   }

   public void swap(Polygon [] rgo, int i, int j) {
      Polygon o;

      o = rgo[i]; 
      rgo[i] = rgo[j];
      rgo[j] = o;
   }

   void zSort(Polygon polygonArray[]) {
      sort(polygonArray, 0, polygonArray.length - 1);   }
                        
   // Project the vertex array on the applet
   // from some distance away
   Matrix[] project(double distance) {
      Matrix ret[] = new Matrix[_vertexArray.length];
      for(int i = 0; i < _vertexArray.length; i++) {
         ret[i] = new Matrix(
                    new double[][]{
                    {(distance * _vertexArray[i].get(0,0)) / (distance - _vertexArray[i].get(2,0))+XORIGIN},
                    {(distance * _vertexArray[i].get(1,0)) / (distance - _vertexArray[i].get(2,0))+YORIGIN},
                    {_vertexArray[i].get(2,0)}
                    },3,1);
      }
      return ret;
   }

   private Matrix[] _scale(Matrix vertexArray[], double scaler) {
      Matrix[] ret = new Matrix[vertexArray.length];
      Matrix T = new Matrix(new double[][]{
                            {scaler, 0.0, 0.0},
                            {0.0, scaler, 0.0},
                            {0.0, 0.0, scaler}
                            }, 3, 3);
      for(int i = 0; i < vertexArray.length; i++)
         ret[i] = T.mul(vertexArray[i]);
      return ret;
   }
   
   private Matrix[] _translate(Matrix vertexArray[], double x, double y, double z) {
      Matrix[] ret = new Matrix[vertexArray.length];
      Matrix T = new Matrix(new double[][]{
                            {x},
                            {y},
                            {z}
                            }, 3, 1);
      for(int i = 0; i < vertexArray.length; i++)
         ret[i] = vertexArray[i].add(T);
      return ret;
   }
   
   /**
    * Init the applet.
    */
   public void init() {
      _stopLock = new Object();
      _stopFlag = true;
      _xRot = new Matrix(new double[][]{
                         {1.0, 0.0, 0.0},
                         {0.0, Math.cos(Math.PI/180.0), -Math.sin(Math.PI/180.0)},
                         {0.0, Math.sin(Math.PI/180.0), Math.cos(Math.PI/180.0)}
                         }, 3, 3);
      _yRot = new Matrix(new double[][]{
                         {Math.cos(Math.PI/180.0), 0.0, Math.sin(Math.PI/180)},
                         {0.0, 1.0, 0.0},
                         {-Math.sin(Math.PI/180.0), 0.0, Math.cos(Math.PI/180.0)}
                         }, 3, 3);
      _zRot = new Matrix(new double[][]{
                         {Math.cos(Math.PI/180.0), -Math.sin(Math.PI/180.0), 0,0},
                         {Math.sin(Math.PI/180.0), Math.cos(Math.PI/180.0), 0.0},
                         {0.0, 0.0, 1.0}
                         }, 3, 3);
      _vertexArray = new Matrix[] {
                       new Matrix(new double[][]{{-50.0},{50.0},{-50.0}},3,1),
                       new Matrix(new double[][]{{50.0},{50.0},{-50.0}},3,1),
                       new Matrix(new double[][]{{50.0},{50.0},{50.0}},3,1),
                       new Matrix(new double[][]{{-50.0},{50.0},{50.0}},3,1),
                       new Matrix(new double[][]{{-50.0},{-50.0},{-50.0}},3,1),
                       new Matrix(new double[][]{{50.0},{-50.0},{-50.0}},3,1),
                       new Matrix(new double[][]{{50.0},{-50.0},{50.0}},3,1),
                       new Matrix(new double[][]{{-50.0},{-50.0},{50.0}},3,1),
                       };
      
      Matrix[] cube2 = _scale(_vertexArray, 0.4);
      cube2 = _translate(cube2, 150.0, 0.0, 0.0);
      
      Matrix[] cube3 = _scale(_vertexArray, 0.4);
      cube3 = _translate(cube3, 0.0, 150.0, 0.0);
      
      Matrix[] cube4 = _scale(_vertexArray, 0.4);
      cube4 = _translate(cube4, 0.0, 0.0, 150.0);
      
      Matrix[] cube5 = _scale(_vertexArray, 0.4);
      cube5 = _translate(cube5, -150.0, 0.0, 0.0);
      
      Matrix[] cube6 = _scale(_vertexArray, 0.4);
      cube6 = _translate(cube6, 0.0, -150.0, 0.0);
      
      Matrix[] cube7 = _scale(_vertexArray, 0.4);
      cube7 = _translate(cube7, 0.0, 0.0, -150.0);
      
      Matrix temp[] = new Matrix[_vertexArray.length 
                                 + cube2.length
                                 + cube3.length 
                                 + cube4.length
                                 + cube5.length
                                 + cube6.length
                                 + cube7.length ];
      int index,j;
      for(index = 0; index < _vertexArray.length; index++)
         temp[index] = _vertexArray[index];
      j = 0;
      while(index < (_vertexArray.length + cube2.length)) {
         temp[index] = cube2[j];
         index++;
         j++;
      }
      j = 0;
      while(index < (_vertexArray.length + cube2.length 
                     + cube3.length)) {
         temp[index] = cube3[j];
         index++;
         j++;
      }
      j = 0;
      while(index < (_vertexArray.length + cube2.length 
                     + cube3.length + cube4.length)) {
         temp[index] = cube4[j];
         index++;
         j++;
      }
      j = 0;
      while(index < (_vertexArray.length + cube2.length 
                     + cube3.length + cube4.length 
                     + cube5.length)) {
         temp[index] = cube5[j];
         index++;
         j++;
      }
      j = 0;
      while(index < (_vertexArray.length + cube2.length 
                     + cube3.length + cube4.length 
                     + cube5.length + cube6.length)) {
         temp[index] = cube6[j];
         index++;
         j++;
      }
      j = 0;
      while(index < (_vertexArray.length + cube2.length 
                     + cube3.length + cube4.length 
                     + cube5.length + cube6.length
                     + cube7.length)) {
         temp[index] = cube7[j];
         index++;
         j++;
      }
      _vertexArray = temp;
      
      _objectArray = new Polygon[][] {
                       {   
                           // Cube 1:
                           new Polygon(new int[] 
                                       {0, 1, 5, 4}),

                           new Polygon(new int[] 
                                       {5, 6, 7, 4}),

                           new Polygon(new int[] 
                                       {6, 2, 3, 7}),
                             
                           new Polygon(new int[] 
                                       {2, 1, 0, 3}),
                             
                           new Polygon(new int[] 
                                       {2, 6, 5, 1}),
                             
                           new Polygon(new int[] 
                                       {4, 7, 3, 0}),
                       },

                       {   
                           // Cube 2:
                           new Polygon(new int[] 
                                       {0+8, 1+8, 5+8, 4+8}),

                           new Polygon(new int[] 
                                       {5+8, 6+8, 7+8, 4+8}),

                           new Polygon(new int[] 
                                       {6+8, 2+8, 3+8, 7+8}),
                             
                           new Polygon(new int[] 
                                       {2+8, 1+8, 0+8, 3+8}),
                             
                           new Polygon(new int[] 
                                       {2+8, 6+8, 5+8, 1+8}),
                             
                           new Polygon(new int[] 
                                       {4+8, 7+8, 3+8, 0+8}),
                       },

                       {   
                           // Cube 3:
                           new Polygon(new int[] 
                                       {0+8+8, 1+8+8, 5+8+8, 4+8+8}),

                           new Polygon(new int[] 
                                       {5+8+8, 6+8+8, 7+8+8, 4+8+8}),

                           new Polygon(new int[] 
                                       {6+8+8, 2+8+8, 3+8+8, 7+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8, 1+8+8, 0+8+8, 3+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8, 6+8+8, 5+8+8, 1+8+8}),
                             
                           new Polygon(new int[] 
                                       {4+8+8, 7+8+8, 3+8+8, 0+8+8}),
                       },

                       {   
                           // Cube 4:
                           new Polygon(new int[] 
                                       {0+8+8+8, 1+8+8+8, 5+8+8+8, 4+8+8+8}),

                           new Polygon(new int[] 
                                       {5+8+8+8, 6+8+8+8, 7+8+8+8, 4+8+8+8}),

                           new Polygon(new int[] 
                                       {6+8+8+8, 2+8+8+8, 3+8+8+8, 7+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8+8, 1+8+8+8, 0+8+8+8, 3+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8+8, 6+8+8+8, 5+8+8+8, 1+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {4+8+8+8, 7+8+8+8, 3+8+8+8, 0+8+8+8}),
                       },
                       {   
                           // Cube 5:
                           new Polygon(new int[] 
                                       {0+8+8+8+8, 1+8+8+8+8, 5+8+8+8+8, 4+8+8+8+8}),

                           new Polygon(new int[] 
                                       {5+8+8+8+8, 6+8+8+8+8, 7+8+8+8+8, 4+8+8+8+8}),

                           new Polygon(new int[] 
                                       {6+8+8+8+8, 2+8+8+8+8, 3+8+8+8+8, 7+8+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8+8+8, 1+8+8+8+8, 0+8+8+8+8, 3+8+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8+8+8, 6+8+8+8+8, 5+8+8+8+8, 1+8+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {4+8+8+8+8, 7+8+8+8+8, 3+8+8+8+8, 0+8+8+8+8}),
                       },
                       {   
                           // Cube 6:
                           new Polygon(new int[] 
                                       {0+8+8+8+8+8, 1+8+8+8+8+8, 5+8+8+8+8+8, 4+8+8+8+8+8}),

                           new Polygon(new int[] 
                                       {5+8+8+8+8+8, 6+8+8+8+8+8, 7+8+8+8+8+8, 4+8+8+8+8+8}),

                           new Polygon(new int[] 
                                       {6+8+8+8+8+8, 2+8+8+8+8+8, 3+8+8+8+8+8, 7+8+8+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8+8+8+8, 1+8+8+8+8+8, 0+8+8+8+8+8, 3+8+8+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8+8+8+8, 6+8+8+8+8+8, 5+8+8+8+8+8, 1+8+8+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {4+8+8+8+8+8, 7+8+8+8+8+8, 3+8+8+8+8+8, 0+8+8+8+8+8}),
                       },
                       {   
                           // Cube 7:
                           new Polygon(new int[] 
                                       {0+8+8+8+8+8+8, 1+8+8+8+8+8+8, 5+8+8+8+8+8+8, 4+8+8+8+8+8+8}),

                           new Polygon(new int[] 
                                       {5+8+8+8+8+8+8, 6+8+8+8+8+8+8, 7+8+8+8+8+8+8, 4+8+8+8+8+8+8}),

                           new Polygon(new int[] 
                                       {6+8+8+8+8+8+8, 2+8+8+8+8+8+8, 3+8+8+8+8+8+8, 7+8+8+8+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8+8+8+8+8, 1+8+8+8+8+8+8, 0+8+8+8+8+8+8, 3+8+8+8+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {2+8+8+8+8+8+8, 6+8+8+8+8+8+8, 5+8+8+8+8+8+8, 1+8+8+8+8+8+8}),
                             
                           new Polygon(new int[] 
                                       {4+8+8+8+8+8+8, 7+8+8+8+8+8+8, 3+8+8+8+8+8+8, 0+8+8+8+8+8+8}),
                       }
                    };
      
      addKeyListener(new Prj6KeyListner());
   }

   /**
    * Get the vector of X values for a polygon in the representation.
    */
   protected int[] getX(Polygon poly, Matrix vertexArray[]) {
      int ret[] = new int[poly.vertexArray.length];
      for(int k = 0; k < poly.vertexArray.length; k++)
         ret[k] = (int)Math.round(vertexArray[poly.vertexArray[k]].get(0,0));
      return ret;
   }
   
   /**
    * Gthe the vector of Y values for a polygon in the representation.
    */
   protected int[] getY(Polygon poly, Matrix vertexArray[]) {
      int ret[] = new int[poly.vertexArray.length];
      for(int k = 0; k < poly.vertexArray.length; k++)
         ret[k] = (int)Math.round(vertexArray[poly.vertexArray[k]].get(1,0));
      return ret;
   }
   
   private Polygon[] _makePolygonArray(Matrix vertexArray[]) {
      Vector ret = new Vector();
      Polygon []object;
      Polygon poly;
      for(int objnum = 0; objnum < _objectArray.length; objnum++) {
         object = _objectArray[objnum];
         for(int polynum = 0; polynum < object.length; polynum++) {
            poly = object[polynum];
            if(!poly.isBackface(vertexArray))
               ret.addElement(poly);
         }
      }
      
      Polygon array[] = new Polygon[ret.size()];
      for(int index = 0; index < ret.size(); index++)
         array[index] = (Polygon)ret.elementAt(index);
      
      return array;
   }
   
   /**
    * Paint the array of 3D objects onto the 2D applet.
    */
   public void paint(Graphics g) {
      // Clear:
      g.setColor(Color.white);
      g.fillRect(0, 0, WIDTH, HEIGHT);
      
      // Project:
      Matrix[] temp = project(400.0);
      
      // Paint:
      int x[], y[];
      Polygon polygonArray[] = _makePolygonArray(temp);
      zSort(polygonArray);
      for(int j = 0; j < polygonArray.length; j++) {
         Matrix n0 = getCentroid(polygonArray[j]);

         Matrix n1 = getNormal(polygonArray[j]);

         Matrix n2 = getLine(n0, _light);

         float dot1 = (float)dot(n1, n2);

         float dot = (float)(dot1 / 6.0 + 0.5);

         x = getX(polygonArray[j], temp);
         y = getY(polygonArray[j], temp);
         g.setColor(new Color(0, dot, 0));
         g.fillPolygon(x, y, x.length);
         
         g.setColor(new Color(dot, 0, dot));
         g.drawPolygon(x, y, x.length);
      }
   }
   
   public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)  {
      return true;   }
   
   /**
    * Is the animation thread running?
    */
   public boolean isRunning() {
      synchronized(_stopLock) {
         return !_stopFlag;
      }
   }
   
   /**
    * Stop the animation thread on applet stop.
    */
   public void stop() {
      synchronized(_stopLock) {
         _stopFlag = true;
      }
   }

   /**
    * Stop the animation thread on applet destory.
    */
   public void destroy() {
      synchronized(_stopLock) {
         _stopFlag = true;
      }
   }
   
   /**
    * Start the applet.
    */
   public void start() {
      new Thread(this).start();
   }
   
   /**
    * Main animation run method.
    */
   public void run() {
      // Set the stop flag
      synchronized(_stopLock) {
         _stopFlag = false;
      }

      Graphics g;
      try {
         // Create the off screen image buffer
         Image offscreen = createImage(WIDTH, HEIGHT);

         // Paint the objects while the applet 
         // is running
         while(isRunning()) {
            // Try to do close to 30 frames per second
            try {
               Thread.sleep(1000/30);
            }
            catch(InterruptedException ie) {
            }
            
            // Paint onto the image
            paint(offscreen.getGraphics());

            // If the applet got stoped
            // in the body of this loop
            // getGraphics() will return
            // null.  If it is null stop
            // the animation loop.
            if((g = getGraphics()) == null)
               break;
            
            // Paint the image onto the applet
            g.drawImage(offscreen, 0, 0, this);
            
            // Rotate about X,Y and Z one degree at a time
            // if the x,y or z keys are pressed.
            for(int i = 0; i < _vertexArray.length; i++) {
               synchronized(_xRot) {
                  if(_xOn) {
                     _vertexArray[i] = _xRot.mul(_vertexArray[i]);
                  }
               }
               synchronized(_yRot) {
                  if(_yOn) {
                     _vertexArray[i] = _yRot.mul(_vertexArray[i]);
                  }
               }
               synchronized(_zRot) {
                  if(_zOn) {
                     _vertexArray[i] = _zRot.mul(_vertexArray[i]);
                  }
               }
            }

            // Rotate the outer cubes again
            for(int i = 1; i < _objectArray.length; i++) {
               for(int j = 0; j < _objectArray[i].length; j++) {
                  Polygon p = _objectArray[i][j];
                  for(int k = 0; k < p.vertexArray.length; k++) {
                     synchronized(_xRot) {
                        if(_xOn) {
                           _vertexArray[p.vertexArray[k]] = _xRot.mul(_vertexArray[p.vertexArray[k]]);
                        }
                     }
                     synchronized(_yRot) {
                        if(_yOn)
                           _vertexArray[p.vertexArray[k]] = _yRot.mul(_vertexArray[p.vertexArray[k]]);
                     }
                     synchronized(_zRot) {
                        if(_zOn) {
                           _vertexArray[p.vertexArray[k]] = _zRot.mul(_vertexArray[p.vertexArray[k]]);
                        }
                     }
                  }
               }
            }
         }
      }
      finally {
         // Set the stop flag
         synchronized(_stopLock) {
            _stopFlag = true;
         }
      }
   }
}
