This paper proposes a way to add behaviors to VRML. It address mainly the extensions needed to VRML to author links to seperate behavior scripts.
This document is part of a collection of VRML2.0 history at http://www.mitra.biz/vrml/vrml2/mw_history.html
This version: Mitra <email@example.com>, 2 Dec 95.
History and Dependencies
This proposal is based on the 12th November version . With changes made as a result of the VRML behaviors meeting in San Diego.
This paper draws heavily on discussions with, and the proposals by Sony ,   and SDSC , and is an attempt to merge the best ideas from these and my own papers.
This paper also reflects a number of concerns brought up by SGI, both in their behaviors paper , and in meetings.
Their are several other papers that should be considered part of this proposal, they are mostly orthogonal to this paper.
- API: the API between browser and Interpreter/Apple
- TimeSensors: Time event generator
- Sensors: Click and Drag sensors etc
- Interpolators: for animation
- Pseudo Nodes: Access to browser state.
- Prototypes: Prototyping and instancing.
- Frames: A proposal for changing Separators (this paper will need some minor rewriting if the Frames proposal is accepted.)
Changes since 12 Nov 95
Firstly - it appears that we are all looking for a Object-Oriented approach, but I think older proposals diverged from this to a certain extent. To be O-O the author should probably be thinking something like ...
An Object has:
- A collection of geometry, data and properties - essentially the existing VRML plus some parts of the Interface proposal.
- A collection of behaviors or methods
- A set of event handlers that link actions on the objects to behaviors/methods on this object or others.
If our O-O model is essentially that a Separator is our Object (since any lower level of detail may have been thrown away by the browser), then this could get fairly easy. Basically we have to:
- Indicate the set of behaviors associated with the object (or class)
- Attach a collection of event handlers to it
The various Nodes in SDSC, Sony and my proposals composite some or all of three sections,
- A way to specify which behavior file to use - this specifies a filename and a scriptType.
- A way to specify the function to call - specifies the procedure, and parameters
- A way to specify an event - specifies eventType (optional parameters)
Behavior files are references through a new ScriptNode
behavior "http://foo.com/bar.class" ; MFSTRING
scriptType "JAVA" ; SFSTRING
eventIn SFSTRING name
eventIn SFVEC3F lookfrom
eventIn PICK selected
eventOut SFSTRING lookto
optimisation SFBITMASK (CULLEVENTS | LODEVENTS | WORLDINOUT |
UNKNOWNOUTPUTS | ALWAYSEVAL )
ScriptNodes can only appear as children of Frames, and have an implicit object which they are attached to.
- The behavior field specifies the URL of a script to use, e.g. "http://foo.com/mycode.java",
- The scriptType can be used to specify the Mime type of the script e.g. "application/java". If unspecified it will be inferred by the transport layer.
- The eventIn fields are used to specify field events that the script can handle, they have two uses
Note that multiple outputs can be fanned into a single eventIn. Also note the example of specifing the type as PICK, which is a hint to the authoring system that it should only attach this eventIn to a ClickSensor.
- They can be used to make the API of the script available to a visual authoring tool so that it can make connections between a field on another node, and a particular function call to the script.
- On some (OI) systems they will allow optimisation to be done, since if this Script's object is not visible, and it is set to allow AUTO culling, then the source of the eventIn may not need to be evaluated.
- The eventOut fields are used to specify outputs from the script, if all the outputs are known then some optimisation can be done. (See Culling rules)
- The optimisation flags are used to help smarter browsers decide when to evaluate a script.
There is a really tough balance to be struck between functionality, and the ability to optimize, it is our belief that these flags allow the author to get the functionality at the cost of optimisation only in the cases where this cost has to be born. Note that the defaults are chosen to support full optimisation when the author does nothing.
- CULLEVENTS - says that the script should receive events when the browser would otherwise cull it,
for example because it went out of view.
- LODEVENTS - says that the script should receive events when the browser would change the LOD level of its object. These will only occur when the behavior is attached to a LOD node. Issue, would it be better to allow this to be independant of visual LOD, for example with a LODSensor
- WORLDEVENTS - says that the script should receive events when the browser wants to load or unload the world. This can be used to save state for example.
- UNKNOWNOUTPUTS - says that the script may effect other parts of the scene graph than its object and its outputs. See Culling rules.
- ALWAYSEVAL - says that no matter what any other flags say, evaluate this script whenever its inputs change.
The following examples are taken from various papers, and converted to this scheme, note that
most of the examples in all of the existing proposals get syntactically more complex with the Interfaces proposal, and VRML1.1 style separators.
A cube that changes color when picked and again when released
This is an easy example because the action is performed on the same object on which the event occurs.
This example needs rewriting
Turning the lights on when the switch is grabbed
This example needs rewriting
Short programs may be specified by placing directly in the behavior field of the ScriptNode (or the Separator if the change planned happens).
A tiny language can be embedded in the "actionMethod" field directly to describe some simple methods.
actionMethod "set SFBOOL on TRUE"
Only the following syntax can be used for describing methods.
set FIELDTYPE FIELDNAME CONSTANT
If we have input events on nodes, then it may be possible to get rid of this language entirely and use EventHandlers, and do all scripting in the external language. For example, the above becomes either:
actionParameters "SFBOOL TRUE"
At this time I can't see how to do this using ROUTES without a GrabSensor, a ROUTE and a LOGIC node?
We need a complete list of possible events, although the eventType field could be extended to new events later. This requirement is because the browsers are going to implement the event detection and propogation at a fairly low level.
For each event we have to define the paramaters that are passed across to the script in the case that a actionMethod is not specified.
All events, and messages, include the following information.
- EventOn: Where the event first occurred, e.g. the object which was picked. Note that in the case of Cascading, this might be different from where the event gets handled.
- EventSentBy: This is used if the event is sent from some other object than that which the script is attached too, for example if a switch sends an On message to a light.
- Time: Open: SGI will probably want a time added to each event
The first set are events sent by sensors. (The ... refers to the parameters sent with all events, and listed above).
The second set of events are sent by the browser in response to various culling, and administration situations. They are controlled by flags on the scriptNode.
- PICK pick( ... , SFNODE Picksensor)
When the mouse is clicked the event generates a pointer to the PICK EventHandler, which can be interogated for the relevant Information.
- GRAB,RELEASE grab(..., SFNODE Grabsensor) and release (..., SFNODE Grabsensor),
When the mouse is held down on the object, and when it is released.
The OS will decide whether the mouse-click is short enough to send as a PICK event, or whether to send GRAB and RELEASE. Also note that DRAG events only occur inside GRAB,RELEASE pairs.
Open issue: Some questions have been raised as to whether the Application should get the raw events (mouse clicks and times) or processed events (Pick, Grab, Drag). The advantage of the latter is that it would then work across different input devices, and be consistent with OS settings (such as the double-click time selected by the user). There are some questions as to whether it is possible to map events for all OS's and most input devices into this metaphor, or whether for example a Data Glove needs a whole different set of events.
- DRAG drag(..., SFNode DragSensor)
Sent when a GRAB-ed object is moved, and where the pointing devise indicates it should be moved to, and - where relevant - the orientation it should be rotated by.
- ENTER, LEAVE enter(..., SFNode VolumeSensor) or leave(..., SFNode VolumeSensor)
Sent when the viewpoint enters or leaves a sensed volume, or collides with a sensed object.
- COLLISION collision(..., SFNode CollisionSensor)
Triggered when the user collides with the object, note this is independant of the changes being made to the Separator to incorporate collision detection. The Collision event is triggered when the collision detection logic prevents the user entering an object. It is never called in conjunction with Enter and Leave. It indicates what part of the user collided with what part of the object, and the orientation of the user (relative to the object?).
- TICK tick(..., SFNode Tick)
Sent when from a TimerSensor.
- LODIN and LODOUT lodin(..., SFLONG range), lodout(..., SFLONG range)
When the object is being signalled that it is not being drawn for LOD reasons. Typically a behavior may use these events to suspend behavior, or to reduce the CPU cycles this behavior absorbs - for example a mobile object might keep track of where it was, but suspend updating the geometry.
- WORLD_IN and WORLD_OUT worldin(...) and worldout(...)
These are called when the world is loaded, and when it is unloaded. Note these are distinct from the Init and End methods which are called when the applet is loaded and unloaded.
- FRAME frame(...)
Sent once per frame.
- CULLIN and CULLOUT cullin(...), cullout(...)
Triggered when rendering is being culled, a behavior may suspend, or scale back, its activities. Note this is different from LODIN and LODOUT.
The Sensor structure doesn't support events which require parameters in order to be triggered. So a small set of extra nodes will need defining for more complex events. The FieldEvent is one example of this, it allows a behavior to be triggered when a field changes value.
eventOn "" ; SFSTRING Node name to watch, default to this Separator
field "" ; SFSTRING field to watch
actionOn, actionMethod, actionParameters ; as for Sensors
The defaults for actionMethod and actionParameters are to send a data structure - to be defined - containing
a pointer to the node changed, the name of the field changed and the value it changed to. This may need more thought
This could be extended to allow more complex tests - for example only trigger when this field goes to TRUE, however is is not clear that the added complexity is worth it, as compared to having the full power of a programming language available in the behavior. Typically the field being watched will be passed to the Applet in one of the actionParameters.
Note that if we adopt Gavin's suggestion of using events as inputs to regular nodes, then FieldEvent can be used for routing fields of one node to another, or from a node to a ScriptNode. In fact, FieldEvent and ROUTE become semantically, and functionally equivalent. For example:
ROUTE foo.alpha -> ascript.timein
is equivalent to
actionMethod setTimein # Or whatever naming convention is used.
actionParameters SFFLOAT alpha
Since ROUTE will actually get implemented as adding something akin to a FieldEvent to a node, it might be better to specify FieldEvent itself, or it might be better and syntactically cleaner to just use ROUTE.
Two features facilitate timers indepndantly of functionality that may or may not be available in the language being used (e.g. Java probably has it, Perl probably doesn't).
A pseudo-object allows a behavior to read the time via GetField calls. The fields need more discussion, but there are probably several different time values that could be watched - so these are made available as seperate fields. The OpenInventor SFTIME type is specified, there might be a better class to use since SFTIME is specified in seconds, or alternatively a millisecond field could be added.
realTime ; SFTIME - the time asserted by the machine's clock.
; This might not be accurate, especially on low-end
wallClock ; SFTIME - in a simulation this is the time being
; simulated in most circumstances this will be the
; same as realTime.
elapsedTime ; SFTIME - time since this simulation started,
; or the world was loaded. (This might not be useful)
To deal with concerns about optimisation,
some rules must be laid down about when a browser can, and when it cannot, optimise away, or cull, a behavior (or a node).
- If a script has an output that is visible, it must be evaluated when it receives events.
- If a script has the UNKNOWNOUPUTS flag set, meaning that it might effect other things in the scene graph, then it must be assumed to be connected to a visible object, so rule 1 applies.
- If a script has the ALWAYSEVAL flag set, then it must be evaluated whenever it receives events. (this might be equivalent to UNKNOWNOUTPUTS, but I'm not sure it will be for all systems).
- If a script would be evaluated, then any Node that it depends on will need evaluating.
In the interests of full disclosure - I've spotted the following nasty features of this proposal that may need addressing.
- There is no way to have multiple scripts on the same object, or to specify which program file that an Sensor should use.
- Handling key input - e.g. grabbing focus, and directing key events?
- The prototype node needs extending to support local variables, scoped by the PROTO, or some other way for items inside a prototype to access each other without exposing themselves outside the prototype.
References are now in a seperate document which seems to have been lost