package cgp.render;

import cgp.basics.Shape3D;
import cgp.basics.Face3D;
import cgp.basics.Vertex3D;
import cgp.basics.LightParams;

import cgp.math.Point4f;

import java.util.Enumeration;

/**
 * Ein Renderer, der 
 * - BFC durchfuehrt,
 * - die uebrigen Flaechen am Frustum clippt
 * - mit FlatShading darstellt.
 *   
 * Diese Version beruecksichtigt, dass es keinen Unterschied zwischen
 * Flat und CurvedFace bzw. -Vertex mehr gibt
 *
 * Diese Version benutzt einen BFCuller.
 *
 * @author Olaf Mueller
 * @author Axel Block
 * @version 1.2 22..07.2002
 */
public class GreyFlatRenderer implements Renderer {
 
  public static final float EPS = -0.001f;

  public WCClipper clipper; 
  public BFCuller bfculler;
  public View view;
  public Lighting lights = null;

  public CGCanvas cgc = null;

  int [] colors;

  public GreyFlatRenderer() {
    clipper = new WCClipper();
    bfculler = new BFCuller();
    colors = new int[256];
    for (int i=0; i<256; i++) {
      colors[i]  = 255<<24;
      colors[i] |= i<<16;
      colors[i] |= i<<8;
      colors[i] |= i;
    }
  }

  /**
   * Zentrale Methode; loest das Rendern der Szene aus. Die uebergebene View 
   * liefert alle Informationen, die der Renderer braucht, um die Szene
   * darzustellen.
   * Die Uebergabe der View ermoeglicht den Einsatz derselben
   * Renderer-Instanz in mehreren Views.
   */
  public void renderScene(View view) {
    Scene scene = view.scene;
    lights = scene.lights;
    Camera cam = view.cam;

    Shape3D shape;                               // aktueller Koerper
    Face3D face;                                 // aktuelle Flaeche

    view.cgc.clearBuffer();                      // neues Frame vorbereiten
    view.cgc.clearZBuffer();                     // neues Frame vorbereiten
    Enumeration e = scene.getVisibleShapes(cam); // Liste sichtbarer Koerper
    while(e.hasMoreElements()) {                 // Liste durchlaufen
      shape = (Shape3D)e.nextElement();          // naechsten Koerper holen
      if (shape == cam) continue;
      for(int j=0; j<shape.count; j++) {         // Alle Flaechen
        face = shape.faces[j];
        renderFace(face, view);                  // Flat or Curved
      } // Fuer alle Flaechen
    } // Fuer alle Koerper
    view.cgc.refresh();                          // Frame auf den Schirm
         
  }

  public void renderFace(Face3D f, View view) {
    Camera cam = view.cam;
    cgc = view.cgc;

    LightParams lp = new LightParams();

    Vertex3D[] knoten;                            // sichtbare Punkte
    int kc=0;                                    // Anzahl sichtb. Punkte
    f = bfculler.bfc(f, cam);                    // Flaeche holen u. bfc
    if(f != null) {
      knoten = clipper.clip(f, cam);             // Flaeche clippen
      if((knoten != null) && (knoten.length >2)){// Flaeche z.T. sichtbar?
        kc = knoten.length;

        Point4f p0 = new Point4f();              // Dreieckspunkte
        Point4f p1 = new Point4f();
        Point4f p2 = new Point4f();
        Point4f tmp;
        float l0,l1,l2;                          // Licht-intensitaeten
            
        view.WC2DC.multiply(knoten[0].wc, p0);
        p0.homogenize();                         // teile durch homogene Koord.
        l0 = lights.getLight(knoten[0].wc, knoten[0].wc_normal ,lp);
        
        view.WC2DC.multiply(knoten[1].wc, p2);
        p2.homogenize();                         // teile durch homogene Koord.
        l2 = lights.getLight(knoten[1].wc, knoten[1].wc_normal ,lp);

        for(int k=2; k<kc; k++) {                // Fuer alle Punkte
        //for(int k=2; k<3; k++) {               // Fuer ersten Punkt
          tmp = p1;
          p1 = p2;
          p2 = tmp;
          l1 = l2;
          view.WC2DC.multiply(knoten[k].wc, p2);
          p2.homogenize();                       // teile durch homogene Koord.
          l2 = lights.getLight(knoten[k].wc, knoten[k].wc_normal ,lp);

          shade(p0,p1,p2, (l0+l1+l2)/3.0f);
        } // fuer alle Punkte dieser Flaeche
      } // Flaeche nicht entartet und mind. z.T. sichtbar
    } // Flaeche nicht abgewandt
  }

  // Faerbt das Dreieck PA PB PC mit Intensitaet I
  private void shade(Point4f pa, Point4f pb, Point4f pc, float I) {
    Point4f pA, pB, pC;    // pA.y <= pB.y <= pC.y

    float szAB, szAC, szBC;  // Steigungen in Dimension z

    int errorAB=0, errorAC=0, errorBC=0;
    int miAB=0, miAC=0, miBC=0;
    int mfAB=0, mfAC=0, mfBC=0;
    int schrittAB=0, schrittAC=0, schrittBC=0;

    int pAx, pBx, pCx;       // X-Werte in Ganzahlarithmetik
    int pAy, pBy, pCy;       // Y-Werte in Ganzahlarithmetik

    int dxAB, dxAC, dxBC;
    int dyAB, dyAC, dyBC;

    Point4f p1, p2;      // Laufvariablen

    if (pa.y <= pb.y) {
      pA = pa; pB = pb;
    }
    else {
      pA = pb; pB = pa;
    }

    if (pc.y >= pB.y) {
      pC = pc;
    }
    else {
      pC = pB;
      if (pc.y >= pA.y) {
        pB = pc;
      }
      else {
        pB = pA; pA = pc;
      }
    }

    pAx = (int)(pA.x+0.5f);
    pAy = (int)(pA.y+0.5f);
    pBx = (int)(pB.x+0.5f);
    pBy = (int)(pB.y+0.5f);
    pCx = (int)(pC.x+0.5f);
    pCy = (int)(pC.y+0.5f);
    dxAB = pBx - pAx;
    int xIncAB = dxAB<0? -1: +1;
    dyAB = pBy - pAy;
    dxAC = pCx - pAx;
    int xIncAC = dxAC<0? -1: +1;
    dyAC = pCy - pAy;
    dxBC = pCx - pBx;
    int xIncBC = dxBC<0? -1: +1;
    dyBC = pCy - pBy;

    if (pAy != pCy) { // Kante AC nicht horizontal -> Steigungen berechnen 
    
      szAC = (pC.z - pA.z) / (pC.y - pA.y);

      //sxAC = (pC.x - pA.x) / (pC.y - pA.y);
      errorAC=-dyAC;
      miAC = dxAC/dyAC;
      mfAC = 2*(dxAC%dyAC);
      if(mfAC<0) mfAC*= -1;
      schrittAC = -2*dyAC;
    }
    else { // Steigungen werden nicht benoetigt - ausser fuer den Compiler ;-)
      szAC = 0f;
    }

    //p2 = new Point4f(pA);  // p2 laeuft von A nach C
    int xAC = pAx;
    float zAC = pA.z;

    if (pAy == pBy) { // Kante AB horizontal
    } else {

      szAB = (pB.z - pA.z) / (pB.y - pA.y);

      //sxAB = (pB.x - pA.x) / (pB.y - pA.y);  // Steigungen berechnen
      errorAB=-dyAB;
      miAB = dxAB/dyAB;
      mfAB = 2*(dxAB%dyAB);
      if(mfAB<0) mfAB*= -1;
      schrittAB = -2*dyAB;

      //p1 = new Point4f(pA); // p1 laeuft von A nach B
      int xAB = pAx;
      float zAB = pA.z;

      for (int y = pAy; y < pBy; y++) {
        putScanline(y, xAC, xAB, zAC, zAB, I);

        //p2.x += sxAC;
        xAC += miAC;
        errorAC += mfAC;
        if(errorAC > 0) {
          xAC+=xIncAC;
          errorAC += schrittAC;
        }

        //p2.z += szAC;
        zAC += szAC;

        //p1.x += sxAB;
        xAB += miAB;
        errorAB += mfAB;
        if(errorAB > 0) {
          xAB+=xIncAB;
          errorAB += schrittAB;
        }

        //p1.z += szAB;
        zAB += szAB;
      }
    }
    
    if (pBy == pCy) {  // Kante BC horizontal
    } else {


      szBC = (pC.z - pB.z) / (pC.y - pB.y);

      //sxBC = (pC.x - pB.x) / (pC.y - pB.y);  // Steigungen berechnen
      errorBC = -dyBC;
      miBC = dxBC/dyBC;
      mfBC = 2*(dxBC%dyBC);
      if(mfBC<0) mfBC*= -1;
      schrittBC = -2*dyBC;

      //p1 = new Point4f(pB);      // p1 laeuft von B nach C (p2 laeuft weiter von A nach C)
      int xBC = pBx;
      float zBC = pB.z;

      for (int y = pBy; y < pCy; y++) {
        // Reihenfolge der X-Werte tauschen?
        putScanline(y, xBC,  xAC, zBC, zAC, I);

        //p1.x += sxBC;
        xBC += miBC;
        errorBC += mfBC;
        if(errorBC > 0) {
          xBC+=xIncBC;
          errorBC += schrittBC;
        }

        //p1.z += szBC;
        zBC += szBC;

        //p2.x += sxAC;
        xAC += miAC;
        errorAC += mfAC;
        if(errorAC > 0) {
          xAC+=xIncAC;
          errorAC += schrittAC;
        }

        //p2.z += szAC;
        zAC += szAC;
      }
    }
  }

  private void putScanline(int y, // Berechnet waagerechte Linie in Hoehe y
    int   x1, int   x2,                          // von  x1 nach x2
    float z1, float z2, float I) {               // mit Z-Komponenten z1, z2
    
    int col = colors[(int)(I*256)];

    if (x1 == x2) {        // Linie ist Punkt
      if(z1 < 0.1f) {
        cgc.setPixelWithZ(x1, y, z1, 
                          colors[Math.min(255, (int)((I+1f-10f*z1)*256f))]);
      }
      else {
        cgc.setPixelWithZ(x1, y, z1, col);
      }
    }
    else {
      int  xl, xr;
      float  zl, zr;

      if (x1 < x2) {
        xl = x1; xr = x2; zl = z1; zr = z2;
      }
      else {
        xl = x2; xr = x1; zl = z2; zr = z1;
      }

      float sz = (zr - zl) / (xr - xl); // Steigung berechnen
      float z = zl;                     // x laeuft von Links nach Rechts

      for (int x = xl; x <= xr; x++) {
        //cgc.setScanlineWithP(xl, xr, y, col);
        if(z < 0.1f) {
          cgc.setPixelWithZ(x, y, z, colors[Math.min(255, (int)((I+1f-10f*z)*256f))]);
        }
        else {
          cgc.setPixelWithZ(x, y, z, col);
        }
        z += sz;
      }
    }
  }
}