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;
    String bunghole;

    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 = 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(-4, 4, -4, 4, -1, -200);
        view.lookAt(eyex, eyey, eyez, lookatx, lookaty, lookatz, upx, upy, upz);
	bunghole=view.toString();
        
        model = new Matrix3D();
	model.rotate(0,1,0,3.141592653589f/4.0f);
        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);
	showStatus(bunghole);
    }

    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 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());
	raster.fill(new Color(0x0));
	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);
	    //	    showStatus("Point["+i+"]"+tranList[i]);
	}
    }
}
