/* The class implements a polygon in 3D, which is useful for implementing
 * clipping.  Clipping a triangle doesn't make much sense because at one
 * point the triangle becomes a polygon and then you have to convert this
 * polygon back into a bunch of triangles.
 */

import Vertex3D;
import Triangle;

public class Polygon {
  Vertex3D vertices[];  //the vertices of the polygon
  int numVertices;  //the number of vertices
  Surface surface; 
  
  /** CONSTANTS **/
  static int MAX_VERTICES= 8;  //a constant
  static int NEAR_Z=1;
  static int FAR_Z=-1;
  static int NEAR=0;  /*enumerated type for coding of*/
  static int FAR=1;
  
/*****************************************************************************/
  public Polygon (Triangle t, Vertex3D[] vertexList) {
    vertices= new Vertex3D[MAX_VERTICES];
    for (int i=0; i<3; i++) {
      vertices[i]= new Vertex3D(vertexList[t.v[i]]);
    }
    numVertices=3;
  }

/*****************************************************************************/
  public Polygon () {
    vertices= new Vertex3D[MAX_VERTICES];
    numVertices=0;
  }

/*****************************************************************************/
  public void print(String name) {
    System.out.println("");
    System.out.println("---- Polygon "+name+" ----");
    for (int i=0; i< numVertices; i++) {
      System.out.println("vertex "+i+"--> ("+vertices[i].x+", "+
			 vertices[i].y+", "+vertices[i].z+") w="+vertices[i].w);
    }
  }
/*****************************************************************************/
  public void setSurface (Surface s) {
    surface=s;
  }

/*****************************************************************************/
  public Polygon clip() {
    /*returns the number of vertices in the resulting polygon
    /*clips the polygon, adding new vertices where necessary */
    /* go through the polygon vertices in order, loading each point 
     * and then clipping.  the next point is added to the saved position,
     * and then moved to the first position for the next clipping.
     */
    //ASSUME at least 3 vertices to the polygon.
    //Only have to clip to the near and far ends of the view frustrum
    Vertex3D first[];  //the first point processed on each border
    Vertex3D saved[];  //the most recent point processed on each border
    Polygon result= new Polygon();  // the resulting polygon from the clipping
    int nextVertex;  //The index of the next vertex in the polygon
    
    /*Initialize Border arrays*/
    first= new Vertex3D[2];
    saved= new Vertex3D[2];
    first[NEAR]=null; 
    first[FAR]=null;
    saved[NEAR]=null; 
    saved[FAR]=null; 

    for (nextVertex=0; nextVertex < numVertices; nextVertex++) 
      //move to the next edge of the polygon
      clipPoint(vertices[nextVertex], NEAR, result, first, saved);
    closeClip(result, first, saved);
    
    return (result);
  }  

/*****************************************************************************/
  public void addVertex (Vertex3D intersection) {
    vertices[numVertices]= new Vertex3D(intersection);
    numVertices++;
  }

/*****************************************************************************/
  public void clipPoint(Vertex3D curPoint, 
			int border,
			Polygon result,
		        Vertex3D[] first,
			Vertex3D[] saved) {
    
    Vertex3D intersection;
    /*if no previous point exists for this edge, save this point*/
    if ( first[border] == null) 
      first[border]=curPoint;
    else {
      /*else previous point exists.  If the curPoint and the previous form
       *a polygon edge that crosses out of the view frustrum, find the
       *intersection of this edge with the view frustrum.
       *Then clip agains the next Border, if any.  If there are no
       *more edges, add the found intersection to the output list
       */
      if (crossBorder(curPoint, saved[border], border)) {
	intersection= getIntersection (curPoint, saved[border], border);
	if (border < FAR)  //if we haven't clipped to all borders yet
	  //clip it to the next border
	  clipPoint(intersection, (border+1), result, first, saved);
	else 
	  result.addVertex(intersection);
      }  //if crossBorder...
    } //else...
    
    /******* Continue processing this point ********/
    saved[border]= curPoint; //save curPoint as the most recent point for this edge
    
    /*for all, if point is 'inside' proceed to next clip edge, if any*/
    if (insideBorder(curPoint, border))
      if (border < FAR)
	clipPoint(curPoint, (border+1), result, first, saved);
      else 
	result.addVertex(curPoint);
  }
   
/*****************************************************************************/
  public boolean crossBorder (Vertex3D a, Vertex3D b, int border) {
    /*if both are inside or both are outside, there's no crossing*/
    if (insideBorder(a, border) == insideBorder(b, border))
      return (false);
    else {
      System.out.println("Crossed border #"+border);
      return (true);
    }
  }
  
/*****************************************************************************/
  public Vertex3D getIntersection (Vertex3D a, Vertex3D b, int border) {
    Vertex3D intersection= new Vertex3D();//the point of intersection with the border
    float slopeX; //slope of line in x direction relative to z
    float slopeY; //slope of line in y direction relative to z
    float deltaZ;
    
    deltaZ= a.z - b.z;
    if (deltaZ != 0) {
      slopeX= (a.x - b.x) / deltaZ;
      slopeY= (a.y - b.y) / deltaZ;
    }
    else {  // They're both at the same z location.  This shouldn't happen....
      slopeX=0;  slopeY=0;
    }
    if (border == NEAR){
      intersection.z = NEAR_Z;
      intersection.y = a.y + (NEAR_Z - a.z) * slopeY;
      intersection.x = a.x + (NEAR_Z - a.z) * slopeX;
    }
    else if (border == FAR) {
      intersection.z = FAR_Z;
      intersection.y = a.y + (FAR_Z - a.z) * slopeY;
      intersection.x = a.x + (FAR_Z - a.z) * slopeX;
    }
    return (intersection);
  }

/*****************************************************************************/
  public void closeClip(Polygon result, Vertex3D[] first, Vertex3D[] saved) {
    Vertex3D intersection;
    int border;  //the current border 
    
    for (border= NEAR; border<= FAR; border++) {
      if (crossBorder (saved[border], first[border], border)) {
	intersection= getIntersection (saved[border], first[border], border);
	if (border < FAR) //not the last border yet
	  clipPoint(intersection, (border+1), result, first, saved);
	else 
	  result.addVertex(intersection);
      }
    }
  }    
/*****************************************************************************/
  public boolean insideBorder (Vertex3D point, int border) {
    if (border == NEAR) {
      return (point.z < NEAR_Z);  //z negative into the screen
    }
    else if (border == FAR) {
      return (point.z > FAR_Z);  //z negative into the screen
    }
    else return (true);
  }

/*****************************************************************************/
  public void Draw(ZRaster raster, Matrix3D project) {
    /*convert the polygon into a list of triangles, then draw it
     *PRECONDITION: at least 4 vertices in the polygon 
     *NOTE: will generate duplicate points in vertlist sometimes, but it's not
     *worth the effort to work around this
     */
    Triangle newTri;
    Vertex3D newVertexList[];
    Vertex3D displayList[];
    
    newVertexList= new Vertex3D[numVertices];
    displayList= new Vertex3D[numVertices];
   
    /*add the vertices to the newVertexList*/
    for (int i=0; i<numVertices; i++) {
      newVertexList[i] = new Vertex3D(this.vertices[i]);
      displayList[i]= new Vertex3D();
    }
    
    /*transform the vertices*/
    project.transform(newVertexList, displayList, numVertices);
    
    /*break the polygon up into triangles and draw them
     *each triangle has a point at vertex 0 */
    for (int i=2; i<numVertices; i++) {
      newTri= new Triangle(0, i-1, i); 
      newTri.setSurface (surface);
      /*hack- switch the triangle vertex list to use the new vertices,
       *then switch it back.
       *This allows us to avoid adding new triangles to the original vertex list
       */
      Vertex3D oldList[]= newTri.getVertexList();
      newTri.setVertexList(displayList);
      newTri.Draw(raster);
      newTri.setVertexList(oldList);
    }
  }
}







