collideWith Ray vs BoundingSphere and UnsupportedCollisionException

Hi All

I am trying to implement some sort of collision detection in my game. I am using a BoundingSphere, but it seems to only work when there are a couple of things to collide with - any more in the scenegraph and I get an UnsupportedCollisionException. If I use a ray, then it doesn’t seem to matter how many things there are…

If I have 13 nodes in my scene graph as below, then I can do collisions all day:

npcs (Node)
    generic characterNode a4711f67-2d17-4996-9c64-3b44eb22b519 (Node)
        generic spatial b405b184-9a2f-49db-8ac5-37623258c07f (Node)
            Oto-geom-1 (Geometry)
    generic characterNode 974f19a2-f862-4283-9184-531409169e26 (Node)
        generic spatial 458a33e5-4f35-4d15-a67c-3b10e30c6eab (Node)
            Oto-geom-1 (Geometry)
    generic characterNode db9ceaad-4c4c-4aa9-bb62-120cf61cae75 (Node)
        generic spatial 4d2fe784-43c9-4dae-b739-53f5c2819c37 (Node)
            Oto-geom-1 (Geometry)
    generic characterNode b78a11c9-8210-4ffe-bba7-99e076e73671 (Node)
        generic spatial 45f507cd-3600-49ec-8563-1cf5c5f53298 (Node)
            Oto-geom-1 (Geometry)

If I add one more characterNode which makes it 16 nodes in my scene graph as below, then I get UnsupportedCollisionException: With: BoundingSphere : as soon as I try and collide anything whether it collides or not:

npcs (Node)
    generic characterNode 33cbff0d-450b-46a4-9fbd-a9f2be533fc0 (Node)
        generic spatial 050ae9c1-d286-4cea-bd7d-1ad8d466613c (Node)
            Oto-geom-1 (Geometry)
    generic characterNode 81f9189b-9173-498a-bdb1-58aa18fcef52 (Node)
        generic spatial 02345d83-03eb-4c0b-9f78-3b37f5ad1c2b (Node)
            Oto-geom-1 (Geometry)
    generic characterNode ffbaeef8-c34f-4ab7-9c51-81445994f6a9 (Node)
        generic spatial faf6cf6c-1f5c-4b85-aff8-abbed9f9470b (Node)
            Oto-geom-1 (Geometry)
    generic characterNode 0a2aa0fb-4b83-43f0-95dc-57bbe69b6583 (Node)
        generic spatial fe49ba9d-7607-486c-8536-1e3f489037ee (Node)
            Oto-geom-1 (Geometry)
    generic characterNode 03c70abe-8eb3-4c64-9da9-08fa0a88703b (Node)
        generic spatial 6c75b57a-911e-4b7d-8db5-621f7dadd8fa (Node)
            Oto-geom-1 (Geometry)

Here is the full stack trace:

Oct 26, 2015 4:16:02 PM com.jme3.app.Application handleError
SEVERE: Uncaught exception thrown in Thread[jME3 Main,5,main]
com.jme3.collision.UnsupportedCollisionException: With: BoundingSphere
	at com.jme3.bounding.BoundingBox.collideWith(BoundingBox.java:822)
	at com.jme3.scene.Node.collideWith(Node.java:579)
	at mygame.InfluenceControl.controlUpdate(InfluenceControl.java:77)
	at com.jme3.scene.control.AbstractControl.update(AbstractControl.java:112)
	at com.jme3.scene.Spatial.runControlUpdate(Spatial.java:661)
	at com.jme3.scene.Spatial.updateLogicalState(Spatial.java:808)
	at com.jme3.scene.Node.updateLogicalState(Node.java:220)
	at com.jme3.scene.Node.updateLogicalState(Node.java:231)
	at com.jme3.app.SimpleApplication.update(SimpleApplication.java:249)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:152)
	at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:192)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:233)
	at java.lang.Thread.run(Thread.java:745)

Hers is the code, I hope its enough. You’ll note that I have tried it with a boundingbox as well, with the same unfortunate results:

package mygame;

import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bullet.BulletAppState;
import com.jme3.collision.CollisionResults;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.Savable;
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.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import java.io.IOException;

public class InfluenceControl extends AbstractControl implements Savable, Cloneable {

    private Float updateNum = 0f;
    public Boolean influenceSpent = false;
    private BulletAppState bulletAppState;
    private float speed = 1100f;
    private Node nodeToInfluence;
    //private BoundingSphere bs;
    private Vector3f position;
    InfluenceInventory influence;
    Geometry anInfluenceArea;

    public InfluenceControl(BulletAppState bulletAppState, Node nodeToInfluence, Vector3f position, InfluenceInventory influence, Geometry anInfluenceArea) {
        this.bulletAppState = bulletAppState;
        this.nodeToInfluence = nodeToInfluence;
        this.influence = influence;
        this.position = position;
        this.anInfluenceArea = anInfluenceArea;
        /* sphere to begin with, but, influence.getMethod() will give us sphere, ray or arc which we should use */

    }

    /**
     * This method is called when the control is added to the spatial, and when
     * the control is removed from the spatial (setting a null value). It can be
     * used for both initialization and cleanup.
     */
    @Override
    public void setSpatial(Spatial spatial) {
        super.setSpatial(spatial);
        /* Example:
         if (spatial != null){
         // initialize
         }else{
         // cleanup
         }
         */
    }

    /**
     * Implement your spatial's behaviour here. From here you can modify the
     * scene graph and the spatial (transform them, get and set userdata, etc).
     * This loop controls the spatial while the Control is enabled.
     *
     * @param tpf
     */
    @Override
    protected void controlUpdate(float tpf) {
        if (!influenceSpent) {
            System.out.println("Influence Area testing collisions");
            BoundingSphere bs = new BoundingSphere(1f, new Vector3f(0, 0, 0));
            BoundingBox bb = new BoundingBox(new Vector3f(0, 0, 0), 1, 1, 1);
                    
            Ray ray = new Ray(new Vector3f(0, 0, 0), new Vector3f(10, 10, 10));
            
            CollisionResults results = new CollisionResults();

            nodeToInfluence.collideWith(bs, results);
            //nodeToInfluence.collideWith(ray, results);

            //anInfluenceArea.collideWith(nodeToInfluence , results);
            if (results.size() > 0) {
                System.out.println("Influenced this many " + results.size());

//                Iterator it = results.iterator();
//                
//                while(it.hasNext()){
//                    System.out.println(results.size() + "Influenced this" + it.next().toString());
//                }
            } else {
                System.out.println("Influenced Nothing");
            }

            if (updateNum > 3) {
                //System.out.println("Influence spent");
                influenceSpent = true;

                bulletAppState.getPhysicsSpace().removeAll(spatial);
                spatial.removeFromParent();
            }

            updateNum += tpf;
        }
    }

    @Override
    public Control cloneForSpatial(Spatial spatial) {
        final InfluenceControl control = new InfluenceControl(bulletAppState, nodeToInfluence, position, influence, anInfluenceArea);
        /* Optional: use setters to copy userdata into the cloned control */
        // control.setIndex(i); // example
        System.out.println("spatial is " + spatial);
        control.setSpatial(spatial);
        return control;
    }

    @Override
    protected void controlRender(RenderManager rm, ViewPort vp) {
        /* Optional: rendering manipulation (for advanced users) */
    }

    @Override
    public void read(JmeImporter im) throws IOException {
        super.read(im);
        // im.getCapsule(this).read(...);
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
        super.write(ex);
        // ex.getCapsule(this).write(...);
    }

}

Actually, I meant to post this test app I made:

Its just a new JME BasicGame with the jme-test-data library for the model. Changing the limit in the for loop from 5 to 4 and it works, reporting 15968 collisions. With 5 collisions it fails. It breaches 16384 which is the limit for a 15 bit integer, but I can’t see how that would be relevant especially after looking at where its failing (BoundingBox.java:822), and googling “java 16384” reveals nothing special about that number.

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingSphere;
import com.jme3.collision.CollisionResults;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import java.awt.Desktop;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.Logger;

/**
 * test
 *
 * @author normenhansen
 */
public class Main extends SimpleApplication {

    public static void main(String[] args) {
        Main app = new Main();
        app.setShowSettings(false);

        app.start();
    }

    @Override
    public void simpleInitApp() {
        Node allGeometriesNode = new Node("allGeometriesNode");

        for (int i = 0; i < 5; i++) {
            Node aGeometryNode = new Node("aGeometryNode");
            Spatial spatial = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
            spatial.setName(" spatial " + UUID.randomUUID().toString());

            aGeometryNode.attachChild(spatial);

            allGeometriesNode.attachChild(aGeometryNode);
        }

        rootNode.attachChild(allGeometriesNode);

        try {

            File f = File.createTempFile("node", ".html");

            FileWriter fw = new FileWriter(f);

            fw.write(mineNode(allGeometriesNode));

            fw.flush();

            fw.close();

            Desktop.getDesktop().open(f);

        } catch (IOException ex) {

            Logger.getLogger(this.getClass().getName()).log(java.util.logging.Level.SEVERE, "Error writing html file", ex);

        }

        BoundingSphere bs = new BoundingSphere(10f, new Vector3f(0, 0, 0));

        CollisionResults results = new CollisionResults();

        allGeometriesNode.collideWith(bs, results);

        if (results.size() > 0) {
            System.out.println("Collided with this many " + results.size());
        } else {
            System.out.println("Collided with Nothing");
        }

    }

    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    private static String mineNode(Node parent) {

        String ptype = parent.getClass().getSimpleName();

        String s = parent.getName() + " (" + ptype + ")" + "<br><ul>";

        if (parent.getChildren().isEmpty()) {
            return parent.getName() + " (" + ptype + ")";
        } else {

            for (Spatial child : parent.getChildren()) {

                if (child instanceof Node) {

                    s = s + "<li>" + mineNode((Node) child) + "</li>";

                } else {

                    String type = child.getClass().getSimpleName();

                    s = s + "<li>" + child.getName() + " (" + type + ")" + "</li>";

                }

            }

        }

        s = s + "</ul>";

        return s;
    }

}

An “optimization” was committed to JME without proper testing and causes issues because of the way it short-circuits a check when a certain child threshold has been reached. This code may have been fixed recently in head.

I just looked at the code… and nope.

The problem is that rather than doing an intersects check it does a full CollisionResults style collision test (and ignores the CollisionResults). The problem with that is that bounding sphere to bounding box collisions are not supported by that.

I personally think this ‘if’ should be removed until a proper version is implemented but I haven’t had time to wade into it. In my opinion, it would be better to do an intersect() test in this case…

hmm, well I might get head and un-optimize the optimization so I can continue :smile:

It’s a good idea.

how does one like run the build? I downloaded the jme sources, and have sucessfully “gradle build”, but I can’t fine an executable or jar to run

gradlew dist

…then look in the dist folder for the jars

ah - similar to maven. I should have guessed…

And note: “gradlew” was not a type. It’s the gradle wrapper which will run the appropriate gradle version no matter which gradle you have installed. It’s safer since that’s the one that’s known to build everything properly.

Thanks :smiley:

That fixed it! running /dist/jMonkeyEngine3.jar just started the tests…

I copied /dist/lib/jme-core.jar to C:\Program Files\jmonkeyplatform3.1beta1\jmonkeyplatform\libs\jme3-core-3.1.0-alpha1.jar and that worked - which is what I realize you meant me to do…

For me, I usually just change my projects to depend on the dist jars instead of the SDK built-ins. That way I don’t potentially mess up projects that I still want to depend on the ‘published’ version.

…but I often have 20+ projects open at any given time, so my constraints are a little different. :slight_smile:

Oh okay haha - well, I’ll keep that in mind for the future if I end up hacking on jmonkeyengine itself

The must be another optimization somewhere, it doesn’t crash any more - but if I have too many objects to collide with I don’t seem to get any collisions…

I’ll have to dig deeper tomorrow.

EDIT: On my small test, I can spawn 100 objects and collisions work fine. Hmmm.