Physics
Physics
Apart from the methods for Collision detection , there's no real support for physics in jPCT. But you can use JBullet for example or any other physics library out there. There is a demo that shows JBullet in combination with jPCT on the demos page, but it's really a hack to make the JBullet example work with jPCT at a minimum effort. There are some sources for this posted in the forum, but beware of the hackiness: Simple JBullet port
jBullet, A simple how to guide
--.jayderyu 23:47, 18 January 2010 (UTC)
[This is a work in progress code and guide. Feel free to make changes and code corrections.]
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:
Vecmath.jar. jBullet uses java3D vecmath library. You will need this. Fortunetly it is packed with the jBullet download.
Scaling in 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.
Axis jBullet uses a YZ axis and coordinates that are more in line with OpenGL. This is important as JPCT has opposing YZ.
Linear or the term used in Bullet. This represents XYZ movement. So a Constraint that limits the Linear will prevent movement.
Multithreading or lack there of. At this time jBullet does not support multi threading though Bullet has it in the works. Do not try to support multi threading at this time. If however anyone wishes to port over Bullet multithreading feel free. I'm sure many would love to see it :)
Kinematic Bodies. There are going to be cases where you will want manual control over your Object3D/RigidBody. A RigidBody can be set to Kinematic state. This will allow for manual updates. *guilty*JPCTBulletMotionState will allow for a seemless integration, but it's not done :P Cases of Kinematic bodies would be characters, paddles and some forms of direct controlled objects.
GhostObjects. These are RigidBody's that do not collide with other Bodies. They are meant to cause Action callbacks only. The Bullet Manual(I suggest reading) suggest to use them for characters, explosions and sensors. At the time of this writing GhostObjects are in jBullet svn, not in the distributed version.
Deterministic Physics. There will be a time when you want your physics simulation to behave the same way time and time again. This is called Deterministic. This type of physics is very important for networking or cases of desired repeated results. Tutorials can be found in further reading.
For further reading I suggest [1] and [2]. If you have a simple question about jBullet your better off emailing the porter than asking of the forums. If you have complicated questions ask on the forums.
The guide
Step 1
jBullet uses a dynamic world to handle physics simulation. This is very similar to JPCT world setup. The following code below shows how to get a basic jBullet dynamic world ready. I would explain how this means, but I'm using some one else's API for the sake of time rather than learning how it works. So I honestly I can not tell you how this all works. Some of the naming conventions should give an idea though.
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. Yes it is a little heftier than getting a JPCT world ready, but it's not that bad.
Step 2 We are going to work with a sample physics test. So we are going to need some objects to collide against. At this time jBullet doesn't support Bullet static plane. So we are going to create a satic box that doesn't move that will serve as our ground. Obviously if your projects are in space 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. Shapes inform how jBullet handles collision. 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 package of information. This stores both a rotation Matrix3x3 of row major and position or translation information. 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 or rotate in the simulation. Static objects do not need a starting inertia.
DefaultMotionState myMotionState = new DefaultMotionState(groundTransform);
A 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 a graphical object.
RigidBodyConstructionInfo rbInfo = new RigidBodyConstructionInfo( mass, myMotionState, groundShape, localInertia); RigidBody body = new RigidBody(rbInfo); dynamicWorld.addRigidBody(body);
Create a parameter object to create the RigidBody. A RigidBody is the equivalent of Object3D. It's important to keep track of it some form. When ready add your RigidBody to the physics world.
Step 3
Static 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 RigidBody's :) First create a shape like above. In 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 my warning don't rip out your hair for a week *cough* like I did *cough*. Just come back to the Wiki :-) 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. So setting a value of 1+ will not cause an object to bounce forever, but it will bounce for a long time.
Friction(0.0 to 1.0) roughness of the surface. This is slide of an object. 0 is as slick as possible while 1 should present no sliding. Think of it as Black Ice to very coarse sandpaper.
Damping(0.0 to 1.0) Linear and rotation/axis damping. This is best represented as 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 without constant force being added.
body.setUserPointer(boxgfx);
You don't need this, but depending on how you end up tracking objects(by physics in this case). You can set a reference to an Object in the RigidBody(similar to Object3D.setUserObject()). That way you can call the Object later on with RigidBody.getUserPointer(). Just thought this might help.
Of course add the RigidBody when ready.
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 and it's simple. Put it before your render code. This is the basic HelloWorld sample. It uses a timer like clock and steps the simulation based on time difference.
Test Code
First 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 renderer. This runs 400 boxes decently. Feel free to implement the hardware renderer and modify some values to see how many boxes you can get going. 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; private JPCTDebugDraw iDebugDraw; 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); 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); iDebugDraw = new JPCTDebugDraw(world, buffer); iDebugDraw.setDebugMode(DebugDrawModes.DRAW_WIREFRAME); dynamicWorld.setDebugDrawer(iDebugDraw); 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(); dynamicWorld.debugDrawWorld(); //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 = 5; 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.translate(0, (strHeight+(i * 4)) * -1,(float) 50 - i/3); boxgfx.build(); boxgfx.rotateY((float) Math.PI/4f); boxgfx.rotateMesh(); boxgfx.getRotationMatrix().setIdentity(); world.addObject(boxgfx); transform = new Transform(); transform.setIdentity(); ms = new JPCTBulletMotionState(boxgfx); 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. The current version of JPCTMotionState supports Kinematic RigidBody's and RigidBody placement automatically by Graphical Object3D. Pending centerOfMass / Pivot rotation.
package jpctbullet; import javax.vecmath.Vector3f; 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; import com.bulletphysics.linearmath.MatrixUtil; public class JPCTBulletMotionState implements MotionState{ public final Transform centerOfMassOffset = new Transform(); private Object3D obj3d; public JPCTBulletMotionState(Object3D obj) { obj3d = obj; centerOfMassOffset.setIdentity(); } public JPCTBulletMotionState(Object3D obj, Transform startTrans) { obj3d = obj; setGraphicFromTransform(startTrans); centerOfMassOffset.setIdentity(); } public JPCTBulletMotionState(Object3D obj, Transform startTrans, Transform centerOfMassOffset) { obj3d = obj; setGraphicFromTransform(startTrans); this.centerOfMassOffset.set(centerOfMassOffset); } public Transform getWorldTransform(Transform worldTrans){ setTransformFromGraphic(worldTrans); return worldTrans; } public void setWorldTransform(Transform worldTrans) { setGraphicFromTransform(worldTrans); } private void setTransformFromGraphic(Transform tran) { SimpleVector p = obj3d.getTransformedCenter(); tran.origin.set(p.x, -p.y, -p.z); // not sure if translation or position Matrix matrixGfx = obj3d.getRotationMatrix(); //matrixGfx.rotateX((float)Math.PI); MatrixUtil.getOpenGLSubMatrix(tran.basis, matrixGfx.getDump()); } private void setGraphicFromTransform(Transform tran) { SimpleVector pos = obj3d.getTransformedCenter(); obj3d.translate(tran.origin.x - pos.x, (-tran.origin.y) - pos.y, (-tran.origin.z) - pos.z); float[] ma = new float[4]; float[] dump = obj3d.getRotationMatrix().getDump(); //new float[16]; Matrix matrixGfx = new Matrix(); MatrixUtil.getOpenGLSubMatrix(tran.basis, dump); matrixGfx.setDump(dump); matrixGfx.rotateX((float)Math.PI); obj3d.setRotationMatrix(matrixGfx); } }
JPCTMotionState updates RigidBody by the Object3D when getWorldTransform() is called. This means that a startTransform isn't required. You can set the Object3D position and rotation. This also means that Kinematic states are now supported.
Example:Object3D.translate(X, Y, Z); JPCTMotionState ms = new JPCTMotionState(Object3D);
Update: Changed class name to JPCTMotionState to reduce typing overhead.
Here is the beginning of implementing IDebugDraw. It is not complete at this time and may have bad graphical drawings in some cases.
package jpctbullet; import java.awt.Color; import java.awt.Graphics; import javax.vecmath.Vector3f; import com.threed.jpct.*; import com.bulletphysics.linearmath.IDebugDraw; import com.bulletphysics.linearmath.VectorUtil; public class JPCTDebugDraw extends IDebugDraw{ private int _debugMode; private World _world; private FrameBuffer _buffer; private SimpleVector Vec3fToSVec(Vector3f vector) { // JPCT uses a inverted ZY axis and coordinates so flip them return new SimpleVector(vector.x, -vector.y, -vector.z); } public JPCTDebugDraw(World w, FrameBuffer b) { _world = w; _buffer = b; } public void setDebugMode(int debugMode) { _debugMode = debugMode; } public int getDebugMode() { return _debugMode; } public void drawLine(Vector3f from, Vector3f to, Vector3f color) { if(_debugMode > 0) { Camera cam = _world.getCamera(); Graphics g = _buffer.getGraphics(); Color lastColour = g.getColor(); SimpleVector pointA = Interact2D.project3D2D(cam, _buffer, Vec3fToSVec(from)); SimpleVector pointB = Interact2D.project3D2D(cam, _buffer, Vec3fToSVec(to)); g.setColor(new Color(color.x, color.y, color.z)); // do not know why, but some times pointB is null if(pointA != null && pointB != null){ g.drawLine((int)pointA.x, (int)pointA.y, (int)pointB.x, (int)pointB.y); } g.setColor(lastColour); } } public void draw3dText(Vector3f location, String textString) { Camera cam = _world.getCamera(); Graphics g = _buffer.getGraphics(); SimpleVector loc = Interact2D.project3D2D(cam, _buffer, Vec3fToSVec(location)); g.drawString(textString, (int)loc.x, (int)loc.y); } public void drawContactPoint(Vector3f pointOnB, Vector3f normalOnB, float distance, int lifeTime, Vector3f color) { Camera cam = _world.getCamera(); Graphics g = _buffer.getGraphics(); Color lastColour = g.getColor(); Vector3f sum = new Vector3f(); VectorUtil.add(sum, pointOnB, normalOnB); SimpleVector pointA = Interact2D.project3D2D(cam, _buffer, Vec3fToSVec(pointOnB)); //SimpleVector pointB = Interact2D.project3D2D(cam, _buffer, Vec3fToSVec(normalOnB)); SimpleVector to = new SimpleVector(sum.x*distance, sum.y*distance, sum.z*distance); g.setColor(new Color(color.x, color.y, color.z)); g.drawLine((int)pointA.x, (int)pointA.y, (int)to.x, (int)to.y); g.setColor(lastColour); draw3dText(pointOnB, "" + lifeTime); } public void reportErrorWarning(String warningString) { System.out.println(warningString); } }
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. [3]http://sre.hopto.org/share/jBulletTest.zip]
Update: jBulletTest.zip has been updates for source code changes and the addition of the IDebugDraw. It also has a new compiled version of jBullet from the SVN as of January 21 2010.