[SOLVED] Bullet question


#1

OK here is a question that probably has not been asked. I am using Bullet to get a mesh accurate result in my ship builder when parts are touching because of the AABB problem with ghost control. I am using hull shapes and even bumped down the solver iterations to 4 in bullet. The problem is since in my happy path all objects are colliding and all objects are kinetic rigid bodies, and fps grinds to a crawl with a ship consists of about 60 or so parts in the builder. If they are not touching each other everything runs smoothly. My suspicion is that broad phase works fast but then the iterative narrow phase is probably what is causing the load when all objects are touching each other. If so, can narrow phase be turned off?


#2

It sounds like you should do some preliminary solving yourself maybe. Why don’t you do traditional AABB and when a collision occurs switch to mesh-accurate like a kind of LOD. The whole graph would be much happier.


#3

im not sure if i understand correctly, but for example you have ship builded from lets say 10 parts, and each of this parts have own kinematic physics mesh with separate control?

if yes, then why you cant just join them “in-run” into single one physics mesh?

about part picking from builded ship, you still should be able.(check some ghost controls or part bounding or raycast part node.)


#4

In bullet you want simple meshes. One complex mesh is less efficient than many small simple parts. In its collision detection it creates a tree and goes up the tree when positives are found.

If you have one massive complex shape it can’t do that and every calculation becomes time consuming.


#5

I suspect you’ve hit issue 1037:

https://github.com/jMonkeyEngine/jmonkeyengine/issues/1037

This issue is only in jme3-bullet, so you might work around it by using Minie or jme3-jbullet.

PS: If you turn off narrow-phase collision detection, you’ll be back to AABB.


#6

You are correct. I ran into that issue since for my needs the objects will always collide unless you move a ship part away from the others. I wish someone would fix our bullet ghost control so that it’s not AABB. This would solve everything for me.


#7

The main virtue of GhostControl is that it provides access to both kinds of collision detection:

  • For quick-and-dirty/broadphase/AABB collision detection, use the getOverlapping() interface.
  • For precise/narrow-phase collision detection, use the PhysicsCollisionListener interface.

#8

Unfortunately it’s an AABB implementation for both in JME


#9

That’s not what I’ve seen in my tests.

To investigate this issue, I’d want to know which version of JME, which physics library (jme3-bullet, Minie, or jme3-jbullet), and the collision shapes involved. Also, are both collision objects ghosts or just one of them?

A short, self-contained example would also be welcome.


#10

It’s the 3.2.2 build. All objects are Ghosts. The code base is my ship building app state which in non trivial but I am using hull shapes. Broad phase I expected to be an AABB but the collision listener reports the same thing. Test is simple. Create two cubes, set each with a hull collision shape, add ghost controls, rotate one 45 degrees and watch the result.


#11

Please run the following test on your system:

package banana;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.collision.PhysicsCollisionEvent;
import com.jme3.bullet.collision.PhysicsCollisionListener;
import com.jme3.bullet.collision.shapes.HullCollisionShape;
import com.jme3.bullet.control.GhostControl;
import com.jme3.font.BitmapText;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;

/**
 * Test collision detection: 2 ghost controls and hull shapes.
 *
 * @author Stephen Gold sgold@sonic.net
 */
public class GhostHull
        extends SimpleApplication
        implements PhysicsCollisionListener, PhysicsTickListener {

    private BitmapText statusText;
    private volatile boolean collisionFlag = false;
    final private float size = 5f;
    private GhostControl fixedGhost;
    private GhostControl movingGhost;
    private volatile int numOverlaps;
    final private Node fixedNode = new Node("fixed");
    final private Node movingNode = new Node("moving");
    private PhysicsSpace physicsSpace;

    public static void main(String[] arguments) {
        new GhostHull().start();
    }

    @Override
    public void simpleInitApp() {
        configureCamera();
        configurePhysics();

        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        statusText = new BitmapText(guiFont, false);
        guiNode.attachChild(statusText);
        statusText.move(0f, cam.getHeight() - statusText.getHeight(), 0f);

        float[] cubeVertices = new float[]{
            +size, +size, +size,
            +size, +size, -size,
            +size, -size, +size,
            +size, -size, -size,
            -size, +size, +size,
            -size, +size, -size,
            -size, -size, +size,
            -size, -size, -size
        };
        HullCollisionShape cube = new HullCollisionShape(cubeVertices);
        cube.setMargin(0.001f);

        rootNode.attachChild(fixedNode);
        fixedGhost = new GhostControl(cube);
        fixedNode.addControl(fixedGhost);
        fixedNode.rotate(0f, 0f, FastMath.QUARTER_PI);
        physicsSpace.add(fixedNode);

        rootNode.attachChild(movingNode);
        movingGhost = new GhostControl(cube);
        movingNode.addControl(movingGhost);
        physicsSpace.add(movingNode);
    }

    @Override
    public void simpleUpdate(float tpf) {
        String text;
        if (collisionFlag) {
            text = "colliding, ";
        } else {
            text = "not colliding, ";
        }
        if (numOverlaps > 0) {
            text += "overlapping";
        } else {
            text += "not overlapping";
        }
        statusText.setText(text);

        collisionFlag = false;

        Vector2f cursorPos = inputManager.getCursorPosition();
        Vector3f location = cam.getWorldCoordinates(cursorPos, 0f);
        location.z = 0f;
        movingNode.setLocalTranslation(location);
    }

    @Override
    public void collision(PhysicsCollisionEvent event) {
        collisionFlag = true;
    }

    @Override
    public void physicsTick(PhysicsSpace space, float timeStep) {
        // do nothing
    }

    @Override
    public void prePhysicsTick(PhysicsSpace space, float timeStep) {
        numOverlaps = fixedGhost.getOverlappingObjects().size()
                + movingGhost.getOverlappingObjects().size();
    }

    private void configureCamera() {
        flyCam.setEnabled(false);
        cam.setParallelProjection(true);
        cam.setFrustumTop(5f * size);
        cam.setFrustumBottom(-5f * size);
    }

    private void configurePhysics() {
        BulletAppState bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(true);

        physicsSpace = bulletAppState.getPhysicsSpace();
        physicsSpace.addCollisionListener(this);
        physicsSpace.addTickListener(this);
    }
}

#14

I initially thought the code was showing what I was experiencing in my version but after modifying your code to also accurately report when collisions are no longer happening you are correct. Narrow phase is using the Hull Shape. I am posting the upgraded code bellow just in case you want to use it in the future. It reports when you move away from a collision as well.

package banana;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.collision.PhysicsCollisionEvent;
import com.jme3.bullet.collision.PhysicsCollisionListener;
import com.jme3.bullet.collision.shapes.HullCollisionShape;
import com.jme3.bullet.control.GhostControl;
import com.jme3.font.BitmapText;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;

/**
 * Test collision detection: 2 ghost controls and hull shapes.
 *
 * @author Stephen Gold sgold@sonic.net
 */
public class GhostHull extends SimpleApplication implements PhysicsCollisionListener, PhysicsTickListener
{

	private BitmapText statusText;

	private volatile boolean collisionFlag = false;

	final private float size = 5f;

	private GhostControl fixedGhost;

	private GhostControl movingGhost;

	private volatile int numOverlaps;

	final private Node fixedNode = new Node("fixed");

	final private Node movingNode = new Node("moving");

	private PhysicsSpace physicsSpace;

	public static void main(String[] arguments)
	{
		new GhostHull().start();
	}

	@Override
	public void simpleInitApp()
	{
		configureCamera();
		configurePhysics();

		guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
		statusText = new BitmapText(guiFont, false);
		guiNode.attachChild(statusText);
		statusText.move(0f, cam.getHeight() - statusText.getHeight(), 0f);

		float[] cubeVertices = new float[]
		{ +size, +size, +size, +size, +size, -size, +size, -size, +size, +size, -size, -size, -size, +size, +size, -size, +size, -size, -size, -size, +size,
				-size, -size, -size };
		HullCollisionShape cube = new HullCollisionShape(cubeVertices);
		cube.setMargin(0.001f);

		rootNode.attachChild(fixedNode);
		fixedGhost = new GhostControl(cube);
		fixedNode.addControl(fixedGhost);
		fixedNode.rotate(0f, 0f, FastMath.QUARTER_PI);
		physicsSpace.add(fixedNode);

		rootNode.attachChild(movingNode);
		movingGhost = new GhostControl(cube);
		movingNode.addControl(movingGhost);
		physicsSpace.add(movingNode);
	}

	@Override
	public void simpleUpdate(float tpf)
	{
		String text;
		if(collisionFlag && (System.nanoTime() - collisionFired < 100000000))
		{
			text = "colliding, ";
		} else
		{
			text = "not colliding, ";
		}
		if(numOverlaps > 0)
		{
			text += "overlapping";
		} else
		{
			text += "not overlapping";
		}
		statusText.setText(text);

		Vector2f cursorPos = inputManager.getCursorPosition();
		Vector3f location = cam.getWorldCoordinates(cursorPos, 0f);
		location.z = 0f;
		movingNode.setLocalTranslation(location);
	}

	private long collisionFired = 0;

	@Override
	public void collision(PhysicsCollisionEvent event)
	{
		collisionFlag = true;
		collisionFired = System.nanoTime();
	}

	@Override
	public void physicsTick(PhysicsSpace space, float timeStep)
	{
		// do nothing
	}

	@Override
	public void prePhysicsTick(PhysicsSpace space, float timeStep)
	{
		numOverlaps = fixedGhost.getOverlappingObjects().size() + movingGhost.getOverlappingObjects().size();

	}

	private void configureCamera()
	{
		flyCam.setEnabled(false);
		cam.setParallelProjection(true);
		cam.setFrustumTop(5f * size);
		cam.setFrustumBottom(-5f * size);
	}

	private void configurePhysics()
	{
		BulletAppState bulletAppState = new BulletAppState();
		stateManager.attach(bulletAppState);
		bulletAppState.setDebugEnabled(true);

		physicsSpace = bulletAppState.getPhysicsSpace();
		physicsSpace.addCollisionListener(this);
		physicsSpace.addTickListener(this);
	}
}

#15

You’re very welcome.