import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.awt.event.*;
import java.lang.*;

public class TriMatTest extends Applet implements MouseListener, MouseMotionListener {
    static int CHUNKSIZE = 100;
    static int CLIPFAC = 5;
    Raster raster;
    Image screen;
    Buffer buffer;

    int vertices;
    Vertex3D vertList[];

    Triangle3D triList[];

    int axesVtcs;
    Matrix3D axesView;
    Point3D axesList[];

    int triangles;
    float state = 0;
    float near = -1;
    float far = -200;

    Matrix3D eyeMod;
    Matrix3D prj; 
    Matrix3D scr; 

    float eyex, eyey, eyez;
    float lookatx, lookaty, lookatz;
    float upx, upy, upz;
    float initEyex,   initEyey,   initEyez; 
    float initLookatx, initLookaty, initLookatz;
    float fov;

    Light lightList[];
    int lights;
    Surface surfaceList[];
    int surfaces;

    public void init( )    {
        raster = new Raster(getSize().width, getSize().height);
        buffer = new Buffer(getSize().width, getSize().height);
        raster.fill(getBackground());
        screen = raster.toImage(this);
	this.addMouseListener(this);
	this.addMouseMotionListener(this);

        eyex = 0;        eyey = 0;        eyez = -10;
        lookatx = 0;     lookaty = 0;     lookatz = 0;
        upx = 0;         upy = 1;         upz = 0;	
        fov = 30;

        lightList = new Light[CHUNKSIZE];
        lights = 0;
        surfaceList = new Surface[CHUNKSIZE];
        surfaces = 0;
	axesList = new Point3D[CHUNKSIZE];
        axesVtcs = 0;
        vertList = new Vertex3D[CHUNKSIZE];
        vertices = 0;
        triList = new Triangle3D[CHUNKSIZE];
        triangles= 0;
        String filename = getParameter("datafile");
	showStatus("Reading "+filename);
        InputStream is = null;
        try {
            is = new URL(getDocumentBase(), filename).openStream();
            ReadInput(is);
            is.close();
        } catch (IOException e) {
            showStatus("Error reading "+filename);
        }
	
	initEyex=eyex;   initEyey=eyey;   initEyez=eyez; 
	initLookatx=lookatx; initLookaty=lookaty; initLookatz=lookatz;

	showStatus(" triangles " +triangles  );  
// 	for( int i=0; i<triangles; i++ ) {
// 	  System.out.println(" PRINTING INPUT TRILIST " + i);  
// 	  triList[i].printVertices();
// 	}

        axesView = new Matrix3D(raster);
 	//VIEW.PERSPEC IS INITIALIZED WITH COORDS IN VIEW SYSTEM
	//	float t = 8*(float) Math.sin(Math.PI*(fov/2)/180);
	float t = (float) Math.sin(Math.PI*(fov/2)/180);
	float s = (t*getSize().height)/getSize().width;
	axesView.perspective(-t, t, -s, s, near, far);
 	axesView.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);


        eyeMod = new Matrix3D();
// 	System.out.println(" PRINTING EYEMOD "); 
// 	eyeMod.printCompMat();
// 	for( int i=0; i<triangles; i++ ) {
// 	  System.out.println(" PRINTING  TRILIST " + i);  
// 	  triList[i].printVertices();
// 	  //	  triList[i].printColorInterpol();
// 	}

	//THROW IN ALL WORLD TRANSFORMS OF PRIMITIVES HERE
        eyeMod.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
// 	System.out.println(" PRINTING EYEMOD "); 
// 	eyeMod.printCompMat();

     	//VIEW.PERSPEC IS INITIALIZED WITH COORDS IN VIEW SYSTEM
	prj = new Matrix3D();
	prj.perspective(-t, t, -s, s, near, far);
// 	System.out.println(" PRINTING PRJ "); 
// 	prj.printCompMat();
	drawObject();

    }
    

    // define legal characters for our parser
    // The remainder of a line after a '#' is ignored
    // you might find this helpful for debugging
    private void configureParser(StreamTokenizer st) {
	st.commentChar('#');
    }



    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;
    }


    // parse a packed integer in hex, octal, or decimal
    private int getColor(StreamTokenizer st) throws IOException {
	st.nextToken();
	int pix;
        if (st.sval.startsWith("0x") || st.sval.startsWith("0X")) {
	    pix = (int) Long.parseLong(st.sval.substring(2), 16);
	} else
            if (st.sval.startsWith("0") || st.sval.length() > 1) {
                pix = (int) Long.parseLong(st.sval.substring(1), 8);
            } else
                pix = Integer.parseInt(st.sval);
        return pix;
    }


  //   // extend the triangle array if needed
    private void growList() {
      //        Drawable newList[] = new Drawable[triList.length+CHUNKSIZE];
         Triangle3D  newList[] = new Triangle3D[triList.length+CHUNKSIZE];
        System.arraycopy(triList, 0, newList, 0, triList.length);
        triList = newList;
        CHUNKSIZE = (3*CHUNKSIZE)/2;
    }


  public void ReadInput(InputStream is) throws IOException
    {
//       System.out.println(" READING INPUT" ); 
      Reader rdr = new BufferedReader(new InputStreamReader(is));
      StreamTokenizer st = new StreamTokenizer(rdr);
      configureParser(st);

    scan: while (true) {
      switch (st.nextToken()) {
      default:
	break scan;
      case StreamTokenizer.TT_WORD:
	
	if (st.sval.equals("c")) {
	  float x = (float) getNumber(st);
	  float y = (float) getNumber(st);
	  float z = (float) getNumber(st);
	  //int clr =  getColor(st);
	  int clr = (int) getNumber(st) ;
	  if (axesVtcs == axesList.length) {
	    Point3D newList[] = new Point3D[axesList.length+CHUNKSIZE];
	    System.arraycopy(axesList, 0, newList, 0, axesList.length);
	    axesList = newList;
	  }
	  axesList[axesVtcs++] = new Point3D(x, y, z, 1, clr);
	} else
	  if (st.sval.equals("v")) {
	      //	    System.out.println(" READING VERTICES" ); 	  
	    float x = (float) getNumber(st);
	    float y = (float) getNumber(st);
	    float z = (float) getNumber(st);
	    if (vertices == vertList.length) {
	      Vertex3D newList[] = new Vertex3D[vertList.length+CHUNKSIZE];
	      System.arraycopy(vertList, 0, newList, 0, vertList.length);
	      vertList = newList;
	    }
	    //	    System.out.println(" vertices " +vertices  );  
	    vertList[vertices++] = new Vertex3D(x, y, z, 1);
	  } else
	    if (st.sval.equals("f")) {
		//	      System.out.println(" READING FACES" ); 	  
	      int faceTris = 0;
	      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) growList();
		// VERTICES IN VERLIST NUMBERED FROM 0 ONWARDS
		//		System.out.println(" triangles " +triangles  );  
		triList[triangles] = new Triangle3D(vertList[v0], vertList[v1], vertList[v2]);
		if (surfaces == 0) {
		  surfaceList[surfaces] = new Surface(0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 1.0f, 5.0f);
		  surfaces++;
		}

		//		System.out.println(" IN TRI SETsurfaces " + surfaces  );  
		triList[triangles].setSurface(surfaceList[surfaces-1]);
// 		triList[triangles].printSurface();
		v1 = v2;
		faceTris +=1;
		triangles +=1;
	      }
	      st.pushBack();
	    } else
	      if (st.sval.equals("la")) {             // ambient light source
		float r = (float) getNumber(st);
		float g = (float) getNumber(st);
		float b = (float) getNumber(st);
		lightList[lights] = new Light(Light.AMBIENT, 0, 0, 0, r, g, b);
		lights += 1;
	      } 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);
		  lightList[lights] = new Light(Light.DIRECTIONAL, x, y, z, r, g, b);
		  lights += 1;
		} 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);
		    lightList[lights] = new Light(Light.POINT, x, y, z, r, g, b);
		    lights += 1;
		  } else
		    if (st.sval.equals("surf")) {
			//		      System.out.println(" READING SURFACES" ); 	  
		      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);
		      //		      System.out.println("IN SETTING SURF surfaces " + surfaces  );  
		      surfaceList[surfaces] = new Surface(r, g, b, ka, kd, ks, ns);
		      surfaces += 1;
		    } else 
		      if (st.sval.equals("eye")) {
			eyex = (float) getNumber(st);
			eyey = (float) getNumber(st);
			eyez = (float) getNumber(st);
		      } else
			if (st.sval.equals("look")) {
			  lookatx = (float) getNumber(st);
			  lookaty = (float) getNumber(st);
			  lookatz = (float) getNumber(st);
			} else
			  if (st.sval.equals("up")) {
			    upx = (float) getNumber(st);
			    upy = (float) getNumber(st);
			    upz = (float) getNumber(st);
			  } else
			    if (st.sval.equals("fov")) {
			      fov = (float) getNumber(st);
			    } else {
			      System.err.println("ERROR: line "+st.lineno()+": unexpected token :"+st.sval);
			      break scan;
			    }
		      break;
      }
	//if (vertices % 100 == 0)showStatus("vertices = " + (vertices+1));
	showStatus("triangles = " + triangles);
    }
      is.close();
      if (st.ttype != StreamTokenizer.TT_EOF) {
	throw new IOException(st.toString());
      }
    }

  
  public void paint(Graphics g) {
        g.drawImage(screen, 0, 0, this);
    }

    public void update(Graphics g) {
        paint(g);
    }

    int oldx, oldy;
    //        public void mousePressed( MouseEvent e){;}
     public void mousePressed( MouseEvent e )
     {
 	System.out.println("IN MOUSE PRESSED");
 	int x=e.getX();
 	int y=e.getY();
         if (e.isMetaDown()) {
             System.out.println("Reset eyeMod matrix");
             eyex=initEyex;   eyey=initEyey;   eyez=initEyez; 
             lookatx=initLookatx; lookaty=initLookaty; lookatz=initLookatz;
             eyeMod.loadIdentity();
 	    //NO CHANGE TO UP - CAN FAIL HERE!!
             eyeMod.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
             prj.loadIdentity();
             float t = (float) Math.sin(Math.PI*(fov/2)/180);
             float s = (t*getSize().height)/getSize().width;
             prj.perspective(-t, t, -s, s, near, far);
 	    axesView.toOriginalMat();
 	    axesView.perspective(-t, t, -s, s, near, far);
 	    axesView.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
             drawObject();
	     repaint();
         }
         oldx = x;
         oldy = y;
 	return ;
     }

    public void mouseMoved( MouseEvent e ) { ; }
    public void mouseReleased( MouseEvent e ) { ; }
    public void mouseEntered( MouseEvent e ) { ; }
    public void mouseExited( MouseEvent e ) { ;}
    public void mouseClicked( MouseEvent e ) { ; }

  //public void mouseDragged( MouseEvent e) { ; }

//     public void mousePressed( MouseEvent e)
//     {
// 	System.out.println("IN MOUSE DRAGGED");
// 	eyeMod.loadIdentity();
// 	eyex = 10 + (float)( 5*Math.sin(Math.PI/4*state ));
// 	eyey = 10;
// 	eyez = 10+ (float)(5*Math.cos(Math.PI/4*state));
// 	eyeMod.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
// 	float t = (float) Math.sin(Math.PI*(fov/2)/180);
// 	float s = (t*getSize().height)/getSize().width;
// 	prj.loadIdentity();
// 	prj.perspective(-t, t, -s, s, near, far);
// 	axesView.toOriginalMat();
// 	axesView.perspective(-t, t, -s, s, near, far);
// 	axesView.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
// 	state ++;
// 	drawObject();
// 	repaint();
//         return ;
//     }

 
     public void mouseDragged( MouseEvent e)
     {
 	System.out.println("IN MOUSE DRAGGED");
         int x=e.getX();
         int y=e.getY();
         if (e.isMetaDown()) {
             System.out.println("Resetting eyeMod matrix");
         } else {
             float ax = lookatx - eyex;
             float ay = lookaty - eyey;
             float az = lookatz - eyez;
             float t = (float) (getSize().width / (2*Math.tan(((fov * Math.PI)/180)/2)));
             Matrix3D r = new Matrix3D( );
             t = (float) (Math.atan((double)(x - oldx)/t));
             r.rotate(upx, upy, upz, t);
             eyex = eyex + ax*(oldy - y)/getSize().height;
             eyey = eyey + ay*(oldy - y)/getSize().height;
             eyez = eyez + az*(oldy - y)/getSize().height;
             lookatx = r.getElem(0,0)*ax + r.getElem(1,0)*ay + r.getElem(2,0)*az;
            lookaty = r.getElem(0,1)*ax + r.getElem(1,1)*ay + r.getElem(2,1)*az;
             lookatz = r.getElem(0,2)*ax + r.getElem(1,2)*ay + r.getElem(2,2)*az;
             lookatx += eyex;
             lookaty += eyey;
             lookatz += eyez;
             eyeMod.loadIdentity();
             eyeMod.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
             t = (float) Math.sin(Math.PI*(fov/2)/180);
             float s = (t*getSize().height)/getSize().width;
             prj.loadIdentity();
             prj.perspective(-t, t, -s, s, near, far);
 	    axesView.toOriginalMat();
 	    axesView.perspective(-t, t, -s, s, near, far);
 	    axesView.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
             oldx = x;
             oldy = y;
             drawObject();
	     repaint();
         }
         return ;
     }







    //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%//


//     public void setViewLine(float eyex,float  eyey, float eyez, float lookatx, float  lookaty, float lookatz, float upx, float upy, float upz) {
// 	prj.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
// 	axesView.setTo( scrPrj );
// 	axesView.transform(axesList, tranAxesList, 0, axesVtcs);
//    }

    void drawObject( ) {
        int frontTris;
        int clipTris;

        Point3D tranAxesList[] = new Point3D[axesList.length];
	for (int i = 0; i < axesVtcs; i++)  tranAxesList[i] = new Point3D();
	axesView.transform(axesList, tranAxesList, 0, axesVtcs);

        Triangle3D viewList[] = new Triangle3D[ triangles ];
	eyeMod.transform( triList,  viewList,  0, triangles);
// 	for( int i=0; i< triangles; i++ ) {
// 	  System.out.println(" PRINTING VIEWLIST " + i);  
// 	  viewList[i].printVertices();
// 	  //	  viewList[i].printColorInterpol();
// 	}	
	

	Triangle3D  frontList[] = new Triangle3D[ triangles];
	showStatus("Backface Culling...");
	frontTris = backFaceCull( viewList, frontList, 0, triangles );
	//NOTE: frontList.length= triangles but only frontTris have allocated memory
// 	for( int i=0; i< frontTris; i++ ) {
// 	    System.out.println(" PRINTING FRONTLIST " + i);  
// 	    frontList[i].printVertices();
// 	    //	    frontList[i].printColorInterpol();
// 	}

	showStatus("Illuminating...");
	for( int i=0; i< frontTris; i++ ) {
 	    showStatus(" Illuminating triangle " + i);  
	    frontList[i].illuminate( lightList, lights );
	    //	    frontList[i].printColorInterpol();
	}
// 	for( int i=0; i< frontTris; i++ ) {
// 	    System.out.println(" PRINTING ILLUMINATED FRONTLIST " + i);  
// 	    frontList[i].printVertices();
// 	    //	    frontList[i].printColorInterpol();
// 	}	

	Triangle3D clipList[] = new Triangle3D[ CLIPFAC * frontTris];
	showStatus("Clipping hither and yon...");
  	clipTris = clip( frontList,  clipList, 0, frontTris, near, far );
// 	System.out.println(" BACK IN TRIMATTEST.JAVA" );  
//   	for( int i=0; i < clipTris; i++ ) {
//   	  System.out.println(" PRINTING CLIPLIST " + i);  
//   	  clipList[i].printVertices();
// 	  //	  clipList[i].printColorInterpol();
//   	}

	
	Triangle3D scrList[] = new Triangle3D[ clipTris];
	if( clipTris>0) {
	  Triangle3D projList[] = new Triangle3D[ clipTris ];
	    showStatus("Canonical-Projecting triangles...");
	    prj.transform( clipList,  projList,  0, clipTris);
// 	    for( int i=0; i< clipTris; i++ ) {
// 		System.out.println(" PRINTING PROJLIST " + i);  
// 		projList[i].printVertices();
// 		//		projList[i].printColorInterpol();
// 	    }
	    // PROJ NEEDS TO BE DEHOMOGIZED BECAUSE THE SCREEN TRANSFORMATION ASSUMES THAT THE POINTS 
	    // ARE IN A CANONICAL CUBE
	    for( int i=0; i < clipTris; i++ ) {
		projList[i].deHomogenize();
	    }
// 	    for( int i=0; i< clipTris; i++ ) {
// 		System.out.println(" PRINTING PROJLISTDEHOMO " + i);  
// 		projList[i].printVertices();
// 		//		projList[i].printColorInterpol();
// 	    }
	    scr = new Matrix3D(raster);
// 	    System.out.println(" PRINTING SCR ");
// 	    scr.printCompMat();
	    showStatus("Displaying triangles...");
	    scr.transform( projList, scrList, 0, clipTris);
	    // THIS DEHOMOGIZATION NOT NECESSARY BECAUSE PROJLIST IS ALREADY IN DEHOMOG FORMAT
	    for( int i=0; i < clipTris; i++ ) {
		scrList[i].deHomogenize();
	    }
	    
// 	    for( int i=0; i < clipTris; i++ ) {
// 		System.out.println(" PRINTING SCRLIST " + i);  
// 		scrList[i].printVertices();
// 		//		scrList[i].printColorInterpol();
// 	    }
	    
	}else {
	    showStatus(" Zero triangles to be rendered ");
	}	    
	buffer.initZ();
	renderScene( clipTris, scrList, tranAxesList );
	screen = raster.toImage(this);
	// DELETE ALL TRIANGLE LISTS NOW

    }//DRAWOBJECT


    void renderScene( int nTris, Triangle3D[] all, Point3D[] axes ){
        int ix, iy;
	float iz, iw;
        int w, h;
        w = raster.getWidth();
        h = raster.getHeight();
        raster.fill(getBackground());
	for (int i = 0; i < axesVtcs; i++) {
	    if( axes[i].getCoord(3) !=0){
		ix = (int) (axes[i].getCoord(0) / axes[i].getCoord(3));
		iy = (int) (axes[i].getCoord(1) / axes[i].getCoord(3));
		iz = axes[i].getCoord(2) /axes[i].getCoord(3) ;
		iw = axes[i].getCoord(3) /axes[i].getCoord(3) ;
		if (ix >= 0 && iy >= 0 && ix < w && iy < h){
		    raster.setPixel(axesList[i].getColor(), ix, iy);
		}
	    }
	}
	if( nTris>0) {
	    for( int i=0; i < nTris; i++ ) {
		showStatus("Rasterizing triangle " + i);
		all[i].draw(raster, buffer);
	    }
	} else {
	    showStatus(" ERROR: Zero triangles to be rendered ");
	}
    }//RENDERSCENE


  public int backFaceCull( Triangle3D[] all,  Triangle3D[] front, int start, int length ) {
    int frontTris=0;
    for( int t=start; t<(start+length); t++ ) {
       Vector3D normToTri = new Vector3D();
       all[t].getNormal( normToTri );
      if( normToTri.getV(2) >= 0 ){
	//all[t].triangleSetup();
	front[ frontTris ] = new Triangle3D( all[t].getVtx3D(0), all[t].getVtx3D(1), all[t].getVtx3D(2) ); 
	front[frontTris].setSurface( all[t].getSurface() );
	front[frontTris].setFlatColor( all[t].getFlatColor() );
	frontTris++;
      } else {
	  showStatus("Culling triangle " + t);	  
      }
    } //FOR T
    return frontTris;
  }//BACKFACE CULL


  public int clip( Triangle3D[] all,  Triangle3D[] clipList, int start, int length, float near, float far ) {
//     System.out.println("IN TRIMATTESTCLIP" );
    // INCLUDE DE-HOMOGENIZING OF ALL HERE
    int clipTris = 0;
    int iClipTris;
    Plane3D[] H = new Plane3D[2];
    //POINTS INSIDE THE PLANE ARE IN POSITIVE HALFPLANES
    H[0] = new Plane3D( 0, 0, -1, near);
    H[1] = new Plane3D( 0, 0,  1, -far);
//     System.out.println("Printing Planes" );
//     H[0].print();
//     H[1].print();

    for( int t=start; t<(start+length); t++ ) {
	showStatus("clipping triangle " + t );
      iClipTris= all[t].clipTri3D( clipList, clipTris,  H, 2, CLIPFAC );
      for( int i=0; i<iClipTris; i++ ) {
	clipList[clipTris+i].setSurface( all[t].getSurface() );
	clipList[clipTris+i].setFlatColor( all[t].getFlatColor() );
      }
//       System.out.println(" triangles formed = " + iClipTris );  
      clipTris += iClipTris;
//       System.out.println("no of clipped tris formed = " + clipTris );  
    }
    return clipTris;
  } //CLIP

 






}//TRIMATTEST





