package classwork;

import javax.media.opengl.*;

import java.awt.event.KeyEvent;
import jocode.*;
import jomodel.*;

/**
 * GLART_4_light_triangles_smooth.java
 *
 * Demonstrate smoothing by averaging normals across shared triangles.
 * Shines a light on a three triangles arranged to create a curved surface.
 *
 * Assigns normals to each vertex.  Vertices that have more than one
 * neighboring triangle will get the average normal of the two or three
 * neighbor triangles.
 *
 * Use arrow keys to rotate model.
 */
public class GLART_4_light_triangles_smooth extends JOApp {
    float rotation = 0;
    // Color value for the overall scene lighting
    float sceneAmbientLight[]  = { 0.18f, 0.1f, .15f, 1f };
    // Color values for light
    float lightDiffuse[]  = { 1f, 1f, .8f, 1f };
    float lightSpecular[] = { 1f, 1f, .8f, 1f };
    float lightAmbient[]  = { 0.5f, 0.5f, .4f, 1f };
    // Light position: if last value is 0, then this describes light direction.  If 1, then light position.
    float lightPosition[] = { -4f, 3f, 3f, 1f };

    /**
     * Main function just creates and runs the application.
     */
    public static void main(String args[]) {
    	GLART_4_light_triangles_smooth app = new GLART_4_light_triangles_smooth();
        app.run();
    }

    /**
     * Initialize OpenGL
     *
     */
    public void setup() {
        // Select the Projection Matrix (controls perspective)
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();    // Reset The Projection Matrix

        // Define perspective
        glu.gluPerspective(
            45.0f,        // Field Of View
            (float)getWidth() / (float)getHeight(), // aspect ratio
            0.1f,         // near Z clipping plane
            100.0f);      // far Z clipping plane

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

        // 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);

        // Create a light
        // diffuse is the color of direct light from this light source
        // ambient is the color of scattered light from this source
        // position is where the light is, or it's direction
        setLight( GL.GL_LIGHT1, lightDiffuse, lightAmbient, lightSpecular, lightPosition );

        // overall scene lighting
        setAmbientLight(sceneAmbientLight);

        // How to shade faces:
        //     GL_FLAT -- each face has same lighting (looks faceted)
        //     GL_SMOOTH -- lighting is blended across face
        gl.glShadeModel(GL.GL_SMOOTH);
    }

    /**
     * rotate scene if right or left arrow keys are pressed
     */
    public void keyDown(int keycode) {
        if(keycode == KeyEvent.VK_RIGHT) {
            rotation -= .5f;
        }
        else if(keycode == KeyEvent.VK_LEFT) {
        	rotation += .5f;
        }
    }

    /**
     * Render the scene.
     */
    public void draw() {
    	// Vertices for three triangles (three verts each)
    	JOVector[] tri1 = new JOVector[3];
    	JOVector[] tri2 = new JOVector[3];
    	JOVector[] tri3 = new JOVector[3];

    	// Normals for three triangles (three normals each)
    	JOVector[] norms1 = new JOVector[3];
    	JOVector[] norms2 = new JOVector[3];
    	JOVector[] norms3 = new JOVector[3];

    	// triangle vertices (center)
        tri1[0] = new JOVector( 0f, .75f, 0f);    // top
        tri1[1] = new JOVector(-1f,  -1f, 0f);    // lower left
        tri1[2] = new JOVector( 1f,  -1f, 0f);    // lower right
    	// triangle vertices (left)
        tri2[0] = new JOVector(-1f,  -1f, 0f);    // bottom
        tri2[1] = new JOVector( 0f, .75f, 0f);    // top right
        tri2[2] = new JOVector(-2f, .75f, -.8f);  // top left
    	// triangle vertices (right)
        tri3[0] = new JOVector( 1f,  -1f, 0f);    // bottom
        tri3[1] = new JOVector( 2f, .75f, -.8f);  // top right
        tri3[2] = new JOVector( 0f, .75f, 0f);    // top left

		// calculate normal of each triangle
		JOVector normal1 = JOVector.getNormal(tri1[0], tri1[1], tri1[2]);
		JOVector normal2 = JOVector.getNormal(tri2[0], tri2[1], tri2[2]);
		JOVector normal3 = JOVector.getNormal(tri3[0], tri3[1], tri3[2]);

		// average normals of center and left tri
		JOVector normAvg12 = JOVector.add(normal1,normal2);
		normAvg12.div(2).normalize();
		// average normals of center and right tri
		JOVector normAvg13 = JOVector.add(normal1,normal3);
		normAvg13.div(2).normalize();
		// average normals of all three
		JOVector normAvg123 = JOVector.add(normal1,normal2).add(normal3);
		normAvg123.div(3).normalize();

		boolean useFaceNormals = true;
		
		if (useFaceNormals) {
			// use one face normal for all verts in the triangle
			// each triangle will look flat with sharp edges
			
			// triangle vertices center
			norms1[0] = normal1;   // top (shared by all)
			norms1[1] = normal1;    // lower left (shared)
			norms1[2] = normal1;    // lower right (shared)
			// triangle vertices left
			norms2[0] = normal2;    // bottom (shared)
			norms2[1] = normal2;   // top right (shared by all)
			norms2[2] = normal2;      // top left
			// triangle vertices right
			norms3[0] = normal3;    // bottom (shared)
			norms3[1] = normal3;      // top right
			norms3[2] = normal3;   // top left (shared by all)
		}
		else {
			// use averaged normals: at each vertex calculate the average normal 
			// of all the verts that meet at that point. This smooths the edges
			// of the triangles.
			
			// triangle vertices center
			norms1[0] = normAvg123;   // top (shared by all)
			norms1[1] = normAvg12;    // lower left (shared)
			norms1[2] = normAvg13;    // lower right (shared)
			// triangle vertices left
			norms2[0] = normAvg12;    // bottom (shared)
			norms2[1] = normAvg123;   // top right (shared by all)
			norms2[2] = normal2;      // top left
			// triangle vertices right
			norms3[0] = normAvg13;    // bottom (shared)
			norms3[1] = normal3;      // top right
			norms3[2] = normAvg123;   // top left (shared by all)
		}

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

        // Select The Modelview Matrix (controls model orientation)
        // and reset the coordinate system to center of screen
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();

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

        // lines will be 2 pixels wide
        gl.glLineWidth(2);

        // Draw the triangles and normals
        gl.glPushMatrix();
        {
        	// rotate scene around Y axis
        	gl.glRotatef(rotation, 0,1,0);
        	
    		gl.glColor4f(1, 0, 0, 1);
        	drawTriangle(tri1,norms1);
        	
    		gl.glColor4f(0, 1, 0, 1);
        	drawTriangle(tri2,norms2);
        	
    		gl.glColor4f(0, 0, 1, 1);
        	drawTriangle(tri3,norms3);
        }
        gl.glPopMatrix();
    }


    public void drawTriangle(JOVector[] verts, JOVector[] norms) {
	    // draw a lit triangle
		gl.glBegin(GL.GL_TRIANGLES);
		{
			// normal and vertex 1
	        gl.glNormal3f(norms[0].x, norms[0].y, norms[0].z);
			gl.glVertex3f(verts[0].x, verts[0].y, verts[0].z);
			// normal and vertex 2
	        gl.glNormal3f(norms[1].x, norms[1].y, norms[1].z);
			gl.glVertex3f(verts[1].x, verts[1].y, verts[1].z);
			// normal and vertex 3
	        gl.glNormal3f(norms[2].x, norms[2].y, norms[2].z);
			gl.glVertex3f(verts[2].x, verts[2].y, verts[2].z);
		}
		gl.glEnd();
		// draw normals for each vertex
		drawNormal(verts[0],norms[0]);
		drawNormal(verts[1],norms[1]);
		drawNormal(verts[2],norms[2]);
    }


    /**
     * Draw a red line to indicate the normal.  Disable lighting
     * so we can use glColor().  ReEnable lighting before returning.
     */
    public void drawNormal(JOVector point, JOVector normal) {
	    // draw a red line to indicate where the normal is
		gl.glDisable(GL.GL_LIGHTING); // turn off so we can use glColor()
		gl.glBegin(GL.GL_LINES);
		{
			gl.glVertex3f(point.x, point.y, point.z);
			gl.glVertex3f(point.x+normal.x, point.y+normal.y,	point.z+normal.z);
		}
		gl.glEnd();
		gl.glEnable(GL.GL_LIGHTING);
    }
}
