package classwork;

import java.nio.*;
import java.awt.event.KeyEvent;
import javax.media.opengl.*;
import jocode.*;
import jomodel.*;

/**
 * A basic shadow demo: render a model and cast an opaque shadow on the floor.
 * Uses a matrix to project the object onto a plane.
 *
 * Hit the L key to toggle between the eye viewpoint and the light viewpoint.
 * Hit the Z key to zoom in/out.
 * HIT the P key to pause/unpause the rotation.
 */
public class GLART_9_Shadow extends JOApp {
    JOModel obj;
    int texture;
    float rotateModelY = 0;
    boolean showLightViewpoint = false;
    boolean paused = false;
    boolean zoomed = true;

    // will hold the shadow matrix (see setup() and makeShadowMatrix())
    FloatBuffer fShadowMatrix;

	// light position: if last value is 0, then this describes light direction.
	float lightPosition[] = { -3f, 10f, 3, 1f };

    // equation for the plane the shadow will fall on (the normal for the plane, 0,1,0)
    float[] rightWallPlane = new float[] {-1f,0f,0f,2f};
    float[] leftWallPlane = new float[] {1f,0f,0f,2f};
    float[] floorPlane = new float[] {0f,1f,0f,0f};
    float[] plane = floorPlane;


    /**
     * create and run the application.
     */
    public static void main(String args[]) {
    	GLART_9_Shadow app = new GLART_9_Shadow();
    	windowTitle = "Shadow Demo";
    	displayWidth = 800;
    	displayHeight = 600;
        app.run();
    }

    /**
     * Initialize the environment
     */
    public void setup() {
        // load model
        obj = new JOModel("models/cow.obj");
        obj.regenerateNormals();

        // prepare a  texture
        texture = makeTexture("images/Flag_of_the_United_States.png");

        // color of overall scene lighting
        float ambient[]       = { .1f, .1f, .1f, 1f };

        // color of light source
        float lightDiffuse[]  = { 1f, 1f, .9f, 1f }; // direct light
        float lightSpecular[] = { 1f, 1f, .9f, 1f }; // highlight
        float lightAmbient[]  = { .3f, .3f, .4f, 1f }; // scattered light

        // setup material
        JOMaterial material = new JOMaterial();
        material = new JOMaterial();
        material.setDiffuse(new float[] {1f,1f,1f,1f});
        material.setAmbient(new float[] {.8f,.8f,.8f,1f});
        material.apply();

        // Select the Projection Matrix (controls perspective)
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();

        // Define perspective
        glu.gluPerspective(
            30.0f,        // Field Of View
            getWidth()/getHeight(), // aspect ratio
            0.01f,         // near Z clipping plane
            1000.0f);      // far Z clipping plane

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

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

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

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

        // turn lighting on
        gl.glEnable(GL.GL_LIGHTING);

        // set the background color
        gl.glClearColor(.5f, .5f, .8f, 1);

        // overall scene lighting
        setAmbientLight(ambient);

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

        //////////////////////////////////////////////////////////////
        // For Shadows: define a matrix based on light position and shadow surface
        float[] fShadowMatrixArray = new float[16];
        makeShadowMatrix(fShadowMatrixArray, lightPosition, plane);
        
        System.out.println();
        for (int i=0; i < 16; i++) {
        	System.out.print(fShadowMatrixArray[i] + ",");
        }
        System.out.println();

        // convert the float array to a FloatBuffer
        fShadowMatrix = allocFloats(fShadowMatrixArray);

        ///////////////////////////////////////////////////////////////
    }

    /**
     * Render the scene.
     */
    public void draw() {
    	if (!paused) {
    		rotateModelY += .2;
    	}

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

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

        // Place the viewpoint at the light position or eye position
        if (showLightViewpoint) {
			glu.gluLookAt(  lightPosition[0], lightPosition[1], lightPosition[2],
							0, 0, 0,
							0f, 1.8f, 0f);
		}
		else {
			glu.gluLookAt(  0, 5, (zoomed? 15 : 25),     // camera position
							0, (zoomed? 2 : 5), 0,
							0f, 1f, 0f);
		}

        // draw a sphere at the light position
    	gl.glDisable(GL.GL_LIGHTING);
    	gl.glBindTexture(GL.GL_TEXTURE_2D,0);
    	gl.glColor4f(1,1,1,1);
        gl.glPushMatrix();
        {
        	gl.glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]);
        	gl.glScalef(.2f,.2f,.2f);
        	renderSphere();
        }
        gl.glPopMatrix();
    	gl.glEnable(GL.GL_LIGHTING);
        
		// draw shadow cast on floor plane
		drawShadow();

        // draw the object normally
        drawObject();
    }

    /**
     * draw the object that will be shadowed
     */
	public void drawObject() {
		gl.glPushMatrix();
		{
			gl.glTranslatef(0,1.8f,0);            // move up a little (off the floor)
	        gl.glScalef(.5f,.5f,.5f);             // scale model down a little
			gl.glRotatef(rotateModelY, 0, 1, 0);  // rotate around vertical axis
			obj.renderTextured(texture);
		}
		gl.glPopMatrix();
	}

	/**
	 * Draw the shadowed object projected onto a plane from the lights point of view.
	 * This will create the right shape for a shadow, but the object will still be
	 * fully textured.  To make the object look like a shadow we turn off lighting
	 * and texture and draw with a flat gray.  Turn off the depth test so the shadow
	 * will be drawn on top of the floor (it won't be hidden by the floor).
	 */
    public void drawShadow() {
		// no texture
		gl.glDisable(GL.GL_TEXTURE_2D);
		// no light
		gl.glDisable(GL.GL_LIGHTING);
		// no depth testing
		gl.glDisable(GL.GL_DEPTH_TEST);
		// dark gray color
		gl.glColor4f(.3f, .3f, .35f, 1f);

        // draw the shadow onto floor
        gl.glPushMatrix();
        {
            // multiply the current modelview matrix with the shadow matrix
            gl.glMultMatrixf(fShadowMatrix);

            // draw the object to be shadowed
            // the shadow matrix above will flatten the object against the plane
            drawObject();
        }
        gl.glPopMatrix();

		// re-enable settings
		gl.glEnable(GL.GL_TEXTURE_2D);
		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glEnable(GL.GL_LIGHTING);
    }

    /**
     *  Make a matrix that will transform the geometry as if it is projected onto
     *  the given plane, from the viewpoint of the given light position. Any
     *  object drawn while this matrix is active will be squashed flat against
     *  a wall or floor plane.
     */
    void makeShadowMatrix(float[] fDestMat, float[] fLightPos, float[] fPlane)
    {
        float dot;

        // dot product of plane and light position
        dot = fPlane[0] * fLightPos[0] +
              fPlane[1] * fLightPos[1] +
              fPlane[2] * fLightPos[2] +
              fPlane[3] * fLightPos[3];

        // first column
        fDestMat[0]  = dot - fLightPos[0] * fPlane[0];
        fDestMat[4]  = 0.0f - fLightPos[0] * fPlane[1];
        fDestMat[8]  = 0.0f - fLightPos[0] * fPlane[2];
        fDestMat[12] = 0.0f - fLightPos[0] * fPlane[3];

        // second column
        fDestMat[1]  = 0.0f - fLightPos[1] * fPlane[0];
        fDestMat[5]  = dot - fLightPos[1] * fPlane[1];
        fDestMat[9]  = 0.0f - fLightPos[1] * fPlane[2];
        fDestMat[13] = 0.0f - fLightPos[1] * fPlane[3];

        // third column
        fDestMat[2]  = 0.0f - fLightPos[2] * fPlane[0];
        fDestMat[6]  = 0.0f - fLightPos[2] * fPlane[1];
        fDestMat[10] = dot - fLightPos[2] * fPlane[2];
        fDestMat[14] = 0.0f - fLightPos[2] * fPlane[3];

        // fourth column
        fDestMat[3]  = 0.0f - fLightPos[3] * fPlane[0];
        fDestMat[7]  = 0.0f - fLightPos[3] * fPlane[1];
        fDestMat[11] = 0.0f - fLightPos[3] * fPlane[2];
        fDestMat[15] = dot - fLightPos[3] * fPlane[3];
    }

	/**
	 *
	 */
	public void keyUp(int keycode) {
		if(keycode == KeyEvent.VK_L) {
			showLightViewpoint = !showLightViewpoint;
		}
		else if(keycode == KeyEvent.VK_Z) {
			zoomed = !zoomed;
		}
		else if(keycode == KeyEvent.VK_P) {
			paused = !paused;
		}
	}
}
