package classwork;

import javax.media.opengl.*;
import java.util.ArrayList;
import jocode.*;
import jomodel.*;

/**
 * Use a JOModel object to load and render an OBJ model with
 * face groups, materials and texture.  The JOModel class can render
 * complicated objects that include multiple materials and textures.
 * <P>
 * Demonstrate how mouse positions can be mapped into the world space.
 * Click on the model to select the triangle at the mouse position.
 * <P>
 * Within the model faces can be organized into groups (look for lines that
 * start with g in the obj file.  Each group can have a different material
 * and texture, which will be specified in the .mtl file.
 * <P>
 * JOModel will load the materials in the .mtl file and will draw the model
 * group by group, applying the correct materials for each group of faces.
 */
public class GLART_7_modelviewer_new extends JOApp {
    // holds mesh and materials, draws the mesh with groups
    JOModel model;
    float rotation = 0;
    float cameraY = 7;
    float cameraZ = 10;
    JOVector mousePos = new JOVector();
    JOVector mousePrevPos = new JOVector();
    // mouseDragged() will change model rotation
    float rotateModelX = 0;
    float rotateModelY = 0;

    /**
     * Main function creates and runs the application.
     */
    public static void main(String args[]) {
    	GLART_7_modelviewer_new app = new GLART_7_modelviewer_new();
    	displayWidth = 800;
    	displayHeight = 600;
    	//fullScreen = true;
        app.run();
    }

    /**
     * Initialize
     */
    public void setup() {
        // color of light source
        float lightDiffuse[]  = { .9f, .9f, .9f, 1f }; // direct light
        float lightSpecular[] = { .9f, .9f, .9f, 1f }; // highlight
        float lightAmbient[]  = { .4f, .4f, .45f, 1f }; // scattered light
        // light position: if last value is 0, then this describes light direction.  If 1, then light position.
        float lightPosition[] = { -4f, 4f, 6, 0f };

        // make sure OpenGL correctly layers objects
        gl.glEnable(GL.GL_DEPTH_TEST);

        // OpenGL won't draw backward facing triangles ("back faces")
        gl.glEnable(GL.GL_CULL_FACE);

        // turn lighting on (does not create a light)
        gl.glEnable(GL.GL_LIGHTING);

        // Force normals to length 1
        gl.glEnable(GL.GL_NORMALIZE);

        // turn on textures
    	gl.glEnable(GL.GL_TEXTURE_2D);

        // blending (for transparency)
        gl.glEnable(GL.GL_BLEND);
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);

        // set the background color
        gl.glClearColor(.50f, .60f, .7f, 1);

        // Draw specular highlghts on top of textures
        gl.glLightModeli(GL.GL_LIGHT_MODEL_COLOR_CONTROL, GL.GL_SEPARATE_SPECULAR_COLOR );

        // Create a light
        setLight( GL.GL_LIGHT1, lightDiffuse, lightAmbient, lightSpecular, lightPosition );

        // Set the Projection Matrix (controls perspective)
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(
            45.0f,        // Field Of View
            (float)getWidth() / (float)getHeight(), // aspect ratio
            1f,           // near Z clipping plane
            1000.0f);     // far Z clipping plane

        //----------------------------------------------------------------
        // Load the model (uncomment one row below)
        //----------------------------------------------------------------
        //model = new JOModel("models/JetFire/JetFire.obj");       cameraY=2;    cameraZ=3;
        //model = new JOModel("models/escocity_patrol_cop_obj/escocity_patrol_cop_.obj");   cameraY=7;  cameraZ = 30;
        //model = new JOModel("models/porsche.obj");     cameraY=10;    cameraZ = 100;
        model = new JOModel("models/f-16.obj");     cameraY=10;    cameraZ = 13;
        //model = new JOModel("models/soccerball.obj");     cameraY=5;    cameraZ = 15;

        if (model.mesh.materialLibeName != null && model.mesh.materialLibeName.equals("porsche.mtl")) {
        	// the porsche needs smoothing
        	model.mesh.setSmoothingAngle(60);
        	model.mesh.regenerateNormals();
        }

        // convert model to a displaylist to optimize
        model.makeDisplayList();
    }

    /**
     * Render the scene.
     */
    public void draw() {
    	rotation += .3f;

		// Clear screen and depth buffer
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

        // Select The Modelview Matrix (controls model orientation)
        gl.glMatrixMode(GL.GL_MODELVIEW);

        // reset the coordinate system to center of screen
        gl.glLoadIdentity();

        // Where is the 'eye'
        glu.gluLookAt(
        	0f, cameraY, cameraZ,   // eye position
            0f, 0f, 0f,    // target to look at
            0f, 1f, 0f);   // which way is up

		// rotate (based on mouse drag)
		gl.glRotatef(rotateModelY, 0, 1, 0); // around vertical axis
		gl.glRotatef(rotateModelX, 1, 0, 0); // around horizontal axis

		if (mouseButtonDown(1)) msg("DOWN");

		model.render();

		renderPickedTriangle(model.mesh);
		
		//--------------------------------------------------------------------
		// Draw a sphere where the mouse touches the model.
		//
		// We got the mouse screen X,Y and Z depth from mouseMoved().  Now
		// we can convert the screen coords into world coords and draw a sphere
		// in the world at the mouse position.
		//
		// We'll call JOApp.getWorldCoordsAtScreen() which calls glu.gluUnproject()
		// to "un-project" the point.  Just as OpenGL uses the modelview matrix,
		// projection matrix and viewport to render a point onto the screen, it
		// can also use these matrices to reverse the process, to go from a screen
		// position back into the world.
		//--------------------------------------------------------------------
		float[] worldPoint = getWorldCoordsAtScreen((int)mousePos.x, (int)mousePos.y, mousePos.z);

		gl.glDepthMask(false);
		gl.glPushMatrix();
		{
			gl.glTranslatef(worldPoint[0],worldPoint[1],worldPoint[2]);
			gl.glScalef(.1f,.1f,.1f);
			gl.glBindTexture(GL.GL_TEXTURE_2D,0);
			renderSphere();
		}
		gl.glPopMatrix();
		gl.glDepthMask(true);

    }

    public void mouseDown(int x, int y) {
    	mousePrevPos = new JOVector(x,y,0);
    }

    public void mouseUp(int x, int y) {
    }

	/**
	 * In order to draw a sphere where the mouse touches the model, we need to
	 * convert the mouse screen coordinates into world coordinates.
	 *
	 * We can get the screen X and Y from the current mouse position. We get
	 * the screen Z value by reading the depth buffer at the X,Y screen position.
	 * Remember that OpenGL keeps track of how close each object is to the eye.
	 * That Zdepth value is what's stored in the depth buffer, and each pixel has
	 * a corresponding depth value that ranges from 0-1. This is our screen Z value.
	 *
	 * We'll call JOApp.getZDepth(X,Y) which calls gl.glReadPixels(X,Y,GL_DEPTH_COMPONENT)
	 * to return the depth buffer value at the given screen X,Y.
	 */
    public void mouseMoved(int x, int y) {
    	float z = getZDepth(x,y);
    	msg("screen coords=" + x + "," + y + "," + z);
    	mousePos = new JOVector(x,y,z);
    }

    public void mouseDragged(int x, int y) {
    	msg("DRAG screen coords=" + x + "," + y);
    	int mouseDragDistanceX = (int)(x - mousePrevPos.x);
    	int mouseDragDistanceY = (int)(y - mousePrevPos.y);
    	rotateModelX += ((float)mouseDragDistanceY/(float)getHeight()) * 360f;
    	rotateModelY += ((float)mouseDragDistanceX/(float)getWidth()) * 360f;
    	mousePrevPos.x = x;
    	mousePrevPos.y = y;
    }

    //----------------------------------------------------------------
    // Pick triangle
    //----------------------------------------------------------------

    /**
     * Return the triangle at the cursor x,y position, or null if
     * no triangle is at the cursor position.  Requires that projectVerts()
     * has been run to populate the vertex screen positions.  If more
     * than one triangle is at the cursor, then returns the one
     * closest to the viewpoint.
     *
     * @param x     cursor x  (screen coordinates)
     * @param y     cursor y  (screen coordinates)
     * @param triangles    array of triangles from a GL_Mesh
     * @return      the triangle at the cursor x,y
     * @see projectVerts()
     */
	public JOTriangle getTriangle(float x, float y, JOTriangle[] triangles) {
		ArrayList candidates = new ArrayList();
		JOTriangle t, closestT = null;
		float minZ = 100000;
		// find all triangles that contain cursor point (ignore Z depth)
		for (int i=0; i < triangles.length; i++) {
			t = triangles[i];
			// See if the screen x,y is inside the triangle on screen.  The triangle.p1.posS
			// is the screen XYZ of the triangle point, populated by JOMesh.projectVerts().
			if (JOMesh.pointInTriangle(x, y,
					t.p1.posS.x, t.p1.posS.y,
					t.p2.posS.x, t.p2.posS.y,
					t.p3.posS.x, t.p3.posS.y))
			{
				candidates.add(t);
			}
		}
		// Of the found triangles, which is closest to viewpoint
		for (int j=0; j < candidates.size(); j++) {
			if (((JOTriangle)(candidates.get(j))).Zdepth < minZ ) {
				closestT = (JOTriangle)(candidates.get(j));
				minZ = closestT.Zdepth;
			}
		}
		return closestT;
	}

	/**
	 * Highlight the triangle that's under the cursor.  To do this we first
	 * project all the triangles into screen space, then compare the mouse
	 * screen position to the triangles screen positions to see if the mouse
	 * is inside a triangle.
	 */
	public void renderPickedTriangle(JOMesh mesh) {
		JOTriangle pickedTri=null;
		// "project" the mesh vertices into the screen space.  This doesn't
		// actually draw anything, it just creates the screen-space xyz
		// coordinate for each vertex.  getTriangles() will use these values.
		mesh.projectVerts(mesh, getModelviewMatrix(), getProjectionMatrix(), getViewport());

		// getTriangle() uses the 'projected' coordinates (created by
		// projectVerts() above) to find the triangle under the cursor.
		pickedTri = mesh.getTriangle(cursorX, cursorY);

		// Draw the triangle
		if (pickedTri != null) {
			gl.glDisable(GL.GL_LIGHTING);
			gl.glDisable(GL.GL_DEPTH_TEST); // draw on top of the model so it's visible
			gl.glColor3f(1,0,0);
			gl.glBindTexture(GL.GL_TEXTURE_2D,0);
			gl.glBegin(GL.GL_TRIANGLES);
			{
				gl.glVertex3f(pickedTri.p1.pos.x, pickedTri.p1.pos.y, pickedTri.p1.pos.z);
				gl.glVertex3f(pickedTri.p2.pos.x, pickedTri.p2.pos.y, pickedTri.p2.pos.z);
				gl.glVertex3f(pickedTri.p3.pos.x, pickedTri.p3.pos.y, pickedTri.p3.pos.z);
			}
			gl.glEnd();
			// Draw a dot at the first vertex
			gl.glColor3f(0,1,0);
			gl.glPointSize(4);
			gl.glBegin(GL.GL_POINTS);
			{
				gl.glVertex3f(pickedTri.p1.pos.x, pickedTri.p1.pos.y, pickedTri.p1.pos.z);
			}
			gl.glEnd();
			gl.glEnable(GL.GL_DEPTH_TEST);
			gl.glEnable(GL.GL_LIGHTING);
		}
	}
}
