Last modified: January 16, 1996. This document can be found at http://webspace.sgi.com/moving-worlds/Examples/examples.html
This document contains examples to clarify various aspects of the Moving Worlds proposal.
This example has 2 parts. First is an example of a simple VRML 1.0 scene. It contains a red cone, a blue sphere, and a green cylinder with a hierarchical transformation structure. Next is the same example using the Moving Worlds Transforms and leaves syntax.
#VRML V1.0 ascii
Separator {
Transform {
translation 0 2 0
}
Material {
diffuseColor 1 0 0
}
Cone { }
Separator {
Transform {
scaleFactor 2 2 2
}
Material {
diffuseColor 0 0 1
}
Sphere { }
Transform {
translation 2 0 0
}
Material {
diffuseColor 0 1 0
}
Cylinder { }
}
}
#VRML V2.0 ascii
Transform {
tranlation 0 2 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 1 0 0
}
}
geometry Cone { }
},
Transform {
scaleFactor 2 2 2
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0 0 1
}
}
geometry Sphere { }
},
Transform {
translation 2 0 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0 1 0
}
}
geometry Cylinder { }
}
]
}
]
}
]
}
Moving Worlds has the capability to define new nodes. VRML 1.0 had the ability to add nodes using the fields field and isA keyword. The prototype feature can duplicate all the features of the 1.0 node definition capabilities, as well as the alternate representation feature proposed in the VRML 1.1 draft spec. Take the example of a RefractiveMaterial. This is just like a Material node but adds an indexOfRefraction field. This field can be ignored if the browser cannot render refraction. In VRML 1.0 this would be written like this:
...
RefractiveMaterial {
fields [ SFColor ambientColor, MFColor diffuseColor,
SFColor specularColor, MFColor emissiveColor,
SFFloat shininess, MFFloat transparency,
SFFloat indexOfRefraction, MFString isA ]
isA "Material"
}
If the browser had been hardcoded to understand a RefractiveMaterial the indexOfRefraction would be used, otherwise it would be ignored and RefractiveMaterial would behave just like a Material node.
In VRML 2.0 this is written like this:
...
PROTO RefractiveMaterial [
field SFColor ambientColor 0 0 0
field MFColor diffuseColor 0.5 0.5 0.5
field SFColor specularColor 0 0 0
field MFColor emissiveColor 0 0 0
field SFFloat shininess 0
field MFFloat transparency 0 0 0
field SFFloat indexOfRefraction 0.1 ]
{
Material {
ambientColor IS ambientColor
diffuseColor IS diffuseColor
specularColor IS specularColor
emissiveColor IS emissiveColor
shininess IS shininess
transparency IS transparency
}
}
While this is more wordy, notice that the default values were given in the prototype. These are different than the defaults for the standard Material. So this allows you to change defaults on a standard node. The EXTERNPROTO capability allows the use of alternative implementations of a node:
...
EXTERNPROTO RefractiveMaterial [
field SFColor ambientColor 0 0 0
field MFColor diffuseColor 0.5 0.5 0.5
field SFColor specularColor 0 0 0
field MFColor emissiveColor 0 0 0
field SFFloat shininess 0
field MFFloat transparency 0 0 0
field SFFloat indexOfRefraction 0.1 ]
http://www.myCompany.com/vrmlNodes/RefractiveMaterial.wrl,
http://somewhere.else/MyRefractiveMaterial.wrl
This will choose from one of three possible sources of RefractiveMaterial. If the browser has this node hardcoded, it will be used. Otherwise the first URL will be requested and a prototype of the node will used from there. If that fails, the second will be tried.
Moving Worlds has a new Text node which allows the use of UTF8 characters to display text in any language. For a few languages (like Chinese) a language field is required to give a full specification of the character set to use. Because this field is part of the Text node, the Chinese language would have to be set in every Text block in order for Chinese to be used throughout the file. The prototype feature solves this problem by allowing a custom ChineseText node to be defined.
PROTO ChineseText [ field MSFString string "" ]
{
Text {
language "ch"
direction TBRL
string IS string
}
}
note also that the default direction is set to be top-to-tottom for each string and right-to-left for consecutive strings, a common format for Chinese text.
Shuttles and pendulums are great building blocks for composing interesting animations. This shuttle translates its children back and forth along the X axis, from -1 to 1. The pendulum rotates its children about the Y axis, from 0 to 3.14159 radians and back again.
PROTO Shuttle [
field SFFloat rate 1
eventIn SFBool moveRight
eventOut SFBool isAtLeft
field MFNode children ]
{
DEF F Transform { children IS children }
DEF T TimeSensor { cycleCount = -1 cycleInterval IS rate }
DEF S Script {
field SFBool right TRUE
eventIn SFBool moveRight IS moveRight
eventIn SFBool isActive
eventOut SFBool isAtLeft IS isAtLeft
eventOut SFBool up
eventOut SFBool down
eventOut SFTime start
eventOut SFInt32 resetCount
behavior "shuttle.java"
}
DEF I PositionInterpolator {
keys [ 0, 1 ]
values [ -1 0 0, 1 0 0 ]
}
ROUTE T.fraction TO I.set_fraction
ROUTE I.outValue TO F.set_translation
ROUTE T.isActive TO S.isActive
ROUTE S.resetCount TO T.cycleCount
}
shuttle.java
------------
import "vrml.*"
class Shuttle extends Script {
SFBool right = (SFBool) getField("right");
SFBool isAtLeft = (SFBool) getEventOut("isAtLeft");
SFBool up = (SFBool) getEventOut("up");
SFBool down = (SFBool) getEventOut("down");
SFTime start = (SFTime) getEventOut("start");
SFInt32 resetCount = (SFInt32) getEventOut("resetCount");
public void moveRight(ConstSFBool value, SFTime ts) {
if (value.getValue()) {
// want to move Right
up.setValue(TRUE);
down.setValue(FALSE);
start.setValue(ts.getValue());
}
else {
// want to move Left
up.setValue(FALSE);
down.setValue(TRUE);
start.setValue(ts.getValue());
}
}
public void isActive(SFBool value, SFTime ts) {
// if this is false (transitioned from active to inactive)
// we can send our isAtLeft event
if (!value.getValue()) {
right.setValue(!right.getValue());
isAtLeft.setValue(!right.getValue());
resetCount.setValue(1); // stop TimerSensor
}
}
}
PROTO Pendulum [
field SFFloat rate 1
eventIn SFBool moveCW
eventOut SFBool isAtCCW
field MFNode children ]
{
DEF F Transform { children IS children }
DEF T TimeSensor { cycleCount = -1 cycleInterval IS rate }
DEF S Script {
field SFBool CW TRUE
eventIn SFBool moveCW IS moveCW
eventIn SFBool isActive
eventOut SFBool isAtCCW IS isAtCCW
eventOut SFBool up
eventOut SFBool down
eventOut SFTime start
eventOut SFInt32 resetCount
behavior "pendulum.java"
}
DEF I RotationInterpolator {
keys [ 0, 1 ]
values [ 0 1 0 0, 0 1 0 3.14159 ]
}
ROUTE T.fraction TO I.set_fraction
ROUTE I.outValue TO F.set_rotation
ROUTE T.isActive TO S.isActive
ROUTE S.resetCount TO T.cycleCount
}
pendulum.java
------------
import "vrml.*"
class Pendulum extends Script {
SFBool CW = (SFBool) getField("CW");
SFBool isAtCCW = (SFBool) getEventOut("isAtCCW");
SFBool up = (SFBool) getEventOut("up");
SFBool down = (SFBool) getEventOut("down");
SFTime start = (SFTime) getEventOut("start");
SFInt32 resetCount = (SFInt32) getEventOut("resetCount");
public void moveCW(ConstSFBool value, SFTime ts) {
if (value.getValue()) {
// want to move CW
up.setValue(TRUE);
down.setValue(FALSE);
start.setValue(ts.getValue());
}
else {
// want to move CCW
up.setValue(FALSE);
down.setValue(TRUE);
start.setValue(ts.getValue());
}
}
public void isActive(SFBool value, SFTime ts) {
// if this is false (transitioned from active to inactive)
// we can send our isAtCCW event
if (!value.getValue()) {
CW.setValue(!CW.getValue());
isAtCCW.setValue(!CW.getValue());
resetCount.setValue(1); // stop TimerSensor
}
}
}
In use, the Shuttle can have its isAtRight output wired to its moveLeft input to give a continuous shuttle. The Pendulum can have its isAtCCW output wired to its moveCW input to give a continuous Pendulum effect. Note the initial value of TimeSensor.cycleCount is -1. This causes the TimeSensor to start immediately. CycleCount is set to 1 after the first cycle to take control of the TimeSensor.
Robots are very popular in in VRML discussion groups. Here's a simple implementation of one. This robot has very simple body parts: a cube for his head, a sphere for his body and cylinders for arms (he hovers so he has no feet!). He is something of a sentry - he walks forward, turns around, and walks back, forever. This makes liberal use of the Shuttle and Pendulum above.
DEF Walk Shuttle {
rate 10
children [
DEF Turn Pendulum {
children [
# The Robot
Shape {
geometry Cube { } # head
},
Transform {
scaleFactor 1 5 1
translation 0 -5 0
children [ Shape { geometry Sphere { } } ] # body
},
DEF Arm Pendulum {
children [
Transform {
scaleFactor 1 7 1
translation 1 -5 0
children [
Shape { geometry Cylinder { } }
]
}
]
},
# duplicate arm on other side and flip so it swings
# in opposition
Transform {
rotation 0 1 0 3.14159
translation 10 0 0
children [ USE Arm ]
}
]
}
]
}
# hook up the sentry. The arms will swing infinitely. He walks
# along the shuttle path, then turns, then walks back, etc.
ROUTE Arm.isAtCCW TO Arm.moveCW
ROUTE Walk.isAtLeft TO Turn.moveCW
ROUTE Turn.isAtCCW TO Walk.moveRight
The Moving Worlds definition of WWWAnchor does not have the map field from VRML 1.0. This is because this field was of limited value. The 1.0 map field tried to duplicate the imagemap facility of HTML. But what was really needed was the texture coordinate. Well Moving Worlds can fix this with this PROTO for a better WWWAnchor. This also adds the target field which has been so popular lately.
PROTO TextureAnchor [
field SFString name ""
field SFString target ""
field MFNode children [ ]
{
Group {
children [
DEF CS ClickSensor { },
Group {
children IS children
}
]
}
DEF S Script {
field SFString name IS name
field SFString target IS target
eventIn SFVec2f hitTexture
behavior "TextureAnchor.java"
}
ROUTE CS.hitTexture TO S.hitTexture
}
TextureAnchor.java
------------------
import "vrml.*"
class TextureAnchor extends Script {
SFString name = (SFString) getField("name");
SFString target = (SFString) getField("target");
public void hitTexture(ConstSFVec2f value, SFTime ts) {
// construct the string
String str;
sprintf(str, "%s?%g,%g target=%s",
name.getValue(),
value.getValue()[0],
value.getValue()[1],
target.getValue());
Browser.loadURL(str);
}
}
Here is a simple example of how to do simple animation triggered by a click sensor. It uses an EXTERNPROTO to include a Rotor node from the net which will do the actual animation.
EXTERNPROTO Rotor [
eventIn MFFloat Spin
field MFNode children ]
"http://somewhere/Rotor.wrl" # Where to look for implementation
PROTO Chopper [
field SFFloat maxAltitude 30
field SFFloat rotorSpeed 1 ]
{
Group {
children [
DEF CLICK ClickSensor { }, # Gotta get click events
Shape { ... body... },
DEF Top Rotor { ... geometry ... },
DEF Back Rotor { ... geometry ... }
]
}
DEF SCRIPT Script {
eventIn SFBool startOrStopEngines
field maxAltitude IS maxAltitude
field rotorSpeed IS rotorSpeed
field SFNode topRotor USE Top
field SFNode backRotor USE Back
scriptType "java"
behavior "chopper.java"
}
ROUTE CLICK.isActive -> SCRIPT.startOrStopEngines
}
DEF MyScene Group {
DEF MikesChopper Chopper { maxAltitude 40 }
}
chopper.java:
-------------
import "vrml.*"
public class Chopper extends Script {
SFNode TopRotor = (SFNode) getField("topRotor");
SFNode BackRotor = (SFNode) getField("backRotor");
float fRotorSpeed = ((SFFloat) getField("rotorSpeed")).getValue();
boolean bEngineStarted = FALSE;
public void startOrStopEngines(ConstSFBool value, SFTime ts) {
boolean val = value.getValue();
// Don't do anything on mouse-down:
if (val == FALSE) return;
// Otherwise, start or stop engines:
if (bEngineStarted == FALSE) {
StartEngine();
}
else {
StopEngine();
}
}
public void SpinRotors(fInRotorSpeed, fSeconds) {
MFFloat rotorParams;
float[] rp = rotorParams.getValue();
rp[0] = 0;
rp[1] = fInRotorSpeed;
rp[2] = 0;
rp[3] = fSeconds;
TopRotor.postEventIn("Spin", rotorParams);
rp[0] = fInRotorSpeed;
rp[1] = 0;
rp[2] = 0;
rp[3] = fSeconds;
BackRotor.postEventIn("Spin", rotorParams);
}
public void StartEngine() {
// Sound could be done either by controlling a PointSound node
// (put into another SFNode field) OR by adding/removing a
// PointSound from the Separator (in which case the Separator
// would need to be passed in an SFNode field).
SpinRotors(fRotorSpeed, 3);
bEngineStarted = TRUE;
}
public void StopEngine() {
SpinRotors(0, 6);
bEngineStarted = FALSE;
}
}
Moving Worlds has great facilities to put the viewer's camera under control of a script. This is useful for things such as guided tours, merry-go-round rides, and transportation devices such as busses and elevators. These next 2 examples show a couple of ways to use this feature.
The first example is a simple guided tour through the world. Upon entry, a guide orb hovers in front of you. Click on this and your tour through the world begins. The orb follows you around on your tour. Perhaps a PointSound node can be embedded inside to point out the sights.
Group {
children [
<geometry for the world>,
DEF GuideTransform Transform {
children [
DEF TourGuide Viewpoint { },
DEF StartTour ClickSensor { },
Shape { geometry Sphere { } }, # the guide orb
]
}
]
}
DEF GuidePI PositionInterpolator {
keys [ ... ]
values [ ... ]
}
DEF GuideRI RotationInterpolator {
keys [ ... ]
values [ ... ]
}
DEF TS TimeSensor { cycleInterval 60 } # 60 second tour
DEF S Script {
field SFNode viewpoint USE TourGuide
eventIn SFBool active
eventIn SFBool done
eventOut SFTime start
behavior "GuidedTour.java"
}
ROUTE StartTour.isActive TO S.active
ROUTE S.start TO TS.startTime
ROUTE TS.isActive TO S.done
ROUTE TS.fraction TO GuidePI.set_fraction
ROUTE TS.fraction TO GuideRI.set_fraction
ROUTE GuidePI.outValue TO GuideTransform.set_translation
ROUTE GuideRI.outValue TO GuideTransform.set_rotation
GuidedTour.java:
-------------
import "vrml.*"
public class GuidedTour extends Script {
SFTime start = (SFTime) getEventOut("start");
SFNode viewpoint = (SFNode) getField("viewpoint");
public void active(ConstSFBool value, SFTime ts) {
if (value.getValue()) { // start tour
Browser.bindViewpoint(viewpoint.getValue());
start.setValue(ts.getValue());
}
}
public void done(ConstSFBool value, SFTime ts) {
if (!value.getValue()) { // end tour
Browser.unbindViewpoint();
}
}
}
Here's another example of animating the camera. This time it's an elevator to ease access to a multistory building. For this example I'll just show a 2 story building and I'll assume that the elevator is already at the ground floor. To go up you just step inside. A BoxProximitySensor fires and starts the elevator up automatically. I'll leave call buttons for outside the elevator, elevator doors and floor selector buttons as an exercise for the reader!
Group {
children [
DEF ETransform Transform {
children [
DEF EViewpoint Viewpoint { },
DEF EProximity BoxProximitySensor { size 2 2 2 },
<geometry for the elevator,
a unit cube about the origin with a doorway>,
]
}
]
}
DEF ElevatorPI PositionInterpolator {
keys [ 0, 1 ]
values [ 0 0 0, 0 4 0 ] # a floor is 4 meters high
}
DEF TS TimeSensor { cycleInterval 10 } # 10 second travel time
DEF S Script {
field SFNode viewpoint USE EViewpoint
eventIn SFBool active
eventIn SFBool done
eventOut SFTime start
behavior "Elevator.java"
}
ROUTE EProximity.isActive TO S.active
ROUTE S.start TO TS.startTime
ROUTE TS.isActive TO S.done
ROUTE TS.fraction TO ElevatorPI.set_fraction
ROUTE ElevatorPI.outValue TO ETransform.set_translation
Elevator.java:
-------------
import "vrml.*"
public class Elevator extends Script {
SFTime start = (SFTime) getEventOut("start");
SFNode viewpoint = (SFNode) getField("viewpoint");
public void active(ConstSFBool value, SFTime ts) {
if (value.getValue()) { // start elevator
Browser.bindViewpoint(viewpoint.getValue());
start.setValue(ts.getValue());
}
}
public void done(ConstSFBool value, SFTime ts) {
if (!value.getValue()) { // end tour
Browser.unbindViewpoint();
}
}
}