package classwork; import javax.media.opengl.*; import java.util.ArrayList; import jocode.*; import jomodel.*; /** * Use a JOModel object to load and render an OBJ model with * face groups, materials and texture. The JOModel class can render * complicated objects that include multiple materials and textures. *
* Demonstrate how mouse positions can be mapped into the world space. * Click on the model to select the triangle at the mouse position. *
* Within the model faces can be organized into groups (look for lines that * start with g in the obj file. Each group can have a different material * and texture, which will be specified in the .mtl file. *
* JOModel will load the materials in the .mtl file and will draw the model * group by group, applying the correct materials for each group of faces. */ public class GLART_7_modelviewer_new extends JOApp { // holds mesh and materials, draws the mesh with groups JOModel model; float rotation = 0; float cameraY = 7; float cameraZ = 10; JOVector mousePos = new JOVector(); JOVector mousePrevPos = new JOVector(); // mouseDragged() will change model rotation float rotateModelX = 0; float rotateModelY = 0; /** * Main function creates and runs the application. */ public static void main(String args[]) { GLART_7_modelviewer_new app = new GLART_7_modelviewer_new(); displayWidth = 800; displayHeight = 600; //fullScreen = true; app.run(); } /** * Initialize */ public void setup() { // color of light source float lightDiffuse[] = { .9f, .9f, .9f, 1f }; // direct light float lightSpecular[] = { .9f, .9f, .9f, 1f }; // highlight float lightAmbient[] = { .4f, .4f, .45f, 1f }; // scattered light // light position: if last value is 0, then this describes light direction. If 1, then light position. float lightPosition[] = { -4f, 4f, 6, 0f }; // 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); // Force normals to length 1 gl.glEnable(GL.GL_NORMALIZE); // turn on textures gl.glEnable(GL.GL_TEXTURE_2D); // blending (for transparency) gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); // set the background color gl.glClearColor(.50f, .60f, .7f, 1); // Draw specular highlghts on top of textures gl.glLightModeli(GL.GL_LIGHT_MODEL_COLOR_CONTROL, GL.GL_SEPARATE_SPECULAR_COLOR ); // Create a light setLight( GL.GL_LIGHT1, lightDiffuse, lightAmbient, lightSpecular, lightPosition ); // Set the Projection Matrix (controls perspective) gl.glMatrixMode(GL.GL_PROJECTION); gl.glLoadIdentity(); glu.gluPerspective( 45.0f, // Field Of View (float)getWidth() / (float)getHeight(), // aspect ratio 1f, // near Z clipping plane 1000.0f); // far Z clipping plane //---------------------------------------------------------------- // Load the model (uncomment one row below) //---------------------------------------------------------------- //model = new JOModel("models/JetFire/JetFire.obj"); cameraY=2; cameraZ=3; //model = new JOModel("models/escocity_patrol_cop_obj/escocity_patrol_cop_.obj"); cameraY=7; cameraZ = 30; //model = new JOModel("models/porsche.obj"); cameraY=10; cameraZ = 100; model = new JOModel("models/f-16.obj"); cameraY=10; cameraZ = 13; //model = new JOModel("models/soccerball.obj"); cameraY=5; cameraZ = 15; if (model.mesh.materialLibeName != null && model.mesh.materialLibeName.equals("porsche.mtl")) { // the porsche needs smoothing model.mesh.setSmoothingAngle(60); model.mesh.regenerateNormals(); } // convert model to a displaylist to optimize model.makeDisplayList(); } /** * Render the scene. */ public void draw() { rotation += .3f; // 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(); // Where is the 'eye' glu.gluLookAt( 0f, cameraY, cameraZ, // eye position 0f, 0f, 0f, // target to look at 0f, 1f, 0f); // which way is up // rotate (based on mouse drag) gl.glRotatef(rotateModelY, 0, 1, 0); // around vertical axis gl.glRotatef(rotateModelX, 1, 0, 0); // around horizontal axis if (mouseButtonDown(1)) msg("DOWN"); model.render(); renderPickedTriangle(model.mesh); //-------------------------------------------------------------------- // Draw a sphere where the mouse touches the model. // // We got the mouse screen X,Y and Z depth from mouseMoved(). Now // we can convert the screen coords into world coords and draw a sphere // in the world at the mouse position. // // We'll call JOApp.getWorldCoordsAtScreen() which calls glu.gluUnproject() // to "un-project" the point. Just as OpenGL uses the modelview matrix, // projection matrix and viewport to render a point onto the screen, it // can also use these matrices to reverse the process, to go from a screen // position back into the world. //-------------------------------------------------------------------- float[] worldPoint = getWorldCoordsAtScreen((int)mousePos.x, (int)mousePos.y, mousePos.z); gl.glDepthMask(false); gl.glPushMatrix(); { gl.glTranslatef(worldPoint[0],worldPoint[1],worldPoint[2]); gl.glScalef(.1f,.1f,.1f); gl.glBindTexture(GL.GL_TEXTURE_2D,0); renderSphere(); } gl.glPopMatrix(); gl.glDepthMask(true); } public void mouseDown(int x, int y) { mousePrevPos = new JOVector(x,y,0); } public void mouseUp(int x, int y) { } /** * In order to draw a sphere where the mouse touches the model, we need to * convert the mouse screen coordinates into world coordinates. * * We can get the screen X and Y from the current mouse position. We get * the screen Z value by reading the depth buffer at the X,Y screen position. * Remember that OpenGL keeps track of how close each object is to the eye. * That Zdepth value is what's stored in the depth buffer, and each pixel has * a corresponding depth value that ranges from 0-1. This is our screen Z value. * * We'll call JOApp.getZDepth(X,Y) which calls gl.glReadPixels(X,Y,GL_DEPTH_COMPONENT) * to return the depth buffer value at the given screen X,Y. */ public void mouseMoved(int x, int y) { float z = getZDepth(x,y); msg("screen coords=" + x + "," + y + "," + z); mousePos = new JOVector(x,y,z); } public void mouseDragged(int x, int y) { msg("DRAG screen coords=" + x + "," + y); 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; } //---------------------------------------------------------------- // Pick triangle //---------------------------------------------------------------- /** * Return the triangle at the cursor x,y position, or null if * no triangle is at the cursor position. Requires that projectVerts() * has been run to populate the vertex screen positions. If more * than one triangle is at the cursor, then returns the one * closest to the viewpoint. * * @param x cursor x (screen coordinates) * @param y cursor y (screen coordinates) * @param triangles array of triangles from a GL_Mesh * @return the triangle at the cursor x,y * @see projectVerts() */ public JOTriangle getTriangle(float x, float y, JOTriangle[] triangles) { ArrayList candidates = new ArrayList(); JOTriangle t, closestT = null; float minZ = 100000; // find all triangles that contain cursor point (ignore Z depth) for (int i=0; i < triangles.length; i++) { t = triangles[i]; // See if the screen x,y is inside the triangle on screen. The triangle.p1.posS // is the screen XYZ of the triangle point, populated by JOMesh.projectVerts(). if (JOMesh.pointInTriangle(x, y, t.p1.posS.x, t.p1.posS.y, t.p2.posS.x, t.p2.posS.y, t.p3.posS.x, t.p3.posS.y)) { candidates.add(t); } } // Of the found triangles, which is closest to viewpoint for (int j=0; j < candidates.size(); j++) { if (((JOTriangle)(candidates.get(j))).Zdepth < minZ ) { closestT = (JOTriangle)(candidates.get(j)); minZ = closestT.Zdepth; } } return closestT; } /** * Highlight the triangle that's under the cursor. To do this we first * project all the triangles into screen space, then compare the mouse * screen position to the triangles screen positions to see if the mouse * is inside a triangle. */ public void renderPickedTriangle(JOMesh mesh) { JOTriangle pickedTri=null; // "project" the mesh vertices into the screen space. This doesn't // actually draw anything, it just creates the screen-space xyz // coordinate for each vertex. getTriangles() will use these values. mesh.projectVerts(mesh, getModelviewMatrix(), getProjectionMatrix(), getViewport()); // getTriangle() uses the 'projected' coordinates (created by // projectVerts() above) to find the triangle under the cursor. pickedTri = mesh.getTriangle(cursorX, cursorY); // Draw the triangle if (pickedTri != null) { gl.glDisable(GL.GL_LIGHTING); gl.glDisable(GL.GL_DEPTH_TEST); // draw on top of the model so it's visible gl.glColor3f(1,0,0); gl.glBindTexture(GL.GL_TEXTURE_2D,0); gl.glBegin(GL.GL_TRIANGLES); { gl.glVertex3f(pickedTri.p1.pos.x, pickedTri.p1.pos.y, pickedTri.p1.pos.z); gl.glVertex3f(pickedTri.p2.pos.x, pickedTri.p2.pos.y, pickedTri.p2.pos.z); gl.glVertex3f(pickedTri.p3.pos.x, pickedTri.p3.pos.y, pickedTri.p3.pos.z); } gl.glEnd(); // Draw a dot at the first vertex gl.glColor3f(0,1,0); gl.glPointSize(4); gl.glBegin(GL.GL_POINTS); { gl.glVertex3f(pickedTri.p1.pos.x, pickedTri.p1.pos.y, pickedTri.p1.pos.z); } gl.glEnd(); gl.glEnable(GL.GL_DEPTH_TEST); gl.glEnable(GL.GL_LIGHTING); } } }