Hi fine folks of the JPCT boards. I sat back down working on a new project using jBullet physics. So I thought for those of you who are not sure how to get jBullet working. I present this basic guide.
[This is a work in progress code and guide]
Since you have decided to use jBullet I suggest going here
http://jbullet.advel.cz/ first. Download the latest version. Integrating straight forward raw physics is easy and won't cause any kind of hair pulling
at least I've already dealt with part of that hair pulling for you
Things to know:
jBullet uses java3D vecmath library. You will need this. Fortunetly it is packed with the jBullet download.
jBullet unit scale by default is a 1:1 meter. Though feel free to scale it as 1 Unit as needed. For safety issues don't create objects less than 0.01. Really big objects also tend to start causing simulation problems if landing on top of smaller objects. IIRC up to 10 is safe.
jBullet uses a Y Z axis and coordinates that are more in line with OGL. This is important as JPCT has opposing YZ.
Step 1jBullet uses a dynamic world to handle simulation. Very similar to JPCT world setup. The following code below shows how to get a basic jBullet world. I would explain how this means, but i'm using some one else's API for the sake of time rather that learning how it works. So honestly I can not tell you how it all works.
collisionConfiguration = new DefaultCollisionConfiguration();
dispatcher = new CollisionDispatcher(collisionConfiguration);
Vector3f worldAabbMin = new Vector3f(-10000,-10000,-10000);
Vector3f worldAabbMax = new Vector3f(10000,10000,10000);
AxisSweep3 overlappingPairCache = new AxisSweep3(worldAabbMin, worldAabbMax);
SequentialImpulseConstraintSolver solver = new SequentialImpulseConstraintSolver();
dynamicWorld = new DiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
dynamicWorld.setGravity(new Vector3f(0,-10,0));
dynamicWorld.getDispatchInfo().allowedCcdPenetration = 0f;
That's it. It's a little heftier than getting a JPCT world going, but it's not that bad.
Step 2This is a sample physics test. So we are going to need some objects to collide against. jBullet doesn't support a generic static plane at this time. So we are going to create a box that doesn't move. Obviously if you in a space ship you won't need this
CollisionShape groundShape = new BoxShape(new Vector3f(100.f, 50.f, 100.f));
Transform groundTransform = new Transform();
groundTransform.setIdentity();
groundTransform.origin.set(new Vector3f(0.f, -56.f, 0.f));
float mass = 0f;
Vector3f localInertia = new Vector3f(0, 0, 0);
DefaultMotionState myMotionState = new DefaultMotionState(groundTransform);
RigidBodyConstructionInfo rbInfo = new RigidBodyConstructionInfo(
mass, myMotionState, groundShape, localInertia);
RigidBody body = new RigidBody(rbInfo);
dynamicWorld.addRigidBody(body);
Time to start explaining what's going on. Some parts are straight forward other parts not so much.
CollisionShape groundShape = new BoxShape(new Vector3f(100.f, 50.f, 100.f));
Similar to JPCT Box. This defines a simple collision shape. There are spheres and trimesh support if needed.
Transform groundTransform = new Transform();
groundTransform.setIdentity();
groundTransform.origin.set(new Vector3f(0.f, -56.f, 0.f));
A Bullet Transform is a Body package of information. It stores both the rotation Matrix and position or translation.
Transform.origin(Vector3f) is where you will find position.
Transform.basis(Matrix3f) is where you will find the bodies rotation.
When a new Transform is created make sure .setIdentity() is run to fill in 0 values.
float mass = 0f;
Vector3f localInertia = new Vector3f(0, 0, 0);
Make note of this. A mass of 0 means that the object is static. Static objects will not move in the simulation. Static objects do not need a starting inertia.
DefaultMotionState myMotionState = new DefaultMotionState(groundTransform);
This(MotionState) is a key component to integrating physics to your graphical world. The DefaultMotionState has methods you can call on to manually get the data for graphical updates.
DefaultMotionState.graphicsWorldTransform.origin
DefaultMotionState.graphicsWorldTransform.basis
For our purposes though we are using DefaultMotionState for a static object since we don't need to update it's position.
RigidBodyConstructionInfo rbInfo = new RigidBodyConstructionInfo(
mass, myMotionState, groundShape, localInertia);
RigidBody body = new RigidBody(rbInfo);
dynamicWorld.addRigidBody(body);
Not much to say here. Create a parameter object to create the RigidBody. RigidBody is the equivalent of Object3D. It's important to keep track of it some way. Then finally add a body to the physics world.
Step 3Static objects are no fun to watch. They don't do anything. This step which is second to last is where coding get's more fun
BoxShape shape = new BoxShape(new Vector3f(2,2,2));
Vector3f localInertia = new Vector3f(0,0,0);
shape.calculateLocalInertia(mass, localInertia);
startTransform = new Transform();
startTransform.setIdentity();
startTransform.origin.set(0, 0, 0);
ms = new JPCTBulletMotionState(yourObject3D, startTransform);
rbInfo = new RigidBodyConstructionInfo(mass, ms, shape, localInertia);
body = new RigidBody(rbInfo);
body.setRestitution(0.1f);
body.setFriction(0.50f);
body.setDamping(0f, 0f);
body.setUserPointer(boxgfx);
dynamicWorld.addRigidBody(body);
Theres the starting code for your cool fun Bodies
First create a shape. Our sample we have a 2x2x2 Cube. Makes using JPCT Primitives easier.
Vector3f localInertia = new Vector3f(0,0,0);
shape.calculateLocalInertia(mass, localInertia);
You don't need a separate inertia variable, but I left it in so you know the parameter arguments. The second line is
VERY IMPORTANT Shape.calculateLocalInertia(float, Vector3f) MUST be called for your physics simulation to work properly. If you miss this after this warning don't rip out your hair for a week*cough* like I did *cough*
Create a new transform and set starting position and or rotation.
ms = new JPCTBulletMotionState(yourObject3D, startTransform);
Here is the magic where jBullet and JPCT will work together. You can find the code at the bottom(with warnings).
body.setRestitution(0.1f);
body.setFriction(0.50f);
body.setDamping(0f, 0f);
Fairly basic representation.
Resitution(0.0 to 1.0) represent a value of energy return on collision. Bullet doesn't naturally support infinite return.
Friction(0.0 to 1.0) well roughness. How much sliding. 0 is as slick as possible while 1 should present no sliding.
Damping(0.0 to 1.0) Linear damping and rotation damping. This represents atmosphere. Space has no atmosphere an thus objects will move and rotate forever. While water would be very dense and limit movement and rotation very fast.
body.setUserPointer(boxgfx);
You don't need this, but depending on you track objects(by physics in this case). You can set a reference to a Object in the body. That way you can call the Object later on with RigidBody.getUserPointer(). Just thought this might help.
Then finally add the body.
Step 4.Ok you have a static body to check collisions against. You have dynamic RigidBody's that can be watched(I know I didn't show graphic code). Well like JPCT some extra code to move the simulation forward is required.
float ms = clock.getTimeMicroseconds();
clock.reset();
dynamicWorld.stepSimulation(ms / 1000000f);
Here it is. It's simple, add it just before your render code. Don't thread it. jBullet has not implemented multi threads yet. This is the basic HelloWorld sample. It uses a timer like clock and steps the simulation based on time difference. There are advanced topics on deterministic physics else where, but this is a good start. Knowing how much time has past since last update.
Test CodeFirst the basic demo code. This demo code is super basic and was written to run on a AMD 2.6mhz dual core running the software rendered. If your running on less it will run slower. If it's a big different drop the numBoxes value near the top.
package jpctbullet;
import com.threed.jpct.*;
import com.threed.jpct.util.Light;
import java.util.Vector;
import java.util.List;
import java.util.ArrayList;
import javax.vecmath.Vector3f;
import javax.swing.*;
import java.awt.*;
import javax.vecmath.Matrix3f;
import javax.vecmath.Matrix4f;
import com.bulletphysics.BulletGlobals;
import com.bulletphysics.collision.broadphase.AxisSweep3;
import com.bulletphysics.collision.dispatch.CollisionObject;
import com.bulletphysics.collision.dispatch.CollisionWorld;
import com.bulletphysics.collision.shapes.BoxShape;
import com.bulletphysics.dynamics.DynamicsWorld;
import com.bulletphysics.dynamics.constraintsolver.Point2PointConstraint;
import com.bulletphysics.dynamics.constraintsolver.TypedConstraint;
import com.bulletphysics.linearmath.*;
import com.bulletphysics.collision.broadphase.BroadphaseInterface;
import com.bulletphysics.collision.broadphase.SimpleBroadphase;
import com.bulletphysics.collision.dispatch.CollisionDispatcher;
import com.bulletphysics.collision.dispatch.DefaultCollisionConfiguration;
import com.bulletphysics.collision.shapes.BoxShape;
import com.bulletphysics.collision.shapes.CollisionShape;
import com.bulletphysics.collision.shapes.StaticPlaneShape;
import com.bulletphysics.dynamics.DiscreteDynamicsWorld;
import com.bulletphysics.dynamics.RigidBody;
import com.bulletphysics.dynamics.RigidBodyConstructionInfo;
import com.bulletphysics.dynamics.constraintsolver.ConstraintSolver;
import com.bulletphysics.dynamics.constraintsolver.SequentialImpulseConstraintSolver;
public class BulletTest {
public int numBoxes = 400;
public int strHeight= 10;
private World world;
private FrameBuffer buffer;
private Object3D box; // only something for the camera to focus on.
private JFrame frame;
public DiscreteDynamicsWorld dynamicWorld;
public int maxSubSteps;
public float timeStep, fixedTimeStep;
protected Clock clock = new Clock();
private List<CollisionShape> collisionShapes = new ArrayList<CollisionShape>();
private BroadphaseInterface overlappingPairCache;
private CollisionDispatcher dispatcher;
private ConstraintSolver solver;
private DefaultCollisionConfiguration collisionConfiguration;
private Vector<RigidBody> boxList;
public static void main(String[] args) throws Exception {
new BulletTest().loop();
}
public BulletTest() throws Exception {
frame=new JFrame("JPCTBullet Test");
frame.setSize(800, 600);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
boxList = new Vector();
int cpu = Runtime.getRuntime().availableProcessors();
if(cpu > 1){
Config.useMultipleThreads = true;
Config.maxNumberOfCores = cpu;
Config.loadBalancingStrategy = 1;
}
world = new World();
//World.setDefaultThread( Thread.currentThread() );
world.setAmbientLight(120, 120, 120);
TextureManager.getInstance().addTexture("box", new Texture("box.jpg"));
box = Primitives.getBox(1f, 1f);
box.translate(0, -50, 0);
box.setTexture("box");
box.setEnvmapped(Object3D.ENVMAP_ENABLED);
box.build();
world.addObject(box);
Object3D ground = Primitives.getPlane(4,25);
ground.setTexture("box");
ground.setEnvmapped(Object3D.ENVMAP_ENABLED);
ground.rotateX((float)-Math.PI);
ground.build();
world.addObject(ground);
world.getCamera().setPosition(150, -50, -5);
world.getCamera().lookAt(box.getTransformedCenter());
Light light = new Light(world);
light.setPosition(new SimpleVector(-200, -50 , 80));
light.setIntensity(150,140,150);
collisionConfiguration = new DefaultCollisionConfiguration();
dispatcher = new CollisionDispatcher(collisionConfiguration);
Vector3f worldAabbMin = new Vector3f(-10000,-10000,-10000);
Vector3f worldAabbMax = new Vector3f(10000,10000,10000);
AxisSweep3 overlappingPairCache = new AxisSweep3(worldAabbMin, worldAabbMax);
//SimpleBroadphase overlappingPairCache = new SimpleBroadphase(1026);
SequentialImpulseConstraintSolver solver = new SequentialImpulseConstraintSolver();
dynamicWorld = new DiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
dynamicWorld.setGravity(new Vector3f(0,-10,0));
dynamicWorld.getDispatchInfo().allowedCcdPenetration = 0f;
CollisionShape groundShape = new BoxShape(new Vector3f(100.f, 50.f, 100.f));
Transform groundTransform = new Transform();
groundTransform.setIdentity();
groundTransform.origin.set(new Vector3f(0.f, -56.f, 0.f));
float mass = 0f;
Vector3f localInertia = new Vector3f(0, 0, 0);
DefaultMotionState myMotionState = new DefaultMotionState(groundTransform);
RigidBodyConstructionInfo rbInfo = new RigidBodyConstructionInfo(
mass, myMotionState, groundShape, localInertia);
RigidBody body = new RigidBody(rbInfo);
dynamicWorld.addRigidBody(body);
dynamicWorld.clearForces();
initTestObects();
}
private void loop() throws Exception {
buffer = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_NORMAL);
//Canvas canvas=buffer.enableGLCanvasRenderer();
//buffer.disableRenderer(IRenderer.RENDERER_SOFTWARE);
//frame.add(canvas);
while (frame.isShowing()) {
float ms = clock.getTimeMicroseconds();
clock.reset();
dynamicWorld.stepSimulation(ms / 1000000f);
box.rotateY(0.01f); // it's rotating because it looks neater :P
buffer.clear(java.awt.Color.BLUE);
world.renderScene(buffer);
world.draw(buffer);
buffer.update();
//buffer.displayGLOnly();
//canvas.repaint();
buffer.display(frame.getGraphics());
//Thread.sleep(10);
}
buffer.disableRenderer(IRenderer.RENDERER_OPENGL);
buffer.dispose();
frame.dispose();
System.exit(0);
}
public void initTestObects(){
Transform transform;
Object3D boxgfx;
BoxShape shape = new BoxShape(new Vector3f(2,2,2));
JPCTBulletMotionState ms;
float mass = 2;
//MotionState ms;
Vector3f localInertia = new Vector3f(0,0,0);
shape.calculateLocalInertia(mass, localInertia);
RigidBodyConstructionInfo rbInfo;
RigidBody body;
for(int i = 1; i < numBoxes; i++){
boxgfx = Primitives.getCube(2);
boxgfx.setTexture("box");
boxgfx.setEnvmapped(Object3D.ENVMAP_ENABLED);
boxgfx.build();
world.addObject(boxgfx);
transform = new Transform();
transform.setIdentity();
//transform.origin.set(0, strHeight+(i * 4),(float) (-(numBoxes / 3.3)) + i/2);
transform.origin.set(0, strHeight+(i * 4),(float) -50 + i/3);
//ms = new DefaultMotionState(transform);
ms = new JPCTBulletMotionState(boxgfx, transform);
rbInfo = new RigidBodyConstructionInfo(mass, ms, shape, localInertia);
body = new RigidBody(rbInfo);
body.setRestitution(0.1f);
body.setFriction(0.50f);
body.setDamping(0f, 0f);
body.setUserPointer(boxgfx);
boxList.add(body);
dynamicWorld.addRigidBody(body);
} // end for loop
}
}
Heres is the magic code. I must warn that this code is still in work of progress. There is a problem with the rotation. You will see the problem in execution.
package jpctbullet;
import javax.vecmath.Vector3f;
import javax.vecmath.Quat4f;
import javax.vecmath.Matrix3f;
import javax.vecmath.Matrix4f;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Object3D;
import com.threed.jpct.Matrix;
import com.bulletphysics.linearmath.MotionState;
import com.bulletphysics.linearmath.Transform;
public class JPCTBulletMotionState implements MotionState{
public final Transform graphicsWorldTrans = new Transform();
public final Transform centerOfMassOffset = new Transform();
public final Transform startWorldTrans = new Transform();
private Object3D obj3d;
public JPCTBulletMotionState(Object3D obj)
{
obj3d = obj;
graphicsWorldTrans.setIdentity();
centerOfMassOffset.setIdentity();
startWorldTrans.setIdentity();
}
public JPCTBulletMotionState(Object3D obj, Transform startTrans)
{
obj3d = obj;
this.graphicsWorldTrans.set(startTrans);
centerOfMassOffset.setIdentity();
this.startWorldTrans.set(startTrans);
}
public JPCTBulletMotionState(Object3D obj, Transform startTrans, Transform centerOfMassOffset)
{
obj3d = obj;
this.graphicsWorldTrans.set(startTrans);
this.centerOfMassOffset.set(centerOfMassOffset);
this.startWorldTrans.set(startTrans);
}
//public void getWorldTransform(Transform worldTrans){
public Transform getWorldTransform(Transform worldTrans){
//worldTrans.set(graphicsWorldTrans);
//worldTrans.inverse(centerOfMassOffset);
//worldTrans.set(centerOfMassOffset);
//worldTrans.mul(graphicsWorldTrans);
worldTrans.inverse(centerOfMassOffset);
worldTrans.mul(graphicsWorldTrans);
//Matrix4f matrix4 = new Matrix4f(obj3d.getRotationMatrix().getDump());
//matrix4.rotX((float)Math.PI);
//matrix4.getRotationScale(worldTrans.basis);// want to place matrix in argument
return worldTrans;
}
public void setWorldTransform(Transform worldTrans)
{
SimpleVector pos = obj3d.getTransformedCenter();
obj3d.translate(worldTrans.origin.x - pos.x,
(-worldTrans.origin.y) - pos.y,
(-worldTrans.origin.z) - pos.z);
// following rotation may or maynot work. no working rotation to
// find out and fix.
float[] ma = new float[4];
float[] dump = new float[16]; //obj3d.getRotationMatrix().getDump();
Matrix4f matrix4 = new Matrix4f();
Matrix matrixGfx = new Matrix();
matrix4.set(worldTrans.basis); // want to set current matrix by arg
matrix4.getRow(0, ma);
dump[0] = ma[0]; dump[1] = ma[1]; dump[2] = ma[2]; dump[3] = ma[3];
matrix4.getRow(1, ma);
dump[4] = ma[0]; dump[5] = ma[1]; dump[6] = ma[2]; dump[7] = ma[3];
matrix4.getRow(2, ma);
dump[8] = ma[0]; dump[9] = ma[1]; dump[10] = ma[2]; dump[11] = ma[3];
matrix4.getRow(3, ma);
dump[12] = ma[0]; dump[13] = ma[1]; dump[14] = ma[2]; dump[15] = ma[3];
/*
Matrix3f matrix3 = worldTrans.basis;
matrix3.normalize();
matrix3.getRow(0, ma);
dump[0] = ma[0]; dump[1] = ma[1]; dump[2] = ma[2];// dump[3] = ma[3];
matrix3.getRow(1, ma);
dump[4] = ma[0]; dump[5] = ma[1]; dump[6] = ma[2];// dump[7] = ma[3];
matrix3.getRow(2, ma);
dump[8] = ma[0]; dump[9] = ma[1]; dump[10] = ma[2];// dump[11] = ma[3];
*/
matrixGfx.setDump(dump);
matrixGfx.rotateX((float)Math.PI);
obj3d.setRotationMatrix(matrixGfx);
}
}
Here is the source. I have limited bandwidth so don't download to much please
Also server sometimes goes down. So just be patient and it will be up. Usually less than a day.
http://sre.hopto.org/share/jBulletTest.zip