package classwork; import java.awt.event.KeyEvent; import java.io.*; import javax.media.opengl.*; import jocode.*; import jomodel.*; /** * Simple model viewer with basic navigation features * - Loads 3DS or OBJ file * - Renders model with lighting * - Shows vertex normals * * Keys * - Arrow keys move viewpoint * - Mouse drag to rotate model * - Mouse wheel zooms in/out * - Hit 'N' to turn normal rendering on/off * * Requires the glmodel package to load model formats into a GL_Object class. * Requires JOMaterial.java * Requires JOVector.java * * The glmodel package contains two readers to parse the OBJ and 3DS formats. * Two importers convert the parsed data into vertex and triangle objects * that we can use to render the mesh in OpenGL. * The GL_Mesh class holds the final mesh that we draw with * the renderMesh() function. * */ public class GLART_6_model extends JOApp { private float rotateModelX = 0; private float rotateModelY = 0; private boolean normalsOn = true; private JOMesh obj; // camera position and rotation (see handleNavigationKeys()) float[] cameraPos = new float[] {0,0,100}; float cameraRotation = 0f; final float piover180 = 0.0174532925f; // A constant used in navigation // Material object will hold color values JOMaterial material = new JOMaterial(); // for mouse drag JOVector mousePrevPos = null; boolean mouseIsDown = false; // light position: if last value is 0, then this describes light direction. // If 1, then light position. float lightPosition[] = { -100f, 100f, 100, 0f }; /** * Main function creates and runs the application. */ public static void main(String args[]) { GLART_6_model app = new GLART_6_model(); app.run(); } /** * Initialize */ public void setup() { // color of overall scene lighting float ambient[] = { .2f, .2f, .2f, 1f }; // color of light source float lightDiffuse[] = { 1f, 1f, .8f, 1f }; // direct light float lightSpecular[] = { 1f, 1f, .8f, 1f }; // highlight float lightAmbient[] = { .2f, .2f, .2f, 1f }; // scattered light // color of material float mtlDiffuse[] = { 1f, .6f, .5f, 1f }; // red float mtlAmbient[] = { 1f, .6f, .5f, 1f }; // red float mtlSpecular[] = { .8f, .8f, .8f, 1f }; // almost white: very reflective float mtlShininess = 100f; // 0=no shine, 127=max shine // Select the Projection Matrix (controls perspective) gl.glMatrixMode(GL.GL_PROJECTION); gl.glLoadIdentity(); // Reset The Projection Matrix // Define perspective glu.gluPerspective( 30.0f, // Field Of View (float)getWidth() / (float)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); // 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 setLight( GL.GL_LIGHT1, lightDiffuse, lightAmbient, lightSpecular, lightPosition ); // no overall scene lighting setAmbientLight(ambient); // change the current material settings material.setDiffuse(mtlDiffuse); material.setAmbient(mtlAmbient); material.setSpecular(mtlSpecular); material.setShininess(mtlShininess); // activate this material material.apply(); // set the background color gl.glClearColor(.08f, .08f, .1f, 1); JOOBJImporter importOBJ = new JOOBJImporter(); obj = importOBJ.load("models/tri_pyramid.obj"); //obj = importOBJ.load("models/pig.obj"); //"shark.obj" "castle.obj" "venus.obj" //========== smoothing ================ // set the smoothing threshold. // If two faces meet at an angle greater than the threshold, they will be treated as two separate faces. // If the faces meet at less that the threshold, they will be treated as one smooth surface. // smoothing angle 0 means that even 1 degree of difference between faces will create a hard edge. // smoothing angle 100 means that faces that meet at 0-99 degrees will be smoothed. obj.setSmoothingAngle(170f); // 0=faceted 179=smoothest obj.regenerateNormals(); } /** * Render the scene. */ public void draw() { handleNavigationKeys(); // Clear screen and depth buffer gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); // Select The Modelview Matrix (controls model orientation) gl.glMatrixMode(GL.GL_MODELVIEW); // reset the coordinate system to center of screen gl.glLoadIdentity(); // Place the viewpoint glu.gluLookAt( // camera position cameraPos[0], cameraPos[1], cameraPos[2], // look at a point directly in front of camera cameraPos[0]- (float) Math.sin(cameraRotation* piover180), cameraPos[1], cameraPos[2]- (float) Math.cos(cameraRotation* piover180), // which way is up 0f, 1f, 0f); // set the light position (have to do this each render() since // we're moving the scene around with navigation, have to update // light position. Light pos is transformed just like a vertex). setLightPosition(GL.GL_LIGHT1, lightPosition[0], lightPosition[1], lightPosition[2]); // draw white sphere at light position gl.glDisable(GL.GL_LIGHTING); gl.glColor4f(1, 1, .9f, 1); gl.glPushMatrix(); { gl.glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]); renderSphere(); } gl.glPopMatrix(); gl.glEnable(GL.GL_LIGHTING); gl.glPushMatrix(); { // rotate model (based on mouse drag) gl.glRotatef(rotateModelY, 0, 1, 0); // around vertical axis gl.glRotatef(rotateModelX, 1, 0, 0); // around horizontal axis // draw the model renderMesh(obj, 0); // draw normals if (normalsOn) { renderMeshNormals(obj); } } gl.glPopMatrix(); } /** * Render mesh with normals and texture coordinates. Loops through * all triangles in the mesh object. * * Several triangles may refer to the same vertex, but each face * can have different normals for that vertex. This allows for * sharp edges between faces. * * @param o mesh object to render */ public void renderMesh(JOMesh o, int textureHandle) { JOTriangle t; gl.glBindTexture(GL.GL_TEXTURE_2D,textureHandle); gl.glBegin(GL.GL_TRIANGLES); for (int j = 0; j < o.triangles.length; j++) { // draw all triangles in object t = o.triangles[j]; gl.glTexCoord2f(t.uvw1.x, t.uvw1.y); gl.glNormal3f(t.norm1.x, t.norm1.y, t.norm1.z); gl.glVertex3f( (float)t.p1.pos.x, (float)t.p1.pos.y, (float)t.p1.pos.z); gl.glTexCoord2f(t.uvw2.x, t.uvw2.y); gl.glNormal3f(t.norm2.x, t.norm2.y, t.norm2.z); gl.glVertex3f( (float)t.p2.pos.x, (float)t.p2.pos.y, (float)t.p2.pos.z); gl.glTexCoord2f(t.uvw3.x, t.uvw3.y); gl.glNormal3f(t.norm3.x, t.norm3.y, t.norm3.z); gl.glVertex3f( (float)t.p3.pos.x, (float)t.p3.pos.y, (float)t.p3.pos.z); } gl.glEnd(); } public void renderMeshNormals(JOMesh o) { JOTriangle t; gl.glDisable(GL.GL_LIGHTING); gl.glColor3f(0,1,0); gl.glBegin(GL.GL_LINES); { for (int j = 0; j < o.triangles.length; j++) { // draw all triangles in object t = o.triangles[j]; gl.glVertex3f( (float)t.p1.pos.x, (float)t.p1.pos.y, (float)t.p1.pos.z); gl.glVertex3f( (float)(t.p1.pos.x+t.norm1.x), (float)(t.p1.pos.y+t.norm1.y), (float)(t.p1.pos.z+t.norm1.z)); gl.glVertex3f( (float)t.p2.pos.x, (float)t.p2.pos.y, (float)t.p2.pos.z); gl.glVertex3f( (float)(t.p2.pos.x+t.norm2.x), (float)(t.p2.pos.y+t.norm2.y), (float)(t.p2.pos.z+t.norm2.z)); gl.glVertex3f( (float)t.p3.pos.x, (float)t.p3.pos.y, (float)t.p3.pos.z); gl.glVertex3f( (float)(t.p3.pos.x+t.norm3.x), (float)(t.p3.pos.y+t.norm3.y), (float)(t.p3.pos.z+t.norm3.z)); } } gl.glEnd(); gl.glEnable(GL.GL_LIGHTING); } /** * Adjust the Camera position based on keyboard arrow key input. * These are repeating events (camera will move as long as key is held * down). */ public void handleNavigationKeys() { // Turn left if (JOApp.isKeyDown(KeyEvent.VK_LEFT)) { cameraRotation += 1.0f; } // Turn right if (JOApp.isKeyDown(KeyEvent.VK_RIGHT)) { cameraRotation -= 1.0f; } // move forward in current direction if (JOApp.isKeyDown(KeyEvent.VK_UP)) { cameraPos[0] -= (float) Math.sin(cameraRotation * piover180) * 1f; cameraPos[2] -= (float) Math.cos(cameraRotation * piover180) * 1f; } // move backward in current direction if (JOApp.isKeyDown(KeyEvent.VK_DOWN)) { cameraPos[0] += (float) Math.sin(cameraRotation * piover180) * 1f; cameraPos[2] += (float) Math.cos(cameraRotation * piover180) * 1f; } // move camera down if (JOApp.isKeyDown(KeyEvent.VK_PAGE_UP)) { cameraPos[1] += .3f; } // move camera up if (JOApp.isKeyDown(KeyEvent.VK_PAGE_DOWN)) { cameraPos[1] -= .3f; } } /** * Key event functions (keyDown() and keyUp() are are called by mainloop()). * @param keycode */ public void keyUp(int keycode) { // Normals off/on if (keycode == KeyEvent.VK_N) { normalsOn = !normalsOn; } } public void keyDown(int keycode) { } /** * Mouse event functions (called by handleMouseEvents() by way of mainloop()). * @param keycode */ public void mouseMove(int x, int y) { if (mouseIsDown) { int mouseDragDistanceX = (int)(x - mousePrevPos.x); int mouseDragDistanceY = (int)(y - mousePrevPos.y); rotateModelX += ((float)mouseDragDistanceY/(float)getHeight()) * 360f; rotateModelY += ((float)mouseDragDistanceX/(float)getWidth()) * 360f; mousePrevPos.x = x; mousePrevPos.y = y; } } public void mouseDown(int x, int y) { mouseIsDown = true; mousePrevPos = new JOVector(x,y,0); } public void mouseUp(int x, int y) { mouseIsDown = false; } public void mouseWheel(int wheelMoved) { // move forward or backward cameraPos[0] -= (float) Math.sin(cameraRotation * piover180) * (wheelMoved/50f); cameraPos[2] -= (float) Math.cos(cameraRotation * piover180) * (wheelMoved/50f); } }