| ## 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 `field`s. The `Script` can be defined as a node within the scene graph. These `Script` nodes are not permitted to have `exposedField`s 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 `eventIn`s 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 `eventIn`s, multiple `eventIn`s 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 `eventOut`s generated as a result are sent as events. If the script generates multiple `eventOut`s for a single `eventOut` field, only one event is sent. All `eventOut`s 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 `eventIn`s 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 `eventIn`s. 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.
| ©Copyright, Macmillan Computer Publishing. All rights reserved.