1.6. Other important VRML features

Now that we're accustomed with VRML syntax and concepts, let's take a quick look at some notable VRML features that weren't shown yet.

1.6.1. Inline nodes

A powerful tool of VRML is the ability to include one model as a part of another. In VRML 2.0 we do this by Inline node. It's url field specifies the URL (possibly relative) of VRML file to load. Note that our engine doesn't actually support URLs right now and treats this just as a file name.

The content of referenced VRML file is placed at the position of given Inline node. This means that you can apply transformation to inlined content. This also means that including the same file more than once is sensible in some situations. But remember the remarks in Section 1.4, “DEF / USE mechanism”: if you want to include the same file more than once, you should name the Inline node and then just reuse it. Such reuse will conserve resources.

url field is actually MFString and is a sequence of URL values, from the most to least preferred one. So VRML browser will try to load files from given URLs in order, until a valid file will be found.

In VRML 1.0 the node is called WWWInline, and the URL (only one is allowed, it's SFString field) is specified in the field name.

When using our engine you can mix VRML versions and include VRML 1.0 file from VRML 2.0, or the other way around. Moreover, you can include 3DS and Wavefront OBJ files too.

An example:

#VRML V2.0 utf8

DEF MyInline Inline { url "reuse_cone.wrl" }

Transform {
  translation 1 0 0
  rotation 1 0 0 -0.2
  children [
    USE MyInline

Transform {
  translation 1 0 0
  rotation 1 0 0 -0.2
  children [
    USE MyInline

Transform {
  translation 1 0 0
  rotation 1 0 0 -0.2
  children [
    USE MyInline

Transform {
  translation 1 0 0
  rotation 1 0 0 -0.2
  children [
    USE MyInline

] } ] } ] } ] }

Figure 1.15. Our earlier example of reusing cone inlined a couple of times, each time with a slight translation and rotation

Our earlier example of reusing cone inlined a couple of times, each time with a slight translation and rotation

1.6.2. Texture transformation

VRML allows you to specify a texture coordinate transformation. This allows you to translate, scale and rotate visible texture on given shape.

In VRML 1.0, you do this by Texture2Transform node — this works analogous to Transform, but transformations are only 2D. Texture transformations in VRML 1.0 accumulate, just like normal transformations. Here's an example:

#VRML V1.0 ascii

Group {
  Texture2 { filename "sample_texture.png" }

  Cube { }

  Transform { translation 3 0 0 }

  Separator {
    # translate texture
    Texture2Transform { translation 0.5 0.5 }
    Cube { }
  }

  Transform { translation 3 0 0 }

  Separator {
    # rotate texture by Pi/4
    Texture2Transform { rotation 0.7853981634 }
    Cube { }
  }

  Transform { translation 3 0 0 }

  Separator {
    # scale texture
    Texture2Transform { scaleFactor 2 2 }
    Cube { }

    Transform { translation 3 0 0 }

    # rotate texture by Pi/4.
    # Texture transformation accumulates, so this will
    # be both scaled and rotated.
    Texture2Transform { rotation 0.7853981634 }
    Cube { }
  }
}

Figure 1.16. Textured cube with various texture transformations

Textured cube with various texture transformations

Remember that we transform texture coordinates, so e.g. scale 2x means that the texture appears 2 times smaller.

VRML 2.0 proposes a different approach here: We have similar TextureTransform node, but we can use it only as a value for textureTransform field of Appearance. This also means that there is no way how texture transformations could accumulate. Here's a VRML 2.0 file equivalent to previous VRML 1.0 example:

#VRML V2.0 utf8

Shape {
  appearance Appearance {
    texture DEF SampleTexture
      ImageTexture { url "sample_texture.png" }
  }
  geometry Box { }
}

Transform {
  translation 3 0 0
  children Shape {
    appearance Appearance {
      texture USE SampleTexture
      # translate texture
      textureTransform TextureTransform { translation 0.5 0.5 }
    }
    geometry Box { }
  }
}

Transform {
  translation 6 0 0
  children Shape {
    appearance Appearance {
      texture USE SampleTexture
      # rotate texture by Pi/4
      textureTransform TextureTransform { rotation 0.7853981634 }
    }
    geometry Box { }
  }
}

Transform {
  translation 9 0 0
  children Shape {
    appearance Appearance {
      texture USE SampleTexture
      # scale texture
      textureTransform TextureTransform { scale 2 2 }
    }
    geometry Box { }
  }
}

Transform {
  translation 12 0 0
  children Shape {
    appearance Appearance {
      texture USE SampleTexture
      # scale and rotate the texture.
      # There's no way to accumulate texture transformations,
      # so we just do both rotation and scaling by
      # TextureTransform node below.
      textureTransform TextureTransform {
        rotation 0.7853981634
        scale 2 2
      }
    }
    geometry Box { }
  }
}

1.6.3. Navigation

You can specify various navigation information using the NavigationInfo node.

  • type field describes preferred navigation type. You can “EXAMINE” model, “WALK” in the model (with collision detection and gravity) and “FLY” (collision detection, but no gravity).

  • avatarSize field sets viewer (avatar) sizes. These typically have to be adjusted for each world to “feel right”. Although you should note that VRML generally suggests to treat length 1.0 in your world as “1 meter”. If you will design your VRML world following this assumption, then default avatarSize will feel quite adequate, assuming that you want the viewer to have human size in your world. Viewer sizes are used for collision detection.

  • Viewer size together with visibilityLimit may be also used to set VRML browsers Z-buffer near and far clipping planes. This is the case with our engine. By default our engine tries to calculate sensible values for near and far based on scene bounding box size.

  • You can also specify moving speed (speed field), and whether head light is on (headlight field).

To specify default viewer position and orientation in the world you use Viewpoint node. In VRML 1.0, instead of Viewpoint you have PerspectiveCamera and OrthogonalCamera (in VRML 2.0 viewpoint is always perspective). Viewpoint and camera nodes may be generally specified anywhere in the file. The first viewpoint/camera node found in the file (but only in the active part of the file — e.g. not in inactive children of Switch) will be used as the starting position/orientation. Note that viewpoint/camera nodes are also affected by transformation.

Finally, note that my VRML viewer view3dscene has a useful function to print VRML viewpoint/camera nodes ready to be pasted to VRML file, see menu item “Console” -> “Print current camera node”.

Here's an example file. It defines a viewpoint (generated by view3dscene) and a navigation info and then includes actual world geometry from other file (shown in our earlier example about inlining).

#VRML V2.0 utf8

Viewpoint  {
  position 11.832 2.897 6.162
  orientation -0.463 0.868 0.172 0.810
}

NavigationInfo {
  avatarSize [ 0.5, 2 ]
  speed 1.0
  headlight TRUE
}

Inline { url "inline.wrl" }

Figure 1.17. Viewpoint defined for our previous example with multiplied cones

Viewpoint defined for our previous example with multiplied cones

1.6.4. IndexedFaceSet features

IndexedFaceSet nodes (and a couple of other nodes in VRML 2.0 like ElevationGrid) have some notable features to make their rendering better and more efficient:

  • You can use non-convex faces if you set convex field to FALSE. It will be VRML browser's responsibility to correctly triangulate them. By default faces are assumed to be convex (following the general rule that the default behavior is the easiest one to handle by VRML browsers).

  • By default shapes are assumed to be solid which allows to use backface culling when rendering them.

  • If you don't supply pre-generated normal vectors for your shapes, they will be calculated by the VRML browser. You can control how they will be calculated by the creaseAngle field: if the angle between adjacent faces will be less than specified creaseAngle, the normal vectors in appropriate points will be smooth. This allows you to specify preferred “smoothness” of the shape. In VRML 2.0 by default creaseAngle is zero (so all normals are flat; again this follows the rule that the default behavior is the easiest one for VRML browsers). See example below.

  • For VRML 1.0 the creaseAngle, backface culling and convex faces settings are controlled by ShapeHints node.

  • All VRML shapes have some sensible default texture mapping. This means that you don't have to specify texture coordinates if you want the texture mapped. You only have to specify some texture. For IndexedFaceSet the default texture mapping adjusts to shape's bounding box (see VRML specification for details).

Here's an example of the creaseAngle use. Three times we define the same geometry in IndexedFaceSet node, each time using different creaseAngle values. The left tower uses creaseAngle 0, so all faces are rendered flat. Second tower uses creaseAngle 1 and it looks good — smooth where it should be. The third tower uses creaseAngle 4, which just means that normals are smoothed everywhere (this case is actually optimized inside our engine, so it's calculated faster) — it looks bad, we can see that normals are smoothed where they shouldn't be.

#VRML V2.0 utf8

Viewpoint  {
  position 31.893 -69.771 89.662
  orientation 0.999 0.022 -0.012 0.974
}

Switch {
  choice DEF TowerCoordinates Coordinate {
    point [
      4.157832 4.157833 -1.000000,
      4.889094 3.266788 -1.000000,
      ........
    ]
  }
}

Transform {
  children Shape {
    appearance Appearance { material Material { } }
    geometry IndexedFaceSet {
      coordIndex [
        63 0 31 32 -1,
        31 30 33 32 -1,
        ........
      ]
      coord USE TowerCoordinates
      creaseAngle 0
    }
  }
}

Transform {
  translation 30 0 0
  children Shape {
    appearance Appearance { material Material { } }
    geometry IndexedFaceSet {
      coordIndex [
        63 0 31 32 -1,
        31 30 33 32 -1,
        ........
      ]
      coord USE TowerCoordinates
      creaseAngle 1
    }
  }
}

Transform {
  translation 60 0 0
  children Shape {
    appearance Appearance { material Material { } }
    geometry IndexedFaceSet {
      coordIndex [
        63 0 31 32 -1,
        31 30 33 32 -1,
        ........
      ]
      coord USE TowerCoordinates
      creaseAngle 4
    }
  }
}

Figure 1.18. Three towers with various creaseAngle settings

Three towers with various creaseAngle settings

1.6.5. Prototypes

Prototypes

These constructions define new VRML nodes in terms of already available ones. The idea is basically like macros, but it works on VRML nodes level (not on textual level, even not on VRML tokens level) so it's really safe.

External prototypes

These constructions define syntax of new VRML nodes, without defining their implementation. The implementation can be specified in other VRML file (using normal prototypes mentioned above) or can be deduced by particular VRML browser using some browser-specific means (for example, a browser may just have some non-standard nodes built-in). If a browser doesn't know how to handle given node, it can at least correctly parse the node (and ignore it).

For example, many VRML browsers handle some non-standard VRML nodes. If you use these nodes and you want to make your VRML files at least readable by other VRML browsers, you should declare these non-standard nodes using external prototypes.

Even better, you can provide a list of proposed implementations for each external prototype. They are checked in order, VRML browser should chose the first implementation that it can use. So you can make the 1st item a URN that is recognized only by your VRML browser, and indicanting built-in node implementation. And the 2nd item may point to a URL with another VRML file that at least partially emulates the functionality of this non-standard node, by using normal prototype. This way other VRML browsers will be able to at least partially make use of your node.

Our engine handles prototypes and external prototypes perfectly (since around September 2007). We have some non-standard VRML extensions (see Kambi VRML game engine extensions list), and they can be declared as external prototypes with URN like "urn:vrmlengine.sourceforge.net:node:KambiTriangulation". So other VRML browsers should be able to at least parse them.

1.6.6. Beyond what is implemented

There are some very notable VRML 97 features that I didn't describe in this document, simply because they are not implemented yet. To name just a few:

Interpolator nodes

They define animation by interpolation of appropriate sets of values. My engine also allows to do animations, also by interpolating, so the internal approach is actually the same. However the way to specify the animations for my engine is not by VRML nodes, but instead by providing two or more VRML models with the same structure. My approach has some advantages and some disadvantages when compared to VRML interpolators, the details will be explained in Chapter 6, Animation.

Sensors, events, scripting

VRML 97 specification includes a great support for extending content with any kind of external language. Detailed description of Java and ECMAScript (JavaScript) bindings is given by the specification, and it's expected that other languages could use similar approaches. X3D specification pushes this even further, by describing external language interface in a way that is “neutral” to actual programming language (which means that it should be applicable to pretty much all existing programming languages).

Scripts can be invoked on various events, and the events can in turn be generated by various nodes. In particular, sensor nodes are special kind of nodes that were designed only to generate events in particular situations.

My engine doesn't support any kind of scripting for now. My initial approach was directed at making special programs (like games ...) that simply use the VRML engine, so any logic was expressed in normal ObjectPascal code that was later compiled.

Of course, it would be great to implement scripting and move as much of this logic as possible to VRML files. For VRML authors, this is also the way to not be tied to any particular VRML engine. Although for really large programs there's no way that whole logic could be moved into a scripting language...

NURBS

NURBS curves and surfaces. Optional in VRML 97 specification (but obviously terribly useful, so their lack in current implementation deserves mention).