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.
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:
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.
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.
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.
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).
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.
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.
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.
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
Here is a complete list of the .java files making up the interface:
// 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); }
// 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; }
// 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(); }
public class EventInMFColor extends EventIn { public void setValue(float[][] value) throws IllegalArgumentException; public void set1Value(int index, float[] value) throws IllegalArgumentException; }
public class EventInMFFloat extends EventIn { public void setValue(float[] value) throws IllegalArgumentException; public void set1Value(int index, float value) throws IllegalArgumentException; }
public class EventInMFInt32 extends EventIn { public void setValue(int[] value) throws IllegalArgumentException; public void set1Value(int index, int value) throws IllegalArgumentException; }
public class EventInMFNode extends EventIn { public void setValue(Node[] node) throws IllegalArgumentException; public void set1Value(int index, Node node) throws IllegalArgumentException; }
public class EventInMFRotation extends EventIn { public void setValue(float[][] value) throws IllegalArgumentException; public void set1Value(int index, float[] value) throws IllegalArgumentException; }
public class EventInMFString extends EventIn { public void setValue(String[] value) throws IllegalArgumentException; public void set1Value(int index, String value) throws IllegalArgumentException; }
public class EventInMFVec2f extends EventIn { public void setValue(float[][] value) throws IllegalArgumentException; public void set1Value(int index, float[] value) throws IllegalArgumentException; }
public class EventInMFVec3f extends EventIn { public void setValue(float[][] value) throws IllegalArgumentException; public void set1Value(int index, float[] value) throws IllegalArgumentException; }
public class EventInSFBool extends EventIn { public void setValue(boolean value); }
public class EventInSFColor extends EventIn { public void setValue(float[] value) throws IllegalArgumentException; }
public class EventInSFFloat extends EventIn { public void setValue(float value); }
public class EventInSFImage extends EventIn { public void setValue(int width, int height, int numComponents, byte[] pixels) throws IllegalArgumentException; }
public class EventInSFInt32 extends EventIn { public void setValue(int value); }
public class EventInSFNode extends EventIn { public void setValue(Node value) throws IllegalArgumentException; }
public class EventInSFRotation extends EventIn { public void setValue(float[] value) throws IllegalArgumentException; }
public class EventInSFString extends EventIn { public void setValue(String value); }
public class EventInSFTime extends EventIn { public void setValue(double value); }
public class EventInSFVec2f extends EventIn { public void setValue(float[] value) throws IllegalArgumentException; }
public class EventInSFVec3f extends EventIn { public void setValue(float[] value) throws IllegalArgumentException; }
// 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); }
// Interface which all observer classes must implement. public interface EventOutObserver { void callback(EventOut value, double timeStamp, Object userData); }
public class EventOutMField extends EventOut { public int getSize(); }
public class EventOutMFColor extends EventOutMField { public float[][] getValue(); public float[] get1Value(int index); }
public class EventOutMFFloat extends EventOutMField { public float[] getValue(); public float get1Value(int index); }
public class EventOutMFInt32 extends EventOutMField { public int[] getValue(); public int get1Value(int index); }
public class EventOutMFNode extends EventOutMField { public Node[] getValue(); public Node get1Value(int index); }
public class EventOutMFRotation extends EventOutMField { public float[][] getValue(); public float[] get1Value(int index); }
public class EventOutMFString extends EventOutMField { public String[] getValue(); public String get1Value(int index); }
public class EventOutMFVec2f extends EventOutMField { public float[][] getValue(); public float[] get1Value(int index); }
public class EventOutMFVec3f extends EventOutMField { public float[][] getValue(); public float[] get1Value(int index); }
public class EventOutSFBool extends EventOut { public boolean getValue(); }
public class EventOutSFColor extends EventOut { public float[] getValue(); }
public class EventOutSFFloat extends EventOut { public float getValue(); }
public class EventOutSFImage extends EventOut { public int getWidth(); public int getHeight(); public int getNumComponents(); public byte[] getPixels(); }
public class EventOutSFInt32 extends EventOut { public int getValue(); }
public class EventOutSFNode extends EventOut { public Node getValue(); }
public class EventOutSFRotation extends EventOut { public float[] getValue(); }
public class EventOutSFString extends EventOut { public String getValue(); }
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(); }
public class EventOutSFVec2f extends EventOut { public float[] getValue(); }
public class EventOutSFVec3f extends EventOut { public float[] getValue(); }
// 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() {} }
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); } }
public class InvalidEventOutException extends RuntimeException { public InvalidEventOutException() { super(); } public InvalidEventOutException(String s) { super(s); } }
public class InvalidNodeException extends RuntimeException { public InvalidNodeException() { super(); } public InvalidNodeException(String s) { super(s); } }
public class InvalidRouteException extends RuntimeException { public InvalidRouteException() { super(); } public InvalidRouteException(String s) { super(s); } }
public class InvalidVrmlException extends RuntimeException { public InvalidVrmlException() { super(); } public InvalidVrmlException(String s) { super(s); } }