import java.awt.*;
import java.awt.event.*;

public class canvas extends Canvas implements MouseMotionListener
{
private int width;       // width of the canvas
private int height;      // height of the canvas
    
private Image offImage;        // buffer for double buffering
private Graphics offGraphics;  // buffer for double buffering

public int    state; // animation cycle state
    
private int   mouse_state;
private double U;
private double V;

// mom matrix
public  matrix mom;
private matrix mview;
private matrix mpers;
private matrix mscrn;
private matrix identity;
    
// bsp trees for each frame
private bspnode tree[];
   
// field of view
public vector center;
public double  umin;
public double  vmin;
public double  umax;
public double  vmax;
public double  xmin;
public double  ymin;
public double  xmax;
public double  ymax;

// screen dimensions
public double nx;
public double ny;

// near and far plane constants
public double n;
public double f;
public double d;

// lookfrom, lookat etc...
public vector lf;
public vector la;
public vector Vup;

//--------- optimization ---------
public  static vector u = new vector();
public  static vector v = new vector();
public  static vector w = new vector();
public  static vector vect = new vector();
private static vector temp[] = null;
private static int    x[]    = null;
private static int    y[]    = null;

public vector Ka;
public vector Kd;
public vector Ke;
public vector Kh;
public int  e;
public vector TT1;
public vector TT2;
public vector TT3;

    
public  static void init_static()
    {
	if(temp == null)
	    {
		temp = new vector[10];
		for(int i = 0; i<10; i++)
		    temp[i] = new vector();
	    }
	x = new int[100]; // assume max 100 vertices in any polygon
	y = new int[100]; // assume max 100 vertices in any polygon
    }
//--------------------------------
    
public canvas(int W, int H)
    {
	width  = W;
	height = H;
	setSize(width, height);
	addMouseMotionListener(this);
	mouse_state = 0;
	nx = (double)(width);
	ny = (double)(height);

	mom      = new matrix();
	mview    = new matrix();
	mpers    = new matrix();
	mscrn    = new matrix();
	identity = new matrix();
	matrix.identity(identity);
	center = new vector();
	lf     = new vector();
	la     = new vector();
	Vup    = new vector(0, 1, 0);
	state  = 0;

	Ka = new vector(0.2f, 0.2f, 0.2f);
	Kd = new vector(0.6f, 0.6f, 0.6f);
	Ke = new vector(1.0f, 1.0f, 1.0f);
	Kh = new vector(1.0f, 1.0f, 1.0f);
	e = 1;
	TT1 = new vector(Ka.xyz[0]*Kd.xyz[0], Ka.xyz[1]*Kd.xyz[1], Ka.xyz[2]*Kd.xyz[2]);
	TT2 = new vector(Ke.xyz[0]*Kh.xyz[0], Ke.xyz[1]*Kh.xyz[1], Ke.xyz[2]*Kh.xyz[2]);
	TT3 = new vector(Ke.xyz[0]*Kd.xyz[0], Ke.xyz[1]*Kd.xyz[1], Ke.xyz[2]*Kd.xyz[2]);

    }
    
public void set(vector lookfrom, vector lookat, double radius,
		double near,      double far,     double d_nose)
    {
	vector.assign(la, lookat);
	vector.assign(lf, lookfrom);
	umin = -radius;
	vmin = -radius;
	umax =  radius;
	vmax =  radius;
	n = near;
	f = far;
	d = d_nose;
	xmin = (n/d)*umin;
	xmax = (n/d)*umax;
	ymin = (n/d)*vmin;
	ymax = (n/d)*vmax;

	matrix.view(lf, la, Vup, mview);
	matrix.persp(n, f, mpers);
	matrix.screen(nx, ny, xmin, ymin, xmax, ymax, mscrn);
	matrix.make_mom(mview, mpers, mscrn, mom);
    }
    
public void update(Graphics g)
    {
	paint(g);
    }

public void setSize(int sizex, int sizey)
    {
	width = sizex;
	height = sizey;
	nx = (double)(width);
	ny = (double)(height);
    }
    
public void paint(Graphics g)
    {
        Dimension thisbig = this.getSize();
        int mywidth  = thisbig.width;
        int myheight = thisbig.height;
	
        if (mywidth != width || myheight != height || offGraphics == null)
	    {
		setSize(mywidth, myheight);
		offImage = createImage(mywidth, myheight);
		offGraphics = offImage.getGraphics();
		matrix.screen(nx, ny, xmin, ymin, xmax, ymax, mscrn);
		matrix.make_mom(mview, mpers, mscrn, mom);
	    }
	
	offGraphics.setColor(Color.black);
	offGraphics.fillRect(0, 0, width, height);
	offGraphics.setColor(Color.white);
	if(tree != null)
	    {
		draw_tree(offGraphics, tree[state]);
		state++;
		if(state >= animator.max_steps)
		    state = 0;
	    }
	g.drawImage(offImage, 0, 0, this);
    }

public void draw_tree(Graphics g, bspnode t)
    {	
        if(t != null)
	    {
		int res = locate(lf, t.p);
		if(res >= 0)
		    {
			draw_tree(g, t.minus);
			draw_poly(g, t.p);
			draw_tree(g, t.plus);
		    }
		else
		    {
			draw_tree(g, t.plus);
			draw_poly(g, t.p);
			draw_tree(g, t.minus);
		    }
	    }
    }

public void draw_poly(Graphics g, polygon p)
    {
	for(int i=0; i<p.num; i++)
	    {
		matrix.mult(mom, p.vertex[i], temp[0]);
		x[i] = (int)(temp[0].xyz[0]);
		y[i] = (int)(temp[0].xyz[1]);
	    }
// need to get the color from the model here
//	g.setColor(Color.red);
	
	vector light = new vector();
	vector.subu(lf, p.vertex[0], light);
	vector Vinc = new vector();
	vector.subu(la, lf, Vinc);
	
	double dotp = (double)(Math.abs(vector.dot(p.N, Vinc)));
	vector temp_1 = new vector();
	vector.scale(p.N, dotp*2.0f, temp_1);
	vector Vref = new vector();
	vector.add(Vinc, temp_1, Vref);
	
	double TP1 = (double)(Math.pow(vector.dot(Vref, light), e));
	double TP2 = vector.dot(p.N, light);
	vector COLOR = new vector();
	vector temp_2 = new vector();
	vector.scale(TT3, TP2, temp_2);
	vector.add(TT1, temp_2, COLOR);
	g.setColor(new Color((float) min(1, max(0, Math.abs(COLOR.xyz[0]*p.color.xyz[0]))),
			     (float) min(1, max(0, Math.abs(COLOR.xyz[1]*p.color.xyz[1]))),
			     (float) min(1, max(0, Math.abs(COLOR.xyz[2]*p.color.xyz[2])))));
	g.fillPolygon(x, y, p.num);

	/*
	vector temp_3 = new vector();
	vector temp_4 = new vector();
	vector.add(p.vertex[0], p.N, temp_3);
	matrix.mult(mom, temp_3, temp_4);
	int temp_x_1 = (int)(temp_4.xyz[0]);
	int temp_y_1 = (int)(temp_4.xyz[1]);

	g.setColor(Color.white);
	g.drawLine(x[0], y[0], temp_x_1, temp_y_1);
	*/
    }
    
public int locate(vector p, polygon root)
    {
	double dotp = (root.N.xyz[0]*(p.xyz[0] - root.vertex[0].xyz[0]) +
		      root.N.xyz[1]*(p.xyz[1] - root.vertex[1].xyz[1]) +
		      root.N.xyz[2]*(p.xyz[2] - root.vertex[2].xyz[2]));
	if(dotp<0)
            return -1;
	if(dotp>0)
	    return 1;
	return 0;
    }
    
public void mouseDragged(MouseEvent e)
    {
	if(mouse_state == 0)
	    {
		U = (double)(e.getX());
		V = (double)(e.getY());
		mouse_state = 1;		
	    }
	else
	    {
		vector.subu(la, lf, w);
		vector.crossu(Vup, w, u);
		vector.cross(w, u, v);
		vector.assign(Vup, v);

		double du = (double)(e.getX() - U);
		double dv = (double)(e.getY() - V);
		U = (double)(e.getX());
		V = (double)(e.getY());

		vector.subtract(lf, la, vect);
		double dist = vect.magnitude();
		if((du!=0) && (dv!=0))
		    {
			vector.scale(v, dv*0.707f, temp[0]);
			vector.scale(u, du*0.707f, temp[1]);
			vector.add(lf, temp[1], temp[2]);
			vector.add(temp[0], temp[2], lf);
		    }
		else if(du != 0)
		    {
			vector.scale(u, du, temp[0]);
			vector.add(lf, temp[0], lf);
		    }
		else if(dv != 0)
		    {
			vector.scale(v, dv, temp[0]);
			vector.add(lf, temp[0], lf);
		    }
		vector.subtract(lf, la, vect);
		double newdist = vect.magnitude();
		vector.scale(vect, dist/newdist, vect);
		vector.add(la, vect, lf);
		matrix.view(lf, la, Vup, mview);
		matrix.make_mom(mview, mpers, mscrn, mom);
		mouse_state = 0;		
	    }
    }
    
public void mouseMoved(MouseEvent e)
    {
	mouse_state = 0;
    }

public void move_lf(double du, double dv)
    {
	vector.subu(la, lf, w);
	vector.crossu(Vup, w, u);
	vector.cross(w, u, v);
	vector.assign(Vup, v);	    
	
	vector.subtract(lf, la, vect);
	double dist = vect.magnitude();
	if((du!=0) && (dv!=0))
	    {
		vector.scale(v, dv*0.707f, temp[0]);
		vector.scale(u, du*0.707f, temp[1]);
		vector.add(lf, temp[1], temp[2]);
		vector.add(temp[0], temp[2], lf);
	    }
	else if(du != 0)
	    {
		vector.scale(u, du, temp[0]);
		vector.add(lf, temp[0], lf);
	    }
	else if(dv != 0)
	    {
		vector.scale(v, dv, temp[0]);
		vector.add(lf, temp[0], lf);
	    }
	vector.subtract(lf, la, vect);
	double newdist = vect.magnitude();
	vector.scale(vect, dist/newdist, vect);
	vector.add(la, vect, lf);
	matrix.view(lf, la, Vup, mview);
	matrix.make_mom(mview, mpers, mscrn, mom);
//	recompute();
    }
    
public void precompute()
    {
	System.out.println(animator.max_steps);
	tree = new bspnode[animator.max_steps];
	for(int i = 0; i<animator.max_steps; i++)
	    {
		for(int j = 0; j<animatorDB.num; j++)
		    animatorDB.animators[j].animate();
		for(int j = 0; j<modelDB.allocated; j++)
		    modelDB.models[j].compute_transforms(identity);
		for(int j = 0; j<poly.allocated; j++)
		    matrix.mult(poly.parents[j].roottrans, poly.vertices[j], poly.tranvert[j]);

		polygonlist pl = new polygonlist();
		for(int j = 0; j<bodypart.allocated; j++) // walk through all polygons
		    pl.insert(new polygon(bodypart.polygons[j], bodypart.polygons[j].parent.color));
		tree[i] = buildtree(pl);
	    }
    }

public bspnode buildtree(polygonlist pl)
    {
	if(pl.head == null)
	    return null;
	polygon root = pl.head.p;
	root.solve_N();
	pl.head = pl.head.next;
	polygonlink index = pl.head;
	polygonlist plus = new polygonlist();
	polygonlist minus = new polygonlist();
	while(index != null)
	    {
		int ret = root.locate(index.p);
		if(ret<0)
		    {
//			index.p.solve_N();
			minus.insert(index.p);
		    }
		else if(ret>0)
		    {
//			index.p.solve_N();
			plus.insert(index.p);
		    }
		else
		    {
			polygonpair pp = root.split(index.p);
			plus.insert(pp.plus);
			minus.insert(pp.minus);
		    }
		index = index.next;
	    }
	return (new bspnode(root, buildtree(plus), buildtree(minus)));
    }
    
public void dump()
    {
	System.out.println("models   : "+modelDB.allocated);
	System.out.println("bodyparts: "+bodypartDB.allocated);
	System.out.println("polygons : "+bodypart.allocated);
	System.out.println("vertices : "+poly.allocated);
    }

public double max(double a, double b)
    {
	if(a>b)
	    return a;
	return b;
    }

public double min(double a, double b)
    {
	if(a<b)
	    return a;
	return b;
    }
}

