ProjectWonderlandNewCellPart4 < Javadesktop < TWiki

TWiki . Javadesktop . ProjectWonderlandNewCellPart4

Home | Changes | Index | Search | Go

Extending Project Wonderland by Creating New Cell Types - Part IV

by Jordan Slott (jslott@dev.java.net)

Purpose

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

import org.jdesktop.lg3d.wg.event.LgEvent;
import org.jdesktop.lg3d.wg.event.LgEventListener;
import org.jdesktop.lg3d.wg.event.MouseButtonEvent3D;
import org.jdesktop.lg3d.wg.internal.j3d.j3dnodes.J3dLgBranchGroup;

Next, make the following modifications to the class member variable declarations at the top of your class definition:

    J3dLgBranchGroup bg = null;
    private URL baseURL = null;
    private String shapeType = null;
    private String texturePath = null;

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:

        bg.setCapabilities();
        bg.setMouseEventEnabled(true);
        bg.setMouseEventSource(MouseButtonEvent3D.class, true);
        bg.addListener(new MouseButtonListener());

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

----- Revision r5 - 22 Aug 2008 - 11:53:49 - Main.uwekamper