I’m in a 3D graphics class that uses JOGL for its rendering.
My current assignment is to get a sphere bouncing around the inside of the box. I’ve got that working. I’ve even got multiple spheres bouncing off of one another. Hooray.
But now I want to add some 3D shapes for the spheres to bounce off of. I understand how to do the bouncing once I’ve got the closest point on a plane to my sphere, but the question is: given a point in 3d space, and a polygon defined as 3 other points in 3d space, how do I find the point on that polygon closest to the first point? If I know that, then I can do the collision detection (compare the distance between that and the center of the sphere to the radius of the sphere) as well as bouncing in the correct direction (reflect the delta vector around that point).
I’ve put together a small test program that contains all of the essentials. I’ve left out the actual drawing of the sphere (since that has nothing to do with the problem), so right now it’s just a point:
import java.awt.BorderLayout;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.swing.JFrame;
import com.sun.opengl.util.Animator;
import com.sun.opengl.util.FPSAnimator;
public class SpherePlaneTest implements GLEventListener {
private final GLCanvas canvas = new GLCanvas();
private final int worldX = -400;
private final int worldY = -400;
private final int worldWidth = 800;
private final int worldHeight = 800;
private final int worldZ = 100;
private final int worldDepth = 800;
Sphere sphere = new Sphere(worldX + Math.random() * worldWidth, worldY + Math.random() * worldHeight, worldZ + Math.random() * worldDepth, 25);
Vector3D planePoint1 = new Vector3D(worldX + Math.random() * worldWidth, worldY + Math.random() * worldHeight, worldZ + Math.random() * worldDepth);
Vector3D planePoint2 = new Vector3D(worldX + Math.random() * worldWidth, worldY + Math.random() * worldHeight, worldZ + Math.random() * worldDepth);
Vector3D planePoint3 = new Vector3D(worldX + Math.random() * worldWidth, worldY + Math.random() * worldHeight, worldZ + Math.random() * worldDepth);
private GL gl;
public SpherePlaneTest(){
canvas.addGLEventListener(this);
gl = canvas.getGL();
Animator animator = new FPSAnimator(canvas, 60);
animator.start();
JFrame frame = new JFrame("SpherePlaneTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(canvas, BorderLayout.CENTER);
frame.setSize(1000, 1000);
frame.setVisible(true);
canvas.requestFocusInWindow();
}
public void init(GLAutoDrawable drawable) {
}
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glFrustum(-1, 1, -1, 1, 1, 1000);
gl.glViewport(0, 0, width, height);
}
public void display(GLAutoDrawable drawable){
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glColor4d(1, .5, .5, .5);
gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
gl.glBegin(GL.GL_TRIANGLES);
gl.glVertex3d(planePoint1.x, planePoint1.y, -planePoint1.z);
gl.glVertex3d(planePoint2.x, planePoint2.y, -planePoint2.z);
gl.glVertex3d(planePoint3.x, planePoint3.y, -planePoint3.z);
gl.glEnd();
sphere.step();
sphere.drawSphere();
gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
gl.glColor3f(1f, 1f, 1f);
gl.glBegin(GL.GL_POLYGON);
gl.glVertex3d(worldX, worldY, -worldZ);
gl.glVertex3d(worldX+worldWidth, worldY, -worldZ);
gl.glVertex3d(worldX+worldWidth, worldY+worldHeight, -worldZ);
gl.glVertex3d(worldX, worldY+worldHeight, -worldZ);
gl.glEnd();
gl.glColor3f(1f, 1f, 1f);
gl.glBegin(GL.GL_POLYGON);
gl.glVertex3d(worldX, worldY, -(worldZ+worldDepth));
gl.glVertex3d(worldX+worldWidth, worldY, -(worldZ+worldDepth));
gl.glVertex3d(worldX+worldWidth, worldY+worldHeight, -(worldZ+worldDepth));
gl.glVertex3d(worldX, worldY+worldHeight, -(worldZ+worldDepth));
gl.glEnd();
gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
gl.glColor3f(1f, 1f, 1f);
gl.glBegin(GL.GL_POLYGON);
gl.glVertex3d(worldX, worldY, -(worldZ+worldDepth));
gl.glVertex3d(worldX, worldY, -(worldZ));
gl.glVertex3d(worldX, worldY+worldHeight, -(worldZ));
gl.glVertex3d(worldX, worldY+worldHeight, -(worldZ+worldDepth));
gl.glEnd();
gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE);
gl.glColor3f(1f, 1f, 1f);
gl.glBegin(GL.GL_POLYGON);
gl.glVertex3d(worldX+worldWidth, worldY, -(worldZ+worldDepth));
gl.glVertex3d(worldX+worldWidth, worldY, -(worldZ));
gl.glVertex3d(worldX+worldWidth, worldY+worldHeight, -(worldZ));
gl.glVertex3d(worldX+worldWidth, worldY+worldHeight, -(worldZ+worldDepth));
gl.glEnd();
}
public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
}
private class Sphere{
double maxSpeed = 5;
Vector3D delta = new Vector3D(-maxSpeed+Math.random()*(maxSpeed*2), -maxSpeed+Math.random()*(maxSpeed*2), -maxSpeed+Math.random()*(maxSpeed*2));
Vector3D centerPoint;
float radius;
public Sphere(double x, double y, double z, float radius){
centerPoint = new Vector3D(x, y, z);
this.radius = radius;
}
public void drawSphere(){
gl.glColor3d(1, 1, 1);
gl.glPointSize(radius);
gl.glBegin(GL.GL_POINTS);
gl.glVertex3d(centerPoint.x, centerPoint.y, -centerPoint.z);
gl.glEnd();
}
public void step(){
centerPoint = centerPoint.add(delta);
if(DISTANCE_TO_PLANE < RADIUS){
bounceOffPoint(POINT_CLOSEST_TO_PLANE);
}
if(centerPoint.x-radius < worldX){
bounceOffPoint(new Vector3D(centerPoint.x-radius, centerPoint.y, centerPoint.z));
centerPoint = centerPoint.add(delta);
}
else if(centerPoint.x+radius > worldX+worldWidth){
bounceOffPoint(new Vector3D(centerPoint.x+radius, centerPoint.y, centerPoint.z));
centerPoint = centerPoint.add(delta);
}
if(centerPoint.y-radius < worldY){
bounceOffPoint(new Vector3D(centerPoint.x, centerPoint.y-radius, centerPoint.z));
centerPoint = centerPoint.add(delta);
}
else if(centerPoint.y+radius > worldX+worldHeight){
bounceOffPoint(new Vector3D(centerPoint.x, centerPoint.y+radius, centerPoint.z));
centerPoint = centerPoint.add(delta);
}
if(centerPoint.z-radius < worldZ){
bounceOffPoint(new Vector3D(centerPoint.x, centerPoint.y, centerPoint.z-radius));
centerPoint = centerPoint.add(delta);
}
else if(centerPoint.z+radius > worldZ+worldDepth){
bounceOffPoint(new Vector3D(centerPoint.x, centerPoint.y, centerPoint.z+radius));
centerPoint = centerPoint.add(delta);
}
}
public void bounceOffPoint(Vector3D point) {
double deltaX = point.x - centerPoint.x;
double deltaY = point.y - centerPoint.y;
double deltaZ = point.z - centerPoint.z;
Vector3D around = new Vector3D(deltaX, deltaY, deltaZ).normalize();
delta = delta.reverse().reflect(around);
}
}
private static class Vector3D{
private final double x;
private final double y;
private final double z;
public Vector3D(double x, double y, double z){
this.x = x;
this.y = y;
this.z = z;
}
public Vector3D add(Vector3D delta) {
return new Vector3D(x+delta.x, y+delta.y, z+delta.z);
}
public Vector3D reflect(Vector3D around) {
double reflectedX = 2 * this.dotProduct(around) * around.x - this.x;
double reflectedY = 2 * this.dotProduct(around) * around.y - this.y;
double reflectedZ = 2 * this.dotProduct(around) * around.z - this.z;
return new Vector3D(reflectedX, reflectedY, reflectedZ);
}
public double dotProduct(Vector3D v2) {
return (this.x * v2.x + this.y * v2.y + this.z * v2.z);
}
public Vector3D normalize(){
double d = Math.sqrt(x * x + y * y + z * z);
if (d == 0) {
return this;
}
double normalizedX = x/d;
double normalizedY = y/d;
double normalizedZ = z/d;
return new Vector3D(normalizedX, normalizedY, normalizedZ);
}
public Vector3D reverse(){
return new Vector3D(-x, -y, -z);
}
private double distance(Vector3D other) {
return Math.sqrt((x-other.x)*(x-other.x) + (y-other.y)*(y-other.y) + (z-other.z)*(z-other.z));
}
}
public static void main(String[] args) {
new SpherePlaneTest();
}
}
The above program contains a sphere (a point) bouncing around a 3D box, as well as a 3D plane between 3 points. There is currently no interaction between the sphere and the plane, which is what I’m trying to add. Line 147 contains the basic logic I’m planning on using.
I’ve done a ton of googling on this, but I feel pretty stupid because a lot of it makes no sense to me. I’ve tried reading up on vector math, but it’s been quite some time since I’ve had linear algebra, so it seems that things other people consider obvious are pretty foreign to me. “Just use the crossproduct to get the normal and use ray tracing to calculate the intersection” might be correct, but I have yet to see a layman’s explanation of what any of that means in a real working program.
So I’m hoping somebody can show me how you might add a function that takes 4 3D points: 1 point at the center of the sphere and the other 3 defining a polygon face, and returns the closest point on that polygon face to that center point. I believe I can do the rest from there.
I also want to reiterate that the first part of this (the bouncing sphere) was homework (which I’ve finished), but the addition of the polygon intersection is outside of the bounds of the class and is mostly for my own curiosity. So nobody is going to be doing my homework for me or anything like that.
As always, I definitely appreciate any insights anybody can offer.