|
This example shows the basics of collision detection, collision response, and physically based modelling effects.
This is the Java port of the one of the NeHe OpenGL tutorials.
You can get complete IntelliJ IDEA project structure (all source, resources, build script, …) by downloading the source distribution from here.
The original post of the programmer who ported the examples can be found here.
package demos.nehe.lesson30;
import demos.common.GLDisplay;
/*--. .-"-.
/ o_O / O o \
\_ (__\ \_ v _/
// \\ // \\
(( )) (( ))
¤¤¤¤¤¤¤¤¤¤¤¤¤¤--""---""--¤¤¤¤--""---""--¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
¤ ||| ||| ¤
¤ | | ¤
¤ ¤
¤ Programmer:Abdul Bezrati ¤
¤ Program :Nehe's 30th lesson port to JOGL ¤
¤ Comments :None ¤
¤ _______ ¤
¤ /` _____ `\;,
This e-mail address is being protected from spam bots, you need JavaScript enabled to view it
¤
¤ (__(^===^)__)';, ___ ¤
¤ / ::: \ ,; /^ ^\ ¤
¤ | ::: | ,;' ( Ö Ö ) ¤
¤¤¤'._______.'`¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ --°oOo--(_)--oOo°--¤¤*/
/**
* @author Abdul Bezrati
*/
public class Lesson30 {
public static void main(String[] args) {
GLDisplay neheGLDisplay = GLDisplay.createGLDisplay(
"Lesson 30: Collision detection");
Renderer renderer = new Renderer();
InputHandler inputHandler = new InputHandler(renderer, neheGLDisplay);
neheGLDisplay.addGLEventListener(renderer);
neheGLDisplay.addKeyListener(inputHandler);
neheGLDisplay.start();
}
}
package demos.nehe.lesson30;
import demos.common.GLDisplay;
import javax.swing.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
class InputHandler extends KeyAdapter {
private Renderer renderer;
public InputHandler(Renderer renderer, GLDisplay glDisplay) {
this.renderer = renderer;
glDisplay.registerKeyStrokeForHelp(
KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "Zoom in");
glDisplay.registerKeyStrokeForHelp(
KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "Zoom out");
glDisplay.registerKeyStrokeForHelp(
KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "Rotate scene left");
glDisplay.registerKeyStrokeForHelp(
KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "Rotate scene right");
glDisplay.registerKeyStrokeForHelp(
KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD8, 0), "Increase speed");
glDisplay.registerKeyStrokeForHelp(
KeyStroke.getKeyStroke(KeyEvent.VK_NUMPAD2, 0), "Decrease speed");
glDisplay.registerKeyStrokeForHelp(
KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), "Track ball");
glDisplay.registerKeyStrokeForHelp(
KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0), "Toggle sound");
}
public void keyPressed(KeyEvent e) {
processKeyEvent(e, true);
}
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_F3:
renderer.toggleSounds();
break;
case KeyEvent.VK_F2:
renderer.toggleCameraAttachedToBall();
break;
default:
processKeyEvent(e, false);
}
}
private void processKeyEvent(KeyEvent e, boolean pressed) {
switch (e.getKeyCode()) {
case KeyEvent.VK_DOWN:
renderer.zoomOut(pressed);
break;
case KeyEvent.VK_UP:
renderer.zoomIn(pressed);
break;
case KeyEvent.VK_LEFT:
renderer.increaseCameraRotation(pressed);
break;
case KeyEvent.VK_RIGHT:
renderer.decreaseCameraRotation(pressed);
break;
case KeyEvent.VK_NUMPAD8:
renderer.increaseTimeStep(pressed);
break;
case KeyEvent.VK_NUMPAD2:
renderer.decreaseTimeStep(pressed);
break;
}
}
}
package demos.nehe.lesson30;
import demos.common.ResourceRetriever;
import javax.sound.sampled.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Timer;
import java.util.TimerTask;
class AudioSample {
private final Clip clip;
public AudioSample(InputStream inputStream) throws IOException {
/* Load Sound*/
try {
// The inputstreams that we get by loading files from jars do not support
// mark() and reset(), which are required by AudioSystem.getAudioInputStream().
// To work around this problem, we first read the entire contents
// of the inputstream to a byte array and wrap it in a ByteArrayInputStream.
// This class does support mark() and reset().
InputStream in = ensureMarkResetAvailable(inputStream);
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(in);
AudioFormat audioFormat = audioInputStream.getFormat();
int size = (int) (audioFormat.getFrameSize() *
audioInputStream.getFrameLength());
byte[] audio = new byte[size];
audioInputStream.read(audio, 0, size);
DataLine.Info info = new DataLine.Info(Clip.class, audioFormat, size);
clip = (Clip)AudioSystem.getLine(info);
clip.open(audioFormat, audio, 0, size);
} catch (UnsupportedAudioFileException e) {
throw new RuntimeException(e);
} catch (LineUnavailableException e) {
throw new RuntimeException(e);
}
}
private static InputStream ensureMarkResetAvailable(InputStream inputStream)
throws IOException {
if (inputStream.markSupported()) {
return inputStream;
} else {
return new ByteArrayInputStream(readEntireStream(inputStream));
}
}
private static byte[] readEntireStream(InputStream inputStream) throws IOException {
byte[] buffer = new byte[8];
byte[] data = null;
int dataLength = 0;
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
data = append(buffer, bytesRead, data, dataLength);
dataLength += bytesRead;
}
return trim(data, dataLength);
}
private static byte[] append(byte[] data, int amount, byte[] array, int offset) {
if (array == null) {
array = new byte[amount];
}
if (offset + amount >= array.length) {
byte[] newArray = new byte[array.length * 2];
System.arraycopy(array, 0, newArray, 0, offset);
array = newArray;
}
System.arraycopy(data, 0, array, offset, amount);
return array;
}
private static byte[] trim(byte[] data, int amount) {
if (data == null) {
return new byte[amount];
} else if (data.length == amount) {
return data;
} else {
byte[] newArray = new byte[amount];
System.arraycopy(data, 0, newArray, 0, amount);
return newArray;
}
}
public void play() {
play(false, false);
}
public void play(boolean wait, boolean loop) {
if (wait) {
WaitUntilFinishedLineListener waitUntilFinishedLineListener =
new WaitUntilFinishedLineListener();
clip.addLineListener(waitUntilFinishedLineListener);
synchronized (clip) {
play(loop);
try {
clip.wait();
} catch (InterruptedException e) {
}
}
clip.removeLineListener(waitUntilFinishedLineListener);
} else {
play(loop);
}
}
private void play(boolean loop) {
clip.stop();
clip.setFramePosition(0);
if (loop) {
clip.loop(Clip.LOOP_CONTINUOUSLY);
} else {
clip.start();
}
}
private class WaitUntilFinishedLineListener implements LineListener {
public WaitUntilFinishedLineListener() {
}
public void update(LineEvent event) {
if (event.getType().equals(LineEvent.Type.STOP) ||
event.getType().equals(LineEvent.Type.CLOSE)) {
synchronized (clip) {
clip.notify();
}
}
}
}
public void stop() {
clip.stop();
}
}
package demos.nehe.lesson30;
import demos.common.ResourceRetriever;
import demos.common.TextureReader;
import demos.nehe.lesson30.math.Tuple3d;
import javax.media.opengl.*;
import javax.media.opengl.glu.GLUquadric;
import javax.media.opengl.glu.GLU;
import java.io.IOException;
class Renderer implements GLEventListener {
private static final float EPSILON = 1e-8f;
private Tuple3d[] arrayVel = new Tuple3d[10]; // holds velocity of balls
private Tuple3d[] arrayPos = new Tuple3d[10]; // position of balls
private Tuple3d[] oldPos = new Tuple3d[10]; // old position of balls
private Tuple3d veloc = new Tuple3d(.5, -.1, .5); // initial velocity of balls
private Tuple3d accel = new Tuple3d(0, -.05, 0); // acceleration ie. gravity of balls
private Tuple3d dir = new Tuple3d(0, 0, -10); // initial direction of camera
// initial position of cameraT
private Tuple3d cameraPosition = new Tuple3d(0, -50, 1000);
private boolean increaseCameraZ;
private boolean decreaseCameraZ;
private double timeStep = .6; // timestep of simulation
private boolean increaseTimeStep;
private boolean decreaseTimeStep;
private float cameraRotation = 0; // holds rotation around the Y axis
private boolean increaseCameraRotation;
private boolean decreaseCameraRotation;
private float[] spec = {1.0f, 1.0f, 1.0f, 1.0f}; // sets specular highlight of balls
private float[] posl = {0.0f, 400f, 0.0f, 1.0f}; // position of ligth source
private float[] amb2 = {0.3f, 0.3f, 0.3f, 1.0f}; // ambient of lightsource
private float[] amb = {0.2f, 0.2f, 0.2f, 1.0f}; // global ambient
private int dlist; // stores display list
private int[][] texture = new int[2][2]; // stores texture objects
private int nrOfBalls; // sets the number of balls,
// hook camera on ball, and sound on/off
private boolean cameraAttachedToBall = false;
private boolean soundsEnabled = true; // hook camera on ball, and sound on/off
private Plane pl1,pl2,pl3,pl4,pl5; // the 5 planes of the room
private Cylinder cyl1,cyl2,cyl3; // the 2 cylinders of the room
private Explosion[] explosions = new Explosion[20]; // holds max 20 explosions at once
private GLUquadric cylinder; // Quadratic object to render the cylinders
private AudioSample audioSample;
private GLU glu = new GLU();
public void zoomOut(boolean increase) {
increaseCameraZ = increase;
}
public void zoomIn(boolean decrease) {
decreaseCameraZ = decrease;
}
public void increaseTimeStep(boolean increase) {
increaseTimeStep = increase;
}
public void decreaseTimeStep(boolean decrease) {
decreaseTimeStep = decrease;
}
public boolean isSoundsEnabled() {
return soundsEnabled;
}
public void toggleSounds() {
this.soundsEnabled = !soundsEnabled;
}
public boolean isCameraAttachedToBall() {
return cameraAttachedToBall;
}
public void toggleCameraAttachedToBall() {
this.cameraAttachedToBall = !cameraAttachedToBall;
cameraRotation = 0;
}
public void increaseCameraRotation(boolean increase) {
increaseCameraRotation = increase;
}
public void decreaseCameraRotation(boolean decrease) {
decreaseCameraRotation = decrease;
}
public void init(GLAutoDrawable drawable) {
GL gl = drawable.getGL();
float df[] = {100f};
gl.glClearDepth(1.0f); // Depth Buffer Setup
gl.glEnable(GL.GL_DEPTH_TEST); // Enables Depth Testing
gl.glDepthFunc(GL.GL_LEQUAL); // The Type Of Depth Testing To Do
// Really Nice Perspective Calculations
gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
gl.glClearColor(0, 0, 0, 0);
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glShadeModel(GL.GL_SMOOTH);
gl.glEnable(GL.GL_CULL_FACE);
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, spec, 0);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_SHININESS, df, 0);
gl.glEnable(GL.GL_LIGHTING);
gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, posl, 0);
gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, amb2, 0);
gl.glEnable(GL.GL_LIGHT0);
gl.glLightModelfv(GL.GL_LIGHT_MODEL_AMBIENT, amb, 0);
gl.glEnable(GL.GL_COLOR_MATERIAL);
gl.glColorMaterial(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE);
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
gl.glEnable(GL.GL_TEXTURE_2D);
loadGLTextures(gl);
//Construct billboarded explosion primitive as display list
//4 quads at right angles to each other
gl.glNewList(dlist = gl.glGenLists(1), GL.GL_COMPILE);
gl.glBegin(GL.GL_QUADS);
gl.glRotatef(-45, 0, 1, 0);
gl.glNormal3f(0, 0, 1);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(-50, -40, 0);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(50, -40, 0);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(50, 40, 0);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(-50, 40, 0);
gl.glNormal3f(0, 0, -1);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(-50, 40, 0);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(50, 40, 0);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(50, -40, 0);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(-50, -40, 0);
gl.glNormal3f(1, 0, 0);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(0, -40, 50);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(0, -40, -50);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(0, 40, -50);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(0, 40, 50);
gl.glNormal3f(-1, 0, 0);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(0, 40, 50);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(0, 40, -50);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(0, -40, -50);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(0, -40, 50);
gl.glEnd();
gl.glEndList();
try {
audioSample = new AudioSample(
ResourceRetriever.getResourceAsStream(
"demos/data/samples/Explode.wav")
);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
initVars(glu);
}
private void loadGLTextures(GL gl) {
TextureReader.Texture image1 = null;
TextureReader.Texture image2 = null;
TextureReader.Texture image3 = null;
TextureReader.Texture image4 = null;
try {
image1 = TextureReader.readTexture("demos/data/images/Marble.bmp");
image2 = TextureReader.readTexture("demos/data/images/Spark.bmp");
image3 = TextureReader.readTexture("demos/data/images/Boden.bmp");
image4 = TextureReader.readTexture("demos/data/images/Wand.bmp");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
/* Create Texture *****************************************/
gl.glGenTextures(2, texture[0], 0);
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0][0]); /* 2d texture (x and y size)*/
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER,
GL.GL_LINEAR); /* scale linearly when image bigger than texture*/
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
GL.GL_LINEAR); /* scale linearly when image smalled than texture*/
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
/* 2d texture, level of detail 0 (normal), 3 components
(red, green, blue), x size from image, y size from image, */
/* border 0 (normal), rgb color data, unsigned byte data,
and finally the data itself.*/
gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, 3, image1.getWidth(),
image1.getHeight(), 0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE,
image1.getPixels());
/* Create Texture ******************************************/
/* 2d texture (x and y size)*/
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0][1]);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER,
GL.GL_LINEAR); /* scale linearly when image bigger than texture*/
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
GL.GL_LINEAR); /* scale linearly when image smalled than texture*/
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
/* 2d texture, level of det | |