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; } } }