|
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 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, image2.getWidth(),
image2.getHeight(), 0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE,
image2.getPixels());
/* Create Texture ********************************************/
gl.glGenTextures(2, texture[1], 0);
/* 2d texture (x and y size)*/
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[1][0]);
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, image3.getWidth(),
image3.getHeight(), 0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE,
image3.getPixels());
/* Create Texture *********************************************/
/* 2d texture (x and y size)*/
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[1][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 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, image4.getWidth(),
image4.getHeight(), 0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE,
image4.getPixels());
}
private void initVars(GLU glu) {
//create palnes
pl1 = new Plane();
pl1.position = new Tuple3d(0, -300, 0);
pl1.normal = new Tuple3d(0, 1, 0);
pl2 = new Plane();
pl2.position = new Tuple3d(300, 0, 0);
pl2.normal = new Tuple3d(-1, 0, 0);
pl3 = new Plane();
pl3.position = new Tuple3d(-300, 0, 0);
pl3.normal = new Tuple3d(1, 0, 0);
pl4 = new Plane();
pl4.position = new Tuple3d(0, 0, 300);
pl4.normal = new Tuple3d(0, 0, -1);
pl5 = new Plane();
pl5.position = new Tuple3d(0, 0, -300);
pl5.normal = new Tuple3d(0, 0, 1);
cyl1 = new Cylinder();
cyl1.position = new Tuple3d(0, 0, 0);
cyl1.axis = new Tuple3d(0, 1, 0);
cyl1.radius = 60 + 20;
cyl2 = new Cylinder();
cyl2.position = new Tuple3d(200, -300, 0);
cyl2.axis = new Tuple3d(0, 0, 1);
cyl2.radius = 60 + 20;
cyl3 = new Cylinder();
cyl3.position = new Tuple3d(-200, 0, 0);
cyl3.axis = new Tuple3d(0, 1, 1);
cyl3.axis.normalize();
cyl3.radius = 30 + 20;
//create quadratic object to render cylinders
cylinder = glu.gluNewQuadric();
glu.gluQuadricTexture(cylinder, true);
//Set initial positions and velocities of balls
//also initialize array which holds explosions
nrOfBalls = 10;
arrayVel[0] = new Tuple3d(veloc);
arrayPos[0] = new Tuple3d(199, 180, 10);
explosions[0] = new Explosion();
explosions[0].alpha = 0;
explosions[0].scale = 1;
arrayVel[1] = new Tuple3d(veloc);
arrayPos[1] = new Tuple3d(0, 150, 100);
explosions[1] = new Explosion();
explosions[1].alpha = 0;
explosions[1].scale = 1;
arrayVel[2] = new Tuple3d(veloc);
arrayPos[2] = new Tuple3d(-100, 180, -100);
explosions[2] = new Explosion();
explosions[2].alpha = 0;
explosions[2].scale = 1;
for (int i = 3; i < 10; i++) {
arrayVel[i] = new Tuple3d(veloc);
arrayPos[i] = new Tuple3d(-500 + i * 75, 300, -500 + i * 50);
explosions[i] = new Explosion();
explosions[i].alpha = 0;
explosions[i].scale = 1;
}
for (int i = 10; i < 20; i++) {
explosions[i] = new Explosion();
explosions[i].alpha = 0;
explosions[i].scale = 1;
}
}
private void update() {
if (decreaseCameraZ)
cameraPosition.z -= 10;
if (increaseCameraZ)
cameraPosition.z += 10;
if (increaseCameraRotation)
cameraRotation += 10;
if (decreaseCameraRotation)
cameraRotation -= 10;
if (increaseTimeStep)
timeStep += 0.1;
if (decreaseTimeStep)
timeStep -= 0.1;
}
public void display(GLAutoDrawable drawable) {
update();
GL gl = drawable.getGL();
int i;
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glLoadIdentity();
//set camera in hookmode
if (cameraAttachedToBall)
glu.gluLookAt(arrayPos[0].x + 250, arrayPos[0].y + 250, arrayPos[0].z,
arrayPos[0].x + arrayVel[0].x, arrayPos[0].y + arrayVel[0].y,
arrayPos[0].z + arrayVel[0].z
, 0, 1, 0);
else
glu.gluLookAt(cameraPosition.x, cameraPosition.y, cameraPosition.z,
cameraPosition.x + dir.x, cameraPosition.y + dir.y,
cameraPosition.z + dir.z,
0, 1.0, 0.0);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glRotatef(cameraRotation, 0, 1, 0);
//render balls
for (i = 0; i < nrOfBalls; i++) {
switch (i) {
case 1:
gl.glColor3f(1.0f, 1.0f, 1.0f);
break;
case 2:
gl.glColor3f(1.0f, 1.0f, 0.0f);
break;
case 3:
gl.glColor3f(0.0f, 1.0f, 1.0f);
break;
case 4:
gl.glColor3f(0.0f, 1.0f, 0.0f);
break;
case 5:
gl.glColor3f(0.0f, 0.0f, 1.0f);
break;
case 6:
gl.glColor3f(0.65f, 0.2f, 0.3f);
break;
case 7:
gl.glColor3f(1.0f, 0.0f, 1.0f);
break;
case 8:
gl.glColor3f(0.0f, 0.7f, 0.4f);
break;
default:
gl.glColor3f(1.0f, 0, 0);
}
gl.glPushMatrix();
gl.glTranslated(arrayPos[i].x, arrayPos[i].y, arrayPos[i].z);
glu.gluSphere(cylinder, 20, 20, 20);
gl.glPopMatrix();
}
gl.glEnable(GL.GL_TEXTURE_2D);
//render walls(planes) with texture
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[1][1]);
gl.glColor3f(1, 1, 1);
gl.glBegin(GL.GL_QUADS);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(320, 320, 320);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(320, -320, 320);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(-320, -320, 320);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(-320, 320, 320);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(-320, 320, -320);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(-320, -320, -320);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(320, -320, -320);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(320, 320, -320);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(320, 320, -320);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(320, -320, -320);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(320, -320, 320);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(320, 320, 320);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(-320, 320, 320);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(-320, -320, 320);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(-320, -320, -320);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(-320, 320, -320);
gl.glEnd();
//render floor (plane) with colours
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[1][0]);
gl.glBegin(GL.GL_QUADS);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(-320, -320, 320);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(320, -320, 320);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(320, -320, -320);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(-320, -320, -320);
gl.glEnd();
//render columns(cylinders)
/* choose the texture to use.*/
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0][0]);
gl.glColor3f(.5f, .5f, .5f);
gl.glPushMatrix();
gl.glRotatef(90, 1, 0, 0);
gl.glTranslatef(0, 0, -500);
glu.gluCylinder(cylinder, 60, 60, 1000, 20, 2);
gl.glPopMatrix();
gl.glPushMatrix();
gl.glTranslatef(200, -300, -500);
glu.gluCylinder(cylinder, 60, 60, 1000, 20, 2);
gl.glPopMatrix();
gl.glPushMatrix();
gl.glTranslatef(-200, 0, 0);
gl.glRotatef(135, 1, 0, 0);
gl.glTranslatef(0, 0, -500);
glu.gluCylinder(cylinder, 30, 30, 1000, 20, 2);
gl.glPopMatrix();
//render/blend explosions
gl.glEnable(GL.GL_BLEND);
gl.glDepthMask(false);
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0][1]);
for (i = 0; i < 20; i++) {
if (explosions[i].alpha >= 0) {
gl.glPushMatrix();
explosions[i].alpha -= 0.01f;
explosions[i].scale += 0.03f;
gl.glColor4f(1, 1, 0, explosions[i].alpha);
gl.glScalef(explosions[i].scale, explosions[i].scale, explosions[i].scale);
gl.glTranslatef((float) explosions[i].position.x / explosions[i].scale,
(float) explosions[i].position.y / explosions[i].scale,
(float) explosions[i].position.z / explosions[i].scale);
gl.glCallList(dlist);
gl.glPopMatrix();
}
}
gl.glDepthMask(true);
gl.glDisable(GL.GL_BLEND);
gl.glDisable(GL.GL_TEXTURE_2D);
idle();
}
/**
* Main loop of the simulation. Moves, finds the collisions and responses
* of the objects in the current time step.
*/
private void idle() {
Tuple3d uveloc = new Tuple3d(),
normal = new Tuple3d(),
point = new Tuple3d(),
norm = new Tuple3d(),
Pos2 = new Tuple3d(),
Nc = new Tuple3d();
double rt2,
rt4,
rt[] = {0},
lamda[] = {10000},
RestTime[] = {0},
BallTime[] = {0};
int BallNr = 0,
BallColNr1[] = {0},
BallColNr2[] = {0};
if (!cameraAttachedToBall) {
cameraRotation += 0.1f;
if (cameraRotation > 360)
cameraRotation = 0;
}
RestTime[0] = this.timeStep;
lamda[0] = 1000;
//Compute velocity for next timestep using Euler equations
for (int j = 0; j < nrOfBalls; j++)
arrayVel[j].scaleAdd(RestTime[0], accel, arrayVel[j]);
//While timestep not over
while (RestTime[0] > EPSILON) {
lamda[0] = 10000; //initialize to very large value
//For all the balls find closest intersection between balls and
//planes/cylinders
for (int i = 0; i < nrOfBalls; i++) {
//compute new position and distance
oldPos[i] = new Tuple3d();
oldPos[i].set(arrayPos[i]);
uveloc.set(arrayVel[i]);
uveloc.normalize();
arrayPos[i].scaleAdd(RestTime[0], arrayVel[i], arrayPos[i]);
rt2 = oldPos[i].distance(arrayPos[i]);
//Test if collision occured between ball and all 5 planes
if (TestIntersionPlane(pl1, oldPos[i], uveloc, rt, norm)) {
//Find intersection time
rt4 = rt[0] * RestTime[0] / rt2;
//if smaller than the one already stored replace and in timestep
if (rt4 <= lamda[0]) {
if (rt4 <= RestTime[0] + EPSILON)
if (!((rt[0] <= EPSILON) &&
(uveloc.dot(norm) > EPSILON))) {
normal.set(norm);
point.scaleAdd(rt[0], uveloc, oldPos[i]);
lamda[0] = rt4;
BallNr = i;
}
}
}
if (TestIntersionPlane(pl2, oldPos[i], uveloc, rt, norm)) {
rt4 = rt[0] * RestTime[0] / rt2;
if (rt4 <= lamda[0]) {
if (rt4 <= RestTime[0] + EPSILON)
if (!((rt[0] <= EPSILON) &&
(uveloc.dot(norm) > EPSILON))) {
normal.set(norm);
point.scaleAdd(rt[0], uveloc, oldPos[i]);
lamda[0] = rt4;
BallNr = i;
}
}
}
if (TestIntersionPlane(pl3, oldPos[i], uveloc, rt, norm)) {
rt4 = rt[0] * RestTime[0] / rt2;
if (rt4 <= lamda[0]) {
if (rt4 <= RestTime[0] + EPSILON)
if (!((rt[0] <= EPSILON) &&
(uveloc.dot(norm) > EPSILON))) {
normal.set(norm);
point.scaleAdd(rt[0], uveloc, oldPos[i]);
lamda[0] = rt4;
BallNr = i;
}
}
}
if (TestIntersionPlane(pl4, oldPos[i], uveloc, rt, norm)) {
rt4 = rt[0] * RestTime[0] / rt2;
if (rt4 <= lamda[0]) {
if (rt4 <= RestTime[0] + EPSILON)
if (!((rt[0] <= EPSILON) &&
(uveloc.dot(norm) > EPSILON))) {
normal.set(norm);
point.scaleAdd(rt[0], uveloc, oldPos[i]);
lamda[0] = rt4;
BallNr = i;
}
}
}
if (TestIntersionPlane(pl5, oldPos[i], uveloc, rt, norm)) {
rt4 = rt[0] * RestTime[0] / rt2;
if (rt4 <= lamda[0]) {
if (rt4 <= RestTime[0] + EPSILON)
if (!((rt[0] <= EPSILON) &&
(uveloc.dot(norm) > EPSILON))) {
normal.set(norm);
point.scaleAdd(rt[0], uveloc, oldPos[i]);
lamda[0] = rt4;
BallNr = i;
}
}
}
//Now test intersection with the 3 cylinders
if (TestIntersionCylinder(cyl1, oldPos[i], uveloc, rt, norm, Nc)) {
rt4 = rt[0] * RestTime[0] / rt2;
if (rt4 <= lamda[0]) {
if (rt4 <= RestTime[0] + EPSILON)
if (!((rt[0] <= EPSILON) &&
(uveloc.dot(norm) > EPSILON))) {
normal.set(norm);
point.set(Nc);
lamda[0] = rt4;
BallNr = i;
}
}
}
if (TestIntersionCylinder(cyl2, oldPos[i], uveloc, rt, norm, Nc)) {
rt4 = rt[0] * RestTime[0] / rt2;
if (rt4 <= lamda[0]) {
if (rt4 <= RestTime[0] + EPSILON)
if (!((rt[0] <= EPSILON) &&
(uveloc.dot(norm) > EPSILON))) {
normal.set(norm);
point.set(Nc);
lamda[0] = rt4;
BallNr = i;
}
}
}
if (TestIntersionCylinder(cyl3, oldPos[i], uveloc, rt, norm, Nc)) {
rt4 = rt[0] * RestTime[0] / rt2;
if (rt4 <= lamda[0]) {
if (rt4 <= RestTime[0] + EPSILON)
if (!((rt[0] <= EPSILON) &&
(uveloc.dot(norm) > EPSILON))) {
normal.set(norm);
point.set(Nc);
lamda[0] = rt4;
BallNr = i;
}
}
}
}
//After all balls were tested with planes/cylinders test for collision
//between them and replace if collision time smaller
if (findBallCol(Pos2, BallTime, RestTime, BallColNr1, BallColNr2) == 1) {
if (soundsEnabled)
audioSample.play();
if ((lamda[0] == 10000) || (lamda[0] > BallTime[0])) {
RestTime[0] = RestTime[0] - BallTime[0];
Tuple3d pb1 = new Tuple3d(),
pb2 = new Tuple3d(),
xaxis = new Tuple3d(),
U1x = new Tuple3d(),
U1y = new Tuple3d(),
U2x = new Tuple3d(),
U2y = new Tuple3d(),
V1x = new Tuple3d(),
V1y = new Tuple3d(),
V2x = new Tuple3d(),
V2y = new Tuple3d();
double a,b;
pb1.scaleAdd(BallTime[0], arrayVel[BallColNr1[0]],
oldPos[BallColNr1[0]]);
pb2.scaleAdd(BallTime[0], arrayVel[BallColNr2[0]],
oldPos[BallColNr2[0]]);
xaxis.sub(pb2, pb1);
xaxis.normalize();
a = xaxis.dot(arrayVel[BallColNr1[0]]);
U1x.scaleAdd(a, xaxis);
U1y.sub(arrayVel[BallColNr1[0]], U1x);
xaxis.sub(pb1, pb2);
xaxis.normalize();
b = xaxis.dot(arrayVel[BallColNr2[0]]);
U2x.scaleAdd(b, xaxis);
U2y.sub(arrayVel[BallColNr2[0]], U2x);
V1x.add(U1x, U2x);
V1x.sub(new Tuple3d(U1x.x - U2x.x, U1x.y - U2x.y, U1x.z - U2x.z));
V1x.scale(.5);
V2x.add(U1x, U2x);
V2x.sub(new Tuple3d(U2x.x - U1x.x, U2x.y - U1x.y, U2x.z - U1x.z));
V2x.scale(.5);
V1y.set(U1y);
V2y.set(U2y);
for (int j = 0; j < nrOfBalls; j++)
arrayPos[j].scaleAdd(BallTime[0], arrayVel[j], oldPos[j]);
arrayVel[BallColNr1[0]].add(V1x, V1y);
arrayVel[BallColNr2[0]].add(V2x, V2y);
//Update explosion array
for (int j = 0; j < 20; j++) {
if (explosions[j].alpha <= 0) {
explosions[j].alpha = 1;
explosions[j].position = arrayPos[BallColNr1[0]];
explosions[j].scale = 1;
break;
}
}
continue;
}
}
//End of tests
//If test occured move simulation for the correct timestep
//and compute response for the colliding ball
if (lamda[0] != 10000) {
RestTime[0] -= lamda[0];
for (int j = 0; j < nrOfBalls; j++)
arrayPos[j].scaleAdd(lamda[0], arrayVel[j], oldPos[j]);
rt2 = arrayVel[BallNr].length();
arrayVel[BallNr].normalize();
normal.scale(-2 * normal.dot(arrayVel[BallNr]));
arrayVel[BallNr].add(normal, arrayVel[BallNr]);
arrayVel[BallNr].normalize();
arrayVel[BallNr].scale(rt2);
// Update explosion array
for (int j = 0; j < 20; j++) {
if (explosions[j].alpha <= 0) {
explosions[j].alpha = 1;
explosions[j].position = point;
explosions[j].scale = 1;
break;
}
}
} else
RestTime[0] = 0;
}
}
public void reshape(GLAutoDrawable drawable,
int xstart,
int ystart,
int width,
int height) {
GL gl = drawable.getGL();
height = (height == 0) ? 1 : height;
gl.glViewport(0, 0, width, height); // Reset The Current Viewport
gl.glMatrixMode(GL.GL_PROJECTION); // Select The Projection Matrix
gl.glLoadIdentity(); // Reset The Projection Matrix
// Calculate The Aspect Ratio Of The Window
glu.gluPerspective(50.0f, (float) width / height, 10.f, 1700.0f);
gl.glMatrixMode(GL.GL_MODELVIEW); // Select The Modelview Matrix
gl.glLoadIdentity();
}
public void displayChanged(GLAutoDrawable drawable,
boolean modeChanged,
boolean deviceChanged) {
}
/*
* Intersetion tests
*/
/**
* Fast Intersection Function between ray/plane
*/
private boolean TestIntersionPlane(Plane plane, Tuple3d position, Tuple3d direction,
double[] lamda, Tuple3d pNormal) {
double dotProduct = direction.dot(plane.normal);
double l2;
//determine if ray paralle to plane
if ((dotProduct < EPSILON) && (dotProduct > -EPSILON))
return false;
Tuple3d substract = new Tuple3d(plane.position);
substract.sub(position);
l2 = (plane.normal.dot(substract)) / dotProduct;
if (l2 < -EPSILON)
return false;
pNormal.set(plane.normal);
lamda[0] = l2;
return true;
}
/**
* Fast Intersection Function between ray/cylinder
*/
private boolean TestIntersionCylinder(Cylinder cylinder, Tuple3d position,
Tuple3d direction, double[] lamda,
Tuple3d pNormal, Tuple3d newposition) {
Tuple3d RC = new Tuple3d(),
HB = new Tuple3d(),
n = new Tuple3d(),
O = new Tuple3d();
double d,t,s,
ln,in,out;
RC.sub(position, cylinder.position);
n.cross(direction, cylinder.axis);
ln = n.length();
if ((ln < EPSILON) && (ln > -EPSILON))
return false;
n.normalize();
d = Math.abs(RC.dot(n));
if (d <= cylinder.radius) {
O.cross(RC, cylinder.axis);
t = -O.dot(n) / ln;
O.cross(n, cylinder.axis);
O.normalize();
s = Math.abs(Math.sqrt(cylinder.radius * cylinder.radius - d * d) /
direction.dot(O));
in = t - s;
out = t + s;
if (in < -EPSILON) {
if (out < -EPSILON)
return false;
else
lamda[0] = out;
} else if (out < -EPSILON) {
lamda[0] = in;
} else if (in < out)
lamda[0] = in;
else
lamda[0] = out;
newposition.scaleAdd(lamda[0], direction, position);
HB.sub(newposition, cylinder.position);
pNormal.scaleAdd(-HB.dot(cylinder.axis), cylinder.axis, HB);
pNormal.normalize();
return true;
}
return false;
}
/**
* Find if any of the current balls intersect with each other in the current timestep.
* @return the index of the 2 itersecting balls, the point and time of intersection
*/
private int findBallCol(Tuple3d point, double[] TimePoint,
double[] Time2, int[] BallNr1, int[] BallNr2) {
Tuple3d RelativeVClone = new Tuple3d(),
RelativeV = new Tuple3d(),
posi = new Tuple3d();
double Timedummy = 10000,
MyTime = 0,
Add = Time2[0] / 150;
TRay rays;
//Test all balls against eachother in 150 small steps
for (int i = 0; i < nrOfBalls - 1; i++) {
for (int j = i + 1; j < nrOfBalls; j++) {
RelativeV.sub(arrayVel[i], arrayVel[j]);
RelativeVClone.set(RelativeV);
RelativeVClone.normalize();
rays = new TRay(oldPos[i], RelativeVClone);
MyTime = 0;
if ((rays.dist(oldPos[j])) > 40)
continue;
while (MyTime < Time2[0]) {
MyTime += Add;
posi.scaleAdd(MyTime, RelativeV, oldPos[i]);
if (posi.distance(oldPos[j]) <= 40) {
point.set(posi);
if (Timedummy > (MyTime - Add))
Timedummy = MyTime - Add;
BallNr1[0] = i;
BallNr2[0] = j;
break;
}
}
}
}
if (Timedummy != 10000) {
TimePoint[0] = Timedummy;
return 1;
}
return 0;
}
private static class Explosion {
public Tuple3d position = new Tuple3d();
public float alpha;
public float scale;
}
private static class Cylinder {
public Tuple3d position;
public Tuple3d axis;
public double radius;
}
private static class Plane {
public Tuple3d position;
public Tuple3d normal;
}
private static class TRay {
public Tuple3d p; // Any point on the line
public Tuple3d v; // Direction of the line
public TRay(Tuple3d p,
Tuple3d v) {
this.p = new Tuple3d(p);
this.v = new Tuple3d(v);
}
public double dist(Tuple3d point) {
double lambda = v.dot(new Tuple3d(point.x - p.x,
point.y - p.y,
point.z - p.z));
Tuple3d point2 = new Tuple3d();
point2.scaleAdd(lambda, v, p);
return point.distance(point2);
}
}
}
|
--> The classes inside math sub-package are available in the source distribution.
Related Tips
|
You can share your information about this topic using the form below!
Please do not post your questions with this form! Thanks.