TOC
BACK
FORWARD
HOME

Java 1.1 Unleashed

- 45 -
Building VRML 2 Behaviors in Java

by Justin Couch revised by Bruce Campbell

IN THIS CHAPTER

  • Making the World Behave
  • An Overview of VRML
  • The VRML Script Node
  • VRML Data Types in Java
  • Integrating Java Scripts with VRML
  • The Browser Class
  • The Script Execution Model
  • Creating Efficient Behaviors
  • Dynamic Worlds--Creating VRML on the Fly
  • Creating Reusable Behaviors
  • The Future: VRML, Java, and AI


You've probably had enough of buttons, menus, and creating a hundred and one pictures for animations. Now you're looking for something a little different. If you have been following the computing press, you may have noticed discussions about another hot Web technology called VRML--the Virtual Reality Modeling Language. VRML is designed to produce the 3D equivalent of HTML: a three-dimensional scene defined in a machine-neutral format that can be viewed by anyone with an appropriate viewer. VRML is similar to HTML in that it can be created with a simple ASCII editor such as Notepad and delivered across the Web by the same Web server that delivers HTML documents.

The first version of the standard, VRML 1, produced static scenes that could be visited by anyone with a VRML 1 browser or appropriate VRML 1 plug-in viewer created for use with the Netscape Navigator or Internet Explorer browser. VRML 1 is a derivative of Silicon Graphic's Open Inventor file format. In that first version, a user could visit a 3D scene by walking or flying around, but there was no way to interact with the scene apart from clicking on the 3D equivalent of hypertext links. This was a deliberate decision on the part of the designers. VRML 1 was enough to get the art community interested in creating some beautiful virtual places.

In December 1995, the VRML community decided to drop planned revisions to version 1 and head straight to a fully interactive version, VRML 2. One of the prime requirements for VRML 2 was the ability to support programmable behaviors. Of the seven proposals, the Moving Worlds submission by Sony and SGI came out as the favorite among the 2,000 members of the VRML mailing list. Contained in a draft proposal for VRML 2 was a Java API for creating behaviors for objects in a VRML 2 scene.

Effectively combining VRML and Java requires a good understanding of how both languages work. This chapter introduces the Java implementation of the VRML API and shows you how to get the most from a dynamic virtual world. For specifics about VRML 2, Teach Yourself VRML 2 in 21 days, published by Sams.net, is an excellent reference book.

Interestingly enough, Sony has implemented a Java API in its CommunityPlace VRML 2 viewer. This Java API is somewhat different than the Java API implemented by SGI in its CosmoPlayer VRML 2 viewer. Although they use the same basic class structure, you can think of Sony's approach as adding Java scripting to the VRML 2 node structure and SGI's approach as providing VRML 2 3D graphics output capabilities to any Java program. Each of these approaches to integrating Java with VRML has its strengths and weaknesses. Both methods are covered in this chapter with appropriate code examples. SGI has promoted a VRML-specific scripting language called VRMLscript, which you can use instead of Java when you are scripting behaviors; VRMLscript is similar to Sony's interpretation of intranode scripting. Consult a VRML 2 reference for the specifics of VRMLscript. VRMLscript is an appropriate alternative for Java scripting on many occasions, but is more similar to JavaScript than it is to the Java language itself.


NOTE: You can learn more about Sony's CommunityPlace viewer at http://vs.spiw.com/vs/; you'll find additional information about SGI's CosmoPlayer at http://vrml.sgi.com/cosmoplayer/.

Making the World Behave

Within the virtual reality environment, any dynamic change in a virtual world is regarded as a behavior. This change can be something as simple as an object changing color when it is touched or something as complex as autonomous, semi-intelligent agents that look and act like humans, such as Neal Stephenson's Librarian from his visionary, sci-fi novel Snow Crash.

To understand how to create behaviors for virtual objects, you have to understand how VRML works. Although this section won't delve into a lengthy discussion of VRML, a few basic concepts are reviewed. To start with, VRML is a separate language from the Java programming language used in scripts. The Java VRML classes interact only with a preexisting VRML scene contained in a file with a .wrl extension. The scene is developed using the VRML language to create a collection of VRML nodes; the scene is then associated with a rectangular region within the browser window. The VRML browser then provides a consistent method for moving around in the scene and interacting with it. Although it is possible to create a VRML browser exclusively in Java, both Sony and SGI have compiled machine-dependent browsers for the purposes of rendering speed. Even with a VRML browser written in Java, a scene must be created using VRML, which the Java VRML classes can then dynamically change over time.

Within Sony's CommunityPlace Java API, each virtual object can have its own script attached to it. Creating a highly complex world in this manner requires writing many short scripts. For more interesting behaviors, a longer, heavyweight script can combine the VRML API classes with Java's thread and networking classes.

To minimize the amount of external programming, the VRML specification contains a number of nodes to create common behaviors within the .wrl file itself. These nodes are of two types: sensors and interpolators. Sensor nodes initiate events when the user interacts with the sensor. Sensors are associated with virtual objects, timers, and locations in three-dimensional space. A location is a 3D volume defined by a cylinder, disk, plane, sphere, or point. Sensors initiate events based on touch (such as a mouse click), time, or proximity (to the user's viewpoint). Interpolators change a virtual object over time between specific end-point values defined by the VRML author. Interpolators are available for changing color, scale, position, and orientation over time. By using the specifics of VRML 2, a sensor can turn on a timer that then interpolates the value of a virtual object's characteristic over time. An example is a virtual doorbell that senses a user's mouse click and turns on an orientation interpolator that opens a door slowly over a specific time period. This door example is easily included in a VRML scene without Java.

An Overview of VRML

The VRML world description uses a traditional hierarchical scene-graph approach reminiscent of PEX/PHIGS and other 3D toolkits. A scene graph is a document used to express the hierarchical relationships between all the objects in a scene. The word graph as used in the term scene graph refers to an organization of objects described by certain mathematical characteristics. In a graph, each object in the scene is called a node, and each connecting line is called an edge. Each node within a scene graph has a parent, and each parent can have multiple children. As an example of a hierarchical VRML scene graph, a body Transform node can include two leg Transform nodes, each of which can contain a foot Transform node, which can contain five toe Transform nodes. The toe Transform node can have children nodes that define its geometry, texture, and color as well as a sensor to make it interactive.

Surprisingly, VRML nodes can be represented in a semiobject-oriented manner that meshes well with Java. Each node has a number of fields. These fields can be accessed by other nodes only if they are explicitly declared to be accessible. You can declare a field as read or write only, or you can define it to require a specific method to access its value. In VRML syntax, the four types of field access are described as follows:

  • field: Hidden from general access--a private field

  • eventIn: Sends a value to a node--a write-only fiel

  • eventOut: Sends a value from a node--a read-only fiel

  • exposedField: Publicly accessible for both read and write

The official VRML 2 node definition specifies the standard accessibility of each field in a node; you must then consider accessibility when you are writing behavior scripts. Most scripts are written to process a value being passed to the script in the form of an eventIn field, which then passes the result back through an eventOut field. Any internal values are kept in fields. The Script can be defined as a node within the scene graph. These Script nodes are not permitted to have exposedFields because of the updating and implementation ramifications within the event system.

Although a node can consist of a number of input and output fields, these fields do not all have to be connected. Usually, the opposite is the case--only a few of the available connections are made. VRML requires explicit connection of nodes using the ROUTE keyword, as shown here:

ROUTE fromNode.fieldname1 TO toNode.fieldname2

The only restriction when connecting fields is that the two fields be of the same type. No casting of types is permitted.

This route mechanism can be very powerful when combined with scripting. Basically, both a VRML ROUTE statement and a Java script can send an eventIn field or process an eventOut field. The specification allows both fan in and fan out of events. Fan in occurs when many nodes send an event to a single eventIn field of a node. Fan out is the opposite: one eventOut object is connected to many other eventIn objects. Fan out is handy when you want one script to control a number of different objects at the same time; for example, a light switch eventOut object turning on multiple lights simultaneously.

If two or more events fan in on a particular eventIn object, the results are undefined. You should be careful to avoid such situations unless the ambiguity is intended. An example of this situation is when two separate animation scripts set the position of the same virtual object. To avoid this situation in a complicated animated scene, create an event graph with arrows showing the direction of events firing from one node to another. Then make sure that any two or more events coming in to the same node cannot possibly fire at the same time.

All VRML data types follow the standard programming norms. There are integer, floating-point, string, and boolean standard types as well as specific types for handling 3D graphics such as points, vectors, image, and color. To deal with the extra requirements of the VRML scene, node and time types have been added. The node data type contains an instance pointer to a particular node in the VRML scene graph (such as a virtual toe or foot). Individual fields within a node are not accessible directly. Individual field references in behaviors programming is rarely necessary because communication is on an event-driven model. When field references are needed within the API, a node instance and field string description pair are used.

Except for the boolean and time types, which are always single, values can be either single or multivalued. The distinction is made in the field name. An SF prefix is used for single-valued fields and an MF prefix is used for multivalued fields. For example, type SFInt32 defines a single integer; type MFInt32 defines an array of integers. The Script node definition in the next section contains an MFString and a SFBool. The MFString is used to contain a collection of URLs, each kept in its own separate substring, but the SFBool contains a single boolean flag controlling a condition.

The VRML Script Node

The Script node provides the way to integrate a custom behavior into VRML. Behaviors can be programmed in any language supported by the browser and for which an implementation of the API can be found. In the final specification of VRML 2, sample APIs were provided for Java, C, and also VRML's own scripting language, VRMLscript--a derivative of Netscape's JavaScript. The Script node is defined as shown here:

Script {
  exposedField MFString url           []
  field        SFBool   directOutput  FALSE
  field        SFBool   mustEvaluate  FALSE
  # And any number of:
  eventIn      eventTypeName eventName
  field        fieldTypeName fieldName initialValue
  eventOut     eventTypeName eventName

}

Unlike standard HTML, VRML enables multiple target files of an open location request to be specified in order of preference. The url field contains any number of strings specifying URLs or URNs to the desired behavior script. For compiled Java scripts, this would be the URL of the .class file, but the url list is not limited to just one script type.

Apart from specifying the behavior file, VRML also enables control over how the Script node performs within the scene graph. The mustEvaluate field tells the browser how often the script should be run. If this field is set to TRUE, the browser must send events to the script as soon as they are generated, forcing an execution of the script. If the field is set to FALSE, in the interests of optimization, the browser may elect to queue events until the outputs of the script are required by the browser. A TRUE setting is most likely to cause browser performance to degrade because of the constant context-swapping needed; a FALSE setting queues events to keep the context-swapping to a minimum. Unless you are performing something the browser is not aware of (such as using a networking or database functionality), you should set the mustEvaluate field to FALSE.

The directOutput field controls whether the script has direct access for sending events to other nodes. Java methods require the node reference of other nodes when setting field values. If, for example, a script is passed an instance of a Transform node, and the directOutput field is set to TRUE, the script can send an event directly to that node. To add a new default box to this group, the script would contain the following code:

SFNode    group_node = (SFNode)getField("group_node");
group_node.postEventIn("add_children", (Field)CreateVRMLfromString("Box"));

If directOutput is set to FALSE, it requires the Script node to have an eventOut field with the corresponding event type specified (an MFNode in this case), and a ROUTE connecting the script with the target node.

There are advantages to both approaches. When the scene graph is static in nature, the second approach (using known events and ROUTE statements) is much simpler. However, in a scene in which objects are being generated on the fly, static routing and events do not work and the first approach is required.

VRML Data Types in Java

The API is built around two Java interfaces defined in the package vrml. The eventIn and Node interfaces are defined as follows:

interface eventIn {
    public String      getName();
    public SFTime      getTimeStamp();
    public ConstField  getValue();
}
interface Node {
    public ConstField getValue(String fieldName)
        throws InvalidFieldException;
    public void postEventIn(String eventName, Field eventValue)
        throws InvalidEventInException;

}

In addition to these two interfaces, each of the VRML field types also has two class definitions that are subclasses of Field: a standard version and a restricted, read-only version. The Const* definitions are used only in the eventIn fields defined in individual scripts. Unless that field class has an exception explicitly defined, these class definitions are guaranteed not to generate exceptions.

For nonconstant fields, each class has at least the setValue() and getValue() methods that return the Java equivalent of the VRML field type. For example, a SFRotation class returns an array of floats mapping to the x, y, z, and orientation, but the MFRotation class returns a two-dimensional array of floats. The multivalued field types also have a set1Value() method, which enables the caller to set an individual element.

SFString and MFString need special attention. Java defines them as being Unicode characters, but VRML defines them as a subset of the Unicode character set--UTF-8. Ninety-nine percent of the time, this difference between Java and VRML should not present any problems. Listing 48.1 includes a complete list of the available field types as part of the VRML API class hierarchy as implemented by SGI's CosmoPlayer VRML viewer. Check out http://vrml.sgi.com/moving-worlds/spec/ExternalInterface.html#Hierarchy for any new developments. Note that the classes are divided into three packages: vrml, vrml.field, and vrml.node. This code is also located on the CD-ROM that accompanies this book.

Listing 45.1. The VRML API class hierarchy.

java.lang.Object
     |
     +- vrml.Event
     +- vrml.Browser
     +- vrml.Field
     |       +- vrml.field.SFBool
     |       +- vrml.field.SFColor
     |       +- vrml.field.SFFloat
     |       +- vrml.field.SFImage
     |       +- vrml.field.SFInt32
     |       +- vrml.field.SFNode
     |       +- vrml.field.SFRotation
     |       +- vrml.field.SFString
     |       +- vrml.field.SFTime
     |       +- vrml.field.SFVec2f
     |       +- vrml.field.SFVec3f
     |       |
     |       +- vrml.MField
     |       |       +- vrml.field.MFColor
     |       |       +- vrml.field.MFFloat
     |       |       +- vrml.field.MFInt32
     |       |       +- vrml.field.MFNode
     |       |       +- vrml.field.MFRotation
     |       |       +- vrml.field.MFString
     |       |       +- vrml.field.MFTime
     |       |       +- vrml.field.MFVec2f
     |       |       +- vrml.field.MFVec3f
     |       |
     |       +- vrml.ConstField
     |               +- vrml.field.ConstSFBool
     |               +- vrml.field.ConstSFColor
     |               +- vrml.field.ConstSFFloat
     |               +- vrml.field.ConstSFImage
     |               +- vrml.field.ConstSFInt32
     |               +- vrml.field.ConstSFNode
     |               +- vrml.field.ConstSFRotation
     |               +- vrml.field.ConstSFString
     |               +- vrml.field.ConstSFTime
     |               +- vrml.field.ConstSFVec2f
     |               +- vrml.field.ConstSFVec3f
     |               |
     |               +- vrml.ConstMField
     |                       +- vrml.field.ConstMFColor
     |                       +- vrml.field.ConstMFFloat
     |                       +- vrml.field.ConstMFInt32
     |                       +- vrml.field.ConstMFNode
     |                       +- vrml.field.ConstMFRotation
     |                       +- vrml.field.ConstMFString
     |                       +- vrml.field.ConstMFTime
     |                       +- vrml.field.ConstMFVec2f
     |                       +- vrml.field.ConstMFVec3f
     |
     +- vrml.BaseNode
             +- vrml.node.Node
             +- vrml.node.Script
java.lang.Exception
        java.lang.RuntimeException
                vrml.InvalidRouteException
                vrml.InvalidFieldException
                vrml.InvalidEventInException
                vrml.InvalidEventOutException
                vrml.InvalidExposedFieldException
                vrml.InvalidNavigationTypeException
                vrml.InvalidFieldChangeException
        vrml.InvalidVRMLSyntaxException

Integrating Java Scripts with VRML

At some point, each VRML Script node is connected with a list of instructions that brings the VRML objects to life. For simple animations, VRMLscript is an appropriate language to use for coding behaviors. If you need more complexity, the thread and networking features of the Java environment make Java a more capable language for coding behaviors efficiently. Java can be used to create VRML behaviors by extending the Script class or by tying VRML-aware classes together with a VRML scene in an HTML document.

The Script Class Definition

Sony's CommunityPlace VRML viewer includes the Script class as part of its Java API. Remember that SGI's CosmoPlayer Java API does not include the Script class because you are expected to use VRMLscript for your intranode scripting. The definition of Sony's Java Script class follows; as this book goes to press, SGI has not yet included a Script class in its API.

Class Script implements Node {
    public void processEvents(Events [] events)
        throws Exception;
    public void eventsProcessed()
        throws Exception
    protected Field getEventOut(String eventName)
        throws InvalidEventOutException;
    protected Field getField(String fieldName)
        throws InvalidFieldException

}

When you create a script, you are expected to subclass the Script class definition to provide the necessary functionality. The class definition deliberately leaves the definition of the codes for the exceptions up to you so that you can create tailored exceptions and handlers.

The getField() method returns the value of the field nominated by the given string. This method is how the Java script gets the values from the VRML Script node fields. This method is used for all fields and exposedField fields. To the Java script, an eventOut looks just like another field. There is no need to write an eventOut function--the value is set by calling the appropriate field type's setValue() method.

Dealing with Event Input

Every eventIn field specified in the VRML Script node definition requires a matching public method in the Java implementation. The method definition takes this form:

public void <eventName>(Const<eventTypeName> <variable name>, SFTime <variable name>);

The method must have the same name as the matching eventIn field in the VRML script description. The second field corresponds to the timestamp of when the event was generated. The SFTime field is particularly useful when the mustEvaluate field is set to FALSE, meaning that an event may be queued for some time before finally being processed.

Because Script is an implementation of the Node interface, it contains the postEventIn() method. Earlier in this chapter, you learned that you should not call the eventIn methods of other scripts directly. To facilitate direct internode communication, the postEventIn() method enables you to send information to other nodes while staying within the VRML event-handling system. The arguments are a String specifying the eventIn field name and a Field containing the value. The value is a VRML data type cast to Field. The use of PostEventIn(), as available in Sony's CommunityPlace API, is shown in the following example and is also used later in this chapter when a simple dynamic world is constructed.

//The node we are getting is a translation
Node translation;
float[3] translation_details;
translation[0] = 0.0f;
translation[1] = 2.3f;
translation[2] = -0.4f;
translation.postEventIn("translation", (Field)translation);

SGI's CosmoPlayer API does not use a postEventIn() method. Instead, it uses the following syntax that, in effect, does the same thing:

Node translation = browser.getNode("box");
EventInSFVec3f set_translation = (EventInSFVec3f) material.getEventIn("translation");
float[] val = new float[3];
val[0] = 0.0f;
val[1] = 2.3f;
val[2] = -0.4f;
set_translation.setValue(val);

Remember that SGI's API does not contain the Script class. The event-processing methods, processEvents() and eventsProcessed(), are described later in this chapter.

The First Behavior--A Color-Changing Box

Now you are ready to put this all together to create a color-changer behavior that toggles a box's color between red and blue. Figure 45.1 shows a simple VRML box that has turned blue as a result of a user's mouse click (trust me; although the figure is shown in black and white, the box really is blue!). The following sections show you how to create the example for both the CommunityPlace and CosmoPlayer VRML viewers. The CommunityPlace example requires five components: a Box primitive, a TouchSensor object, a Material node, the Script node, and the Java script. The CosmoPlayer example requires a Box primitive, a TouchSensor object, a Material node, the Java script, and an HTML document to associate the Java script with the VRML scene.

Figure 45.1.
The color-changing box example.

The CommunityPlace Version

For the CommunityPlace viewer, the basic VRML scene consists of a TouchSensor-enabled, red box placed at the scene origin (0,0,0) with an associated Script node and a couple of ROUTE statements. Listing 45.2 shows the complete VRML scene, which was created using a simple text editor. In fact, all the VRML scenes in this chapter were typed by hand into the Notepad text editor that is a part of the Windows operating system. You can find the code in Listing 45.2 on the CD-ROM that accompanies this book.

Listing 45.2. The VRML scene graph for CommunityPlace.

#VRML V2.0 utf8
#filename: s_box.wrl
Transform {
    bboxSize    1 1 1
    children [
        Shape {
             appearance Appearance {
                material DEF cube_material Material {
                    diffuseColor 1 0 0 #start red.
                }
            }
            geometry Box {size 1 1 1}
        } # end of shape definition
        # Now define a TouchSensor node. This node takes in the
        # geometry of the parent transform. Default behavior OK.
        DEF cube_sensor TouchSensor {}
    ]
}
DEF color_script Script {
    url    "s_color_changer.class"
    # now define our needed fields
    field      SFBool        isRed        TRUE
    eventIn    SFBool        clicked
    eventOut   SFColor       color_out
}
ROUTE cube_sensor.isActive TO color_script.clicked
ROUTE color_script.color_out TO cube_material.set_diffuseColor

The Script node acts as the color changer. It takes input from the TouchSensor object and outputs the new color to the Material node. The Script node must also keep internal track of the color. This is done by reading in the value from the Material node, but for demonstration purposes, an internal flag is included in the script. No fancy processing or event sending to other nodes is necessary, so both the mustEvaluate and directOutputs fields can be left at the default setting of null.

Finally, the Java script shown in Listing 45.3 is compiled to produce the color_changer.class file that is referenced in Listing 45.2 in the Script node's behavior field. You can find this code on the CD-ROM that accompanies this book.

Listing 45.3. The sample Java script for CommunityPlace.

//filename: s_color_changer.java
import vrml.field.*;
import vrml.node.*;
import vrml.*;
class s_color_changer extends Script {
    // declare the field
    private SFBool   isRed = (SFBool)getField("isRed");
    // declare the eventOut
    private SFColor  color_out = (SFColor)getEventOut("color_out");
    // declare color float array
    float[] color;
    // declare eventIns
    public void clicked(ConstSFBool isClicked, ConstSFTime ts) {
        // called when the user clicks or touches the cube or
        // stops touching/click so first check the status of the
        // isClicked field. We will only respond to a button up.
        if(isClicked.getValue() == false) {
            // now check whether the cube is red or blue
            if(isRed.getValue() == true)
                isRed.setValue(false);
            else
                isRed.setValue(true);
        }
    }
    // finally the event processing call
    public void eventsProcessed() {
        if(isRed.getValue() == true) {
            color = new float[3];
            color[0] = 0.0f;
            color[1] = 0.0f;
            color[2] = 1.0f;
            color_out.setValue(color);
        }
        else {
            color = new float[3];
            color[0] = 1.0f;
            color[1] = 0.0f;
            color[2] = 0.0f;
            color_out.setValue(color);
        }
    }

}

The CosmoPlayer Version

For the CosmoPlayer viewer, the basic VRML scene consists of a TouchSensor-enabled, red box placed at the scene origin (0,0,0) but does not include a Script node or the ROUTE statements used with the CommunityPlace example. As you see in Listing 45.4 (which is also located on the CD-ROM that accompanies this book), the routing is done within the Java script itself.

Listing 45.4. The VRML scene graph for CosmoPlayer.

#VRML V2.0 utf8
#filename: sgi_box.wrl
Transform {
    bboxSize    1 1 1
    children [
        Shape {
            appearance Appearance {
                material DEF cube_material Material {
                    diffuseColor    1.0 0 0 #start red.
                }
            }
            geometry Box {size 1 1 1}
        } # end of shape definition
        # Now define a TouchSensor node. This node takes in the
        # geometry of the parent transform. Default behavior OK.
        DEF cube_sensor TouchSensor {}
    ]

}

Instead of connecting the Java script to the VRML scene within the VRML file, the connection is made in an HTML document. This approach has one big advantage: You can use the Java AWT to control your VRML scene interaction. The HTML code is provided in the file Color_changer.htm, as shown in Listing 45.5, and can be found on the CD-ROM.

Listing 45.5. The HTML document for the color-changer example in CosmoPlayer.

<HTML>
<HEAD>
<TITLE>Color Changer Example</TITLE>
</HEAD>
<BODY>
<CENTER>
<EMBED SRC="sgi_box.wrl" BORDER=0 HEIGHT="400" WIDTH="400">
</CENTER>
<APPLET CODE="sgi_color_changer.class" MAYSCRIPT>
</APPLET>
</BODY>
</HTML>

In the HTML file, you connect the box.wrl VRML file to the Color_changer.class Java class using the <EMBED> and <APPLET> HTML tags. Note that you can set the HEIGHT and WIDTH of the VRML scene to whatever size you want; any Java controls you include in the .class file appear in an area following your VRML scene.

Finally, the Java sgi_color_changer class source code is shown in Listing 45.6 (this code can also be found on the CD-ROM). This code creates the Java controls and connects them to the VRML scene for visitors to use to interact with the scene. The code in Listing 45.6 allows a user to interact with the VRML scene using Java AWT controls such as buttons and checkboxes, which are made available in a separate control panel. The code in Listing 45.7 allows a user to interact with the VRML scene by clicking on VRML objects in the scene itself.

Listing 45.6. The sample Java script for CosmoPlayer.

//filename: sgi_color_changer.java
import java.awt.*;
import java.applet.*;
import vrml.external.field.EventOut;
import vrml.external.field.EventInSFColor;
import vrml.external.Node;
import vrml.external.Browser;
import vrml.external.exception.*;
import netscape.javascript.JSObject;
public class sgi_color_changer extends Applet {
  TextArea output = null;
  Browser browser = null;
  Node material = null;
  EventInSFColor diffuseColor = null;
  boolean red = true;
  boolean error = false;
  public void init() {
    add(new Button("Change Color"));
    JSObject win = JSObject.getWindow(this);
    JSObject doc = (JSObject) win.getMember("document");
    JSObject embeds = (JSObject) doc.getMember("embeds");
    browser = (Browser) embeds.getSlot(0);
    // Now we've got the handle to the VRML Browser.
    try {
      // Get the material node...
      material = browser.getNode("cube_material");
      // Get the diffuseColor EventIn
      diffuseColor = (EventInSFColor) material.getEventIn("set_diffuseColor");
    }
    catch (InvalidNodeException ne) {
      showStatus("Failed to get node:" + ne);
      error = true;
    }
    catch (InvalidEventInException ee) {
      showStatus("Failed to get EventIn:" + ee);
      error = true;
    }
    catch (InvalidEventOutException ee) {
      showStatus("Failed to get EventOut:" + ee);
      error = true;
    }
  }

  public boolean action(Event event, Object what) {
    if (error)
      {
         showStatus("Problems! Had an error during initialization");
         return true;  // Uh oh...
      }
    if (event.target instanceof Button)
      {
        Button b = (Button) event.target;
        if (b.getLabel() == "Change Color") {
            if (red) {
                showStatus ("Blue!");
                float[] val = new float[3];
                val[0] = 0.2f;
                val[1] = 0.2f;
                val[2] = 0.8f;
                diffuseColor.setValue(val);
                red = false;
           }
           else {
                showStatus("Red!");
                float[] val = new float[3];
                val[0] = 0.8f;
                val[1] = 0.2f;
                val[2] = 0.2f;
                diffuseColor.setValue(val);
                red = true;
           }
        }
      }
    return true;
  }

}

So that you can click the box and change the color from within the VRML scene instead of from a Java AWT button, SGI's browser sets up an event callback in the Java class by implementing EventOutObserver. The EventOutObserver sets up event awareness for the VRML scene. If you want to interact with the cube within the VRML itself instead of from a Java AWT button, just replace sgi_color_changer.class with Color_changer.class, compiled from the source code in Listing 45.7. This code can also be found on the CD-ROM that accompanies this book.

Listing 45.7. An alternative Java script for CosmoPlayer.

//filename: Color_changer.java
import java.awt.*;
import java.applet.*;
import vrml.external.field.EventOut;
import vrml.external.field.EventInSFColor;
import vrml.external.field.EventOutSFColor;
import vrml.external.field.EventOutSFTime;
import vrml.external.field.EventOutObserver;
import vrml.external.Node;
import vrml.external.Browser;
import vrml.external.exception.*;
import netscape.javascript.JSObject;
public class Color_changer extends Applet implements EventOutObserver {
  TextArea output = null;
  Browser browser = null;
  Node material = null;
  EventInSFColor diffuseColor = null;
  EventOutSFColor outputColor = null;
  EventOutSFTime touchTime = null;
  boolean red = true;
  boolean error = false;
  public void init() {
    JSObject win = JSObject.getWindow(this);
    JSObject doc = (JSObject) win.getMember("document");
    JSObject embeds = (JSObject) doc.getMember("embeds");
    browser = (Browser) embeds.getSlot(0);
    // Now we've got the handle to the VRML Browser.
    try {
      // Get the material node...
      material = browser.getNode("cube_material");
      // Get the diffuseColor EventIn
      diffuseColor = (EventInSFColor) material.getEventIn("set_diffuseColor");
      // Get the Touch Sensor
      Node sensor = browser.getNode("cube_sensor");
      // Get its touchTime EventOut
      touchTime = (EventOutSFTime) sensor.getEventOut("touchTime");
      // Set up the callback
      touchTime.advise(this, new Integer(1));
      // Get its diffuseColor EventOut
      outputColor = (EventOutSFColor) material.getEventOut("diffuseColor");
      // Set up its callback
      outputColor.advise(this, new Integer(2));
    }
    catch (InvalidNodeException ne) {
      add(new TextField("Failed to get node:" + ne));
      error = true;
    }
    catch (InvalidEventInException ee) {
      add(new TextField("Failed to get EventIn:" + ee));
      error = true;
    }
    catch (InvalidEventOutException ee) {
      add(new TextField("Failed to get EventOut:" + ee));
      error = true;
    }
  }

  public void callback(EventOut who, double when, Object which) {
    Integer whichNum = (Integer) which;
    if (whichNum.intValue() == 1) {
          if (red) {
                showStatus ("Blue!");
                float[] val = new float[3];
                val[0] = 0.2f;
                val[1] = 0.2f;
                val[2] = 0.8f;
                diffuseColor.setValue(val);
                red = false;
         }
         else {
                showStatus("Red!");
                float[] val = new float[3];
                val[0] = 0.8f;
                val[1] = 0.2f;
                val[2] = 0.2f;
                diffuseColor.setValue(val);
                red = true;
         }
    }
    if (whichNum.intValue() == 2) {
      // Make the new color of the sphere and timestamp
      // show up in the textarea.
      float[] val = outputColor.getValue();
      showStatus("Got color " + val[0] + ", " + val[1] + ", " +
                                val[2] + " at time " + when + "\n");
    }
  }

}

Of course, you can combine both the Java controls and the VRML node callback routine in the same Java file.

That's it. Now you have a cube that changes color when you click it. The code looks almost identical for changing a virtual object's translation, rotation, or scale in the VRML scene. All other eventIns can be accessed in similar fashion--including the current scene viewpoint. Creating more complex behaviors is just a variation of this scheme, with more Java code and fields. Although the basic user input usually comes from sensors, in the SGI approach the events can come from any Java program--including a program that embeds all kinds of physics to handle interactions between the objects drawn to the screen in the VRML scene. This approach creates many scientific visualization opportunities.

Even with Sony's approach, scripts are not restricted to input methods based on eventIn fields. One example is a stock market tracker that runs as a separate thread. It can constantly receive updates from the network, process them, and then send the results through a public method to the script, which then puts the appropriate results into the 3D world.

The Browser Class

Behaviors using the methods presented in the color-changer box examples work for many simple systems. Effective virtual reality systems, however, require more than just being able to change the color and shape of objects that already exist in the virtual world. As a thought experiment, consider a virtual taxi: A user should be able to step inside and instruct the cab where to go. Using the techniques from the preceding examples, the cab would move off, leaving the user in the same place. The user does not "exist" as part of the scene graph--the user is known to the browser but not to the VRML scene-rendering engine. Clearly, a greater level of control is needed.

Changing the Current Scene

The VRML 2 specification defines a series of actions the programmer can access to set and retrieve information about the virtual world. Within the Java implementation of the API, world information is provided in the Browser class. The Browser class provides all the functions a programmer needs that are not specific to any particular part of the scene graph.

To define a system-specific behavior, the first functions you must define are these:

public static String getName();
public static String getVersion();

These strings are defined by the browser writer and identify the browser in some unspecified way. If this information is not available, empty strings are returned.

If you are programming expensive calculations, you may want to know how they affect the rendering speed (frame rate) of the system. The getCurrentFrameRate() method returns the value in frames per second. If this information is not available, the return value is 100.0.

public static float getCurrentFrameRate();

In systems that use prediction, two more handy pieces of information to know are what mode the user is navigating the scene in, and at what speed the user is traveling. Similar to the getName() method, the string returned to describe the navigation type is browser dependent. VRML defines that, at a minimum, the following four navigation types must be supported: WALK, EXAMINE, FLY, and NONE. However, if you are building applications for an intranet and you know what type of browser is used, this navigation information can be quite handy for varying the behavior, depending on how the user approaches the object of interest. Information about navigation is available from the following methods:

public static String getNavigationType();
public static void   setNavigationType(String type)
    throws InvalidNavigationTypeException;
public static float getNavigationSpeed();
public static void  setNavigationSpeed(float speed);
public static float getCurrentSpeed();

The difference between navigation speed and current speed is in the definition. VRML 2 defines a navigationInfo node that contains default information about how to act if given no other external cues. The navigation speed is the default speed in units per second; these units are defined for each browser by the individual browser developers. There is no specification about what this speed represents, only hints. A reasonable estimate of the navigation speed is the movement speed in WALK and FLY mode and the movement speed used in panning and dollying in EXAMINE mode, encountered when a user has not selected any speed controls. The current speed is the actual speed at which the user is traveling at that point in time. This is the speed that the user has set with the browser controls. Speed controls, when they are provided by the browser developer, allow the user to vary from the default navigation speed. For example, a browser with a default speed of 70 pixels/second may slow down to 40 pixels/second when the user selects the slow speed control from an available menu.

Having two different descriptions of speed may seem wasteful, but it comes in quite handy when moving between different worlds. The first world may be a land of giants, where traveling at 100 units per second is considered slow, but in the next world, which models a molecule that is only 0.001 units across, this speed would be ridiculously fast. The navigation speed value can be used to scale speeds to something reasonable for the particular world.

Also contained in the navigationInfo node is a boolean field for a headlight. The headlight is a directional light that points in the direction the user is facing. Where the scene creator has used other lighting effects (such as radiosity), the headlight is usually turned off. In the currently available browsers, the headlight field has led to software bugs (for example, turning off the headlight results in the whole scene going black). It is recommended that you do not use the headlight feature within the behaviors because the browser includes logic to determine when best to use the headlight. For example, the headlight is usually disabled when the browser is showing the effects of a single point of light. If you have to access the headlight, the following functions are provided by the Browser class:

public static boolean getHeadlight();
public static void setHeadlight(boolean onOff);

The methods described in this section enable you to change individual components of the world. The other approach is to completely replace the world with some internally generated one. This approach enables you to use VRML to generate new VRML worlds on the fly--assuming that you already are part of a VRML world (you cannot use this approach in an application to generate a 3D graphics front-end). Use the following statement to replace the current world with an internally generated one:

public static void replaceWorld(node nodes[]);

This is a nonreturning call that unloads the current scene graph and replaces it with a new one.

Modifying the Scene

There is only so much you can do with what is already available in a scene. Complex worlds use a mix of static and dynamically generated scenery to achieve their impressive special effects. You can dramatically change a VRML scene while a user is visiting it. The fact that nodes are embedded within other nodes in a scene graph makes VRML very flexible for changing just the specific part of the scene you want to change.

You can query the current world to find out the URL from which it was originally loaded. Your Java code can then contain different paths based on the current world:

public static String getWorldURL();

GetWorldURL() returns the URL of the root of the scene graph rather than the URL of the currently occupied part of the scene. VRML enables a complex world to be created using a series of small files that are included in the world--a technique called inlining in VRML parlance.

You can change your VRML scene dynamically by using the following three browser methods:

public static void loadWorld(String[] url);
public static Node createVrmlFromString(String vrmlSyntax);
public static void createVrmlFromURL(String[] url,
                                     Node     node,
                                     String   eventInName);

To completely replace the scene graph, you call the loadWorld() method. As with all URL references in VRML, an array of strings are passed. These strings are a list of URLs and URNs to be loaded in order of preference. Should the load of the first URL fail, the browser attempts to load the second, and so on until a scene is loaded or the browser reaches the end of the list. If the load fails, the VRML viewer can notify the user as the developer sees fit. The specification also states that it is up to the browser whether the loadWorld() call blocks or starts a separate thread when loading a new scene.

In addition to replacing the whole scene, you may want to add bits at a time to a scene. You can do this in one of two ways. If you are very familiar with VRML syntax, you can create strings on the fly and pass them to the createVrmlFromString() call. The node that is returned can be added to the scene to produce dynamic results.

Perhaps the most useful of the preceding three functions is the createVrmlFromURL() method. From the definition, you may have noticed that, along with a list of URLs, the method also takes a node instance and a string that refers to an eventIn field name. This call is a nonblocking call that starts a separate thread to retrieve the given file from the URL, converts it into the VRML viewer's internal representation, and then finally sends the newly created list of nodes to the specified node's eventIn field. The eventIn field type must be an MFNode. The Node reference can be any sort of node, not just a part of the script node. This arrangement enables the script writer to add new nodes directly to the scene graph without having to write extra functionality in the script.

With both of the create methods, the returned nodes do not become visible until they have been added to some preexisting node in the scene. Although it is possible to create an entire scene on the fly within a standalone applet, there is no way to make the scene visible unless there is an exiting node instance to which you can add the dynamically generated scene.

Once you have created a set of new nodes, you also want to be able to link them together to get a behavior system similar to the one in the original world. The Browser class defines methods for dynamically adding and deleting ROUTE statements between nodes:

public void addRoute(Node fromNode, String fromEventOut,
                     Node toNode, String toEventIn)
    throws InvalidRouteException;
public void addRoute(Node fromNode, String fromEventOut,
                     Node toNode, String toEventIn)
throws InvalidRouteException;

For each of these methods, you must know the node instance for both ends of the ROUTE. In VRML, you cannot obtain an instance pointer to an individual field in a node. It is also assumed that if you know you will be adding a ROUTE, you also know what fields you are dealing with, so a string is used to describe the field name corresponding to an eventIn or eventOut field. Exceptions are thrown if either of the nodes or fields does not exist or an attempt to delete a nonexistent ROUTE is made.

You now have all the tools required to generate a world on the fly, respond to user input, and modify the scene. The only thing that remains is to acquire the wisdom to create responsive worlds that won't get bogged down in Java code.

The Script Execution Model

When tuning the behaviors in a virtual world, the methods used depend on the execution model. The VRML API gives you a lot of control over exactly how scripts are executed and how events passed to it are distributed.

The arrival of an eventIn field at a Script node causes the execution of the matching method. There is no other way to invoke a method. A script can start an asynchronous thread, which in turn can call another non-eventIn method of the script or can even send events directly to other nodes. The VRML 2 specification makes no mention about scripts containing non-eventIn public methods. Although it is possible to call an eventIn method directly, it is in no way encouraged. Such programming interferes with the script execution model by preventing browser optimization and can affect the running of other parts of the script. Calling an eventIn method directly can also cause performance penalties in other parts of the world, not to mention reentrancy problems within the eventIn method itself. If you find it necessary to call an eventIn method of the script, use the postEventIn() method so that the operation of the browser's execution engine is not affected.

Unless the mustEvaluate field is set, all the events are queued in timestamp order from oldest to newest. For each queued event, the corresponding eventIn method is called. Each eventIn field calls exactly one method. If an eventOut fans out to a number of eventIns, multiple eventIns are generated--one for each node. Once the queue is empty, the eventsProcessed() method for that script is called. The eventsProcessed() method enables any post-event processing to be performed.

A typical use of this post-processing was shown in the example of the color-changing cube, earlier in this chapter. In that example, the eventIn method just took the data and stored it in an internal variable. The eventsProcessed() method took the internal value and generated the eventOut. This approach was overkill for such simple behavior. Normally, such simplistic behavior uses VRMLscript instead of Java. However, the separation of data processing from data collection is very effective in a high-traffic environment, in which event counts are very high and the overhead of data processing is best absorbed into a single, longer run instead of many short runs.

Once the eventsProcessed() method has completed execution, any eventOuts generated as a result are sent as events. If the script generates multiple eventOuts for a single eventOut field, only one event is sent. All eventOuts generated during the execution of the script have the same timestamp.

If your script has spawned a thread, and that script is removed from the scene graph, the browser is required to call the shutdown() method for each active thread to facilitate a graceful exit.

If you want to maintain static data between invocations of the script, it is recommended that your VRML Script node have fields to hold the values. Although it is possible to use static variables within the Java class, VRML makes no guarantees that these variables will be retained, especially if the script is unloaded from memory.

If you are a hardcore programmer, you probably want to keep track of all the event-handling mechanisms yourself. VRML provides the facility to do this with the processEvents() method. This method is called when the browser decides to process the queued eventIns for a script. The method is sent an array of the events waiting to be processed, which you can then do with as you please. Graphics programmers should already be familiar with event-handling techniques from the Microsoft Windows, Xlib, or Java AWT system.

Circular Event Loops

The ROUTE syntax makes it very easy to construct circular event loops. Circular loops can be quite handy. The VRML specification states that if the browser finds event loops, it only processes each event once per timestamp. Events generated as a result of a change are given the same timestamp as the original change because events are considered to happen instantaneously. When event loops are encountered in this situation, the browser enforces a breakage of the loop. The following sample script from the VRML specification uses VRMLscript to explain this process:

DEF S Script {
    eventIn  SFInt32    a
    eventIn  SFInt32    b
    eventOut SFInt32    c
    field    SFInt32    save_a    0
    field    SFInt32    save_b    0
    url    "data:x-lang/x-vrmlscript, TEXT;
        function a(val) { save_a = val; c = save_a+save_b;}
        function b(val) { save_b = val; c = save_a+save_b;}
}
ROUTE S.c to S.b

S computes c=a+b with the ROUTE, completing a loop from the output c back to input b. After the initial event with a=1, the script leaves the eventOut c with the value of 1. This causes a cascade effect, in which b is set to 1. Normally, this generates an eventOut on c with the value 2, but the browser has already seen that the eventOut c has been traversed for this timestamp, and therefore enforces a break in the loop. This leaves the values save_a=1, save_b=1, and the eventOut c=1.

Creating Efficient Behaviors

For all animation programming, the ultimate goal is to keep the frame rate as high as possible. In a multithreaded application like a VRML browser, the less time spent in behaviors code, the more time that can be spent rendering. Virtual reality behavior programming in VRML is still very much in its infancy. This section outlines a few common-sense approaches to keeping up reasonable levels of performance--not only for the renderer, but also for the programmer.

The first technique is to use Java only where necessary. This may sound a little strange in a book about Java programming, but consider the resources required to have not only a 3D-rendering engine but a Java VM loaded to run even a simple behavior; also consider that the majority of your viewers may be people using low-end PCs. Because most VRML browsers specify that a minimum of 16M of RAM is required (and 32M is recommended), also loading the Java VM into memory requires lots of swapping to keep the behaviors going. The inevitable result is bad performance. For this reason, the interpolator nodes and VRMLscript were created--built-in nodes for common basic calculations and a small, light language to provide basic calculation abilities. Use of Java should be limited to the times when you require the capabilities of a full programming language, such as for multithreading and network interfaces.

When you do have to use Java, keep the amount of calculation in the script to a minimum. If you are producing behaviors that require either extensive network communication or data processing, these behaviors should be kept out of the Script node and sent off in separate threads. The script should start the thread as either part of its constructor or in response to some event and then return as soon as possible.

In VR systems, frame rate is king. Don't aim to have a one-hundred percent correct behavior if it leads to half the frame rate when a ninety-percent correct behavior will do. It is amazing how users don't notice an incorrect behavior, but as soon as the picture update slows down, they start to complain. Every extra line of code in the script delays the return of the CPU back to the renderer. In military simulations, the goal is to achieve 60fps; even for Pentium-class machines, your goal should be to maintain at least 20fps. Much of this comes down not only to how detailed the world is, but also to how complex the behaviors are. As always, the tradeoff between accuracy and frame rate is up to the individual programmer and the requirements of the application. Your user will typically accept that a door does not open smoothly as long as he or she can move around without watching individual frames redraw.

Don't play with the event-processing loop unless you really must. Your behaviors code will be distributed on many different types of machines and browsers. Each browser writer knows best how to optimize the event-handling mechanism to mesh with its internal architecture. With windowing systems, dealing with the event loop is a must if you are to respond to user input, but in virtual reality, you don't have control over the whole system. The processEvents() method applies only to the individual script, not as a common method across all scripts. So although you think you are optimizing the event handling, you are doing so only for one script. In a reasonably sized world, another few hundred scripts may also be running, so the optimization of an individual script isn't generally worth the effort.

Changing the Scene

Add to the scene graph only what is necessary. If you can modify existing primitives, do so instead of adding new ones. Every primitive you add to a scene requires the renderer to convert the scene to its internal representation and then reoptimize the scene graph to take the new objects into account. When it modifies existing primitives, the browser is not required to resort the scene graph structure, saving computation time. A cloudy sky is better simulated using a multiframed texture map image format (such as MJPEG or PNG) on the background node than using lots of primitives that are constantly modified or dynamically added.

If your scene requires objects to be added and removed on the fly, and many of these objects are the same, don't just delete them from the scene graph. It is better to remove them from a node and keep an instance pointer to them so that they can be reinserted at a later time. At the expense of a little extra memory, you save time. If you don't take the time now, you may later have to access the objects from a network or construct them from the ground up from a string representation.

Another trick is to create VRML objects but not add them to the visual scene graph. VRML scripting enables objects to be created but not added to the scene graph. Any object not added isn't drawn. For node types such as sensors, interpolators, and scripts, there is no need for these objects to be added because they are never drawn. Doing so causes extra events to be generated, resulting in a slower system. Normal Java garbage collection rules apply when these nodes are no longer referenced. VRML, however, adds one little extra: Adding a ROUTE to any object is the same as keeping a reference to the object. If a script creates a node, adds one or more ROUTE statements, and then exits, the node stays allocated and it functions properly. You can use this approach to set up a VRML node that does not use events for its visualization.

There are dangers in this approach. Once you lose the node instance pointer, you have no way to delete the node. You need this pointer if you are to delete the ROUTE. Deleting ROUTE statements to the object is the only way to remove these floating nodes. Therefore, you should always keep the node instance pointers for all floating nodes you create so that you can delete the ROUTE statements to them when they're no longer needed. You must be particularly careful when you delete a section of the scene graph that has the only routed eventIn to a floating node that also contains an eventOut to a section of an undeleted section. This situation creates the VRML equivalent of memory leaks. The only way to remove this node is to replace the whole scene or to remove the part of the scene referenced by the eventOut.

Dynamic Worlds--Creating VRML on the Fly

This section develops a framework for creating worlds on the fly. Dynamically changing worlds add tremendous potential to your VRML scenes. You can develop cyberspace protocol-based, seamless worlds in which a visitor can take an object from one world and use or leave it in another world. You can provide a VRML toolkit with which a visitor creates new VRML objects from existing ones and saves them as separate VRML files. But for starters, you may just want to add a few new objects or eliminate objects as a visitor interacts with your scene. The next sections provide an example of a dynamic world for both CommunityPlace and CosmoPlayer. The example primarily familiarizes you with the createVrmlFromString(String vrmlSyntax), createVrmlFromURL(String[] url, Node node, String event), and loadURL(String[] url, String[] parameter) methods of the Browser class. Figure 45.2 shows the sample VRML scene before it dynamically changes.

Figure 45.2.
A simple VRML scene based on the VRML logo.

Creating the VRML Source File

If you are to add new objects to a VRML scene, a placeholder node must already exist in the .wrl file. This placeholder need not be anything more sophisticated than the following empty Transform node:

DEF root_node Transform { }

In fact, this statement can be the entire starting world if the world is to be built using Java AWT controls. More typically, however, other objects are already in the world when it is loaded and the Transform node becomes just another potential node to which you can add new objects. The Transform node has two eventIn fields--addChildren and removeChildren--that can be used to add or delete multiple children from within the node.

In the following code example, three objects exist in the VRML scene when the world is first loaded. Each object enables an event that dynamically changes the world. Using the three primitive shapes that form the VRML logo, the red cube enables the createVrmlFromURL() method, the green sphere enables the createVrmlFromString() method, and the blue cone takes the user to another VRML world by using the loadURL() method. The cube, sphere, and cone are each children of Transform nodes to make sure that they are located in different parts of the world (all objects are located at the Transform node's origin by default). The code that creates the cube Transform node follows:

DEF cube Transform {
    children [
        DEF cube_sensor TouchSensor{}
        Shape {
           appearance Appearance {
               material Material {
                   diffuseColor 1 0 0
               }
           }
           geometry Box { size  1 1 1}
        }
        # note: script node goes here for CommunityPlace
    ]
    bboxSize     1 1 1
    translation -2 0 0

}

Notice that the TouchSensor itself has been defined (with DEF) as has the whole Transform node. The TouchSensor is the object that triggers events. Without a defined sensor, the cube has no way to interact with the rest of the scene. Any mouse click (or touch, if the user has a dataglove) on the cube does nothing. As you soon see, the other two nodes are similar in definition. The Shape node contains the appearance of the cube (in this case, a bright red color) as well as the geometry of the cube (which is 1 unit wide, 1 unit tall, and 1 unit deep). The bboxSize field defines a bounding box that helps the VRML viewer render the cube efficiently. The translation field places the cube two units to the right from the (0,0,0) origin of the VRML scene.

Keeping this VRML primer in mind, you are ready to follow the example for both the CommunityPlace and CosmoPlayer VRML viewers.

The CommunityPlace Version of the Dynamic World Example

The following sections break down the process for creating the dynamically changing world for the CommunityPlace browser.


NOTE: For demonstration purposes, the separate scripts are described and grouped with their appropriate objects. It makes no difference if you have lots of small scripts or one large one. If you are a VR scene creator, it is probably better to have one large script to keep track of the scene graph if you want to save a VRML file with the changes. If you are creating a virtual factory of reusable, plug-and-play component VRML objects, you may prefer to use many small scripts, perhaps with some "centralized" script to act as the system controller.

Defining the Script Nodes

Once the basic VRML file is defined, you must add behaviors. The VRML file stands on its own at this point. You can click objects, but nothing happens. Because each object has its own behavior, the requirement for each script is different. Each script requires one eventIn, which is the notification from its TouchSensor().

The example presented does not have any real-time constraints, so the mustEvaluate field for each object is left with the default setting of FALSE. For the cone object, no outputs are sent directly to nodes, so the directOutputs field is left at FALSE. For the sphere object, outputs are sent directly to the Group node, so the directOutputs field is set to TRUE. The directOutputs field for the cube object must also be set to TRUE for reasons explained in the next section.

In addition to eventIn, the box_script example also needs an eventOut to send the new object to the Group node that is acting as the scene root. Good behavior is desirable if the user clicks the cube more than once, so an extra internal variable is added, keeping the position of the last object that was added. Each new object added is translated two units along the z axis from the previous one. A field is also needed to store the URL of the sample file that will be loaded. The box_script definition follows:

DEF box_script Script {
    url            "box_script.class"
    directOutputs  TRUE
    eventIn        SFBool    isClicked
    eventIn        MFNode    newNodes
    eventOut       MFNode    childlist
    field          SFInt32   zposition   0
    field          SFNode    thisScript  USE box_script
    field          MFNode    newUrl      []

}

Notice that there is an extra eventIn. Because some processing must be done on the node returned from the createVrmlFromURL() method, you must provide an eventIn for the argument. If you do not have to process the returned nodes, use the root_node.add_children() method instead.

The other interesting point to note is that the script declaration includes a field that is a reference to itself. At the time this chapter was written, the draft specifications for VRML 2 did not specify how a script was to refer to itself when calling its own eventIns. To play it safe, the method in the preceding declaration is guaranteed to work. However, it should be possible for the script to specify the this keyword as the node reference when referring to itself. Check the most current version of the specification, available at http://vag.vrml.org/, for more information.

To show the use of direct outputs, the sphere uses the postEventIn() method to send the new child directly to root_node. To do this, a copy of the name that was defined for the Group is taken; when this copy is resolved in Java, it essentially becomes an instance pointer to the node. Using direct writing to nodes means that you no longer require the eventOut from the cube's script, but you do keep the other fields:

DEF sphere_script Script {
    url            "sphere_script.class"
    directOutputs  TRUE
    eventIn        SFBool    isClicked
    field          SFNode    root         USE root_node
    field          SFInt32   zposition    0

}

The script for the cone is very simplistic. When the user clicks the cone, all it does is fetch some named URL and set the URL as the new scene graph: a simple cylinder.

DEF cone_script Script {
    url        "cone_script.class"
    eventIn     SFBool      isClicked
    field       MFString    target_url ["cylinder.wrl"]

}

Completing the VRML Description

Now that you have defined the scripts, you must wire them together. A number of routes are added between the sensors and scripts, as shown in Listing 45.8.

Listing 45.8. The main world VRML description for CommunityPlace.

#VRML V2.0 utf8
#filename: s_dynamic.wrl
# first the pseudo root
DEF root_node Transform { bboxSize    1000 1000 1000}
# The cube
Transform {
    children [
        DEF cube_sensor TouchSensor{}
        Shape {
           appearance Appearance {
               material Material {
                   diffuseColor 1 0 0
               }
           }
           geometry Box { size  1 1 1}
        }
        DEF box_script Script {
            url           "box_script.class"
            directOutputs TRUE
            eventIn       SFBool    isClicked
            eventIn       MFNode    newNodes
            eventOut      MFNode    childList
            field         SFInt32   zPosition    0
            field         SFNode    thisScript   USE box_script;
            field         MFString  newUrl ["cylinder.wrl"]
        }
    ]
    bboxSize     1 1 1
    translation  2 0 0
}
ROUTE cube_sensor.isActive TO cube_script.isClicked
ROUTE cube_script.childlist TO root_node.add_children
# The sphere
Transform {
children [
       DEF sphere_sensor TouchSensor {}
       Shape {
           appearance Appearance {
               material Material {
                   diffuseColor 0 1 0
               }
           }
           geometry Sphere { radius    0.5 }
        }
        DEF sphere_script Script {
            url            "sphere_script.class"
            directOutputs  TRUE
            eventIn        SFBool    isClicked
            field          SFNode    root        USE root_node
            field          SFInt32   zPosition   0
        }
    ]
    # no translation needed as it the origin already
    bboxSize    1 1 1
}
ROUTE sphere_sensor.isActive TO sphere_script.isClicked
# The cone
Transform {
    children [
        DEF cone_sensor TouchSensor {}
        Shape {
           appearance Appearance {
               material Material {
                   diffuseColor 0 0 1
               }
           }
           geometry Cone {
              bottomRadius  .5
              height        1
           }
       }
       DEF cone_script Script {
            url        "cone_script.class"
            eventIn    SFBool     isClicked
       }
    ]
    bboxSize      1 1 1
    translation  -2 0 0
}
ROUTE cone_sensor.isActive TO cone_script.isClicked

# end of file 

The box sensor adds objects to the scene graph from an external file. This external file, shown in Listing 45.9, contains a Transform node with a single Cylinder as a child. Because the API does not permit you to create node types and you have to place the newly created box at a point other than the origin, you must use a Transform node. Although you can just load a box from the external scene and then create a Transform node with the createVrmlFromString() method, this approach requires more code and slows execution speed. Remember that behavior writing is about getting things done as quickly as possible; the more you can move to external static file descriptions, the better.

Listing 45.9. The external VRML world file.

#VRML V2.0 utf8
#filename: cylinder.wrl
Transform {
  children
     Shape {
       appearance Appearance {
          material Material {
            diffuseColor 1 1 .85
          }
       }
       geometry Cylinder {}
     }
  translation 0 4 0
}

# end of file

Organizing the Java Behaviors

Probably the most time-consuming task for someone writing a VRML scene with behaviors is deciding how to organize the various parts in relation to the scene graph structure. In a simple example like the one in Listing 45.8, there are two ways to arrange the scripts. Imagine what can happen in a moderately complex file of two or three thousand objects!

All the scripts in this example are simple. When the node is received back in newNodes eventIn, the node must be translated to the new position. Ideally, you would like to do this directly by setting the translation field, but you are not able to do so because the translation field is encapsulated within the received node. The only way to translate the node to the new position is to post an event to the node, naming that field as the destination (which is the reason you set directOutputs to TRUE). After this is done, you can then call the add_children() method. Because all the scripts are short, the processEvents() method is not used because the risk to browser execution interruption is minimal. Shorter scripts are less likely to significantly impair the browser's usual processing. Listing 45.10 shows the complete source code for the cube script; this code can also be found on the CD-ROM that accompanies this book.

Listing 45.10. Java source for the cube script.

//filename: box_script.java
import vrml.field.*;
import vrml.node.*;
import vrml.*;
class box_script extends Script {
    private SFInt32  zPosition  = (SFInt32)getField("zPosition");
    private SFNode   thisScript = (SFNode)getField("thisScript");
    private MFString newUrl     = (MFString)getField("newUrl");
    // declare the eventOut field
    private MFNode   childList  = (MFNode)getEventOut("childList");
    // now declare the eventIn methods
    public void isClicked(ConstSFBool clicked, SFTime ts)
    {
        // check to see if picking up or letting go
        if(clicked.getValue() == false)
// Note: as of the writing of this book, Sony's CommunityPlace 
Java API had yet to implement
//       the createVrmlFromURL and postEventIn methods
            Browser.createVrmlFromUrl(newUrl.getValue(),
                                      thisScript, "newNodes");
    }
    public void newNodes(ConstMFNode nodelist, SFTime ts)
    {
        Node[]   nodes = (Node[])nodelist.getValue();
        float[] translation={0.0f,0.0f,0.0f};
        // Set up the translation
        zPosition.setValue(zPosition.getValue() + 2);
        translation[0] = zPosition.getValue();
        translation[1] = 0;
        translation[2] = 0;
        // There should only be one node with a transform at the
        // top. No error checking.
        nodes[0].postEventIn("translation", (Field)translation);
        // now send the processed node list to the eventOut
        childList.setValue(nodes);
    }

}

Listing 45.11 (the code can also be found on the CD-ROM) shows the sphere_script class, which is similar to the cube_script class, except that you have to construct the text-string equivalent of the cylinder.wrl file. This is a straightforward string buffer problem. All you have to do is make sure that the Transform node of the newly added object has an appropriate value for the translation field to avoid a collision with the existing world objects.

Listing 45.11. Java source for the sphere script.

//filename: sphere_script.java
import vrml.field.*;
import vrml.node.*;
import vrml.*;
class sphere_script extends Script {
    private SFInt32  zPosition = (SFInt32)getField("zPosition");
    private SFNode   root      = (SFNode)getField("root");
    // now declare the eventIn methods
    public void isClicked(ConstSFBool clicked, SFTime ts)
    {
        StringBuffer vrml_string = new StringBuffer();
        MFNode       nodes=null;
        // set the new position
        zPosition.setValue(zPosition.getValue() + 2);
        // check to see if picking up or letting go
        if(clicked.getValue() == false)
        {
            vrml_string.append("Transform { bboxSize 1 1 1 ");
            vrml_string.append("translation ");
            vrml_string.append(zPosition.getValue());
            vrml_string.append(" 0 0 ");
            vrml_string.append("children [ ");
            vrml_string.append("sphere { radius 0.5} ] }");
            nodes.setValue(
                     Browser.createVrmlFromUrl(vrml_string));
// Note: as of the writing of this book, Sony's CommunityPlace 
Java API had yet to implement
//       the createVrmlFromURL and postEventIn methods
            root.postEventIn("add_children", (Field)nodes);
        }
    }

}

The cone_script class is the easiest of the lot. As soon as it receives a confirmation of a touch, it starts to load another world specified by the URL. Listing 45.12 reveals the source code; the code is also located on the CD-ROM that accompanies this book.

Listing 45.12. Java Source for the cone script.

//filename: cone_script.java
import vrml.field.*;
import vrml.node.*;
import vrml.*;
class cone_script extends Script {
    SFBool   isClicked = (SFBool)getField("isClicked");
    // The eventIn method
    public void isClicked(ConstSFBool clicked, SFTime ts)
    {
        if(clicked.getValue() == false) {
           String s[] = new String[1] ;
           s[0] = "cylinder.wrl";
           String t[] = new String[1] ;
           t[0] = "target=info_frame";
           getBrowser().loadURL( s,t );
        }
    }

}

By compiling the preceding Java code samples and placing these and the two VRML source files in your Web directory, you can serve this basic dynamic world to the rest of the world. The rest of the world will get the same behavior as you do--regardless of what system individual users are running.

The CosmoPlayer Version of the Dynamic World Example

The following sections break down the process for creating the dynamically changing world for the CosmoPlayer browser.

The Complete VRML Description

As Listing 45.13 shows, the VRML 2 file for CosmoPlayer contains no Script nodes or ROUTE statements. Instead, all scripting is done in the Java class, which is tied to the .wrl file in the HTML document, Dynamic.htm. This code is also located on the CD-ROM that accompanies this book.

Listing 45.13. The main world VRML description for CosmoPlayer.

#VRML V2.0 utf8
#filename: sgi_dynamic.wrl
DEF root_node Transform { },
DEF cube Transform {
    children [
        DEF cube_sensor TouchSensor{}
        Shape {
           appearance Appearance {
               material Material {
                   diffuseColor 1 0 0
               }
           }
           geometry Box { size    1 1 1}
        }
    ]
    bboxSize     1 1 1
    translation -2 0 0
},
DEF sphere Transform {
    children [
        DEF sphere_sensor TouchSensor{}
        Shape {
           appearance Appearance {
               material Material {
                   diffuseColor 0 1 0
               }
           }
           geometry Sphere { radius .5 }
        }
    ]
    bboxSize     1 1 1
    translation  0 0 0
},
DEF cone Transform {
    children [
        DEF cone_sensor TouchSensor{}
        Shape {
           appearance Appearance {
               material Material {
                   diffuseColor 0 0 1
               }
           }
           geometry Cone {
               bottomRadius .5
               height        1
           }
        }
    ]
    bboxSize     1 1 1
    translation  2 0 0

}

The HTML File

The HTML file in Listing 45.14 is almost identical to the one used in the first behavior example in Listing 45.5, earlier in this chapter. We only have to change the document title to Dynamic World Example, the SRC tag value to Dynamic.wrl, and the CODE tag value to Dynamic.class. With these changes, the HTML document will load the VRML file in a 400x400 pixel area within the browser window. The Java class is associated with the VRML scene and even provides an area for any AWT controls instantiated in the Java source code.

Listing 45.14. The HTML document.

<HTML>
<HEAD>
<TITLE>Dynamic World Example</TITLE>
</HEAD>
<BODY>
<CENTER>
<EMBED SRC="sgi_dynamic.wrl" BORDER=0 HEIGHT="400" WIDTH="400">
</CENTER>
<APPLET CODE="sgi_dynamic.class" MAYSCRIPT>
</APPLET>
</BODY>
</HTML>

The Java File

The source code in Listing 45.15 compiles without any warnings when you use the JDK 1.1 from Sun and the beta 3 version of CosmoPlayer 1.0. In the listing, I have commented out the lines that have not yet been implemented by SGI so that you can compare them to the VRML Consortium's suggested Java API at http://vrml.vag.org. As of this writing, neither the createVrmlFromURL() nor the loadURL() method had been implemented for the Windows 95/NT 4.0 CosmoPlayer viewer. Note that SGI has implemented a method declared as replaceWorld(Node node), which works like the loadURL() method when you use a string parameter that refers to the URL of a .wrl file. As usual, you can find the code presented in Listing 45.15 on the CD-ROM that accompanies this book.

Listing 45.15. The Java source code for CosmoPlayer.

//filename: sgi_dynamic.java
import java.awt.*;
import java.applet.*;
import vrml.external.field.*;
import vrml.external.Node;
import vrml.external.Browser;
import vrml.external.exception.*;
import netscape.javascript.JSObject;
public class sgi_dynamic extends Applet  implements EventOutObserver{
  boolean error = false;
  // Browser we're using
  Browser browser;
  // Root of the scene graph (to which we add our nodes)
  Node root=null;
  Node sensor[] = {null,null,null};
  // Shape group hierarchy
  Node[] shape[] = {null,null};
  Node[] scene = null;
  // EventIns of the TouchSensors
  EventOutSFTime touchTime[] = {null,null,null};
  // EventIns of the root node
  EventInMFNode addChildren;
  EventInMFNode removeChildren;
  public void init() {
    JSObject win = JSObject.getWindow(this);
    JSObject doc = (JSObject) win.getMember("document");
    JSObject embeds = (JSObject) doc.getMember("embeds");
    browser = (Browser) embeds.getSlot(0);
    try {
      // Get root node of the scene, and its EventIns
      root = browser.getNode("root_node");
      sensor[0] = browser.getNode("cube_sensor");
      sensor[1] = browser.getNode("sphere_sensor");
      sensor[2] = browser.getNode("cone_sensor");
      for(int x=0;x<3;x++) {
          touchTime[x] = (EventOutSFTime) sensor[x].getEventOut("touchTime");
          touchTime[x].advise(this, new Integer(x));
      }
      addChildren = (EventInMFNode) root.getEventIn("addChildren");
      removeChildren = (EventInMFNode) root.getEventIn("removeChildren");
       // Create shapes to be added on the fly -- 
       can re-assign new strings at any time
       //NOTE: as of beta 3 of CosmoPlayer 1.0 for Windows95/NT 4.0, 
       the createVRMLFromURL method
       //was not implemented:
       //shape[0] = browser.createVrmlFromURL("cylinder.wrl",root,"addChildren");
       shape[0] = browser.createVrmlFromString("Transform {\n" +
                                               " children\n" +
                                               "   Shape {\n" +
                                               "     appearance Appearance {\n" +
                                               "       material Material {\n" +
                                               "         diffuseColor 0 1 1\n" +
                                               "       }\n" +
                                               "     }\n" +
                                               "     geometry Cylinder {}\n" +
                                               "   }\n" +
                                               " translation 0 -4 0\n" +
                                               "}\n");
       shape[1] = browser.createVrmlFromString("Transform {\n" +
                                               " children\n" +
                                               "   Shape {\n" +
                                               "     appearance Appearance {\n" +
                                               "       material Material {\n" +
                                               "         diffuseColor 1 1 .85\n" +
                                               "       }\n" +
                                               "     }\n" +
                                               "     geometry Cylinder {}\n" +
                                               "   }\n" +
                                               " translation 0 4 0\n" +
                                               "}\n");
       Node[] scene = browser.createVrmlFromString("Transform {\n" +
                                               " children\n" +
                                               "   Shape {\n" +
                                               "     appearance Appearance {\n" +
                                               "       material Material {\n" +
                                               "         diffuseColor 1 1 .85\n" +
                                               "       }\n" +
                                               "     }\n" +
                                               "     geometry Sphere {radius 2}\n" +
                                               "   }\n" +
                                               " translation 0 0 0\n" +
                                               "}\n");
    }
    catch (InvalidNodeException e) {
      showStatus("PROBLEMS!: " + e + "\n");
      error = true;
    }
    catch (InvalidEventInException e) {
      showStatus("PROBLEMS!: " + e + "\n");
      error = true;
    }
    catch (InvalidVrmlException e) {
      showStatus("PROBLEMS!: " + e + "\n");
      error = true;
    }

    if (error == false)
      showStatus("Ok...\n");
  }
  public void callback(EventOut who, double when, Object which) {
    Integer whichNum = (Integer) which;
    if (whichNum.intValue() == 0) {
        addChildren.setValue(shape[0]);
    }
    else if (whichNum.intValue() == 1) {
        addChildren.setValue(shape[1]);
    }
    else if (whichNum.intValue() == 2) {
       //NOTE: as of beta3 of CosmoPlayer 1.0 for Windows95, the loadURL method
       //was not implemented:
       //loadURL("cylinder.wrl","");
       browser.replaceWorld(scene);
    }
  }

}

The Java source code contains a object and event declaration and an initialization section that connects the Java variables to the VRML scene graph nodes and sets up the callback. Then it defines three nodes: shape[0] is a cyan cylinder, shape[1] is a yellow cylinder, and scene is another yellow cylinder centered at the origin. The callback listens for any events generated by the user. When the cube's sensor is activated, shape[0] is added to the VRML scene graph with the addChildren() method. When the sphere's sensor is activated, shape[1] is added to the VRML scene graph with the addChildren() method. When the cone's sensor is activated, the current VRML scene graph is replaced with the scene node through the replaceWorld() method of the browser object. Although not used in this example, the removeChildren() method is called in exactly the same way as the addChildren() method when you want to remove a single node from the VRML scene graph.

Creating Reusable Behaviors

It would be problematic if you had to rewrite this code every time you wanted to use it in another file. Although you could reuse the Java bytecodes, this means that you would need identical copies of the script declaration every time you wanted to use it. Redundancy is not a particularly nice practice from the software engineering point of view, either. Eventually, you will be caught by the cut-and-paste error of having extra pieces of ROUTE statements (and extra fields) floating around that could accidentally be connected to nodes in the new scene, resulting in difficult-to-trace bugs.

VRML 2 provides a mechanism similar to the C/C++ #include directive and typedef statements all rolled into one--the PROTO and EXTERNPROTO statement pair. The PROTO statement acts like a typedef: you use PROTO with a node and its definition and then you can use that name as though it were an ordinary node within the context of that file.

If you want to access that prototyped node outside of that file, you can use the EXTERNPROTO statement to include it in the new file and then use it as though it were an ordinary node.

Although this approach is useful for creating libraries of static parts, where it really comes into its own is in creating canned behaviors. A programmer can create a completely self-contained behavior and, in the best object-oriented tradition, provide interfaces to only the behaviors he or she wants. The syntax of the PROTO and EXTERNPROTO statements follow:

PROTO prototypename [ # any collection of
    eventIn       eventTypeName eventName
    eventOut      eventTypeName eventName
    exposedField  fieldTypeName fieldName initialValue
    field         fieldTypeName fieldName initialValue
] {
    # scene graph structure. Any combination of
    # nodes, prototypes, and ROUTEs
}
EXTERNPROTO prototypename [ # any collection of
    eventIn       eventTypeName eventName
    eventOut      eventTypeName eventName
    exposedField  fieldTypeName fieldName
    field         fieldTypeName fieldName
]

"URL" or [ "URN1" "URL2"]

You can then add a behavior to a VRML file by using just the prototypename in the file. For example, if you have a behavior that simulates a taxi, you may want to have many taxis in a number of different worlds representing different countries. The cabs are identical except for their color. Note again the ability to specify multiple URLs for the behavior. If the browser cannot retrieve the first URL, it tries the second until it gets a cab.

A taxi can have many attributes (such as speed and direction) that the user of the cab need not really discriminate. To incorporate a virtual taxi into your world, all you really care about is a few things such as being able to signal a cab, get in, tell it where to go, pay the fare, and then get out when it has reached its destination. From the world authors' point of view, how the taxi finds its virtual destination is unimportant. A declaration of the taxi prototype file might look like the following:

#VRML V2.0 utf8
# Taxi prototype file taxi.wrl
PROTO taxicab [
    exposedField SFBool    isAvailable  TRUE
    eventIn      SFBool    inCab
    eventIn      SFString  destination
    eventIn      SFFloat   payFare
    eventOut     SFFloat   fareCost
    eventOut     SFInt32   speed
    eventOut     SFVec3f   direction
    field        SFColor   color       1 0 0
    # rest of externally available variables
] {
    DEF root_group Transform {
        # Taxi shape description here
    }
    DEF taxi_script Script {
        url    ["taxi.class"]
        # rest of event and field declarations
    }
    # ROUTE statements to connect it altogether

}

To include the taxi in your world, the file would look something like the following:

#VRML V2.0 utf8
#
# myworld.wrl
EXTERNPROTO taxi [
    exposedField SFBool    isAvailable
    eventIn      SFBool    inCab
    eventIn      SFString  destination
    eventIn      SFFloat   payFare
    eventOut     SFFloat   fareCost
    eventOut     SFInt32   speed
    eventOut     SFVec3f   direction
    field        SFColor   color
    # rest of externally available variables
]
[ " http://myworld.com/taxi.wrl", "http://yourworld.com/taxi.wrl"]
# some scene graph
#....
Transform {
    children [
        # other VRML nodes. Then we use the taxi
        DEF my_taxi taxi {
            color  0 1. 0
        }
    ]

}

Here is a case in which you are likely to use the postEventIn() method to call a cab. Somewhere in the scene graph, you have a control that your avatar uses to query a nearby cab for its isAvailable field. (An avatar is the virtual body used to represent you in the virtual world.) If the isAvailable field is TRUE, the avatar sends the event to flag the cab. Apart from the required mechanics to signal the cab with the various instructions, the world creator does not care how the cab is implemented. By using the EXTERNPROTO call, the world's creator and users can always be sure of getting the latest version of the taxi implementation and that the taxi will exhibit uniform behaviors regardless of which world the users are in.

CommunityPlace's approach to the VRML/Java API is set up to take advantage of using Java within prototyped intranode behaviors. CosmoPlayer, on the other hand, leaves intranode behavior scripting to VRMLscript. You can take advantage of Java class reusability directly within the CosmoPlayer VRML/Java API. For example, you can create a bounce class in Java that many different types of VRML objects could inherit in the Java source code itself. The bounce class would define how to deform an object as it hits an obstacle and changes direction; the class would be written only once, yet take advantage of full reusability.

The Future: VRML, Java, and AI

The information in this chapter has so far relied on static, predefined behaviors available either within the original VRML file or retrievable from somewhere on the Internet.

One exciting goal of VR worlds is to be able to create autonomous agents that have some degree of artificial intelligence. Back in the early days of programming, self-modifying code was common, but it faded away as more resources and higher-level programming languages removed the need. A VR world can take advantage of self-modifying code.

Stephenson's Librarian from Snow Crash is just one example of how an independent agent can act in a VR world. His model was very simple--a glorified version of today's 2D HTML-based search engine that, when requested, searched the U.S. Library of Congress for information related to a desired topic (the Librarian also has speech recognition and synthesis capabilities). The next generation of intelligent agents will include learning behavior as well.

The VRML API enables you to go the next step further--a virtual assistant that can modify its own behavior to suit your preferences. This is not just a case of loading in some canned behaviors. By combining VRMLscript and Java behaviors, you can create customized behaviors on the fly by concatenating the behavior strings and script nodes, calling the createVrmlFromString() method, and adding it to the scene graph in the appropriate place. Although doing so is probably not feasible with current Pentium-class machines, the next generation of processors will probably make it so.

Summary

With the tools presented in this chapter, you should be able to create whatever you require of cyberspace. There is only so much you can do with a 2D screen in terms of new information-presentation techniques. The third dimension of VRML enables you to create experiences that are far beyond what you expect of today's Web pages. 3D representation of data and VR behaviors programming is still very much in its infancy--so much so that, at the time of this writing, only Sony's CommunityPlace, SGI's CosmoPlayer, and Netscape's Live3D viewers were available for testing, and even then, many parts of these viewers were not implemented. In fact, the Live3D API works only with VRML 1 and is quite different from what is suggested by the VRML 2 standard.

If you are serious about creating behaviors, you must learn VRML thoroughly. Many little problems can catch the unwary, particularly the peculiarities of VRML syntax when it comes to ordering objects within the scene graph. An object placed at the wrong level severely restricts its actions. A book on VRML is a must for this work. For a good reference on VRML 2, check out Teach Yourself VRML 2 in 21 Days, by Chris Marrin and Bruce Campbell (published by Sams.net Publishing).

Whether you are creating reusable behavior libraries, an intelligent postman that brings the mail to you wherever you are, or simply a functional Java machine for your virtual office, the excitement of behavior programming is catching.

TOCBACKFORWARDHOME


©Copyright, Macmillan Computer Publishing. All rights reserved.