Getting Class Properties through Mouse Pick

I'm building a model of the solar system … each planet is a node …  a very short example …



public class Earth extends Node {

  private long population = 100000;

 

  public Earth(){

  }

};



ok so I add this planet to the scene graph … I can click on the model and get the name with …



results.getPickData(i).getTargetMesh().getParentGeom().getName()





but how would I access the custom properties like population?? If I click on a planet I not only want to see its name but all the properties I add such as population … climate type … resources … ect.

Wow that's a great feature, I've read a few books on Java and can't imagine why I don't remember reading about this idea, or it could be I just forgot cause of the wealth of information I try to take in at once, I want to know it all … about software that is but I know its not possible.  I find this singleton info very valuable and will most likely find many uses for it … thanks. I completely understood your last post about generalizing, I actually started building this solor system model from some of the JME examples …  I  realized this and knew I should start from scratch. I got so involved in trying out picking, rotation, and translation ect that I started to go to far. I'm going to start from scratch and build from everything I've learned from this thread thanks to all. Wow a singleton … what a concept … :slight_smile: this is going to work out well … I love Java  :smiley: I'll be back!!   

Maybe you should have a separate class called Earth which contains all your attributes.

Then have a class EarthNode that extends Node.

Then set the userData property for the node:

earthNode.setUserData("earth", earth);


Then you can retrieve the userData from the node you clicked.

Roslan

Let me be more precise.


Planet earth = new Planet(...);
earth.setPopulation(100000);
PlanetNode earthNode = new PlanetNode(...);
scene.add(earthNode);
earthNode.addUserData("data", earth);



After picking:

PlanetNode earthNode = (PlanetNode ) results.getPickData(i).getTargetMesh().getParentGeom();
Planet earth = earthNode.getUserData("data");
// use the attributes from earth


Though I thought there was a more eloquent way of doing what you wanted, you could in theory keep all your code the same and simply have a HashMap (or some data structure) that holds the data of "Planet" objects by name.



So a Hashmap with a Planet object indexed with the name "earth". That way when you pick and get the name you can then get the object from the Hashmap. Imo this might be a little bit better if you ever decide to have a database backed system or if you want to manipulate the data, it is separated from the GUI (3D interface). Not to mention not much of your code changes.



The main reason why I advance this idea is because I am used to JEE programming where

business logic and presentation must be separate… but thats not always true/good with 3D programming I guess.





so for intsance:



private static HashMap<String, Planet> planets = new HashMap<String, Planet>();



Planet earth = new Planet(…);

earth.setPopulation(1000);

planets.put("earth",earth);







To get data:

Planet planet = planets.get(results.getPickData(i).getTargetMesh().getParentGeom().getName());

ahhh ok I see … I'll get back to you and see how it goes. I especially like the hashmap Idea.

The whole idea is your POJO class Planet should not know anything about 3D shapes or world coordinates. Don't mix them in the same class.

Ok the HashMap worked great thanks, only one issue … how do I now manipulate the data like you said … I built a separate class called PlanetData like you said … when the earth node is build it sets all the PlanetData …



protected void BuildEarth() {

  // set up planet data

    PlanetData earthData = new PlanetData();

    earthData.setClimate(3);

    earthData.setPopulation(6000000l);

    planetData.put("Earth",earthData); // add to HashMap



    // Earth

    earth = new Earth(planetData); // pass HashMap to earth node to minipulate the data

    this.attachChild(earth);

   

   

}



now in the actual earth node class I set up a behaviour to make the earth orbit around the sun, On every full orbit I want the population to increase so as you can see in the above code I passed the HashMap to the earth node. In the earth behaviour I have this …



protected void process() {

    timer.update();

    float interpolation = timer.getTimePerFrame();

if (interpolation < 1) {

orbitAngle = orbitAngle + (interpolation * 1.025f);

rotateAngle = rotateAngle + (interpolation * 0.05f);

moonOrbitAngle = moonOrbitAngle + (interpolation * 0.09f);

      if (orbitAngle > 360) {

      orbitAngle = 0;

      planetData.get("Earth").setPopulation(5); // update population ???

      }

      if (rotateAngle > 360){

      rotateAngle = 0;

      }

      if (moonOrbitAngle > 360){

      moonOrbitAngle = 0;

      }

    }



orbitEarth.fromAngleAxis(orbitAngle, axis);

earthOrbit.setLocalRotation(orbitEarth);



orbitMoon.fromAngleAxis(moonOrbitAngle, axis);

moonOrbit.setLocalRotation(orbitMoon);



earth.getLocalRotation().fromAngles(0, rotateAngle,0);

}





as you can see when the orbit reaches 360 this would be a full orbit so update the earth data … but when I click with the mouse the data never seems to change, all I get are the default values … why??

In PlanetData simply add a method  "addToPopulation(int i){

this.population = this.population+i;

}



Then, instead of "setPopulation(5)", use planetData.get("Earth").addToPopulation(5);





I assume that each year it is set to 5 instead of adding 5, thats why you don't see the values change.



If you dont want to make your own method, then simply use:



planetData.get("Earth").setPopulation(planetData.get("Earth").getPopulation()+5);

I did get it to work before I read your reply, it wasn't anything I was doing wrond I just had the…



planetData.get("Earth").setPopulation(5);



in the wrong place … the if condition was never met so the population never increased, but I got it now thanks.



One thing I would like to ask is … In this game I'm going to have many types of objects … not just planets, so will I have to create a HashMap for each type of object and then a customized MousePick method to pass the HashMap to aswell or is there a way to generalize the HashMap to accept any and all types of objects? To make a Map and a custom MousePick for the hundreds of objects I may have seems like an awful lot of work so before I get into it I'd like to know if there is a better way. Maybe using the UserData of the node may be the way. 

IMHO, using UserData is the way to go. That's what it is designed to do. Each UserData is actually a HashMap already.



Roslan

ahhh I see … ok, I'll get to work and let ya know how it goes.



Thanks for all your help.

Well… the HashMap object can be made:



HashMap planetData = new HashMap<String, Object>();



Then you can put anything you want in it…









In truth for picking if you have 100s of types of objects, you should make an object implement an interface you write:



public interface clickable {

public void clicked();

}



PlanetData implements Clickable{



public void clicked(){

  setPopulation(5);

}

}





and each object implements Clickable, now in your picking code all you need to do is check if the class that you pick implements clickable and click it…



Similarly if an event happens for many types of objects at the end of a year (or something) make that interface and run through the hashmap at the end of the year checking if it is an instanceof ____ and then cast and run the method.







Its hard for me to tell how you have done your code so far, but if all the animation, years etc… is in one java file then to not use "implements" might require many many if statements (the same applies to userData).







Btw, the javadoc says getUserData returns a "Salvable" object (if getUserData is inherited from Spatial, which I assume it is), so I think that might be a misuse?

I did run into the problem you mentioned with userdata being savable, so I scratched that idea. What I've come up with is to just make a HashMap for each type of objects … may it be planets, buildings, workers ect. In the Mouse pick method I will just add a switch statment and depending on what the user is actually viewing the appropriate HashMap will be searched … the only problem I foresee now is when a player is on a planet surface … now he/she may be viewing buildings and workers as well so I will have to find a way to search the correct HashMap … in space all you can view are planets so its works fine. I like the interface idea but I'm not to familiar with them, maybe I should do a little reading.

Interfaces are quite simple, for instance:



public interface myInterface {

  public int getInt();

}





This interface makes any class that implements it, require there to be a "getInt()" method that returns an "int"…



This way 100 different classes can all implement myInterface, and one doesn't need to know what class it is (buillding, planet etc…) just that they have the interface "getInt();" which allows one to call it without worrying.







There is not much more to interfaces, but you can probably find better tutorials… I do suggest one main HashMap for everything, but there is no reason why you can't have separate HashMaps for each object, might turn out better in the end - not sure.

I'm thinking about using the interface mechanism with a HashMap, my question now is how would I check if the node clicked implements the Clickable interface … I've tried a few I ideas but no luck.

Lets assume we have an interface called CanClick which requires all classes implementing to have the method public void click().







HashMap hashMap = new HashMap<String, Object>();







Object obj = hashMap.get("earth");

if (obj instanceof CanClick){

    CanClick clickable = (CanClick)obj;

    clickable.click();

}



Or in one line write: ((CanClick)obj).click();





Did that make sense?

I believe my problem is I'm using the JME Sphere class to model my planets and stars … I built the clickable interface to return a clickName which I added to the sphere class, I also added the implements Clickable to the class but to no avail it just don't work … here is my picking code …



if(results.getPickData(i).getTargetMesh().getParentGeom() instanceof Clickable){

  Clickable clicked = (Clickable)results.getPickData(i).getTargetMesh().getParentGeom(); // should be a Sphere

  StarData star = GalacticConflict.starData.get(clicked.getClickName()); // global HashMap hold all star data

  hitItems += star.getStarName() + " " + results.getPickData(i).getDistance();

  text2.print("Planets: " + star.getPlanets());

SOLVED  :mrgreen:



I have what I believe is a nice elegant solution … let me know what you thing in the main class I have a global HashMap for the objects I want to click …


public static HashMap<String, PlanetData> planetData = new HashMap<String, PlanetData>();
Public static HashMap<String, StarData> starData = new HashMap<String, StarData>();



I build a class called Sol ... for everyone who dosn't know this is the name of our sun, this class is responsible for building the solor system ... here is the method to add the earth ...

protected void BuildEarth() {
      // set up planet data
       PlanetData earthData = new PlanetData();
       earthData.setClimate(3);
       earthData.setPopulation(6000000l);
       GalacticConflict.planetData.put("Earth",earthData);
      
      // Earth
      earth = new Earth(earthData); // pass in earthData so it can be manipulated with behaviors
      this.attachChild(earth);
   }



I now build all the objects I want and make them extend Node ... notice I use the name "planet"  for the Sphere ... since earth is a planet then I'll give the object this generic name ... could be "building" or "Tree" right ... or anything I want the user to be able to click.

public class Earth extends Node {
 ......
   public Earth(PlanetData data){
      .......
      earth = new Sphere("planet",100, 100, 0.9f); // could be any loaded model or mesh
      earth.clickName = "Earth"; // I added clickName to the JME Sphere class it will also be added to any other models I add to the scene
      ......
   }
}



After all that and a few other steps my picking code looks like this ...

nodeType = results.getPickData(i).getTargetMesh().getParentGeom().getName();
                                                          
                   // figure out what type of object was clicked, planet, building, ect.
                   if(nodeType.equalsIgnoreCase("planet")){
                      if(results.getPickData(i).getTargetMesh().getParentGeom() instanceof Sphere){
                         Sphere clicked = (Sphere)results.getPickData(i).getTargetMesh().getParentGeom();
                         PlanetData planet = GalacticConflict.planetData.get(clicked.clickName);
                         text2.print("Climate: " + planet.getClimate());
                         text3.print("Population: " + planet.getPopulation());
                      }
                    }
                   
                   if(nodeType.equalsIgnoreCase("star")){
                      if(results.getPickData(i).getTargetMesh().getParentGeom() instanceof Sphere){
                         Sphere clicked = (Sphere)results.getPickData(i).getTargetMesh().getParentGeom();
                         StarData star = GalacticConflict.starData.get(clicked.clickName);
                         hitItems += clicked.clickName + " " + results.getPickData(i).getDistance();
                              text2.print("Planets: " + star.getPlanets());
                      }
                      
                      
                   }


The only thing I don't like is the global HashMaps ... but I didn't think it was that much of a concern that I should pass these Maps all around the program ... what would you guys say.  All the code works great and the picking is very good, it does exactly what I wanted.
public class Earth extends Node {
......
  public Earth(PlanetData data){
      .......
      earth = new Sphere("planet",100, 100, 0.9f); // could be any loaded model or mesh
      earth.clickName = "Earth"; // I added clickName to the JME Sphere class it will also be added to any other models I add to the scene
      ......
  }
}


This does not make much sense to me.... You should simply have something along the lines:


public class Planet extends Node{
    public Planet(PlanetData data){
      planet  = new Sphere("planet",100, 100, 0.9f); // could be any loaded model or mesh
      planet.clickName = data.getName();
    }
}




Everything that acts similarly should be generalized....

However, if you have it working, perhaps don't worry too much....


You shouldn't need to pass the HashMap around, simply have one class (like Sol) that has the HashMap and give access to the HashMap either by making the HashMap static, or making a singleton out of Sol so that one can get a non-static HashMap.... These are programming practices that you will pick up, don't worry too much about the best elegance, as that comes from experience, seeing how others programs, and learning programming architectures.... But don't worry about passing the HashMap, not a big deal.