package pipeline;

import java.util.Vector;

import pipeline.misc.Fragment;
import pipeline.misc.Vertex;

/**
 * This class is responsible for interpolating the attributes given to it across
 * the triangle, and handing off the correctly interpolated values to the
 * fragment processor. Clipping also happens within this class.
 * 
 * @author ags
 */
public class Rasterizer {

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

  /** Width of the image */
  protected int nx;

  /** Height of the image */
  protected int ny;

  /** vertex data for triangle setup */
  protected float[][] vData;

  // state arrays for rasterization
  // data is [e0, e1, e2, z/w, a0/w, a1/w, ..., 1/w]
  protected float[] xInc;

  protected float[] yInc;

  protected float[] rowData;

  protected float[] pixData;

  protected float[] fragData;

  /**
   * The only constructor.
   * 
   * @param newNa The number of user defined attributes.
   * @param newNx The width of the image.
   * @param newNy The height of the image.
   */
  public Rasterizer(int newNa, int newNx, int newNy) {

    na = newNa;
    nx = newNx;
    ny = newNy;

    vData = new float[3][5 + na];
    xInc = new float[5 + na];
    yInc = new float[5 + na];
    rowData = new float[5 + na];
    pixData = new float[5 + na];
    fragData = new float[1 + na];
  }

  protected void rasterize(Vertex[] f, Vector output) {

    for (int iv = 0; iv < 3; iv++) {
      float invW = 1.0f / f[iv].v.w;
      f[iv].v.scale(invW);
      for (int k = 0; k < 3; k++)
        vData[iv][k] = (k == iv ? 1 : 0);
      vData[iv][3] = f[iv].v.z;
      for (int ia = 0; ia < na; ia++)
        vData[iv][4 + ia] = invW * f[iv].attrs[ia];
      vData[iv][4 + na] = invW;
    }

    int ixMin = Math.max(0, ceil(min(f[0].v.x, f[1].v.x, f[2].v.x)));
    int ixMax = Math.min(nx - 1, floor(max(f[0].v.x, f[1].v.x, f[2].v.x)));
    int iyMin = Math.max(0, ceil(min(f[0].v.y, f[1].v.y, f[2].v.y)));
    int iyMax = Math.min(ny - 1, floor(max(f[0].v.y, f[1].v.y, f[2].v.y)));
    if (ixMin > ixMax || iyMin > iyMax)
      return;

    float dx1 = f[1].v.x - f[0].v.x, dy1 = f[1].v.y - f[0].v.y;
    float dx2 = f[2].v.x - f[0].v.x, dy2 = f[2].v.y - f[0].v.y;
    float det = dx1 * dy2 - dx2 * dy1;
    if (det < 0)
      return;

    for (int k = 0; k < 5 + na; k++) {
      float da1 = vData[1][k] - vData[0][k];
      float da2 = vData[2][k] - vData[0][k];
      xInc[k] = (da1 * dy2 - da2 * dy1) / det;
      yInc[k] = (da2 * dx1 - da1 * dx2) / det;
      rowData[k] = vData[0][k] + (ixMin - f[0].v.x) * xInc[k] + (iyMin - f[0].v.y) * yInc[k];
    }

    for (int iy = iyMin; iy <= iyMax; iy++) {
      for (int k = 0; k < 5 + na; k++)
        pixData[k] = rowData[k];
      for (int ix = ixMin; ix <= ixMax; ix++) {
        if (pixData[0] >= 0 && pixData[1] >= 0 && pixData[2] >= 0) {
          fragData[0] = pixData[3];
          float w = 1.0f / pixData[4 + na];
          for (int ia = 0; ia < na; ia++)
            fragData[1 + ia] = pixData[4 + ia] * w;
            Fragment newFrag = FragmentPool.get();
            newFrag.set(ix, iy, fragData);
            output.add(newFrag);
        }
        for (int k = 0; k < 5 + na; k++)
          pixData[k] += xInc[k];
      }
      for (int k = 0; k < 5 + na; k++)
        rowData[k] += yInc[k];
    }
  }

  // Utility routines for clarity

  protected static int ceil(float x) {

    return (int) Math.ceil(x);
  }

  protected static int floor(float x) {

    return (int) Math.floor(x);
  }

  protected static float min(float a, float b, float c) {

    return Math.min(Math.min(a, b), c);
  }

  protected static float max(float a, float b, float c) {

    return Math.max(Math.max(a, b), c);
  }

}
