java.net: Wiki

The Source for Java Technology Collaboration


 <<O>>  Difference Topic ProjectWonderlandNewCellPart5 (5 - 16 Jul 2008 - Main.nicer94)
Line: 1 to 1
 
META TOPICPARENT name="ProjectWonderlandNewCellPart4"
Home | Changes | Index | Search | Go
<-- This creates the navigation links to :  Home | Help | Index | etc.  -->
Line: 199 to 199
 import com.sun.sgs.app.ClientSession;
Added:
>
>
and modify the definition of ShapeCellGLO as follows:

public class ShapeCellGLO  extends StationaryCellGLO implements BeanSetupGLO, CellMessageListener
 Next, you'll implement the details of the receivedMessage() method. It will accomplish two things: update the state of the shapeType member variable to reflect the new shape and communicate the new shape type to all of the clients. Since Project Wonderland is a multi-user environment, there may be several different users

 <<O>>  Difference Topic ProjectWonderlandNewCellPart5 (4 - 15 Jul 2008 - Main.jslott)
Line: 1 to 1
 
META TOPICPARENT name="ProjectWonderlandNewCellPart4"
Home | Changes | Index | Search | Go
<-- This creates the navigation links to :  Home | Help | Index | etc.  -->
Line: 194 to 194
 import org.jdesktop.lg3d.wonderland.darkstar.common.messages.CellMessage; import org.jdesktop.lg3d.wonderland.darkstar.server.CellMessageListener; import org.jdesktop.lg3d.wonderland.shapecell.common.ShapeCellChangeMessage;
Added:
>
>
import java.util.HashSet; import java.util.Set; import com.sun.sgs.app.ClientSession;
 

Next, you'll implement the details of the receivedMessage() method. It will accomplish two things: update the

Line: 256 to 259
 public class ShapeCell? extends Cell implements ExtendedClientChannelListener? {
Changed:
<
<
and add the following import statement:
>
>
and add the following import statements:
 
import org.jdesktop.lg3d.wonderland.darkstar.client.ExtendedClientChannelListener;
Added:
>
>
import com.sun.sgs.client.ClientChannel; import com.sun.sgs.client.SessionId;
 

The ExtendedClientChannelListener interface comes with Project Wonderland and extends Darkstar's


 <<O>>  Difference Topic ProjectWonderlandNewCellPart5 (3 - 09 Jul 2008 - Main.jslott)
Line: 1 to 1
 
META TOPICPARENT name="ProjectWonderlandNewCellPart4"
Home | Changes | Index | Search | Go
<-- This creates the navigation links to :  Home | Help | Index | etc.  -->

Extending Project Wonderland by Creating New Cell Types - Part V

by Jordan Slott (jslott@dev.java.net)
Deleted:
<
<
UNDER CONSTRUCTION -- CHECK BACK SOON!
 
<-- Your JavaDesktop? article goes here. Please try to include at least one sentence describing this topic. -->
<-- Also please try to include at least one sentence describing where each link goes. -->
<-- Please make sure some other page points to your new article so that others can find it! -->
Line: 46 to 44
 

Creating a new message: ShapeCellChangeMessage

Changed:
<
<
Before the client can communicate any changes in the type of shape when the user clicks on the shape, you need to create a new type of class to communicate
>
>
Before the client can communicate any changes in the type of shape when the user clicks on the shape, you need to create a new class to communicate
 this message.

In the org.jdesktop.lg3d.wonderland.shapecell.common package, create a new class named ShapeCellChangeMessage.java. Add the following import

Line: 67 to 65
 The CellMessage class is the base class for all messages passed between the client and server cell classes in Project Wonderland. The two methods you will need to override from this class are extractMessageImpl() and populateDataElements(), but first you'll
Changed:
<
<
create some of the needed infrastructure for this class. Add the following imports to near the top of your file that you will need later:

import java.nio.ByteBuffer;
import org.jdesktop.lg3d.wonderland.darkstar.common.CellID;
import org.jdesktop.lg3d.wonderland.darkstar.common.messages.CellMessage;
import org.jdesktop.lg3d.wonderland.darkstar.common.messages.DataString;
>
>
create some of the needed infrastructure for this class.
 This message communicates the new shape of the cell to be displayed on all clients, so add a field to your class to store this new type (as a string), along with setter and getter methods:
Line: 92 to 83
 

Next, implement the following constructor. As its first argument, it takes a CellID object: this is necessary so that when the message is

Changed:
<
<
received by your the Wonderland server, it knows to which object it should dispatch the messsage. Each cell's ID is unique and is assigned
>
>
received by the Wonderland server, it knows to which object it should dispatch the messsage. Each cell's ID is unique and is assigned
 automatically when it is created by the Wonderland system.
Line: 110 to 101
 

Finally, you'll implement the populateDataElements() and extractMessageImpl() methods. These methods populate the message

Changed:
<
<
with the proper information and extract data from the message into an instance of the ShapeCellChangeMessage class. (Note: although
>
>
with the proper information and extract data from the message into an instance of the ShapeCellChangeMessage class, respectively. (Note: although
 you pack messages by hand, you may also simply use Java's object serialization too. Version 0.5 of Project Wonderland will move entirely to object serialization).
Line: 125 to 116
  }
Changed:
<
<
The extractMessageImpl() method does the opposite of the populateDataElements(): give a byte-buffer of the message data send over
>
>
The extractMessageImpl() method does the opposite of the populateDataElements(): given a byte-buffer of the message data sent over
 the network, extract the shape type attribute, using the DataString object to extract a string value:
Line: 139 to 130
 

Communicating the Shape Change to the Server

Changed:
<
<
Next, you will modify you ShapeCell class to send a message to the server when the user clicks on the shape to
>
>
Next, you will modify your ShapeCell class to send a message to the server when the user clicks on the shape to
 change its type. Each cell automatically has a communication channel opened between the client-side and server- side cell classes. All you need to do on the client-side is to create a new message and send it.
Line: 176 to 167
 Fortunately, because Wonderland is built on top of Project Darkstar, synchronizing the state among many different concurrent clients is made easy. But first, set up your class to receive the message from the client. You will need
Changed:
<
<
to open the cell's communicate channel, by overriding the openChannel() method in CellGLO. Fortunately,
>
>
to open the cell's communication channel, by overriding the openChannel() method in CellGLO. Fortunately,
 CellGLO also provides a convenience method, openDefaultChannel(), to open the channel meant for the client-server cell communication:
Line: 210 to 201
 all of the clients. Since Project Wonderland is a multi-user environment, there may be several different users who click on their shape to update the shape type at the same time. In a typical multi-user environment, you must make sure updates to the state type is synchronized with all other possible updates. This is often a very
Changed:
<
<
trick task!
>
>
tricky task!
 
Changed:
<
<
Fortunately, the Project Darkstar infrastructure makes this really simply. All updates to the state of server-side
>
>
Fortunately, the Project Darkstar infrastructure makes this really simple. All updates to the state of server-side
 objects happen within the context of a transaction -- either the state update happens at once without conflicting with other requests to update the state of the object, or it does not happen at all. The Project Darkstar infrastructure manages the resource contention for you: it notes what objects you update and when the transaction completes
Changed:
<
<
and commits its changes, it does so in an atomic fashion. It knows ShapeCellGLO is such as object to manage
>
>
and commits its changes, it does so in an atomic fashion. It knows ShapeCellGLO is such an object to manage
 in a transaction because (in Part 1) you had it implement the ManagedObject interface by extending the StationaryCellGLO class.

 <<O>>  Difference Topic ProjectWonderlandNewCellPart5 (2 - 07 Jul 2008 - Main.jslott)
Line: 1 to 1
 
META TOPICPARENT name="ProjectWonderlandNewCellPart4"
Home | Changes | Index | Search | Go
<-- This creates the navigation links to :  Home | Help | Index | etc.  -->
Line: 14 to 14
 

Purpose

Changed:
<
<
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.
>
>
In this tutorial, you will complete the simple new cell type you have created in Part 1, Part 2, Part 3, and Part 4 of this tutorial series by enabling it to synchronize its state among many Wonderland clients. You will modify your shape cell type to communicate when the user clicks on the shape to change its type back to the server-side object. The server-side object updates its state in a thread-safe manner, and communicates the change back to all of the clients.
 This tutorial is designed for the v0.3 and v0.4 releases of Project Wonderland.
Changed:
<
<
Expected Duration: 30 minutes
>
>
Expected Duration: 45 minutes
 

Prerequisites

Before completing this tutorial, you should have already successfully completed Part 1,

Changed:
<
<
Part 2, and Part 3 of this tutorial series. You will be extending the functionality you implemented there.
>
>
Part 2, Part 3, and Part 4 of this tutorial series. You will be extending the functionality you implemented there.
 
Changed:
<
<

Handling user input events in ShapeCell.java

>
>
You should also be familiar with programming Project Darkstar: it is the middleware technology upon which Project Wonderland is built that handles client-server communication and makes it easy to write server-side objects to manage their state in a thread-safe manner. Visit the Project Darkstar website--it is best to download the distribution and read the server-side tutorial document.
 
Changed:
<
<
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.
>
>

High-level Design

 
Changed:
<
<
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):
>
>
From a high-level, here is what you will implement in this tutorial to enable your cell type for a multi-user environment:

  1. Communicate the new shape type from the client-side ShapeCell class to the server-side ShapeCellGLO class when the user clicks on the shape.
  2. Update the shape type state stored by the ShapeCellGLO class.
  3. Communicate the new shape type to all other clients.

Creating a new message: ShapeCellChangeMessage

Before the client can communicate any changes in the type of shape when the user clicks on the shape, you need to create a new type of class to communicate this message.

In the org.jdesktop.lg3d.wonderland.shapecell.common package, create a new class named ShapeCellChangeMessage.java. Add the following import statements near the top of the file:

 
Changed:
<
<
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;
>
>
import java.nio.ByteBuffer; import org.jdesktop.lg3d.wonderland.darkstar.common.CellID; import org.jdesktop.lg3d.wonderland.darkstar.common.messages.CellMessage; import org.jdesktop.lg3d.wonderland.darkstar.common.messages.DataString;
 
Changed:
<
<
Next, make the following modifications to the class member variable declarations at the top of your class definition:
>
>
Next, have your class extend the CellMessage class as follows:
 
Changed:
<
<
J3dLgBranchGroup? bg = null; private URL baseURL = null; private String shapeType = null; private String texturePath = null;
>
>
public class ShapeCellChangeMessage? extends CellMessage? {
 
Changed:
<
<
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.
>
>
The CellMessage class is the base class for all messages passed between the client and server cell classes in Project Wonderland. The two methods you will need to override from this class are extractMessageImpl() and populateDataElements(), but first you'll create some of the needed infrastructure for this class. Add the following imports to near the top of your file that you will need later:
 
Changed:
<
<
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.
>
>
import java.nio.ByteBuffer;
import org.jdesktop.lg3d.wonderland.darkstar.common.CellID;
import org.jdesktop.lg3d.wonderland.darkstar.common.messages.CellMessage;
import org.jdesktop.lg3d.wonderland.darkstar.common.messages.DataString;
 
Changed:
<
<
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:
>
>
This message communicates the new shape of the cell to be displayed on all clients, so add a field to your class to store this new type (as a string), along with setter and getter methods:
 
Changed:
<
<
@Override public void setup(CellSetup? setupData) {
>
>
private String shapeType = null;
 
Changed:
<
<
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;
>
>
public String getShapeType() { return this.shapeType;
  }
Deleted:
<
<
this.texturePath = new String(((ShapeCellSetup?)setupData).getTexturePath()); this.shapeType = new String(((ShapeCellSetup?)setupData).getShapeType());
 
Changed:
<
<
this.setup();
>
>
public void setShapeType(String shapeType) { this.shapeType = shapeType;
  }
Changed:
<
<
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:
>
>
Next, implement the following constructor. As its first argument, it takes a CellID object: this is necessary so that when the message is received by your the Wonderland server, it knows to which object it should dispatch the messsage. Each cell's ID is unique and is assigned automatically when it is created by the Wonderland system.
 
Changed:
<
<
public void setup() { bg = new J3dLgBranchGroup?();
>
>
public ShapeCellChangeMessage?(CellID? cellID, String shapeType) { super(cellID); this.shapeType = shapeType; }
 
Changed:
<
<
bg.setCapabilities(); bg.setMouseEventEnabled(true); bg.setMouseEventSource(MouseButtonEvent3D?.class, true); bg.addListener(new MouseButtonListener?());
>
>
Also add the default constructor:
 
Changed:
<
<
bg.setCapability(BranchGroup?.ALLOW_CHILDREN_EXTEND); bg.setCapability(BranchGroup?.ALLOW_CHILDREN_WRITE); SceneGraphUtil?.setCapabilitiesGraph(bg, false);
>
>
    public ShapeCellChangeMessage() {
    }
 
Changed:
<
<
ImageComponent2DURL? ic = AssetManager?.getAssetManager().createImageComponent2DURL(baseURL, this.texturePath);
>
>
Finally, you'll implement the populateDataElements() and extractMessageImpl() methods. These methods populate the message with the proper information and extract data from the message into an instance of the ShapeCellChangeMessage class. (Note: although you pack messages by hand, you may also simply use Java's object serialization too. Version 0.5 of Project Wonderland will move entirely to object serialization).
 
Changed:
<
<
Appearance app = new Appearance(); Texture2D? tex = new Texture2D?(Texture.BASE_LEVEL, Texture.RGBA, ic.getWidth(), ic.getHeight()); tex.setImage(0, ic); app.setTexture(tex);
>
>
The populateDataElements() method simply places the string (encapsulated by a DataString object) into a collection of data elements stored by the CellMessage superclass:
 
Changed:
<
<
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);
>
>
    @Override
    protected void populateDataElements() {
        super.populateDataElements();
        dataElements.add(new DataString(this.shapeType));
  }
Changed:
<
<
else if (this.shapeType.compareTo("SPHERE") == 0) { Sphere sphere = new Sphere(1, Sphere.GENERATE_TEXTURE_COORDS, app); SceneGraphUtil?.setCapabilitiesGraph(sphere, false); bg.addChild(sphere);
>
>

The extractMessageImpl() method does the opposite of the populateDataElements(): give a byte-buffer of the message data send over the network, extract the shape type attribute, using the DataString object to extract a string value:

    @Override
    protected void extractMessageImpl(ByteBuffer data) {
        super.extractMessageImpl(data);
        this.shapeType = DataString.value(data);
  }
Deleted:
<
<
cellLocal.addChild(bg);
  }
Changed:
<
<
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.
>
>

Communicating the Shape Change to the Server

 
Changed:
<
<
The most important change is the addition of the following lines of code near the top of the method:
>
>
Next, you will modify you ShapeCell class to send a message to the server when the user clicks on the shape to change its type. Each cell automatically has a communication channel opened between the client-side and server- side cell classes. All you need to do on the client-side is to create a new message and send it.

Locate the processEvent() method in your MouseButtonListener inner class in ShapeCell.java. Locate where you invoke the setup() method to change the shape type locally. After this method call, insert the following two lines to create a new ShapeCellChangeMessage object with the new shape type and send the message to the server:

 
Changed:
<
<
bg.setCapabilities(); bg.setMouseEventEnabled(true); bg.setMouseEventSource(MouseButtonEvent3D?.class, true); bg.addListener(new MouseButtonListener?());
>
>
ShapeCellChangeMessage? msg = new ShapeCellChangeMessage?(getCellID(), shapeType); ChannelController?.getController().sendMessage(msg);
 
Changed:
<
<
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.
>
>
This has the effect of immediately updating the shape type visually on the client, so that the user gets immediate feedback and then sends a message to the server with the new shape. The getCellID() method is defined by the Cell superclass and simply returns the unique ID of the cell that is automatically assigned to the cell when it is created.
 
Changed:
<
<
Note: You may leave the existing reconfigure() method as defined in previous tutorials as-is.
>
>
You will also need to add the following import statements to near the top of your ShapeCell.java file:
 
Changed:
<
<

Implementing the mouse listener

>
>
import org.jdesktop.lg3d.wonderland.darkstar.client.ChannelController;
import org.jdesktop.lg3d.wonderland.darkstar.common.messages.CellMessage;
import org.jdesktop.lg3d.wonderland.darkstar.common.messages.Message;
import org.jdesktop.lg3d.wonderland.shapecell.common.ShapeCellChangeMessage;
 
Changed:
<
<
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:
>
>

Synchronizing the State of the Cell on the Server

Your server-side cell class, ShapeCellGLO, now needs to do the following:

  1. Receive the message from the client.
  2. Update its state based upon the new shape type sent from the client.
  3. Inform all other clients of the new shape type.

Fortunately, because Wonderland is built on top of Project Darkstar, synchronizing the state among many different concurrent clients is made easy. But first, set up your class to receive the message from the client. You will need to open the cell's communicate channel, by overriding the openChannel() method in CellGLO. Fortunately, CellGLO also provides a convenience method, openDefaultChannel(), to open the channel meant for the client-server cell communication:

 
Changed:
<
<
class MouseButtonListener? implements LgEventListener? {
>
>
@Override public void openChannel() { this.openDefaultChannel(); }
 
Changed:
<
<
/** Default constructor */ public MouseButtonListener?() {}
>
>
Whenever a message is received, it calls the receivedMessage() method on the CellMessageListener interface. Add a stub for the receivedMessage() method, you will fill in its details shortly:
 
Changed:
<
<
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());
>
>
    public void receivedMessage(ClientSession client, CellMessage message) {
 
Deleted:
<
<
shapeType = (shapeType.compareTo("BOX") == 0) ? "SPHERE" : "BOX"; if (bg = null) { bg.detach(); } setup();
  }
Added:
>
>

You will also need to make your ShapeCellGLO class implement the CellMessageListener interface. Then, add the following imports to near the top of your file:

import org.jdesktop.lg3d.wonderland.darkstar.common.messages.CellMessage;
import org.jdesktop.lg3d.wonderland.darkstar.server.CellMessageListener;
import org.jdesktop.lg3d.wonderland.shapecell.common.ShapeCellChangeMessage;

Next, you'll implement the details of the receivedMessage() method. It will accomplish two things: update the state of the shapeType member variable to reflect the new shape and communicate the new shape type to all of the clients. Since Project Wonderland is a multi-user environment, there may be several different users who click on their shape to update the shape type at the same time. In a typical multi-user environment, you must make sure updates to the state type is synchronized with all other possible updates. This is often a very trick task!

Fortunately, the Project Darkstar infrastructure makes this really simply. All updates to the state of server-side objects happen within the context of a transaction -- either the state update happens at once without conflicting with other requests to update the state of the object, or it does not happen at all. The Project Darkstar infrastructure manages the resource contention for you: it notes what objects you update and when the transaction completes and commits its changes, it does so in an atomic fashion. It knows ShapeCellGLO is such as object to manage in a transaction because (in Part 1) you had it implement the ManagedObject interface by extending the StationaryCellGLO class.

When a message is delivered to your server-side cell class, it automatically happens within the context of a transaction: all you need to do is update the state you wish and when the method completes, Project Darkstar will commit any changes you made atomically and in a multi-user safe manner.

So, in your receivedMessage() method, the first two lines will be:

        ShapeCellChangeMessage sccm = (ShapeCellChangeMessage)message;
        this.shapeType = sccm.getShapeType();

These two lines are all that's required to insure that the state of this cell that is shared among many users is updated in a safe manner. Next, send a message to all of the other clients connected of the new state. (You do not need to send a message back to the client from which the change came -- although doing so would be harmless!). Note that you'll use the same ShapeCellChangeMessage class that you used to communicate the new shape type from the client to the server. In this case, you'll use the default constructor that sets the cell ID in CellMessage to null (you do not need it in this instance):

        ShapeCellChangeMessage msg = new ShapeCellChangeMessage();
        msg.setShapeType(shapeType);

And to actually go ahead and send the message:

        Set<ClientSession> sessions = new HashSet<ClientSession>(getCellChannel().getSessions());
        sessions.remove(client);
        getCellChannel().send(sessions, msg.getBytes());

The getCellChannel() method returns the communications channel associated with this cell. It returns a list of the Wonderland clients attached to the channel via the getSessions() method.

Listening for Message on the Client

There is one final step that completes the loop: your ShapeCell client-side class must also listen for messages sent to it that inform it of a new cell type. Here, your ShapeCell class must implement the ExtendedClientChannelListener interface and the setChannel(), leftChannel(), and receivedMessage() methods. First, modify the definition of ShapeCell as follows:

public class ShapeCell extends Cell implements ExtendedClientChannelListener {

and add the following import statement:

import org.jdesktop.lg3d.wonderland.darkstar.client.ExtendedClientChannelListener;

The ExtendedClientChannelListener interface comes with Project Wonderland and extends Darkstar's ClientChannelListener interface. The former adds the setChannel() method which you can implement simply as follows:

    public void setChannel(ClientChannel channel) {
        this.channel = channel;
  }
Added:
>
>

The this.channel member variable is part of the Cell superclass. The ClientChannelListener interface defines two methods you must implement: leftChannel() (which can be empty) and receivedMessage() that handles the ShapeCellChangeMessage message from the server:

    public void leftChannel(ClientChannel arg0) {
        // ignore
  }
Changed:
<
<
@SuppressWarnings("unchecked") public Class[] getTargetEventClasses() { return new Class[] { MouseButtonEvent3D?.class };
>
>
public void receivedMessage(ClientChannel? client, SessionId? session, byte[] data) { CellMessage? msg = Message.extractMessage(data, CellMessage?.class); if (msg instanceof ShapeCellChangeMessage?) { ShapeCellChangeMessage? sccm = Message.extractMessage(data, ShapeCellChangeMessage?.class); this.shapeType = sccm.getShapeType(); if (bg = null) { bg.detach(); } this.setup();
  } }
Changed:
<
<
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.

>
>
The receivedMessage() method simply extracts a ShapeCellChangeMessage message from the message byte array, fetches the new shape type, sets the shapeType member variable to the new type, detaches the current shape from the scene, and asks the setup() method to draw the new shape.
 

Running Your New Shape Cell

Line: 199 to 318
 % ant run
Deleted:
<
<
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.

 \ No newline at end of file
Added:
>
>
You'll also want someone else on another machine to run the same client. (Note that you will both have to use the same client code -- it is best to setup the machine you run the Wonderland server to allow users to download the client software via Java Web Start).

Whenever you click on the box or sphere, it should change to the other shape type. The other user should see the shape change type too!

Summary

In this five part tutorial series, you learned how to extend Wonderland by creating a new cell type. You set up the project infrastructure, drew a basic object configured by a file in WFS, and then used the client side asset manager to load a texture for the shape. Finally, you learned how to make your new cell type synchronize its state among many Wonderland clients.


 <<O>>  Difference Topic ProjectWonderlandNewCellPart5 (1 - 06 Jul 2008 - Main.jslott)
Line: 1 to 1
Added:
>
>
META TOPICPARENT name="ProjectWonderlandNewCellPart4"
Home | Changes | Index | Search | Go
<-- This creates the navigation links to :  Home | Help | Index | etc.  -->

Extending Project Wonderland by Creating New Cell Types - Part V

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

UNDER CONSTRUCTION -- CHECK BACK SOON!

<-- Your JavaDesktop? article goes here. Please try to include at least one sentence describing this topic. -->
<-- Also please try to include at least one sentence describing where each link goes. -->
<-- Please make sure some other page points to your new article so that others can find it! -->
<-- For more on how to write Javapedia articles please read the WritingArticles? page. -->

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.


Topic ProjectWonderlandNewCellPart5 . { View | Diffs r5 < r4 < r3 < r2 | More }
 XML java.net RSS