Instantiating copies of a model: best practices?

I’m handling model instantiation by cloning the Spatial returned by assetManager.loadModel. Is this is correct way to do this in jMonkey?



[java]

public MonkeyGame()

{

// Load resources

Monkey.setClassSpatial(assetManager.loadModel(“Models/Oto/Oto.mesh.xml”));

rootNode.attachChild(Monkey.getClassNode());

// … etc

}

[/java]

[java]

public Monkey(Vector3f position)

{

// Get a copy of the model and move it to the correct position

spatial = classSpatial.clone();

spatial.setLocalTranslation(position);

// Attach to the scene graph

classNode.attachChild(spatial);

// … etc

}[/java]

Just use assetManager.loadModel() repeatedly, it will share data as efficiently as possible.

1 Like

You mean that if the model has already been loaded it won’t import it a second time? Nifty if so.



Do you think my Monkey class should extend Spatial or simply incorporate a Spatial object as it does now? At the moment to find which Monkey corresponds to a collision (collisionResults.getClosestCollision().getGeometry()) I need to have mapped the Monkey objects to their Spatials. Hence when I add a new Monkey object:



[java] public void addMonkey(Monkey new_monkey)

{

monkeys.put(new_monkey.getSpatial(), new_monkey);

}[/java]



And when I want to find the corresponding to a collision Monkey:



[java]monkeys.get(results.getClosestCollision().getGeometry());[/java]



Doesn’t quite work yet, but it should in theory (last week I gave Ogre3D a try and used a similar system), so I’ve probably just made a silly mistake somewhere.

It won’t be loaded a second time. We have a best practices article in the documentation. You should use Controls, extending is not a good idea.

1 Like

Okay. You mean this https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:custom_controls ?



edit: I’m still not sure how to communicate to the Controls that the Spatial they’re sitting on has been, for instance, clicked. I’m using Spatial.setUserData() to save the hashCode of my Monkey “babysitter” object, which can then be found in a HashMap. In theory I should be able to take the geometry that the ray-cast collides with and read the user data to find the Monkey in this Map. Thing is the two Spatials don’t match up o_O The ray-cast is returning a different Spatial object from the one I’ve added user data to.

Spatial getcontroll //find yours and cast to your class// do stuff

1 Like

You might find you need to walk up the scene graph too.



i.e. if you attach your control to a node the first collision will be a geometry inside that node - so you need to check the parents of your geometry and parents of that etc looking for your control until either you don’t find it or you reach the root node.

1 Like
@zarch said:
You might find you need to walk up the scene graph too.
i.e. if you attach your control to a node the first collision will be a geometry inside that node - so you need to check the parents of your geometry and parents of that etc looking for your control until either you don't find it or you reach the root node.

edit: I can confirm that this is indeed what was causing the problem:

[java] Monkey.getNode().collideWith(ray, results);
if(results.size() > 0)
{
// Attempt to find the Monkey corresponding to the click
try
{
// Extract the identifier from the Spatial, search for it in the Map
Integer id =
results.getClosestCollision().getGeometry().getUserData("Monkey_id");
Monkey clicked = getMonkey(id);

// Select the Monkey that was clicked on
clicked.setSelected(!clicked.isSelected());
}
catch(NullPointerException ex)
{
System.out.println("Failed to find Monkey attached to "
+ results.getClosestCollision().getGeometry().hashCode());
for(String s : results.getClosestCollision().getGeometry().getUserDataKeys())
System.out.println(s);
}
}[/java]

We need to replace

[java]results.getClosestCollision().getGeometry().getUserData("Monkey_id");[/java]

With

[java]results.getClosestCollision().getGeometry().getParent().getUserData("Monkey_id");[/java]

Though it does make more sense to use Controls now that I see how they work ;)

I’d still recommend walking the scene graph rather than just assuming it’s on getParent. What you have works but could potentially break in the future if someone made an unrelated change to the model.

@zarch said:
I'd still recommend walking the scene graph rather than just assuming it's on getParent. What you have works but could potentially break in the future if someone made an unrelated change to the model.

You mean going through all the children of Monkey.getNode() and seeing if any of them match?

No, the other way around. All the parents of the intersected geometry.



[java]Node node = geom.getParent();

while (node != null) {

node = node.getParent();

// Check for match, break loop and process if found

}

[/java]



That way even if your geometry is a few layers deep in the scene graph from your user data/control you still detect the intersection. Especially if you are doing this on a click that’s a manual operation so low frequency so you can afford a few extra cycles to make it robust.

1 Like

Thanks for the tip, here’s my implementation :slight_smile:



[java] // Search for collisions with Monkeys

Monkey.getNode().collideWith(ray, results);

if(results.size() > 0)

{

// Attempt to find the Monkey corresponding to the click

Monkey clicked = null;

Spatial spatial = results.getClosestCollision().getGeometry();

// NB - We don’t know how many levels down the Geometry object is!

while(spatial != null && clicked == null)

{

// Extract the Monkey from the Spatial if possible

clicked = spatial.getControl(Monkey.class);

spatial = spatial.getParent();

}

if(clicked != null)

// Select the Unit that was clicked on

clicked.setSelected(!clicked.isSelected());

else

throw new Exception(“Confirm rule: all children of Monkey.node have a Monkey Control attached to them.”);

}[/java]



For the record, I’m trying to make a simple RTS demo along the lines of what I made to try out Ogre3D. I have a video, but I should probably mark it as NSFW because in a fit of inspiration I used a cartoon hairy bum for terrain. So basically it’s a bunch of robots running around on a hairy bum. Here’s the video: https://www.youtube.com/watch?v=mUqik2ZgGd0.



If I continue with jMonkey (which is likely: it’s so much easier to use than Ogre3D) I’ll probably borrow some navigation-mesh pathing code from MonkeyZone. I’m not sure yet whether I should use bullet for keeping units at terrain-height: I think TerrainQuad.getHeight should be sufficient really. I don’t want to use more than I need because I’d like to have a decent number of units on screen. Things like arrows and gibs will need physics though. I also need a way of stopping the Spatials managed by my Monkey Control from intersecting.



Apparently among the best practices for using Controls is to use different ones for different behaviour: I might end up with a MonkeyPathingControl, a MonkeyCollisionControl, etc. A very novel way of programming but quite pleasant :slight_smile:

Ouch, there is a terrible high pitched whine on that video - I had to turn it off :s



For just terrain following a full physics engine is overkill. Most people just cast a ray down from the character to the terrain.



You implementation looks good - throwing a non-subclassed exception is bad though :stuck_out_tongue:

:frowning: My in-built microphone is terrible - I don’t have this problem on my computer, but on some of my friends’ there does seem to be a whine, sorry :confused: Maybe I should buy an actual microphone. In the meantime you can just cut the sound I suppose.



I’d like to avoid raycasting if it’s something as simple as looking up the 4 neighbouring height-map cells and interpolating between them. Here’s what I’m using so far. I’m assuming that all my TerrainQuad instances will be children of a single Node which I’ve put under MyTerrain.node. I get search for a give height by calling MyTerrain.getHeight:



[java]public static float getHeight(Vector2f position)

{

// Search for a TerrainQuad that corresponds to the specified position

for(Spatial spatial : MyTerrain.node.getChildren())

{

MyTerrain terrain = spatial.getControl(MyTerrain.class);

if(terrain != null)

{

float result = terrain.quad.getHeight(position);

if(result != Float.NaN)

return result;

}

}

// Return NaN if no TerrainQuad underlies the requested position

return Float.NaN;

}[/java]



Once this is in place all I need to do is add a few lines to Monkey.controlUpdate:



[java] // Always stay above terrain

Vector3f pos = spatial.getLocalTranslation();

float terrain_h = Terrain.getHeight(new Vector2f(pos.x, pos.z));

spatial.setLocalTranslation(pos.x, terrain_h, pos.z);[/java]



edit: I suppose I could create a ConfirmRuleException class :wink: