package classwork;

import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import java.awt.event.*;

import jocode.*;
import jomodel.*;

/**
 * GLART_5_transparent_material_problem
 *
 * Draw a row of transparent textured spheres.  Use left,right arrow keys to rotate scene.
 *
 * Set the alpha value in the diffuse material color to make a material transparent.
 *  
 * This demo shows the front-to-back drawing problem that transparent
 * shapes have.  The depth test throws out any objects that are overlapped
 * by objects closer to the eye.  This is a problem when drawing transparent
 * surfaces because we want to see the background objects through the 
 * foreground objects, but they aren't rendered.
 * 
 * If you disable the depth test, then all objects are drawn, but as you
 * rotate the sccene the objects are clearly not drawn in the right order.
 * They are drawn in the order they appear in the code, not in the spatial
 * order they appear in the scene.
 * 
 * We need to draw background objects first, then foreground objects, so 
 * that we can see the background objects through the transparent foreground 
 * objects.  See GLART_5_transparent_material_solution.java for a method
 * that solves the problem of rendering transparent objects.
 * 
 * @see GLART_5_transparent_material_solution
 */
public class GLART_5_transparent_material_problem extends JOApp {
    float rotation = 0;

    // Material objects will hold surface color values
    JOMaterial clearMtl = new JOMaterial();
    JOMaterial opaqueMtl = new JOMaterial();

    // texture handle (a number that refers to an allocated texture)
    int marbleTextureHandle = 0;

    /**
     * Main function just creates and runs the application.
     */
    public static void main(String args[]) {
    	GLART_5_transparent_material_problem app = new GLART_5_transparent_material_problem();
    	displayWidth = 800;
    	displayHeight = 600;
        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
        // specular is the hightlight color
        // ambient is the color of scattered light from this source
        // position is where the light is, or it's direction
        float lightDiffuse[]  = { .9f, .9f, .8f, 1f }; // direct light
        float lightSpecular[] = { .9f, .9f, .8f, 1f }; // highlight
        float lightAmbient[]  = { .1f, .1f, .0f, 1f }; // scattered light
        float lightPosition[] = { -4f, 4f, 6, 1f };    // Last value is 1: this is light position.
        setLight( GL.GL_LIGHT1, lightDiffuse, lightAmbient, lightSpecular, lightPosition );

        // overall scene lighting
        setAmbientLight( new float[] { .7f, .7f, .7f, 1f } );

        // make the transparent material
        clearMtl.setDiffuse(  new float[] { .9f, .95f, .9f, .5f });    // white/green  .5f in the fourth position is ALPHA value
        clearMtl.setAmbient(  new float[] { .7f, .75f, .7f, 1f });     // light gray greenish
        clearMtl.setSpecular( new float[] { .9f, .9f, .9f, 1f });      // almost white: very reflective
        clearMtl.setShininess(127f);   // 0=no shine,  127=max shine

        // make the opaque material
        opaqueMtl.setDiffuse(new float[] { 1f, .3f, .3f, 1f }); // reddish
        opaqueMtl.setAmbient(new float[] { .8f, .2f, .2f, 1f });   // reddish

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

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

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

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

        // Create a texture from the image
        marbleTextureHandle = makeTexture("images/marble.jpg");
    }

    /**
     * Render the scene.
     */
    public void draw() {
		// 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.glLoadIdentity();

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

        // Can try to disable the depth test so that background objects still render, but this causes other problems
        //gl.glDisable(GL.GL_DEPTH_TEST);

        // rotate scene
        gl.glRotatef(rotation, 0,1,0);

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

        // activate opaque material and draw sphere
        opaqueMtl.apply();
		drawSphereAt(0f,0f,-4f);

        // activate transparent material, draw 3 transparent spheres
        clearMtl.apply();
		drawSphereAt(0f, 0f, -2f);
		drawSphereAt(0f, 0f, 0f);
		drawSphereAt(0f, 0f, 2f);

        // draw opaque sphere at other end of line
        opaqueMtl.apply();
		drawSphereAt(0f,0f,4f);
    }

	/**
	 * Translate to the given world position and draw a sphere.
	 * Return the Z depth of that position.
	 */
    public void drawSphereAt(float x, float y, float z) {
		gl.glPushMatrix();
        {
			// shift to sphere position
        	gl.glTranslatef(x,y,z);
        	// draw double-sided sphere
        	draw2SidedSphere();
        }
        gl.glPopMatrix();
    }

	/**
	 * Draw a sphere with another slightly smaller sphere inside.  The inner sphere faces
	 * inside, creating the effect that the sphere is a glass shell and can reflect
	 * light both on the outer surface and the inner surface.
	 */
    public void draw2SidedSphere() {
        gl.glPushMatrix();
        {
        	// draw inside-facing surface a little smaller
        	gl.glScalef(.99f,.99f,.99f);
        	renderSphereInside();
        }
        gl.glPopMatrix();
        gl.glPushMatrix();
        {
        	// draw outside-facing sphere a little bigger
        	gl.glScalef(1.01f, 1.01f, 1.01f);
        	renderSphere();
        }
        gl.glPopMatrix();
    }

    /**
     * call glu functions to create geometry for a 1 unit sphere, with normals.
     * NOTE: this is not optimized at all.
     */
	public static void renderSphereInside() {
        GLUquadric s = glu.gluNewQuadric();
        glu.gluQuadricOrientation(s, GLU.GLU_INSIDE);  // normals point inwards
        glu.gluQuadricTexture(s, true);
	    glu.gluSphere(s, 1, 48, 48);  // run GL commands to draw sphere
	}

    public static void renderSphere() {
        GLUquadric s = glu.gluNewQuadric();
        glu.gluQuadricOrientation(s, GLU.GLU_OUTSIDE);  // normals point outwards
        glu.gluQuadricTexture(s, true);
	    glu.gluSphere(s, 1, 48, 48);  // run GL commands to draw sphere
    }

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