In this tutorial, you will enhance the simple new cell type you have created in Part 1,
Part 2, and Part 3 of this tutorial series by
registering a listener to receive mouse button events. You will modify your shape cell type to switch between
displaying a sphere and cube whenever the user clicks on your cell.
This tutorial is designed for the v0.3 and v0.4 releases of Project Wonderland.
Expected Duration: 30 minutes
Prerequisites
Before completing this tutorial, you should have already successfully completed Part 1,
Part 2, and Part 3 of this tutorial series. You will
be extending the functionality you implemented there.
Handling user input events in ShapeCell.java
In this tutorial, your modifications will occur entirely in the ShapeCell.java file. You should use your existing code from
previous tutorials as a base, even though you'll be gutting several of the methods in there. Conceptually, the changes are
straightforward: you'll register for mouse events and when the user clicks the mouse button, you will render a sphere if the
current shape is a cube, and visa versa.
To start, add the following import statements to the top of your file. (Although it is always good at the end to have your IDE
fix all of your import statements for you):
Note that the type of the variable bg changed from BranchGroup to J3dLgBranchGroup. The J3dLgBranchGroup
object comes as part of the Looking Glass 3D platform upon which Wonderland is built. This
change is needed to capture keyboard and mouse events.
Also, you added class member variables to store several properties passed from the server upon creating (or reconfiguring)
the cell: the base URL for artwork assets, the relative path of the shape texture, and the type of the shape to create.
Next, you'll rework the setup() method quite a bit. The changes are (mostly) relatively minor, but it is probably best to
completely replace your existing code for this method. First:
@Override
public void setup(CellSetup setupData) {
String url = ((ShapeCellSetup) setupData).getBaseURL();
try {
this.baseURL = new URL(url);
} catch (MalformedURLException ex) {
System.err.println("Failed to load slide, bad baseURL " + url);
return;
}
this.texturePath = new String(((ShapeCellSetup)setupData).getTexturePath());
this.shapeType = new String(((ShapeCellSetup)setupData).getShapeType());
this.setup();
}
This setup() method simply takes the information passed from the server via the ShapeCellSetup object
and sets the values of the class member variables. Note that it calls another setup() method (which takes
no arguments) before it returns. Implement this new setup() method as follows:
public void setup() {
bg = new J3dLgBranchGroup();
bg.setCapabilities();
bg.setMouseEventEnabled(true);
bg.setMouseEventSource(MouseButtonEvent3D.class, true);
bg.addListener(new MouseButtonListener());
bg.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
bg.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
SceneGraphUtil.setCapabilitiesGraph(bg, false);
ImageComponent2DURL ic = AssetManager.getAssetManager().createImageComponent2DURL(baseURL, this.texturePath);
Appearance app = new Appearance();
Texture2D tex = new Texture2D(Texture.BASE_LEVEL, Texture.RGBA, ic.getWidth(), ic.getHeight());
tex.setImage(0, ic);
app.setTexture(tex);
if (this.shapeType.compareTo("BOX") == 0) {
Box box = new Box(1, 1, 1, Box.GENERATE_TEXTURE_COORDS, app);
SceneGraphUtil.setCapabilitiesGraph(box, false);
bg.addChild(box);
}
else if (this.shapeType.compareTo("SPHERE") == 0) {
Sphere sphere = new Sphere(1, Sphere.GENERATE_TEXTURE_COORDS, app);
SceneGraphUtil.setCapabilitiesGraph(sphere, false);
bg.addChild(sphere);
}
cellLocal.addChild(bg);
}
This new setup() method is similar to the setup() method you defined in earlier tutorials with
a couple of differences. First, it creates an J3dLgBranchGroup object instead of a BranchGroup
object. It also uses the values for the base asset URL, relative texture path, and shape type stored
in the class member variables. This is done so that (as shown below) you can change the value
of shapeType and have this setup() method redraw your scene.
The most important change is the addition of the following lines of code near the top of the method:
These four lines of code tell the J3dLgBranchGroup object that you want to receive any mouse
button events that happen inside of the cell. Although not explicitly covered in this tutorial, listening for
keyboard events is also done in a similar way. Consult the J3dLGBranchGroup Javadoc
for more information. Your listener class, MouseButtonListener, which you define below, receives
the mouse events.
Note: You may leave the existing reconfigure() method as defined in previous tutorials as-is.
Implementing the mouse listener
The remaining step is to implement the class that will handle the mouse events, MouseButtonListener.
You can make this class an inner class of the ShapeCell class, to save you from passing some needed
information between the two. At the end of the ShapeCell class (but still within the class definition), add:
class MouseButtonListener implements LgEventListener {
/** Default constructor */
public MouseButtonListener() {}
public void processEvent(LgEvent evt) {
if (evt instanceof MouseButtonEvent3D) {
MouseButtonEvent3D mbEvent = (MouseButtonEvent3D)evt;
if (mbEvent.isClicked() == true) {
System.out.println("MOUSE BUTTON EVENT: " + evt.toString());
shapeType = (shapeType.compareTo("BOX") == 0) ? "SPHERE" : "BOX";
if (bg != null) {
bg.detach();
}
setup();
}
}
}
@SuppressWarnings("unchecked")
public Class<LgEvent>[] getTargetEventClasses() {
return new Class[] { MouseButtonEvent3D.class };
}
}
It is the processEvent() method that actually handles the mouse button event. The mouse button event is represented
by MouseButtonEvent3D, a subclass of LgEvent. After it checks that the event you have received is indeed a mouse
button event, the processEvent() method checks if the mouse button was clicked (your listener will also receive events
for mouse button presses and releases, which you ignore). If the mouse button was clicked, you then change the shape
drawn in the cell, detach the current shape's branch group from the scene, and ask the setup() method to redraw the
shape in the scene.
Finally, the getTargetEventClasses() method simply returns an array of event classes that your listener is interested in--
in this case, MouseButtonEvent3D.
Running Your New Shape Cell
To recompile and re-run both the Wonderland server and client, in two separate terminal windows:
% ant run-sgs
% ant run
Whenever you click on the box or sphere, it should change to the other shape type. Note that you should
keep a steady hand when clicking the mouse button. If you move the mouse even slightly, then a mouse
button click event is not registered!
Next Steps
The code you wrote for this tutorial does have a flaw: any time you click on the shape to change the type
of shape drawn, only you can see the new shape. Other avatars in the world looking at the shape will not
see your changes. In order for this to be done, you must communicate the shape change to all other users
via the Wonderland server, making sure that all users see the same shape. In the final tutorial in this
series, you will learn how to communicate with the Wonderland server and synchronize the state of your
cell among many users.
Part 5 - Complete the cell