The EAI adapts this interface by giving access to the top-level named nodes in the system to the external environment. The interface then mimics the access of the Script node with a reference to the named node. To facilitate the sending of events to the external environment, a new mechanism is made available. To receive notification when an event is sent to an eventOut, the external environment registers interest in it. This mechanism is similar in concept to the processEvents functionality in the VRML specification, but is effectively different due to the asynchronous nature of the external environment.
This interface can be applied to both language based and protocol based implementations (see 2. Language Based Interface and 3. Protocol Based Interface). The interface in either case is conceptually identical.
Conceptually, the External Authoring Interface allows 4 types of access into the VRML scene:
The concepts of a language based EAI are similar to those of Script Authoring Interface, which provides the interface for languages supported by the VRML Script node. There are two conceptual differences between the two. The first has to do with obtaining a node reference through which eventIns and eventOuts can be accessed. When creating a VRML file a Script node (and therefore the script it contains) can get a node reference with the USE construct (see Instancing). Since the external environment has no implicit access to this mechanism an explicit method is provided to get the reference from its DEF name string.
EventOut notification is also conceptually different since creating a ROUTE is not possible between the VRML scene and the external environment. The application 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.
Because some environments may be inherantly asynchronous, getting a reference to an existing browser instance may block the application until that instance is created.
For object oriented languages these are virtual
methods which can be added to the Browser class through subclassing
or interface implementation. For other languages, these are callback methods,
passed as parameters to the createBrowser(
When a VRML scene is changed through use of the replaceWorld( ) method, the named nodes of that scene are not available to the application. The only way to access such a world is through the root nodes, which are passed as a parameter to replaceWorld( ). However, when a scene is changed using the loadURL( ) method, the new URL becomes the base scene and therefore the named nodes of that scene are available to the application.
A VRML scene may be in the process of loading when an application attempts to access nodes in it. The requested nodes will not be accessible in this case, and the getNode( ) call will fail. To prevent this, access to the scene should not be attempted until the initialize( ) method is called.
In order to better control the sending of events
to the scene, language-based implementations shall provide a beginUpdate(
) and endUpdate( ) mechanism. If these methods are not invoked,
each event sent to the scene shall be processed immediately. All event
sending methods called after the first call to
There shall also be an
Since the Script Authoring Interface does not support the concept of protocol based interfaces, the two are functionally quite different. But conceptually the interaction with the VRML scene is very similar, with two differences. The first has to do with obtaining a node reference through which eventIns and eventOuts can be accessed. When creating a VRML file a Script node (and therefore the script it contains) can get a node reference with the USE construct (see Instancing). Since the external environment has no implicit access to this mechanism the protocol must supply an identifier for the desired node. This identifier is equivalent, but is not necessarily identical, to the DEF name of the node.
EventOut notification is also conceptually different since creating a ROUTE is not possible between the VRML scene and the external environment. The protocol must register interest in an eventOut of a given node. When the eventOut generates an event a packet is sent to the external environment.
Because some environments may be inherantly asynchronous, getting an identifier for an existing browser instance may require that an acknowledgement packet be sent in response to an identifier request.
When a VRML scene is changed through use of the replaceWorld function, the named nodes of that scene are not available to the application. The only way to access such a world is through the root node, which is passed as a parameter to replaceWorld. However, when a scene is changed using the loadURL( ) method, the named nodes of that scene are available through the same node identifier system as the original scene.
A VRML scene may be in the process of loading when an external environment attempts to access nodes in it. The requested nodes will not be accessible in this case. To prevent this, access to the scene should not be attempted until the initialize packet is received.
There shall also be a mechanism to terminate this notification.
An instance of an existing scene is obtained by instantiating a Browser class. This class is used when the application is embedded in another environment (such as a HTML browser), which has already instantiated one or more VRML scenes. This class may be instantiated in two ways:
public Browser(Applet pApplet);
public Browser(Applet
pApplet, String frameName, int index);
The Applet class is a standard Java class which is the encapsulation of the application in the environment. The first form associates the first or only VRML scene with the instantiation. The second form is more general and allows a frameName string identifying the container owning the desired VRML scene to be specified. Passing NULL or the empty string selects the first or only container in the environment. Additionally, an index of the VRML scene within the container can be specified. This is used in environments where a given container can own more than one sub-component. If this is not the case, a value of 0 shall be specified.
To create a new instance of a VRML scene, the CBrowser class is used. This class implements the Browser interface and also extends the AWT Component class. Therefore it can have all the capabilities of accessing the scene, plus it can be added to an AWT Container class. When creating an instance of CBrowser, the replaceWorld( ) or loadURL( ) methods of the Browser class are used to add the initial scene.
Both the Browser and CBrowser classes contain initialize( ) and shutdown( ) methods (see A.3 Initialization and shutdown). The author may extend these classes, providing a new implementation for these methods.
Once an instance of the desired EventIn is obtained, an event can be sent to it. But EventIn has no methods for sending events. It must first be cast to the appropriate eventIn 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 for sending an event to change the translation field (assume browser is the instance of a Browser, ABrowser, or CBrowser 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.
Events are sent immediately when setValue is called
unless the call is preceeded by one or more
Once an instance of a desired EventOut is obtained, two operations can be performed. The current value of the eventOut can be retrieved, and a callback can be setup to be invoked whenever the eventOut is generated. EventOut does not have any methods for getting the current value so it must be cast into the appropriate eventOut subclass type, which contains appropriate access methods.
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 x, y, and z components of the current translation value.
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 the translation exposedField occurs, observer.callback() is invoked.
The EventOut class also has an
vrml.external | +- vrml.external.IBrowser | +- vrml.external.Browser | +- vrml.external.CBrowser | +- 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.InvalidVrmlException
// Specification of the External Interface for a VRML browser. public interface IBrowser { // 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; // begin and end an update cycle public void beginUpdate(); public void endUpdate();
// called after the scene is loaded, before the first event is processed public void initialize(); // called just before the scene is unloaded public void shutdown(); }
// Specification of the External Interface for a VRML applet browser. public class Browser implements IBrowser { // construct an instance of the Browser class // Associates this instance with the first embedded plugin in the current frame. public Browser(Applet pApplet); // construct an instance of the Browser class // If frameName is NULL, current frame is assumed. public Browser(Applet pApplet, String frameName, int index); // 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; // begin and end an update cycle public void beginUpdate(); public void endUpdate();
// called after the scene is loaded, before the first event is processed public void initialize(); // called just before the scene is unloaded public void shutdown(); // 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 External Interface for a VRML browser component. public class CBrowser implements IBrowser extends Component { // Construct an instance public CBrowser(); // 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; // begin and end an update cycle public void beginUpdate(); public void endUpdate();
// called after the scene is loaded, before the first event is processed public void initialize(); // called just before the scene is unloaded public void shutdown(); }
// 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); // terminate notification on the passed EventOutObserver public void unadvise(EventOutObserver f); }
// 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() {} }
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 InvalidVrmlException extends RuntimeException { public InvalidVrmlException() { super(); } public InvalidVrmlException(String s) { super(s); } }
See http://tecfa.unige.ch/guides/vrml/examples/eai/ instead for a bigger selection of examples.
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.