jBullet - Performance killer or unfortunate one-time problem?

When fiddling around with jME today, I was a little surprised how much of an impact jBullet had when it comes to FPS.



Using the scenario from HelloTerrain, I added 4 stacks of 5 Otos each (Same x and z coordinates within one stack, differing y-wise)

No problem. Despite the large terrain and two dozen Otos it still ran at 200-300 FPS.



Then I put a PhysicsNode around each of those Oto’s…

As a result, the game only had 1-2 FPS (This was true no matter whether the Oto’s were actually falling down or already on the ground)

I know that Oto has quite some triangles, but such a drastic decrease in performance is pretty incredible.



So what’s the trick when trying to keep your FPS high and at the same time using jBullet? :slight_smile:

Anything else you can do besides using low poly models?

Will low poly models even lead to a major performance boost?

Did you give the OTO’s a GImpact collision shape? Mesh-accurate dynamic physics is simply too much for your CPU. No game ever does that and if then only in very limited areas. You should “model” a close match to your original shape from basic collision shapes (box, sphere, cylinder) and combine them as a CompoundCollisionShape. Using a very basic low-poly representation of your model as a GImpact shape might also work, but it will still use more CPU. Otherwise, do you have assertions enabled? ("-ea" switch in the run properties) Assertions slow down physics a lot.



Cheers,

Normen



P.S. The “PhysicsCharacterNode” uses a very simple physics simulation and is using less cpu if you really want many physics-enabled entities.

What I gave them in my first test was a CapsuleCollisionShape - after assuming this might actually be a rather complex one I tried out a box shape (No noticeable improvement in performance)



Assertions were not activated. (Unless they are by default in the jME SDK)

I was running the game out of the jME SDK rather than using a generated .jar, but I guess that shouldn’t have too much of a performance impact.



Oh, and it was PhysicsCharacterNodes I was using.



No idea if it was simple the number of Nodes that were causing the slowdown or their positioning.

'Think I’ll recreate the scenario this evening and place them all apart from each other (x/z-coordinates) to see whether or not it will have any impact on game performance.

Well when I have 3k boxes dloating around and ccolliding, I reach the end of my cpu (4800x2 amd)

I ned 50ms per tick then.



Is the terrain using a meshshape?

I’d be happy with a couple hundred, rarely colliding objects, so slowdowns at 3k boxes shouldn’t be a problem for this project :slight_smile:



Not really sure what the Terrain is using - what is it the TerrainPhysicsShapeFactory creates? xD

My two lines for the terrain physics are:

TerrainPhysicsShapeFactory factory = new TerrainPhysicsShapeFactory();

terrainPhysicsNode = factory.createPhysicsMesh(terrain);



Is there a more efficient way of creating terrain physics? I really like the accuracy of this method, so if there are any alternatives I’d preferably hear about those that won’t make my models hover in the air or move through the ground from time to time :wink:



And thanks for your help you two, really appreciated! :slight_smile:



PS:

While I mostly created this thread out of curiosity, there is another related question I have:

The main part I use physics for is to make sure my Nodes don’t fall through or hover above the ground - collision with each other or impassable terrain can most likely be prevented by pathfinding and occasional collidesWith-checks.

Is there any other easy way to do this aside from a Physics-Engine? My self-made interpolation-code for terrain height calculation is unfortunately pretty inaccurate in some areas, thus simply setting the y-coordinate to the height won’t do :frowning:

Terrain uses a heightmap for physics collision as well. Its very efficient.

Grins Any hints on what Class / Method to look at to get a look at the HeightMap calculation physics uses?



Well, alright, but that means I really have no idea what is causing the slowdown :confused:

I can upload the executable (or source) if anyone wants to take a look at it, but the situation I described was definitely at 1-2 FPS (tested on 2 machines, both of them not too old)



Any other ideas on what could be causing it, or at what I should take a closer look at to figure it out? (E.g. number of triangles or whatnot)

Well if normally only massiv eamount is needed when complex shapes collide, or you have stucking ones.

As soon as they are far enough apart to not being inside the others oriente bounding box, they shouldn’t need much cpu

The nodes should not use much CPU when they dont move. You can also lower the size of your physics space (do before attaching BulletAppState) and enable parallel multithreading to improve performance. But still the performance you get is a bit low, maybe theres something else you do? Can you post your test case?

I’ll recreate it this evening (in like 4-5 hours), reduce it to the essential parts and post it here! :slight_smile:



If characters being in each others bounding boxes create much of a slowdown, that might be it.

Due to my stacking, all 20 Otos had their bounding boxes colliding with those of 1-2 other ones each.

But I actually think it was slow even before they fell down and their bounding boxes began overlapping, so not 100% sure that’s it in this case.



I’ll keep you up to date.

Thanks again you two! :slight_smile:



PS:

Didn’t even know parallel multithreading was supported - nice! :slight_smile:

Alrighty, rebuilt it as good as possible.

Some facts: You were right: It only slows down once all the Otos stand close to each other!

In the initial few seconds when they fall down the speed is bearable (~20FPS)

And CapsuleCollisionShape and BoxCillisionShape does make quite the difference after all - While Capsule gets slower and slower and eventually stops doing anything, with Box it stays at ~5 FPS

But there was another disturbing difference… when using BoxCollisionShapes, some Otos managed to push those they fell on through the ground!

(As soon as they are deleted the FPS of course increase again)



But by now I’m also certain it’s not the stacking - I just put all Otos apart from each other and even though using BoxCollisionShapes it went down to 2 FPS! (And one of em fell through the ground again :/)

You folks think there’s anything that can be done against those slowdowns? :frowning:



But oh well, here’s the code (removed most of the irrelevant stuff):

Main Class

[java]public class SimpleCamTester extends SimpleBulletApplication {



private TerrainQuad terrain;



private Material mat_terrain;

private StrategyCamera scam;

private PhysiclessHeightmap heightmap;



public static void main(String[] args) {

SimpleCamTester app = new SimpleCamTester();

app.start();

}



@Override

public void simpleInitApp() {



createCamera();

initMaterials();

createTerrain();

createSetting();

initStrategyElements();



createCreature(-146.29f, 50, -56.55f);

createCreature(-159.92209f, 50, -53.94384f);

createCreature(-173f, 50, -50.94384f);

createCreature(-133.92209f, 50, -59.94384f);



createCreature(-146.29f, 150, -56.55f);

createCreature(-159.92209f, 150, -53.94384f);

createCreature(-173f, 150, -50.94384f);

createCreature(-133.92209f, 150, -59.94384f);



createCreature(-146.29f, 250, -56.55f);

createCreature(-159.92209f, 250, -53.94384f);

createCreature(-173f, 250, -50.94384f);

createCreature(-133.92209f, 250, -59.94384f);



createCreature(-146.29f, 350, -56.55f);

createCreature(-159.92209f, 350, -53.94384f);

createCreature(-173f, 350, -50.94384f);

createCreature(-133.92209f, 350, -59.94384f);



createCreature(-146.29f, 450, -56.55f);

createCreature(-159.92209f, 450, -53.94384f);

createCreature(-173f, 450, -50.94384f);

createCreature(-133.92209f, 450, -59.94384f);



}



private void createCamera(){

flyCam.setEnabled(false);

scam = new StrategyCamera(getCamera(),mouseInput,settings.getWidth(), settings.getHeight());

scam.setMoveSpeed(100);

scam.registerWithInput(inputManager);

getCamera().setLocation(new Vector3f(-141.60059f, 49.80316f, -28.36869f));

getCamera().setDirection(new Vector3f(-0.24119106f, -0.41835046f, -0.87567675f));

}



private void initMaterials(){

/** 1. Create terrain material and load four textures into it. /

mat_terrain = new Material(assetManager,

“Common/MatDefs/Terrain/Terrain.j3md”);



/
* 1.1) Add ALPHA map (for red-blue-green coded splat textures) /

mat_terrain.setTexture(“m_Alpha”,

assetManager.loadTexture(“Textures/Terrain/splat/alphamap.png”));



/
* 1.2) Add GRASS texture into the red layer (m_Tex1). /

Texture grass =

assetManager.loadTexture(“Textures/Terrain/splat/grass.jpg”);

grass.setWrap(WrapMode.Repeat);

mat_terrain.setTexture(“m_Tex1”, grass);

mat_terrain.setFloat(“m_Tex1Scale”, 64f);



/
* 1.3) Add DIRT texture into the green layer (m_Tex2) /

Texture dirt = assetManager.loadTexture(“Textures/Terrain/splat/dirt.jpg”);

dirt.setWrap(WrapMode.Repeat);

mat_terrain.setTexture(“m_Tex2”, dirt);

mat_terrain.setFloat(“m_Tex2Scale”, 32f);



/
* 1.4) Add ROAD texture into the blue layer (m_Tex3) */

Texture rock = assetManager.loadTexture(“Textures/Terrain/splat/road.jpg”);

rock.setWrap(WrapMode.Repeat);

mat_terrain.setTexture(“m_Tex3”, rock);

mat_terrain.setFloat(“m_Tex3Scale”, 128f);



}



private void createCreature(float x, float y, float z) {

Unit u = new Unit();

PhysicsCharacterNode character = u.getUnitNode();

rootNode.attachChild(character);

getPhysicsSpace().add(character);

u.move(x, y, z);

}



private void initStrategyElements()

{

SharedUI.setSharedResources(assetManager, audioRenderer, heightmap);

}



private void createTerrain(){



heightmap = null;

Texture heightMapImage =

assetManager.loadTexture(“Textures/Terrain/splat/mountains512.png”);

heightmap = new PhysiclessHeightmap(

ImageToAwt.convert(heightMapImage.getImage(), false, true, 0));

heightmap.load();



terrain = new TerrainQuad(“terrain”, 65, 513, heightmap.getHeightMap());

List cameras = new ArrayList();

cameras.add(getCamera());

TerrainLodControl control = new TerrainLodControl(terrain, cameras);

terrain.addControl(control);

terrain.setMaterial(mat_terrain);

terrain.setModelBound(new BoundingBox());

terrain.updateModelBound();

terrain.setLocalScale(new Vector3f(2, 1f, 2));

rootNode.attachChild(terrain);



TerrainPhysicsShapeFactory factory = new TerrainPhysicsShapeFactory();

Node terrainPhysicsNode = factory.createPhysicsMesh(terrain);

terrainPhysicsNode.attachChild(terrain);

rootNode.attachChild(terrainPhysicsNode);

getPhysicsSpace().add(terrainPhysicsNode);

}



private void createSetting(){

DirectionalLight dl = new DirectionalLight();

dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalizeLocal());

rootNode.addLight(dl);

}



}[/java]



and the Unit class:

[java]public class Unit extends SharedUI

{



private Node character;



private PhysicsCharacterNode physics;



public void move(float x, float y, float z){

physics.setLocalTranslation(x, y, z);

}



public Unit(){

initializeOgre();

}



private void initializeOgre(){

character = (Node) assetManager.loadModel(“Models/Oto/Oto.mesh.j3o”);

character.setLocalScale(0.5f);



BoxCollisionShape box = new BoxCollisionShape(new Vector3f(1.6270885f, 2.4733696f, 0.67756826f));



physics= new PhysicsCharacterNode(box, 0.01f);

physics.attachChild(character);



}



public PhysicsCharacterNode getUnitNode(){

return physics;

}



}[/java]

Well two ideas:

1.) Your computer is really crap (or a netbook , similar)

2.) Are you using -Xmx1024m as a Vm parameter? It might be that the garbagecollector is doing far to much work to clean up stuff. (Or you’r out of ram similar)



Btw ybout your testcase:



I cant import:

PhysiclessHeightmap

SharedUI

StrategyCamera



Thus i cannot test it myself

Well, the two computers I tested it on are not the best machines around but no, not crappy either. (Desktop PCs, no notebooks)

CPU-wise it’s a Core2Quad @ ~2Ghz and a Core2Duo @ ~1.8Ghz

Definitely not out of Ram (2000/600 MB free)

Tried it as well without any Vm-parameters as with a variety of -Xmx ones





And sorry, didn’t think anyone would actually want to try it out xD

You can replace PhysiclessHeightmap with ImageBasedHeightMap, must have forgotten to do so before posting here, sorry :frowning:



Furthermore if you want the full performance impact, replace the following line in Unit.java:

BoxCollisionShape box = new BoxCollisionShape(new Vector3f(1.6270885f, 2.4733696f, 0.67756826f));

with:

CapsuleCollisionShape box = new CapsuleCollisionShape(1.5f, 2f);





The two missing classes are:



SharedUI:

[java]public class SharedUI {



protected static AssetManager assetManager;

protected static AudioRenderer audioRenderer;

protected static PhysiclessHeightmap height;



public static void setSharedResources(AssetManager ass, AudioRenderer aud, PhysiclessHeightmap phys){

assetManager = ass;

audioRenderer = aud;

height = phys;

}





}[/java]



StrategyCamera:

[java]import com.jme3.collision.MotionAllowedListener;

import com.jme3.input.FlyByCamera;

import com.jme3.input.InputManager;

import com.jme3.input.KeyInput;

import com.jme3.input.MouseInput;

import com.jme3.input.RawInputListener;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.AnalogListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.input.controls.MouseAxisTrigger;

import com.jme3.input.controls.MouseButtonTrigger;

import com.jme3.input.event.JoyAxisEvent;

import com.jme3.input.event.JoyButtonEvent;

import com.jme3.input.event.KeyInputEvent;

import com.jme3.input.event.MouseButtonEvent;

import com.jme3.input.event.MouseMotionEvent;

import com.jme3.math.FastMath;

import com.jme3.math.Matrix3f;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector3f;

import com.jme3.renderer.Camera;



/**

  • A first person view camera controller.
  • After creation, you must register the camera controller with the
  • dispatcher using #registerWithDispatcher().

    *
  • Controls:
    • Move the mouse to rotate the camera
    • Mouse wheel for zooming in or out
    • WASD keys for moving forward/backward and strafing
    • QZ keys raise or lower the camera

      */

      public class StrategyCamera implements AnalogListener, ActionListener {



      protected Camera cam;

      protected Vector3f initialUpVec;

      protected float rotationSpeed = 1f;

      protected float moveSpeed = 3f;

      protected float zoomSpeed = 3f;

      protected MotionAllowedListener motionAllowed = null;

      protected boolean enabled = true;

      protected boolean canRotate = false;

      protected InputManager inputManager;

      private int mouseX, mouseY;

      private int maxX, maxY;

      private int strafeupdown = 0; // 1 = up, 2 = down

      private int strafeleftright = 0;



      /**
  • Creates a new FlyByCamera to control the given Camera object.
  • @param cam

    */

    public StrategyCamera(Camera cam, MouseInput mouseInput, int screenWidth, int screenHeight){

    this.cam = cam;

    initialUpVec = cam.getUp().clone();

    maxX = screenWidth-1;

    maxY = screenHeight-1;

    }



    public int getMouseX(){

    return mouseX;

    }



    public int getMouseY(){

    return mouseY;

    }



    public void setMotionAllowedListener(MotionAllowedListener listener){

    this.motionAllowed = listener;

    }



    /**
  • Sets the move speed. The speed is given in world units per second.
  • @param moveSpeed

    */

    public void setMoveSpeed(float moveSpeed){

    this.moveSpeed = moveSpeed;

    }



    /**
  • Sets the rotation speed.
  • @param rotationSpeed

    */

    public void setRotationSpeed(float rotationSpeed){

    this.rotationSpeed = rotationSpeed;

    }



    /**
  • @param enable If false, the camera will ignore input.

    */

    public void setEnabled(boolean enable){

    if (enabled && !enable){

    if (canRotate){

    inputManager.setCursorVisible(true);

    }

    }

    enabled = enable;

    }



    /**
  • @return If enabled
  • @see FlyByCamera#setEnabled(boolean)

    */

    public boolean isEnabled(){

    return enabled;

    }





    /**
  • Registers the FlyByCamera to receive input events from the provided
  • Dispatcher.
  • @param dispacher

    */

    public void registerWithInput(InputManager inputManager){

    this.inputManager = inputManager;



    String[] mappings = new String[]{

    “STRATCAM_StrafeLeft”,

    “STRATCAM_StrafeRight”,

    “STRATCAM_Forward”,

    “STRATCAM_Backward”,

    “STRATCAM_MouseLeft”,

    “STRATCAM_MouseRight”,

    “STRATCAM_MouseForward”,

    “STRATCAM_MouseBackward”,



    “STRATCAM_ZoomIn”,

    “STRATCAM_ZoomOut”,

    “STRATCAM_RotateDrag”,

    };



    // both mouse and button - rotation of cam

    inputManager.addMapping(“STRATCAM_StrafeLeft”,

    new KeyTrigger(KeyInput.KEY_A));



    inputManager.addMapping(“STRATCAM_StrafeRight”,

    new KeyTrigger(KeyInput.KEY_D));

    inputManager.addMapping(“STRATCAM_Forward”,

    new KeyTrigger(KeyInput.KEY_W));



    inputManager.addMapping(“STRATCAM_Backward”,

    new KeyTrigger(KeyInput.KEY_S));

    inputManager.addMapping(“STRATCAM_MouseLeft”, new MouseAxisTrigger(0, true));

    inputManager.addMapping(“STRATCAM_MouseRight”, new MouseAxisTrigger(0, false));

    inputManager.addMapping(“STRATCAM_MouseForward”, new MouseAxisTrigger(1, false));

    inputManager.addMapping(“STRATCAM_MouseBackward”, new MouseAxisTrigger(1, true));



    // mouse only - zoom in/out with wheel, and rotate drag

    inputManager.addMapping(“STRATCAM_ZoomIn”, new MouseAxisTrigger(2, false));

    inputManager.addMapping(“STRATCAM_ZoomOut”, new MouseAxisTrigger(2, true));

    inputManager.addMapping(“STRATCAM_RotateDrag”, new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE));



    inputManager.addListener(this, mappings);



    inputManager.addRawInputListener(new RawInputListener() {



    public void onJoyAxisEvent(JoyAxisEvent evt) {}

    public void onJoyButtonEvent(JoyButtonEvent evt) {}



    public void onMouseMotionEvent(MouseMotionEvent evt) {

    mouseX = evt.getX();

    mouseY = evt.getY();

    }



    public void onMouseButtonEvent(MouseButtonEvent evt) {}

    public void onKeyEvent(KeyInputEvent evt) {}

    });

    inputManager.setCursorVisible(true);

    }



    protected void rotateCamera(float value, Vector3f axis){

    if(!canRotate)

    return;



    Matrix3f mat = new Matrix3f();

    mat.fromAngleNormalAxis(rotationSpeed * value, axis);



    Vector3f up = cam.getUp();

    Vector3f left = cam.getLeft();

    Vector3f dir = cam.getDirection();



    mat.mult(up, up);

    mat.mult(left, left);

    mat.mult(dir, dir);



    Quaternion q = new Quaternion();

    q.fromAxes(left, up, dir);

    q.normalize();



    cam.setAxes(q);

    }



    protected void zoomCamera(float value){

    Vector3f vel = new Vector3f();

    Vector3f pos = cam.getLocation().clone();



    cam.getDirection(vel);

    vel.multLocal(value * zoomSpeed);



    if (motionAllowed != null)

    motionAllowed.checkMotionAllowed(pos, vel);

    else

    pos.addLocal(vel);



    cam.setLocation(pos);

    }



    protected void riseCamera(float value){

    Vector3f vel = new Vector3f(0, value * moveSpeed, 0);

    Vector3f pos = cam.getLocation().clone();



    if (motionAllowed != null)

    motionAllowed.checkMotionAllowed(pos, vel);

    else

    pos.addLocal(vel);



    cam.setLocation(pos);

    }



    protected void moveCamera(float value, boolean sideways){

    Vector3f vel = new Vector3f();

    Vector3f pos = cam.getLocation().clone();



    if (sideways){

    cam.getLeft(vel);

    }else{

    cam.getDirection(vel);

    }

    vel.multLocal(value * moveSpeed);

    vel.y = 0;



    if (motionAllowed != null)

    motionAllowed.checkMotionAllowed(pos, vel);

    else

    pos.addLocal(vel);



    cam.setLocation(pos);

    }



    public void onAnalog(String name, float value, float tpf) {

    if (!enabled)

    return;

    /*if (name.equals(“STRATCAM_Left”)){

    rotateCamera(value, initialUpVec);

    }else if (name.equals(“STRATCAM_Right”)){

    rotateCamera(-value, initialUpVec);

    }else if (name.equals(“STRATCAM_Up”)){

    rotateCamera(-value, cam.getLeft());

    }else if (name.equals(“STRATCAM_Down”)){

    rotateCamera(value, cam.getLeft());

    }else */if (name.equals(“STRATCAM_Forward”))

    moveCamera(value, false);

    else if (name.equals(“STRATCAM_Backward”))

    moveCamera(-value, false);

    else if (name.equals(“STRATCAM_StrafeLeft”))

    moveCamera(value, true);

    else if (name.equals(“STRATCAM_StrafeRight”))

    moveCamera(-value, true);

    else if (name.equals(“STRATCAM_Rise”))

    riseCamera(value);

    else if (name.equals(“STRATCAM_Lower”))

    riseCamera(-value);

    else if (name.equals(“STRATCAM_ZoomIn”))

    zoomCamera(value);

    else if (name.equals(“STRATCAM_ZoomOut”))

    zoomCamera(-value);

    else if (name.equals(“STRATCAM_MouseForward”)){

    if(canRotate) rotateCamera(-value, cam.getLeft());

    else {

    if(mouseY >= maxY) strafeupdown = 2;

    else strafeupdown = 0;

    }}

    else if (name.equals(“STRATCAM_MouseBackward”)){

    if(canRotate) rotateCamera(value, cam.getLeft());

    else

    if(mouseY <= 0) strafeupdown = 1;

    else strafeupdown = 0;



    }

    else if (name.equals(“STRATCAM_MouseLeft”)){

    if(canRotate) rotateCamera(value, initialUpVec);

    else {

    if(mouseX <= 0) strafeleftright = 1;

    else strafeleftright = 0;

    }}

    else if (name.equals(“STRATCAM_MouseRight”)){

    if(canRotate) rotateCamera(-value, initialUpVec);

    else{

    if(mouseX >= maxX) strafeleftright = 2;

    else strafeleftright = 0; //moveCamera(-value, true);

    }}

    }



    public void executeAction(float tpf){

    if(strafeupdown == 1) moveCamera(-tpf, false);

    else if(strafeupdown == 2) moveCamera(tpf, false);

    if(strafeleftright == 1) moveCamera(tpf, true);

    else if(strafeleftright == 2) moveCamera(-tpf, true);

    }



    public void onAction(String name, boolean value, float tpf) {

    if (!enabled)

    return;



    if (name.equals(“STRATCAM_RotateDrag”)){

    canRotate = value;

    inputManager.setCursorVisible(!value);

    }

    }



    }

    [/java]

One more class, please. PhysiclessHeightmap.

You can replace PhysiclessHeightmap with ImageBasedHeightMap, must have forgotten to do so before posting here, sorry


Think we don't need it was hidden in his text above