package pipeline;

import pipeline.misc.Vertex;

/**
 * @author Beowulf
 */
public class Clipper {

  /** Number of user-supplied attributes */
  protected int na;

  /** Temporaries for clipping */
  protected Vertex[] fClip = { new Vertex(), new Vertex(), new Vertex() };

  /**
   * Initializes a new clipper with a given number of attributes.
   * 
   * @param newNa The number of attributes.
   */
  public Clipper(int newNa) {

    na = newNa;

    fClip[0].setAttrs(newNa);
    fClip[1].setAttrs(newNa);
    fClip[2].setAttrs(newNa);
  }

  /**
   * The interface for the clipper. Each triangle will be clipped against the
   * near plane, resulting in either 0, 1, or 2 triangles. The number of
   * triangles will be returned, and the resulting vertices will be stored back
   * in f if only one triangle results, or in both f and fOut if two triangles
   * result.
   * 
   * @param f The vertices from where the values will be interpolated. Also
   *          holds one set of clipped vertices if there are any.
   * @param fOut Holds a second set of cipped vertices, if they exist.
   * @return The number of resulting triangles.
   */
  public int clip(Vertex[] f, Vertex[] fOut) {

    // Clip the triangle against the near plane, which is z == 0 in homogeneous
    // screen space.

    int code = ((f[0].v.z > 0) ? 1 : 0) | ((f[1].v.z > 0) ? 2 : 0) | ((f[2].v.z > 0) ? 4 : 0);

    if (code == 0) // all three out
      return 0;

    else if (code == 1 || code == 2 || code == 4) { // one in, two out
      int kIn, kOut1, kOut2;

      if (code == 1) { // only v[0] in
        kIn = 0;
        kOut1 = 1;
        kOut2 = 2;
      }
      else if (code == 2) { // only v[1] in
        kIn = 1;
        kOut1 = 2;
        kOut2 = 0;
      }
      else if (code == 4) { // only v[2] in
        kIn = 2;
        kOut1 = 0;
        kOut2 = 1;
      }
      else { // error
        return -1;
      }

      float a1 = -f[kIn].v.z / (f[kOut1].v.z - f[kIn].v.z);
      float a2 = -f[kIn].v.z / (f[kOut2].v.z - f[kIn].v.z);

      fClip[kIn].v.set(f[kIn].v);
      fClip[kOut1].v.set((1 - a1) * f[kIn].v.x + a1 * f[kOut1].v.x, (1 - a1) * f[kIn].v.y + a1 * f[kOut1].v.y, 0.0f, (1 - a1) * f[kIn].v.w + a1 * f[kOut1].v.w);
      fClip[kOut2].v.set((1 - a2) * f[kIn].v.x + a2 * f[kOut2].v.x, (1 - a2) * f[kIn].v.y + a2 * f[kOut2].v.y, 0.0f, (1 - a2) * f[kIn].v.w + a2 * f[kOut2].v.w);
      for (int ia = 0; ia < na; ia++) {
        fClip[kIn].attrs[ia] = f[kIn].attrs[ia];
        fClip[kOut1].attrs[ia] = (1 - a1) * f[kIn].attrs[ia] + a1 * f[kOut1].attrs[ia];
        fClip[kOut2].attrs[ia] = (1 - a2) * f[kIn].attrs[ia] + a2 * f[kOut2].attrs[ia];
      }

      f[kIn].v.set(fClip[kIn].v);
      f[kOut1].v.set(fClip[kOut1].v);
      f[kOut2].v.set(fClip[kOut2].v);
      for (int ia = 0; ia < na; ia++) {
        f[kIn].attrs[ia] = fClip[kIn].attrs[ia];
        f[kOut1].attrs[ia] = fClip[kOut1].attrs[ia];
        f[kOut2].attrs[ia] = fClip[kOut2].attrs[ia];
      }

      return 1;
    }
    else if (code == 6 || code == 5 || code == 3) { // two in, one out
      int kOut, kIn1, kIn2;
      if (code == 6) { // only v[0] out
        kOut = 0;
        kIn1 = 1;
        kIn2 = 2;
      }
      else if (code == 5) { // only v[1] out
        kOut = 1;
        kIn1 = 2;
        kIn2 = 0;
      }
      else if (code == 3) { // only v[2] out
        kOut = 2;
        kIn1 = 0;
        kIn2 = 1;
      }
      else { // error
        return -1;
      }

      float a1 = -f[kOut].v.z / (f[kIn1].v.z - f[kOut].v.z);
      float a2 = -f[kOut].v.z / (f[kIn2].v.z - f[kOut].v.z);

      fClip[kOut].v.set((1 - a1) * f[kOut].v.x + a1 * f[kIn1].v.x, (1 - a1) * f[kOut].v.y + a1 * f[kIn1].v.y, 0.0f, (1 - a1) * f[kOut].v.w + a1 * f[kIn1].v.w);
      fClip[kIn1].v.set(f[kIn1].v);
      fClip[kIn2].v.set(f[kIn2].v);
      for (int ia = 0; ia < na; ia++) {
        fClip[kOut].attrs[ia] = (1 - a1) * f[kOut].attrs[ia] + a1 * f[kIn1].attrs[ia];
        fClip[kIn1].attrs[ia] = f[kIn1].attrs[ia];
        fClip[kIn2].attrs[ia] = f[kIn2].attrs[ia];
      }
      // Set up one Triangle
      fOut[kIn1].v.set(fClip[kIn1].v);
      fOut[kIn2].v.set(fClip[kIn2].v);
      fOut[kOut].v.set(fClip[kOut].v);

      fOut[kIn1].setAttrs(na);
      fOut[kIn2].setAttrs(na);
      fOut[kOut].setAttrs(na);

      for (int ia = 0; ia < na; ia++) {
        fOut[kIn1].attrs[ia] = fClip[kIn1].attrs[ia];
        fOut[kIn2].attrs[ia] = fClip[kIn2].attrs[ia];
        fOut[kOut].attrs[ia] = fClip[kOut].attrs[ia];
      }

      fClip[kOut].v.set((1 - a1) * f[kOut].v.x + a1 * f[kIn1].v.x, (1 - a1) * f[kOut].v.y + a1 * f[kIn1].v.y, 0.0f, (1 - a1) * f[kOut].v.w + a1 * f[kIn1].v.w);
      fClip[kIn1].v.set(f[kIn2].v);
      fClip[kIn2].v.set((1 - a2) * f[kOut].v.x + a2 * f[kIn2].v.x, (1 - a2) * f[kOut].v.y + a2 * f[kIn2].v.y, 0.0f, (1 - a2) * f[kOut].v.w + a2 * f[kIn2].v.w);
      for (int ia = 0; ia < na; ia++) {
        fClip[kOut].attrs[ia] = (1 - a1) * f[kOut].attrs[ia] + a1 * f[kIn1].attrs[ia];
        fClip[kIn1].attrs[ia] = f[kIn2].attrs[ia];
        fClip[kIn2].attrs[ia] = (1 - a2) * f[kOut].attrs[ia] + a2 * f[kIn2].attrs[ia];
      }
      // Set up the other
      f[kIn1].v.set(fClip[kIn1].v);
      f[kIn2].v.set(fClip[kIn2].v);
      f[kOut].v.set(fClip[kOut].v);
      for (int ia = 0; ia < na; ia++) {
        f[kIn1].attrs[ia] = fClip[kIn1].attrs[ia];
        f[kIn2].attrs[ia] = fClip[kIn2].attrs[ia];
        f[kOut].attrs[ia] = fClip[kOut].attrs[ia];
      }

      return 2;
    }
    else { // code == 7 => all three in
      return 1;
    }
  }

}
