Brought to you by the

Proposal for a VRML 2.0 Informative Annex

External Authoring Interface Reference

Chris Marrin
Silicon Graphics, Inc.

January 21, 1997

For communication between a VRML world and its external environment an interface between the two is needed. This interface is call an External Authoring Interface and it defines the set of functions on the VRML browser that the external environment can perform to affect the VRML world. To understand the relationship between this interface and the rest of the VRML environment please see Anatomy of a VRML Browser.

Note: This proposal deals only with language based interfaces, although a variety of other interfaces such as network based or IPC based are also possible. Furthermore, the only practical example of communication between an external environment and a VRML world shown to date is the interface between a Java applet on an HTML page and an embedded VRML world on that same page. Therefore this proposal deals only with that style of interface.

1 Fundamental Interface Concepts

2 Language Support

3 Interface Description


1 Fundamental Interface Concepts

The interface described here is designed to allow an external program (referred to here as an applet) to access nodes in a VRML scene using the existing VRML event model. In this model an eventOut of a given node can be routed to an evenIn of another node. When the eventOut generates an event, the eventIn is notified and its node processes that event. Additionally, if a script in a Script node has a pointer to a given node it can send events directly to any eventIn of that node and it can read the last value sent from any of its eventOuts.

The External Authoring Interface allows 4 types of access into the VRML scene:

  1. Accessing the functionality of the Browser Script Interface.
  2. Sending events to eventIns of nodes inside the scene.
  3. Reading the last value sent from eventOuts of nodes inside the scene.
  4. Getting notified when events are sent from eventOuts of nodes inside the scene.

The External Authoring Interface is patterned after the Script Authoring Interface (the interface used by scripts inside a Script node). The first 3 access types above are conceptually identical to this interface. For type 1 access, a Browser object is avaliable to the applet to give access to the Browser Script Interface. For type 2 and 3 access, a pointer to a node can be obtained at which point events can be sent to its eventIns and the value of its eventOuts can be read.

There are 2 conceptual differences between the External Authoring Interface and the Script Authoring Interface. The first has to do with obtaining a node pointer through which its eventIns and eventOuts can be accessed. When creating a VRML file a Script node (and therefore the script it contains) can get a pointer to a node with the USE construct (see Instancing). Since the applet has no implicit access to this instancing mechanism an explicit method is provided to get the node pointer from its DEF name string. The 4th access type (eventOut notification) is also conceptually different since creating a ROUTE is not possible between the VRML scene and the applet. The applet must create a method to be called when the eventOut occurs. This method is registered with an eventOut of a given node. When the eventOut generates an event the registered method is called.

1.1 Node Access

Nodes in VRML can be named using the DEF construct. Any node named with the DEF construct can be accessed by the applet and is referred to as an accessible node. Once a pointer is obtained the eventIns and eventOuts of that node can be accessed. Since an exposedField implicitly contains an eventIn and eventOut, these are accessible as well, using the set_ and _changed modifiers.

Once the node pointer is obtained, methods for that node are provided to get pointers to the eventIns and eventOuts of that node. Furthermore, once these pointers are obtained interfaces are provided to access them. There is an interface on the eventIn to send it an event, and on the eventOut to get its current value and to register a function to be called when it generates an event.


2 Language Support

The External Authoring Interface allows applets embedded on an HTML page to communicate with a VRML scene on that same page.

Note: As of the writing of this proposal the WWW consortium was still in the process of finalizing both embedded objects and applets on an HTML page. Therefore any discussion of the details of the communication between the two (e.g. - how Java gets a pointer to the Browser instance) is subject to change. The concepts outlined here were tested using Netscape's LiveConnect interface so examples will be given using the same assumptions as that mechanism. LiveConnect accesses the Browser instance using a proprietary netscape package and the Browser class must be a subclass of Plugin (also proprietary to Netscape), so that interface may change in the future as the HTML standard progresses. Fortunately, once the Browser instance is gotten the remainder of the interface is independent of the implementation.

A Java applet communicates with a VRML world by first obtaining an instance of the Browser class. This class is the Java encapsulation of the VRML world. It contains the entire Browser Script Interface as well as the getNode() method, which returns a Node when given a DEF name string. Only DEF names in the base file are accessible. Names in Inline files and those created with createVRMLFromString() or createVRMLFromURL() are not accessible. Since VRML files can have multiple occurances of the same DEF name only the node with the last occurance of a given name is accessible.

2.1 Accessing Nodes

Once a Node instance is obtained from the getNode() method of the Browser class, it's EventIns and EventOuts can be accessed. The getEventIn() method of the Node class returns an EventIn when passed a string with the desired eventIn name. The getEventOut() method of the Node class returns an EventOut when passed a string with the desired eventOut name. ExposedFields can also be accessed, either by giving a string for the exposedField itself (such as translation) or by giving the name of the corresponding eventIn or eventOut (set_translation for the eventIn and translation_changed for the eventOut).

2.2 Sending Events to an EventIn

Once an instance of the desired EventIn is obtained, an event can be sent to it. But EventIn is an abstract class and has no methods for sending events. It must first be cast to the appropriate eventIn type subclass, which contains methods for sending events of the given type.

Here is an example of sending an eventIn to a VRML scene containing this node:

    DEF Mover Transform { ... }

Here is the Java code to send it an event to change its translation field (assume browser is the instance of a Browser class gotten from a previous call):

    Node mover = browser.getNode("Mover");
    EventInSFVec3f translation = 
                (EventInSFVec3f) mover.getEventIn("set_translation");

    float value[3] = new float[3];
    value[0] = 5; value[1] = 0; value[2] = 1;
    translation.setValue(value);

In the above example, the translation value (5, 0, 1) is sent to the translation field of the Transform node.

2.3 Accessing an EventOut

Once an instance of a desired EventOut is obtained, 2 operations can be performed. The current value of the eventOut can be gotten and a callback can be setup to be invoked when the eventOut is generated. EventOut does not have any methods for getting the current value of the eventOut so it must be cast into the appropriate eventOut subclass type, which contains appropriate access methods. The advise() method on EventOut is called to set the callback.

Using the eventIn example above, the current value of the translation field can be read like this:

    float current[] = ((EventOutSFVec3f)
              (mover.getEventOut("translation_changed"))).getValue();

The array current now contains 3 floats with the current translation value.

2.4 The EventOut Observer

To receive notification when an eventOut is generated from the scene, the applet must first subclass the EventOutObserver class, implementing the callback() method. Next the advise() method of EventOut is passed the EventOutObserver. Then whenever an event is generated for that eventOut the callback() method is executed and passed the value and timestamp of the event. The advise() method is also passed a user defined object. This value is passed to the callback() method and can be used by the applet author to pass user defined data to the callback. It allows a single EventOutObserver subclass to handle events from multiple sources. It is a subclass of the standard Object class so it can be used to hold any data.

Using the above example again, the applet can get notified when the translation field of the Transform is changed like this:

    public class MyObserver implements EventOutObserver {
        public void callback(EventOut value, 
                             double timeStamp, 
                             Object data)
        {
            // cast value into an EventOutSFVec3f and use it
        }
    }

    ...

    MyObserver observer = new MyObserver;
    mover.getEventOut("translation_changed").advise(observer, null);

When the eventOut from translation occurs, observer.callback() is executed.


3 Interface Description

3.1 Interface Hierarchy

The VRML External Interface is specified in three Java packages: vrml.external, vrml.external.field, and vrml.external.exception. All of the members of package vrml.external.exception are classes derived from java.lang.RuntimeException; the rest of the members of the packages are specified as interfaces (with the exception of vrml.external.field.FieldTypes, which merely defines an integer constant for each EventIn/EventOut type). This allows the compiled Java applet to be used with any VRML browser's External Authoring Interface implementation.

vrml.external
  |
  +- vrml.external.Browser
  +- vrml.external.Node
  +- vrml.external.field
  |       +- vrml.external.field.EventIn
  |       |       +- vrml.external.field.EventInMFColor
  |       |       +- vrml.external.field.EventInMFFloat
  |       |       +- vrml.external.field.EventInMFInt32
  |       |       +- vrml.external.field.EventInMFNode
  |       |       +- vrml.external.field.EventInMFRotation
  |       |       +- vrml.external.field.EventInMFString
  |       |       +- vrml.external.field.EventInMFVec2f
  |       |       +- vrml.external.field.EventInMFVec3f
  |       |       +- vrml.external.field.EventInSFBool
  |       |       +- vrml.external.field.EventInSFColor
  |       |       +- vrml.external.field.EventInSFFloat
  |       |       +- vrml.external.field.EventInSFImage
  |       |       +- vrml.external.field.EventInSFInt32
  |       |       +- vrml.external.field.EventInSFNode
  |       |       +- vrml.external.field.EventInSFRotation
  |       |       +- vrml.external.field.EventInSFString
  |       |       +- vrml.external.field.EventInSFTime
  |       |       +- vrml.external.field.EventInSFVec2f
  |       |       +- vrml.external.field.EventInSFVec3f
  |       |
  |       +- vrml.external.field.EventOut
  |       |       +- vrml.external.field.EventOutMField
  |       |       |       +- vrml.external.field.EventOutMFColor
  |       |       |       +- vrml.external.field.EventOutMFFloat
  |       |       |       +- vrml.external.field.EventOutMFInt32
  |       |       |       +- vrml.external.field.EventOutMFNode
  |       |       |       +- vrml.external.field.EventOutMFRotation
  |       |       |       +- vrml.external.field.EventOutMFString
  |       |       |       +- vrml.external.field.EventOutMFVec2f
  |       |       |       +- vrml.external.field.EventOutMFVec3f
  |       |       |
  |       |       +- vrml.external.field.EventOutSFBool
  |       |       +- vrml.external.field.EventOutSFColor
  |       |       +- vrml.external.field.EventOutSFFloat
  |       |       +- vrml.external.field.EventOutSFImage
  |       |       +- vrml.external.field.EventOutSFInt32
  |       |       +- vrml.external.field.EventOutSFNode
  |       |       +- vrml.external.field.EventOutSFRotation
  |       |       +- vrml.external.field.EventOutSFString
  |       |       +- vrml.external.field.EventOutSFTime
  |       |       +- vrml.external.field.EventOutSFVec2f
  |       |       +- vrml.external.field.EventOutSFVec3f
  |       |
  |       +- vrml.external.field.EventOutObserver
  |       +- vrml.external.field.FieldTypes
  |
  +- vrml.external.exception
          +- vrml.external.exception.InvalidEventInException
          +- vrml.external.exception.InvalidEventOutException
          +- vrml.external.exception.InvalidNodeException
          +- vrml.external.exception.InvalidRouteException
          +- vrml.external.exception.InvalidVrmlException

3.2 Java Interface

Here is a complete list of the .java files making up the interface:

3.2.1 Package vrml.external

Browser.java

// Specification of the External Interface for a VRML browser.

public class Browser {
  // Get the "name" and "version" of the VRML browser (browser-specific)
  public String                getName();
  public String                getVersion();

  // Get the current velocity of the bound viewpoint in meters/sec,
  // if available, or 0.0 if not
  public float         getCurrentSpeed();

  // Get the current frame rate of the browser, or 0.0 if not available
  public float         getCurrentFrameRate();

  // Get the URL for the root of the current world, or an empty string
  // if not available
  public String                getWorldURL();

  // Replace the current world with the passed array of nodes
  public void          replaceWorld(Node[] nodes)
       throws IllegalArgumentException;

  // Load the given URL with the passed parameters (as described
  // in the Anchor node)
  public void          loadURL(String[] url, String[] parameter);

  // Set the description of the current world in a browser-specific
  // manner. To clear the description, pass an empty string as argument
  public void          setDescription(String description);

  // Parse STRING into a VRML scene and return the list of root
  // nodes for the resulting scene
  public Node[]                createVrmlFromString(String vrmlSyntax)
       throws InvalidVrmlException;

  // Tells the browser to load a VRML scene from the passed URL or
  // URLs. After the scene is loaded, an event is sent to the MFNode
  // eventIn in node NODE named by the EVENT argument
  public void          createVrmlFromURL(String[] url,
                                         Node node,
                                         String event);

  // Get a DEFed node by name. Nodes given names in the root scene
  // graph must be made available to this method. DEFed nodes in inlines,
  // as well as DEFed nodes returned from createVrmlFromString/URL, may
  // or may not be made available to this method, depending on the
  // browser's implementation
  public Node          getNode(String name)
       throws InvalidNodeException;

  // Add and delete, respectively, a route between the specified eventOut
  // and eventIn of the given nodes
  public void          addRoute(Node fromNode, String fromEventOut,
                                Node toNode, String toEventIn)
       throws IllegalArgumentException;
  public void          deleteRoute(Node fromNode, String fromEventOut,
                                   Node toNode, String toEventIn)
       throws IllegalArgumentException;

  // return an instance of the Browser class
  // This returns the first embedded plugin in the current frame.
  static public Browser getBrowser(Applet pApplet);

  // return an instance of the Browser class
  // If frameName is NULL, current frame is assumed.
  static public Browser getBrowser(Applet pApplet, String frameName, int index);
}


Node.java

// Specification of the Java interface to a VRML node.

package vrml.external;

import vrml.external.field.EventIn;
import vrml.external.field.EventOut;
import vrml.external.exception.InvalidEventInException;
import vrml.external.exception.InvalidEventOutException;

public class Node {
  // Get a string specifying the type of this node. May return the
  // name of a PROTO, or the class name
  public String                getType();

  // Means of getting a handle to an EventIn of this node
  public EventIn       getEventIn(String name)
       throws InvalidEventInException;

  // Means of getting a handle to an EventOut of this node
  public EventOut      getEventOut(String name)
       throws InvalidEventOutException;
}

3.2.2 Package vrml.external.field

EventIn.java

// Specification of the base interface for all eventIn types.

public class EventIn {
  // Get the type of this EventIn (specified in FieldTypes.java)
  public int           getType();
}

EventInMFColor.java

public class EventInMFColor extends EventIn {
  public void          setValue(float[][] value)
       throws IllegalArgumentException;
  public void          set1Value(int index, float[] value)
       throws IllegalArgumentException;
}

EventInMFFloat.java

public class EventInMFFloat extends EventIn {
  public void setValue(float[] value)
       throws IllegalArgumentException;
  public void set1Value(int index, float value)
       throws IllegalArgumentException;
}

EventInMFInt32.java

public class EventInMFInt32 extends EventIn {
  public void          setValue(int[] value)
       throws IllegalArgumentException;
  public void          set1Value(int index, int value)
       throws IllegalArgumentException;
}

EventInMFNode.java

public class EventInMFNode extends EventIn {
  public void          setValue(Node[] node)
       throws IllegalArgumentException;
  public void          set1Value(int index, Node node)
       throws IllegalArgumentException;
}

EventInMFRotation.java

public class EventInMFRotation extends EventIn {
  public void          setValue(float[][] value)
       throws IllegalArgumentException;
  public void          set1Value(int index, float[] value)
       throws IllegalArgumentException;
}

EventInMFString.java

public class EventInMFString extends EventIn {
  public void          setValue(String[] value)
       throws IllegalArgumentException;
  public void          set1Value(int index, String value)
       throws IllegalArgumentException;
}

EventInMFVec2f.java

public class EventInMFVec2f extends EventIn {
  public void          setValue(float[][] value)
       throws IllegalArgumentException;
  public void          set1Value(int index, float[] value)
       throws IllegalArgumentException;
}

EventInMFVec3f.java

public class EventInMFVec3f extends EventIn {
  public void          setValue(float[][] value)
       throws IllegalArgumentException;
  public void          set1Value(int index, float[] value)
       throws IllegalArgumentException;
}

EventInSFBool.java

public class EventInSFBool extends EventIn {
  public void          setValue(boolean value); 
}

EventInSFColor.java

public class EventInSFColor extends EventIn {
  public void          setValue(float[] value)
       throws IllegalArgumentException;
}

EventInSFFloat.java

public class EventInSFFloat extends EventIn {
  public void          setValue(float value);
}

EventInSFImage.java

public class EventInSFImage extends EventIn {
  public void          setValue(int width, int height, int numComponents,
                                byte[] pixels)
       throws IllegalArgumentException;
}

EventInSFInt32.java

public class EventInSFInt32 extends EventIn {
  public void          setValue(int value);
}

EventInSFNode.java

public class EventInSFNode extends EventIn {
  public void          setValue(Node value)
       throws IllegalArgumentException;
}

EventInSFRotation.java

public class EventInSFRotation extends EventIn {
  public void          setValue(float[] value)
       throws IllegalArgumentException;
}

EventInSFString.java

public class EventInSFString extends EventIn {
  public void          setValue(String value);
}

EventInSFTime.java

public class EventInSFTime extends EventIn {
  public void          setValue(double value);
}

EventInSFVec2f.java

public class EventInSFVec2f extends EventIn {
  public void          setValue(float[] value)
       throws IllegalArgumentException;
}

EventInSFVec3f.java

public class EventInSFVec3f extends EventIn {
  public void          setValue(float[] value)
       throws IllegalArgumentException;
}

EventOut.java

// Specification of the base interface for all eventOut types.

public class EventOut {
  // Get the type of this EventOut (specified in FieldTypes.java)
  public int           getType();

  // Mechanism for setting up an observer for this field.
  // The EventOutObserver's callback gets called when the
  // EventOut's value changes.
  public void          advise(EventOutObserver f, Object userData);
}

EventOutObserver.java

// Interface which all observer classes must implement.

public interface EventOutObserver {
  void callback(EventOut value, double timeStamp, Object userData);
}

EventOutMField.java

public class EventOutMField extends EventOut {
  public int           getSize();
}

EventOutMFColor.java

public class EventOutMFColor extends EventOutMField {
  public float[][]     getValue();
  public float[]       get1Value(int index);
}

EventOutMFFloat.java

public class EventOutMFFloat extends EventOutMField {
  public float[]       getValue();
  public float         get1Value(int index);
}

EventOutMFInt32.java

public class EventOutMFInt32 extends EventOutMField {
  public int[]         getValue();
  public int           get1Value(int index);
}

EventOutMFNode.java

public class EventOutMFNode extends EventOutMField {
  public Node[]                getValue();
  public Node          get1Value(int index);
}

EventOutMFRotation.java

public class EventOutMFRotation extends EventOutMField {
  public float[][]     getValue();
  public float[]       get1Value(int index);
}

EventOutMFString.java

public class EventOutMFString extends EventOutMField {
  public String[]      getValue();
  public String                get1Value(int index);
}

EventOutMFVec2f.java

public class EventOutMFVec2f extends EventOutMField {
  public float[][]     getValue();
  public float[]       get1Value(int index);
}

EventOutMFVec3f.java

public class EventOutMFVec3f extends EventOutMField {
  public float[][]     getValue();
  public float[]       get1Value(int index);
}

EventOutSFBool.java

public class EventOutSFBool extends EventOut {
  public boolean       getValue();
}

EventOutSFColor.java

public class EventOutSFColor extends EventOut {
  public float[]       getValue();
}

EventOutSFFloat.java

public class EventOutSFFloat extends EventOut {
  public float         getValue();
}

EventOutSFImage.java

public class EventOutSFImage extends EventOut {
  public int           getWidth();
  public int           getHeight();
  public int           getNumComponents();
  public byte[]         getPixels();
}

EventOutSFInt32.java

public class EventOutSFInt32 extends EventOut {
  public int           getValue();
}

EventOutSFNode.java

public class EventOutSFNode extends EventOut {
  public Node          getValue();
}

EventOutSFRotation.java

public class EventOutSFRotation extends EventOut {
  public float[]       getValue();
}

EventOutSFString.java

public class EventOutSFString extends EventOut {
  public String                getValue();
}

EventOutSFTime.java

public class EventOutSFTime extends EventOut {
  // Note that this returns a VRML "Time" - the number of seconds since
  // Jan 1, 1970 GMT, rather than a Java time which is a long, the number
  // of milliseconds since Jan 1, 1970 GMT
  public double                getValue();
}

EventOutSFVec2f.java

public class EventOutSFVec2f extends EventOut {
  public float[]       getValue();
}

EventOutSFVec3f.java

public class EventOutSFVec3f extends EventOut {
  public float[]       getValue();
}

FieldTypes.java

// Wrapper class specifying the types of all VRML eventIns/eventOuts.

public final class FieldTypes {
  public final static int UnknownType  = 0;
  public final static int SFBOOL       = 1;
  public final static int SFIMAGE      = 2;
  public final static int SFTIME       = 3;
  public final static int SFCOLOR      = 4;
  public final static int MFCOLOR      = 5;
  public final static int SFFLOAT      = 6;
  public final static int MFFLOAT      = 7;
  public final static int SFINT32      = 8;
  public final static int MFINT32      = 9;
  public final static int SFNODE       = 10;
  public final static int MFNODE       = 11;
  public final static int SFROTATION   = 12;
  public final static int MFROTATION   = 13;
  public final static int SFSTRING     = 14;
  public final static int MFSTRING     = 15;
  public final static int SFVEC2F      = 16;
  public final static int MFVEC2F      = 17;
  public final static int SFVEC3F      = 18;
  public final static int MFVEC3F      = 19;

  // This class should never need to be instantiated
  private FieldTypes() {}
}

3.2.3 Package vrml.external.exception

InvalidEventInException.java

public class InvalidEventInException extends RuntimeException
{
    /**
     * Constructs an InvalidEventInException with no detail message.
     */
    public InvalidEventInException() {
        super();
    }

    /**
     * Constructs an InvalidEventInException with the specified detail message.
     * A detail message is a String that describes this particular exception.
     * @param s the detail message
     */
    public InvalidEventInException(String s) {
        super(s);
    }
}

InvalidEventOutException.java

public class InvalidEventOutException extends RuntimeException
{
    public InvalidEventOutException() {
        super();
    }

    public InvalidEventOutException(String s) {
        super(s);
    }
}

InvalidNodeException.java

public class InvalidNodeException extends RuntimeException
{
    public InvalidNodeException() {
        super();
    }

    public InvalidNodeException(String s) {
        super(s);
    }
}

InvalidRouteException.java

public class InvalidRouteException extends RuntimeException
{
    public InvalidRouteException() {
        super();
    }

    public InvalidRouteException(String s) {
        super(s);
    }
}

InvalidVrmlException.java

public class InvalidVrmlException extends RuntimeException
{
    public InvalidVrmlException() {
        super();
    }

    public InvalidVrmlException(String s) {
        super(s);
    }
}


This proposal and all examples and source code contained herein are hereby placed in the public domain. No rights are claimed or inferred by the author or Silicon Graphics, Inc.

Contact chris@marrin.com with questions or comments.