package classwork;

import javax.media.opengl.*;

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

/**
 * GLART_3_city_viewports.java
 *
 * Create a simple grid of "buildings" by translating and scaling a cube.  Use
 * viewports to draw the scene in two squares on the screen, one in perspective
 * and one in ortho.  Use glFrustum() and glOrtho() to take a closer look at how
 * to set the projection in OpenGL.
 *
 * Use the arrow keys to move the viewpoint.
 *
 * gl.glFrustum() is the lower level function that glu.gluPerspective() calls to
 * create the perspective projection matrix.
 *
 * gl.glOrtho() sets the projection matrix to render with no perspective.
 *
 */
public class GLART_3_city_viewports extends JOApp {
	// create two arrays to hold random building sizes and colors.  see setup().
	int NUM_BLDG_ROWS = 5;
	int NUM_BLDG_COLS = 5;
	JOVector[][] buildingSizes = new JOVector[NUM_BLDG_ROWS][NUM_BLDG_COLS];
	JOVector[][] buildingColors = new JOVector[NUM_BLDG_ROWS][NUM_BLDG_COLS];

	// hold the viewpoint position in a vector object
	JOVector cameraPos = new JOVector(0f, 15f, 0f);

	// we'll apply a texture image to the buildings
	int buildingTexture = 0;

	// toggle value to control perspective/ortho rendering
	boolean perspectiveOn = true;

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

    /**
     * Initialize OpenGL
     */
    public void setup() {
    	// set the background color
    	gl.glClearColor(.9f, .9f, .9f, 1f);

    	// load and activate a texture for the buildings
        buildingTexture = makeTexture("images/Blue-Building-Facade-by-Shi-Yali-qpps.jpg");
        gl.glBindTexture(GL.GL_TEXTURE_2D, buildingTexture);

        // make random building sizes and colors
        // Yes, I'm using a vector to store color values. Sue me.
        for (int r=0; r < NUM_BLDG_ROWS; r++) {
			for (int c=0; c < NUM_BLDG_COLS; c++) {
				float grayvalue = .2f + random(.7f);
				buildingSizes[r][c] = new JOVector( 1+random(2.5f), 3+random(6f), 1+random(2.5f) );
				buildingColors[r][c] = new JOVector( grayvalue, grayvalue, grayvalue );
			}
		}
    }

    /**
     * Render the scene in two viewports, one with perspective one with orth.
     */
    public void draw() {
        // Clear screen and depth buffer
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

        // cosmetics: draw two boxes around the viewports (first reset viewport to the full window!)
        // ??? what happens if we comment out this glViewport line:
        gl.glViewport(0,0, getWidthWindow(), getHeightWindow());
        gl.glColor3f(.5f,.5f,.5f);
        drawRect(5,95,390,390);
        drawRect(395,95,390,390);

        // draw scene in left side viewport, in perspective
        gl.glViewport(10,100, 380,380);  // viewport uses screen coordinates, not world!
        setPerspective();
        drawScene();

        // draw scene in right side viewport, in ortho
        gl.glViewport(400,100, 380,380);  // viewport uses screen coordinates, not world!
        setOrtho();
        drawScene();
    }

    /**
     * draw the cityscape with camera looking down from above. In this function we only
     * care about the MODELVIEW.  We set the camera position and draw the geometry.
     * The PROJECTION is handled by the setPerspective() and setOrtho() functions below.
     */
    public void drawScene() {
        // Select The Modelview Matrix (controls model orientation)
        gl.glMatrixMode(GL.GL_MODELVIEW);

        // Reset the Modelview matrix
        gl.glLoadIdentity();

        // Where is the 'eye'
        glu.gluLookAt(
            cameraPos.x, cameraPos.y, cameraPos.z,    // eye is up the Y axis
            0f, -7f,  0f,    // look down below the origin
            0f, 0f, -1f);   // top of camera is pointing down the Z axis

        // we're going to draw a 5x5 grid of buildings.  Each cell in the grid is 4x4 units, so
        // the total grid size is 20x20.  Translate -8 on X and Z to center the grid at the origin.
        // the X and Z axes are the ground plane.  Y is the vertical axis.
        gl.glTranslatef(-8,0,-8);
        for (int r=0; r < NUM_BLDG_ROWS; r++) {
			for (int c=0; c < NUM_BLDG_COLS; c++) {
				// set the color
				gl.glColor3f( buildingColors[r][c].x, buildingColors[r][c].y, buildingColors[r][c].z );
				gl.glPushMatrix();
				{
					// shift coordinate system to building position
					gl.glTranslatef(r*4, buildingSizes[r][c].y/2, c*4);
					// scale coordinate system to building dimensions
					gl.glScalef(buildingSizes[r][c].x, buildingSizes[r][c].y, buildingSizes[r][c].z);
					// now the 1x1x1 cube will be stretched to the building size
					renderCube();
				}
				gl.glPopMatrix();
			}
		}
    }

    /**
     * render the scene in perspective using glFrustum().  A "frustum" is a
     * pyramid shape (see the Week3 notes page on the syllabus) that defines
     * the "viewing volume": how much of the scene will be included in the rendering.
     */
    public void setPerspective() {
        // select projection matrix (controls perspective) and reset it
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();

        // use the lower level glFrustum function to create a projection matrix.
        // we'll create a square view because we're rendering the scene into a square viewport.
        // the view is small (from -1 to 1) but remember that the frustum is a
        // pyramid shape.  It is 2 units wide at the NEAR PLANE but extends outward
        // from there to include a much larger volume.
        gl.glFrustum(
        		-1,   1,    // left and right side of scene (world coordinates)
        		-1,   1,    // bottom and top of scene (2 units wide, same as height, to make a square)
        		1.5f, 100); // near plane, far plane (world coordinates, distance from eye)

        // return to modelview matrix
        gl.glMatrixMode(GL.GL_MODELVIEW);
    }

    /**
     * render the scene with an Orthogonal projection.  In this case we define a
     * square shaped view because our viewport is square.  The grid of buildings
     * is 20x20 so we'll make our ortho view to be 20 units wide and 20 units high.
     */
    public void setOrtho() {
        // select projection matrix and reset it
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();

        // With glOrtho the viewing volume is like a box, not a frustum.  The sides of
        // the "viewing volume" are parallel, not pyramid shaped.  This makes things
        // simpler, as we can just imagine putting a box around our scene.  The edges
        // of the box are the edges of the view that will be rendered.  Our scene is
        // 20x20 units centered at the origin, so we'll make the glOrtho bounds -10 to 10.
        gl.glOrtho(
        		-10, 10,    // left and right side of scene (world coordinates)
        		-10, 10,    // bottom and top of scene (world coordinates)
        		 2f,100);   // near plane, far plane  (world coordinates, distance from eye)

        // return to modelview matrix
        gl.glMatrixMode(GL.GL_MODELVIEW);

    }

    /**
     * The keyDown() function is called by JOApp when a key is pressed.  The keycode
     * parameteris one of the Java KeyEvent ids such as VK_LEFT for the left arrow key.
     * When the arrow keys are pressed we shift the camera position in the XZ plane.
     */
    public void keyDown(int keycode) {
    	float increment = .2f;
    	if (keycode == KeyEvent.VK_LEFT) {
    		cameraPos.x -= increment;
    	}
    	else if (keycode == KeyEvent.VK_RIGHT) {
    		cameraPos.x += increment;
    	}
    	else if (keycode == KeyEvent.VK_UP) {
    		cameraPos.z -= increment;
    	}
    	else if (keycode == KeyEvent.VK_DOWN) {
    		cameraPos.z += increment;
    	}
    }
}

