CLIPS as a scripting language for VRML

-- Swaminathan N.

The Virtual Reality Modeling Language (VRML) is a file format for describing interactive 3D objects and worlds. For more information, please read the VRML specification at http://www.vrml.org/VRML97/DIS. I've been implementing a VRML 2.0 browser designed to work in immersive environments. For some sketchy information please look at http://www.evl.uic.edu/swami/vrml.html

Apart from the semantic descriptions, VRML consists of nodes and fields. There are various kinds of nodes - grouping nodes that define a coordinate space for their children; lighting nodes; sensor nodes (e.g. TimeSensor and TouchSensor ) that provide support for much of the user interaction; Interpolators which are designed for key frame animation and geometry and appearance nodes that represent the objects actually drawn.

These nodes have a set of fields that distinguish each node, and a set of events that the nodes can receive and send. One can thus create interesting interactions using ROUTEs to connect one event to another. For instance, one could connect the isOver eventOut of a TouchSensor to the set_on eventIn of a DirectionalLight. Thereafter, whenever the user moves the pointing device over the geometry, the light will be turned on.

To provide for more complex decisions, aside from simple routing, one can use a Script node. A Script node typically receives events, processes them and sends events out. It may also be used to implement persistence. Although the VRML specification does not require any particular scripting language, the appendices include details for Java and JavaScript.

A Script node is activated when it receives an event. The browser must then execute the program in the Script's url field. The program can do a variety of actions including sending out eventOuts, keeping state, communicating with servers etc.

CLIPS as a scripting language

My approach to interfacing VRML to CLIPS (or vice versa) is as follows.

0. The browser loads the constructs listed in the url field of the Script node into CLIPS.
1. If there are any fields, it asserts them as facts at initialization.
2. eventIns to the script nodes => facts asserted in CLIPS.
3. These facts cause certain rules (defined in the url field) to fire. During the update traversal (of the browser), one could either run till there are no activations, or one could run one step at a time.
4. facts asserted in CLIPS => sent out as eventOuts. The facts are then retracted.

Facts are ordered facts. One could conceivably do a better job using COOL extensions. The various type of facts and the corresponding mapping to VRML are listed below. They follow the layout (field "fieldType" name values). fieldType can be any of the VRML fields. name is the name of the field as listed in the script.values is a series of slots which are interpreted according to the fieldType.

(field SFBool name [TRUE|FALSE])
(field SFColor name FLOAT FLOAT FLOAT)
(field SFFloat name FLOAT)
(field SFImage name INTEGER INTEGER (INTEGER)*)
(field SFInt32 name INTEGER)
(field SFNode name SYMBOL)
(field SFRotation name FLOAT FLOAT FLOAT FLOAT)
(field SFString name STRING)
(field SFTime name FLOAT)
(field SFVec2f name FLOAT FLOAT)
(field SFVec3f name FLOAT FLOAT FLOAT)

MFFields have an additional slot num which is an INTEGER representing the number of elements in the MField

(field MFBool name num ([TRUE|FALSE])* )
(field MFColor name num (FLOAT FLOAT FLOAT)* )
(field MFFloat name num (FLOAT)* )
(field MFImage name num (INTEGER INTEGER INTEGER*)* )
(field MFInt32 name num (INTEGER)*)
(field SFNode name num (SYMBOL)* )
(field MFRotation name num (FLOAT FLOAT FLOAT FLOAT)* )
(field MFString name num (STRING)* )
(field MFTime name num (FLOAT)* )
(field MFVec2f name num (FLOAT FLOAT)* )
(field MFVec3f name num (FLOAT FLOAT FLOAT)* )


I could see no way of getting at the deftemplate used to assert a fact, which would have made the design cleaner, and got rid of ordered facts. Also, I'm only interested in the new facts that have been asserted. However there is again, no direct way of getting the last asserted fact. What I had to do was to check if the facts list had changed, and then go through all the facts and pick out the ones whose id was larger than the last time I checked for new facts.

Now, all that has to be done is to assert facts whenever an eventIn is received and send an eventOut if the corresponding fact is asserted. Since one cannot assert ordered facts using CreateFact, one has to use AssertString(). This is very tedious since I have to do all the string processing to convert from SYMBOL/STRING to float, integer, vector as the case may be. But, that is merely an implementation problem. Currently I've implemented only for the case of SFBool, SFFloat and SFTime types, as a proof of concept. Also, there is no LoadFromString() function in CLIPS (go figure!). To work around this I had to write the string (from url of the Script node) to a temporary file and Load from that file.

Examples

1. Using ROUTES we can ensure that a light is turned on when the pointer is over the geometry. But, if one wants to turn the light off while the pointer is over the geometry, one would have to use a script. This cannot be done by using only ROUTES. The script below in CLIPS simply negates the boolean event it receives. It has an eventIn "in" which is used to assert facts and an eventOut "out" which is sent out by asserted facts.

#VRML V2.0 utf8
    Shape {
      appearance Appearance {
        material Material { diffuseColor 1.0 0.0 0.0} }
      geometry Sphere { }
    }
    DEF onOffSwitch TouchSensor { } 
    DEF theLight PointLight { }
    DEF onOffScript Script {
      eventIn SFBool in
      eventOut SFBool out
      url "CLIPScript:
        (defrule changeToFALSE
          ?f <- (field SFBool in TRUE)
        =>
          (retract ?f)
          (assert (field SFBool out FALSE)))
 
        (defrule changeToTRUE
          ?f <- (field SFBool in FALSE)
        =>
          (retract ?f)
          (assert (field SFBool out TRUE)))
      "
    }
  ]
  ROUTE onOffSwitch.isOver TO onOffScript.in
  ROUTE onOffScript.out TO theLight.set_on


Whenever the pointer is moved over the geometry (not shown here), it generates an isOver "TRUE" event. This event is then asserted as a fact in CLIPS. The rule changeToFALSE the fires which asserts the fact (field SFBool out FALSE). This new fact is then retrieved by the browser, which then sends an eventOut "FALSE" event to the eventOut SFBool out.

2. Consider a lamp which one would like to turn on or off by clicking on it. Here one would need persistent data to remember the lamp's state in order to negate it. This data is represented by a field called state in our script. fields are automatically asserted as facts when initializing CLIPS.

#VRML V2.0 utf8
    Shape {
      appearance Appearance {
        material Material { diffuseColor 1.0 0.0 0.0} }
      geometry Sphere { }
    }
    DEF onOffSwitch TouchSensor { }
    DEF theLight DirectionalLight { }
    DEF onOffScript Script {
      eventIn SFTime time
      eventOut SFBool out
      field SFBool state TRUE
      url "CLIPScript:
        (defrule changeToFALSE
          ?f <- (field SFTime time ?t)
          ?g <- (field SFBool state TRUE)
        =>
          (retract ?f ?g)
          (assert (field SFBool state FALSE))
          (assert (field SFBool out FALSE)))

        (defrule changeToTRUE
          ?f <- (field SFTime time ?t)
          ?g <- (field SFBool state FALSE)
        =>
          (retract ?f ?g)
          (assert (field SFBool state TRUE))
          (assert (field SFBool out TRUE)))
      "
    }
    ROUTE onOffSwitch.touchTime TO onOffScript.time
    ROUTE onOffScript.out TO theLight.set_on


When the pointer clicks on the geometry, it sends out an SFTime event containing the current time stamp. This event, when asserted as a fact, fires either of the rules changeToFALSE or changeToTRUE depending on the value of state, which invert the state and sends it out as an eventOut.


Last modified April 30, 1997. Swami