import java.io.InputStream;
import java.io.StreamTokenizer;
import java.io.IOException;
import GfxLib.*;

public class Pipeline3D
{
  //
  // Class constants
  //
  public static final int MAX_OBJECTS = 8;
  public static final int MAX_LIGHT_SOURCES = 8;
  public static final int NUM_INTENSITY_LEVELS = 256;
  public static final int CHUNKSIZE = 100;

  public static boolean GOURAD = false;

  //
  // Member variables
  //
  private float             m_fov, m_s, m_t;
  private Light             m_ambientLight;
  private Light[]           m_lightSources;
  private int               m_numLightSources;
  private int               m_numObjects;
  private PipelineObject[]  m_objects;
  private Point3D           m_eyePt;
  private Point3D           m_lookatPt;
  private Point3D           m_upVector;

  // 
  // Class constructor
  //
  public Pipeline3D(Raster r)
  {
    Matrix3D view, proj;

    //
    // Initialize member variables
    //
    m_fov             = 90;
    m_t               = (float)Math.sin(Math.PI * (m_fov / 2) / 180);
    m_s               = (m_t * r.getHeight()) / (r.getWidth());
    m_ambientLight    = new Light(Light.AMBIENT, 0, 0, 0, .5f, .5f, .5f);
    m_lightSources    = new Light[MAX_LIGHT_SOURCES];
    m_numLightSources = 0;
    m_numObjects      = 0;
    m_objects         = new PipelineObject[MAX_OBJECTS];
    m_eyePt           = new Point3D(0f, 0f, -9f);
    m_lookatPt        = new Point3D(0f, 0f, 0f);
    m_upVector        = new Point3D(0f, 1f, 0f);

    for (int i = 0; i < MAX_OBJECTS; i++)
      m_objects[i] = null;
    
    for (int i = 0; i < MAX_LIGHT_SOURCES; i++)
      m_lightSources[i] = null;

    //
    // Establish view and perspective projection matrices
    // for all pipeline objects
    //
    view = new Matrix3D();
    view.lookAt(m_eyePt.x, m_eyePt.y, m_eyePt.z,
		m_lookatPt.x, m_lookatPt.y, m_lookatPt.z,
		m_upVector.x, m_upVector.y, m_upVector.z);
    PipelineObject.setViewMatrix(view);

    //System.out.println(view);

    proj = new Matrix3D(r, -1, -15);
    //proj = new Matrix3D(r, -1, -150);
    proj.perspective(-m_t, m_t, -m_s, m_s, -1, -15);
    //proj.perspective(-m_t, m_t, -m_s, m_s, -1, -20);
    PipelineObject.setPerspectiveMatrix(proj);

  }


  /************************************************************
   *                                                          *
   *                    PIPELINE STAGES                       *
   *                                                          *
   ***********************************************************/
   
  //
  // Clips the scene to the viewing volume, then
  // draws it to the specified Raster object
  //
  public final void clipAndDraw(Raster r)
  {
    for (int i = 0; i < m_numObjects; i++)
      m_objects[i].clipAndDraw(r);
  }

  public final void clipAndDrawGourad(Raster r)
  {
    for (int i = 0; i < m_numObjects; i++)
      m_objects[i].clipAndDrawGourad(m_ambientLight, r);
  }

  //
  // Illuminates the scene based on the specified light sources
  //
  public final void illumination()
  {
    for (int i = 0; i < m_numObjects; i++)
      m_objects[i].illumination(m_ambientLight, m_lightSources, 
				m_numLightSources, m_eyePt);
  }

  public final void illuminateGourad()
  {
    for (int i = 0; i < m_numObjects; i++)
      m_objects[i].illuminateGourad(m_ambientLight, m_lightSources, 
				    m_numLightSources, m_eyePt);
  }

  //
  // Applies the specified modeling transformations to the scene
  //
  public final void modelingTransforms()
  {
    for (int i = 0; i < m_numObjects; i++)
	m_objects[i].modelingTransforms();
  }
  
  public final void modelingGourad() {
      for (int i = 0; i < m_numObjects; i++)
	m_objects[i].modelingGourad();
  }

  //
  // Applies the specified projection transformations to the scene
  //
  public final void projectionTransforms()
  {
    for (int i = 0; i < m_numObjects; i++)
      m_objects[i].projectionTransforms();
  }

  //
  // Performs backface-culling 
  //
  public final void trivialRejection()
  {
    for (int i = 0; i < m_numObjects; i++)
      m_objects[i].trivialRejection(m_eyePt, m_lookatPt);
  }

  //
  // Applies the specified viewing transformation to the scene
  //
  public final void viewingTransforms()
  {
    for (int i = 0; i < m_numObjects; i++)
      m_objects[i].viewingTransforms();
  }

  //
  // Modeling Transforms --> Trivial Rejection --> 
  //   Illumination --> Viewing Transformation -->
  //   Clipping --> Projection --> Rasterization --> Display
  //
  public void render(Raster r)
  {
    GOURAD = false;
    modelingTransforms();
    trivialRejection();
    illumination();
    viewingTransforms();
    clipAndDraw(r);
  }

  public void renderGourad(Raster r)
  {
    GOURAD = true;
    modelingGourad();
    trivialRejection();
    illuminateGourad();
    viewingTransforms();
    clipAndDrawGourad(r);
  }

  public long[] debugRenderGourad(Raster r)
  {
    GOURAD = true;
    long[] ret = new long[6];
    ret[0] = System.currentTimeMillis();
    modelingGourad();
    ret[1] = System.currentTimeMillis();
    ret[0] = ret[1] - ret[0];
    trivialRejection();
    ret[2] = System.currentTimeMillis();
    ret[1] = ret[2]-ret[1];
    illuminateGourad();
    ret[3] = System.currentTimeMillis();
    ret[2] = ret[3]-ret[2];
    viewingTransforms();
    ret[4] = System.currentTimeMillis();
    ret[3] = ret[4]-ret[3];
    clipAndDrawGourad(r);
    long tm  = System.currentTimeMillis();
    ret[4] = tm-ret[4];
    ret[5] = -1;
    return ret;
  }


  public long[] debugRender(Raster r)
  {
    GOURAD = false;
    long[] ret = new long[7];
    ret[0] = System.currentTimeMillis();
    modelingTransforms();
    ret[1] = System.currentTimeMillis();
    ret[0] = ret[1] - ret[0];
    trivialRejection();
    ret[2] = System.currentTimeMillis();
    ret[1] = ret[2] - ret[1];
    illumination();
    ret[3] = System.currentTimeMillis();
    ret[2] = ret[3] - ret[2];
    viewingTransforms();
    ret[4] = System.currentTimeMillis();
    ret[3] = ret[4] - ret[3];
    ret[5] = System.currentTimeMillis();
    ret[4] = ret[5] - ret[4];
    clipAndDraw(r);
    long tm = System.currentTimeMillis();
    ret[5] = ret[6] = tm - ret[5];
    return ret;
  }
    
    

  /************************************************************
   *                                                          *
   *                    UTILITY METHODS                       *
   *                                                          *
   ***********************************************************/

  //
  // Sets the level of ambient light in the scene
  //
  public final void setAmbientLight(Light light)
  {
    m_ambientLight = light;
  }

  //
  // Adds a light sources to the scene
  //
  public int addLightSource(Light source)
  {
    int retval = -1;

    if (m_numLightSources < MAX_LIGHT_SOURCES)
      {
	m_lightSources[m_numLightSources] = source;
	retval = m_numLightSources++;
      }
    return retval;
  }

  //
  // Removes the specified light source
  //
  public void removeLightSource(int source)
  {
    if (source < MAX_LIGHT_SOURCES)
      {
	m_lightSources[source] = m_lightSources[--m_numLightSources];
	m_lightSources[m_numLightSources] = null;
      }
  }

  private void setPt()
  {
    Matrix3D view = new Matrix3D();
    view.lookAt(m_eyePt.x, m_eyePt.y, m_eyePt.z,
		m_lookatPt.x, m_lookatPt.y, m_lookatPt.z,
		m_upVector.x, m_upVector.y, m_upVector.z);
    PipelineObject.setViewMatrix(view);
  }

  /************************************************************
   *                                                          *
   *                  Object manipulation                     *
   *            (through modeling transforms)                 *
   *                                                          *
   ***********************************************************/
  
  //
  // Rotates "obj" by the specified parameters
  //
  public void rotateObject(int obj, float ax, float ay, 
				 float az, float theta)
  {
    m_objects[obj].rotate(ax, ay, az, theta);
  }

  //
  // Scales "obj" by the specified parameters
  //
  public void scaleObject(int obj, float x, float y, float z)
  {
    m_objects[obj].scale(x, y, z);
  }

  //
  // Translates "obj" by the specified parameters
  //
  public void translateObject(int obj, float x, float y, float z)
  {
    m_objects[obj].translate(x, y, z);
  }



  /*************************************************************
   *                                                           *
   *                        PARSER                             *
   *                                                           *
   * Initializes the Pipeline3D object from an InputStream     *
   *                                                           *
   ************************************************************/

  public void ReadInput(InputStream is) throws IOException
  {
    Point3D[] vertList;
    Tri3D[]   triList;
    Surface   surface = null;

    int triangles = 0, vertices = 0, surfaces = 0;

    vertList = new Point3D[CHUNKSIZE];
    triList  = new Tri3D[CHUNKSIZE];
    
    StreamTokenizer st = new StreamTokenizer(is);
    st.commentChar('#');
  scan: while (true) 
    {
    switch (st.nextToken()) 
      {
      default:
	break scan;
      case StreamTokenizer.TT_WORD:
	if (st.sval.equals("v")) 
	  {
	    float x = (float) getNumber(st);
	    float y = (float) getNumber(st);
	    float z = (float) getNumber(st);
	    if (vertices == vertList.length) 
	      {
		Point3D newList[] = new Point3D[vertList.length+CHUNKSIZE];
		System.arraycopy(vertList, 0, newList, 0, vertList.length);
		vertList = newList;
	      }
	    vertList[vertices++] = new Point3D(x, y, z);
	  } 
	else if (st.sval.equals("f")) 
	  {
	    int v0 = (int) getNumber(st);
	    int v1 = (int) getNumber(st);
	    while (st.nextToken() == StreamTokenizer.TT_NUMBER) 
	      {
		st.pushBack();
		int v2 = (int) getNumber(st);
		if (v2 == v0) continue;
		if (triangles == triList.length) triList = growList(triList);
		triList[triangles] = new Tri3D(v0, v1, v2);
		if (surface == null)		 
		  surface = new Surface(0.5f, 0.5f, 0.5f, 
					1.0f, 1.0f, 1.0f, 5.0f);

		triList[triangles].m_surface = surface;
		v1 = v2;
		triangles += 1;
	      }
	    st.pushBack();
	  } 
	else if (st.sval.equals("eye")) 
	  {
	    m_eyePt.x = (float) getNumber(st);
	    m_eyePt.y = (float) getNumber(st);
	    m_eyePt.z = (float) getNumber(st);
	  } 
	else if (st.sval.equals("look")) 
	  {
	    m_lookatPt.x = (float) getNumber(st);
	    m_lookatPt.y = (float) getNumber(st);
	    m_lookatPt.z = (float) getNumber(st);
	  } 
	else if (st.sval.equals("up")) 
	  {
	    m_upVector.x = (float) getNumber(st);
	    m_upVector.y = (float) getNumber(st);
	    m_upVector.z = (float) getNumber(st);
	  } 
	else if (st.sval.equals("fov")) 
	  {
	    m_fov = (float) getNumber(st);
	  }
	else if (st.sval.equals("la"))
	  { 
            // ambient light source
	    float r = (float) getNumber(st);
	    float g = (float) getNumber(st);
	    float b = (float) getNumber(st);
	    m_ambientLight = new Light(Light.AMBIENT, 0, 0, 0, r, g, b);
	  } 
	else if (st.sval.equals("ld")) 	  
	  {
	    // directional light source
	    float r = (float) getNumber(st);
	    float g = (float) getNumber(st);
	    float b = (float) getNumber(st);
	    float x = (float) getNumber(st);
	    float y = (float) getNumber(st);
	    float z = (float) getNumber(st);
	    m_lightSources[m_numLightSources++] = new Light(Light.DIRECTIONAL, 
							    x, y, z, r, g, b);
	  } 
	else if (st.sval.equals("lp"))
	  {
	    // point light source
	    float r = (float) getNumber(st);
	    float g = (float) getNumber(st);
	    float b = (float) getNumber(st);
	    float x = (float) getNumber(st);
	    float y = (float) getNumber(st);
	    float z = (float) getNumber(st);
	    m_lightSources[m_numLightSources++] = new Light(Light.POINT, 
							    x, y, z, r, g, b);
	  } 
	else if (st.sval.equals("surf"))
	  {
	    float r = (float) getNumber(st);
	    float g = (float) getNumber(st);
	    float b = (float) getNumber(st);
	    float ka = (float) getNumber(st);
	    float kd = (float) getNumber(st);
	    float ks = (float) getNumber(st);
	    float ns = (float) getNumber(st);
	    surface = new Surface(r, g, b, ka, kd, ks, ns);
	  } 
	else 
	  {
	    System.err.println("ERROR: line "+st.lineno()+": unexpected token :"+st.sval);
	    break scan;
	  }
	break;
      }
    }

  setPt();
  m_objects[0] = new PipelineObject();
  m_objects[0].setVertices(vertList, vertices);
  m_objects[0].setTriangles(triList, triangles);
  m_numObjects = 1;
  }
  private double getNumber(StreamTokenizer st) throws IOException
  {
    if (st.nextToken() != StreamTokenizer.TT_NUMBER) {
      System.err.println("ERROR: line "+st.lineno()+": expected number");
      throw new IOException(st.toString());
    }
    return st.nval;
  }

  private Tri3D[] growList(Tri3D[] triList)
  {
    Tri3D newList[] = new Tri3D[triList.length+CHUNKSIZE];
    System.arraycopy(triList, 0, newList, 0, triList.length);
    return newList;
  }
}


class PipelineObject
{
  /**************************************************
   *                                                *
   *             CLASS CONSTANTS                    *
   *                                                *
   *************************************************/
  
  public static final int MAX_VERTICES  =  50; 
  public static final int MAX_TRIANGLES =  100;
  public static final int FAR_PLANE     =  0;
  public static final int NEAR_PLANE    =  1;
  public static final int NEAR          = -1;
  public static final int FAR           = -15;
  
  /**************************************************
   *                                                *
   *              STATIC FIELDS                     *
   *                                                *
   *************************************************/

  private static Matrix3D m_perspective;
  private static Matrix3D m_view;

  //
  // Temporary variables pre-allocated to avoid the
  // cost of dynamic memory allocation during
  // rendering
  //
  private static Point3D[] pTemp, pIn, pOut, pDraw;
  private static Triangle  triTemp;
  private static Point3D   ptTemp;
  private static float[]   mi;

  /***************************************************
   *                                                 *
   *                 DATA MEMBERS                    *
   *                                                 *
   **************************************************/

  //
  // Transformation matrices:
  //
  private Matrix3D      m_model;       // Modeling transformations on pts
  private Matrix3D      m_modelNormal; // Modeling transformations on normals

  //
  // Triangle list
  //
  private Tri3D[]       m_triangles;
  private int           m_numTriangles;

  //
  // Vertex lists
  //
  private Point3D[]       m_modeledList;
  private Point3D[]       m_projList;
  private Point3D[]       m_vertList;
  private Point3D[]       m_viewList;
  private int             m_numVertices;

  //
  // Normals
  //
  private Point3D[]       m_modeledNormals;
  private Point3D[]       m_vertNormals;
  private Point3D[]       m_gouradVertNormals;
  private Point3D[]       m_gouradModeledNormals;

  public PipelineObject()
  {

    //
    // Transformation matrices
    //
    m_model           = new Matrix3D();
    m_modelNormal     = new Matrix3D();           
    m_model.loadIdentity();
    m_modelNormal.loadIdentity();

    //
    // Triangle list
    //
    m_triangles       = null;
    m_numTriangles    = 0;

    //
    // Vertex lists
    //
    m_modeledList     = null;
    m_projList        = null;
    m_vertList        = null;
    m_viewList        = null;
    m_numVertices     = 0;
    
    //
    // Normals 
    //
    m_modeledNormals  = null;
    m_vertNormals     = null;
  }
  
  /************************************************************
   *                                                          *
   *                    PIPELINE STAGES                       *
   *                                                          *
   ***********************************************************/

  public void clipAndDraw(Raster r)
  {
    int   numTriangles, numVertices;
    Tri3D t;

    numTriangles = m_numTriangles;
    for (int i = 0; i < numTriangles; i++)
      {
	t = m_triangles[i];
	if (t.m_isVisible)
	  {
	    pIn[0] = m_viewList[t.m_v1];
	    pIn[1] = m_viewList[t.m_v2];
	    pIn[2] = m_viewList[t.m_v3];
	    numVertices = clipTriangle(pIn, pTemp, pOut);
	    m_perspective.transform(pOut, pDraw, 0, numVertices);
	    
	    //
	    // Split the generated polygon into triangles
	    //
	    for (int j = numVertices - 1; j >= 2; j--)
	      {
		pDraw[0].argb = pDraw[j].argb = pDraw[j-1].argb = t.m_shade;
		triTemp.reset(pDraw[j], pDraw[j-1], pDraw[0]);
		triTemp.Draw(r);
	      }
	  }
      }    
  }

  public void clipAndDrawGourad(Light ambient,
				Raster r)
  {
    int   numTriangles, numVertices;
    Surface s;
    Tri3D t;
    int red, green, blue;
    float ambientRed, ambientGreen, ambientBlue;
    
    numTriangles = m_numTriangles;
    for (int i = 0; i < numTriangles; i++)
      {
	t = m_triangles[i];
	s = t.m_surface;
	if (t.m_isVisible)
	  {
	    pIn[0] = m_viewList[t.m_v1];
	    pIn[1] = m_viewList[t.m_v2];
	    pIn[2] = m_viewList[t.m_v3];
	    numVertices = clipTriangle(pIn, pTemp, pOut);
	    m_perspective.transform(pOut, pDraw, 0, numVertices);

	    ambientRed   = ambient.r * s.m_ambReflectivity[GfxLibDefs.RED];
	    ambientGreen = ambient.g * s.m_ambReflectivity[GfxLibDefs.GREEN];
	    ambientBlue  = ambient.b * s.m_ambReflectivity[GfxLibDefs.BLUE];

	    //
	    // Split the generated polygon into triangles
	    //
	    for (int j = numVertices - 1; j >= 2; j--)
	      {
		//
		// STEP 1:  Calculate pixel values from intensities
		
		red   = (int)(((pDraw[0].m_intensity[GfxLibDefs.RED] * s.m_difReflectivity[GfxLibDefs.RED]) +
			       ambientRed) * 256);
		green = (int)(((pDraw[0].m_intensity[GfxLibDefs.GREEN] * s.m_difReflectivity[GfxLibDefs.GREEN]) +
			       ambientGreen) * 256);
		blue  = (int)(((pDraw[0].m_intensity[GfxLibDefs.BLUE] * s.m_difReflectivity[GfxLibDefs.BLUE]) +
			       ambientBlue) * 256);
		
		red   = ((0xffffff00 & red)   != 0) ? 0x00ff0000 : (red << 16);
		green = ((0xffffff00 & green) != 0) ? 0x0000ff00 : (green << 8);
		blue  = ((0xffffff00 & blue)  != 0) ? 0x000000ff : blue;
		pDraw[0].argb = 0xff000000 | red | green | blue;


		red   = (int)(((pDraw[j-1].m_intensity[GfxLibDefs.RED] * s.m_difReflectivity[GfxLibDefs.RED]) +
			       ambientRed) * 256);
		green = (int)(((pDraw[j-1].m_intensity[GfxLibDefs.GREEN] * s.m_difReflectivity[GfxLibDefs.GREEN]) +
			       ambientGreen) * 256);
		
		blue  = (int)(((pDraw[j-1].m_intensity[GfxLibDefs.BLUE] * s.m_difReflectivity[GfxLibDefs.BLUE]) +
			       ambientBlue) * 256);
		
		red   = ((0xffffff00 & red)   != 0) ? 0x00ff0000 : (red << 16);
		green = ((0xffffff00 & green) != 0) ? 0x0000ff00 : (green << 8);
		blue  = ((0xffffff00 & blue)  != 0) ? 0x000000ff :  blue;
		pDraw[j-1].argb = 0xff000000 | red | green | blue;


		red   = (int)(((pDraw[j].m_intensity[GfxLibDefs.RED] * s.m_difReflectivity[GfxLibDefs.RED]) +
			       ambientRed) * 256);
		green = (int)(((pDraw[j].m_intensity[GfxLibDefs.GREEN] * s.m_difReflectivity[GfxLibDefs.GREEN]) +
			       ambientGreen) * 256);	       
		blue  = (int)(((pDraw[j].m_intensity[GfxLibDefs.BLUE] * s.m_difReflectivity[GfxLibDefs.BLUE]) +
			       ambientBlue) * 256);
		
		red   = ((0xffffff00 & red)   != 0) ? 0x00ff0000 : (red << 16);
		green = ((0xffffff00 & green) != 0) ? 0x0000ff00 : (green << 8);
		blue  = ((0xffffff00 & blue)  != 0) ? 0x000000ff : blue;
		pDraw[j].argb = 0xff000000 | red | green | blue;

		//
		// STEP 2: Scan convert the triangle

		triTemp.reset(pDraw[j], pDraw[j-1], pDraw[0]);
		triTemp.Draw(r);
	      }
	  }
      }    

  }
    
  //
  // Illuminates this object in a scene
  //
  public void illumination(Light   ambient,
			   Light[] lightSources,
			   int     numLightSources,
			   Point3D eyePt)
  {
    float        dotProd = -1;
    float        ambientRed, ambientGreen, ambientBlue;
    float        diffuseRed, diffuseGreen, diffuseBlue; 
    int          red, green, blue;
    Light        light;
    Point3D      normal;
    Surface      surface;
    Tri3D        t;         
    
    ambientRed         = ambient.r;
    ambientGreen       = ambient.g;
    ambientBlue        = ambient.b;
    
    for (int i = 0; i < m_numTriangles; i++)
      {
	t = m_triangles[i];
	
	if (t.m_isVisible)
	  {
	    diffuseRed = diffuseGreen = diffuseBlue = 0;
	    surface    = t.m_surface;
	    normal     = m_modeledNormals[i];

	    for (int j = 0; j < numLightSources; j++)
	      {
		light        = lightSources[j];
		
		switch (light.type)
		  {
		  case Light.POINT:
		    
		    ptTemp.x = light.position.x - 
		      (.333333f * (m_modeledList[t.m_v1].x + 
				   m_modeledList[t.m_v2].x + 
				   m_modeledList[t.m_v3].x));
		    ptTemp.y = light.position.y - 
		      (.333333f * (m_modeledList[t.m_v1].y + 
				   m_modeledList[t.m_v2].y + 
				   m_modeledList[t.m_v3].y));
		    ptTemp.z = light.position.z - 
		      (.333333f * (m_modeledList[t.m_v1].z + 
				   m_modeledList[t.m_v2].z + 
				   m_modeledList[t.m_v3].z));
		    dotProd = normal.dotProduct(ptTemp);
		    
		    if (dotProd > 0)
		      {
			ptTemp.computeNormalLength();
			diffuseRed   += light.r*dotProd/ptTemp.m_normalLength;
			diffuseGreen += light.g*dotProd/ptTemp.m_normalLength;
			diffuseBlue  += light.b*dotProd/ptTemp.m_normalLength;
		      }
		    
		    break;
		   
		  case Light.DIRECTIONAL:

		    dotProd = normal.dotProduct(light.position);
		    if (dotProd > 0)
		      {
			diffuseRed   += light.r * dotProd;
			diffuseGreen += light.g * dotProd;
			diffuseBlue  += light.b * dotProd;
		      }
		    break;

		  default:
		    System.err.println("Unsupported lightsource!");
		    System.exit(1);
		  }
    
	      }
	    
	    diffuseRed   *= 
	      surface.m_difReflectivity[GfxLibDefs.RED];
	    diffuseGreen *=
	      surface.m_difReflectivity[GfxLibDefs.GREEN];
	    diffuseBlue  *=
	      surface.m_difReflectivity[GfxLibDefs.BLUE];

	    red   = (int)(((ambientRed *
			   surface.m_ambReflectivity[GfxLibDefs.RED]) + 
			  diffuseRed) * 256);
	    green = (int)(((ambientGreen * 
			   surface.m_ambReflectivity[GfxLibDefs.GREEN]) +
			  diffuseGreen) * 256);
	    blue  = (int)(((ambientBlue *
			   surface.m_ambReflectivity[GfxLibDefs.BLUE]) + 
			  diffuseBlue) * 256);


	    t.m_shade = 0xff000000 | 
		  (((red   & 0xffffff00) == 0) ? red   << 16 : 0x00ff0000) |
		  (((green & 0xffffff00) == 0) ? green << 8  : 0x0000ff00) |
		  (((blue  & 0xffffff00) == 0) ? blue        : 0x000000ff);
	  }
      }
  }

  //
  // Illuminates this object in a scene
  //
  public void illuminateGourad(Light   ambient,
			       Light[] lightSources,
			       int     numLightSources,
			       Point3D eyePt)
  {
    float        dotProd = -1;
    float        diffuseRed, diffuseGreen, diffuseBlue; 
    int          red, green, blue;
    Light        light;
    Point3D      normal;
    Surface      surface;
    Point3D      t;         
    
    for (int i = 0; i < m_numVertices; i++)
      {
	t = m_vertList[i];
	t.m_intensity[GfxLibDefs.RED] = 0;
	t.m_intensity[GfxLibDefs.GREEN] = 0;
	t.m_intensity[GfxLibDefs.BLUE] = 0;

	normal = m_gouradModeledNormals[i];
	
	diffuseRed = diffuseGreen = diffuseBlue = 0;
	
	for (int j = 0; j < numLightSources; j++)
	  {
	    light        = lightSources[j];
	    
	    switch (light.type)
	      {
	      case Light.POINT:
		
		ptTemp.x = light.position.x - t.x;
		ptTemp.y = light.position.y - t.y;
		ptTemp.z = light.position.z - t.z;
		dotProd = normal.dotProduct(ptTemp);
		
		if (dotProd > 0)
		  {
		    ptTemp.computeNormalLength();
		    diffuseRed   += light.r*dotProd/ptTemp.m_normalLength;
		    diffuseGreen += light.g*dotProd/ptTemp.m_normalLength;
		    diffuseBlue  += light.b*dotProd/ptTemp.m_normalLength;
		  }
		
		break;
		
	      case Light.DIRECTIONAL:
		
		dotProd = normal.dotProduct(light.position);
		if (dotProd > 0)
		  {
		    diffuseRed   += light.r * dotProd;
		    diffuseGreen += light.g * dotProd;
		    diffuseBlue  += light.b * dotProd;
		  }
		break;
		
	      default:
		System.err.println("Unsupported lightsource!");
		System.exit(1);
	      }

	    t.m_intensity[GfxLibDefs.RED]   += diffuseRed;
	    t.m_intensity[GfxLibDefs.GREEN] += diffuseGreen;
	    t.m_intensity[GfxLibDefs.BLUE]  += diffuseBlue;
	    
	  }	    
      }
  }
  

  //
  // Applies modeling transformations to this object
  //
  public final void modelingTransforms()
  {
    m_model.transform(m_vertList, m_modeledList, 0, m_numVertices);
    m_modelNormal.transform(m_vertNormals, m_modeledNormals, 0, m_numTriangles);
  }

  public final void modelingGourad()
  {
    m_model.transform(m_vertList, m_modeledList, 0, m_numVertices);
    m_modelNormal.transform(m_vertNormals, m_modeledNormals, 0, m_numTriangles);
    m_modelNormal.transform(m_gouradVertNormals, m_gouradModeledNormals,
			    0, m_numVertices);
  }

  //
  // Applies projection transformations to this object
  //
  public final void projectionTransforms()
  {
    m_perspective.transform(m_viewList, m_projList, 0, m_numVertices);
  }

  //
  // Performs backface-culling on this object:
  //
  public void trivialRejection(Point3D eyePt, Point3D lookatPt)
  {
    float   dotProd;
    Point3D normal, viewingDirection;
    Tri3D   t;

    for (int i = 0; i < m_numTriangles; i++)
      {
	t       = m_triangles[i];
	normal  = m_modeledNormals[i];
	
	dotProd = (normal.x*(eyePt.x - m_modeledList[t.m_v1].x)+
		   normal.y*(eyePt.y - m_modeledList[t.m_v1].y)+
		   normal.z*(eyePt.z - m_modeledList[t.m_v1].z));
	
	if (dotProd > 0) { t.m_isVisible = false; }
	else             { t.m_isVisible = true; }
      }
  }

  //
  // Applies viewing transformations to this object
  //
  public void viewingTransforms()
  {
    m_view.transform(m_modeledList, m_viewList, 0, m_numVertices);
  }

  /************************************************************
   *                                                          *
   *                  Object manipulation                     *
   *            (through modeling transforms)                 *
   *                                                          *
   ***********************************************************/

  public final void rotate(float ax, float ay, float az, float theta)
  {
    m_model.rotate(ax, ay, az, theta);
    m_modelNormal = Matrix3D.makeNormalMatrix(m_model);
  }

  public final void scale(float x, float y, float z)
  {
    Matrix3D m = new Matrix3D();
    m.scale(x, y, z);
    m.compose(m_model);
    m_model = m;
    m_modelNormal = Matrix3D.makeNormalMatrix(m_model);
  }

  public final void shear(float kxy, float kxz, float kyz)
  {
    Matrix3D m = new Matrix3D();
    m.shear(kxy, kxz, kyz);
    m.compose(m_model);
    m_model = m;
    m_modelNormal = Matrix3D.makeNormalMatrix(m_model);
  }
    
  public final void translate(float x, float y, float z)
  {
    Matrix3D m = new Matrix3D();
    m.translate(x, y, z);
    m.compose(m_model);
    m_model = m;
    m_modelNormal = Matrix3D.makeNormalMatrix(m_model);
  }


  /************************************************************
   *                                                          *
   *                    UTILITY METHODS                       *
   *                                                          *
   ***********************************************************/

  public void setVertices(Point3D[] vertices, int numVertices)
  {
    m_vertList    = new Point3D[numVertices];    
    m_modeledList = new Point3D[numVertices];
    m_viewList    = new Point3D[numVertices];
    m_projList    = new Point3D[numVertices];
    
    for (int i = 0; i < numVertices; i++)
      {
	m_modeledList[i] = new Point3D(0, 0, 0);
	m_viewList[i]    = new Point3D(0, 0, 0);	
	m_projList[i]    = new Point3D(0, 0, 0);
      }

    System.arraycopy(vertices, 0, m_vertList, 0, numVertices);
    m_numVertices = numVertices;
  }

  public void setTriangles(Tri3D[] triangles, int numTriangles)
  {
    Tri3D.m_vertexList = m_vertList;

    m_triangles      = new Tri3D[numTriangles];
    System.arraycopy(triangles, 0, m_triangles, 0, numTriangles);
    m_numTriangles = numTriangles;

    m_vertNormals    = new Point3D[numTriangles];    
    m_modeledNormals = new Point3D[numTriangles];
    m_gouradVertNormals = new Point3D[numTriangles];
    m_gouradModeledNormals = new Point3D[numTriangles];

    for (int i = 0; i < m_numTriangles; i++)
      {
	m_vertNormals[i]    = m_triangles[i].makeNormal();
	m_vertNormals[i].normalize();
	m_vertList[m_triangles[i].m_v1].addSurroundingNormal(m_vertNormals[i]);
	m_vertList[m_triangles[i].m_v2].addSurroundingNormal(m_vertNormals[i]);
	m_vertList[m_triangles[i].m_v3].addSurroundingNormal(m_vertNormals[i]);

	m_modeledNormals[i] = new Point3D(0, 0, 0);
      }

    for (int i = 0; i < m_numVertices; i++)
      {
	m_vertList[i].averageSurroundingNormals();
	m_gouradVertNormals[i] = m_vertList[i].m_normal;
	m_gouradModeledNormals[i] = new Point3D();
      }
  }

  public String toString()
  {
    String out = "";

    out += "Pipeline object:\n";
    for (int i = 0; i < m_numTriangles; i++)
      {
	Tri3D t = m_triangles[i];
	out += t.toString();
      }
    
    return out;
  }

  /**********************************************************
   *                                                        *
   *                   CLIPPING ROUTINES                    *
   *                                                        *
   *********************************************************/

  //
  // Clips a triangle to the view volume, in the z-direction.
  // Returns the new coordinates in pOut
  //
  private int clipTriangle(Point3D[] pIn, Point3D[] pOut, Point3D[] temp)
  {
    int numInVertices;
    int numOutVertices;

    //
    // Clip with near plane
    //
    numOutVertices = 0;
    for (int i = 0; i < 3; i++)
      numOutVertices = clipPoint(pIn, 3, temp, numOutVertices, i, NEAR_PLANE);
      
    //
    // Clip with far plane
    //
    numInVertices  = numOutVertices;
    numOutVertices = 0;
    for (int i = 0; i < numInVertices; i++)
      numOutVertices = clipPoint(temp, numInVertices, pOut,  
				 numOutVertices, i, FAR_PLANE);
    
    //
    // Return the number of vertices in the out array
    //
    return numOutVertices;
  }
  
  private int clipPoint(Point3D[] pIn, int numInVertices,
			Point3D[] pOut, int numOutVertices, 
			int currentPt, int plane)
  {
    int next = (currentPt + 1) % numInVertices ;
    float mx, my;

    switch (plane)
      {
      case NEAR_PLANE:
	if (pIn[currentPt].z <= NEAR) // Inside the near plane
	  {
	    if ((pIn[currentPt].z <= NEAR) != (pIn[next].z <= NEAR))  // Next point intersects
	      {
		// Save only intersection

		mx = ((pIn[next].x - pIn[currentPt].x) / 
		      (pIn[next].z - pIn[currentPt].z));
		my = ((pIn[next].y - pIn[currentPt].y) / 
		      (pIn[next].z - pIn[currentPt].z));
		pOut[numOutVertices].z   = NEAR;
		pOut[numOutVertices].x   = (pIn[currentPt].x + mx * 
					    (NEAR - pIn[currentPt].z));
		pOut[numOutVertices].y = (pIn[currentPt].y + my * 
					  (NEAR - pIn[currentPt].z));
		
		if (Pipeline3D.GOURAD)
		  {
		    mi[0] = ((pIn[next].m_intensity[0]-pIn[currentPt].m_intensity[0])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    mi[1] = ((pIn[next].m_intensity[1]-pIn[currentPt].m_intensity[1])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    mi[2] = ((pIn[next].m_intensity[2]-pIn[currentPt].m_intensity[2])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    for (int i = 0; i < 3; i++)
		      {
			pOut[numOutVertices].m_intensity[i] = 
			  (pIn[currentPt].m_intensity[i] + mi[i] * (NEAR - pIn[currentPt].z));
		      }
		  }
	      }
	    else  // Next point does not intersect 
	      {
		// Save only next point
		
		pOut[numOutVertices].x   = pIn[next].x;
		pOut[numOutVertices].y   = pIn[next].y;
		pOut[numOutVertices].z = pIn[next].z;
		
		if (Pipeline3D.GOURAD)
		  {
		    for (int i = 0 ; i < 3; i++)
		      pOut[numOutVertices].m_intensity[i] = pIn[next].m_intensity[i];
		  }
		numOutVertices++;
	      }
	  }
	else  // Outside near plane
	  {
	    if ((pIn[currentPt].z <= NEAR) != (pIn[next].z <= NEAR)) // Next point intersects
	      {
		// Save intersection AND next point
		
		//
		// Step 1: save intersection
		mx = ((pIn[next].x - pIn[currentPt].x) / 
		      (pIn[next].z - pIn[currentPt].z));
		my = ((pIn[next].y - pIn[currentPt].y) / 
		      (pIn[next].z - pIn[currentPt].z));
		
		pOut[numOutVertices].z   = NEAR;
		pOut[numOutVertices].x   = (pIn[currentPt].x + mx * 
					    (NEAR - pIn[currentPt].z));
		pOut[numOutVertices].y = (pIn[currentPt].y + my * 
					  (NEAR - pIn[currentPt].z));
		
		if (Pipeline3D.GOURAD)
		  {
		    mi[0] = ((pIn[next].m_intensity[0]-pIn[currentPt].m_intensity[0])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    mi[1] = ((pIn[next].m_intensity[1]-pIn[currentPt].m_intensity[1])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    mi[2] = ((pIn[next].m_intensity[2]-pIn[currentPt].m_intensity[2])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    for (int i = 0 ; i < 3; i++)
		      {
			pOut[numOutVertices].m_intensity[i] = 
			  (pIn[currentPt].m_intensity[i] + mi[i] * 
			   (NEAR - pIn[currentPt].z));
		      }
		  }
		numOutVertices++;
	      		
		pOut[numOutVertices].x   = pIn[next].x;
		pOut[numOutVertices].y   = pIn[next].y;
		pOut[numOutVertices].z = pIn[next].z;
		
		if (Pipeline3D.GOURAD)
		  {
		    for (int i = 0 ; i < 3; i++)
		      {
			pOut[numOutVertices].m_intensity[i] = pIn[next].m_intensity[i];
		      }
		  }
		numOutVertices++;
	      }
	  }
	break;
     	
      case FAR_PLANE:
	if (pIn[currentPt].z >= FAR) // Inside far plane
	  {
	    if ((pIn[currentPt].z >= FAR) != (pIn[next].z >= FAR))  // Next Point intersects
	      {
		//
		// Save intersection

		mx = ((pIn[next].x - pIn[currentPt].x) / 
		      (pIn[next].z - pIn[currentPt].z));
		my = ((pIn[next].y - pIn[currentPt].y) / 
		      (pIn[next].z - pIn[currentPt].z));
		
		pOut[numOutVertices].z   = FAR;
		pOut[numOutVertices].x   = (pIn[currentPt].x + mx * 
					    (FAR - pIn[currentPt].z));
		pOut[numOutVertices].y = (pIn[currentPt].y + my * 
					  (FAR - pIn[currentPt].z));
		
		if (Pipeline3D.GOURAD)
		  {
		    mi[0] = ((pIn[next].m_intensity[0]-pIn[currentPt].m_intensity[0])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    mi[1] = ((pIn[next].m_intensity[1]-pIn[currentPt].m_intensity[1])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    mi[2] = ((pIn[next].m_intensity[2]-pIn[currentPt].m_intensity[2])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    for (int i = 0 ; i < 3; i++)
		      {
			pOut[numOutVertices].m_intensity[i] = 
			  (pIn[currentPt].m_intensity[i] + mi[i] * 
			   (FAR - pIn[currentPt].z));
		      }
		  }
		numOutVertices++;
	      }
	    else  // Next point does not intersect
	      {
		// Save next point
		
		pOut[numOutVertices].x   = pIn[next].x;
		pOut[numOutVertices].y   = pIn[next].y;
		pOut[numOutVertices].z   = pIn[next].z;
		
		if (Pipeline3D.GOURAD)
		  {
		    for (int i = 0; i < 3; i++)
		      {
			pOut[numOutVertices].m_intensity[i] = pIn[next].m_intensity[i];
		      }
		  }
		numOutVertices++;
	      }
	  }
	else  // Outside far plane
	  {
	    if ((pIn[currentPt].z >= FAR) != (pIn[next].z >= FAR)) // next point interesects
	      {
		//
		// Save intersection AND next point

		// 
		// Step #1:  Save intersection
		//
		mx = ((pIn[next].x - pIn[currentPt].x) / 
		      (pIn[next].z - pIn[currentPt].z));
		my = ((pIn[next].y - pIn[currentPt].y) / 
		      (pIn[next].z - pIn[currentPt].z));
		
		if (Pipeline3D.GOURAD)
		  {
		    mi[0] = ((pIn[next].m_intensity[0]-pIn[currentPt].m_intensity[0])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    mi[1] = ((pIn[next].m_intensity[1]-pIn[currentPt].m_intensity[1])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    mi[2] = ((pIn[next].m_intensity[2]-pIn[currentPt].m_intensity[2])/
			     (pIn[next].z - pIn[currentPt].z));
		    
		    for (int i = 0; i < 3; i++)
		      {
			pOut[numOutVertices].m_intensity[i] = 
			  (pIn[currentPt].m_intensity[i] + mi[i] * 
			   (FAR - pIn[currentPt].z));
		      }
		  }
		
		pOut[numOutVertices].z   = FAR;
		pOut[numOutVertices].x   = (pIn[currentPt].x + mx * 
					    (FAR - pIn[currentPt].z));
		pOut[numOutVertices].y = (pIn[currentPt].y + my * 
					  (FAR - pIn[currentPt].z));
		
		numOutVertices++;
		
		//
		// Step #2:  save next point
		//
		pOut[numOutVertices].x   = pIn[next].x;
		pOut[numOutVertices].y   = pIn[next].y;
		pOut[numOutVertices].z = pIn[next].z;
		
		if (Pipeline3D.GOURAD)
		  {
		    for (int i = 0; i < 3; i++)
		      {
			pOut[numOutVertices].m_intensity[i] = pIn[next].m_intensity[i];
		      }
		  }
		numOutVertices++;
	      }
	  }	  
	break;
      }
    
    return numOutVertices;
  }


  private boolean cross(Point3D p1, Point3D p2, int plane)
  {
    return inside(p1, plane) != inside(p2, plane);
  }


  private boolean inside(Point3D p, int plane)
  {
    switch (plane)
      {
      case NEAR_PLANE: return p.z <= NEAR; 
      case FAR_PLANE:  return p.z >= FAR;  
      }
    return false;
  }

  private void intersect(Point3D p1, Point3D p2, Point3D pOut, int plane)
  {
    float mx, my;
    
    if (p1.z != p2.z)
      {
	mx = (p2.x - p1.x) / (p2.z - p1.z);
	my = (p2.y - p1.y) / (p2.z - p1.z);
	switch (plane)
	  {
	  case NEAR_PLANE:
	    pOut.z = NEAR;
	    pOut.x = p1.x + mx * (NEAR - p1.z);
	    pOut.y = p1.y + my * (NEAR - p1.z);
	    break;
	  case FAR_PLANE:
	    pOut.z = FAR;
	    pOut.x = p1.x + mx * (FAR - p1.z);
	    pOut.y = p1.y + my * (FAR - p1.z);
	    break;
	  }
      }
  }

  /**************************************************
   *                                                *
   *               STATIC METHODS                   *
   *                                                *
   *************************************************/


  //
  // The static initializer: 
  // 
  // Initializes the static fields of the PipelineObject class
  //
  static
  {
    pIn = new Point3D[5];
    pOut = new Point3D[5];
    pDraw = new Point3D[5];
    pTemp = new Point3D[5];
    for (int i = 0; i < 5; i++)
      {
	pDraw[i] = new Point3D();
	pIn[i] = new Point3D();
	pTemp[i] = new Point3D();
	pOut[i] = new Point3D();
      }

    triTemp = new Triangle();
    ptTemp  = new Point3D();
    mi = new float[3];
  }


  public static void setPerspectiveMatrix(Matrix3D m)
  {
    m_perspective = m;
  }
  
  public static void setViewMatrix(Matrix3D m)
  {
    m_view = m;
  }

}












