Moving Worlds Examples

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.


Transforms and Leaves

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 1.0

#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 2.0

#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 { }
                        }
                    ]
                }
            ]
        }
    ]
}

Prototypes and Alternate Representations

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.


Text in Other Languages

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

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.


Robot

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

A Better WWWAnchor

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

Chopper

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

Guided Tour

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

Elevator

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