import java.applet.*;
import java.awt.*;
import java.io.*;
import java.net.*;
import Point3D;
import Matrix3D;

public class TestMatrix extends Applet {
  final static int CHUNKSIZE = 100;
  Raster raster;
    Image screen;
  Point3D vertList[];
  Point3D worldList[];
  Point3D tranList[];
  int vertices;
  Matrix3D view;
  Matrix3D model;
  
  float eyex, eyey, eyez;
  float lookatx, lookaty, lookatz;
  float upx, upy, upz;
    float fov;
  
  public void init( )
  {
    raster = new Raster(size().width, size().height);
    raster.fill(getBackground());
    screen = raster.toImage();
    
        eyex = 0;        eyey = 0;        eyez = -10;
        lookatx = 1;     lookaty = 0;     lookatz = 0;
        upx = 0;         upy = 1;         upz = 0;
        fov = 30;
	
        vertList = new Point3D[CHUNKSIZE];
        vertices = 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);
        }
	
        worldList = new Point3D[vertList.length];
        tranList = new Point3D[vertList.length];
        for (int i = 0; i < vertices; i++) {
	  worldList[i] = new Point3D();
	  tranList[i] = new Point3D();
        }
        
        view = new Matrix3D(raster);
        float t = (float) Math.sin(Math.PI*(fov/2)/180);
        float s = (t*size().height)/size().width;
	
	view.perspective(-t, t, -s, s, -1, -200);
        //view.orthographic(-t, t, -s, s, -1, -200);
	view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
        
        model = new Matrix3D();
        long time = System.currentTimeMillis();
        model.transform(vertList, worldList, 0, vertices);
        view.transform(worldList, tranList, 0, vertices);
        DrawObject();
        time = System.currentTimeMillis() - time;
        showStatus("Time = "+time+" ms");
        screen = raster.toImage();
    }
  
  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;
  }

  public void ReadInput(InputStream is) throws IOException
  {
    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("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);
  }
  is.close();
  if (st.ttype != StreamTokenizer.TT_EOF) {
    throw new IOException(st.toString());
  }
  }
  
  public void paint(Graphics g)
  {
    g.drawImage(screen, 0, 0, this);
    System.out.println("Painted\n");
  }
  
  public void update(Graphics g)
  {
    paint(g);
  }
  
  float v0x, v0y, v0z;
  
  public boolean mouseDown(Event e, int x, int y)
  {
    if (e.metaDown()) {
      showStatus("Resetting model matrix");
      model.loadIdentity();	    
    }
    v0x = (float) (x - (size().width / 2));
    v0y = (float) ((size().height / 2) - y);
    v0z = (float) size().width;
        float l0 = (float) (1 / Math.sqrt(v0x*v0x + v0y*v0y + v0z*v0z));
        v0x *= l0;
        v0y *= l0;
        v0z *= l0;
	return true;
  }
  
  public boolean keyDown(Event e, int key) {
    if ((e.id == Event.KEY_PRESS) && (key == 'o')){
      showStatus("Resetting view to ortho");
      float t = (float) Math.sin(Math.PI*(fov/2)/180);
      float s = (t*size().height)/size().width;
      view.orthographic(-t, t, -s, s, -1, -200);
      view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
    }
    if ((e.id == Event.KEY_PRESS) && (key == 'p')){
      showStatus("Resetting view to persp");
      float t = (float) Math.sin(Math.PI*(fov/2)/180);
      float s = (t*size().height)/size().width;
      view.perspective(-t, t, -s, s, -1, -200);
      view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
    }
    if ((e.id == Event.KEY_PRESS) && (key == 'q')){
      showStatus("Translate");
      model.translate((float)-2.0, (float)0.0, (float)0.0);
    }    
    if ((e.id == Event.KEY_PRESS) && (key == 'w')){
      showStatus("Translate");
      model.translate((float)2.0, (float)0.0, (float)0.0);
    }    
    if ((e.id == Event.KEY_PRESS) && (key == 'a')){
      showStatus("Translate");
      model.translate((float)0.0, (float)-2.0, (float)0.0);
    }    
    if ((e.id == Event.KEY_PRESS) && (key == 's')){
      showStatus("Translate");
      model.translate((float)0.0, (float)2.0, (float)0.0);
    }    
    if ((e.id == Event.KEY_PRESS) && (key == 'z')){
      showStatus("Translate");
      model.translate((float)0.0, (float)0.0, (float)-2.0);
    }    
    if ((e.id == Event.KEY_PRESS) && (key == 'x')){
      showStatus("Translate");
      model.translate((float)0.0, (float)0.0, (float)2.0);
    }    
    if ((e.id == Event.KEY_PRESS) && (key == 'e')){
      showStatus("Skew");
      model.skew((float)-0.1, (float)-0.1, (float)-0.1);
    }
    if ((e.id == Event.KEY_PRESS) && (key == 'r')){
      showStatus("Skew");
      model.skew((float)0.1, (float)0.1, (float)0.1);
    }
    if ((e.id == Event.KEY_PRESS) && (key == 'f')){
      showStatus("Scale");
      model.scale((float)1.1, (float)1.1, (float)1.1);
    }
    if ((e.id == Event.KEY_PRESS) && (key == 'd')){
      showStatus("Scale");
      model.scale((float)0.9, (float)0.9, (float)0.9);
    }
    screen = raster.toImage();
    repaint();
    return true;
  }

  public boolean mouseDrag(Event e, int x, int y)
  {
    if (e.metaDown()) {
      showStatus("Resetting model matrix");
    } else {
            float v1x = (float) (x - (size().width / 2));
            float v1y = (float) ((size().height / 2) - y);
            float v1z = (float) size().width;
            float l = (float) (1 / Math.sqrt(v1x*v1x + v1y*v1y + v1z*v1z));
            v1x *= l;
            v1y *= l;
            v1z *= l;
	    
            float ax = v0y*v1z - v0z*v1y;
            float ay = v0z*v1x - v0x*v1z;
            float az = v0x*v1y - v0y*v1x;
            l = (float) Math.sqrt(ax*ax + ay*ay + az*az);
            float theta = (float) Math.asin(l);
            if (v0x*v1x + v0y*v1y + v0z*v1z < 0)
	      theta += (float) Math.PI / 2;
            model.rotate(ax, ay, az, theta);
            v0x = v1x;
            v0y = v1y;
            v0z = v1z;
            model.transform(vertList, worldList, 0, vertices);
            view.transform(worldList, tranList, 0, vertices);
            DrawObject();
    }
    screen = raster.toImage();
    repaint();
    return true;
  }
  
  void DrawObject()
  {
    int ix, iy;
    int w, h;
    w = raster.width;
    h = raster.height;
    raster.fill(getBackground());
    for (int i = 0; i < vertices; i++) {
      ix = (int) tranList[i].x;
      iy = (int) tranList[i].y;
      if (ix >= 0 && iy >= 0 && ix < w && iy < h)
	raster.setPixel(0xffa000a0, ix, iy);
    }
  }
  
}
