( Note: the starter tutorials in this wiki are not always up to date with the latest development version of jME. You can find up to date source files for the tutorials here: https://jme.dev.java.net/source/browse/jme/src/jmetest/TutorialGuide/ )
( Note: the code has been updated to latest development version because previous one didn't compile, but code segments explanation were not updated)
This program introduces AbsoluteMouse, AlphaState, InputSystem, Ray, and Intersection. You will learn how to create your own mouse icon and determine if the user is clicking an item on screen.
import java.net.URL; import com.jme.app.AbstractGame; import com.jme.app.SimpleGame; import com.jme.bounding.BoundingBox; import com.jme.image.Texture; import com.jme.input.AbsoluteMouse; import com.jme.input.FirstPersonHandler; import com.jme.input.MouseInput; import com.jme.intersection.BoundingPickResults; import com.jme.intersection.PickResults; import com.jme.math.Ray; import com.jme.math.Vector2f; import com.jme.math.Vector3f; import com.jme.scene.shape.Box; import com.jme.scene.state.AlphaState; import com.jme.scene.state.LightState; import com.jme.scene.state.TextureState; import com.jme.util.TextureManager; /** * Started Date: Jul 22, 2004 <br> * <br> * * Demonstrates picking with the mouse. * * @author Jack Lindamood */ public class HelloMousePick extends SimpleGame { // This will be my mouse AbsoluteMouse am; // This will be he box in the middle Box b; PickResults pr; public static void main(String[] args) { HelloMousePick app = new HelloMousePick(); app.setDialogBehaviour(AbstractGame.ALWAYS_SHOW_PROPS_DIALOG); app.start(); } protected void simpleInitGame() { // Create a new mouse. Restrict its movements to the display screen. am = new AbsoluteMouse("The Mouse", display.getWidth(), display .getHeight()); // Get a picture for my mouse. TextureState ts = display.getRenderer().createTextureState(); URL cursorLoc = HelloMousePick.class.getClassLoader().getResource( "jmetest/data/cursor/cursor1.png" ); Texture t = TextureManager.loadTexture(cursorLoc, Texture.MM_LINEAR, Texture.FM_LINEAR); ts.setTexture(t); am.setRenderState(ts); // Make the mouse's background blend with what's already there AlphaState as = display.getRenderer().createAlphaState(); as.setBlendEnabled(true); as.setSrcFunction(AlphaState.SB_SRC_ALPHA); as.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA); as.setTestEnabled(true); as.setTestFunction(AlphaState.TF_GREATER); am.setRenderState(as); // Get the mouse input device and assign it to the AbsoluteMouse // Move the mouse to the middle of the screen to start with am.setLocalTranslation(new Vector3f(display.getWidth() / 2, display .getHeight() / 2, 0)); // Assign the mouse to an input handler am.registerWithInputHandler( input ); // Create the box in the middle. Give it a bounds b = new Box("My Box", new Vector3f(-1, -1, -1), new Vector3f(1, 1, 1)); b.setModelBound(new BoundingBox() ); b.updateModelBound(); // Attach Children rootNode.attachChild(b); rootNode.attachChild(am); // Remove all the lightstates so we can see the per-vertex colors lightState.detachAll(); b.setLightCombineMode( LightState.OFF ); pr = new BoundingPickResults(); (( FirstPersonHandler ) input ).getMouseLookHandler().setEnabled( false ); } protected void simpleUpdate() { // Get the mouse input device from the jME mouse // Is button 0 down? Button 0 is left click if (MouseInput.get().isButtonDown(0)) { 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 Vector3f worldCoords = display.getWorldCoordinates(screenPos, 0); Vector3f worldCoords2 = display.getWorldCoordinates(screenPos, 1); System.out.println( worldCoords ); // Create a ray starting from the camera, and going in the direction // of the mouse's location Ray mouseRay = new Ray(worldCoords, worldCoords2 .subtractLocal(worldCoords).normalizeLocal()); // Does the mouse's ray intersect the box's world bounds? pr.clear(); rootNode.findPick(mouseRay, pr); for (int i = 0; i < pr.getNumber(); i++) { pr.getPickData(i).getTargetMesh().setRandomColors(); } } } }
The first new part here is when I declare my AbsoluteMouse:
// This will be my mouse AbsoluteMouse am;
An AbsoluteMouse holds an absolute position for the mouse currently on the screen. There is also a RelativeMouse, which doesn’t bother with the mouse’s actual position but only with the position relative to the last movement. An AbsoluteMouse is, obviously, like the one we’re using here. A RelativeMouse is like +mouselook on Quake or your favorite shooter game. It doesn’t care about the mouse’s position on the screen, but just where you’re moving the mouse. The next new thing is when I create the AbsoluteMouse object:
// Create a new mouse. Restrict its movements to the display screen. am = new AbsoluteMouse("The Mouse", display.getWidth(), display.getHeight());
AbsoluteMouse extends Mouse
Mouse extends Geometry
Geometry extends Spatial
As we know, all Spatials must have a name. The name of this spatial is “The Mouse”. It makes sense. All things that are drawn are Geometry objects. So to draw the mouse on the screen, we make it a Geometry. The two numbers at the end restrict the movement of the mouse. If it was 40,40 then the mouse could only move in the lower 40,40 of the screen. We simply restrict it to the size of the display. The object “display” is created for us in SimpleGame. The next new item is AlphaState:
// Make the mouse's background blend with what's already there AlphaState as = display.getRenderer().createAlphaState(); as.setBlendEnabled(true); as.setSrcFunction(AlphaState.SB_SRC_ALPHA); as.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA); as.setTestEnabled(true); as.setTestFunction(AlphaState.TF_GREATER); am.setRenderState(as);
That’s a lot of strange things there. If I didn’t use AlphaState, my program would look like this:
That’s because my cursor is a square picture. Doesn’t look too nice, does it? What I need is to tell jME to blend the cursor’s transparent color with what’s already there. That’s what my AlphaState does. Color blending for AlphaStates is divided into a source function and destination function. jME’s alphastates work in a similar way to OpenGL alpha states. For more information, look up alpha states for OpenGL and how they are used. For now, just use what I told you.
NOTE : Deprecated, according to the javadoc this is no longer needed!
Now that we’ve setup how our mouse will “look”, we have to actually tie the mouse input device to the mouse on the screen:
// Get the mouse input device and assign it to the AbsoluteMouse am.setMouseInput(InputSystem.getMouseInput());
InputSystem holds input information for whatever rendering environment you’re using. In my example, it returns a LWJGLMouseInput because I’m using LWJGL. This lets jME separate whatever environment you’re using and the input devices associated with it.
End of deprecated section
Next, I move the mouse to the center of the screen for the user:
// Move the mouse to the middle of the screen to start with am.setLocalTranslation(new Vector3f(display.getWidth() / 2, display.getHeight() / 2, 0));
Again, I use display.getWidth() to figure out the width of the window. Notice the setLocalTranslation I use is just like the one I use for other Spatials. Next, I tie the mouse to an input handler:
// Assign the mouse to an input handler am.registerWithInputHandler( input );
The object input is created in SimpleGame. By default, it’s the thing that lets us move around. I change it to work with my AbsoluteMouse instead of aiming. In your own game, you would use your own InputHandler. An InputHandler is a class that lets you handle your inputs separately from your main game code. After creating my box, I attach both my mouse and the box to rootNode. After I deactivate my lightstate, you see a strange line by itself at the end of simpleInitGame()
pr = new BoundingPickResults();
This creates a container for our “pick results” which we will use during simpleUpdate. Next, you see simpleUpdate. Remember, it is called every frame.
Deprecated
First, we get the input device associated with am:
// Get the mouse input device from the jME mouse MouseInput thisMouse = am.getMouseInput();
Note that this is equivalent to the following:
MouseInput thisMouse = InputSystem.getMouseInput();
I suggest you get the MouseInput from am instead of the system, though, as it abstracts the two.
End Deprecated Section
After getting the mouse, we check for a button press:
// Is button 0 down? Button 0 is left click if(MouseInput.get().isButtonDown(0)) {
We could have checked for isButtonDown(1) or isButtonDown(2) and so on if your mouse has that many buttons on it. Button 0 happens to be left click in windows. If the button is pressed, we check to see if they are pressing it on the box, and if so we change the box’s color. The first step in that is getting to where the mouse is on the screen:
Vector2f screenPos = new Vector2f(); // Get the position that the mouse is pointing to screenPos.set(am.getHotSpotPosition().x, am.getHotSpotPosition().y);
We get the mouse’s absolute X and Y position. To understand why I use getHotSpotPosition, lets look at a picture of my mouse:
Notice that the actual cursor icon points to the top. Because of this, my cursor is actually pointing to its position plus whatever the icon’s height is. You can set your hotspot to wherever your mouse pointer actually is. But default, the hotspot is at x=0, y=ImageHeight so we’re ok with the default. After getting the correct cursor position, I get the real world value for wherever it’s pointing:
// Get the world location of that X,Y value Vector3f worldCoords = display.getWorldCoordinates( screenPos, 0);
This translates a position on the screen to a position in my world. The 3rd float I pass in, 0, is the distance away from the screen’s position that this 3D position will take. 0 would be the screen position right in front of you. 10 would be the screen position 10 units away from you. Now that I know at which point my cursor is located in the real world, I need to create a ray using that point:
// Create a ray starting from the camera, and // going in the direction of the mouse's location Ray mouseRay = new Ray(cam.getLocation(), worldCoords .subtractLocal(cam.getLocation()));
This creates a ray starting wherever my camera is and going in whichever direction I’m clicking. Rays in jME are defined with a starting location and a direction. I subtract my world coordinate from my camera’s location to get a direction relative to the camera’s location. There’s some math going on there. Sadly, you’ll need to use math to create any good 3D game. For more math information, check out jME’s math links. Next, I check to see if the ray I have intersects the BoundingBox for my box. If so, I change colors. To do that, I first clear any previous pick results I may have had.
pr.clear();
Next, I have to initiate the “pick” call. I am going to see if any children of rootNode intersect with the ray I just created, and if so their results are stored in pr. For each item that intersected that ray, I’m changing its color.
rootNode.findPick(mouseRay, pr); for (int i = 0; i < pr.getNumber(); i++) { pr.getPickData(i).getTargetMesh().setRandomColors(); }
Here is a picture of what the scene graph would look like:
| rootNode | |
| b “My Box” | am “The Mouse” |
| tiedtoo | |
| mouseInput | |