/*
 * PuppetApplet.java
 *
 * (C) 2004, Alex S.
 */
 
import java.awt.*;
import java.util.Vector;


public class PuppetApplet extends BufferedApplet
{
	pMatrix matrix = new pMatrix();
	pMatrix pmatrix = new pMatrix();

	// points/colors that are rendered.
	Vector points = new Vector();
	Vector colors = new Vector();

	// for z sorting
	double[] avgz;
	int[] polyorder;

	// pre-computed points.	
	Vector curve_points = new Vector();
	Vector object_points = new Vector();

	// current rendering color
	Color currentColor = Color.red;

	// angle, mouse state
	private double m_anglex,m_angley;
	private int m_mousex,m_mousey;

    private double[][][] mesh;
    private double[][][] sphere;
    private double[][][] losphere;	

    public double m_angledx = 0;
    public double m_angledy = Math.PI/128;
    
    public int renderSpheres = 1;
    public boolean dragged = false;
    
    /**
     * make a globe sphere mesh
     *
     * @param m Horizontal splits.
     * @param m Vertical Splits.
     */
    public double[][][] globeMesh(int m,int n){
        double mesh[][][] = new double[m][n][3];
        for (int i = 0 ; i < m ; i++){
            for (int j = 0 ; j < n ; j++) {
                double u = i / (m - 1.0);
                double v = j / (n - 1.0);
                double theta = 2.0 * Math.PI * u;
                double phi = Math.PI * v - Math.PI/2.0;
                mesh[i][j][0] = Math.cos(theta) * Math.cos(phi);
                mesh[i][j][1] = Math.sin(theta) * Math.cos(phi);
                mesh[i][j][2] = Math.sin(phi);
            }
        }
        return mesh;
    }

    /**
     * dumps phere to rendering pipeline
     */
    public void dumpSphere(){
        int i,j;
        double[][][] mesh = sphere;
        for(i=0;i<mesh.length-1;i++){
            for(j=0;j<mesh[i].length-1;j++){
            	addVertex(mesh[i][j][0],mesh[i][j][1],mesh[i][j][2]);
            	addVertex(mesh[i+1][j][0],mesh[i+1][j][1],mesh[i+1][j][2]);
            	addVertex(mesh[i+1][j+1][0],mesh[i+1][j+1][1],mesh[i+1][j+1][2]);
            	addVertex(mesh[i][j+1][0],mesh[i][j+1][1],mesh[i][j+1][2]);
            }
        }
    }

    /**
     * dumps phere to rendering pipeline
     */
    public void dumpLoSphere(){
        int i,j;
        if(renderSpheres != 0){
            for(i=0;i<mesh.length-1;i++){
                for(j=0;j<mesh[i].length-1;j++){
            	    addVertex(mesh[i][j][0],mesh[i][j][1],mesh[i][j][2]);
            	    addVertex(mesh[i+1][j][0],mesh[i+1][j][1],mesh[i+1][j][2]);
            	    addVertex(mesh[i+1][j+1][0],mesh[i+1][j+1][1],mesh[i+1][j+1][2]);
                	addVertex(mesh[i][j+1][0],mesh[i][j+1][1],mesh[i][j+1][2]);
                }
            }
        }else{
    		j=object_points.size();
	    	for(i=0;i<j;i++){
		    	double[] point = (double[])object_points.elementAt(i);
			    addVertex(point[0],point[1],point[2]);
		    }
        }
    }


	/**
	 * initialize things
	 */
	public void init(){
		super.init();
        
        sphere = globeMesh(32,32);
        losphere = globeMesh(6,6);
        mesh = losphere;
        
		m_mousex = m_mousey = 0;
		m_anglex = m_angley = 0;
		
		pmatrix.translate(bounds().width/2,bounds().height/2,0);
		pmatrix.perspective(bounds().width,-1);
        
        
		// initialize curve
		Vector v_points = new Vector();
		Vector v_faces = new Vector();

		v_points.addElement(new double[]{1,-1,1,1});
		v_points.addElement(new double[]{1,1,1,1});
		v_points.addElement(new double[]{-1,1,1,1});
		v_points.addElement(new double[]{-1,-1,1,1});
		
		v_points.addElement(new double[]{1,-1,-1,1});
		v_points.addElement(new double[]{1,1,-1,1});
		v_points.addElement(new double[]{-1,1,-1,1});
		v_points.addElement(new double[]{-1,-1,-1,1});
				
		v_faces.addElement(new int[]{0,1,2,3});
		v_faces.addElement(new int[]{4,7,6,5});
		v_faces.addElement(new int[]{0,4,5,1});
		v_faces.addElement(new int[]{7,3,2,6});
		v_faces.addElement(new int[]{1,5,6,2});
		v_faces.addElement(new int[]{0,3,7,4});
		
		
		for(int i=0;i<v_faces.size();i++){
			int[] face = (int[])v_faces.elementAt(i);
			for(int j=0;j<4;j++){
				object_points.addElement(v_points.elementAt(face[j]));
			}
		}

		v_points.removeAllElements();
		v_faces.removeAllElements();
        
	}

	/**
	 * similar to glVertex
	 */
	private void addVertex(double x,double y,double z){
		points.addElement(matrix.mult(new double[]{x,y,z,1}));
		colors.addElement(currentColor);
	}
    
    double time = 0;
    
    public void dumpModel(){
    
    
        time += 0.01;
        if(time >= 1)
            time = 0;    
        matrix.push();
        
        // 1 
        //matrix.rotatez(0);
        matrix.rotatez(0.1*(Math.sin(time*3 * Math.PI*2)+Math.cos(time*3 * Math.PI*2)));
        matrix.rotatex(0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatey(0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));
        
	    matrix.translate(0,.4,0);
	    matrix.push();
	    matrix.scale(.3,.4,.3);
	    dumpLoSphere();
        matrix.pop();
	    matrix.translate(0,.4,0);

        // 2 (stomach)
        //matrix.rotatez(0);
        matrix.rotatez(-0.1*(Math.sin(time*3 * Math.PI*2)+Math.cos(time*3 * Math.PI*2)));
        matrix.rotatex(-0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatey(-0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));

        
	    matrix.translate(0,1,0);
	    matrix.push();
	    matrix.scale(.5,1,.3);
	    dumpLoSphere();	    
        matrix.pop();
	    matrix.translate(0,1,0);

        // 3 (neck)
        matrix.push();
	        //matrix.rotatex(0);
        matrix.rotatez(0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*3 * Math.PI*2)));
        matrix.rotatex(0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*8 * Math.PI*2)));
        matrix.rotatey(0.1*(Math.sin(time*5 * Math.PI*2)+Math.cos(time*7 * Math.PI*2)));
            
            matrix.translate(0,.2,0);
	        matrix.push();
	        matrix.scale(.1,.2,.1);
	        dumpLoSphere();
            matrix.pop();
    	    matrix.translate(0,.2,0);
            
            // 4  (head)
            //matrix.rotatez(0);
        matrix.rotatez(0.2*(Math.sin(time * Math.PI*2)+Math.cos(time * Math.PI*2)));
        matrix.rotatex(0.1*(Math.sin(time * Math.PI*2)+Math.cos(time * Math.PI*2)));
        matrix.rotatey(0.1*(Math.sin(time * Math.PI*2)+Math.cos(time * Math.PI*2)));
	        matrix.translate(0,.6,0);
	        matrix.push();
	        matrix.scale(.3,.6,.3);
	        dumpLoSphere();	    
            matrix.pop();
        matrix.pop();
        
        // 5 (left arm)
        matrix.push();
	        //matrix.rotatez(0);           // SHOULDER JOINT

	        matrix.translate(-0.4,0,0);
	        matrix.push();
	        matrix.scale(.4,.1,.1);
	        dumpLoSphere();
            matrix.pop();
	        matrix.translate(-0.4,0,0);
        
	        //matrix.rotatez(0);           // SHOULDER JOINT
        matrix.rotatez(0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatex(0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*6 * Math.PI*2)));
        matrix.rotatey(0.1*(Math.sin(time*6 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));

	        matrix.translate(0,-0.7,0);
	        matrix.push();
	        matrix.scale(.2,0.7,.2);
	        dumpLoSphere();
            matrix.pop();
	        matrix.translate(0,-0.7,0);

	        //matrix.rotatez(0);          // ELBOW JOINT
        matrix.rotatez(-0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatex(-0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*6 * Math.PI*2)));
        matrix.rotatey(-0.1*(Math.sin(time*6 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));

	        matrix.translate(0,-0.7,0);
	        matrix.push();
	        matrix.scale(.2,0.7,.2);
	        dumpLoSphere();	    
            matrix.pop();
	        matrix.translate(0,-0.7,0);

	        //matrix.rotatex(0);          // WRIST JOINT
        matrix.rotatez(0.1*(Math.sin(time*6 * Math.PI*2)+Math.cos(time*5 * Math.PI*2)));
        matrix.rotatex(0.1*(Math.sin(time*3 * Math.PI*2)+Math.cos(time*3 * Math.PI*2)));
        matrix.rotatey(0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*5 * Math.PI*2)));

	        matrix.translate(0,-.4,0);
	        matrix.push();
	        matrix.scale(.2,.4,.2);
	        dumpLoSphere();
            matrix.pop();
        
        matrix.pop();
        
        // 6 (right arm)
        matrix.push();
	        //matrix.rotatez(0);           // SHOULDER JOINT

	        matrix.translate(0.4,0,0);
	        matrix.push();
	        matrix.scale(.4,.1,.1);
	        dumpLoSphere();
            matrix.pop();
	        matrix.translate(0.4,0,0);
        
	        //matrix.rotatez(0);           // SHOULDER JOINT
        matrix.rotatez(0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatex(0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*6 * Math.PI*2)));
        matrix.rotatey(0.1*(Math.sin(time*6 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));
	        matrix.translate(0,-0.7,0);
	        matrix.push();
	        matrix.scale(.2,0.7,.2);
	        dumpLoSphere();
            matrix.pop();
	        matrix.translate(0,-0.7,0);

	        //matrix.rotatez(0);          // ELBOW JOINT
        matrix.rotatez(-0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatex(-0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*6 * Math.PI*2)));
        matrix.rotatey(-0.1*(Math.sin(time*6 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));

	        matrix.translate(0,-0.7,0);
	        matrix.push();
	        matrix.scale(.2,0.7,.2);
	        dumpLoSphere();	    
            matrix.pop();
	        matrix.translate(0,-0.7,0);

	        //matrix.rotatex(0);          // WRIST JOINT
        matrix.rotatez(0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*6 * Math.PI*2)));
        matrix.rotatex(0.1*(Math.sin(time*3 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));
        matrix.rotatey(0.1*(Math.sin(time*1 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));

	        matrix.translate(0,-.4,0);
	        matrix.push();
	        matrix.scale(.2,.4,.2);
	        dumpLoSphere();
            matrix.pop();
        
        matrix.pop();
        
        matrix.pop();
                
        
        // 5 (left leg)
        matrix.push();
	        //matrix.rotatez(0);

	        matrix.translate(-0.2,0,0);
	        matrix.push();
	        matrix.scale(.2,.1,.1);
	        dumpLoSphere();
            matrix.pop();
	        matrix.translate(-0.2,0,0);
        
	        //matrix.rotatez(0);
        matrix.rotatez(0.1*(Math.sin(time*6 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatex(0.1*(Math.sin(time*3 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatey(0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));

	        matrix.translate(0,-1,0);
	        matrix.push();
	        matrix.scale(.2,1,.2);
	        dumpLoSphere();
            matrix.pop();
	        matrix.translate(0,-1,0);

	        //matrix.rotatez(0);
        matrix.rotatez(-0.1*(Math.sin(time*6 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatex(-0.1*(Math.sin(time*3 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatey(-0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));

	        matrix.translate(0,-1,0);
	        matrix.push();
	        matrix.scale(.2,1,.2);
	        dumpLoSphere();	    
            matrix.pop();
	        matrix.translate(0,-1,0);

	        //matrix.rotatex(-Math.PI);
        matrix.rotatez(0.01*(Math.sin(time * Math.PI*2)+Math.cos(time * Math.PI*2)));
        matrix.rotatex(-Math.PI + 0.01*(Math.sin(time * Math.PI*2)+Math.cos(time * Math.PI*2)));
        matrix.rotatey(0.01*(Math.sin(time * Math.PI*2)+Math.cos(time * Math.PI*2)));

	        matrix.translate(0,-.2,-.5);
	        matrix.push();
	        matrix.scale(.2,.1,.5);
	        dumpLoSphere();
            matrix.pop();
        
        matrix.pop();
        
        
        
        // 6 (right leg)
        matrix.push();
	        //matrix.rotatez(0);

	        matrix.translate(0.2,0,0);
	        matrix.push();
	        matrix.scale(.2,.1,.1);
	        dumpLoSphere();
            matrix.pop();
	        matrix.translate(0.2,0,0);
        
	        //matrix.rotatez(0);
        matrix.rotatez(0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatex(0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*6 * Math.PI*2)));
        matrix.rotatey(0.1*(Math.sin(time*6 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));

	        matrix.translate(0,-1,0);
	        matrix.push();
	        matrix.scale(.2,1,.2);
	        dumpLoSphere();
            matrix.pop();
	        matrix.translate(0,-1,0);

	        //matrix.rotatez(0);
        matrix.rotatez(-0.1*(Math.sin(time*2 * Math.PI*2)+Math.cos(time*4 * Math.PI*2)));
        matrix.rotatex(-0.1*(Math.sin(time*4 * Math.PI*2)+Math.cos(time*6 * Math.PI*2)));
        matrix.rotatey(-0.1*(Math.sin(time*6 * Math.PI*2)+Math.cos(time*2 * Math.PI*2)));

	        matrix.translate(0,-1,0);
	        matrix.push();
	        matrix.scale(.2,1,.2);
	        dumpLoSphere();	    
            matrix.pop();
	        matrix.translate(0,-1,0);

	        //matrix.rotatex(-Math.PI);
        matrix.rotatez(0.01*(Math.sin(time * Math.PI*2)+Math.cos(time * Math.PI*2)));
        matrix.rotatex(-Math.PI + 0.01*(Math.sin(time * Math.PI*2)+Math.cos(time * Math.PI*2)));
        matrix.rotatey(0.01*(Math.sin(time * Math.PI*2)+Math.cos(time * Math.PI*2)));

	        matrix.translate(0,-.2,-.5);
	        matrix.push();
	        matrix.scale(.2,.1,.5);
	        dumpLoSphere();
            matrix.pop();
        
        matrix.pop();
               
    }

	/**
	 * dumps the scene
	 */
	public void dumpScene(){
        currentColor = Color.yellow;
        
        matrix.rotatey(Math.PI);
        matrix.rotatex(Math.PI);
        
        dumpModel();
    }

	/**
	 * sort via average z 
	 */
	public void sort(){
 		int i,j;
		int e1;
        double e2;
        for(i=1;i<polyorder.length;i++){
            e1 = polyorder[i];
            e2 = avgz[i];
			
            for(j=i-1;j>=0 && avgz[j] > e2;j--){
                avgz[j+1] = avgz[j];
				polyorder[j+1] = polyorder[j];
			}
			polyorder[j+1] = e1;			
            avgz[j+1] = e2;
        }
	}

	/**
  	 * function that gets called whenever something needs to be
	 * rendered.
	 */
    public void render(Graphics g) {
        if (true || damage) {		
            m_angley += m_angledy;
            m_anglex += m_angledx;
         	
            g.setColor(Color.lightGray);
         	g.fillRect(0, 0, bounds().width, bounds().height);
            
         	g.setColor(Color.gray);
         	g.fillRect(0, (int)(bounds().height*3.0/4.0), bounds().width, bounds().height);


			matrix.push();
			
			matrix.translate(0,0,-144);
			matrix.scale(10);
			matrix.rotatey(m_angley);
			matrix.rotatex(m_anglex);

			dumpScene();

			// calculate average z for all points.
			avgz = new double[points.size() / 4];
			polyorder = new int[points.size() / 4];
						

			int k,i=0,j=points.size();
			double[] plane = new double[4];

			for(i=0;i<j;i+=4){
				double sum = 0;
				for(k=0;k<4;k++){
					double[] point = (double[])points.elementAt(i+k);
					sum += point[2];
				}
				avgz[i/4] = sum / 4;
				polyorder[i/4] = i/4;
			}
			sort(); // sort polyorder by avgz

			int[] x = new int[4];
			int[] y = new int[4];
			
			j = polyorder.length;
			for(i=0;i<j;i++){
				int poly = polyorder[i] * 4;
				
				double[] p0 = (double[])points.elementAt(poly+0);
				double[] p1 = (double[])points.elementAt(poly+1);
				double[] p2 = (double[])points.elementAt(poly+2);
				double[] p3 = (double[])points.elementAt(poly+3);
				
				planeFromPoints(plane,p0,p1,p2,p3);
				double d = plane[3];

				if(d > 0){
				
					for(k=0;k<4;k++){
						double[] point = (double[])points.elementAt(poly+k);
						point = pmatrix.mult(point);
						x[k] = (int)(point[0] / point[3]);
						y[k] = (int)(point[1] / point[3]);
					}
					Color c = (Color)colors.elementAt(poly);
					
					double color1 = dotProduct(plane,new double[]{0,0,-1});
					
					if(color1 >= 0){
		    			g.setColor(
    						new Color(
			    				(float)(((c.getRed()+50) * color1)/306.0),
				    			(float)(((c.getGreen()+50) * color1)/306.0),
					    		(float)(((c.getBlue()+50) * color1)/306.0)
						    )
					    );
					    g.fillPolygon(x,y,x.length);
					    //g.setColor(c);
					    //g.drawPolygon(x,y,x.length);
                    }
				}
			}
			
			avgz = null;
			polyorder = null;
			matrix.pop();
			points.removeAllElements();
			colors.removeAllElements();
      	}
    }


	/**
	 * given 2 points, find their difference
	 */
	void vectorSubtract (double[] va, double[] vb, double[] out){
		out[0] = va[0]-vb[0];
		out[1] = va[1]-vb[1];
		out[2] = va[2]-vb[2];
	}
	
	/**
	 * cross product of two vectors
	 */
	void crossProduct(double[] v1, double[] v2, double[] cross ) {
		cross[0] = v1[1]*v2[2] - v1[2]*v2[1];
		cross[1] = v1[2]*v2[0] - v1[0]*v2[2];
		cross[2] = v1[0]*v2[1] - v1[1]*v2[0];
	}

	/**
	 * normalize
	 */
	double vectorNormalize(double[] in, double[] out ) {
		double	length, ilength;
		length = Math.sqrt(in[0]*in[0] + in[1]*in[1] + in[2]*in[2]);
		if (length == 0){
			return 0;
		}
		ilength = 1.0/length;
		out[0] = in[0]*ilength;
		out[1] = in[1]*ilength;
		out[2] = in[2]*ilength;
		return length;
	}	

	/**
	 * dot product
	 */
	double dotProduct (double[] v1, double[] v2){
		return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
	}
		
	/**
	 * gets plane equation from three points on plane
	 */
	boolean planeFromPoints(double[] plane, 
            double[] a, double[] b,double[] c,double[] d ){
            
		double[] d1 = new double[4];
		double[] d2 = new double[4];
		vectorSubtract( b, a, d1 );
		if(dotProduct(d1,d1) < 0.02){
    		vectorSubtract( c, a, d1 );
            vectorSubtract( d, a, d2 );
        }else{
            vectorSubtract( c, a, d2 );
            if(dotProduct(d2,d2) < 0.02){
                vectorSubtract( d, a, d2 );
            }
        }
        crossProduct( d2, d1, plane );
		if ( vectorNormalize( plane, plane ) == 0 ) {
			return false;
		}
		plane[3] = dotProduct( a, plane );
		return true;
	}
	

	/**
	 * mouse event handler: let the user move things
	 */
	public boolean mouseDown(Event evt, int x, int y){
        dragged = false;

        m_angledx = 0;
        m_angledy = 0;

		return true;
	}
	public boolean mouseUp(Event evt, int x, int y){
        if(!dragged)
            renderSpheres ^= 1;
        dragged = false;
        m_angledx = 0;
        m_angledy = Math.PI/128;
		return true;
	}
	
	/**
	 * mouse event handler: let the user move things
	 */
	public boolean mouseDrag(Event evt, int x, int y){
        dragged = true;
    
		if((m_mousey - y) < 0)
			m_anglex += Math.PI/45;
		if((m_mousey - y) > 0)
			m_anglex -= Math.PI/45;

		if((m_mousex - x) < 0)
			m_angley -= Math.PI/45;
		if((m_mousex - x) > 0)
			m_angley += Math.PI/45;
		m_mousex = x;
		m_mousey = y;

		damage = true;
		return true;
	}	
}

