[SOLVED] Help with JBullet RayTest

Hello,
I’ve run into an issue with JBullet rayTest.
rayTest seems to inconsistently return no result on GhostControls, depending on ray origin it seems?
I was able to reproduce the thing in a small test app, here is the code:

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.PhysicsRayTestResult;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.control.GhostControl;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.Arrow;

import java.util.ArrayList;


public class HelloJME3 extends SimpleApplication {

    private BulletAppState bulletAppState;

    public static void main(String[] args){
        HelloJME3 app = new HelloJME3();
        app.start(); // start the game
    }

    @Override
    public void simpleInitApp() {
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        this.bulletAppState.setDebugEnabled(true);
        Node node = new Node("test");
        this.getRootNode().attachChild(node);


        debugArrow(node, new Vector3f(),new Vector3f(0,1,0), ColorRGBA.Green);
        debugArrow(node, new Vector3f(),new Vector3f(1,0,0), ColorRGBA.Red);
        debugArrow(node,  new Vector3f(),new Vector3f(0,0,1), ColorRGBA.Blue);


        Vector3f end = new Vector3f(20,0,0);
        genBlock(this,end,new Vector3f(1,1,1));

        Vector3f origin = new Vector3f(0,0,0);
        this.rayCast(origin, end.subtract(origin).normalize(),25);
    }

    private void debugArrow(Node node,Vector3f origin, Vector3f end, ColorRGBA color) {
        Arrow arrow = new Arrow(end);
        Spatial debug = new Geometry("arrow", arrow);
        Material matArrow = getAssetManager().loadMaterial("materials/debugMat.j3m");
        matArrow.setColor("Color", color);

        debug.setMaterial(matArrow);
        node.attachChild(debug);
        debug.setLocalTranslation(origin);
    }

    private void rayCast(Vector3f origin, Vector3f dir, float length) {
        ArrayList<PhysicsRayTestResult> tmpRes = new ArrayList<>();
        Vector3f end = origin.add(dir.mult(length));
        try {
            getPhysicSpace().rayTest(origin.clone(), end.clone(), tmpRes);

        } catch (IllegalArgumentException e) {
            System.err.println("origin: " + origin + " dir: " + dir + " length: " + length + " results: " + tmpRes);
            throw new RuntimeException(e);
        }

        if (bulletAppState.isDebugEnabled()) {
           debugArrow(getRootNode(),origin, end,  (tmpRes.size() > 0 ? ColorRGBA.Orange : ColorRGBA.Red));
        }
    }

    private void genBlock(HelloJME3 gameEng,Vector3f pos,Vector3f extend) {
        Node node2 = new Node("test");

        GhostControl control = new GhostControl(new BoxCollisionShape(extend));
        control.setCollisionGroup(0);
        node2.addControl(control);
        node2.setLocalTranslation(pos);
        gameEng.getPhysicSpace().add(control);
        gameEng.getRootNode().attachChild(node2);

    }

    private PhysicsSpace getPhysicSpace() {
        return bulletAppState.getPhysicsSpace();

    }
}

for example the rayTest will return no result if the origin is 0,0,0 but will work fine with -5,0,0
which should (obviously ?) not change anything
If anyone as an idea of what I’m doing wrong I’d be grateful^^

1 Like

I changed the J3M path and added a print statement. At the time of the raycast, the physics location of the GhostControl was (0,0,0), perhaps because update() had not been invoked yet.

I suspect the ray missed the cube because the ray originated inside the cube. Translating the ray’s origin to (-5,0,0) caused the ray to originate outside the cube.

2 Likes

Ok I moved the ray cast in the update loop, and it worked.
thanks a lot for your help!
for reference, the example is now:

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.PhysicsRayTestResult;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.control.GhostControl;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.Arrow;

import java.util.ArrayList;


public class HelloJME3 extends SimpleApplication {

    private BulletAppState bulletAppState;
    boolean raycastDone = false;

    public static void main(String[] args){
        HelloJME3 app = new HelloJME3();
        app.start(); // start the game
    }

    @Override
    public void simpleInitApp() {
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        this.bulletAppState.setDebugEnabled(true);
        Node node = new Node("test");
        this.getRootNode().attachChild(node);


        debugArrow(node, new Vector3f(),new Vector3f(0,1,0), ColorRGBA.Green);
        debugArrow(node, new Vector3f(),new Vector3f(1,0,0), ColorRGBA.Red);
        debugArrow(node,  new Vector3f(),new Vector3f(0,0,1), ColorRGBA.Blue);


        Vector3f end = new Vector3f(20,0,0);
        genBlock(this,end,new Vector3f(1,1,1));

    }

    @Override
    public void update() {
        super.update();
        if(!raycastDone){
            raycastDone=true;
            Vector3f end = new Vector3f(20,0,0);
            Vector3f origin = new Vector3f(0,0,0);
            this.rayCast(origin, end.subtract(origin).normalize(),25);

        }
    }

    private void debugArrow(Node node, Vector3f origin, Vector3f end, ColorRGBA color) {
        Arrow arrow = new Arrow(end);
        Spatial debug = new Geometry("arrow", arrow);
        Material matArrow = getAssetManager().loadMaterial("materials/debugMat.j3m");
        matArrow.setColor("Color", color);

        debug.setMaterial(matArrow);
        node.attachChild(debug);
        debug.setLocalTranslation(origin);
    }

    private void rayCast(Vector3f origin, Vector3f dir, float length) {
        ArrayList<PhysicsRayTestResult> tmpRes = new ArrayList<>();
        Vector3f end = origin.add(dir.mult(length));
        try {
            getPhysicSpace().rayTest(origin.clone(), end.clone(), tmpRes);

        } catch (IllegalArgumentException e) {
            System.err.println("origin: " + origin + " dir: " + dir + " length: " + length + " results: " + tmpRes);
            throw new RuntimeException(e);
        }

        if (bulletAppState.isDebugEnabled()) {
           debugArrow(getRootNode(),origin, end,  (tmpRes.size() > 0 ? ColorRGBA.Orange : ColorRGBA.Red));
        }
    }

    private void genBlock(HelloJME3 gameEng,Vector3f pos,Vector3f extend) {
        Node node2 = new Node("test");

        GhostControl control = new GhostControl(new BoxCollisionShape(extend));
        control.setCollisionGroup(0);
        node2.addControl(control);
        node2.setLocalTranslation(pos);
        gameEng.getPhysicSpace().add(control);
        gameEng.getRootNode().attachChild(node2);

    }

    private PhysicsSpace getPhysicSpace() {
        return bulletAppState.getPhysicsSpace();

    }
}
2 Likes

One thing I’ve found is that every time I’m sure I’ve found a bug in jME’s ray casting mechanism, I’ve actually found a bug in my own code. :wink:

A small handful of System.out.println()s (ray origin/direction, object location/bounds, etc) can go a long way towards identifying the problem.

I’m slightly puzzled by the behavior here though - if a ray ever passes through part of a bounding volume (especially an edge), I’d expect it to count as a collision.

1 Like

Bullet raycasting tests against the exact shape of each collision object, not its bounding volume.

Ok, that makes sense but leaves me even more puzzled why a ray that originated inside the object doesn’t register as a collision.

1 Like

I think it’s not like mesh collisions that show collisions with the surface whether entering or leaving… but it will show collisions with the “object”.

So what would the contact be for a ray that starts in the object? If you have an answer other than “Oh, I see… none”… then imagine what would happen if this contact point were used for constraints.

2 Likes

Gotcha, that makes sense… if using rays for constraints, that’s a tricky scenario.