diff options
Diffstat (limited to 'src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java')
-rw-r--r-- | src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java | 1346 |
1 files changed, 1346 insertions, 0 deletions
diff --git a/src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java b/src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java new file mode 100644 index 0000000..dde5135 --- /dev/null +++ b/src/demos/infiniteShadowVolumes/InfiniteShadowVolumes.java @@ -0,0 +1,1346 @@ +/* + * 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.sun.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.MouseButtonHelper; +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.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: <P> + + <a href = "http://developer.nvidia.com/view.asp?IO=robust_shadow_volumes">http://developer.nvidia.com/view.asp?IO=robust_shadow_volumes</a><P> + + This code is intended to illustrate the technique. It + is not optimized for performance. <P> + + Cass Everitt <BR> + 04-04-2002 <P> + + Ported to Java by Kenneth Russell +*/ + +public class InfiniteShadowVolumes extends Demo { + 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(MouseButtonHelper.numMouseButtons()); + 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 [<esc>]", 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(); + } +} |