import java.awt.image.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Event;
import java.awt.Image;
import java.awt.Font;
import java.util.Vector;  //  http://java.sun.com/products/jdk/1.1/docs/api/java.util.Vector.html

/*

TODOs:

* Poly.line - clip z to horizontal edges of buffer
* Frame rate dependent time increments

*/



//---- Triangle rasteriser ---------------------------------------------------//

class Tri
{
  //- Output buffers

  private int w, h;
  private int[] buf;   //  Pixel buffer (0x00rrggbb)
  private int[] zbuf;  //  1/Z Buffer
  private int[] ibuf;  //  Index buffer (for shape picking)

  //- Rendering workspace

  private int ymin, ymax;
  private int xmin[], xmax[];
  private int zmin[], zmax[];

  //- Triangle parameters

  public double x1, y1, z1, r1, g1, b1, a1, u1, v1;
  public double x2, y2, z2, r2, g2, b2, a2, u2, v2;
  public double x3, y3, z3, r3, g3, b3, a3, u3, v3;

  //---- Tri

  public Tri(int[] buf, int[] zbuf, int w, int h)
  {
    this.w = w;
    this.h = h;
    this.buf = buf;
    this.zbuf = zbuf;
    
    xmin = new int[h];
    xmax = new int[h];

    zmin = new int[h];
    zmax = new int[h];
  }

  //---- col
  
  public void col(int c)
  {
    clear();
    line(x1, y1, z1, x2, y2, z2);
    line(x2, y2, z2, x3, y3, z3);
    line(x3, y3, z3, x1, y1, z1);
    fill_col(c);
  }

  //---- clearZBuf

  public void clearZBuf()
  {
    for(int i=0; i<w*h; i++) zbuf[i] = 0;
  }

  //---- clear

  public void clear()
  {
    int i;
 
    ymin = h;
    ymax = 0;
 
    for(i=0; i<h; i++)
    {
      xmin[i] = w;
      xmax[i] = 0;
    }
  }

  public void line(double x1, double y1, double z1, double x2, double y2, double z2)
  {
    double t;
    int x, y, z, i;
    int iy1, iy2;

    if(y2>y1) {

      // line going down - right hand side

      iy1 = (int)(y1)+1;
      iy2 = (int)(y2)+1;

      if(iy1 >= h) { return; }
      if(iy2 <= 0) { return; }
      if(iy1 < 0) { iy1 = 0; }
      if(iy2 > h) { iy2 = h; }

      if(iy1 < ymin) { ymin = iy1; }
      if(iy2 > ymax) { ymax = iy2; }

      for(y=iy1; y<iy2; y++)
      {
        x = (int) (x1 + (x2-x1)*(y-y1)/(y2-y1));
        z = (int)(32*65536/(z1 + (z2-z1)*(y-y1)/(y2-y1)));
        if(x > w) { x = w; }  //  TODO:  Clip z to edge of screen too
        if(x < 0) { x = 0; }  //  TODO:  Clip z to edge of screen too
        xmax[y] =  x;
        zmax[y] =  z;
      }

    } else {

      // line going up - left hand side

      t = x1; x1 = x2; x2 = t;
      t = y1; y1 = y2; y2 = t;
      t = z1; z1 = z2; z2 = t;

      iy1 = (int)(y1)+1;
      iy2 = (int)(y2)+1;

      if(iy1 >= h) { return; }
      if(iy2 <= 0) { return; }
      if(iy1 < 0) { iy1 = 0; }
      if(iy2 > h) { iy2 = h; }

      if(iy1 < ymin) { ymin = iy1; }
      if(iy2 > ymax) { ymax = iy2; }

      for(y=iy1; y<iy2; y++)
      {
        x = (int) (x1 + (x2-x1)*(y-y1)/(y2-y1));
        z = (int)(32*65536/(z1 + (z2-z1)*(y-y1)/(y2-y1)));
        if(x > w) { x = w; }  //  TODO:  Clip z to edge of screen too
        if(x < 0) { x = 0; }  //  TODO:  Clip z to edge of screen too
        xmin[y] = x;
        zmin[y] = z;
      }

    }

  }

  public void fill_col(int c)
  {
    int x, y, z, zd=0;
    int o, oo;

    oo = w*ymin;

    for(y=ymin; y<ymax; y++)
    {
      o = oo + xmin[y];
      
      z = zmin[y];
      if(xmax[y] > xmin[y]) zd = (zmax[y]-z)/(xmax[y]-xmin[y]);
      
      for(x=xmin[y]; x<xmax[y]; x++)
      {
        if(z > zbuf[o])
        {
          buf[o] = c;
          zbuf[o] = z;
        }
        z += zd;

        o++;
      }

      oo += w;
    }

  }

}


//---- Polygon rasteriser ----------------------------------------------------//

class Poly
{
  int w, h;
  int[] buf;
  int[] zbuf;

  int poly_ymin, poly_ymax;
  int poly_xmin[], poly_xmax[];
  int poly_zmin[], poly_zmax[];

//  int poly2_ymin, poly2_ymax;
//  int poly2_xmin[], poly2_xmax[];

  public Poly(int[] buf, int[] zbuf, int w, int h)
  {
    this.w = w;
    this.h = h;
    this.buf = buf;
    this.zbuf = zbuf;
    
    poly_xmin = new int[h];
    poly_xmax = new int[h];

    poly_zmin = new int[h];
    poly_zmax = new int[h];
  }

  public void clearZBuf()
  {
    for(int i=0; i<w*h; i++) zbuf[i] = 0;
  }

  public void clear()
  {
    int i;
 
    poly_ymin = h;
    poly_ymax = 0;
 
    for(i=0; i<h; i++)
    {
      poly_xmin[i] = w;
      poly_xmax[i] = 0;
    }
  }

  public void line(double x1, double y1, double z1, double x2, double y2, double z2)
  {
    double t;
    int x, y, z, i;
    int iy1, iy2;

    if(y2>y1) {

      // line going down - right hand side

      iy1 = (int)(y1)+1;
      iy2 = (int)(y2)+1;

      if(iy1 >= h) { return; }
      if(iy2 <= 0) { return; }
      if(iy1 < 0) { iy1 = 0; }
      if(iy2 > h) { iy2 = h; }

      if(iy1 < poly_ymin) { poly_ymin = iy1; }
      if(iy2 > poly_ymax) { poly_ymax = iy2; }

      for(y=iy1; y<iy2; y++)
      {
        x = (int) (x1 + (x2-x1)*(y-y1)/(y2-y1));
        z = (int)(4*4096*65536/ ( ( z1 + (z2-z1) * (y-y1) / (y2-y1) )) );
        if(x > w) { x = w; }  //  TODO:  Clip z to edge of screen too
        if(x < 0) { x = 0; }  //  TODO:  Clip z to edge of screen too
        poly_xmax[y] =  x;
        poly_zmax[y] =  z;
      }

    } else {

      // line going up - left hand side

      t = x1; x1 = x2; x2 = t;
      t = y1; y1 = y2; y2 = t;
      t = z1; z1 = z2; z2 = t;

      iy1 = (int)(y1)+1;
      iy2 = (int)(y2)+1;

      if(iy1 >= h) { return; }
      if(iy2 <= 0) { return; }
      if(iy1 < 0) { iy1 = 0; }
      if(iy2 > h) { iy2 = h; }

      if(iy1 < poly_ymin) { poly_ymin = iy1; }
      if(iy2 > poly_ymax) { poly_ymax = iy2; }

      for(y=iy1; y<iy2; y++)
      {
        x = (int) (x1 + (x2-x1)*(y-y1)/(y2-y1));
        z = (int)(4*4096*65536/(z1 + (z2-z1)*(y-y1)/(y2-y1)));
        if(x > w) { x = w; }  //  TODO:  Clip z to edge of screen too
        if(x < 0) { x = 0; }  //  TODO:  Clip z to edge of screen too
        poly_xmin[y] = x;
        poly_zmin[y] = z;
      }

    }

  }

  public void fill_col(int c)
  {
    int x, y, z, zd=0;
    int o, oo;
    
    ////////
    c = 0x00FFFFFF;
    ////////

    oo = w*poly_ymin;

    //- Solid fill

    if((c & 0xFF000000) == 0)
    {
      for(y=poly_ymin; y<poly_ymax; y++, oo += w)
      {
        z = poly_zmin[y]; if(poly_xmax[y] > poly_xmin[y]) zd = (poly_zmax[y]-z)/(poly_xmax[y]-poly_xmin[y]);
        for(x=poly_xmin[y], o = oo + poly_xmin[y]; x<poly_xmax[y]; x++, o++, z += zd) if(z > zbuf[o]) { buf[o] = c; zbuf[o] = z; }
      }
    }
    
    //- Alpha fill

    else
    {
      for(y=poly_ymin; y<poly_ymax; y++, oo += w)
      {
        z = poly_zmin[y]; if(poly_xmax[y] > poly_xmin[y]) zd = (poly_zmax[y]-z)/(poly_xmax[y]-poly_xmin[y]);
        for(x=poly_xmin[y], o = oo + poly_xmin[y]; x<poly_xmax[y]; x++, o++, z += zd) if(z > zbuf[o])
        {
          if(zbuf[o] > 0) buf[o] = ((buf[o]>>1)&0x7F7F7F)+((c>>1)&0x7F7F7F);
                     else buf[o] = c;
          zbuf[o] = z;
        }
      }
    }

  }

}



//---- 3D Renderer -----------------------------------------------------------//



//---- Surface

class Surface
{

  public Surface()
  {
  
  }

}



//---- Scene

class Scene
{
  //---- Data

  //- Camera

  private double camera_x=0, camera_y=0, camera_z=-11;  //  -17 to fit whole scene in, -11 for close-up
  private double camera_rx=0, camera_ry=0;

  //- Output buffer

  private int[] buf = null;
  private int[] zbuf = null;
  private int w = 0, h = 0;

  private Poly poly;

  int bg_col = 0x00FFFFFF, bg_r=0, bg_g=0, bg_b=0;

  //- Shapes in this scene

  private Vector shapes;

  //- Transformation stack

  private Transform[] trans;
  private int numTransforms;

  protected double time = 0;

  //---- Main code

  public Scene()
  {
    shapes = new Vector();

    trans = new Transform[32];
    numTransforms = 0;

    setBackground(bg_col);
  }

  //---- addShape

  public void addShape(Shape s)
  {
    shapes.addElement(s);
  }

  //---- setBuffer

  public void setBuffer(int[] buf, int w, int h)
  {
    this.buf = buf; this.w = w; this.h = h;
    this.zbuf = new int[w*h];

    poly = new Poly(buf, zbuf, w, h);
  }

  //---- setBackground
  
  public void setBackground(int col)
  {
    bg_col = col;
    bg_r = (col>>16) & 0xFF;  
    bg_g = (col>> 8) & 0xFF;  
    bg_b = (col    ) & 0xFF;  
  }

  //---- setCamera

  public void setCamera(double x, double y, double z, double rx, double ry)
  {
    camera_x = x; camera_y = y; camera_z = z;
    camera_rx = rx; camera_ry = ry;
  }

  //---- transformPoint
  
  public void transformPoint(double p[], double t[], double s[])
  {
    //  Transform point

    trans[numTransforms-1].multiply(p, t);

    //  Screen space point

    if(s != null)
    {
      s[0] = w*0.5 + 250.0*t[0]/(t[2]);
      s[1] = h*0.5 - 250.0*t[1]/(t[2]);
      s[2] = t[2];
    }
  }

  //---- plotPoint

  public void plotPoint(double sx, double sy)
  {
//    if(sx >= 0 && sx < w && sy >= 0 && sy < h) buf[(int)sx + w*(int)sy] = 0xFFFFFF;

    if(sx >= 0 && sx < w-1 && sy >= 0 && sy < h-1)
    {
      double dx = sx-(int)sx;
      double dy = sy-(int)sy;

      int o = (int)sx + w*(int)sy;
      
      int c00 = (int)(255*Math.sqrt((1-dx)*(1-dy)));
      int c01 = (int)(255*Math.sqrt((  dx)*(1-dy)));
      int c10 = (int)(255*Math.sqrt((1-dx)*(  dy)));
      int c11 = (int)(255*Math.sqrt((  dx)*(  dy)));

      buf[o    ] = c00 + (c00<<8) + (c00 << 16);
      buf[o+1  ] = c01 + (c01<<8) + (c01 << 16);
      buf[o+w  ] = c10 + (c10<<8) + (c10 << 16);
      buf[o+w+1] = c11 + (c11<<8) + (c11 << 16);
    }
  }

  //---- drawLine
  
  public void drawLine(double x1, double y1, double x2, double y2)
  {
    double dx = x2-x1;
    double dy = y2-y1;
    
    if(dx < 0) dx =- dx;
    if(dy < 0) dy =- dy;
    
    if(dx > dy)
    {
      dx = 1/dx;
      if(x2 < x1)
      {
        double y = y1; y1 = y2; y2 = y;
        double x = x1; x1 = x2; x2 = x;
      }
      for(int i=(int)(x1+1); i<=(int)x2; i++) plotPoint(i, y1 + (y2-y1)*(i-x1)*dx);
    }
    else
    {
      dy = 1/dy;
      if(y2 < y1)
      {
        double y = y1; y1 = y2; y2 = y;
        double x = x1; x1 = x2; x2 = x;
      }
      for(int i=(int)(y1+1); i<=(int)y2; i++) plotPoint(x1 + (x2-x1)*(i-y1)*dy, i);
    }
    

  }

  //---- drawFace
  
  public void drawFace(double[] s1, double[] s2, double[] s3, int c)
  {
    poly.clear();
    poly.line(s1[0], s1[1], s1[2], s2[0], s2[1], s2[2]);
    poly.line(s2[0], s2[1], s2[2], s3[0], s3[1], s3[2]);
    poly.line(s3[0], s3[1], s3[2], s1[0], s1[1], s1[2]);
    poly.fill_col(c);
  }

  //---- transform

  public void transform(Transform t)
  {
    trans[numTransforms] = new Transform(trans[numTransforms-1]);
    trans[numTransforms].transform(t);
    numTransforms ++;
  }

  //---- untransform

  public void untransform()
  {
    numTransforms --;
  }

  //---- render

  public void render()
  {
    //- Sort out time
    
    time += 0.05;  //  Assume 20 fps

    //- Set up transformation stack

    trans[0] = new Transform();

    trans[0].translate(-camera_x, -camera_y, -camera_z);
    trans[0].rotateY(-camera_ry);
    trans[0].rotateX(-camera_rx);

    numTransforms = 1;

    //- Clear buffer

    int size = w*h;
    int i, j;

/* */
    for(i=0; i<size; i++) buf[i] = bg_col;
/* */

/* * /
    for(i = 1+w; i < size-w-1; i++)
    {
      buf[i] = 0;
      buf[i] += (buf[i-1]>>2)&0x003F3F3F;
      buf[i] += (buf[i+1]>>2)&0x003F3F3F;
      buf[i] += (buf[i-w]>>2)&0x003F3F3F;
      buf[i] += (buf[i+w]>>2)&0x003F3F3F;
    }
/* */
    
    poly.clearZBuf();

    //- Render shapes

    for(i=0; i<shapes.size(); i++) ((Shape)shapes.elementAt(i)).render();
    
    //- Post-processing effects
    
    int o;
    
    int d, dd, ddd;
    int r, g, b;

    int[][] gauss =
    {
      { 1,  5,  9,  5,  1 },
      { 5, 16, 27, 16,  5 },
      { 9, 27, 44, 27,  9 },
      { 5, 16, 27, 16,  5 },
      { 1,  5,  9,  5,  1 }
    };

    int m, n;

    int rad = 4;
    
    for(j=rad; j<h-rad; j++) for(i=rad, o=j*w+i; i<w-rad; i++, o++)
    {
      //  Calculate difference
      
      d  = (zbuf[o-1]>>2) + (zbuf[o+1]>>2) + (zbuf[o-w]>>2) + (zbuf[o+w]>>2);

      d -= zbuf[o];
      if(d < 0) d = 0;
      
      d = (d)>>15;
      if(d > 256) d = 256;
      
      d = 256-d;
      
      //  Calculate rate of change
/*
      dd  = (zbuf[o-1]>>3) + (zbuf[o+1]>>3) + (zbuf[o-w]>>3) + (zbuf[o+w]>>3);
      dd += (zbuf[o-2]>>3) + (zbuf[o+2]>>3) + (zbuf[o-w-w]>>3) + (zbuf[o+w+w]>>3);

      dd -= zbuf[o];
      if(dd < 0) dd = 0;
      
      dd = (dd)>>17;
      if(dd > 256) dd = 256;
      
      dd = 256-dd;
*/     
      //  Calculate 3rd order
/*
      ddd  = (zbuf[o-1]>>4) + (zbuf[o+1]>>4) + (zbuf[o-w]>>4) + (zbuf[o+w]>>4);
      ddd += (zbuf[o-2]>>4) + (zbuf[o+2]>>4) + (zbuf[o-w-w]>>4) + (zbuf[o+w+w]>>4);
      ddd += (zbuf[o-3]>>4) + (zbuf[o+3]>>4) + (zbuf[o-w-w-w]>>4) + (zbuf[o+w+w+w]>>4);
      ddd += (zbuf[o-4]>>4) + (zbuf[o+4]>>4) + (zbuf[o-w-w-w-w]>>4) + (zbuf[o+w+w+w+w]>>4);

      ddd -= zbuf[o];
      if(ddd < 0) ddd = 0;
      
      ddd = (ddd)>>16;
      if(dd > 256) ddd = 256;
      
      ddd = 256-ddd;
*/
      //  Apply filter

//      if(dd < d) d = dd;
//      d = (d>>1)+(dd>>1);

      if(d < 256)
      {
        r = (buf[o]>>16)&255;
        g = (buf[o]>> 8)&255;
        b = (buf[o]    )&255;

        r = (r*d)>>8;
        g = (g*d)>>8;
        b = (b*d)>>8;
      
        buf[o] = (r<<16)+(g<<8)+b;
      }
    }

    // System.out.println(zbuf[w*h/2]);

    //- Done
  }
}



//---- Shape

class Shape
{
  //---- Data

  Scene scene = null;
  public Transform trans = null;

  int numPoints = 0;
  int numLines = 0;
  int numFaces = 0;

  double[][] p;         //  Points
  int[] lp1, lp2;       //  Lines
  int[] fp1, fp2, fp3;  //  Faces
  int[] col;            //  Face colours

  double[][] t;  //  Translated points
  double[][] s;  //  Screen points

  Vector shapes;

  //- Center point
  
  double[] op;
  double[] ot;
  double[] os;

  double fog;
  double fog_start = 100, fog_dist = 100;

  //---- Main code

  public Shape(Scene sc)
  {
    scene = sc;
    shapes = new Vector();

    trans = new Transform();

    p = new double[64][4];

    lp1 = new int[64];
    lp2 = new int[64];

    fp1 = new int[64];
    fp2 = new int[64];
    fp3 = new int[64];
    col = new int[64];

    t = new double[64][4];
    s = new double[64][4];
    
    op = new double[4];
    ot = new double[4];
    os = new double[4];

    op[0] = 0;
    op[1] = 0;
    op[2] = 0;
    op[3] = 1;
  }

  //---- addPoint

  public void addPoint(double x, double y, double z)
  {
    p[numPoints][0] = x;
    p[numPoints][1] = y;
    p[numPoints][2] = z;
    p[numPoints][3] = 1;

    numPoints ++;
  }

  public void addLine(int p1, int p2)
  {
    if(p1 == p2) return;
    if(p1 > p2) { int p = p1; p1 = p2; p2 = p; }

    lp1[numLines] = p1;
    lp2[numLines] = p2;
    
    numLines ++;
  }

  public void addFace(int p1, int p2, int p3)
  {
    fp1[numFaces] = p1;
    fp2[numFaces] = p2;
    fp3[numFaces] = p3;
    col[numFaces] = (p1*0x76D761A9 + p2*0x78421687 + p3*0xF76DE1A7) & 0x00FFFFFF;  //  Random colour if none specified
    
    numFaces ++;
  }

  public void addFace(int p1, int p2, int p3, int c)
  {
    fp1[numFaces] = p1;
    fp2[numFaces] = p2;
    fp3[numFaces] = p3;
    col[numFaces] = c;

    numFaces ++;
  }

  //---- addShape

  public void addShape(Shape s)
  {
    shapes.addElement(s);
  }

  //---- setTransform

  public void setTransform(Transform newtrans)
  {
    this.trans = null;
    this.trans = new Transform(newtrans);
  }

  //---- drawFace
  
  public void drawFace(double[] p1, double[] p2, double[] p3, int col)
  {
    if(p1[2] < 0.1 || p2[2] < 0.1 || p3[2] < 0.1) return;
    
    int a = (col>>24) & 0xFF;
    int r = (col>>16) & 0xFF;
    int g = (col>> 8) & 0xFF;
    int b = (col    ) & 0xFF;
    
    r = (int)(r*fog + scene.bg_r*(1-fog));
    g = (int)(g*fog + scene.bg_g*(1-fog));
    b = (int)(b*fog + scene.bg_b*(1-fog));
    
    col = (a<<24)+(r<<16)+(g<<8)+b;

    scene.drawFace(p1, p2, p3, col);
  }

  //---- render

  public void render()
  {
    int i;

    //  Set up transformation

    scene.transform(trans);

    //  Calculate position of shape origin
    
    scene.transformPoint(op, ot, os);

    //  Calculate fogging for this object

    fog = 1 - (os[2]-fog_start)/fog_dist;
    
    if(fog < 0) fog = 0;
    if(fog > 1) fog = 1;

    //  Render sub-shapes
    
    for(i=0; i<shapes.size(); i++) ((Shape)shapes.elementAt(i)).render();
    
    //  Transform points
    
    for(i=0; i<numPoints; i++) scene.transformPoint(p[i], t[i], s[i]);

    //  Render this shape

    if(numLines+numFaces == 0) for(i=0; i<numPoints; i++) scene.plotPoint(s[i][0], s[i][1]);
    if(numFaces == 0)          for(i=0; i<numLines ; i++) scene.drawLine (s[lp1[i]][0], s[lp1[i]][1], s[lp2[i]][0], s[lp2[i]][1]);
                               for(i=0; i<numFaces ; i++) drawFace (s[fp1[i]], s[fp2[i]], s[fp3[i]], col[i]);
    
    //  Revert transformation
    
    scene.untransform();
  }
  
}



//---- This scene ------------------------------------------------------------//



//---- Square

class Square extends Shape
{
  public Square(Scene sc)
  {
    super(sc);

    addPoint(-1, 0, -1);
    addPoint(-1, 0,  1);
    addPoint( 1, 0,  1);
    addPoint( 1, 0, -1);
    
    addLine(0, 1);
    addLine(1, 2);
    addLine(2, 3);
    addLine(3, 4);

    addFace(0, 1, 2);
    addFace(2, 3, 0);
  }
}



//---- Cube

class Cube extends Shape
{
  public Cube(Scene sc)
  {
    super(sc);
    makeCube(sc, 0x999999);  
  }

  public Cube(Scene sc, int col)
  {
    super(sc);
    makeCube(sc, col);
  }
  
  private void makeCube(Scene sc, int col)
  {
//    System.out.println("Creating Cube");

    addPoint(-1, -1, -1);  //  0
    addPoint(-1, -1,  1);  //  1
    addPoint(-1,  1, -1);  //  2
    addPoint(-1,  1,  1);  //  3
    addPoint( 1, -1, -1);  //  4
    addPoint( 1, -1,  1);  //  5
    addPoint( 1,  1, -1);  //  6
    addPoint( 1,  1,  1);  //  7
    
    addLine(0, 1);
    addLine(0, 2);
    addLine(0, 4);
    addLine(1, 3);
    addLine(1, 5);
    addLine(2, 3);
    addLine(2, 6);
    addLine(3, 7);
    addLine(4, 5);
    addLine(4, 6);
    addLine(5, 7);
    addLine(6, 7);

    addFace(0, 1, 3, col);  //  0 1 3
    addFace(0, 5, 1, col);  //  0 1 5 !
    addFace(0, 3, 2, col);  //  0 2 3 !
    addFace(0, 2, 6, col);  //  0 2 6
    addFace(0, 4, 5, col);  //  0 4 5
    addFace(0, 6, 4, col);  //  0 4 6 !
    addFace(1, 7, 3, col);  //  1 3 7 !
    addFace(1, 5, 7, col);  //  1 5 7
    addFace(2, 3, 7, col);  //  2 3 7
    addFace(2, 7, 6, col);  //  2 6 7 !
    addFace(4, 7, 5, col);  //  4 5 7 !
    addFace(4, 6, 7, col);  //  4 6 7

  }
}



//---- Checks

class Checks extends Shape
{
  int size = 4;

  public Checks(Scene sc, int col1, int col2)
  {
    super(sc);
    
    int i, j;
    
    for(j=0; j<size+1; j++) for(i=0; i<size+1; i++)
    {
      addPoint(i-0.5*size, 0, j-0.5*size);
//      System.out.println(numPoints+" "+i+" "+j);
    }

    for(j=0; j<size; j++) for(i=0; i<size; i++)
    {
      int p = i+j*(size+1);
      int col = ((i^j)&1)==1 ? col1 : col2;
      addFace(p+size+2, p+1, p, col);
      addFace(p, p+size+1, p+size+2, col);
    }
  }

}



//---- Pillar

class Pillar extends Shape
{
  public Pillar(Scene sc)
  {
    super(sc);
//    System.out.println("Creating Pillar");

    Shape s;

    s = new Cube(sc, 0x336699);
    s.trans.scale(1.0, 0.2, 1.0);
    s.trans.translate(0.0, -2.0, 0.0);
    addShape(s);

    s = new Cube(sc, 0x003366);
    s.trans.scale(0.6, 1.8, 0.6);
    addShape(s);

    s = new Cube(sc, 0x336699);
    s.trans.scale(1.0, 0.2, 1.0);
    s.trans.translate(0.0, 2.0, 0.0);
    addShape(s);
  }
}



//---- LinkShape1

class LinkShape1 extends Shape
{
  public LinkShape1(Scene sc)
  {
    super(sc);
    
    Shape s;

    s = new Cube(sc, 0xFFCC00);
    s.trans.scale(0.3, 0.3, 0.3);
    s.trans.translate(-0.7, 0, 0);
    addShape(s);  

    s = new Cube(sc, 0xFFCC00);
    s.trans.scale(0.3, 0.3, 0.3);
    s.trans.translate( 0.7, 0, 0);
    addShape(s);  

    s = new Cube(sc, 0xFFFF00);
    s.trans.scale(0.3, 0.3, 0.3);
    s.trans.translate(0, -0.7, 0);
    addShape(s);  

    s = new Cube(sc, 0xFFFF00);
    s.trans.scale(0.3, 0.3, 0.3);
    s.trans.translate(0,  0.7, 0);
    addShape(s);  

    s = new Cube(sc, 0xFF9900);
    s.trans.scale(0.3, 0.3, 0.3);
    s.trans.translate(0, 0, -0.7);
    addShape(s);  

    s = new Cube(sc, 0xFF9900);
    s.trans.scale(0.3, 0.3, 0.3);
    s.trans.translate(0, 0,  0.7);
    addShape(s);  

    s = new Cube(sc, 0xCC6600);
    s.trans.scale(0.3, 0.3, 0.3);
    s.trans.translate(0, 0, 0);
    addShape(s);  
  }

}



//---- LinkShape2

class LinkShape2Corner extends Shape
{
  public LinkShape2Corner(Scene sc, int col)
  {
    super(sc);
    
    addPoint(1, 1, 1);
    addPoint(1, 0, 0);
    addPoint(0, 1, 0);
    addPoint(0, 0, 1);
    
    addFace(0, 1, 2, col);
    addFace(0, 2, 3, col);
    addFace(0, 3, 1, col);
  }

}

class LinkShape2 extends Shape
{
  public LinkShape2(Scene sc)
  {
    super(sc);
    
    int col1 = 0xFFFF00;
    int col2 = 0x0000FF;

    Shape s;

    s = new LinkShape2Corner(sc, col1);
    addShape(s);  

    s = new LinkShape2Corner(sc, col2);
    s.trans.rotateY(3.1415926*0.5);
    addShape(s);  

    s = new LinkShape2Corner(sc, col1);
    s.trans.rotateY(3.1415926*1.0);
    addShape(s);  

    s = new LinkShape2Corner(sc, col2);
    s.trans.rotateY(3.1415926*1.5);
    addShape(s);  

    s = new LinkShape2Corner(sc, col2);
    s.trans.rotateX(3.1415926*0.5);
    addShape(s);  

    s = new LinkShape2Corner(sc, col1);
    s.trans.rotateX(3.1415926*0.5);
    s.trans.rotateY(3.1415926*0.5);
    addShape(s);  

    s = new LinkShape2Corner(sc, col2);
    s.trans.rotateX(3.1415926*0.5);
    s.trans.rotateY(3.1415926*1.0);
    addShape(s);  

    s = new LinkShape2Corner(sc, col1);
    s.trans.rotateX(3.1415926*0.5);
    s.trans.rotateY(3.1415926*1.5);
    addShape(s);  

  }

}



//---- LinkShape3

class LinkShape3 extends Shape
{
  public LinkShape3(Scene sc)
  {
    super(sc);
    
    Shape s;

    for(int i=0; i<8; i++)
    {
      s = new Cube(sc, 0xFF0000 + i*0x001F1F);
      s.trans.scale(0.2, 0.4, 0.2);
      s.trans.translate(-0.8, 0, 0);
      s.trans.rotateY(i*2*3.1415926/8);
      addShape(s);
    }
  }
}



//---- LinkShape4

class LinkShape4 extends Shape
{
  public LinkShape4(Scene sc)
  {
    super(sc);
    
    Shape s;

    for(int i=0; i<8; i++)
    {
      int b0 = (i   )&1;
      int b1 = (i>>1)&1;
      int b2 = (i>>2)&1;

      s = new Cube(sc, b0*0xFF0000 + b1*0x00FF00 + b2*0x0000FF);
      s.trans.scale(0.4, 0.4, 0.4);
      s.trans.translate(b0*1.2-0.6, b1*1.2-0.6, b2*1.2-0.6);
      addShape(s);
    }
  }
}



//---- LinkShape5

class LinkShape5 extends Shape
{
  public LinkShape5(Scene sc)
  {
    super(sc);
    
    Shape s;

    for(int i=0; i<12; i++)
    {
      s = new Cube(sc, 0x660099 + i*0x001100);
      s.trans.scale(0.2, 0.2, 0.2);
      s.trans.translate(-0.8, (i-6)*0.14, 0);
      s.trans.rotateY(i*2*3.1415926/7);
      addShape(s);
    }
  }
}



//---- LinkShape6

class LinkShape6 extends Shape
{
  public LinkShape6(Scene sc)
  {
    super(sc);
    
    Shape s;

    for(int i=0; i<64; i+=5)
    {
      s = new LinkShape1(sc);//Cube(sc, 0x660099 + i*0x001100);

      s.trans.rotateZ( i*3.14159/52);
      s.trans.rotateX( i*3.14159/41);
      
      double scale = 0.9+0.6*Math.sin(i*3.14159/7);
      s.trans.scale(scale, scale, scale);      

      s.trans.translate(3+2*Math.sin(i*3.14159/12), 0*(i-32.0)/32.0, 0);
      s.trans.rotateY( i*3.14159/37);
      addShape(s);
    }
  }
}



//---- MainShape

class MainShape extends Shape
{
  Shape[] st;  
  boolean reflections = false;
  boolean rocky_floor = false;

  public MainShape(Scene sc)
  {
    super(sc);

    Shape s;
    int i, j;

    //- Reflected pillars

    if(reflections)
    {
      for(i=0; i<5; i++)
      {
        s = new Pillar(sc);
        s.trans.translate(-4.5, -4.4, 0.0);
        s.trans.rotateY(3.14159*2.0*i/5.0);
        addShape(s);
      }
    }
  
    //- Floor

    int col1 = 0xFFFFFF;
    int col2 = 0x000000;
    
    if(reflections) { col1 += 0x80000000; }
    if(reflections) { col2 += 0x80000000; }

    for(j=0; j<2; j++) for(i=0; i<2; i++)
    {
      if(rocky_floor)
      {
        s = new LinkShape6(sc);//Checks(sc, col1, col2);
        s.trans.scale(1, 0.3, 1);
        s.trans.translate(8*i-4, -2.0, 8*j-4);
        addShape(s);
      }

      s = new Checks(sc, col1, col2);
      s.trans.scale(2, 1, 2);
      s.trans.translate(8*i-4, -2.2, 8*j-4);
      addShape(s);
    }

    //- Pillars with things on them

    st = new Shape[5];

    st[0] = new LinkShape1(sc);
    st[1] = new LinkShape2(sc);
    st[2] = new LinkShape3(sc);
    st[3] = new LinkShape4(sc);
    st[4] = new LinkShape5(sc);

    for(i=0; i<5; i++)
    {
      s = new Pillar(sc);
      s.trans.translate(-4.5, 0.0, 0.0);
      s.trans.rotateY(3.14159*2.0*i/5.0);
      addShape(s);
      
      st[i].trans.scale(0.5, 0.5, 0.5);
      st[i].trans.translate(-4.5, 0.0, 0.0);
      st[i].trans.rotateY(3.14159*2.0*i/5.0);
      st[i].trans.translate(0.0, 2.95, 0.0);
      addShape(st[i]);
    }
    
  }

  int frameno = 0;

  public void render()
  {
    int i;

    double[] scale = {1, 1, 1, 1, 1};
    i = (frameno / 25)%5;

    scale[i] = 1 + Math.exp(-(frameno%25)*0.1)*Math.sin(frameno*3.14159/5);
    
    frameno ++;
    
    for(i=0; i<5; i++)
    {
      st[i].trans.identity();

      if(i != 0) st[i].trans.rotateX(scene.time * 0.18428);
      if(i != 1) st[i].trans.rotateY(scene.time * 0.38291);
      if(i != 2) st[i].trans.rotateZ(scene.time * 0.29219);
      if(i != 3) st[i].trans.rotateY(scene.time * 0.50291);
      if(i != 4) st[i].trans.rotateX(scene.time * 0.18428);

      st[i].trans.scale(1, scale[i], 1);

      st[i].trans.scale(0.5, 0.5, 0.5);
      st[i].trans.translate(-4.5, 0.0, 0.0);
      st[i].trans.rotateY(3.14159*2.0*i/5.0);
      st[i].trans.translate(0.0, 2.85, 0.0);
    }
    
    super.render();
  }

}



//---- MyScene

class MyScene extends Scene
{
  Shape s;
  Transform t;
  
  double rx=-0.6, ry=0, rz=0;
  
  public MyScene()
  {
//    System.out.println("Creating MyScene");

    s = new MainShape(this);
    addShape(s);
  }

  public void render()
  {
    t = new Transform();

    rx = -0.45 + 0.15 * Math.sin(time*3.14159/7.391);
    ry = 0.257 * time;
    rz = 0.157 * time;

/*
    t.rotateX(rx);
    t.rotateY(ry);
    t.rotateZ(rz);
    t.rotateX(rx-ry);
    t.rotateZ(rz-rx);
    t.rotateX(3.1415926*0.5);
*/

    t.rotateY(ry);
    t.rotateX(rx);    
    s.setTransform(t);

    super.render();  
  }

}



//---- Applet ----------------------------------------------------------------//

public class Site3D extends tinyptc
{
  MyScene scene;

  public void main(int width,int height)
  {
    int size = width * height;
    int buf[] = new int[size];

    scene = new MyScene();

    scene.setBuffer(buf, width, height);
    
    while (true)
    {
      scene.render();
      update(buf);
    }
  }

  int mouse_x = 0, mouse_y = 0;
  int mouse_oldx = 0, mouse_oldy = 0;
  boolean mouse_drag = true;

  public boolean mouseDown(Event evt, int x, int y)
  {
    mouse_oldx = x;
    mouse_oldy = y;
    mouse_x = x;
    mouse_y = y;
    mouse_drag = true;
    return true;
  }

  public boolean mouseDrag(Event e, int x, int y)
  {
    scene.ry += (x-mouse_x)*0.01;
    scene.rx -= (y-mouse_y)*0.01;
    
    if(scene.rx > -0.1) scene.rx = -0.1;
    if(scene.rx < -1.2) scene.rx = -1.2;

    mouse_x = x;
    mouse_y = y;
    return true;
  }

  public boolean mouseUp(Event evt, int x, int y)
  {
    mouse_drag = false;
    return true;
  }

}






class Site3D123 extends java.applet.Applet implements Runnable
{
  //- Data

  Thread runThread = null;

  static int imw = 320, imh = 240;
  Image im = null;
  Graphics img = null;
  int[][] buffer;

  MyScene myScene;

  //- Main code

  public void init()
  {
    System.out.println("LKS 3D Site v0.01");

    myScene = new MyScene();
  }

  public void start()
  {
    if(runThread == null)
    {
      runThread = new Thread(this);
      runThread.start();
    }
  }

  public void stop()
  {
    if(runThread != null)
    {
      runThread.stop();
      runThread = null;
    }
  }

  public void run()
  {
    imw = size().width;
    imh = size().height;

    im = createImage(imw, imh);
    img = im.getGraphics();

    img.setColor(Color.black);
    img.fillRect(0, 0, imw, imh);

//    myScene.setBuffer(img, imw, imh);
    
    while(true)
    {
      repaint();
      try { runThread.sleep(10); } catch (InterruptedException e) { return; }
    }
  }

  public void update(Graphics g)
  {
    paint(g);
  }

  int k = 0;

  public void paint(Graphics g)
  {
    if(im != null)
    {
      img.setColor(Color.black);
      img.fillRect(0, 0, imw, imh);
      
      myScene.render();

      g.drawImage(im, 0, 0, this);
      g.setColor((k++&1) == 0 ? Color.blue : Color.yellow);
      g.fillRect(0, 0, 10, 10);
    }
  }



  int mouse_x = 0, mouse_y = 0;
  int mouse_oldx = 0, mouse_oldy = 0;
  boolean mouse_drag = true;



  public boolean mouseDown(Event evt, int x, int y)
  {
    mouse_oldx = x;
    mouse_oldy = y;
    mouse_x = x;
    mouse_y = y;
    mouse_drag = true;
    return true;
  }


  
  public boolean mouseDrag(Event e, int x, int y)
  {
    mouse_x = x;
    mouse_y = y;

    repaint();
    return true;
  }



  public boolean mouseUp(Event evt, int x, int y)
  {
    mouse_drag = false;
    return true;
  }
  

}



