Hello,
I am trying to cast pick rays to collide with parts of my scene graph, which includes an InstancedNode, but get a NullPointerException due to missing world bounds.
Is this expected?
Simple example - click to cause the NPE:
import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.instancing.InstancedNode;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
// Based on distance from camera, swap in/out more/less detailed geometry to/from an InstancedNode.
public class TestInstancedNodeAttachDetachWithPicking extends SimpleApplication {
public static void main(String[] args) {
TestInstancedNodeAttachDetachWithPicking app = new TestInstancedNodeAttachDetachWithPicking();
app.setShowSettings(false); // disable the initial settings dialog window
app.start();
}
private InstancedNode instancedNode;
private Vector3f[] locations = new Vector3f[10];
private Geometry[] spheres = new Geometry[10];
private Geometry[] boxes = new Geometry[10];
@Override
public void simpleInitApp() {
addPointLight();
addAmbientLight();
Material material = createInstancedLightingMaterial();
instancedNode = new InstancedNode("theParentInstancedNode");
rootNode.attachChild(instancedNode);
// create 10 spheres & boxes, positioned along Z-axis successively further from the camera
for (int i = 0; i < 10; i++) {
Vector3f location = new Vector3f(0, -3, -(i*5));
locations[i] = location;
Geometry sphere = new Geometry("sphere", new Sphere(16, 16, 1f));
sphere.setMaterial(material);
sphere.setLocalTranslation(location);
sphere.getMesh().createCollisionData();
instancedNode.attachChild(sphere); // initially just add the spheres to the InstancedNode
spheres[i] = sphere;
Geometry box = new Geometry("box", new Box(0.7f, 0.7f, 0.7f));
box.setMaterial(material);
box.setLocalTranslation(location);
box.getMesh().createCollisionData();
boxes[i] = box;
}
instancedNode.instance();
flyCam.setMoveSpeed(30);
addCrossHairs();
// when you left-click, print the distance to the object to system.out
inputManager.addMapping("leftClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addListener(new ActionListener() {
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if( isPressed ) {
CollisionResult result = pickFromCamera();
if( result != null ) {
System.out.println("Distance = "+result.getDistance());
}
}
}
}, "leftClick");
}
@Override
public void simpleUpdate(float tpf) {
// Each frame, determine the distance to each sphere/box from the camera.
// If the object is > 25 units away, switch in the Box. If it's nearer, switch in the Sphere.
// Normally we wouldn't do this every frame, only when player has moved a sufficient distance, etc.
for (int i = 0; i < 10; i++) {
Vector3f location = locations[i];
float distance = location.distance(cam.getLocation());
instancedNode.detachChild(boxes[i]);
instancedNode.detachChild(spheres[i]);
if( distance > 25.0f ) {
instancedNode.attachChild(boxes[i]);
} else {
instancedNode.attachChild(spheres[i]);
}
}
instancedNode.instance();
}
private Material createInstancedLightingMaterial() {
Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
material.setBoolean("UseMaterialColors", true);
material.setBoolean("UseInstancing", true);
material.setColor("Ambient", ColorRGBA.Red);
material.setColor("Diffuse", ColorRGBA.Red);
material.setColor("Specular", ColorRGBA.Red);
material.setFloat("Shininess", 1.0f);
return material;
}
private void addAmbientLight() {
AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f));
rootNode.addLight(ambientLight);
}
private void addPointLight() {
PointLight pointLight = new PointLight();
pointLight.setColor(ColorRGBA.White);
pointLight.setRadius(100f);
pointLight.setPosition(new Vector3f(10f, 10f, 0));
rootNode.addLight(pointLight);
}
private void addCrossHairs() {
BitmapText ch = new BitmapText(guiFont, false);
ch.setSize(guiFont.getCharSet().getRenderedSize()+4);
ch.setText("+"); // crosshairs
ch.setColor(ColorRGBA.White);
ch.setLocalTranslation( // center
settings.getWidth() / 2 - ch.getLineWidth() / 2,
settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
guiNode.attachChild(ch);
}
private CollisionResult pickFromCamera() {
CollisionResults results = new CollisionResults();
Ray ray = new Ray(cam.getLocation(), cam.getDirection());
instancedNode.collideWith(ray, results);
return results.getClosestCollision();
}
}
The stack trace that I get is:
java.lang.NullPointerException
at com.jme3.collision.bih.BIHTree.collideWithRay(BIHTree.java:414)
at com.jme3.collision.bih.BIHTree.collideWith(BIHTree.java:471)
at com.jme3.scene.Mesh.collideWith(Mesh.java:1035)
at com.jme3.scene.Geometry.collideWith(Geometry.java:472)
at com.jme3.scene.Node.collideWith(Node.java:615)
at com.codealchemists.jme.test.instancing.TestInstancedNodeAttachDetachWithPicking.pickFromCamera(TestInstancedNodeAttachDetachWithPicking.java:148)
at com.codealchemists.jme.test.instancing.TestInstancedNodeAttachDetachWithPicking.access$000(TestInstancedNodeAttachDetachWithPicking.java:23)
at com.codealchemists.jme.test.instancing.TestInstancedNodeAttachDetachWithPicking$1.onAction(TestInstancedNodeAttachDetachWithPicking.java:77)
at com.jme3.input.InputManager.invokeActions(InputManager.java:171)
at com.jme3.input.InputManager.onMouseButtonEventQueued(InputManager.java:448)
at com.jme3.input.InputManager.processQueue(InputManager.java:867)
at com.jme3.input.InputManager.update(InputManager.java:917)
at com.jme3.app.LegacyApplication.update(LegacyApplication.java:724)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:246)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:153)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:193)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:234)
at java.lang.Thread.run(Thread.java:745)
When I set a breakpoint, the worldbounds of the InstancedGeometry under the InstancedNode is null, which seems to be the cause - but updating model bounds or geometry states of the InstancedNode doesnât seem to help.
This is on windows, with jMonkeyEngine 3.3.2-stable.
Thanks,
Duncan