The Annotated VRML 97 Reference

1 Intro     Concepts     3 Nodes     4 Fields/Events    Conformance
A Grammar     B Java     C JavaScript     D Examples     E Related Info    References
Quick Java         Quick JavaScript         Quick Nodes   
 

  About the Book
  
Help
  Copyright © 1997-99
  Purchase the book from Amazon.com

Chapter 3:
Node Reference


Intro
Anchor
Appearance
AudioClip
Background
Billboard
Box
Collision
Color
ColorInterpolator
Cone
Coordinate
CoordinateInterpolator
Cylinder
CylinderSensor
DirectionalLight
ElevationGrid
Extrusion
Fog
FontStyle
Group
ImageTexture
IndexedFaceSet
IndexedLineSet
Inline
LOD
Material
MovieTexture
NavigationInfo
Normal
NormalInterpolator
OrientationInterpolator
PixelTexture
PlaneSensor
PointLight
PointSet
PositionInterpolator
ProximitySensor
ScalarInterpolator
Script
Shape
Sound
Sphere
SphereSensor
SpotLight
Switch
Text
TextureCoordinate
TextureTransform
TimeSensor
TouchSensor
Transform
Viewpoint
VisibilitySensor
WorldInfo

+3.53 Viewpoint

Viewpoint { 
  eventIn      SFBool     set_bind
  exposedField SFFloat    fieldOfView    0.785398  # (0,PI)
  exposedField SFBool     jump           TRUE
  exposedField SFRotation orientation    0 0 1 0   # [-1,1],(-INF,INF)
  exposedField SFVec3f    position       0 0 10    # (-INF,INF)
  field        SFString   description    ""
  eventOut     SFTime     bindTime
  eventOut     SFBool     isBound
}

The Viewpoint node defines a specific location in the local coordinate system from which the user may view the scene. Viewpoint nodes are bindable children nodes (see "2.6.10 Bindable children nodes") and thus there exists a Viewpoint node stack in the browser in which the top-most Viewpoint node on the stack is the currently active Viewpoint node. If a TRUE value is sent to the set_bind eventIn of a Viewpoint node, it is moved to the top of the Viewpoint node stack and activated. When a Viewpoint node is at the top of the stack, the user's view is conceptually re-parented as a child of the Viewpoint node. All subsequent changes to the Viewpoint node's coordinate system change the user's view (e.g., changes to any ancestor transformation nodes or to the Viewpoint node's position or orientation fields). Sending a set_bind FALSE event removes the Viewpoint node from the stack and produces isBound FALSE and bindTime events. If the popped Viewpoint node is at the top of the viewpoint stack, the user's view is re-parented to the next entry in the stack. More details on binding stacks can be found in "2.6.10 Bindable children nodes." When a Viewpoint node is moved to the top of the stack, the existing top of stack Viewpoint node sends an isBound FALSE event and is pushed down the stack.

TIP: If you want to control completely how the viewer may move through your world, bind to a NavigationInfo node that has its type field set to NONE. When the navigation type is NONE, browsers should remove all out-of-scene navigation controls and not allow the user to move away from the currently bound Viewpoint.

TECHNICAL NOTE: Viewpoints follow the binding stack paradigm because they are like a global property—the viewer's position and orientation are determined by, at most, one Viewpoint at a time.

It may seem strange that the viewer's position or orientation is changed by binding to and then modifying a Viewpoint, but a completely different node (ProximitySensor) is used to determine the viewer's current position or orientation. This asymmetry makes sense, because reporting the viewer's position and orientation is not like a global property. The viewer's position and orientation can be reported in any local coordinate system, and there may be multiple Scripts tracking the movements of the viewer at the same time.


An author can automatically move the user's view through the world by binding the user to a Viewpoint node and then animating either the Viewpoint node or the transformations above it. Browsers shall allow the user view to be navigated relative to the coordinate system defined by the Viewpoint node (and the transformations above it) even if the Viewpoint node or its ancestors' transformations are being animated.

The bindTime eventOut sends the time at which the Viewpoint node is bound or unbound. This can happen:

  1. during loading
  2. when a set_bind event is sent to the Viewpoint node
  3. when the browser binds to the Viewpoint node through its user interface described below

The position and orientation fields of the Viewpoint node specify relative locations in the local coordinate system. Position is relative to the coordinate system's origin (0,0,0), while orientation specifies a rotation relative to the default orientation. In the default position and orientation, the user is on the Z-axis looking down the -Z-axis toward the origin with +X to the right and +Y straight up. Viewpoint nodes are affected by the transformation hierarchy.

Navigation types (see "3.29 NavigationInfo") that require a definition of a down vector (e.g., terrain following) shall use the negative Y-axis of the coordinate system of the currently bound Viewpoint node. Likewise navigation types that require a definition of an up vector shall use the positive Y-axis of the coordinate system of the currently bound Viewpoint node. The orientation field of the Viewpoint node does not affect the definition of the down or up vectors. This allows the author to separate the viewing direction from the gravity direction.

TIP: The distinction between the gravity direction (which way is down) and the Viewpoint's orientation (which way the user happens to be looking) allows you to create interesting effects, but also requires you to be careful when animating Viewpoint orientations. For example, if you create an animation that moves the viewer halfway up a mountain, with the final orientation looking up toward the top of the mountain, you should animate the fields of the Viewpoint and not a Transform node above the Viewpoint. If you do animate the coordinate system of the Viewpoint (a Transform node above it), then you are changing the down direction, and if the user happens to step off a bridge over a chasm you placed on the mountain, they will fall in the wrong direction. Note that all of this is assuming that the VRML browser being used implements terrain following--keeping users on the ground as they move around the world. Although not required by the VRML specification, it is expected that most VRML browsers will support terrain following because it makes moving through a 3D world so much easier.

The jump field specifies whether the user's view "jumps" to the position and orientation of a bound Viewpoint node or remains unchanged. This jump is instantaneous and discontinuous in that no collisions are performed and no ProximitySensor nodes are checked in between the starting and ending jump points. If the user's position before the jump is inside a ProximitySensor the exitTime of that sensor shall send the same timestamp as the bind eventIn. Similarly, if the user's position after the jump is inside a ProximitySensor the enterTime of that sensor shall send the same timestamp as the bind eventIn. Regardless of the value of jump at bind time, the relative viewing transformation between the user's view and the current Viewpoint node shall be stored with the current Viewpoint node for later use when un-jumping (i.e., popping the the Viewpoint node binding stack from a Viewpoint node with jump TRUE). The following summarizes the the bind stack rules (described in "2.6.10 Bindable children nodes") with additional rules regarding Viewpoint nodes (displayed in boldface type):

  1. During read, the first encountered Viewpoint node is bound by pushing it to the top of the Viewpoint node stack. Nodes contained within Inlines, within the strings passed to the Browser.createVrmlFromString() method, or within files passed to the Browser.createVrmlFromURL() method (see "2.12.10 Browser script interface")are not candidates for the first encountered Viewpoint node. The first node within a prototype instance is a valid candidate for the first encountered Viewpoint node. The first encountered Viewpoint node sends an isBound TRUE event.
  2. When a set_bind TRUE event is received by a Viewpoint node,
    1. if it is not on the top of the stack: The relative transformation from the current top of stack Viewpoint node to the user's view is stored with the current top of stack Viewpoint node. The current top of stack node sends an isBound FALSE event. The new node is moved to the top of the stack and becomes the currently bound Viewpoint node. The new Viewpoint node (top of stack) sends an isBound TRUE event. If jump is TRUE for the new Viewpoint node, the user's view is instantaneously "jumped" to match the values in the position and orientation fields of the new Viewpoint node.
    2. If the node is already at the top of the stack, this event has no affect.
  3. When a set_bind FALSE event is received by a Viewpoint node in the stack, it is removed from the stack. If it was on the top of the stack,
    1. it sends an isBound FALSE event,
    2. the next node in the stack becomes the currently bound Viewpoint node (i.e., pop) and issues an isBound TRUE event,
    3. if its jump field value is TRUE, the user's view is instantaneously "jumped" to the position and orientation of the next Viewpoint node in the stack with the stored relative transformation of this next Viewpoint node applied.
  4. If a set_bind FALSE event is received by a node not in the stack, the event is ignored and isBound events are not sent.
  5. When a node replaces another node at the top of the stack, the isBound TRUE and FALSE events from the two nodes are sent simultaneously (i.e., with identical timestamps).
  6. If a bound node is deleted, it behaves as if it received a set_bind FALSE event (see c.).

The jump field may change after a Viewpoint node is bound. The rules described above still apply. If jump was TRUE when the Viewpoint node is bound, but changed to FALSE before the set_bind FALSE is sent, the Viewpoint node does not un-jump during unbind. If jump was FALSE when the Viewpoint node is bound, but changed to TRUE before the set_bind FALSE is sent, the Viewpoint node does perform the un-jump during unbind.

Note that there are two other mechanisms that result in the binding of a new Viewpoint:

  1. an Anchor node's url field specifies a "#ViewpointName"
  2. a script invokes the loadURL() method and the URL argument specifies a "#ViewpointName"

Both of these mechanisms override the jump field value of the specified Viewpoint node (#ViewpointName) and assume that jump is TRUE when binding to the new Viewpoint. The behavior of the viewer transition to the newly bound Viewpoint depends on the currently bound NavigationInfo node's type field value (see "3.29 NavigationInfo").

TIP: The rules for properly implementing the jump field are pretty complicated, but it is really a pretty simple concept. If jump is TRUE, then the viewer will be teleported to the Viewpoint as soon as the Viewpoint is bound (they will jump there instantaneously). If jump is FALSE, then the viewer will not appear to move at all when the Viewpoint is bound; the viewer will move only when the Viewpoint changes. Discontinuous movements can be disorienting for the user. It is as if they are suddenly blindfolded, taken to another place, and unblindfolded. It is generally better to animate viewers to a new location smoothly so that they have a sense of where they are relative to where they were. Combining a Viewpoint with its jump field set to FALSE with a ProximitySensor to detect the user's position and orientation, and some interpolators to move the viewer smoothly to a new position and orientation, is a good technique. For example, here is a prototype that will smoothly move the user to a new position and orientation at a given startTime:

     #VRML V2.0 utf8
     PROTO SmoothMove [
       exposedField SFTime howLong 1
       eventIn SFTime set_startTime
       field SFVec3f newPosition 0 0 0
       field SFRotation newOrientation 0 0 1 0 ]
     {
       # Group is just a convenient way of holding the
       #necessary ProximitySensor and Viewpoint:
       Group {
         children [
           # ProximitySensor to detect where the user is.
           DEF PS ProximitySensor { size 1e10 1e10 1e10 }
           # This Viewpoint is bound when the TimeSensor
           # goes active, animated, and then unbound when
           # the TimeSensor finishes.
           DEF V Viewpoint { jump FALSE }
         ]
       }
       # TimeSensor drives the interpolators
       # to give smooth animation.
       DEF TS TimeSensor {
         set_startTime IS set_startTime
         cycleInterval IS howLong
       }
       DEF PI PositionInterpolator {
         key [ 0, 1 ]
         # These are dummy keyframes; real keyframes
         # set by Script node when the TimeSensor
         # becomes active.
         keyValue [ 0 0 0, 0 0 0 ]
       }
       DEF OI OrientationInterpolator {
         key [ 0, 1 ]
         keyValue [ 0 0 1 0,  0 0 1 0 ]
       }
       DEF S Script {
         field SFVec3f newPosition IS newPosition
         field SFRotation newOrientation IS newOrientation
         field SFNode p_sensor USE PS
         eventIn SFTime startNow
         eventOut MFVec3f positions
         eventOut MFRotation orientations
         url "javascript:
           function startNow() {
             // Starting: setup interpolators (direct
             // ROUTE from TimeSensor.isActive to
             // Viewpoint.set_bind binds the Viewpt for us)
  
             positions[0] = p_sensor.position_changed;
             positions[1] = newPosition;
             orientations[0] = p_sensor.orientation_changed;
             orientations[1] = newOrientation;
           }"
       }
       ROUTE TS.isActive TO V.set_bind
       ROUTE TS.fraction_changed TO PI.set_fraction
       ROUTE TS.fraction_changed TO OI.set_fraction
       ROUTE OI.value_changed TO V.set_orientation
       ROUTE TS.cycleTime TO S.startNow
       ROUTE S.positions TO PI.keyValue
       ROUTE S.orientations TO OI.keyValue
       ROUTE PI.value_changed TO V.set_position
     }
     # Example use: Touch cube, move to new
     # position/orientation
     Group {
       children [
         Shape {
           appearance Appearance { material
             Material { diffuseColor 0.8 0.2 0.4 }
           }
           geometry Box { }
         }
         DEF SM SmoothMove {
           howLong 5
           newPosition 0 0 10
           newOrientation 0 1 0  .001
         }
         DEF TS TouchSensor { }
       ]
     }
     ROUTE TS.touchTime TO SM.set_startTime

The fieldOfView field specifies a preferred minimum viewing angle from this viewpoint in radians. A small field-of-view roughly corresponds to a telephoto lens; a large field-of-view roughly corresponds to a wide-angle lens. The field-of-view shall be greater than zero and smaller than PI. The value of fieldOfView represents the minimum viewing angle in any direction axis perpendicular to the view. For example, a browser with a rectangular viewing projection shall have the following relationship:

      display width    tan(FOVhorizontal/2)
      -------------- = -----------------
      display height   tan(FOVvertical/2)

where the smaller of display width or display height determines which angle equals the fieldOfView (the larger angle is computed using the relationship described above). The larger angle shall not exceed PI and may force the smaller angle to be less than fieldOfView in order to sustain the aspect ratio.

TIP: A small field-of-view is like a telephoto lens on a camera and will make distant objects look bigger. Changing the field-of-view can give interesting effects, but browsers may choose to implement a fixed field-of-view for the very good reason that display devices such as head-mounted displays have an inherent field-of-view and overriding it can result in the user becoming disoriented or even sick. If you want to zoom in on some part of your world, it is better to move the Viewpoint closer rather than changing its field-of-view.

The description field specifies a textual description of the Viewpoint node. This may be used by browser-specific user interfaces. If a Viewpoint's description field is empty it is recommended that the browser not present this Viewpoint in its browser-specific user interface.

The URL syntax ".../scene.wrl#ViewpointName" specifies the user's initial view when loading "scene.wrl" to be the first Viewpoint node in the file that appears as DEF ViewpointName Viewpoint {...}. This overrides the first Viewpoint node in the file as the initial user view, and a set_bind TRUE message is sent to the Viewpoint node named "ViewpointName". If the Viewpoint node named "ViewpointName" is not found, the browser shall use the first Viewpoint node in the file (i.e. the normal default behaviour). The URL syntax "#ViewpointName" (i.e. no file name) specifies a viewpoint within the existing file. If this URL is loaded (e.g. Anchor node's url field or loadURL() method is invoked by a Script node), the Viewpoint node named "ViewpointName" is bound (a set_bind TRUE event is sent to this Viewpoint node).


TECHNICAL NOTE: This "#name" URL syntax comes directly from HTML, where specifying a URL of the form page.html#name jumps to a specific (named) location in a given page.

TIP: The easiest way to move the viewer between viewpoints in the world is to use an Anchor node and the #ViewpointName URL syntax. For example, this VRML file format fragment will take users to the viewpoint named MOUNTAIN_TOP when they click on the Box:

     Anchor {
       children Shape {
         appearance Appearance {
           material Material { }
         }
         geometry Box { }
       }
       url "#MOUNTAIN_TOP"
     }

If a Viewpoint node is bound and is the child of an LOD, Switch, or any node or prototype that disables its children, the result is undefined. If a Viewpoint node is bound that results in collision with geometry, the browser shall perform its self-defined navigation adjustments as if the user navigated to this point (see Collision).

TIP: You will almost always want to name a Viewpoint node using DEF, because Viewpoints don't do much by themselves. You will either ROUTE events to them or will use the URL #ViewpointName syntax.

You should be careful when you use the USE feature with Viewpoints. USE can make the same object appear in multiple places in the world at the same time. For example, you might create just a few different cars and then USE them many times to create a traffic jam. However, what would happen if you put a Viewpoint named "DRIVER" inside one of the cars and then bound the viewer to that Viewpoint? The car would be in multiple places in the world at the same time--something that is OK for a virtual world, but can never happen in the real world. And since the person looking at the virtual world is a real person who can look at the virtual world from only one place at a time, there is a problem. What happens is undefined. Browsers can do whatever they wish, including completely ignoring the DRIVER Viewpoint or randomly picking one of the locations of the car and putting the viewer there.

To avoid problems, you should make sure that each Viewpoint is the child of exactly one grouping node. And that any of the Viewpoint's parent grouping nodes is the child of only one grouping node, too.


EXAMPLE (click to run) : The following example illustrates the Viewpoint node. This example demonstrates a couple of typical uses of Viewpoints. Click on one of the shapes (Box, Cone, Sphere) to move yourself to a Viewpoint on their platform. Navigate yourself onto the moving white platform, which will then bind you to a Viewpoint on that platform and move you along with it. Move off the platform to unbind yourself from that Viewpoint:

#VRML V2.0 utf8
# Three fixed viewpoints
DEF V1 Viewpoint {
  position 0 1.8 -12
  orientation 0 1 0  3.1416
  description "View: green platform"
}
DEF V2 Viewpoint {
  position -10.4 1.8 6
  orientation 0 1 0  -1.047
  description "View: red platform"
}
DEF V3 Viewpoint {
  position 10.4 1.8 6
  orientation 0 1 0  1.047
  description "View: blue platform"
}
# A moving Viewpoint.  This Transform rotates, taking the
# Viewpoint, ProximitySensor and children with it:
DEF VT Transform { children [
  Transform {
    translation 0 -.1 -4
    children [
      DEF V4 Viewpoint {
        position 0 1.3 1.8      # Edge of platform
        orientation 0 1 0  0  # Looking out
        jump FALSE
        description "View: moving platform"
      }
      Shape {  # Octagonal platform
        appearance Appearance { material Material { } }
        geometry  IndexedFaceSet {
          coord Coordinate {
            point [ 1 0 2, 2 0 1, 2 0 -1, 1 0 -2,
                    -1 0 -2, -2 0 -1, -2 0 1, -1 0 2 ]
          }
          coordIndex [ 0, 1, 2, 3, 4, 5, 6, 7, -1 ]
        }
      }
      # When this ProximitySensor is activated, 
      # viewer bound to V4:
      DEF PS ProximitySensor {
        center 0 2 0
        size 4 4 4
      }
    ]
  }
  DEF OI OrientationInterpolator {
    # It takes 18 seconds to go all the way around.
    # Four-second 120-degree rotation, two second pause,
    # repeated three times. These keytimes are 18'ths:
    key [ 0,    .056, .167, .22,
          .33,  .389, .5,   .556,
          .667, .722, .833, .889,
          1 ]
    # Rotate a total of 2 PI radians.
    # Keys are given at 1/8 and 7/8 of each one-third
    # rotation to make the rotation smoother 
    # (slow in-out animation); that's why these angles
    # are multiples of 1/24'th (PI/12 radians) rotation:
    keyValue [
     0 1 0 0,     0 1 0  .262,  0 1 0  1.833, 0 1 0  2.094,
     0 1 0 2.094, 0 1 0  2.356, 0 1 0  3.927, 0 1 0  4.189,
     0 1 0 4.189, 0 1 0  4.45,  0 1 0  6.021, 0 1 0  0,
     0 1 0 0
    ]
  }
  DEF TS TimeSensor {
    loop TRUE  startTime 1
    cycleInterval 18
  }
]}
#Routes for platform animation:
ROUTE TS.fraction_changed TO OI.set_fraction
ROUTE OI.value_changed TO VT.set_rotation
# And bind viewer to V4 when they're
#  on the moving platform:
ROUTE PS.isActive TO V4.set_bind
# Some geometry to look at:
DirectionalLight { direction 0 -1 0 }
Transform {
  translation 0 0 -9
  children [
    Shape {
      appearance DEF A1 Appearance {   
        material Material { diffuseColor 0 0.8 0 }
      }
      geometry DEF IFS IndexedFaceSet {
        coord Coordinate {
          point [ 0 0 -6, -5.2 0 3,  5.2 0 3 ]
        }
        coordIndex [ 0, 1, 2, -1 ]
        solid FALSE
      }
    }
    Anchor {
      url "#V1"
      children
      Transform {
        translation 0 0.5 0
        children Shape {
          appearance USE A1
          geometry Box { size 1 1 1 }
        }
}}]}
Transform {
  translation -7.8 0 4.5
  children [
    Shape {
      geometry USE IFS
      appearance DEF A2 Appearance {   
        material Material { diffuseColor 0.8 0 0 }
    }}
    Anchor {
      url "#V2"
      children Transform {
        translation 0 .5 0
        children Shape {
          appearance USE A2
          geometry Sphere { radius .5 }
}}}]}
Transform {
  translation 7.8 0 4.5
  children [
    Shape {
      geometry USE IFS
      appearance DEF A3 Appearance {   
        material Material { diffuseColor 0 0 0.8 }
      }
    }
    Anchor {
      url "#V3"
      children Transform {
        translation 0 .5 0
        children Shape {
          appearance USE A3
          geometry Cone { bottomRadius .5  height 1 }
}}}]}