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;

	float v0x, v0y, v0z;

	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);
		}
	}
	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 init( )
	{
		raster = new Raster(size().width, size().height);
		raster.fill(getBackground());
		screen = raster.toImage();

		eyex = 0;        eyey = 0;        eyez = -10;
		lookatx = 0;     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();
		model.scale((float)0.2,(float)0.2,(float)0.2);
		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();
	}
	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 mouseDrag(Event e, int x, int y)
	{
		if (e.metaDown()) {
			showStatus("Resetting model matrix");
			model = new Matrix3D();
		} 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;
	}
	public void paint(Graphics g)
	{
		g.drawImage(screen, 0, 0, this);
	}
	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 update(Graphics g)
	{
		paint(g);
	}
}
