// A basic OpenGL program.
// Displays a wireframe house, viewed along the z axis.

import java.awt.*;
import java.awt.event.*;
import com.hermetica.magician.*;
import geometry.*;

public class TeaPotLid extends Frame
implements GLEventListener {

    static int subdivision = 5;		// init number of subdivisions
    static int mode = 2;		// 1: Shaded  2: Wireframe
    static boolean control = false;	// if the control poits should be displayed

    float[][] vertices = {
        {0,0,3.15f},
        {0.8f,0,3.15f},
        {0.8f,-0.45f,3.15f},
        {0.45f,-0.8f,3.15f},
        {0,-0.8f,3.15f},
        {0,0,2.85f},
        {0.2f,0,2.7f},
        {0.2f,-0.112f,2.7f},
        {0.112f,-0.2f,2.7f},
        {0,-0.2f,2.7f},
        {0.4f,0,2.55f},
        {0.4f,-0.224f,2.55f},
        {0.224f,-0.4f,2.55f},
        {0,-0.4f,2.55f},
        {1.3f,0,2.55f},
        {1.3f,-0.728f,2.55f},
        {0.728f,-1.3f,2.55f},
        {0,-1.3f,2.55f},
        {1.3f,0,2.4f},
        {1.3f,-0.728f,2.4f},
        {0.728f,-1.3f,2.4f},
        {0,-1.3f,2.4f}
    };

    int[][] patch1 = {{1,1,1,1},{2,3,4,5},{6,6,6,6},{7,8,9,10}};
    int[][] patch2 = {{7,8,9,10},{11,12,13,14},{15,16,17,18},{19,20,21,22}};

    private CoreGL gl_ = new CoreGL();
    private CoreGLU glu_ = new CoreGLU();
    private GLComponent glc = null;
    private Trackball trackball;

    private WindowListener quitOnClose = new WindowAdapter() {
        public void windowClosing(WindowEvent e) { System.exit(0); }
    };

    public static void main( String argv[] ) {
        new TeaPotLid();
    }

    public TeaPotLid() {
        super("Teapot Lid");
        glc = (GLComponent) GLDrawableFactory.createGLComponent(400,400);
       /* setLayout(new FlowLayout());
    	Checkbox controlPointsDisplay = new Checkbox ("Display Control Points", true);
        add(controlPointsDisplay);
        controlPointsDisplay.addItemListener(this);
       */ trackball = new Trackball(glc);
        add( glc );
        addWindowListener(quitOnClose);
        pack(); show();
        
        glc.addGLEventListener( this );
        glc.initialize();
    }

    // The remaining four methods ...
    // ... implement the GLEventListener interface

    public void reshape( GLDrawable component, int x, int y, int width, int height ) {
        // Called at start, and whenever user resizes component
        int size = Math.min(width, height);
        gl_.glViewport(component, x, y, size, size);
        gl_.glLoadIdentity();
        if (width <= height) { glu_.gluOrtho2D(0.0,30.0,0.0,30.0*height/width); }
        else glu_.gluOrtho2D(0.0,30.0*height/width,0.0,30.0);
        gl_.glMatrixMode(GL.GL_MODELVIEW);
    }
    
    // compute the b(i or j) from s or t 
    private float B(int i, float x){
    switch (i) {
    	case 1 : return (1-x)*(1-x)*(1-x); 
	case 2 : return 3*x*(1-x)*(1-x);
	case 3 : return 3*x*x*(1-x);
	case 4 : return x*x*x;
	default: { System.out.println("Failure in computation of B.");
		   return 0.0f; }
	}
    }

    // compute the b'(i of j) from s or t
    private float Bprime(int i, float x){
    switch (i) {
    	case 1: return -3*(x-1)*(x-1);
    	case 2: return 3*(x-1)*(3*x-1);
    	case 3: return 3*x*(2-3*x);
    	case 4: return 3*x*x;
    	default:  { System.out.println("Failure in computation of B'.");
		   return 0.0f; }
    	}
    }

    // derive a point's position using the tensor product form
    private Vector3f pst(int[][] patch, float s, float t) {
    Vector3f result = new Vector3f(0,0,0);
    for (int i=1; i<5; i++) {
	for (int j=1; j<5; j++) {
	    result.addScaled((B(i,s)*B(j,t)),new Vector3f (vertices[patch[i-1][j-1]-1]));
	}
    }
    return result;
    }

    // derive a point's normal via the cross product of the derivatives in s and t directions
    // (must be nug inside, the output is not satisfactory)
    private Vector3f getNormal(int[][] patch, float s, float t) {
    Vector3f result = new Vector3f(0,0,0);
    Vector3f temp1 = new Vector3f(0,0,0);
    Vector3f temp2 = new Vector3f(0,0,0);
    for (int i=1; i<5; i++) {
	for (int j=1; j<5; j++) {
	    System.out.println("i: " + i + "   j: " + j); 
	    temp1.addScaled((Bprime(i,s)*B(j,t)),new Vector3f (vertices[patch[i-1][j-1]-1]));
	    System.out.println("Temp1: " + temp1.toString());
	    temp2.addScaled((B(i,s)*Bprime(j,t)),new Vector3f (vertices[patch[i-1][j-1]-1]));
	    System.out.println("Temp2: " + temp2.toString());
	    temp1.normalise();
	    temp2.normalise();
	    result.add(temp2.cross(temp1));
	    // System.out.println("i: " + i + "   j: " + j + "   Normal: " + result.toString());
	}
    }
    return result;
    }

    // precalc polygones for surface rendering
    private void subdivide(int subdivision, int vertexlist){	
       
        // compile a display list for the shaded lid
	gl_.glNewList(1,GL.GL_COMPILE);
	gl_.glColor3f(1.0f, 0.5f, 0.5f);
	System.out.println(subdivision);

        for (int subd_s = 0; subd_s < subdivision; subd_s++) {	// for each subdivision in s do (first patch)
	    	gl_.glBegin(GL.GL_QUADS);			// 'handier' than triangle_list or quad_strip: we don't need 
	    							// to handle special cases. drawback: more computations
	    	for (int subd_t=0; subd_t < subdivision; subd_t++) {
	
		    gl_.glNormal3fv(getNormal(patch1, ((1.0f/subdivision)*subd_t), (1.0f/subdivision)*(subd_s+1)).get3fv());
		    gl_.glVertex3fv(pst(patch1, ((1.0f/subdivision)*subd_t), (1.0f/subdivision)*(subd_s+1)).get3fv());	    	
		    gl_.glNormal3fv(getNormal(patch1, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*subd_s)).get3fv());
		    gl_.glVertex3fv(pst(patch1, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*subd_s)).get3fv());
		    gl_.glNormal3fv(getNormal(patch1, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*subd_s)).get3fv()); 
	    	    gl_.glVertex3fv(pst(patch1, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*subd_s)).get3fv());
		    gl_.glNormal3fv(getNormal(patch1, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*(subd_s+1))).get3fv());
		    gl_.glVertex3fv(pst(patch1, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*(subd_s+1))).get3fv());
	    	}
	    	gl_.glEnd();
	 } 
	 for (int subd_s = 0; subd_s < subdivision; subd_s++) {	// for each subdivision in s do (second patch)
	    	gl_.glBegin(GL.GL_QUADS);
	    	for (int subd_t=0; subd_t < subdivision; subd_t++) {
		    gl_.glNormal3fv(getNormal(patch2, ((1.0f/subdivision)*subd_t), (1.0f/subdivision)*(subd_s+1)).get3fv());
		    gl_.glVertex3fv(pst(patch2, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*(subd_s+1))).get3fv());	    	
		    gl_.glNormal3fv(getNormal(patch2, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*subd_s)).get3fv());
	   	    gl_.glVertex3fv(pst(patch2, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*subd_s)).get3fv());
		    gl_.glNormal3fv(getNormal(patch2, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*subd_s)).get3fv()); 
	    	    gl_.glVertex3fv(pst(patch2, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*subd_s)).get3fv());
		    gl_.glNormal3fv(getNormal(patch2, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*(subd_s+1))).get3fv());
	    	    gl_.glVertex3fv(pst(patch2, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*(subd_s+1))).get3fv());
	   	}    
	    	gl_.glEnd();
	 }
	 gl_.glEndList();	

         // compile a display list for the line representation of the lid
	 gl_.glNewList(2,GL.GL_COMPILE);
  	 gl_.glColor3f(0.0f, 0.5f, 0.5f);

         for (int subd_s = 0; subd_s < subdivision; subd_s++) {	// for each subdivision in s do (first patch)
	    	gl_.glBegin(GL.GL_LINES);
	    	for (int subd_t=0; subd_t < subdivision; subd_t++) {
		    gl_.glVertex3fv(pst(patch1, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*(subd_s+1))).get3fv());
	   	    gl_.glVertex3fv(pst(patch1, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*subd_s)).get3fv());
	    	    gl_.glVertex3fv(pst(patch1, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*subd_s)).get3fv());
		    gl_.glVertex3fv(pst(patch1, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*(subd_s+1))).get3fv());
		    gl_.glVertex3fv(pst(patch1, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*(subd_s+1))).get3fv());
	    	}
	    	gl_.glEnd();
	 } 
	 for (int subd_s = 0; subd_s < subdivision; subd_s++) {	// for each subdivision in s do (second patch)
	    	gl_.glBegin(GL.GL_LINES);
	    	for (int subd_t=0; subd_t < subdivision; subd_t++) {
		    gl_.glVertex3fv(pst(patch2, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*(subd_s+1))).get3fv());	    	
	   	    gl_.glVertex3fv(pst(patch2, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*subd_s)).get3fv());
	    	    gl_.glVertex3fv(pst(patch2, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*subd_s)).get3fv());
	    	    gl_.glVertex3fv(pst(patch2, ((1.0f/subdivision)*(subd_t+1)), ((1.0f/subdivision)*(subd_s+1))).get3fv());
		    gl_.glVertex3fv(pst(patch2, ((1.0f/subdivision)*subd_t), ((1.0f/subdivision)*(subd_s+1))).get3fv());	    	
	   	}    
	    	gl_.glEnd();
	 }
	 gl_.glEndList();
	
	// the control points' net  
        gl_.glNewList(3,GL.GL_COMPILE);
	gl_.glColor3f(1.0f, 0.0f, 0.0f);
        for (int i = 0; i < patch1.length; i++) {
  	  gl_.glBegin(GL.GL_LINE_STRIP);
          for (int j = 0; j < patch1[i].length; j++) {
            gl_.glVertex3fv(vertices[patch1[i][j]-1]);
	  }
	  gl_.glEnd();
	}
	for (int i = 0; i < patch1.length; i++) {
  	  gl_.glBegin(GL.GL_LINE_STRIP);
          for (int j = 0; j < patch1[i].length; j++) {
            gl_.glVertex3fv(vertices[patch1[j][i]-1]);
	  }
	  gl_.glEnd();
	}
        for (int i = 0; i < patch1.length; i++) {
  	  gl_.glBegin(GL.GL_LINE_STRIP);
          for (int j = 0; j < patch2[i].length; j++) {
            gl_.glVertex3fv(vertices[patch2[i][j]-1]);
	  }
	  gl_.glEnd();
	}
	for (int i = 0; i < patch1.length; i++) {
  	  gl_.glBegin(GL.GL_LINE_STRIP);
          for (int j = 0; j < patch2[i].length; j++) {
            gl_.glVertex3fv(vertices[patch2[j][i]-1]);
	  }
	  gl_.glEnd();
	} 
	gl_.glEndList();
   }	

    // Called as a side-effect of the glc.initialize() call
    public void initialize( GLDrawable component ) {
        gl_.glShadeModel(GL.GL_SMOOTH);			// general render mode
	int vertexlist = gl_.genLists(3);		// allocato space for display lists
	subdivide(subdivision, vertexlist);		// do the precalcullation for the meshes
    }

    public void display( GLDrawable component ) {
        
        // Display the 3D scene.
        // Called whenever component is painted.
 	   gl_.glClearColor(0f,0f,0.45f,0f);
 	   gl_.glMatrixMode ( GL.GL_PROJECTION );
	   gl_.glLoadIdentity();
	   gl_.glOrtho(-2,2,-2,2,-3,3);
	   gl_.glMatrixMode( GL.GL_MODELVIEW );
 	   gl_.glLoadIdentity();
        gl_.glMultMatrixf(trackball.getGLRotation());
        gl_.glTranslatef(-0.5f,-0.5f,-1);
        gl_.glClear(GL.GL_COLOR_BUFFER_BIT);
        
        
        gl_.glCallList(mode);
        if (control==true) gl_.glCallList(3);
	gl_.glRotatef(90.0f,0.0f,0f,1.0f);
	gl_.glCallList(mode);
	if (control==true) gl_.glCallList(3);
	gl_.glRotatef(90.0f,0.0f,0f,1.0f);
	gl_.glCallList(mode);
	if (control==true) gl_.glCallList(3);
	gl_.glRotatef(90.0f,0.0f,0,1.0f);
	gl_.glCallList(mode);
	if (control==true) gl_.glCallList(3);
	gl_.glFlush();
    }

    public GL getGL() {
        // Return a GL object[for use by Magician code]
    	return gl_;
    }
}


class Trackball implements MouseListener, MouseMotionListener {
    // Implements a virtual trackball.
    
    private Component component; // Thing to repaint after each rotation
    private Vector3f v0, v1;    // Vectors from centre of trackball
    Transformation totalRotation = new Transformation(); // Identity
    
    Trackball(Component component) {
        this.component = component;
        component.addMouseListener(this);
        component.addMouseMotionListener(this);
    }
    
    float[] getGLRotation() {
        return totalRotation.getGLMatrix();
    }
    
    // Now the code to handle the Mouse actions, i.e. the virtual trackball.
    // Have to implement both MouseListener and MouseMotionListener
    
    public void mousePressed(MouseEvent e) {
        // Save the vector from the trackball centre to the start point of the
        // drag (i.e. where the mouse is clicked) in instance variable v0
        v0 = makeTrackballVector(e);
    }

    public void mouseDragged(MouseEvent e) {
        Vector3f v1 = makeTrackballVector(e);
         Vector3f axis = v0.cross(v1);
        if (axis.length() > 1.0e-5f) { // Ignore negligible drags
            axis = axis.normalised();
            float degrees = (float) (Math.acos(v0.dot(v1)) * 180 / Math.PI);
            totalRotation.multiplyOnLeft(Transformation.rotation(degrees, axis));
            component.repaint();
        }
        v0 = v1;
    }

    Vector3f makeTrackballVector(MouseEvent e) {
        Point p = e.getPoint();
        Dimension dim = e.getComponent().getSize();
        int width = dim.width;
        int height = dim.height;

        float x = ( 2 * p.x - width ) / (float) width;
        float y = ( height - 2 * p.y ) / (float) height;
        float tmp = 1 - x*x - y*y;
        float z = tmp < 0 ? 0 : (float) Math.sqrt(tmp);
        return new Vector3f(x, y, z).normalised();
    }

    // Other methods of MouseListener, MouseMotionListener are empty

    public void mouseReleased(MouseEvent e) {}
    public void mouseClicked(MouseEvent e) {}
    public void mouseMoved(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}   
};