/*
   Extremely simple example of zbuffer algorithm
*/

public class zbuffer1 extends PixApplet
{
// THE SCENE

   // ALL VERTICES IN THE SCENE

   double V[][] = {
      {-.01,0,0, -.2,-.2,1},  // POINT AND NORMAL
      {.1,0,0, .2,-.2,1},
      {0,.11,0,  -.2,.2,1},
      {.08,.09,0,  .2,.2,1},
   };

   // ALL POLYGONS IN THE SCENE - THESE REFER TO THE VERTICES

   double polygon[][][] = {
      { V[0], V[1], V[2] },
      { V[1], V[2], V[3] },
   };

   // ALL LIGHT SOURCE DIRECTION VECTORS IN THE SCENE

   double L[][] = {
      {1,1,1}
   };

// THE RENDERER

   final double FAR_Z = -100000; // BACKGROUND Z
   double zbuffer[], normal[] = new double[3];

   // INITIALIZE EVERYTHING WHEN APPLET STARTS

   public void init() {
      super.init();
      zbuffer = new double[W*H];
      clearZbuffer();

      // NORMALIZE ALL POLYGON VERTEX NORMAL VECTOR LENGTHS

      for (int n = 0 ; n < polygon.length ; n++)
	 for (int i = 0 ; i < polygon[n].length ; i++) {
	    for (int k = 0 ; k < 3 ; k++)
	       normal[k] = polygon[n][i][k+3];
	    normalize(normal);
	    for (int k = 0 ; k < 3 ; k++)
	       polygon[n][i][k+3] = normal[k];
	 }

      // NORMALIZE ALL LIGHT SOURCE DIRECTION VECTOR LENGTHS

      for (int i = 0 ; i < L.length ; i++)
         normalize(L[i]);
   }

   // THIS IS CALLED EVERY FRAME TO RENDER THE IMAGE

   public void setPix(int frame) {
      clearZbuffer();
      for (int n = 0 ; n < polygon.length ; n++)
	 displayPolygon(polygon[n]);
   }

   // CLEAR THE ZBUFFER AND IMAGE

   void clearZbuffer() {
      for (int i = 0 ; i < zbuffer.length ; i++)
	 zbuffer[i] = FAR_Z;
      for (int i = 0 ; i < W*H ; i++)
	 pix[i] = pack(0,0,0);
   }

   // PROJECT ONE {X,Y,Z,Nx,Ny,Nz} VERTEX ONTO THE IMAGE PLANE,
   // AND SHADE IT, TO CREATE A {Px,Py,Pz,Red,Green,Blue} VECTOR

   double rgb[] = new double[3];

   void projectVertex(double pt[], double p[]) {

      // PROJECT VERTEX: X,Y,Z  ->  Px,Py,Pz

      double x = pt[0], y = pt[1], z = pt[2];
      x =  W * x + W/2;
      y = -W * y + H/2;
      p[0] = x;
      p[1] = y;
      p[2] = z;

      // SHADE VERTEX: Nx,Ny,Nz  ->  Red,Green,Blue

      doShading(pt, rgb);
      p[3] = rgb[0];
      p[4] = rgb[1];
      p[5] = rgb[2];
   }

   // VERY ELEMENTARY VERTEX SHADER (JUST A PLACE-HOLDER)
   // THIS SHOULD BE REPLACED BY A TRUE PHONG SHADING ALGORITHM

   void doShading(double pt[], double rgb[]) {
      rgb[0] = rgb[1] = rgb[2] = 0.2;
      for (int i = 0 ; i < L.length ; i++) {
         double d = pt[3]*L[i][0] + pt[4]*L[i][1] + pt[5]*L[i][2];
	 if (d > 0) {
            rgb[0] += .8 * d;
            rgb[1] += .8 * d;
            rgb[2] += .8 * d;
         }
      }
   }

   // DISPLAY ONE POLYGON

   double p[][] = new double[30][7]; // TEMPORARY STORAGE FOR SHADED PROJECTED POLYGON

   void displayPolygon(double polygon[][]) {
      int npts = polygon.length, x;
      double t, pL[] = new double[6], pR[] = new double[6];

      // FIND TOP AND BOTTOM SCAN LINES

      int ymin = H;
      int ymax = 0;
      for (int i = 0 ; i < npts ; i++) {
	 projectVertex(polygon[i], p[i]);
         ymin = Math.min(ymin, (int)p[i][1]);
         ymax = Math.max(ymax, (int)p[i][1]);
      }

      // LOOP THROUGH ALL SCAN LINES CONTAINING POLYGON

      for (int y = ymin ; y <= ymax ; y++) {

	 // FIND LEFT AND RIGHT EDGES OF POLYGON.
	 // P[0,1,2,3,4,5] IS {Px,Py,Pz,Red,Green,Blue}

         pL[0] = W;
         pR[0] = 0;
         for (int i = 0 ; i < npts ; i++) {
	    int j = (i+1) % npts;
	    if (p[i][1] < y != p[j][1] < y) {
	       t = (y - p[i][1]) / (p[j][1] - p[i][1]);
	       x = (int)lerp(t, p[i][0], p[j][0]);
	       if (x < pL[0])
		  for (int k = 0 ; k < 6 ; k++)
		     pL[k] = lerp(t, p[i][k], p[j][k]);
	       if (x >= pR[0])
		  for (int k = 0 ; k < 6 ; k++)
		     pR[k] = lerp(t, p[i][k], p[j][k]);
	    }
	 }

	 // LOOP THRU PIXELS IN SCAN LINE

         for (x = (int)pL[0] ; x < (int)pR[0] ; x++) {
	    int i = xy2i(x,y);

	    // INTERPOLATE TO GET Z AT THIS PIXEL

	    t = (x - pL[0]) / (pR[0] - pL[0]);
	    double z = lerp(t, pL[2], pR[2]);

	    // IF Z IS CLOSER, REPLACE PIXEL IN ZBUFFER AND IMAGE

            if (z > zbuffer[i]) {
               zbuffer[i] = z;
               pix[i] = pack(f2i(lerp(t, pL[3], pR[3])),
                             f2i(lerp(t, pL[4], pR[4])),
                             f2i(lerp(t, pL[5], pR[5])));
            }
         }
      }
   }

// GENERIC UTILITY ROUTINES

   // NORMALIZE A VECTOR

   void normalize(double v[]) {
      double s = Math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
      v[0] /= s;
      v[1] /= s;
      v[2] /= s;
   }

   // LINEAR INTERPOLATION

   double lerp(double t, double a, double b) {
      return a + t * (b - a);
   }

   // CONVERT A FLOATING POINT VALUE INTO A 0..255 INTEGER

   int f2i(double t) {
      return Math.max(0, Math.min(255, (int)(255 * t)));
   }
}

