VRML Behavior examples

Gavin Bell, <gavin@sgi.com>, Silicon Graphics
Rob Myers, Silicon Graphics

This document can be found at http://reality.sgi.com/employees/gavin/vrml/Examples.html. It was last updated on Dec. 11, 1995.


Examples

All of these examples are "pseudo-VRML" to save space (coordinates are not specified, etc). They also use a C or C++-like language for all Logic nodes. Please use these to get a feeling for the overall architecture of this proposal, and NOT as an example of exactly what the scripting language should look like:

A cube that changes color when picked

DEF CLICKSENSOR ClickSensor {
  DEF MATERIAL Material { }
  Cube { }

  DEF LOGIC Logic {
    eventIn SFBool isBeingClicked
    eventOut SFColor color
    script "processEvents(EventList events) {
              Color outputColor;
              for (int i = 0; i < events.getLength(); i++) {
                BoolEvent event = (BoolEvent) events[i];
                if (event.isTrue())
                  outputColor = Color(1,0,0);
                else
                  outputColor = Color(0,1,0);
              }
              SEND_COLOR_EVENT(\"color\", outputColor);
            }"
  }
  ROUTE LOGIC.color -> MATERIAL.setDiffuseColor
  ROUTE CLICKSENSOR.isActive -> LOGIC.isBeingClicked
}

The Logic node was defined as the first child of the ClickSensor. The colors it produces are hard-wired in the script, but could also have been provided as fields:

  DEF LOGIC Logic {
    field SFColor color1 0 0 0  # Defaults to black
    field SFColor color2 0 0 0
    eventIn SFBool isBeingClicked
    eventOut SFColor color
    script "... if (event.isTrue()) outputColor = GETFIELD(\"color1\");
                else outputColor = GETFIELD(\"color2\");"
    color1 1 0 0
    color2 0 1 0
  }
  ROUTE CLICKSENSOR.isActive -> LOGIC.isBeingClicked

Start a keyframe animation whenever a cube is picked

Separator {

  # This is the cube that will start the animation:
  DEF START ClickSensor {
    Cube { }
 
    # Need a time source and an interpolator:
    DEF TIME TimeSensor {
      # Animate for 10 seconds starting whenever the button is
      # released from the cube:
      interval 10.0
    } 
    DEF INTERP PointInterpolator {
        keys [ .... ]
        values [ .... ]
      }
    }

    # Wire things up:
    ROUTE START.release -> TIME.setStart
    ROUTE TIME.alpha -> INTERP.setAlpha
 }

  # And this is the object that will move:
  Separator {
    DEF TRANSFORM Transform { }
    ... objects to be animated ...

    ROUTE INTERP.outValue -> TRANSFORM.setTranslation
  }
}

This example just starts a 10-second keyframe animation that changes the translation of some objects when the user clicks and releases the mouse (or other pointing device) over the cube. To both animate and rotate the objects, we could just add a set of keyframes to the Transform's rotation field:

DEF ROTATE_INTERP RotationInterpolator {
       keys [ ... ] values [ ... ]
     }
}
ROUTE TIME.alpha -> ROTATE_INTERP.setAlpha
ROUTE ROTATE_INTERP.outValue -> TRANSFORM.setRotation

A Logic node could also be inserted between the Transform and the TimeSensor to (for example) start the animation 3 seconds after the cube is picked, or to only start an animation IF a button has been pressed or a puzzle has been solved, or to start a series of animations (e.g. animation #1 starts right away and lasts 14 seconds, animations #2 and #3 start 14 seconds from now and last 5 and 7 seconds, respectively, animation #4 starts when animation #2 ends (19 seconds from now), etc-- arbitrary scheduling and general modifications of time can be done using Logic nodes.

A simple toggle-button prototype

This is a very simple implementation of a button that alternately outputs TRUE/FALSE events when clicked:

PROTO ToggleButton [ field SFBool initialState  IS  LOGIC.state,
                     eventOut SFBool isOn  IS  LOGIC.eventOut ] {
  DEF CLICKSENSOR ClickSensor {
    DEF LOGIC Logic {
      eventIn SFTime release
      field SFBool state FALSE
      eventOut SFBool state
      script "processEvents(Events events) {
                // Only need to change state if odd number of events:
                if ((events.length % 2) == 1) {
                   SFBool curState = GET_FIELD(\"state\");
                   curState = !curState;
                   SET_FIELD(curState);
                   SEND_EVENT(\"state\", curState);
              }"
    }
    ROUTE CLICKSENSOR.release -> LOGIC.release
    ... Geometry of button is here...
  }
}
... later, to use a toggle button:
DEF TB ToggleButton { initialState FALSE }
DEF LIGHT PointLight { }
ROUTE TB.state -> LIGHT.setOn

This is overly simple; a real implementation would also have a Switch node that changed the button's appearance depending on its state. A good implementation would also use the isOver eventOut of the ClickSensor to locate-highlight the button.

HSV color space Material node

Mitra/Sony: Believe that prototyping or routing directly into property nodes such as Material will force browsers to maintain those nodes internally, and that examples such as this are better done as Script nodes that are routed to geometry.

Gavin: Believes that being able to prototype properties in this way is a great feature, and that remapping routes from the property (e.g. color -> Material.diffuseColor) to the geometry (color -> Geometry.MaterialdiffuseColor) can be easily done automatically by the browser.

This prototype defines the HSV equivalent of the Material node:

PROTO HSVMaterial
  [ eventIn MFColor setAmbientColor IS CONVERT.ambientIn,
    field   MFColor ambientColor IS M.ambientColor,
    eventIn MFColor setDiffuseColor IS CONVERT.diffuseIn,
    field   MFColor diffuseColor IS M.diffuseColor,
    eventIn MFColor setSpecularColor IS CONVERT.specularIn,
    field   MFColor specularColor IS M.specularColor,
    eventIn MFColor setEmissiveColor IS CONVERT.emissiveIn,
    field   MFColor emissiveColor IS M.emissiveColor,
    eventIn MFFloat setShininess IS M.setShininess,
    field   MFColor shininess IS M.shininess,
    eventIn MFFloat setTransparency IS M.setTransparency,
    field   MFColor transparency IS M.transparency ] {
# Implementation: Material hooked up to Logic:
    DEF M Material { }
    DEF CONVERT Logic {
       eventIn MFColor ambientIn
       eventOut MFColor ambientOut
       ... etc ...
       script "processEvents(Events e) {
           For all events:
              switch on event name
                 convert HSV to RGB and send out event
           done."
    }
    ROUTE CONVERT.ambientOut -> M.setAmbientColor
    ROUTE CONVERT.diffuseOut -> M.setDiffuseColor
    ... etc, I'm being lazy again...
}

Note that attaching a ColorInterpolator (which interpolates in RGB space) to an HSVMaterial gives color interpolation in HSV space (the "RGB" values that are being interpolated will be translated into HSV values as they're interpolated by the HSVMaterial's logic).

Useful properties such as a camera defined by a look-at and a look-up direction, or a Transform whose rotation is defined by yaw/pitch/roll values could also be implemented this way.