/* * Portions Copyright (C) 2003 Sun Microsystems, Inc. * All rights reserved. */ /* * * COPYRIGHT NVIDIA CORPORATION 2003. ALL RIGHTS RESERVED. * BY ACCESSING OR USING THIS SOFTWARE, YOU AGREE TO: * * 1) ACKNOWLEDGE NVIDIA'S EXCLUSIVE OWNERSHIP OF ALL RIGHTS * IN AND TO THE SOFTWARE; * * 2) NOT MAKE OR DISTRIBUTE COPIES OF THE SOFTWARE WITHOUT * INCLUDING THIS NOTICE AND AGREEMENT; * * 3) ACKNOWLEDGE THAT TO THE MAXIMUM EXTENT PERMITTED BY * APPLICABLE LAW, THIS SOFTWARE IS PROVIDED *AS IS* AND * THAT NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, * EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED * TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE. * * IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS BE LIABLE FOR ANY * SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES * WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS * OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS * INFORMATION, OR ANY OTHER PECUNIARY LOSS), INCLUDING ATTORNEYS' * FEES, RELATING TO THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * */ package demos.infiniteShadowVolumes; import com.jogamp.opengl.util.gl2.GLUT; import demos.common.Demo; import demos.common.DemoListener; import demos.util.MD2; import gleem.BSphere; import gleem.BSphereProvider; import gleem.CameraParameters; import gleem.ExaminerViewer; import gleem.HandleBoxManip; import gleem.ManipManager; import gleem.linalg.Mat4f; import gleem.linalg.Rotf; import gleem.linalg.Vec3f; import gleem.linalg.Vec4f; import java.awt.BorderLayout; import java.awt.Frame; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; import java.nio.FloatBuffer; import javax.media.opengl.GLProfile; import javax.media.opengl.GL; import javax.media.opengl.GL2ES1; import javax.media.opengl.GL2; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLCapabilities; import javax.media.opengl.awt.AWTGLAutoDrawable; import javax.media.opengl.awt.GLCanvas; import javax.media.opengl.glu.GLU; /** Infinite shadow volumes are described in the paper "Practical and Robust Stenciled Shadow Volumes for Hardware-Accelerated Rendering" which can be found online at:

http://developer.nvidia.com/view.asp?IO=robust_shadow_volumes

This code is intended to illustrate the technique. It is not optimized for performance.

Cass Everitt
04-04-2002

Ported to Java by Kenneth Russell */ public class InfiniteShadowVolumes extends Demo { static { GLProfile.initSingleton(); } public static void main(String[] args) { GLCapabilities caps = new GLCapabilities(null); caps.setStencilBits(16); final GLCanvas canvas = new GLCanvas(caps); InfiniteShadowVolumes demo = new InfiniteShadowVolumes(); canvas.addGLEventListener(demo); demo.setDemoListener(new DemoListener() { public void shutdownDemo() { runExit(); } public void repaint() { canvas.repaint(); } }); Frame frame = new Frame("Infinite Stenciled Shadow Volumes"); frame.setLayout(new BorderLayout()); canvas.setSize(512, 512); frame.add(canvas, BorderLayout.CENTER); frame.pack(); frame.setVisible(true); canvas.requestFocus(); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { runExit(); } }); } //---------------------------------------------------------------------- // Internals only below this point // public void shutdownDemo() { ManipManager.getManipManager().unregisterWindow((AWTGLAutoDrawable) drawable); drawable.removeGLEventListener(this); super.shutdownDemo(); } static class Model { Model() { frame_num = 0; frame_incr = 0.25f; draw = true; ambient = new Vec4f(0.1f, 0.1f, 0.1f, 1); diffuse = new Vec4f(0.8f, 0, 0, 1); specular = new Vec4f(0.6f, 0.6f, 0.6f, 1); shininess = 64; } MD2.Model mod; MD2.Frame interp_frame; float frame_num; float frame_incr; Vec4f ambient; Vec4f diffuse; Vec4f specular; float shininess; boolean draw; }; // You can load multiple models and // position them independently. If they're // quake2 models you can animate them as well. private static final int MAX_MODELS = 4; private Model[] m = new Model[MAX_MODELS]; private int curr_model = 0; private int num_models = 0; // selector for the current view mode private static final int CAMERA_VIEW = 0; private static final int SCENE_VIEW = 1; private static final int CLIP_VIEW = 2; private int curr_view = CAMERA_VIEW; private GLU glu = new GLU(); private GLUT glut = new GLUT(); private GLAutoDrawable drawable; private ExaminerViewer viewer; private HandleBoxManip objectManip; private HandleBoxManip lightManip; private Mat4f objectManipXform; private Mat4f lightManipXform; int faceDisplayList; int wallTexObject; private boolean[] b = new boolean[256]; Vec4f light_position = new Vec4f(0,0,0,1); float light_object_scale = 1; float volume_alpha = .1f; float room_ambient = .3f; boolean doViewAll = true; private boolean enableDepthClampNV; private boolean toggleDepthClampNV; private boolean animateContinually; private boolean animateForward; private boolean animateBackward; private boolean hideCurrentModel; private boolean toggleWireframe; public void init(GLAutoDrawable drawable) { GL2 gl = drawable.getGL().getGL2(); gl.glClearStencil(128); //glEnable(GL2.GL_DEPTH_CLAMP_NV); gl.glEnable(GL2.GL_DEPTH_TEST); gl.glDepthFunc(GL2.GL_LESS); gl.glEnable(GL2.GL_NORMALIZE); gl.glLightModeli(GL2.GL_LIGHT_MODEL_TWO_SIDE, GL2.GL_FALSE); float[] ambient = new float[] {0.3f, 0.3f, 0.3f, 1}; gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, ambient, 0); faceDisplayList = gl.glGenLists(1); gl.glNewList(faceDisplayList, GL2.GL_COMPILE); drawMesh(gl, 20, 40); gl.glEndList(); int[] tmp = new int[1]; gl.glGenTextures(1, tmp, 0); wallTexObject = tmp[0]; gl.glBindTexture(GL2.GL_TEXTURE_2D, wallTexObject); gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_GENERATE_MIPMAP, GL2.GL_TRUE); gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR_MIPMAP_LINEAR); gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR); float[] tex = new float[32*32]; for(int i=0; i < 32; i++) { for(int j=0; j < 32; j++) { if ((i>>4 ^ j>>4) != 0) tex[i+j*32] = 1; else tex[i+j*32] = .9f; } } gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGBA, 32, 32, 0, GL2.GL_LUMINANCE, GL2.GL_FLOAT, FloatBuffer.wrap(tex)); initModel(); b['S'] = true; // no silhouette outlines b['v'] = true; // no volume drawing b['I'] = true; // use infinite far plane b['L'] = true; // use local light for shadowing doViewAll = true; //TODO drawable has no addKeyListener // drawable.addKeyListener(new KeyAdapter() { // public void keyTyped(KeyEvent e) { // dispatchKey(e.getKeyChar()); // demoListener.repaint(); // } // }); // Register the window with the ManipManager ManipManager manager = ManipManager.getManipManager(); manager.registerWindow((AWTGLAutoDrawable) drawable); this.drawable = drawable; objectManip = new HandleBoxManip(); manager.showManipInWindow(objectManip, (AWTGLAutoDrawable) drawable); objectManip.setTranslation(new Vec3f(0, 0, -2)); objectManip.setRotation(new Rotf(new Vec3f(1, 0, 0), (float) Math.toRadians(-90))); lightManip = new HandleBoxManip(); manager.showManipInWindow(lightManip, (AWTGLAutoDrawable) drawable); lightManip.setTranslation(new Vec3f(0.5f, 0.5f, -1)); lightManip.setGeometryScale(new Vec3f(0.1f, 0.1f, 0.1f)); viewer = new ExaminerViewer(); viewer.setUpVector(Vec3f.Y_AXIS); viewer.attach((AWTGLAutoDrawable) drawable, new BSphereProvider() { public BSphere getBoundingSphere() { return new BSphere(objectManip.getTranslation(), 1.0f); } }); viewer.setZNear(1.0f); viewer.setZFar(100.0f); viewer.setOrientation(new Rotf(new Vec3f(0, 1, 0), (float) Math.toRadians(15))); // FIXME // glutAddMenuEntry("mouse controls view [1]", '1'); // glutAddMenuEntry("mouse controls model [2]", '2'); // glutAddMenuEntry("mouse controls light [3]", '3'); // glutAddMenuEntry("mouse controls room [4]", '4'); // glutAddMenuEntry("enable depth clamp [!]", '!'); // glutAddMenuEntry("disable depth clamp [~]", '~'); // glutAddMenuEntry("start animation [ ]", ' '); // glutAddMenuEntry("step animation forward [a]", 'a'); // glutAddMenuEntry("step animation backward [b]", 'b'); // glutAddMenuEntry("toggle drawing silhouette [S]", 'S'); // glutAddMenuEntry("toggle drawing shadow [s]", 's'); // glutAddMenuEntry("toggle drawing visible shadow volume [v]", 'v'); // glutAddMenuEntry("toggle drawing model geometry[m]", 'm'); // glutAddMenuEntry("increase shadow volume alpha [;]", ';'); // glutAddMenuEntry("decrease shadow volume alpha [:]", ':'); // glutAddMenuEntry("next model [,]", ','); // glutAddMenuEntry("hide current model [.]", '.'); // glutAddMenuEntry("toggle view frustum clip planes [X]", 'X'); // glutAddMenuEntry("camera view [5]", '5'); // glutAddMenuEntry("scene view [6]", '6'); // glutAddMenuEntry("clipspace view [7]", '7'); // glutAddMenuEntry("enable depth clamp [!]", '!'); // glutAddMenuEntry("disable depth clamp [~]", '~'); // glutAddMenuEntry("increase light size [n]", 'n'); // glutAddMenuEntry("decrease light size [N]", 'N'); // glutAddMenuEntry("move near plane in [[]", '['); // glutAddMenuEntry("move near plane out []]", ']'); // glutAddMenuEntry("move far plane in [{]", '['); // glutAddMenuEntry("move far plane out [}]", ']'); // glutAddMenuEntry("toggle local/infinite light [L]", 'L'); // glutAddMenuEntry("hide room [R]", 'R'); // glutAddMenuEntry("view all with camera [c]", 'c'); // glutAddMenuEntry("quit []", 27); } public void dispose(GLAutoDrawable drawable) { GL2 gl = drawable.getGL().getGL2(); gl.glDeleteLists(faceDisplayList, 1); faceDisplayList=0; int[] tmp = new int[1]; tmp[0]=wallTexObject; gl.glDeleteTextures(1, tmp, 0); wallTexObject = 0; objectManip = null; lightManip = null; viewer = null; } public void display(GLAutoDrawable drawable) { GL2 gl = drawable.getGL().getGL2(); gl.glMatrixMode(GL2.GL_PROJECTION); gl.glLoadIdentity(); if (doViewAll) { viewer.viewAll(gl); doViewAll = false; } objectManipXform = objectManip.getTransform(); lightManipXform = lightManip.getTransform(); // TODO GL_DEPTH_CLAMP_NV not available // if (toggleDepthClampNV) { // if (enableDepthClampNV) { // gl.glEnable(GL2.GL_DEPTH_CLAMP_NV); // } else { // gl.glDisable(GL2.GL_DEPTH_CLAMP_NV); // } // toggleDepthClampNV = false; // } if (b[' ']) { animateForward = true; } if (animateForward) { Model mm = m[curr_model]; mm.frame_num += mm.frame_incr; if (mm.frame_num >= mm.mod.f.length) mm.frame_num = 0; interpolate_frame(); animateForward = false; } if (animateBackward) { Model mm = m[curr_model]; mm.frame_num -= mm.frame_incr; if (mm.frame_num < 0) mm.frame_num += mm.mod.f.length; interpolate_frame(); animateBackward = false; } if (hideCurrentModel) { gl.glNewList(faceDisplayList, GL2.GL_COMPILE); drawMesh(gl, 20, 40); gl.glEndList(); hideCurrentModel = false; } if (toggleWireframe) { if(b['w']) gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_LINE); else gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_FILL); } if(b['I']) { // push far plane to infinity switch (curr_view) { case CAMERA_VIEW: viewer.update(gl); // Undo perspective effects of ExaminerViewer gl.glMatrixMode(GL2.GL_PROJECTION); gl.glLoadIdentity(); applyInfinitePerspective(gl, viewer); break; case SCENE_VIEW: applyInfinitePerspective(gl, viewer); // FIXME: do we need more primitives in the ExaminerViewer class? // scenecam.apply_inverse_transform(); break; case CLIP_VIEW: applyInfinitePerspective(gl, viewer); // FIXME // clipcam.apply_inverse_transform(); gl.glScalef(10,10,-10); applyInfinitePerspective(gl, viewer); break; default: break; } } else { switch (curr_view) { case CAMERA_VIEW: viewer.update(gl); break; case SCENE_VIEW: applyInfinitePerspective(gl, viewer); // FIXME // scenecam.apply_inverse_transform(); break; case CLIP_VIEW: applyInfinitePerspective(gl, viewer); // FIXME // clipcam.apply_inverse_transform(); gl.glScalef(10,10,-10); // FIXME // reshaper.apply_projection(); break; default: break; } } gl.glMatrixMode(GL2.GL_MODELVIEW); // FIXME if (b['X']) { gl.glLoadIdentity(); if(b['I']) { // FIXME applyInfinitePerspectiveInverse(gl, viewer); } else { // FIXME // reshaper.apply_projection_inverse(); } double[] pos_x = new double[] {-1, 0, 0, 1}; double[] neg_x = new double[] { 1, 0, 0, 1}; double[] pos_y = new double[] { 0,-1, 0, 1}; double[] neg_y = new double[] { 0, 1, 0, 1}; double[] pos_z = new double[] { 0, 0,-1, 1}; double[] neg_z = new double[] { 0, 0, 1, 1}; gl.glClipPlane(GL2.GL_CLIP_PLANE0, pos_x, 0); gl.glClipPlane(GL2.GL_CLIP_PLANE1, neg_x, 0); gl.glClipPlane(GL2.GL_CLIP_PLANE2, pos_y, 0); gl.glClipPlane(GL2.GL_CLIP_PLANE3, neg_y, 0); gl.glClipPlane(GL2.GL_CLIP_PLANE4, pos_z, 0); gl.glClipPlane(GL2.GL_CLIP_PLANE5, neg_z, 0); gl.glEnable(GL2.GL_CLIP_PLANE0); gl.glEnable(GL2.GL_CLIP_PLANE1); gl.glEnable(GL2.GL_CLIP_PLANE2); gl.glEnable(GL2.GL_CLIP_PLANE3); gl.glEnable(GL2.GL_CLIP_PLANE4); gl.glEnable(GL2.GL_CLIP_PLANE5); gl.glLoadIdentity(); } gl.glPushMatrix(); // FIXME // camera.apply_inverse_transform(); // light.apply_transform(); gl.glMultMatrixf(getData(lightManipXform), 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, getData(light_position), 0); gl.glPopMatrix(); gl.glEnable(GL2.GL_LIGHT0); // FIXME gl.glPushMatrix(); // gl.glLoadIdentity(); // camera.apply_inverse_transform(); gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_STENCIL_BUFFER_BIT); ManipManager.getManipManager().updateCameraParameters((AWTGLAutoDrawable) drawable, viewer.getCameraParameters()); ManipManager.getManipManager().render((AWTGLAutoDrawable) drawable, gl); if (!b['R']) { drawRoom(gl, false); } if (!b['m']) { for (int i = 0; i < num_models; i++) if (m[i].draw) drawModel(gl, i, false); } if (b['X']) { gl.glDisable(GL2.GL_CLIP_PLANE0); gl.glDisable(GL2.GL_CLIP_PLANE1); gl.glDisable(GL2.GL_CLIP_PLANE2); gl.glDisable(GL2.GL_CLIP_PLANE3); gl.glDisable(GL2.GL_CLIP_PLANE4); gl.glDisable(GL2.GL_CLIP_PLANE5); } if (!b['s']) { for (int i = 0; i < num_models; i++) if (m[i].draw) drawShadowVolumeToStencil(gl, i); } // Be aware that this can cause some multipass artifacts // due to invariance issues. if (b['X']) { gl.glEnable(GL2.GL_CLIP_PLANE0); gl.glEnable(GL2.GL_CLIP_PLANE1); gl.glEnable(GL2.GL_CLIP_PLANE2); gl.glEnable(GL2.GL_CLIP_PLANE3); gl.glEnable(GL2.GL_CLIP_PLANE4); gl.glEnable(GL2.GL_CLIP_PLANE5); } if (!b['d']) { if (!b['R']) drawRoom(gl, true); if (!b['m']) for (int i = 0; i < num_models; i++) if (m[i].draw) drawModel(gl, i, true); } if(!b['S']) { for (int i = 0; i < num_models; i++) if (m[i].draw) drawPossibleSilhouette(gl, i); } if (!b['v']) { for (int i = 0; i < num_models; i++) if (m[i].draw) drawShadowVolumeToColor(gl, i); } // Be aware that this can cause some multipass artifacts // due to invariance issues. if (b['X']) { gl.glDisable(GL2.GL_CLIP_PLANE0); gl.glDisable(GL2.GL_CLIP_PLANE1); gl.glDisable(GL2.GL_CLIP_PLANE2); gl.glDisable(GL2.GL_CLIP_PLANE3); gl.glDisable(GL2.GL_CLIP_PLANE4); gl.glDisable(GL2.GL_CLIP_PLANE5); } drawLight(gl); gl.glPopMatrix(); // In an "external" viewing mode, show the camera's view volume // as a yellow wireframe cube or frustum. if (curr_view != CAMERA_VIEW) { gl.glPushMatrix(); if (b['I']) { // FIXME applyInfinitePerspectiveInverse(gl, viewer); } else { // FIXME // reshaper.apply_projection_inverse(); } gl.glColor3f(.75f,.75f,0); gl.glLineWidth(3); glut.glutWireCube(2); gl.glLineWidth(1); gl.glPopMatrix(); } if (b[' ']) { // Animating continually. Schedule another repaint soon. demoListener.repaint(); } } // Unused routines public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {} public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {} private void dispatchKey(char k) { b[k] = ! b[k]; if (k==27 || k=='q') { shutdownDemo(); return; } if(';' == k) { volume_alpha *= 1.1f; } if(':' == k) { volume_alpha /= 1.1f; } if('\'' == k) { room_ambient += .025f; } if('"' == k) { room_ambient -= .025f; } if(',' == k) { curr_model++; curr_model %= num_models; // FIXME // key('2',0,0); } if('.' == k) { m[curr_model].draw = ! m[curr_model].draw; } if('w' == k) { toggleWireframe = true; } if('1' == k) { // FIXME /* curr_manip = 1; camera.disable(); clipcam.disable(); scenecam.disable(); if(curr_view == 0) camera.enable(); else if(curr_view == 1) scenecam.enable(); else clipcam.enable(); for(int i=0; i < num_models; i++) object[i].disable(); light.disable(); room.disable(); */ } if('2' == k) { // FIXME /* curr_manip = 2; camera.disable(); clipcam.disable(); scenecam.disable(); light.disable(); for(int i=0; i < num_models; i++) object[i].disable(); object[curr_model].enable(); room.disable(); */ } if('3' == k) { // FIXME /* curr_manip = 3; camera.disable(); clipcam.disable(); scenecam.disable(); light.enable(); for(int i=0; i < num_models; i++) object[i].disable(); room.disable(); */ } if('4' == k) { // FIXME /* curr_manip = 4; camera.disable(); clipcam.disable(); scenecam.disable(); light.disable(); for(int i=0; i < num_models; i++) object[i].disable(); room.enable(); */ } if('5' == k) { // FIXME /* curr_view = 0; if(curr_manip == 1) key('1',0,0); */ } if('6' == k) { // FIXME /* curr_view = 1; if(curr_manip == 1) key('1',0,0); */ } if('7' == k) { // FIXME /* curr_view = 2; if(curr_manip == 1) key('1',0,0); */ } if('[' == k) { // FIXME: correct? viewer.setZNear(viewer.getZNear() / 2); // reshaper.zNear /= 2; } if(']' == k) { // FIXME: correct? viewer.setZNear(viewer.getZNear() * 2); // reshaper.zNear *= 2; } if('{' == k) { // FIXME: correct? viewer.setZFar(viewer.getZFar() / 2); // reshaper.zFar /= 2; } if('}' == k) { // FIXME: correct? viewer.setZFar(viewer.getZFar() * 2); // reshaper.zFar *= 2; } if('!' == k) { enableDepthClampNV = true; toggleDepthClampNV = true; } if('~' == k) { enableDepthClampNV = false; toggleDepthClampNV = true; } if('a' == k) { animateForward = true; } if('b' == k) { animateBackward = true; } if('.' == k) { hideCurrentModel = true; } if('n' == k) { light_object_scale *= 1.1f; } if('N' == k) { light_object_scale /= 1.1f; } if('L' == k) { if(b[k]) light_position.set(0,0,0,1); else light_position.set(0.25f, 0.25f, 1, 0); } if ('c' == k) { doViewAll = true; } } private void initModel() { int i = 0; try { MD2.Model mod = MD2.loadMD2(getClass().getClassLoader().getResourceAsStream("demos/data/models/knight.md2")); m[i] = new Model(); m[i].mod = mod; m[i].interp_frame = (MD2.Frame) m[i].mod.f[0].clone(); m[i].ambient.componentMul(m[i].diffuse); i++; } catch (IOException e) { e.printStackTrace(); } num_models = i; } // interpolate between keyframes private void interpolate_frame() { float frac = m[curr_model].frame_num - (float) Math.floor(m[curr_model].frame_num); int f0_index = (int) Math.floor(m[curr_model].frame_num); int f1_index = ((int) Math.ceil(m[curr_model].frame_num)) % m[curr_model].mod.f.length; MD2.Frame f0 = m[curr_model].mod.f[f0_index]; MD2.Frame f1 = m[curr_model].mod.f[f1_index]; for (int i = 0; i < f0.pn.length; i++) { MD2.PositionNormal pn = m[curr_model].interp_frame.pn[i]; MD2.PositionNormal pn0 = f0.pn[i]; MD2.PositionNormal pn1 = f1.pn[i]; pn.x = (1-frac) * pn0.x + frac * pn1.x; pn.y = (1-frac) * pn0.y + frac * pn1.y; pn.z = (1-frac) * pn0.z + frac * pn1.z; pn.nx = (1-frac) * pn0.nx + frac * pn1.nx; pn.ny = (1-frac) * pn0.ny + frac * pn1.ny; pn.nz = (1-frac) * pn0.nz + frac * pn1.nz; } for (int i = 0; i < f0.triplane.length; i++) { MD2.Plane p = m[curr_model].interp_frame.triplane[i]; MD2.computePlane(m[curr_model].interp_frame.pn[m[curr_model].mod.tri[i].v[0].pn_index], m[curr_model].interp_frame.pn[m[curr_model].mod.tri[i].v[1].pn_index], m[curr_model].interp_frame.pn[m[curr_model].mod.tri[i].v[2].pn_index], p); } } // This routine draws the end caps (both local and infinite) for an // occluder. These caps are required for the zfail approach to work. private void drawShadowVolumeEndCaps(GL2 gl, int mindex) { Vec4f olight = new Vec4f(); Mat4f ml = new Mat4f(objectManipXform); ml.invertRigid(); ml = ml.mul(lightManipXform); ml.xformVec(light_position, olight); MD2.PositionNormal[] vpn = m[mindex].interp_frame.pn; gl.glPushMatrix(); gl.glMultMatrixf(getData(objectManipXform), 0); gl.glBegin(GL2.GL_TRIANGLES); for (int i = 0; i < m[mindex].mod.tri.length; i++) { if (m[mindex].mod.tri[i].kill) continue; MD2.Plane p = m[mindex].interp_frame.triplane[i]; boolean facing_light = (( p.a * olight.get(0) + p.b * olight.get(1) + p.c * olight.get(2) + p.d * olight.get(3) ) >= 0 ); for (int j = 0; j < 3; j++) { MD2.PositionNormal pn = vpn[m[mindex].mod.tri[i].v[j].pn_index]; if (facing_light) // draw locally gl.glVertex4f(pn.x, pn.y, pn.z, 1); else // draw at infinity gl.glVertex4f(pn.x*olight.get(3) - olight.get(0), pn.y*olight.get(3) - olight.get(1), pn.z*olight.get(3) - olight.get(2), 0); } } gl.glEnd(); gl.glPopMatrix(); } private void drawModel(GL2 gl, int mindex, boolean do_diffuse) { MD2.PositionNormal[] vpn = m[mindex].interp_frame.pn; float[] zero = new float[] { 0, 0, 0, 0}; float[] dim = new float[] {.2f,.2f,.2f,.2f}; float[] diffuse = new float[4]; float[] specular = new float[4]; gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_AMBIENT, getData(m[mindex].ambient), 0); gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, getData(m[mindex].diffuse), 0); gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, getData(m[mindex].specular), 0); gl.glMaterialf(GL2.GL_FRONT_AND_BACK, GL2.GL_SHININESS, m[mindex].shininess); if (!do_diffuse) { gl.glGetLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, diffuse, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, dim, 0); gl.glGetLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, specular, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, zero, 0); } else { gl.glBlendFunc(GL2.GL_ONE, GL2.GL_ONE); gl.glEnable(GL2.GL_BLEND); gl.glStencilFunc(GL2.GL_EQUAL, 128, ~0); gl.glStencilOp(GL2.GL_KEEP, GL2.GL_KEEP, GL2.GL_KEEP); gl.glEnable(GL2.GL_STENCIL_TEST); gl.glDepthFunc(GL2.GL_EQUAL); } gl.glPushMatrix(); gl.glMultMatrixf(getData(objectManipXform), 0); gl.glEnable(GL2.GL_LIGHTING); gl.glPolygonOffset(0,-2); gl.glEnable(GL2.GL_POLYGON_OFFSET_FILL); gl.glBegin(GL2.GL_TRIANGLES); { for (int i = 0; i < m[mindex].mod.tri.length; i++) { for(int j=0; j < 3; j++) { MD2.PositionNormal pn = vpn[m[mindex].mod.tri[i].v[j].pn_index]; gl.glNormal3f(pn.nx, pn.ny, pn.nz); gl.glVertex4f(pn.x, pn.y, pn.z, 1); } } } gl.glEnd(); gl.glDisable(GL2.GL_POLYGON_OFFSET_FILL); gl.glDisable(GL2.GL_LIGHTING); gl.glPopMatrix(); gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, new float[] { 0.8f, 0.8f, 0.8f, 1}, 0); gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, new float[] { 0.3f, 0.3f, 0.3f, 1}, 0); if (!do_diffuse) { gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, diffuse, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, specular, 0); } else { gl.glDisable(GL2.GL_BLEND); //glDisable(GL2.GL_STENCIL_TEST); gl.glStencilFunc(GL2.GL_ALWAYS, 128, ~0); gl.glStencilOp(GL2.GL_KEEP, GL2.GL_KEEP, GL2.GL_KEEP); gl.glDepthFunc(GL2.GL_LESS); } } // This is for drawing the walls of the room. private void drawMesh(GL2 gl, float size, int tess) { float hsize = size/2; float delta = size/(tess-1); gl.glPushMatrix(); gl.glTranslatef(-hsize, -hsize, hsize); gl.glNormal3f(0,0,-1); float x = 0; for(int i=0; i < tess-1; i++) { float y = 0; gl.glBegin(GL2.GL_QUAD_STRIP); for(int j=0; j < tess; j++) { gl.glTexCoord2f( x, y); gl.glVertex2f ( x, y); gl.glTexCoord2f(x+delta, y); gl.glVertex2f (x+delta, y); y += delta; } gl.glEnd(); x += delta; } gl.glPopMatrix(); } private void drawCube(GL2 gl) { gl.glBindTexture(GL2.GL_TEXTURE_2D, wallTexObject); gl.glEnable(GL2.GL_TEXTURE_2D); gl.glPushMatrix(); // FIXME // room.apply_transform(); gl.glCallList(faceDisplayList); gl.glRotatef(90, 1, 0, 0); gl.glCallList(faceDisplayList); gl.glRotatef(90, 1, 0, 0); gl.glCallList(faceDisplayList); gl.glRotatef(90, 1, 0, 0); gl.glCallList(faceDisplayList); gl.glRotatef(90, 1, 0, 0); gl.glRotatef(90, 0, 1, 0); gl.glCallList(faceDisplayList); gl.glRotatef(180, 0, 1, 0); gl.glCallList(faceDisplayList); gl.glPopMatrix(); gl.glDisable(GL2.GL_TEXTURE_2D); } private void drawRoom(GL2 gl, boolean do_diffuse) { float[] zero = new float[] {0,0,0,0}; float[] a = new float[4]; a[0] = room_ambient; a[1] = room_ambient; a[2] = room_ambient; a[3] = 1; float[] d1 = new float[] {.1f,.1f,.1f,.1f}; float[] d2 = new float[] {.7f,.7f,.7f,.7f}; float[] s = new float[] {.7f,.7f,.7f,.7f}; float[] emission = new float[4]; float[] ambient = new float[4]; float[] diffuse = new float[4]; float[] specular = new float[4]; gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_AMBIENT, a, 0); gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, new float[] {0.8f, 0.8f, 0.8f, 1}, 0); gl.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, new float[] {0.4f, 0.4f, 0.4f, 1}, 0); gl.glMaterialf(GL2.GL_FRONT_AND_BACK, GL2.GL_SHININESS, 64.0f); if (!do_diffuse) { gl.glGetLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, diffuse, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, d1, 0); gl.glGetLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, specular, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, zero, 0); gl.glStencilFunc(GL2.GL_ALWAYS, 128, ~0); } else { gl.glGetLightfv(GL2.GL_LIGHT0, GL2.GL_EMISSION, emission, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_EMISSION, zero, 0); gl.glGetLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, ambient, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, zero, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, d2, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, s, 0); gl.glBlendFunc(GL2.GL_ONE, GL2.GL_ONE); gl.glEnable(GL2.GL_BLEND); gl.glStencilFunc(GL2.GL_EQUAL, 128, ~0); gl.glDepthFunc(GL2.GL_EQUAL); } gl.glPushMatrix(); gl.glTranslatef(0,9,0); gl.glEnable(GL2.GL_LIGHTING); gl.glStencilOp(GL2.GL_KEEP, GL2.GL_KEEP, GL2.GL_KEEP); gl.glEnable(GL2.GL_STENCIL_TEST); drawCube(gl); gl.glStencilFunc(GL2.GL_ALWAYS, 128, ~0); gl.glStencilOp(GL2.GL_KEEP, GL2.GL_KEEP, GL2.GL_KEEP); gl.glDisable(GL2.GL_LIGHTING); gl.glPopMatrix(); if (!do_diffuse) { gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE, diffuse, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_SPECULAR, specular, 0); } else { gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_EMISSION, emission, 0); gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT, ambient, 0); gl.glDisable(GL2.GL_BLEND); gl.glDepthFunc(GL2.GL_LESS); } } // This routine draws the extruded "possible silhouette" edge. The // edge is extruded to infinity. // The paper describes identifying silhouette edge loops. The approach // in this demo is to visit each edge, determine if it's a "possible silhouette" // or not, and if it is, draw the extruded edge. This approach is not // as efficient, but it has the benefit of being extremely simple. // This routine also doubles as the routine for drawing the local and ininite // silhouette edges (when prim == GL_LINES). private void drawShadowVolumeEdges(GL2 gl, int mindex, int prim, boolean local, boolean infinity) { Vec4f olight = new Vec4f(); Mat4f ml = new Mat4f(objectManipXform); ml.invertRigid(); ml = ml.mul(lightManipXform); ml.xformVec(light_position, olight); gl.glPushMatrix(); gl.glMultMatrixf(getData(objectManipXform), 0); MD2.Frame f = m[mindex].interp_frame; gl.glBegin(prim); for (int i = 0; i < m[mindex].mod.edge.length; i++) { MD2.WingedEdge we = m[mindex].mod.edge[i]; if (we.w[0] == -1 || m[mindex].mod.tri[we.w[0]].kill || we.w[1] == -1 || m[mindex].mod.tri[we.w[1]].kill ) continue; MD2.Plane p0 = f.triplane[we.w[0]]; float f0 = ( p0.a * olight.get(0) + p0.b * olight.get(1) + p0.c * olight.get(2) + p0.d * olight.get(3) ); float f1 = -f0; if(we.w[1] != -1) { MD2.Plane p1 = f.triplane[we.w[1]]; f1 = ( p1.a * olight.get(0) + p1.b * olight.get(1) + p1.c * olight.get(2) + p1.d * olight.get(3) ); } int[] edge = new int[2]; if(f0 >= 0 && f1 < 0) { edge[0] = we.e[1]; edge[1] = we.e[0]; } else if(f1 >= 0 && f0 < 0) { edge[0] = we.e[0]; edge[1] = we.e[1]; } else { continue; } MD2.PositionNormal pn0 = f.pn[edge[0]]; MD2.PositionNormal pn1 = f.pn[edge[1]]; if(prim == GL2.GL_QUADS || local) { // local segment gl.glVertex4f(pn0.x, pn0.y, pn0.z, 1); gl.glVertex4f(pn1.x, pn1.y, pn1.z, 1); } if(prim == GL2.GL_QUADS || infinity) { // segment projected to infinity gl.glVertex4f(pn1.x*olight.get(3) - olight.get(0), pn1.y*olight.get(3) - olight.get(1), pn1.z*olight.get(3) - olight.get(2), 0); gl.glVertex4f(pn0.x*olight.get(3) - olight.get(0), pn0.y*olight.get(3) - olight.get(1), pn0.z*olight.get(3) - olight.get(2), 0); } } gl.glEnd(); gl.glPopMatrix(); } private void drawShadowVolumeExtrudedEdges(GL2 gl, int mindex) { drawShadowVolumeEdges(gl, mindex, GL2.GL_QUADS, true, true); } private void drawPossibleSilhouette(GL2 gl, int mindex) { gl.glLineWidth(3); gl.glColor3f(1,1,1); drawShadowVolumeEdges(gl, mindex, GL2.GL_LINES, true, !b['-']); gl.glLineWidth(1); } // Draw the shadow volume into the stencil buffer. private void drawShadowVolumeToStencil(GL2 gl, int mindex) { gl.glDepthFunc(GL2.GL_LESS); gl.glDepthMask(false); gl.glStencilFunc(GL2.GL_ALWAYS, 128, ~0); gl.glEnable(GL2.GL_STENCIL_TEST); gl.glEnable(GL2.GL_CULL_FACE); gl.glCullFace(GL2.GL_FRONT); gl.glStencilOp(GL2.GL_KEEP, GL2.GL_INCR, GL2.GL_KEEP); gl.glColorMask(false, false, false, false); drawShadowVolumeExtrudedEdges(gl, mindex); drawShadowVolumeEndCaps(gl, mindex); gl.glCullFace(GL2.GL_BACK); gl.glStencilOp(GL2.GL_KEEP, GL2.GL_DECR, GL2.GL_KEEP); drawShadowVolumeExtrudedEdges(gl, mindex); drawShadowVolumeEndCaps(gl, mindex); gl.glColorMask(true, true, true, true); gl.glDisable(GL2.GL_CULL_FACE); gl.glStencilFunc(GL2.GL_ALWAYS, 128, ~0); gl.glStencilOp(GL2.GL_KEEP, GL2.GL_KEEP, GL2.GL_KEEP); gl.glDepthMask(true); gl.glDepthFunc(GL2.GL_LESS); } // Draw the shadow volume into the color buffer. private void drawShadowVolumeToColor(GL2 gl, int mindex) { gl.glDepthFunc(GL2.GL_LESS); gl.glDepthMask(false); gl.glEnable(GL2.GL_BLEND); gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA); gl.glColor4f(1,1,1,.7f * volume_alpha); drawShadowVolumeEndCaps(gl, mindex); gl.glColor4f(1,1,.7f,.15f * volume_alpha); drawShadowVolumeExtrudedEdges(gl, mindex); gl.glDepthMask(true); gl.glDepthFunc(GL2.GL_LESS); gl.glDisable(GL2.GL_BLEND); } // Draw an icon to show where the local light is // or in what direction the infinite light is pointing. private void drawLight(GL2 gl) { gl.glColor3f(1,1,0); gl.glPushMatrix(); gl.glMultMatrixf(getData(lightManipXform), 0); gl.glScalef(light_object_scale, light_object_scale, light_object_scale); if (b['L']) { glut.glutSolidSphere(.01f, 20, 10); } else { Vec3f ldir = new Vec3f(light_position.get(0), light_position.get(1), light_position.get(2)); Rotf r = new Rotf(new Vec3f(0,0,1), ldir); Mat4f m = new Mat4f(); m.makeIdent(); m.setRotation(r); m = m.mul(perspectiveInverse(30, 1, 0.001f, 0.04f)); gl.glRotatef(180, 1, 0, 0); gl.glTranslatef(0,0,-0.02f); gl.glMultMatrixf(getData(m), 0); glut.glutSolidCube(2); } gl.glPopMatrix(); } // The infinite frustum set-up code. private Mat4f infiniteFrustum(float left, float right, float bottom, float top, float zNear) { Mat4f m = new Mat4f(); m.makeIdent(); m.set(0,0, (2*zNear) / (right - left)); m.set(0,2, (right + left) / (right - left)); m.set(1,1, (2*zNear) / (top - bottom)); m.set(1,2, (top + bottom) / (top - bottom)); // nudge infinity in just slightly for lsb slop float nudge = 1 - 1.0f / (1<<23); m.set(2,2, -1 * nudge); m.set(2,3, -2*zNear * nudge); m.set(3,2, -1); m.set(3,3, 0); m.transpose(); return m; } private Mat4f infiniteFrustumInverse(float left, float right, float bottom, float top, float zNear) { Mat4f m = new Mat4f(); m.makeIdent(); m.set(0,0, (right - left) / (2 * zNear)); m.set(0,3, (right + left) / (2 * zNear)); m.set(1,1, (top - bottom) / (2 * zNear)); m.set(1,3, (top + bottom) / (2 * zNear)); m.set(2,2, 0); m.set(2,3, -1); m.set(3,2, -1 / (2 * zNear)); m.set(3,3, 1 / (2 * zNear)); return m; } private Mat4f infinitePerspective(float fovy, float aspect, float zNear) { float tangent = (float) Math.tan(fovy / 2.0); float y = tangent * zNear; float x = aspect * y; return infiniteFrustum(-x, x, -y, y, zNear); } private Mat4f infinitePerspectiveInverse(float fovy, float aspect, float zNear) { float tangent = (float) Math.tan(fovy / 2.0); float y = tangent * zNear; float x = aspect * y; return infiniteFrustumInverse(-x, x, -y, y, zNear); } private void applyInfinitePerspective(GL2 gl, ExaminerViewer v) { CameraParameters parms = v.getCameraParameters(); float aspect = parms.getImagePlaneAspectRatio(); gl.glMultMatrixf(getData(infinitePerspective(parms.getVertFOV(), aspect, v.getZNear())), 0); } private void applyInfinitePerspectiveInverse(GL2 gl, ExaminerViewer v) { CameraParameters parms = v.getCameraParameters(); float aspect = parms.getImagePlaneAspectRatio(); gl.glMultMatrixf(getData(infinitePerspectiveInverse(parms.getVertFOV(), aspect, v.getZNear())), 0); } private Mat4f perspectiveInverse(float fovy, float aspect, float zNear, float zFar) { float tangent = (float) Math.tan(Math.toRadians(fovy / 2.0)); float y = tangent * zNear; float x = aspect * y; return frustumInverse(-x, x, -y, y, zNear, zFar); } private Mat4f frustumInverse(float left, float right, float bottom, float top, float zNear, float zFar) { Mat4f m = new Mat4f(); m.makeIdent(); m.set(0, 0, (right - left) / (2 * zNear)); m.set(0, 3, (right + left) / (2 * zNear)); m.set(1, 1, (top - bottom) / (2 * zNear)); m.set(1, 3, (top + bottom) / (2 * zNear)); m.set(2, 2, 0); m.set(2, 3, -1); m.set(3, 2, -(zFar - zNear) / (2 * zFar * zNear)); m.set(3, 3, (zFar + zNear) / (2 * zFar * zNear)); return m; } private float[] getData(Vec4f v) { return new float[] { v.x(), v.y(), v.z(), v.w() }; } private float[] getData(Mat4f m) { float[] res = new float[16]; m.getColumnMajorData(res); return res; } private static void runExit() { // Note: calling System.exit() synchronously inside the draw, // reshape or init callbacks can lead to deadlocks on certain // platforms (in particular, X11) because the JAWT's locking // routines cause a global AWT lock to be grabbed. Instead run // the exit routine in another thread. new Thread(new Runnable() { public void run() { System.exit(0); } }).start(); } }