Null Node BoundingVolumes?

In trying to diagnose why my node intersections are always returning true, I added code to attach a Box to each node to show its BoundingBox rendered as a wireframe.



Problem is, I keep getting NPE on the results of node.getWorldBounds(). And sure enough, node.worldBounds is null in the debugger nomatter what I try.



In inspecting the code, this seems intentional; worldBounds seems mainly used for spatials, not nodes. Can't tell for sure; I get lost in the twisty little passages of updateThisOrThats.



So the question; given a node, how do I reliably determine its bounds or do intersections. Should I be working with spatials, not nodes? And if not, what is getWorldBounds() for Nodes actually there for?



PS: Nodes are created with



Spatial spatial = (Spatial) BinaryImporter.getInstance().load(bis);

spatial.setName(objFilePath);

Node node = (Node)spatial;



node.setModelBound(new BoundingBox());

node.updateModelBound();

node.updateGeometricState(0f, true);



and at the root after all children are created



                rootNode.updateWorldBound();

worldBounds seems mainly used for spatials, not nodes.


Actually, all Spatials (Nodes/Geometry) make use of world bounds for all calculations. Here is where a lot of confusion about the API comes in, due to public methods that *shouldn't* be called by the user (mostly a documentation problem on our end).

Node's set/updateModelBound are convience methods to set up model bounds on any geometry attached to them. Node's don't make use of any model bounds themselves. Therefore, when you call:

Node node = (Node)spatial;

node.setModelBound(new BoundingBox());
node.updateModelBound();

You should have your leaf Geometry already attached, otherwise these calls don't really end up doing anything. My suggestion is:

1. Set up the scene graph completely (do not call any update methods or setModelBounds).
2. Call set/updateModelBounds on the root scene node.
3. Call updateGeometricState(0, true) on the root scene node. --- this will set up the world bounds for the entire tree at once.
4. Call updateRenderStates on the root scene node.

Hope that helps, and cleaning up/documenting this process is definately something that needs to be done.

Thanks. Tried that but got exactly the same problem



In simpleSetup()

this.world = controller.loadWorld();

rootNode.attachChild(world.getNode());

rootNode.setModelBound(new BoundingBox());

rootNode.updateModelBound();

rootNode.updateWorldBound();

rootNode.updateRenderState();

rootNode.updateGeometricState(0f, true);

world.attachBoxes();



NPE occurs in world.attachBoxes() in 2nd line of this; bb is null

void showBoundingBox()

{

BoundingBox bb = (BoundingBox)node.getWorldBound();

Box box = new Box(name, bb.getCenter(), bb.xExtent, bb.yExtent, bb.zExtent);

DisplaySystem display = DisplaySystem.getDisplaySystem();

WireframeState wireframeState = display.getRenderer().createWireframeState();

wireframeState.setLineWidth(1.0f);

wireframeState.setFace(WireframeState.WS_FRONT_AND_BACK);

box.setRenderState(wireframeState);

node.attachChild(box);

node.updateRenderState();

}

this.world = controller.loadWorld();



Does this load any geometry? Or do you only start attaching geometry here:



world.attachBoxes();



if controller.loadWorld() does not have any Geometry objects, the setting of the model bounds isn't going to have anything to se the model bounds on.

Here's the full story. World contains numerous subobjects, all inheriting AbstractObject. Each object is in a separate .obj file (since I never found a way to make groups (g) or object (o) lines work right). I use XML to splice them all together by name.



Unless I did something stupid; yes, geometry is fully loaded during the recursion. I'll double check for stupidity while you read this.



public AbstractObject(Element element) throws Fault

{

this.name = element.getAttributeValue("name");

String objFile = OBJ_DIR+"/"+element.getName()+".obj";

try

{

this.node = loadFile(objFile);

node.setName(name+" Node");

}

catch (FileNotFoundException e)

{

log.error("No such file: "+objFile);

this.node = new Node(name+" fake");

}

catch (Exception e)

{

e.printStackTrace();

this.node = new Node(name+" fake");

}



//

// Iterate over contained elements, using reflection to invoke the

// Constructor(Element e) for the class in "com.binary.nagumo.models"

// whose name matches the element's name.

// The hardcoded package name will be generalized in the

// future. The resulting sub-objects are maintained by name in childMap to

// help each constructor find its sub-components.

for (Iterator i = element.getChildren().iterator(); i.hasNext(); )

{

Element e = (Element)i.next();

String type = e.getName();

try

{

Class[] argTypes = new Class[] { Element.class };

Object[] argValues = new Object[] { e };

Class cls = Class.forName("com.binary.nagumo.models."+type);

Constructor constructor = cls.getConstructor(argTypes);

AbstractObject child = (AbstractObject)constructor.newInstance(argValues);

node.attachChild(child.node);

childMap.put(child.name, child);

}

catch (Exception ex)

{

throw new Fault("Exception", ex);

}

}



String translateByStr = element.getAttributeValue("translateBy");

if (translateByStr != null)

{

Vector3f translateBy = FloatUtil.parseVector3f(translateByStr);

node.setLocalTranslation(translateBy);

}

log.info(this+" loaded");

}

static Node loadFile(String objFilePath) throws Exception

{

LoggingSystem.getLogger().setLevel(Level.WARNING);

FormatConverter converter = null;

if (objFilePath.endsWith(".obj"))

converter = new ObjToJme();

else if (objFilePath.endsWith(".3ds"))

converter = new MaxToJme();

else if (objFilePath.endsWith(".md2"))

converter = new Md2ToJme();

else if (objFilePath.endsWith(".md3"))

converter = new Md3ToJme();

else if (objFilePath.endsWith(".ms3d"))

converter = new MilkToJme();

else throw new Exception("Unknown format: "+objFilePath);



URL url = new File(objFilePath).toURL();

converter.setProperty("mtllib", url);

converter.setProperty("texdir", url);



    ByteArrayOutputStream bos = new ByteArrayOutputStream();

converter.convert(url.openStream(), bos);



ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());

BinaryImporter importer = BinaryImporter.getInstance();

Node n = (Node)importer.load(bis);

return n;

}

This may be clue. BinaryImporter's returning Node's for some .obj files, TriMeshes for others, so some Nodes will contain Nodes, not Geometry. This might break bounds propagation



For example, here's a .obj file from which BinaryImporter creates a Node instead of a Trimesh. All are created the same way (Sketchup export) but some are loaded one way, others the other.


Alias OBJ Model File

Exported from SketchUp, © 2000-2005 @Last Software, Inc.

File units = feet


mtllib Bomb.mtl

g Bomb1 Model

usemtl Concrete
v 204.448 0.384718 -120.864
vt -0.117208 0.66746
vn -4.15604e-16 0.49989 0.866089
v 204.448 0.584 -120.947
vt -0.116085 0.721374
vn -5.25279e-16 0.258748 0.965945
v 204.393 0.591287 -120.947
vt -0.129902 0.723345
vn 0.0669689 0.249931 0.965945
v 204.341 0.398795 -120.864
vt -0.143901 0.671269
vn 0.129381 0.482856 0.866089
f 1/1/1 2/2/2 3/3/3 4/4/4

usemtl Concrete
v 204.297 0.233498 -120.733
vt 0.190628 0.88454
vn 0.182986 0.682913 0.70721
vt 0.210568 0.934643
vn 0.129381 0.482856 0.866089
v 204.241 0.440068 -120.864
vt 0.186683 0.947154
vn 0.249945 0.432917 0.866089
v 204.156 0.291867 -120.733
vt 0.15685 0.902232
vn 0.353502 0.612283 0.70721
f 5/5/5 4/6/6 6/7/7 7/8/8

---truncated here

I've not verified the consequences of Nodes within Nodes, only that some .obj files to load as Nodes, others as TriMeshes.

Anyway, with these changes, it loads now:

public AbstractObject(Element element) throws Fault
{
this.name = element.getAttributeValue("name");
String objFile = OBJ_DIR+"/"+element.getName()+".obj";
try
{
Savable s = loadFile(objFile);
if (s instanceof Node)
{
this.node = (Node)s;
node.setName(name+" node");
}
else if (s instanceof TriMesh)
{
this.node = new Node(name+" node");
TriMesh t = (TriMesh)s;
t.setName(name+" trimesh");
node.attachChild(t);
}
else throw new Fault("Unexpected loader result:"+s);
}

Anybody knows, why some .obj files are imported as nodes, and some as trimesh?

Maybe, if a model contains more than one mesh, they're attached to a node directly, but if theres only one mesh, a trimesh is created?

Yes, I think your assumption is correct.