The Annotated VRML 97 Reference

1 Intro     Concepts     3 Nodes     4 Fields/Events    Conformance
A Grammar     B Java     C JavaScript     D Examples     E Related Info    References
Quick Java         Quick JavaScript         Quick Nodes   
 

  About the Book
  
Help
  Copyright © 1997-99
  Purchase the book from Amazon.com

 

 

Chapter 2
Key Concepts

2.1 Intro

2.1.1 Overview
2.1.2 TOC
2.1.3 Conventions

2.2 Overview
2.2.1 File Structure
2.2.2 Header
2.2.3 Scene graph
2.2.4 Prototypes
2.2.5 Routing
2.2.6 Generating files
2.2.7 Presentation
     Interaction
2.2.8 Profiles

2.3 UTF-8 syntax
2.3.1 Clear text
2.3.2 Statements
2.3.3 Node
2.3.4 Field
2.3.5 PROTO
2.3.6 IS
2.3.7 EXTERNPROTO
2.3.8 USE
2.3.9 ROUTE

2.4 Scene graph
2.4.1 Root nodes
2.4.2 Hierarchy
2.4.3 Descendants
       & ancestors
2.4.4 Hierarchy
2.4.5 Units coord sys

2.5 VRML & WWW
2.5.1 MIME type
2.5.2 URLs
2.5.3 Relative URLs
2.5.4 data:
2.5.5 Scripting protocols
2.5.6 URNs

2.6 Nodes
2.6.1 Intro
2.6.2 DEF/USE
2.6.3 Geometry
2.6.4 Bboxes
2.6.5 Grouping & children
2.6.6 Lights
2.6.7 Sensors
2.6.8 Interpolators
2.6.9 Time nodes
2.6.10 Bindable children
2.6.11 Textures

2.7 Field, eventIn,
     eventOut

2.8 PROTO
2.8.1 Declaration
2.8.2 Definition
2.8.3 Scoping

2.9 EXTERNPROTO
2.9.1  Interface
2.9.2  URL
2.9.3 Extensions

2.10 Events
2.10.1 Intro
2.10.2 Routes
2.10.3 Execution
2.10.4 Loops
2.10.5 Fan-in & fan-out

2.11 Time
2.11.1 Intro
2.11.2 Origin
2.11.3 Discrete/cont

2.12 Scripting
2.12.1 Intro
2.12.2 Execution
2.12.3 Initialize/shutdown
2.12.4 eventsProcessed
2.12.5 Direct outputs
2.12.6 Asynchronous
2.12.7 Languages
2.12.8 EventIns
2.12.9 fields events
2.12.10 Browser interface

2.13 Navigation
2.13.1 Intro
2.13.2 Navigation
2.13.3 Viewing
2.13.4 Collisions

2.14 Lighting
2.14.1 Intro
2.14.2 'off'
2.14.3 'on'
2.14.4 Equations
2.14.5 References

+2.12 Scripting

2.12.1 Introduction

Authors often require that VRML worlds change dynamically in response to user inputs, external events, and the current state of the world. The proposition "if the vault is currently closed AND the correct combination is entered, open the vault" illustrates the type of problem which may need addressing. These kinds of decisions are expressed as Script nodes (see "3.40 Script") that receive events from other nodes, process them, and send events to other nodes. A Script node can also keep track of information between subsequent executions (i.e., retaining internal state over time).

This section describes the general mechanisms and semantics of all scripting language access protocols. Note that no particular scripting language is required by the VRML standard. Details for two scripting languages are in Appendix B, "Java Scripting Reference" and Appendix C, "JavaScript Scripting Reference", respectively. If either of these scripting languages are implemented, the Script node implementation shall conform with the definition described in the corresponding appendix.

Event processing is performed by a program or script contained in (or referenced by) the Script node's url field. This program or script may be written in any programming language that the browser supports.

design note

The lack of a required scripting language for VRML is a problem for content creators who want their content to run on all VRML browsers. Unfortunately, the VRML community was unable to reach consensus on a language to require. The leading candidates were Java, JavaScript (or possibly a subset of JavaScript), and both Java and JavaScript. The scripting language situation isn't completely chaotic, however. Appendix C, Java Scripting Reference, and Appendix D, JavaScript Scripting Reference, define the language integration specifications if a browser chooses to implement one of these two.

2.12.2 Script execution

A Script node is activated when it receives an event. The browser shall then execute the program in the Script node's url field (passing the program to an external interpreter if necessary). The program can perform a wide variety of actions including sending out events (and thereby changing the scene), performing calculations, and communicating with servers elsewhere on the Internet. A detailed description of the ordering of event processing is contained in "2.10 Event processing."

Script nodes may also be executed after they are created (see "2.12.3 Initialize() and shutdown()"). Some scripting languages may allow the creation of separate processes from scripts, resulting in continuous execution (see "2.12.6 Asynchronous scripts").

Script nodes receive events in timestamp order. Any events generated as a result of processing an event are given timestamps corresponding to the event that generated them. Conceptually, it takes no time for a Script node to receive and process an event, even though in practice it does take some amount of time to execute a Script.

design note

Scripts are also activated when the file is loaded (see Section 2.12.3, Initialize and Shutdown). Some scripting languages allow the creation of asynchronous threads of execution, allowing scripts to be continuously active (see Section 2.7.6, Asynchronous Scripts). But it is expected that most scripts will act as "glue" logic along routes and will be executed only when they receive events.

design note

Creating Script nodes that take a long time to process events (e.g., half a second) is a bad idea, since one slow Script node might slow down the entire VRML browser. At the very least, slow scripts will cause browsers problems as they try to deal with events with out-of-date time stamps, since even if the script takes three seconds to process an event, the events it generates will have time stamps equal to the original event.

If you want a Script node to perform some lengthy calculation, it is best to use a language like Java that allows the creation of separate threads, and perform the lengthy calculation in a separate thread. The user will then be able to continue interacting with the world while the calculation is proceeding.

 

2.12.3 Initialize() and shutdown()

The scripting language binding may define an initialize() method. This method shall be invoked before the browser presents the world to the user and before any events are processed by any nodes in the same VRML file as the Script node containing this script. Events generated by the initialize() method shall have timestamps less than any other events generated by the Script node. This allows script initialization tasks to be performed prior to the user interacting with the world.

design note

Note that the specification is fuzzy about exactly when initialize() is called. The only requirement is that it be called before the Script generates any events (which can happen only after either an event has been received or initialize() is called). However, implementations should call the initialize() method as soon as possible after the Script node is created. For example, you might write a script that has an initialize() method that starts a thread that establishes and listens to a connection to a server somewhere on the network. Such a Script might not generate any events until it receives a message from the server, so an implementation that never called its initialize() method in the first place would, technically, be compliant with the requirements of the VRML specification.

Requiring that the initialize() method be called "as soon as possible" may not be desirable, either. Implementations may have prefetching strategies that call for loading part of the world into memory but not initializing it until the user performs some action (e.g., walks through the teleportation device). In this case, browser implementors are trusted to make reasonable decisions.

tip

It is sometimes useful to create Scripts that have only an initialize() method. This technique can be used to ensure that exposedFields along an event cascade route start out with reasonable values. The Script simply generates an initial event (with a value that might be specified as a field of the Script, for example) in its initialize() method. The same technique is also useful for generating geometry or textures at load time; transmitting code that generates nodes rather than specifying the nodes explicitly, can save lots of bandwidth.

design note

If a Script has no eventIns and doesn't start up an asynchronous thread, then it can safely be deleted as soon as its initialize() method has been called. There is no way for such a Script to ever generate events after the initialize() method is finished.

Likewise, the scripting language binding may define a shutdown() method. This method shall be invoked when the corresponding Script node is deleted or the world containing the Script node is unloaded or replaced by another world. This method may be used as a clean-up operation, such as informing external mechanisms to remove temporary files. No other methods of the script may be invoked after the shutdown() method has completed, though the shutdown() method may invoke methods or send events while shutting down. Events generated by the shutdown() method that are routed to nodes that are being deleted by the same action that caused the shutdown() method to execute will not be delivered. The deletion of the Script node containing the shutdown() method is not complete until the execution of its shutdown() method is complete.

design note

Again, the specification doesn't precisely specify when shutdown() is called. Unless you are writing a script that starts separate threads, you probably won't need a shutdown() method.

2.12.4 EventsProcessed()

The scripting language binding may define an eventsProcessed() method that is called after one or more events are received. This method allows Scripts that do not rely on the order of events received to generate fewer events than an equivalent Script that generates events whenever events are received. If it is used in some other time-dependent way, eventsProcessed() may be nondeterministic, since different browser implementations may call eventsProcessed() at different times.

For a single event cascade, a given Script node's eventsProcessed() method shall be called at most once. Events generated from an eventsProcessed() method are given the timestamp of the last event processed.

design note

Sophisticated implementations may determine that they can defer executing certain scripts, resulting in several events being sent to a script at once. The eventsProcessed() routine is an optimization that lets the script creator be more efficient in these cases. For example, if you create a simple Script that receives set_a and set_b events and generates sum_changed events where sum = a + b, it is more efficient to calculate the sum and generate the sum_changed event in an eventsProcessed() routine, after all set_a and set_b events have been received. The end result is the same as generating sum_changed events whenever a set_a or set_b event is received, but fewer events will be generated.

Of course, if it is important that events for all of the changes to sum are generated, eventsProcessed() should not be used. For example, you might create a script that recorded the time and value of each event it receives, which could be used to generate a history of the sum over time. Most of the time, however, only the most current result is of interest.

2.12.5 Scripts with direct outputs

Scripts that have access to other nodes (via SFNode/MFNode fields or eventIns) and that have their directOutput field set to TRUE may directly post eventIns to those nodes. They may also read the last value sent from any of the node's eventOuts.

When setting a value in another node, implementations are free to either immediately set the value or to defer setting the value until the Script is finished. When getting a value from another node, the value returned shall be up-to-date; that is, it shall be the value immediately before the time of the current timestamp (the current timestamp returned is the timestamp of the event that caused the Script node to execute).

Script nodes that are not connected by ROUTE statements may be executed asynchronously. If multiple directOutput Scripts read from and/or write to the same node, the results may be undefined.

design note

The directOutput field is a hint to the browser that the Script may directly read or write other nodes in the scene, instead of just receiving and sending events through its own eventIns and eventOuts. When directOutput is FALSE (the default), several optimizations are possible that cannot be safely performed if the Script might directly modify other nodes in the scene. If we assumed that browsers could examine the Script's code and look at the calls it makes before execution, then this hint wouldn't be necessary. However, it is assumed that scripts are black boxes and browsers may not be able to examine their code. For example, Java byte code may be passed directly to a Java interpreter embedded in the computer's operating system, separate from the VRML browser.

If a Script node, with its directOutput set to FALSE, directly modifies other nodes, results are undefined. Browsers are not required to check for this case because it would slow down scripts that have set the field correctly (slowing down execution of the common case to test for a rare error condition would violate the design principle that VRML should be high performance). Errors due to incorrectly setting the directOutput flag are likely to be hard to find, since they will cause some browsers to make invalid assumptions about what optimizations they can perform and have no effect on other browsers that perform different optimizations.

Scripts that have their directOutput field set to TRUE can only read or write nodes to which they have access. There are four ways for scripts to get access to other nodes:

  • Scripts with SFNode or MFNode fields have access to the nodes in those fields.
  • Scripts with SFNode or MFNode eventIns have access to the nodes in those events.
  • Scripts that create nodes using the createVrmlFromString() routine (see Section 2.7.10, Browser Script Interface) have access to the nodes they create.
  • If a script has access to a node, and that node has SFNode or MFNode eventOuts, then the script has access to all of the nodes in those eventOuts. This means that if a Script node has access to a Group node, for example, it has access to (and may read or write to) any of the Group's children.

    Since browsers know whether or not a script might directly modify other nodes (from the directOutput field), and because browsers know which nodes scripts may access (from their fields, events received, etc.), they can determine which parts of the scene cannot possibly change. And, knowing that, browsers may decide to perform certain optimizations that are only worthwhile if the scene doesn't change. For example, a browser could decide to create texture map "imposters"—images of the object from a particular point of view that can be drawn less expensively than the object itself—for objects that cannot change. It is best to limit the number of nodes to which a script has access so that browsers have maximum opportunity for such optimizations.

    Often the same task can be performed either using a ROUTE or by giving a script direct access to a node and setting its directOutput field to TRUE. In general, it is better to use a ROUTE, since the ROUTE gives the browser more information about what the script is doing and, therefore, gives the browser more potential optimizations.

2.12.6 Asynchronous scripts

Some languages supported by VRML browsers may allow Script nodes to spontaneously generate events, allowing users to create Script nodes that function like new Sensor nodes. In these cases, the Script is generating the initial events that causes the event cascade, and the scripting language and/or the browser shall determine an appropriate timestamp for that initial event. Such events are then sorted into the event stream and processed like any other event, following all of the same rules including those for looping.

tip

Java, for example, allows the creation of separate threads. Those threads can generate eventOuts at any time, essentially allowing the Script containing the Java code to function as a new, user-defined sensor node.

If you want to create scalable worlds, you should be careful when creating asynchronous threads. You can easily swamp any CPU by creating a lot of little scripts that are all constantly busy. Make each script as efficient as possible, and make each thread inactive (blocked and waiting for input from the network, for example) as much of the time as possible.

2.12.7 Script languages

The Script node's url field may specify a URL which refers to a file (e.g., using protocol http:) or incorporates scripting language code directly in-line (e.g., using protocol javabc:). The MIME-type of the returned data defines the language type. Additionally, instructions can be included in-line using either the data: protocol (which allows a MIME-type specification) or a "2.5.5 Scripting Language Protocol" defined for the specific language (from which the language type is inferred).

For example, the following Script node has one eventIn field named start and three different URL values specified in the url field: Java, JavaScript, and inline JavaScript:

    Script {
      eventIn SFBool start
      url [ "http://foo.com/fooBar.class",
        "http://foo.com/fooBar.js",
        "javascript:function start(value, timestamp)
          { ... }"
      ]
    }

In the above example when a start eventIn is received by the Script node, one of the scripts found in the url field is executed. The Java code is the first choice, the JavaScript code is the second choice, and the inline JavaScript code the third choice. A description of order of preference for multiple valued URL fields may be found in "2.5.2 URLs."

design note

An earlier design of the Script node had a languageType SFString field and a script SFString field that either contained or pointed to the script code. Using the same URL paradigm for the script code as is used for other media types that aren't part of VRML (images, movies, sounds) is a much better design and had several unexpected benefits:

  • Scripts can be placed either inline (using the data:, javabc:, or javascript: protocols) or can be placed in separate files.
  • The desire to put script code inline inside the VRML file led to investigation and the discovery of the data: protocol proposal (see Section 2.1.4, Data Protocol). That, in turn, allows any media type (images, sounds, movies, or script code) to be put inline in the VRML file—a feature that was often requested.
  • All of the functionality of the MIME and URL standards can be used with script code (e.g., server-client content-type negotiation or automatic decompression of script code via the standard content-encoding mechanisms).

2.12.8 EventIn handling

Events received by the Script node are passed to the appropriate scripting language method in the script. The method's name depends on the language type used. In some cases, it is identical to the name of the eventIn; in others, it is a general callback method for all eventIns (see the scripting language appendices for details). The method is passed two arguments: the event value and the event timestamp.

design note

Passing the event time stamp along with every event makes it easy for script authors to generate results that are consistent with VRML's ideal execution model. If time stamps were not easily available, then script authors that wished to schedule things relative to events (e.g., start animations or sounds two seconds after receiving an event) would be forced to schedule them relative to the time the script happened to be executed. That wouldn't be a problem for an ideal implementation that processed all events the instant they happened, but it could cause synchronization problems for real implementations, since two scripts triggered by the same event will be executed at slightly different times (ignoring multiprocessor implementations, which are possible and could execute scripts in parallel).

There is no way for a Script node to find out what nodes are sending it events, and no way for it to find out what nodes are receiving the events it generates (unless it is a directOutput Script that is directly sending events to nodes, of course). This was done to restrict the number of nodes to which a script potentially has access, allowing browsers more opportunities for optimization (refer back to Section 2.7.5, Scripts with Direct Outputs). Passing some sort of opaque node identifier with each event was also considered, and would have allowed scripts to do some interesting things with events that were fanned-in from several different nodes. However, that feature probably would not be used often enough to justify the cost of passing an extra parameter with every event.

2.12.9 Accessing fields and events

The fields, eventIns, and eventOuts of a Script node are accessible from scripting language methods. Events can be routed to eventIns of Script nodes and the eventOuts of Script nodes can be routed to eventIns of other nodes. Another Script node with access to this node can access the eventIns and eventOuts just like any other node (see "2.12.5 Scripts with direct outputs").

It is recommended that user-defined field or event names defined in Script nodes follow the naming conventions described in "2.7 Fields, eventIns, and eventOuts semantics."

2.12.9.1 Accessing fields and eventOuts of the script

Fields defined in the Script node are available to the script through a language-specific mechanism (e.g., a variable is automatically defined for each field and event of the Script node). The field values can be read or written and are persistent across method calls. EventOuts defined in the Script node may also be read; the returned value is the last value sent to that eventOut.

2.12.9.2 Accessing eventIns and eventOuts of other nodes

The script can access any eventIn or eventOut of any node to which it has access. The syntax of this mechanism is language dependent. The following example illustrates how a Script node accesses and modifies an exposed field of another node (i.e., sends a set_translation eventIn to the Transform node) using JavaScript:

    DEF SomeNode Transform { }
    Script {
      field   SFNode  tnode USE SomeNode
      eventIn SFVec3f pos
      directOutput TRUE
      url "javascript:
        function pos(value, timestamp) {
          tnode.set_translation = value;
        }"
    }

The language-dependent mechanism for accessing eventIns or eventOuts (or the eventIn or eventOut part of an exposedField) shall support accessing them without their "set_" or "_changed" prefix or suffix, to match the ROUTE statement semantics. When accessing an eventIn named "zzz" and an eventIn of that name is not found, the browser shall try to access the eventIn named "set_zzz". Similarly, if accessing an eventOut named "zzz" and an eventOut of that name is not found, the browser shall try to access the eventIn named "zzz_changed".

design note

If the Script accesses the eventIns or eventOuts of other nodes, then it must have its directOutput field set to TRUE, as previously described in Section 2.7.5, Scripts with Direct Outputs.

EventIns of other nodes are write-only; the only operation a directOutput Script may perform on such eventIns is sending them an event. Note that this is exactly the opposite of the Script node's own eventIns, which are read-only—the Script can just read the value and time stamp for events it receives.

EventOuts of other nodes are read-only; the only operation a directOutput Script may perform on them is reading the last value they generated (see Chapter 4, Fields and Events, for the definition of the initial value of eventOuts before they have generated any events). A Script's own eventOuts, on the other hand, can be both read and written by the script.

Fields of other nodes are completely opaque and private to the node. A Script may read and write its own fields as it pleases, of course. If fields were not private to the nodes that owned them it would be quite difficult to write a robust Script node, since other nodes could possibly change the Script's fields at any time.

ExposedFields of other nodes are just an eventIn, an eventOut, and a field. The eventIn may be written to by a directOutput Script, and the eventOut may be read, giving complete read/write access to the field.

2.12.9.3 Sending eventOuts

Each scripting language provides a mechanism for allowing scripts to send a value through an eventOut defined by the Script node. For example, one scripting language may define an explicit method for sending each eventOut, while another language may use assignment statements to automatically defined eventOut variables to implicitly send the eventOut. Sending multiple values through an eventOut during a single script execution will result in the "last" event being sent, where "last" is determined by the semantics of the scripting language being used.

design note

The specification should be more precise here. To avoid potential problems, you should not write scripts that generate events from the same eventOut that have the same time stamp. And browser implementors should create Script node implementations that send out only the "last" eventOut with a given time stamp, assuming that the scripting language being used has a well-defined execution order (which may not be true if languages that support implicit parallelism are ever used with VRML).

2.12.10 Browser script interface

The browser interface provides a mechanism for scripts contained by Script nodes to get and set browser state (e.g., the URL of the current world). This section describes the semantics of methods that the browser interface supports. An arbitrary syntax is used to define the type of parameters and returned values. The specific appendix for a language contains the actual syntax required. In this abstract syntax, types are given as VRML field types. Mapping of these types into those of the underlying language (as well as any type conversion needed) is described in the appropriate language appendix.

2.12.10.1 SFString getName( ) and SFString getVersion( )

The getName() and getVersion() methods return a string representing the "name" and "version" of the browser currently in use. These values are defined by the browser writer, and identify the browser in some (unspecified) way. They are not guaranteed to be unique or to adhere to any particular format and are for information only. If the information is unavailable these methods return empty strings.

2.12.10.2 SFFloat getCurrentSpeed( )

The getCurrentSpeed() method returns the average navigation speed for the currently bound NavigationInfo node in meters per second, in the coordinate system of the currently bound Viewpoint node. If speed of motion is not meaningful in the current navigation type, or if the speed cannot be determined for some other reason, 0.0 is returned.

2.12.10.3 SFFloat getCurrentFrameRate( )

The getCurrentFrameRate() method returns the current frame rate in frames per second. The way in which frame rate is measured and whether or not it is supported at all is browser dependent. If frame rate measurement is not supported or cannot be determined, 0.0 is returned.

2.12.10.4 SFString getWorldURL( )

The getWorldURL() method returns the URL for the root of the currently loaded world.

2.12.10.5 void replaceWorld( MFNode nodes )

The replaceWorld() method replaces the current world with the world represented by the passed nodes. An invocation of this method will usually not return since the world containing the running script is being replaced. Scripts that may call this method shall have mustEvaluate set to TRUE.

2.12.10.6 void loadURL( MFString url, MFString parameter )

The loadURL() method loads the first recognized URL from the specified url field with the passed parameters. The parameter and url arguments are treated identically to the Anchor node's parameter and url fields (see "3.2 Anchor"). This method returns immediately. However, if the URL is loaded into this browser window (e.g., there is no TARGET parameter to redirect it to another frame), the current world will be terminated and replaced with the data from the specified URL at some time in the future. Scripts that may call this method shall set mustEvaluate to TRUE.

2.12.10.7 void setDescription( SFString description )

The setDescription() method sets the passed string as the current description. This message is displayed in a browser dependent manner. An empty string clears the current description. Scripts that may call this method must have mustEvaluate set to TRUE.

2.12.10.8 MFNode createVrmlFromString( SFString vrmlSyntax )

The createVrmlFromString() method imports a string consisting of a VRML scene description, parses the nodes contained therein, and returns the root nodes of the corresponding VRML scene. The string must be self-contained (i.e., USE statements inside the string may refer only to nodes DEF'ed in the string, and non-built-in node types used by the string must be prototyped using EXTERNPROTO or PROTO statements inside the string).

2.12.10.9 void createVrmlFromURL( MFString url, SFNode node, SFString event )

The createVrmlFromURL() instructs the browser to load a VRML scene description from the given URL or URLs. The VRML file referred to must be self-contained (i.e., USE statements inside the string may refer only to nodes DEF'ed in the string, and non-built-in node types used by the string must be prototyped using EXTERNPROTO or PROTO statements inside the string). After the scene is loaded, event is sent to the passed node returning the root nodes of the corresponding VRML scene. The event parameter contains a string naming an MFNode eventIn on the passed node.

2.12.10.10 void addRoute(...) and void deleteRoute(...)

void addRoute( SFNode fromNode, SFString fromEventOut,
                       SFNode toNode, SFString toEventIn );

void deleteRoute( SFNode fromNode, SFString fromEventOut,
                          SFNode toNode, SFString toEventIn );

These methods respectively add and delete a route between the given event names for the given nodes. Scripts that may call this method must have directOutput set to TRUE.

design note

The composability and scalability design goals put severe restrictions on the Script node browser interface API. For example, a call that returned the root nodes of the world was considered and rejected because it would allow a script unrestricted access to almost all of the nodes in the world, severely limiting a browser's ability to reason about what might and might not be changing. Many features that were initially part of the API were moved into nodes in the scene, because doing so made the design much more composable and consistent.

The functions that are left are very general and quite powerful. Several of the standard nodes in the VRML specification could be implemented via prototypes that used these API calls. For example, the Anchor node could be implemented as a Group, a TouchSensor, and a Script that made loadURL() and setDescription() calls at the appropriate times, and an Inline could be implemented as a Group and a Script that called createVrmlFromURL(url, group, "set_children") in its initialize() method.