Wierd picking output[SOLVED]

Hey All,



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++) {

               hitItems = results.getPickData(i).getTargetMesh()
                     .getParentGeom();
            }

         }
         shots++;
         results.clear();
         System.out.println("Hits: " + hits + " Shots: " + shots + " : "
               + hitItems.getName());
      }
   }

}



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();

      } catch (final IOException e) {
         FastLogger.log(e);
         throw new RuntimeException(e);
      }




Any help/direction would be appreciated.

thanks,
D

…just to add to that, it does look like some kind of name that the .obj to .jme conversion is giving to my in game model"



Aug 25, 2008 2:35:01 PM com.jmex.model.converters.ObjToJme processLine

INFO: Object:Cube.001_Cube.002

Aug 25, 2008 2:35:01 PM com.jme.scene.Node <init>

INFO: Node created.

Aug 25, 2008 2:35:01 PM com.jme.util.geom.GeometryTool minimizeVerts

INFO: batch: Cube.001_Cube.002: Batch 0 old: 1433 new: 1433

Aug 25, 2008 2:35:01 PM com.jme.scene.Node attachChild

INFO: Child (Cube.001_Cube.002) attached to this node (obj file)

Aug 25, 2008 2:35:01 PM com.jme.util.geom.GeometryTool minimizeVerts

INFO: batch: Cube.001_Cube.002: Batch 0 old: 338 new: 338

Aug 25, 2008 2:35:01 PM com.jme.scene.Node attachChild

INFO: Child (Cube.001_Cube.002) attached to this node (obj file)

Aug 25, 2008 2:35:01 PM com.jme.util.geom.GeometryTool minimizeVerts

INFO: batch: Cube.001_Cube.002: Batch 0 old: 85 new: 85

Aug 25, 2008 2:35:01 PM com.jme.scene.Node attachChild

INFO: Child (Cube.001_Cube.002) attached to this node (obj file)

Aug 25, 2008 2:35:01 PM com.jme.util.geom.GeometryTool minimizeVerts

INFO: batch: Cube.001_Cube.002: Batch 0 old: 209 new: 209

Aug 25, 2008 2:35:01 PM com.jme.scene.Node attachChild

INFO: Child (Cube.001_Cube.002) attached to this node (obj file)

Aug 25, 2008 2:35:01 PM com.jme.util.geom.GeometryTool minimizeVerts

INFO: batch: Cube.001_Cube.002: Batch 0 old: 141 new: 141

Aug 25, 2008 2:35:01 PM com.jme.scene.Node attachChild

INFO: Child (Cube.001_Cube.002) attached to this node (obj file)

Aug 25, 2008 2:35:01 PM com.jme.util.geom.GeometryTool minimizeVerts

INFO: batch: Cube.001_Cube.002: Batch 0 old: 285 new: 285

Aug 25, 2008 2:35:01 PM com.jme.scene.Node attachChild

INFO: Child (Cube.001_Cube.002) attached to this node (obj file)

Aug 25, 2008 2:35:01 PM com.jme.scene.Node <init>

INFO: Node created.

I have a List of the relevant references to Nodes in my Game. (the Nodes of player and enemies for example)



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 :slight_smile:

This works pretty good for me.



You need to have some kind of way to tell if the Spatial you picked is relevant to you or not.

Either check its name or compare it with a list of references which you maintain.

I will give that a try.



Thanks Core Dump.

Core-Dump said:

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:

so long,
Andy

…hey Andy,



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.



:wink:

use getparent to get the object

darrenl said:

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

doh, i guess dhdd is right.

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?



…sorry, still a bit confused.

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  :expressionless:



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.

OK…I think I understand now :wink:



…thanks guys.



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.

My approach is a bit of both.



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.

Alric…I like that approach.



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.

Sure:



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(Node geomNode, GameActionable entity) {
      for(Spatial geometry : geomNode.getChildren() ) {
         if(geometry instanceof Node) {      // TODO: Penance
            Node childNode = (Node)geometry;
            registerGeometryEntity(childNode, entity);
         } else {
            registerGeometryEntity(geometry, entity);
         }
      }
   }
   
   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.

Thanks Alric…I'll play around with that a bit.

I got it working.



For those who are interested, here is the code.



First, the mouse targeting code:




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++) {

               hitItems = results.getPickData(i).getTargetMesh();
               shipTarget = RegisterEntity.getRegisterEntity().get(
                     hitItems.getParentGeom());
            }

         }
         shots++;
         results.clear();
         // System.out.println("Hits: " + hits + " Shots: " + shots + " : "
         // + hitItems.getName());
         System.out.println("Hits: " + hits + " Shots: " + shots + " : "
               + shipTarget);
      }
   }

}




The registering class that supports this:



public class RegisterEntity {

   public static HashMap<Spatial, Node> entityXRef = new HashMap<Spatial, Node>();

   public void registerGeometryEntity(final Node geomNode, final Node ship) {
      for (final Spatial geometry : geomNode.getChildren()) {
         if (geometry instanceof Node) { // TODO: Penance
            final Node childNode = (Node) geometry;
            registerGeometryEntity(childNode, ship);
         } else {
            registerGeometryEntity(geometry, ship);
         }
      }
   }

   public void registerGeometryEntity(final Spatial geometry, final Node ship) {

      entityXRef.put(geometry, ship);
      // System.out.println(entityXRef);
   }

   public static HashMap<Spatial, Node> getRegisterEntity() {
      return entityXRef;
   }

}



Now, to use it you just declare:

protected RegisterEntity regWhatever;

and call:

regShip.registerGeometryEntity(whatevergeometery, whatever node);

If any of the veterans see anything out of place, let me know.

Thanks to all who helped. Big learning experience for me.

Cheers,
D