import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;
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;

  String eyeV, atV, upV;


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

    readfile("cow.obj");

    ControlFrame control = new ControlFrame(this);
    control.resize(620, 350);
    control.setTitle("George's Applet Control Panel");
    control.show();
    view = calculateView(true);

    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(this);
  }

  public Matrix3D calculateView(boolean type) {
    view = new Matrix3D(raster);
    float t = (float) Math.sin(Math.PI*(fov/2)/180);
    float s = (t*size().height)/size().width;
    
    if (type)
      view.perspective(-t, t, -s, s, -1, -200);
    else
      view.orthographic(-t*10, t*10, -s*10, s*10, -1, -200);

    view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);

    return(view);
  }

  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 readfile(String filename) {
    vertList = new Point3D[CHUNKSIZE];
    vertices = 0;

    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();
    }
  }
    
    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);
			      eyeV = eyex + " " + eyey + " " + eyez;
                            } else
                            if (st.sval.equals("look")) {
			      lookatx = (float) getNumber(st);
			      lookaty = (float) getNumber(st);
			      lookatz = (float) getNumber(st);
			      atV = lookatx + " " + lookaty + " " + lookatz;
                            } else
                            if (st.sval.equals("up")) {
			      upx = (float) getNumber(st);
			      upy = (float) getNumber(st);
			      upz = (float) getNumber(st);
			      upV = upx + " " + upy + " " + upz;
                            } 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);
  }

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

  void reTransform() {
    model.transform(vertList, worldList, 0, vertices);
    view.transform(worldList, tranList, 0, vertices);
    DrawObject();
    
    screen = raster.toImage(this);
    repaint();
  }

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

}


class ControlFrame extends Frame implements ItemListener, ActionListener{
  private TestMatrix parent;

  TextField transx, transy, transz;
  TextField scalex, scaley, scalez;
  TextField skewxy, skewxz, skewyz;
  TextField aor, theta;
  TextField eyeVec, lookAtVec, upVec;
  CheckboxGroup projOpt;
  Checkbox orthoBox, perspBox;
  Choice aorChooser, objChooser;
  Panel south;

  ControlFrame(TestMatrix p) {
    parent = p;
    init();
  }

  public void init() {
    Button doScale, doTranslate, doSkew, doRotate, resetModelButton;
    Button moveViewButton, resetViewButton;

    setLayout(new GridLayout(8,1));
    Panel south1 = new Panel(new FlowLayout());
    Panel south1a = new Panel(new FlowLayout());
    south1a.add(new Label("Object"));
    objChooser = new Choice();
    objChooser.add("cow.obj");
    objChooser.add("cube.obj");
    objChooser.add("densecube.obj");
    objChooser.add("torus.obj");
    south1a.add(objChooser);
    objChooser.addItemListener(this);
    south1.add(south1a);

    projOpt = new CheckboxGroup();
    perspBox = new Checkbox("Perspective Projection", projOpt, true);
    south1.add(perspBox);
    orthoBox = new Checkbox("Orthographic Projection", projOpt, false);
    south1.add(orthoBox);
    perspBox.addItemListener(this);
    orthoBox.addItemListener(this);
    add(south1);

    Panel south2 = new Panel(new GridLayout(1,7));
    transx = new TextField("0.0", 5);
    transy = new TextField("0.0", 5);
    transz = new TextField("0.0", 5);
    south2.add(new Label("delta-x", Label.RIGHT));
    south2.add(transx);
    south2.add(new Label("delta-y", Label.RIGHT));
    south2.add(transy);
    south2.add(new Label("delta-z", Label.RIGHT));
    south2.add(transz);
    doTranslate = new Button("X-late It!");
    south2.add(doTranslate);
    doTranslate.addActionListener(this);
    add(south2);

    Panel south3 = new Panel(new GridLayout(1,7));
    scalex = new TextField("1.0", 5);
    scaley = new TextField("1.0", 5);
    scalez = new TextField("1.0", 5);
    south3.add(new Label("Scale-x", Label.RIGHT));
    south3.add(scalex);
    south3.add(new Label("Scale-y", Label.RIGHT));
    south3.add(scaley);
    south3.add(new Label("Scale-z", Label.RIGHT));
    south3.add(scalez);
    doScale = new Button("Scale It!");
    south3.add(doScale);
    doScale.addActionListener(this);
    add(south3);

    Panel south4 = new Panel(new GridLayout(1,7));
    skewxy = new TextField("0.0", 5);
    skewxz = new TextField("0.0", 5);
    skewyz = new TextField("0.0", 5);
    south4.add(new Label("Skew-xy", Label.RIGHT));
    south4.add(skewxy);
    south4.add(new Label("Skew-xz", Label.RIGHT));
    south4.add(skewxz);
    south4.add(new Label("Skew-yz", Label.RIGHT));
    south4.add(skewyz);
    doSkew = new Button("Skew It!");
    south4.add(doSkew);
    doSkew.addActionListener(this);
    add(south4);

    Panel south5 = new Panel(new FlowLayout());
    south5.add(new Label("Rotate around", Label.RIGHT));
    theta = new TextField("0.0",5);
    aorChooser = new Choice();
    aorChooser.add("X-Axis (1,0,0)");
    aorChooser.add("Y-Axis (0,1,0)");
    aorChooser.add("Z-Axis (0,0,1)");
    aorChooser.add("Arbitrary");
    aorChooser.addItemListener(this);
    south5.add(aorChooser);
    aor = new TextField("1 1 1");
    south5.add(aor);
    aor.disable();
    south5.add(new Label("by", Label.RIGHT));
    south5.add(theta);
    south5.add(new Label("Degrees", Label.LEFT));
    doRotate = new Button("Rotate It!");
    south5.add(doRotate);
    doRotate.addActionListener(this);
    add(south5);

    Panel south6 = new Panel(new FlowLayout());
    eyeVec = new TextField(parent.eyeV);
    lookAtVec = new TextField(parent.atV);
    upVec = new TextField(parent.upV);
    south6.add(new Label("Eye", Label.RIGHT));
    south6.add(eyeVec);
    south6.add(new Label("LookAt", Label.RIGHT));
    south6.add(lookAtVec);
    south6.add(new Label("Up", Label.RIGHT));
    south6.add(upVec);
    moveViewButton = new Button("Move Viewpoint");
    moveViewButton.addActionListener(this);
    south6.add(moveViewButton);
    add(south6);

    Panel south7 = new Panel(new GridLayout(1,2));
    resetModelButton = new Button("Reset Model Transformation");
    resetModelButton.addActionListener(this);
    south7.add(resetModelButton);
    resetViewButton = new Button("Reset Viewpoint");
    resetViewButton.addActionListener(this);
    south7.add(resetViewButton);
    add(south7);

    Button disposeButton = new Button("Dispose of the Control Panel -- WARNING: must restart the applet to get this panel back");
    disposeButton.addActionListener(this);
    add(disposeButton);
  }

  public void itemStateChanged(ItemEvent e) {
    if (e.getItemSelectable().equals(orthoBox) ||
	e.getItemSelectable().equals(perspBox)) {
      if (e.getStateChange() == ItemEvent.SELECTED) {
	parent.showStatus("Changing Projection...");
	parent.view = parent.calculateView(e.getItemSelectable().equals(perspBox));
	parent.reTransform();
	if (e.getItemSelectable().equals(orthoBox))
	  parent.showStatus("You're now looking at orthographic projection");
	else
	  parent.showStatus("You're now looking at perspective projection");
      }
    } else if (e.getItemSelectable().equals(aorChooser)){
      if (aorChooser.getSelectedItem().equals("Arbitrary"))
	aor.enable();
      else
	aor.disable();
    } else if (e.getItemSelectable().equals(objChooser)) {
      parent.readfile(objChooser.getSelectedItem());
      eyeVec.setText(parent.eyeV);
      lookAtVec.setText(parent.atV);
      upVec.setText(parent.upV);
      parent.view = parent.calculateView(perspBox.getState());
      parent.reTransform();
    }
  }

  public float[] stringToVec(String s) {
    float v[] = new float[3];
    try {
      StringTokenizer st = new StringTokenizer(s);
      v[0] = (new Float(st.nextToken())).floatValue(); 
      v[1] = (new Float(st.nextToken())).floatValue();
      v[2] = (new Float(st.nextToken())).floatValue();
      return(v);
    } catch (NoSuchElementException ex) {
      parent.showStatus("Data Format Error: " + s);
    }
    return(null);
  }

  public void actionPerformed(ActionEvent e) {
    Matrix3D M = new Matrix3D();
    parent.showStatus("");
    try {
      if (e.getActionCommand().equals("Scale It!")) {
	M.scale((new Float(scalex.getText())).floatValue(),
		(new Float(scaley.getText())).floatValue(),
		(new Float(scalez.getText())).floatValue());
	M.compose(parent.model);
	parent.model = M;
      }
      else if (e.getActionCommand().equals("X-late It!")) {
	M.translate((new Float(transx.getText())).floatValue(),
		    (new Float(transy.getText())).floatValue(),
		    (new Float(transz.getText())).floatValue());
	M.compose(parent.model);
	parent.model = M;
      }
      else if (e.getActionCommand().equals("Skew It!")) {
	M.skew((new Float(skewxy.getText())).floatValue(),
	       (new Float(skewxz.getText())).floatValue(),
	       (new Float(skewyz.getText())).floatValue());
	M.compose(parent.model);
	parent.model=M;
      }
      else if (e.getActionCommand().startsWith("Reset Model",0)) {
	parent.model.loadIdentity();
      }
      else if (e.getActionCommand().equals("Rotate It!")) {
	float ang = (float) ((new Float(theta.getText())).floatValue()
	  * Math.PI / 180.0f);
	if(aorChooser.getSelectedItem().equals("X-Axis (1,0,0)")) {
	  M.rotate(1, 0, 0, ang);
	  M.compose(parent.model);
	  parent.model=M;
	} else if(aorChooser.getSelectedItem().equals("Y-Axis (0,1,0)")) {
	  M.rotate(0, 1, 0, ang);
	  M.compose(parent.model);
	  parent.model=M;
	} else if(aorChooser.getSelectedItem().equals("Z-Axis (0,0,1)")) {
	  M.rotate(0, 0, 1, ang);
	  M.compose(parent.model);
	  parent.model=M;
	} else {
	  float v[] = stringToVec(aor.getText());
	  if (v!= null) {
	    M.rotate(v[0], v[1], v[2], ang);
	    M.compose(parent.model);
	    parent.model=M;
	  }
	}
      } else if (e.getActionCommand().equals("Move Viewpoint")) {
	float eye[] = stringToVec(eyeVec.getText());
	float at[] =  stringToVec(lookAtVec.getText());
	float up[] =  stringToVec(upVec.getText());

        parent.eyex = eye[0];   parent.eyey = eye[1];   parent.eyez = eye[2];
        parent.lookatx = at[0]; parent.lookaty = at[1]; parent.lookatz = at[2];
        parent.upx = up[0];     parent.upy = up[1];     parent.upz = up[2];

	parent.showStatus("Moving Viewpoint...");
	parent.view = parent.calculateView(perspBox.getState());
	parent.reTransform();
	parent.showStatus("Viewpoint Moved.");
      } else if (e.getActionCommand().equals("Reset Viewpoint")) {
        eyeVec.setText(parent.eyeV);
        lookAtVec.setText(parent.atV);
	upVec.setText(parent.upV);

	float eye[] = stringToVec(eyeVec.getText());
	float at[] =  stringToVec(lookAtVec.getText());
	float up[] =  stringToVec(upVec.getText());

        parent.eyex = eye[0];   parent.eyey = eye[1];   parent.eyez = eye[2];
        parent.lookatx = at[0]; parent.lookaty = at[1]; parent.lookatz = at[2];
        parent.upx = up[0];     parent.upy = up[1];     parent.upz = up[2];

	parent.showStatus("Resetting Viewpoint...");
	parent.view = parent.calculateView(perspBox.getState());
	parent.reTransform();
	parent.showStatus("Viewpoint Reset.");

      } else if (e.getActionCommand().startsWith("Dispose",0)) {
	dispose();
      }
    }catch (NumberFormatException ex) {
      parent.showStatus("Data Format Error: " + ex.getMessage());
    }
    parent.reTransform();
  }
}


