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(); } } }