next up previous
Next: Summary Up: VRML Previous: Geometry - Polygonal Description

VRML Objects and Events

VRML borrows lots of ideas for Objected oriented technologies:

Classes
-- A class is a template for a data structure and the things that act on it. You can't do anything with a class itself, but as soon as you instantiate it (create an instance of it) then you have an object. The way you access an object is through its methods. Generally the methods associated with an object let you read the value of a piece of data, change that value, and control some other behaviors. You can instantiate as many objects of a given class as you like. Practically speaking, every instance is unique and independent of all the other instances.

Let's look at the spec definition for Sphere in the VRML spec:

Sphere { 
  field SFFloat radius  1    # (0,infinity) 
}

It's been really useful to me to think of the definition in the spec as a definition of the Sphere class. This is distinct from the Java classes for nodes. But I want to make clear that the spec definition doesn't describe a specific object so much as it describes the class of Spheres, Cylinders, etc. VRML has 54 of these class definitions. It's legal inside a VRML file to create any (reasonable) number of instances of the Sphere class (that is, objects of the Sphere class), just as you can create any number of, say, Stacks in your favorite OO programming language.

Objects
--

How do you create a Sphere object? By typing "Sphere" in a VRML file and sending that file to a VRML browser.

In OO programming languages, you customarily give names to your objects:

<someType> MySphere = new Sphere;

You give objects names so that you can operate on them through their visible methods. Most of the time it would be pointless in a programming language to create an object that you didn't want to name, because if you just create an instance of a class and never do anything with it, usually nothing very interesting happens (unless you create a million instances and crash horribly).

VRML is different because it describes a scene. Sometimes simply placing the object in the scene is good enough. Of course, sometimes you do want to operate on the object through its methods, and in that case you can give the object a name:

 DEF MySphere Sphere{}

Parameters
--

The Sphere class has one visible method: the constructor. When you type " sphere{}" in a VRML file and send it to the browser, you invoke this method. The constructor method has one parameter: the radius. There's a default value for the radius parameter, and it's shown in the spec: 1. If you don't specify the parameter, the VRML browser will create an object with the default value for the radius parameter.

The spec also shows the legal values this parameter can take. So if you try to create a Sphere object with a negative radius, that's an error. Browsers may warn you of the error, or they may simply choose to ignore it and not render the erroneous sphere.

When you see in the spec that a class has a "field", as the Sphere class does, it means that the field is a parameter you can use when you create an instance of the class. But a "field" does not describe a method of the class. That is, once you've created the object and specified the parameter for the field, you can't ever change it again.

Let's start jotting down some of these ideas, and we'll call them heuristics (rules of thumb, statements that are only approximately true). Why only approximately true? Because, as opposed to rules, which come from the spec itself, these heuristics are ways of thinking about these elements; ways of thinking about them that I've found to be useful. So here's our first:

Heuristic 1 : A "field" is a parameter whose value you can set when you create an object. Once you create the object, you can't read or write that parameter again.

We'll see another use for fields that may refine our thinking about them when we get to Script nodes, but for now it's a pretty good rule of thumb.

Methods
--

Let's turn to a more interesting example:

Group { 
  eventIn      MFNode  addChildren
  eventIn      MFNode  removeChildren
  exposedField MFNode  children      []
  field SFVec3f bboxCenter    0 0 0     # (-,)
  field SFVec3f bboxSize      -1 -1 -1  # (0,) or -1,-1,-1
}

Group nodes have two fields -- parameters, useful only when you create an instance of the Group class.

Now you've got a couple of eventIns, and an exposedField. We'll get to the exposedField in a minute, but let's concentrate on the eventIns.

Heuristic 2: An "eventIn" is a method. It's the way you change an internal state or value of an object, and very often that change will be visible in the scene.

In a Group node, if you invoke the addChildren method, you'll generally change what the visitor to your virtual world sees.

So how do you invoke that method?

Variables and Events
You can change a VRML object through two mechanisms: one you're familiar with (variables) and one you probably aren't familiar with (events).

So let's talk about variables first.

VRML has no variables -- the paths between VRML objects are not simply dataflows or parameter passing like they're used to. It's better to have that shock now than after you've struggled with VRML events for several months like I did.

So the only way VRML objects can communicate with one another is through events. What's an event?

Heuristic 3: An "event" consists of:

The value is a dataflow you're used to. Like a dataflow (the argument of a function) in a conventional programming language, the value is of a given type. For example, in the Group node, events in and out are of type MFNode. Just as in any other language, you can't have type mismatches in VRML. If an object is expecting an SFColor and gets an SFRotation, what would you expect it to do? So basically, the rule says that your dataflows have to make sense.

Rule 1: Dataflows have to make sense:

This is a rule, rather than a heuristic, because I don't care whether you think of it that way or not: if you violate the rule, the VRML browser or syntax checker will slap you upside the head.

The timestamp part of an event is the time on the VRML clock that the event occurred. We'll talk about the VRML clock in more detail later, but right now if you think of it as a wall clock, you won't be far wrong. You'll see some uses for the timestamp behind the scenes if you read the VRML spec section on event processing (Clause 4.10), but in the few cases where you use the timestamp, you'll use it in Scripts for finer control of time-dependent behaviors.

Now to the last part of the event: the action request. Suppose you've got an event that you're sending to the addChildren eventIn (method) of a Group object. Whenever the value of the event changes, the method will get called. It's as though you had a variable in C or FORTRAN or Ada and that variable was being passed to a function. If these other programming languages worked like VRML, every time you changed the value of that variable, the function would be called automatically. It works a lot like a mailbox interrupt or an address that's mapped to a port: write to it and it does something.

Now let's turn to the other thing our Group node has that our Sphere node doesn't:

exposedFields.

ExposedFields

Whenever you see an exposedField in the spec definition of a class, that's shorthand for three different things:

Let's ignore the fields and eventIns in our Group node and pretend it has only the one exposedField:

SimplerGroup {
  exposedField MFNode  children      []
}

That's exactly equivalent to saying:

SimplerGroup {
  field    MFNode  children      []
  eventIn  MFNode  set_children
  eventOut MFNode  children_changed
}

These, in fact, are the names you use to refer to the field, the eventIn and the eventOut.

Heuristic 4: An "exposedField" is a field (remember Heuristic 1), an eventIn, and an eventOut. Nothing more. Nothing less.

Objects of the SimplerGroup node class, then, have one field (used when you create the object), and one method: set_children. What's children_changed? We'll get to that next.

EventOuts

Heuristic 5: An "eventOut" is the place an event comes from.

For most VRML objects, an event is generated in only two cases:

Initially. Every eventOut of every VRML object that has eventOuts sends out an initial event: Ordinary scene objects generate an event, as soon as the object joins the scene graph. Objects that react to the environment, like ProximitySensors, first detect whether or not the visitor to the VRML world is in range, and then set the value of the event. Script objects can have an initialize() method. If they do, the initialize() method may generate an event.

Not every VRML object has eventOuts; for example, a Sphere node doesn't.

And don't forget

Heuristic 4 - there's an eventOut for every exposedField, too. As we'll see shortly, most of these events fall into the bit bucket and never do anything. We'll mostly ignore initial events for now.

After the object changes. Most ordinary scene objects don't ever change, so they only ever generate (at most) one event. But some objects (sensors) can react to things the visitor does, and one of these sensors, the TimeSensor, reacts to the time on the VRML clock.

So now we can see what the three things in our SimplerGroup object are:

SimplerGroup { 
  field    MFNode children   # a parameter used to initialize the object 
  eventIn  MFNode set_children      # a method used to change the object 
  eventOut MFNode children_changed  # an event, generated when the object changes  }

So eventOuts are the faucets that events come out of. Where do events go? Well, the names probably tipped you off already:eventOuts go to eventIns. That's where routing comes in.

Routing
--

Let's suppose we have two objects, each with a Material node. Let's write down the spec definition for the Material node class:

Material { 
  exposedField SFFloat ambientIntensity  0.2  # [0,1]
  exposedField SFColor diffuseColor      0.8 0.8 0.8 # [0,1]
  exposedField SFColor emissiveColor     0 0 0# [0,1]
  exposedField SFFloat shininess  0.2  # [0,1]
  exposedField SFColor specularColor     0 0 0# [0,1]
  exposedField SFFloat transparency      0    # [0,1]
}

Let's imagine that we have two spheres. Let's also imagine that we have some way to change the first sphere, and whenever we change that color we also want the color of the second sphere to change. The first thing we need to do is give each of the Material objects associated with those spheres a name:

DEF Material_1 Material { diffuseColor 0 0 1 }  
DEF Material_2 Material { diffuseColor 0 01 }

(We're ignoring where these statements would live in the actual VRML file and the fact that it may sometimes be more convenient to name the Shape nodes instead).

Now you use a ROUTE statement from Material_1's eventOut to Material_2's eventIn:

ROUTE Material_1.diffuseColor_changed TO Material_2.set_diffuseColor

That's it. Remember we said that whenever the value changes, you'll get an eventOut. Whenever the diffuseColor in Material_1 changes, the eventOut will activate the eventIn on Material_2 and the diffuseColor in Material_2 will be set to match.

Heuristic 6: When you use a route to connect an eventOut with an eventIn:

Let's be a little more precise on that "whenever the value changes". If you change the value of the emissiveColor, you'll get an event from emissiveColor_changed, but you won't get an event from diffuseColor_changed or any of the other eventOuts. Only the event corresponding to the change will be generated.

Rule 2: Whenever the part of an object's internal state corresponding to an eventOut changes, the object generates an event.

E.g., if an object's diffuseColor changes, it will generate a diffuseColor_changed event (but it won't generate any other kind of event).

Interpolators
--

We started off assuming that there was some way to change the diffuseColor of Material_1 so that when that color changed it could be sent to Material_2. How would we do that?

One way is from an interpolator. There's a ColorInterpolator node (class), and if you look at the spec, you see that it has

ColorInterpolator { 
  eventIn      SFFloat set_fraction # (-inf,inf) 
  exposedField MFFloat key    []    # (-inf,omf) 
  exposedField MFColor keyValue      []    # [0,1] 
  eventOut     SFColor value_changed 
}

ColorInterpolator objects have three eventOuts:

eventOut MFFloat key_changed 
eventOut MFFloat keyValue_changed 
eventOut SFColor value_changed

Which one would you route to the eventIn on our Material node's eventIn?

eventIn  SFColor set_diffuseColor

Imagine that you haven't got a copy of the spec handy, because the spec describes exactly what all these fields, methods, and events are for.

Remember Rule 1, that if a method (eventIn) is expecting an event of a given type, it had better get an event of that type? Well let's apply that rule backwards. What eventOut of the ColorInterpolator will make the set_diffuseColor eventIn happy?

Just one: value_changed. So the ROUTE statement from the interpolator (which we'll name MyColorInterpolator) to the material (which we already named Material_1) is:

ROUTE MyColorInterpolator.value_changed TO Material_1.set_diffuseColor

Two things:

There's only one more step. Set up some kind of sensor. Unlike ordinary scene objects and interpolators, sensors can change because of some action by the visitor or the running of the VRML clock. Let's make it a TimeSensor which we'll call MyTimeSensor. And that ROUTE statement will look like this:

ROUTE MyTimeSensor.fraction_changed TO MyColorInterpolator.set_fraction

Let's take a look at the three routes together:

ROUTE MyTimeSensor.fraction_changed 
      TO MyColorInterpolator.set_fraction  
ROUTE MyColorInterpolator.value_changed 
      TO Material_1.set_diffuseColor  
ROUTE Material_1.diffuseColor_changed 
      TO Material_2.set_diffuseColor

Something in MyTimeSensor changes (the value of the clock, which is running) causing it to send an event to MyColorInterpolator. MyColorInterpolator responds by doing the interpolation and sending an event that changes the diffuseColor of Material_1. Material_1 reacts to its diffuseColor being changed by sending an event to Material_2. And finally, Material_2 reacts to the event by changing its diffuseColor.

That's called an event cascade.

So one little bit of tidying up and we're done with this section. We said that events consist of dataflows, timestamps, and action requests. The dataflows in this cascade are pretty straightforward. Looking back to the spec, MyTimeSensor sends an SFFloat to MyColorInterpolato, which sends an SFColor to Material_1, and Material_1 sends an SFColor to Material_2.

Similarly the action requests are spelled out in the ROUTE statements of the cascade: MyTimeSensor is routed so that when the clock changes, it causes the set_fraction method of MyColorInterpolator to execute. That in turn causes the set_diffuseColor method of Material_1 to execute, which causes the set_diffuseColor method of Material_2 to execute.

But what about the timestamp?

Rule 3: All events in a cascade have the same timestamp.

That means that you can think of an event cascade as occurring in order all at the same time. Yes, that's a contradiction. The spec has just gobs of stuff to say about timestamps and event ordering, but most of the words in the spec deal with unusual and even degenerate cases. Since (by the time we're done with you) you're going the be the kind of VRML world builder who never writes degenerate code or routes, you can simply ignore all that difficulty.

Heuristic 7: All the events in a cascade happen pretty much in the order you specify them. If you write such a tangled series of routes that you can't understand what that order is, you're probably headed for trouble.


next up previous
Next: Summary Up: VRML Previous: Geometry - Polygonal Description
Dave Marshall
10/4/2001