package demo;

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

/**
 * Render a scene with two cameras, one still and one moving.  Hit SPACE to
 * toggle viewpoints between the two cameras.  Uses the JOCamera class to
 * hold camera position and orientation, and the JOCam class to move the
 * current camera in response to arrow key events.
 * <P>
 * @see JOCamera.java
 * @see JOCam.java
 * <P>
 * napier at potatoland dot org
 */
public class DemoCamera extends JOApp {
    // Handle for texture
    int sphereTextureHandle = 0;
    int groundTextureHandle = 0;
    // Light position: if last value is 0, then this describes light direction.  If 1, then light position.
    float lightPosition[]= { -2f, 2f, 2f, 0f };
    // Camera position
    float[] cameraPos = {0f,3f,20f};

    // two cameras and a cam to move them around scene
    JOCamera camera1 = new JOCamera();
    JOCamera camera2 = new JOCamera();
    JOCam cam = new JOCam(camera1);

    // vectors used to orient airplane motion
    JOVector UP = new JOVector(0,1,0);
    JOVector ORIGIN = new JOVector(0,0,0);

    // for earth rotation
    float degrees = 0;

    // model of airplane and sphere displaylist for earth
    JOModel airplane;
    int earth;

    // shadow handler will draw a shadow on floor plane
    JOShadowOnPlane airplaneShadow;

    public JOVector airplanePos;
    
	FloatBuffer bbmatrix = allocFloats(16);

    /**
     * Initialize application and run main loop.
     */
    public static void main(String args[]) {
    	DemoCamera demo = new DemoCamera();
        VSyncEnabled = true;
        fullScreen = false;
        displayWidth = 800;
        displayHeight = 600;
        demo.run();  // will call init(), render(), mouse functions
    }
    
    /**
     * Initialise the environment.
     * Called automatically on startup.
     */
    @Override
    public void init(GLAutoDrawable _glDrawable) {
        super.init(_glDrawable);
        
        // init input callbacks
        _glDrawable.addKeyListener(cam);
    }

    /**
     * Initialize the scene.  Called by JOApp.run()
     */
    @Override
    public void setup() {
        // setup and enable perspective
        setPerspective();

        // Create a light (diffuse light, ambient light, position)
        setLight(GL.GL_LIGHT1,
        		new float[] { 1f, 1f, 1f, 1f },
        		new float[] { 0.5f, 0.5f, .53f, 1f },
        		new float[] { 1f, 1f, 1f, 1f },
        		lightPosition );

        // Create a directional light (light green, to simulate reflection off grass)
        setLight(GL.GL_LIGHT2,
        		new float[] { 0.15f, 0.4f, 0.1f, 1.0f },  // diffuse color
        		new float[] { 0.0f, 0.0f, 0.0f, 1.0f },   // ambient
        		new float[] { 0.0f, 0.0f, 0.0f, 1.0f },   // specular
        		new float[] { 0.0f, -10f, 0.0f, 0f } );   // direction (pointing up)

        // enable lighting and texture rendering
        gl.glEnable(GL.GL_LIGHTING);
        gl.glEnable(GL.GL_TEXTURE_2D);

        // Enable alpha transparency (so text will have transparent background)
        gl.glEnable(GL.GL_BLEND);
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);

        // Create texture for sphere
        sphereTextureHandle = makeTexture("images/earth.gif");

        // Create texture for ground plane
        groundTextureHandle = makeTexture("images/grass_1_512.jpg",true,true);

        // set camera 1 position
        camera1.setCamera(0,4,15, 0,-.3f,-1, 0,1,0);

        // load the airplane model and make it a display list
        airplane = new JOModel("models/JetFire/JetFire.obj");
        airplane.mesh.regenerateNormals();
        airplane.makeDisplayList();

        // make a sphere display list
        earth = beginDisplayList(); {
        	renderSphere();
        }
        endDisplayList();

        // make a shadow handler
        // params:
        //		the light position,
        //		the plane the shadow will fall on,
        //		the color of the shadow,
        // 		this application,
        // 		the function that draws all objects that cast shadows
        airplaneShadow = new JOShadowOnPlane(lightPosition, new float[] {0f,1f,0f,3f}, null, this, method(this,"drawObjects"));
    }

    /**
     * set the field of view and view depth.
     */
    public void setPerspective() {
    	setPerspective(50f, .1f, 500f);
    }

    /**
     * Render one frame.  Called by JOApp.run().
     */
    @Override
    public void draw() {
    	degrees += 30f * getSecondsPerFrame();

        // place airplane in orbit around ball, and place camera slightly above airplane
    	airplanePos = JOVector.rotationVector(degrees).mult(8);
    	camera2.moveTo(airplanePos.x, airplanePos.y+.53f, airplanePos.z);

    	// align airplane and camera2 (perpendicular to the radius and up vector)
        JOVector airplaneDirection = JOVector.crossProduct(UP,airplanePos);
    	camera2.viewDir( airplaneDirection );  // point camera in direction of airplane motion

        // user keystrokes adjust camera position
        cam.handleNavKeys((float)getSecondsPerFrame());

        // combine user camera motion with current camera position (so user can look around while on the airplane)
        float apRot = camera2.rotatedY;  // how much is camera rotated?
    	camera2.rotatedY = 0;            // zero out rotation
    	camera2.rotateY(apRot);          // set rotation again (camera will add rotation to its current view direction)

        // clear depth buffer and color
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

        // select model view for subsequent transforms
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();

        // do gluLookAt() with camera position, direction, orientation
        cam.render();

        // draw the ground plane
        gl.glPushMatrix();
        {
            gl.glTranslatef(0f, -3f, 0f); // down a bit
            gl.glScalef(15f, .01f, 15f);
            gl.glBindTexture(GL.GL_TEXTURE_2D, groundTextureHandle);
            renderCube();
        }
        gl.glPopMatrix();

        // invokes the drawObjects() method to create shadows for objects in the scene
        airplaneShadow.drawShadow();

        // draw sphere at center (rotate 10 degrees per second)
        rotation += 10f * getSecondsPerFrame();

        // draw the scene (after we draw the shadows, so everything layers correctly)
        drawObjects();

        // Place the light.  Light will move with the rest of the scene
        setLightPosition(GL.GL_LIGHT1, lightPosition);

		// render some text using texture-mapped font
		print( 30, viewportH- 45, "Use arrow keys to navigate:");
        print( 30, viewportH- 80, "Left-Right arrows rotate camera", 1);
        print( 30, viewportH-100, "Up-Down arrows move camera forward and back", 1);
        print( 30, viewportH-120, "PageUp-PageDown move vertically", 1);
        print( 30, viewportH-140, "SPACE key switches cameras", 1);
    }

    public void drawObjects() {
        // draw the airplane
        gl.glPushMatrix();
        {
        	// place plane at orbit point, and orient it toward origin
        	billboardPoint(airplanePos, ORIGIN, UP);
        	// turn plane toward direction of motion
            gl.glRotatef(-90, 0, 1, 0);
            // make it big
            gl.glScalef(4f, 4f, 4f);
        	airplane.render();
        	// reset material, since model.render() will alter current material settings
            setMaterial( new float[] {.8f, .8f, .7f, 1f}, .4f);
        }
        gl.glPopMatrix();

    	// draw the earth
        gl.glPushMatrix();
        {
            gl.glRotatef(rotation, 0, 1, 0);  // rotate around Y axis
            gl.glScalef(2f, 2f, 2f);          // scale up
            gl.glBindTexture(GL.GL_TEXTURE_2D, sphereTextureHandle);
            callDisplayList(earth);
        }
        gl.glPopMatrix();
    }

	/**
	 * Given position of object and target, create matrix to
	 * orient object so it faces target.
	 */
	public void billboardPoint(JOVector bbPos, JOVector targetPos, JOVector targetUp) {
		// direction the billboard will be facing (looking):
		JOVector look = JOVector.sub(targetPos,bbPos).normalize();
		
		// billboard Right vector is perpendicular to Look and Up (the targets Up vector)
		JOVector right = JOVector.crossProduct(targetUp,look).normalize();
		
		// billboard Up vector is perpendicular to Look and Right
		JOVector up = JOVector.crossProduct(look,right).normalize();
		
		// Create a 4x4 matrix that will orient the object at bbPos to face targetPos
		JOMatrix.createBillboardMatrix(bbmatrix, right, up, look, bbPos);
		
		// apply the billboard matrix
		gl.glMultMatrixf(bbmatrix);
	}

	@Override
	public void keyPressed(KeyEvent _event) {
	    super.keyPressed(_event);
	    
	    if (_event.getKeyCode() == KeyEvent.VK_SPACE) {
	    	cam.setCamera((cam.camera == camera1)? camera2 : camera1);
    	}
    }
}