Loading a model into a pre-existing Geometry

I’ve been using jMonkey for a few months now, using it for my latest project, and so far I’ve been using simple cubes and spheres as placeholders for everything while I worked on the game logic. Today, for the first time, I was going to start using some models to start making things look better. I was shocked to see that loading a model creates its own Spatial to use and applies the model to that then returns the created spatial. I had gone quickly through the tutorials back when I started, and I must have seen it used this way in the HelloAsset tutorial, but I did not think about it at the time.



The way I have my project set up, the classes that represent my in-game objects are all derived from Geometry. So when I click on something in my game, I can do whatever I need to do to the clicked object because the geometry I clicked is the object.



If I use assetManager.loadModel() to get a model, it is not going to be applied to the class I have created to handle the object. So now I just lost my input handling encapsulation. Is there any way to tell an AssetManager that you want the Spatial it creates to be of a specific class, or to supply it your own for it to apply the model to?



The only other way I can think of at the moment to handle this would be to keep a huge Map<Spatial, MyClass> around that has a mapping for every single Geometry specifying which object it is associated with. Then instead of handling a click on a collision result’s getGeometry() I would do it on the map.get(getGeometry()) instead. This approach seems stupid though, and I’m assuming there is a better way.



So where is my problem? Is it that I’m missing something in the model-loading methods we have available? Does jME not lend itself to doing input handling this way, and if so how are others handling it instead?



If there is a way to apply a model to an already existing Geometry, that would be great and would be the preferable way to deal with this.

I dont know if its the general case, but:

If i load the Ogre Oto Model into a node, there is only one subchild, and thats a geometry!

So, try it out for different models, maybe you can forget about the node that holds the geometry, and just work with the geometry directly.

Maybe I did not explain well enough. “and just work with the geometry directly.” That’s the part I’m asking about.



Let’s say I use the AssetManager to load a model, and I attach that to my scene. So now I have a Geometry in my scene; good. Now let’s say I want to be able to click on the model in-game to do something to it… let’s say it’s an item that I want to pick up (not my situation, but a good, simple example nonetheless).



Using my current style, I could do this very simple example…

pre type="java"
public interface Clickable


{


void click();


}





public class MyItem extends Geometry implements Clickable


{


public void click()


{


getParent().detachChild(this);


Characters.getMainPlayer().getInventory().add(this);


}


}





public class MyGame extends SimpleApplication


{


// “Click” action registered with inputManager, let’s say all it does is call this


public void click()


{


// “point A” and “point B” being from mouse or cam or whatever


Ray r = new Ray(point A, point B);


CollisionResults results = new CollisionResults();


rootNode().collideWith(r, results);





if(results.getClosestCollision().getGeometry() instanceof Clickable)


((Clickable)results.getClosestCollision().getGeometry()).click();


}


}
/pre



In that situation, when you want to handle the item being clicked, it won’t be an “Item” if it’s a model loaded by the asset manager. It will be some Spatial that does not have a click() method. I do the collision check to see what was clicked on, then what? The alternative that I mentioned, using a map, could be something like…



pre type="java"
public interface Click


{


void click();


}





public class MyItem


{


protected Spatial associatedSpatial;





click()


{


// same as before


}


}





public MyGame extends SimpleApplication


{


HashMap<Spatial, Clickable> clickMap;





click()


{


// same ray/results as before


// now instead of the “if it’s a Clickable” instead we do…


Clickable c = clickMap.get(results.getClosestCollision().getGeometry());


if(c != null)


c.click();


}


}
/pre



But that seems very unnecessary, inefficient, “kludgy” and just all around stupid. I will refactor my entire project if I have to, and I will keep a giant map around for this purpose, but before I go ripping the intestines out of this project, I’d like to hear if there’s a better way, as I suspect there has to be.

What i meant with direct approach:



Dont attach the node that contains the geometry (the mesh) to the scenegraph. Attach the geometry directly (could possibly work on single models, could possibly not work on whole scenes)

[java]Node ninjaNode = (Node) assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");

Geometry ninjaGeo = ninjaNode.getChild(0);

MyItem ninjaItem = new MyItem(ninjaGeo);

rootNode.attachChild(ninjaItem);[/java]



If the ninjaNode somehow has more children, then you could MyItem for each child:

[java]

for (int i=0; i<ninjaNode.getChildCount(); i++) {

MyItem item = new MyItem(ninjaNode.getChild(i));

ninjaNode.detachChildAt(i);

ninjaNode.attachChildAt(i, item);

}

rootNode.add(ninjaNode);

[/java]



all the above is pseudocode; that is what comes to my mind right now, hope that helps

I just noticed that someone else was asking about the same thing a few weeks ago. http://hub.jmonkeyengine.org/groups/import-assets/forum/topic/extend-geometry/



At first I didn’t think there was a reasonable answer there either, but the last suggestion was to use the Spatial’s userData. At first I didn’t think that helped the situation, but it sort of does.

pre type="java"
public class MyItem


{


    MyItem()


    {


        Spatial s = load model here


        s.setUserData(“sillyPointer”, this);


    }


}





public class MyGame extends SimpleApplication


{


    public void click()


    {


        …


        Object o = results.getClosestCollision().getGeometry().getUserData(“sillyPointer”);


        if(o != null)


            // do stuff with o here, in my case call its click()


    }


}
/pre



This works, and I think this is what I will do for now. It is almost equivalent to the “keep a giant map hanging around” strategy I put forth, but at least this way each Spatial handles its own keeper locally. Annoying but tolerable.