I'm putting in some mouse picking functionality into my game. After reading an eye-bleeding amount of threads on the topic, I came down to this implementation, which should be familiar to most:
public class MouseTarget extends MouseInputAction {
private final Camera camera;
private final Node scene;
private float shotTime = 0;
private int hits = 0;
private int shots = 0;
private Spatial hitItems;
private final AbsoluteMouse am;
private PickResults results;
public MouseTarget(final Camera camera, final Node scene) {
this.camera = camera;
this.scene = scene;
am = MouseClickMove.getAm();
}
@Override
public void performAction(final InputActionEvent evt) {
// TODO Auto-generated method stub
shotTime += evt.getTime();
if (MouseInput.get().isButtonDown(2) && (shotTime > 0.1f)) {
shotTime = 0;
results = new BoundingPickResults();
final Vector2f screenPos = new Vector2f();
// Get the position that the mouse is pointing to
screenPos.set(am.getHotSpotPosition().x, am.getHotSpotPosition().y);
// Get the world location of that X,Y value
final Vector3f worldCoords = DisplaySystem.getDisplaySystem()
.getWorldCoordinates(screenPos, 0);
final Vector3f worldCoords2 = DisplaySystem.getDisplaySystem()
.getWorldCoordinates(screenPos, 1);
// Create a ray starting from the camera, and going in the direction
// of the mouse's location
final Ray mouseRay = new Ray(worldCoords, worldCoords2
.subtractLocal(worldCoords).normalizeLocal());
// Does the mouse's ray intersect the box's world bounds?
results.clear();
scene.findPick(mouseRay, results);
hits += results.getNumber();
hitItems = null;
if (results.getNumber() > 0) {
for (int i = 0; i < results.getNumber(); i++) {
When I do some picking on a imported .obj model, I get this output for the hitItems.getName() call:
"Hits: 89 Shots: 46 : Cube.001_Cube.002"
If I select a planet, the name comes back as "Planet"...heck, my skycube will even get selected if I just pick around and come out as "west", or "east"...depending on what wall of the skycube gets intersected by the Ray. But, I have no idea what the "Cube.001_Cube.002" output means. Is it the name of a triangle within my model? At a loss. Anyone else get these kind of results?
My basic model importer is pretty typical:
public Spatial buildShipModel() {
Spatial model = null;
try {
final ObjToJme converter = new ObjToJme();
final ByteArrayOutputStream BO = new ByteArrayOutputStream();
final URL objFile = TestObjJmeWrite.class.getClassLoader()
.getResource(shipName);
converter.setProperty("mtllib", objFile);
converter.setProperty("texdir", objFile);
System.out.println("Starting to convert .obj to .jme");
converter
.convert(new BufferedInputStream(objFile.openStream()), BO);
// load as a TriMesh if single object
// model = (TriMesh) BinaryImporter.getInstance().load(
// new ByteArrayInputStream(BO.toByteArray()));
model = (Spatial) BinaryImporter.getInstance().load(
new ByteArrayInputStream(BO.toByteArray()));
// load as a node if multiple objects
// model = (Node) BinaryImporter.getInstance().load(
// new ByteArrayInputStream(BO.toByteArray()));
model.setModelBound(new BoundingSphere());
model.updateModelBound();
When i Pick something, i check (recursively to the root node) if the Node i picked is in this list, if its not, i picked some trash :)
This works pretty good for me.
As far as I know thats not how picking is intended to work. If you retrieve the pickingResults from a Node, it checks for itself and if its picked, it checks for all the pickingResults from the childNodes and so on right down to the leaves. so if you have a background Quad and a smaller quad in front of it (on which you click) and both have a common parent and if you check for a pick on that parent you will get the pickResults with the parent and both the children.
the only thing you need to do is: every Node and its subtree has to have boundingVolumes set and updated.
no recursion on your own needed, just one call. also like that its heavily optimized because if a parent boundingvolume is not intersecting the Ray of the pick, none of the spatials in its subtree can be and dont have to be checked. :wink:
Thanks. The picking does seem to work in my case…it's just not returning the node name that I expect it too. For the Skybox, it's reporting the face (i.e. north, south, west etc). For my planet, the pick returns the name of "Planet" (…the name I've given the textured sphere…). However, for the imported .obj model, it's returning "Cube.001_Cube.002"…and I have no clue where it's getting that from, but I know it's from the model.
So, I know the picking functionality is working…it's just not working the way I need it to. What I want it to do, is return the object name of the .obj model that I click…not some arbitrary identifier that it's given during the .obj to .jme converstion.
What I want it to do, is return the object name of the .obj model that I click...not some arbitrary identifier that it's given during the .obj to .jme converstion.
if the node that has the model beneath it has a boundingvolume, it will also be in the PickingResults ... I think. :wink:
EDIT: Check that, no it won't. sorry, my mistake, sry
i think i added the recursive check because i didn't get pickresults for my enemy (obj model attached to a DynamicPhysicNode which still dosen't work) but that must be a problem i my code then.
…so, what am I actually seeing then for this ""Cube.001_Cube.002" output that I'm getting when picking an imported .obj model? Is it the boundingVolume that I'm getting? Is it the node name?
since in the pickingResults you can retrieve the 'TargetMesh' you will only get the Geometries it hits, which means it will not give you a Node … so no complete model picking (unless that model is one mesh). I see no other possibility than doing it CoreDumps way, recursively going up towards the rootNode.
Sorry for bullshitting you earlier
EDIT: What you see there are the names of the Geometries you picked (The names are taken from the model file and are not changed when imported.) You can always change the names of the parts in the model editor.
What I'll do is go into Blender and see if I can do anything in there to change the geometries. On top of that, I'll start to look at Core-Dump's recursive method. I think there was a more detailed thread somewhere with an example on how to do that.
I maintain a "register" of all entities in the game that I might want to pick.
Every time I add an entity into the game, at that point I walk through it's geometry.
For each mesh that belongs to the entity, I put an entry in the register. It is keyed on the mesh, and it stores the entity.
Then, whenever I get a pick result, I can just do a register lookup using the mesh I found, and get back a game entity that I can work with.
This approach seems to allow me easy access to the data I want, with all the work (which isn't much) being one-time rather than every time I do a pick.
So, it almost sounds like an Observer-like pattern there. Hmmmm…
Any chance you can give me a very quick code example I can leverage to complete the picture in my head. Not asking for the full solution as I'd like to learn as much as I can…but something that will give me a push in the right direction.
This is a snippet from my EntityRegister class. You pass in either a node or other kind of spatial, and it adds all geometry under that to the register.
public void registerGeometryEntity(Spatial geometry, GameActionable entity) {
entityXRef.put(geometry, entity);
}
entityXRef is just a HashMap
public HashMap<SceneElement, GameActionable> entityXRef;
Then when I have a pick result, I this is one instance of how I used it:
GameActionable d = EntityRegister.getEntityRegister().entityXRef.get(tg.getParentGeom());
tg stands for target geometry, and it is the GeomBatch returned by the pick (this is JME1 code).
I have used getParentGeom as I didn't bother to add every GeomBatch to the lookup - there's no reason to with my setup - but you easily could. Should not be needed under JME2.
public class MouseTarget extends MouseInputAction {
private final Camera camera;
private final Node scene;
private float shotTime = 0;
private int hits = 0;
private int shots = 0;
private GeomBatch hitItems;
private final AbsoluteMouse am;
private PickResults results;
private Node shipTarget;
public MouseTarget(final Camera camera, final Node scene) {
this.camera = camera;
this.scene = scene;
am = MouseClickMove.getAm();
}
@Override
public void performAction(final InputActionEvent evt) {
// TODO Auto-generated method stub
shotTime += evt.getTime();
if (MouseInput.get().isButtonDown(2) && (shotTime > 0.1f)) {
shotTime = 0;
results = new BoundingPickResults();
final Vector2f screenPos = new Vector2f();
// Get the position that the mouse is pointing to
screenPos.set(am.getHotSpotPosition().x, am.getHotSpotPosition().y);
// Get the world location of that X,Y value
final Vector3f worldCoords = DisplaySystem.getDisplaySystem()
.getWorldCoordinates(screenPos, 0);
final Vector3f worldCoords2 = DisplaySystem.getDisplaySystem()
.getWorldCoordinates(screenPos, 1);
// Create a ray starting from the camera, and going in the direction
// of the mouse's location
final Ray mouseRay = new Ray(worldCoords, worldCoords2
.subtractLocal(worldCoords).normalizeLocal());
// Does the mouse's ray intersect the box's world bounds?
results.clear();
scene.findPick(mouseRay, results);
hits += results.getNumber();
hitItems = null;
if (results.getNumber() > 0) {
for (int i = 0; i < results.getNumber(); i++) {