Multiple ray casts in one frame = no collisions returned (3.1alpha)

This one is kind of confusing. Here’s the code in question:

@Override
protected void controlUpdate(float tpf) {
    time += tpf;
    if (time > 10.0F) {
        spatial.removeFromParent();
        return;
    }

    Vector3f moveDir = dir.mult(tpf * 1000.0F);
    float dist = moveDir.length();

    Ray r = new Ray(spatial.getLocalTranslation(), dir);
    r.setLimit(dist);
    CollisionResults results = new CollisionResults();
    Utils.getSceneNode().collideWith(r, results);

    for (CollisionResult next : results) {
        System.out.println("Hit " + next.getGeometry().getName());
        if (parent.hasChild(next.getGeometry()) || next.getGeometry() == spatial) {
            continue;
        }
        spatial.removeFromParent();
        return;
    }

    spatial.move(moveDir);
}

This code is the update method of a laser control I’m using. Basically the laser is fired from a source, and the ray-casting checks for hits and moves the laser forward if no hit is found. So here’s the issue: If I fire only one laser, the ray-casting works perfectly fine. If there is more than 1 laser, ray-casting will always return with 0 hits. Even if all of the lasers follow an identical path, directly into a target. I tried to set up a test case where clicking the mouse triggers 10 ray-casts (each slightly ahead of the last, but in the same direction), but in that case the collision results returned just fine. Even if the original, first shot is heading right into a target, if a second laser is fired before it reaches the target, it’ll pass right through with no collision registered. If I wait 10 seconds for all the lasers I’ve fired to disappear, then the ray-cast works again until multiple lasers are fired. Could it have anything to do with accessing the scene node from a static method? I know that’s really sloppy code, it’s just there temporarily until the rest of the game’s world code is done.

There shouldn’t be any issue with this. I’ve routinely fired multiple ray collisions per frame… it’s pretty common when trying to implement your own collisions for physics to cast a bunch of rays.

I recommend that you write a simple single class test case that illustrates the issue so that we might spot the state leakage.

That’s the weird thing, because in previous projects I’ve had several ray-casts done in a single frame with no issues. I was hoping that by posting the code someone would notice some really obvious asinine mistake I made somewhere that I was just glossing over lol. I’ll get a test case written up shortly.

Wasn’t there a bug ion the collision stuff in 3.1? An optimization made the collisions no longer work? I’ll see if I can dig it up. Perhaps I should have submitted a pull request…

And the plot thickens: the test-case I just wrote, using the exact code above (except with directly using rootNode.collideWith() instead of Utils.getSceneNode().collideWith()) works exactly as expected. Even if I intentionally fire off a “laser” away from all geometries, other “lasers” will still correctly hit targets and register collisions. So I figured it must be related to using a static method to access the scene root, and I wrote a really rough bit of code to try and circumvent that; still, the problem persists in the larger project, even passing the scene node into the control and checking for collisions there. I just can’t seem to recreate this issue in a test case, even using the exact models and angles from the full code are working just fine in a single-class test case.

@jakebriggs I did do a bit of digging before posting, and I didn’t come across anything that sounded similar to this. If you do have something that might explain this as an engine bug, I’d be surprised - at this point with it working fine in a test case, I’m fairly certain it’s an error on my end and I’m just missing something.

Hmmm okay these issues do seem unrelated, but here is the thread anyway:

and its already fixed in head

This shouldn’t cause an issue unless you are corrupting that sate somehow.

Then something is still different and you’ll have to figure out what it is… because that’s the root of the issue and it’s the one thing we can’t easily see.

I know I’ve had some issues with casting rays in the past, however we had a lot of meshes that were automatically generated and we had to ensure all the bounds were up to date on the node and it’s children before the ray would collide.

I assume you are still using a control and stuff just like your original code? (As in inner class in the single-class test case.)

Yes, here’s the entire LaserControl class as it is right now:

import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;

/**
 *
 * @author Brendan
 */
public class LaserControl extends AbstractControl {

    private float time = 0.0F;
    private Vector3f dir;
    private Node parent, sceneRoot;

    public LaserControl(Vector3f dir, Node parent, Node sceneRoot) {
        this.dir = dir;
        this.parent = parent;
        this.sceneRoot = sceneRoot;
    }

    @Override
    protected void controlUpdate(float tpf) {
        time += tpf;
        if (time > 10.0F) {
            spatial.removeFromParent();
            return;
        }

        Vector3f moveDir = dir.mult(tpf * 1000.0F);
        float dist = moveDir.length();

        Ray r = new Ray(spatial.getLocalTranslation(), dir);
        r.setLimit(dist * 1.5F);
        CollisionResults results = new CollisionResults();
        sceneRoot.collideWith(r, results);

        for (CollisionResult next : results) {
            if (parent.hasChild(next.getGeometry()) || next.getGeometry() == spatial) {
                continue;
            }
            System.out.println("Hit " + next.getGeometry().getName());
            spatial.removeFromParent();
            return;
        }

        spatial.move(moveDir);
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
    }
}

For the test case, I just made it an inner class (copied and pasted the LaserControl class into the test case class). Here’s how I fire the lasers:

public void fire() {
    Geometry newShot = new Geometry("LaserTest", new Box(1, 1, 1));
    newShot.setMaterial(Utils.loadMaterial("Materials/Laser.j3m"));
    newShot.setLocalTranslation(getWorldTranslation());

    Vector3f dir = getWorldRotation().mult(Vector3f.UNIT_Z).normalizeLocal();
            
    Utils.getSceneNode().attachChild(newShot);
    
    newShot.addControl(new LaserControl(dir, getParent(), getParent().getParent())); //temporary; getParent().getParent() = scene root
}

The fire method is called from an AppState update method in the real code; in the test case, it’s called in the simpleUpdate method.

EDIT: I just tried running the test-case without the inner-class version of the LaserControl, as in directly using the same LaserControl from the full project, and it still works fine. I’m at a loss at this point, I really don’t know what could be causing this. I’m going to try completely recreating the full game scene without the game logic and running the test-case again.

I’ve figured out the issue, and to say the least I’m surprised and confused. The skybox being in the scene was causing my multiple ray-casts to simply not work. I added a skybox to my test-case, and the ray-casting stopped working. I removed the skybox from the full game, and it’s now working exactly as it should. Here’s a test-case class to validate if someone else has the issue (keep in mind this is on 3.1 alpha that I’m having this issue, the same issue might or might not be present on 3.0):

import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.shape.Box;

/**
 *
 * @author Brendan
 */
public class TestMultipleRaycastBug extends SimpleApplication {

    boolean isClick = false;
    float shootTimer = 0.0F;

    public static void main(String... args) {
        TestMultipleRaycastBug app = new TestMultipleRaycastBug();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        flyCam.setMoveSpeed(50.0F);

        Geometry geom = new Geometry("Geom", new Box(50, 10, 15));
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        geom.setMaterial(mat);
        rootNode.attachChild(geom);

        //This is the suspect bit of code; run with it commented out, test the collisions, then run with it un-commented
//        rootNode.attachChild(SkyFactory.createSky(assetManager, assetManager.loadTexture("Textures/please work.jpg"), SkyFactory.EnvMapType.EquirectMap));

        Material laserMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        laserMat.setColor("Color", ColorRGBA.Red);

        inputManager.addMapping("Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener((ActionListener) (String name, boolean value, float tpf) -> {
            if (name.equals("Click") && !value) {
                //Fire a "laser"
                Geometry g = new Geometry("Laser", new Box(1, 1, 1));
                g.setMaterial(laserMat);
                g.setLocalTranslation(cam.getLocation());
                rootNode.attachChild(g);

                g.addControl(new LaserControl(cam.getDirection()));
            }
        }, "Click");
    }

    public class LaserControl extends AbstractControl {

        private float time = 0.0F;
        private Vector3f dir;

        public LaserControl(Vector3f dir) {
            this.dir = dir;
        }

        @Override
        protected void controlUpdate(float tpf) {
            time += tpf;
            if (time > 10.0F) {
                spatial.removeFromParent();
                return;
            }

            Vector3f moveDir = dir.mult(tpf * 50.0F);
            float dist = moveDir.length();

            Ray r = new Ray(spatial.getLocalTranslation(), dir);
            r.setLimit(dist * 1.5F);
            CollisionResults results = new CollisionResults();
            rootNode.collideWith(r, results);

            System.out.println(results.size());
            for(CollisionResult result : results) {
                if(result.getGeometry() == spatial) {
                    continue;
                }
                spatial.removeFromParent();
            }

            spatial.move(moveDir);
        }

        @Override
        protected void controlRender(RenderManager rm, ViewPort vp) {
        }
    }
}

Fly outward a little bit, there’s a large blue box. Click to fire a laser, the update method prints how many collisions it’s picking up. The laser geometries also disappear when they hit the blue box. You’ll notice that, when the skybox is added to the scene, firing >1 laser will always output 0 collisions, not even colliding with the laser mesh itself. The laser geometries will also just pass through the blue box. However, if you remove the skybox from the scene, you can fire as many lasers as you want and they’ll correctly detect collisions.

Perhaps because you are inside a box, the collisions are ignored? I mean I am totally guessing here, but you could make a skybox out of 5 planes instead. You can do some funky maths to rotate them - this method makes a Quad, then rotates it so that its normal is vertical. You’ll probably want to do the opposite:

private void addFloor(int w, int h, Node aNode) {
    Quad quad = new Quad(0.1f, 0.1f);

    Geometry geo = new Geometry(w + "," + h, quad);
    geo.rotate((float) (Math.PI / 2) * 3, 0, 0);
    /* we add/remove 0.05 because the rotation isn't about the centre, I think its off to one side */
    geo.setLocalTranslation((w * 0.1f) - 0.05f, 0f, (h * 0.1f) + 0.05f);
    
    geo.setMaterial(mat_terrain);
    aNode.attachChild(geo);
}

We make a Quad, then rotate about it X axis by 4.71238898038 radians ( (Math.PI / 2) * 3 ) to make it “flat”. To make it face “down”, perhaps you’ll do this? The “1” is there for verbosity.

geo.rotate((float) (Math.PI / 2) * 1, 0, 0);

Add the sky box to the root node but don’t do collisions against the root node… do it against a child scene node.

Adding the Skybox to the root node, but colliding with a sub-node fixed the issue. Thanks!