VRML nodes may be named and later referenced. This allows
you to reuse the same node (which can be any VRML node type —
like a shape, a material, or even a whole group) more than once. The syntax
is simple: you name a node by writing
DEF <node-name>
before node type.
To reuse the node, just write USE <node-name>
.
This mechanism is available in all VRML versions.
Here's a simple example
that uses the same Cone
twice, each time with a different material color.
#VRML V2.0 utf8 Shape { appearance Appearance { material Material { diffuseColor 1 1 0 } } geometry DEF NamedCone Cone { height 5 } } Transform { translation 5 0 0 children Shape { appearance Appearance { material Material { diffuseColor 0 0 1 } } geometry USE NamedCone } }
Using DEF / USE mechanism makes your VRML files
smaller and easier to author, and it also allows
VRML implementations to save resources (memory, loading time...).
That's because VRML implementation can allocate the node
once, and then just copy the pointer to this node.
VRML specifications are formulated to make this
approach always correct, even when mixed with features like
scripting or sensors. Note that some nodes
can “pull” additional data with them
(for example ImageTexture
nodes will load
texture image from file), so the memory saving may be even larger.
Consider these two VRML files:
#VRML V2.0 utf8 Shape { appearance Appearance { texture DEF SampleTexture ImageTexture { url "../textures/test_texture.png" } } geometry Box { } } Transform { translation 5 0 0 children Shape { appearance Appearance { texture USE SampleTexture } geometry Sphere { } } }
#VRML V2.0 utf8 Shape { appearance Appearance { texture ImageTexture { url "../textures/test_texture.png" } } geometry Box { } } Transform { translation 5 0 0 children Shape { appearance Appearance { texture ImageTexture { url "../textures/test_texture.png" } } geometry Sphere { } } }
Both files above look the same when rendered, but in the first case VRML implementation loads the texture only once, since we know that this is the same texture node [3].
Note that the first node definition, with DEF
keyword, not only names the node, but also includes it in the file.
Often it's more comfortable to first define a couple of named
nodes (without actually using them) and then use them.
You can use the Switch
node for this
— by default Switch
node doesn't
include any of it's children nodes, so you can write
VRML file like this:
#VRML V2.0 utf8 Switch { choice [ DEF RedSphere Shape { appearance Appearance { material Material { diffuseColor 1 0 0 } } geometry Sphere { } } DEF GreenSphere Shape { appearance Appearance { material Material { diffuseColor 0 1 0 } } geometry Sphere { } } DEF BlueSphere Shape { appearance Appearance { material Material { diffuseColor 0 0 1 } } geometry Sphere { } } DEF SphereColumn Group { children [ Transform { translation 0 -5 0 children USE RedSphere } Transform { translation 0 0 0 children USE GreenSphere } Transform { translation 0 5 0 children USE BlueSphere } ] } ] } Transform { translation -5 0 0 children USE SphereColumn } Transform { translation 0 0 0 children USE SphereColumn } Transform { translation 5 0 0 children USE SphereColumn }
One last example shows a reuse of Coordinate
node. Remember that a couple of sections earlier we defined
a simple PointSet
. PointSet
node has an SFNode
field named coord
.
You can place there a Coordinate
node.
A Coordinate
node, in turn, has a point
field of type SFVec3f
that allows you to specify
point positions. The obvious question is “Why all this complexity?
Why not just say that coord
field is
of SFVec3f
type and directly include the point
positions?”. One answer was given earlier when talking
about grouping nodes: this allowed VRML specification for painless
addition of GeoCoordinate
as an alternative
way to specify positions. Another answer is given by the example
below. As you can see, the same set of positions may be used
by a couple of different nodes[4].
#VRML V2.0 utf8 Shape { appearance Appearance { material Material { } } geometry IndexedFaceSet { coord DEF TowerCoordinates Coordinate { point [ 4.157832 4.157833 -1.000000, 4.889094 3.266788 -1.000000, ...... ] } coordIndex [ 63 0 31 32 -1, 31 30 33 32 -1, ...... ] } } Transform { translation 30 0 0 children Shape { geometry IndexedLineSet { coordIndex [ 63 0 31 32 63 -1, 31 30 33 32 31 -1, ...... ] coord USE TowerCoordinates } } } Transform { translation 60 0 0 children Shape { geometry PointSet { coord USE TowerCoordinates } } }
Now that we know all about children relationships and DEF / USE mechanism, we can grasp the statement mentioned at the beginning of this chapter: every VRML file is a directed graph of nodes. It doesn't have cycles, although if we will forget about direction of edges (treat it as an undirected graph), we can get cycles (because of DEF / USE mechanism).
Note that VRML 1.0 file must contain exactly one root node,
while VRML 2.0 file is a sequence of any number of root nodes.
So, being precise, VRML graph doesn't have to be a connected graph.
But actually our engine when reading VRML file with many
root nodes just wraps them in an “invisible”
Group
node. This special Group
node acts just like any other group node, but it's not written
back to the file (when e.g. using our engine to pretty-print VRML files).
This way, internally, we always see VRML file as a connected graph,
with exactly one root node.
[3] Actually, in the second case, our engine can also figure out that this is the same texture filename and not load the texture twice. But the first case is much “cleaner” and should be generally better for all decent VRML implementations.
[4] I do not cite full VRML source
code here, as it includes a long list of coordinates and indexes generated by
Blender exporter. See VRML files distributed with this document:
full source is in the file examples/reuse_coordinate.wrl
.