In this tutorial, you will begin to learn how to create a new type of cell for Project Wonderland. A cell is a 3D
volume; creating new cell types is the primary means developers extend the functionality of Wonderland. This
is the first in a series of five tutorials. In this first tutorial, you will learn how to write the essential classes that comprise
a new cell type and how to create instances of the new cell type in-world via WFS.
This tutorial is designed for the v0.3 and v0.4 releases of Project Wonderland.
Expected Duration: 60 minutes
Prerequisites
Before completing this tutorial, you should have already successfully downloaded and compiled Project Wonderland.
Instructions on downloading and running Wonderland can be found at https://lg3d-wonderland.dev.java.net/#try. You
should download and compile both the primary Wonderland workspace (lg3d-wonderland) and the optional modules
workspace (wonderland-modules). The wonderland-modules workspace provides the infrastructure necessary to
compile your new cell type.
You should also be familiar with Project Wonderland's architecture (here) and
elements of the Wonderland File System (WFS) (see tutorial here) before completing
this tutorial too.
Cell Architecture
The basic 3D visual building block inside Wonderland are cells, comprised of a sever piece and a client
piece (Figure 1).
The server piece consists of a single class (MyCellGLO, Figure 1) that maintains the shared state of the world across
all client participants. It has the suffix 'GLO' to indicate that it is a special object that participates in the Project Darkstar
gaming engine mechanism. The client-side piece (MyCell, Figure 1) is responsible for rendering the cell in-world either
by loading an art resource or by drawing objects directly using the Java 3D APIs. Instances of cells are added to a
world by including an XML-formatted cell descriptor file within the WFS (mycell-wlc.xml) that provides values for the
configurable parameters of a cell.
Shape Cell: A New Cell Type
In this tutorial, you will learn how to create a new cell type that draws a 3D shape in-world. The kind of shape (box
or sphere) is set by a parameter in the XML-formatted cell descriptor file within WFS.
Setting up your build environment
If you have not done so already, you should have downloaded and compiled both the lg3d-wonderland and
wonderland-modules workspaces. This tutorial will assume you are familiar with the Netbeans IDE--since the
Netbeans compilation mechanism is based upon ant, you may complete this tutorial using other IDEs
that use ant for their build infrastructure or you may use command-line tools.
Once you have compiled your workspaces, open the wonderland-modules workspace in Netbeans using File ->
Open Project.
Click on the Files view on the top-left of your Netbeans Window.
Expand the nodes so that the wonderland-modules/src/modules/apps/3d path is fully exposed.
On the 3d folder, right-click and select New ->Folder. For Folder Name, enter shapecell and click Finish.
Inside your new folder, you will create two files: build.xml and project.xml.
On the shapecell folder, right-click and select New -> Other.
In the dialog box select Other -> Empty File. Click Next.
Name the file build.xml and click Finish.
Copy the following XML text int your build.xml file. Once copied, save the file.
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="jar" name="shapecell">
<property name="modules.dir" location="../../../../.."/>
<property name="current.dir" location="."/>
<!-- Use my-build.properties to override default values in build.properties -->
<property file="my.build.properties"/>
<!-- set the project name -->
<property name="project.name" value="${ant.project.name}"/>
<!-- import the common build attributes -->
<import file="${modules.dir}/build-tools/import/module-common.xml"/>
<!-- the name of the client jar file -->
<property name="module.client.jar" value="shapecell-client.jar"/>
<!-- all files that should be built as part of the client jar file -->
<patternset id="module.client.classes">
<include name="org/jdesktop/lg3d/wonderland/shapecell/client/**"/>
<include name="org/jdesktop/lg3d/wonderland/shapecell/common/**"/>
</patternset>
<!-- the name of the server jar file -->
<property name="module.server.jar" value="shapecell-server.jar"/>
<!-- all files that should be built as part of the server jar file -->
<patternset id="module.server.classes">
<include name="org/jdesktop/lg3d/wonderland/shapecell/server/**"/>
<include name="org/jdesktop/lg3d/wonderland/shapecell/common/**"/>
</patternset>
<!-- extra classes for compiling the server -->
<!-- <path refid="extra-server.classpath"/> -->
<!-- copy targets so NetBeans will recognize them -->
<target name="compile" depends="compile-client, compile-server"/>
<target name="jar" depends="jar-client, jar-server"/>
<target name="clean" depends="clean-all"/>
<!-- You can override default methods to add functions at various
points. See module-common.xml for more information:
-pre-init: before init
-post-init: after init
-pre-compile-client: before compiling client
-post-compile-client: after compiling client
-pre-compile-server: before compiling server
-post-compile-server: after compiling server
-pre-jar-client: before jar client
-post-jar-client: after jar client
-pre-jar-server: before jar server
-post-jar-server: after jar server
-pre-clean: before clean
-post-clean: after clean
-->
<target name="-post-compile-server">
<javac debug="${build.debug}"
debuglevel="${build.debuglevel}"
deprecation="${build.showdeprecation}"
destdir="${module.classes.dir}"
srcdir="${module.javasrc.dir}"
nowarn="true"
source="1.5"
target="1.5">
<classpath>
<pathelement path="${wonderland-server.classpath}"/>
</classpath>
</javac>
</target>
</project>
Next you will create the project.xml file. This file essentially creates a Netbeans project for you.
Right-click on the shapecell folder and select New -> Folder. Name the folder nbproject and click Finish.
Inside the nbproject sub-folder, create an empty file named project.xml and copy the following contents into it.
Once you have your project and ant files all setup, you can create the directories in which your source code will reside.
The source code for new cell types are broken up into three packages: client, common, and server. Classes in the
client package are compiled into the Wonderland client, classes in the server package are compiled into the
Wonderland server, and classes in the common package are compiled into both the client and server.
Right-click on the shapecell folder and select New -> Folder. For Folder Name, enter src/classes and click Finish.
On the classes folder node, right-click and select New -> Java Package. Enter "org.jdesktop.lg3d.wonderland.shapecell.client" in the File Name text box and click Finish.
On the classes folder node, right-click and select New -> Java Package. Enter "org.jdesktop.lg3d.wonderland.shapecell.common" in the File Name text box and click Finish.
On the classes folder node, right-click and select New -> Java Package. Enter "org.jdesktop.lg3d.wonderland.shapecell.server" in the File Name text box and click Finish.
The ShapeCellGLO server class
First you will create the server-side class representing the cell, ShapeCellGLO. The purpose of the server-side class is to store the basic
configuration information defined by the XML cell descriptor file in WFS. It also manages the shared state of the cell for all clients. To help
manage that state, and the individual, asynchronous updates to the state from clients, the ShapeCellGLO class participates in the Project
Darkstar transactional gaming infrastructure. In this first example, however, the ShapeCellGLO does not maintain shared state for different
clients, and will not make use of the Darkstar transaction mechanism. (Future tutorials, will however, exercise some of this functionality.)
Right-click on the server folder and select New -> Java Class. Name the class ShapeCellGLO and click Finish.
Netbeans should create a skeleton of a class that looks something like this:
package org.jdesktop.lg3d.wonderland.shapecell.server;
public class ShapeCellGLO {
}
First, copy the necessary import statements into your Java class beneath the package statement:
Next, modify the definition of your Java class to be:
public class ShapeCellGLO extends StationaryCellGLO implements BeanSetupGLO {
This class extends StationaryCellGLO that resides in the org.jdesktop.lg3d.wonderland.darkstar.server.cell package and is the base
class for all cells that do not move. (It itself extends the CellGLO class). The ShapeCellGLO class is a "managed object" in the context
of Project Darkstar (because the CellGLO class implements the ManagedObject interface). The ShapeCellGLO class also implements
the BeanSetupGLO interface and its three methods setupCell(), reconfigureCell(), and getCellGLOSetup(). You will implement
these three methods below shortly.
The ShapeCellGLO class maintains one piece of configurable information: a String describing the shape of the object to draw: either
BOX or SPHERE. You will store this String in the ShapeCellGLO class:
private String shapeType = null;
The CellGLO class declares an abstract method, getSetupData, that you must implement. This method should return an
instance of the class that represents your cell-specific configuration data (which itself is a subclass of CellSetup). Here, you
can simply create and return a new instance of ShapeCellSetup:
@Override
public ShapeCellSetup getSetupData() {
ShapeCellSetup setup = new ShapeCellSetup();
setup.setShapeType(this.shapeType);
return setup;
}
Implement the methods of the BeanSetupGLO class as follows:
public void setupCell(CellGLOSetup setupData) {
super.setupCell((BasicCellGLOSetup<?>) setupData);
BasicCellGLOSetup<ShapeCellSetup> setup = (BasicCellGLOSetup<ShapeCellSetup>)setupData;
this.shapeType = setup.getCellSetup().getShapeType();
/* Setup bounds based upon shape of the object */
if (this.shapeType.compareTo("BOX)") == 0) {
super.setBounds(new BoundingBox());
}
else if (this.shapeType.compareTo("SPHERE") == 0) {
super.setBounds(new BoundingSphere());
}
}
The setupCell() method is called after an instance of the ShapeCellGLO class is created to initialize the configurable parameters
of the cell. The configurable parameters are usually set by the XML cell descriptor files within a WFS--details on how to configure
this file will be discussed below. This method is passed an object that implements the CellGLOSetup interface, but also is an instance
of the BasicCellGLOSetup class. The BasicCelGLOSetup class stores information common to all cell types--origin, rotation, scaling,
and bounds. New cell types do not need to worry about storing these configurable parameters--the StationaryCellGLO class handles
this via its own setupCell() method.
The BasicCellGLOSetup class has as one of its members (called cellSetup) a class that stores the parameters specific to your new
cell type. You will define this class (ShapeCellSetup) later. The ShapeCellSetup class stores the shape type--you will use this value to
initialize the shapeType String in the ShapeCellGLO class. You also use it to properly initialize the bounds of the cell.
Next, implement the reconfigureCell() and getCellGLOSetup() methods. The reconfigureCell() method is called if the
parameters within the XML cell descriptor file within the WFS changes while the server is running. We leave this method unimplemented
for now, but will return to it later.
public void reconfigureCell(CellGLOSetup setupData) {
}
The getCellGLOSetup() method returns a new BasicCellGLOSetup class (parameterized by the ShapeCellSetup class).
public CellGLOSetup getCellGLOSetup() {
return new BasicCellGLOSetup<ShapeCellSetup>(getBounds(),
getOrigin(), getClass().getName(), getSetupData());
}
Finally, implement the getClientCellClassName() method as follows. This method returns the fully-qualified class name of the
client-side cell class (which you will implement shortly).
@Override
public String getClientCellClassName() {
return "org.jdesktop.lg3d.wonderland.shapecell.client.ShapeCell";
}
The ShapeCellSetup common class
Next, you will implement the ShapeCellSetup class that is compiled both into the server-side and client-side code. This class follows
the Java Bean pattern and is simply meant as a container of configurable information that is passed from the server to the client.
Right-click on the common folder and select New -> Java Class. Name the class ShapeCellSetup and click Finish.
Implement this class as follows:
package org.jdesktop.lg3d.wonderland.shapecell.common;
import org.jdesktop.lg3d.wonderland.darkstar.common.CellSetup;
public class ShapeCellSetup implements CellSetup {
private String shapeType = null;
public ShapeCellSetup() {}
public String getShapeType() {
return shapeType;
}
public void setShapeType(String shapeType) {
this.shapeType = shapeType;
}
}
This class stores the type of shape as a String and has a default constructor and the standard setter and getter methods. If you
wish to extend the functionality of the shape cell type (e.g. by including a radius or color configurable parameters), you would
include these new parameters in this class, for example.
The ShapeCell client class
Finally, you will implement the ShapeCell client-side class that draws the shape in-world.
Right-click on the client folder and select New -> Java Class. Name the class ShapeCell and click Finish.
Implement this class as follows:
package org.jdesktop.lg3d.wonderland.shapecell.client;
import javax.media.j3d.*;
import com.sun.j3d.utils.geometry.*;
import javax.vecmath.Matrix4d;
import org.jdesktop.j3d.util.SceneGraphUtil;
import org.jdesktop.lg3d.wonderland.darkstar.client.cell.Cell;
import org.jdesktop.lg3d.wonderland.darkstar.common.*;
import org.jdesktop.lg3d.wonderland.shapecell.common.ShapeCellSetup;
public class ShapeCell extends Cell {
public ShapeCell(CellID cellID, String channelName, Matrix4d origin) {
super(cellID, channelName, origin);
}
@Override
public void setup(CellSetup setupData) {
BranchGroup bg = new BranchGroup();
bg.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
bg.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
SceneGraphUtil.setCapabilitiesGraph(bg, false);
if (((ShapeCellSetup)setupData).getShapeType().compareTo("BOX") == 0) {
Box box = new Box();
SceneGraphUtil.setCapabilitiesGraph(box, false);
bg.addChild(box);
}
else if (((ShapeCellSetup)setupData).getShapeType().compareTo("SPHERE") == 0) {
Sphere sphere = new Sphere();
SceneGraphUtil.setCapabilitiesGraph(sphere, false);
bg.addChild(sphere);
}
cellLocal.addChild(bg);
}
}
The ShapeCell class extends the Cell class that provides the basic functionality of all client-side cells. Each sublcass must
override the setup() method that takes the set of parameters configured on the server side and draws the cell in-world. In
this case, the CellSetup object is an instance of the ShapeCellSetup class. Using the type of shape specified, the setup()
method draws either a box or sphere (each of radius 1).
Creating a Shape Cell In-World
To create an instance of your new shape cell type in-world, you can create an XML cell descriptor file and place it within your
world's WFS. In this tutorial, you will create a WFS that contains only this cell, however feel free to drop as many copies of this
cell into existing worlds.
Edit the build.properties class in the lg3d-wonderland workspace. Change the wonderland.wfs.root property to point to ${src.dir}/worlds/shape-wfs.
Create the shape-wfs directory within the ${src.dir}/worlds directory (which is typically lg3d-wonderand/src/worlds/).
Inside the shape-wfs directory, create a file named shape-wlc.xml.
Copy the following contents into your shape-wlc.xml file and save.
This file is formatted according to the Long-term Persistence for Java Beans mechanism (JSR-57, see http://java.sun.com/products/jfc/tsc/articles/persistence3/). It creates
an instance of the BasicCellGLOSetup class containing all of the basic cell attributes (in this case, its origin) and provides
the name of the cell to create in the cellGLOClassName property. As discussed previously, the BasicCellGLOSetup class
has a member, cellSetup, that contains cell-type specific configuration information. In this case, we populate this member
with an instance of the ShapeCellSetup class, that has a single attribute, shapeType, that stores the type of shape to draw.
The origin of the shape is set to (x, y, z) = (50.0, 2.0, 40.0) that makes the sphere hover slightly in front of our initial position.
Compiling and Running
One nice feature of the lg3d-wonderland workspace is that is automatically looks for and compiles the wonderland-modules
directory. In fact, in order to properly compile your module (so that the classpath properly includes all of the Wonderland-related
JARs), you must compile from the lg3d-wonderland directory. To compile and run your new cell type, inside of a shell type:
% cd lg3d-wonderland
% ant run-sgs
(To note: In the log file generated by running the Wonderland server using the steps above, you might see warning messages
that the aliases.xml and version.xml files are missing. These warning messages are innocuous. The two files represent functionality
in WFS that has yet to be implemented.)
And in a separate shell, type:
% cd lg3d-wonderland
% ant run
The Wonderland client should appear, ask you to log in (you can use no password by default) and in a few moments you should
see a black sphere appear before you.
Extra Credit
If you would like to extend the functionality of this new cell type, you may make the radius of the object or its color configurable.
These changes would involve both the client and server class, as well as the ShapeCellSetup class and corresponding XML
cell descriptor file within WFS. You may also add support for other types of shapes (e.g. cones, cylinders). This basic cell type
can be used as a template for any cell that draws into the world using Java3D primitives (e.g. 3D text)--feel free to experiment!
Next Steps
In the next tutorial, you will learn how to handle when the configurable parameters of your cell type change during run-time. In
future tutorials you will learn how to texture your shape, enhancing its appearance. By using textures, you will learn how
to interact with artwork repositories. Future tutorials will also explore how to receive notification of events (e.g. mouse clicks) for
your cell and also manage and update the state of your cell shared among many clients.
Part 2 - Handle the reconfigure of a cell's properties dynamically. Part 3 - Texture your shape with an image. Part 4 - Handle input events for your cell Part 5 - Complete the cell