| Authoring VRML 1.0 (Part 2 of 4)
|
| by Len Bullard
|
|
Hey, thought you had it made. No such luck. You are out
of basic training and into advanced studies now. Now we
have to talk about space... the final frontier. Ok, we are
talking about geometric space and how things are positioned
in a VRML world, Mr Sulu. If you already remember all
of this from geometry class, skip to the "Nesting Coordinate
Systems" section. I won't
tell the Captain you were smoking in the boys room. Promise.
|
|
|
Coordinate Systems
Remember graph paper from geometry class? First you drew two
intersecting lines called axes on the grid. You drew curves by putting
points on the grid, counting positions and penciling in dots. Then you
drew the lines to connect the dots. That was 2D, and as you remember,
those Ds are called dimensions. You named the axes X and Y. X named the
dots that went left to right and Y named dots that went up and down.
In VRML, these guys are back to haunt you. Except like
Caspar's uncles, they have a partner, Z. Z goes in and out.
|
|
|
In technical jargon, VRML uses a right-handed coordinate system. This
refers to a way to remember which axes point in which direction
depending on which way your thumb points. Really technical stuff.
Now forget that, and remember this:
- X = horizontal. Positive goes right. Negative goes left.
- Y = vertical. Positive goes up. Negative goes down.
- Z = Forward and Backward. Positive comes toward you out of the screen.
Negative goes away from you into the screen.
|
|
|
When defining a position in space, one states its position as points
relative to the XYZ axes. Remember earlier I said to think in meters.
On that ancient graph paper, you had to tell your teacher what the
distance was that any square on the grid represented. So if one square
was one inch, the scale was equal to one inch. In VRML, one square on
the paper equals one meter.
NOTE: This isn't exactly true. The VRML 1.0 specification lets you use
any scale you like, but stick to meters and you will have a better time
with the VRML community, VRML 2.0, and the real world at large.
|
|
|
Let's say you want to define a point, one meter right, one meter
up, and one meter forward. You would write this:
x=1 y=1 z=1
Sure. Right, up and forward from where? To make sense, you need an
origin. On the graph paper, the place where the axes all intersect
is called an origin. The position of the origin is:
x=0 y=0 z=0
|
|
|
Whenever you build an object in VRML, it will be built relative
to the origin. It is the center of the VRML universe. But, every
number has two neighbors: one greater and one lesser. Some win, some lose,
some are tall, some are short, some are liberal, some are fanatical
and some are plain ornery. In short, for any position, there are
positive and negative neighbors. To make that simple to write,
let's say we wanted the position inside the screen, and just left
of center (the origin). For our geometry teacher, we would write:
x=-1 y=1 z=-1
|
|
|
But VRML was designed by guys smart enough to know that when
you write:
-1 1 -1
you mean XYZ. It's assumed. So, you will always see the second
form, -1 1 -1.
|
|
|
What if you want to be half a meter away? No problem. VRML
uses standard decimal notation (a.k.a, floating point). It looks
like this:
-0.5 1 -1
|
|
|
Nesting Coordinate Systems
you can place boxes inside boxes inside boxes until you or the computer drops from exhaustion
There is one more bit of technical cruft: nesting coordinate systems.
Remember the Separator node from last month's article. We said it was a *container*. Like
any container, it holds things together and allows you to identify them
as a *separate* unit. Another way to think about this is to say
it declares a local coordinate system. In 3D graphics, one has two
kinds of coordinate systems, local and global. Think of these
as a box inside a box. In fact, you can place boxes inside boxes
inside boxes until you or the computer drops from exhaustion. This
is called "nesting". When a box (a local coordinate system) is inside
another box (a global coordinate system) these boxes are said to "nest".
This is the tricky part. There is only one world coordinate system.
That is the biggest box in the VRML world. (Call it a universe if
you like.) This system is signified by the first separator node right
after the VRML declaration.
#VRML V1.0 ascii
Separator { ... }
|
|
|
All VRML 1.0 worlds have a root
separator. Any new separators should be inside this one. Call the first
separator, the root separator.
Any separator can include separators inside it, or
at the same level. This is sometimes referred to as a parenthetical
nodelist. You can think of them as boxes inside boxes.
|
#VRML V1.0 ascii
DEF Root Separator {
DEF Leve1a Separator { Inside Root
DEF Level2a Separator { Inside Level1A
}
#end Level2A
}
#end Level1A
DEF Level1b Separator { Inside Root
}
#end Level1b
}
#end root
|
|
|
Level1A is inside Root. Level2A is inside Level1A. Level1B is inside root, and so on.
In the next section on transforms, you will see why this nesting of
separators is needed.
NOTE: Some VRML 1.0 browsers allow you to get away without having a root
separator. Don't Do It. It will cause you a lot of trouble later on
when you combine worlds using inlines or copy operations. A well-nested
world is an easy world to maintain and reuse.
|
|
|
Translate, Rotate, Scale: I Put This Here. I Put This Here
When you first build an object, it is automatically placed
at the world origin (0 0 0) in a forward facing position, in regular form (cube, sphere,
cylinder, cone, compound object). To change this, you need Transform nodes:
- Translate: change the position of the object in the coordinate system
- Rotate: change the orientation of the object
- Scale: make it bigger or smaller, or stretch or squash it in some axis
|
|
|
Here is an example of the syntax of each of these:
Translate: the fields are XYZ floating point values that move
the object X (left/right), Y (up/down), Z (forward/back)
Translation {
translation 0 4.9 -1
}
Rotate: the fields are XYZ floating point values followed by
the angle of rotation in radians
Rotation {
rotation 0 0 1 0.436332
}
Scale: the fields are XYZ floating point values for ratios
that independently scale along each axis
Scale {
scaleFactor 1 0.3 1
}
|
|
|
Translations, rotations and scaling are common operations. There is an
extra VRML node, Transform, that combines these into one node. Translations
*push* objects into position and rotations *turn* them.
|
|
|
Shaping Objects By Scaling: Squashing and Stretching
Scaling is very powerful because it allows us to shape a primitive node or
a compound object by independently specifying the values of the scaling axes (XYZ).
Think of this as stretching and squashing. If you change the
values by the same amount in the same positive or negative direction, an object
gets larger or smaller.
|
|
|
This makes an object shrink by half in all dimensions:
Scale {
scaleFactor 0.5 0.5 0.5
}
|
|
|
This makes the object shrink by half it's height:
Scale {
scaleFactor 1 0.5 1
}
|
|
|
This makes the object grow by twice it's depth:
Scale {
scaleFactor 1 1 2
}
|
|
|
Try this on some objects; it's obvious why I call it
stretching and squashing. If you set the axis values to be different for each axis, it has the effect of
stretching (increasing the value) or squashing (decreasing the value) of the
object along that axis or axes. Try this with any object, primitive
or compound. It is not very useful with a cube. If you want to
shape a primitive cube, use the width, height, and depth fields.
If you decrease the z-axis scale value, a sphere becomes a standing shield. If
you increase it, it becomes an ovoid (err.. a blunt football). By stretching,
squashing, and then combining the scaled shapes with other shapes, you
have a very large pallette of shapes to work with.
|
Princess of Talos
|
NOTE: A visualizing editor is really handy when experimenting with scaling
because you see the results in real time as you adjust the values. All of the spacecraft in
the Talosian Spaceport world are examples of experiments with this technique.
|
|
|
Transform Operation Order and State Accumulation
Say it out loud: TRS!
Hammer this into your mind, tattoo it on your hand, or just paste
it to the front of your 'droid. Transform operations should be done in this
order: Translate Rotate Scale. Say it out loud: TRS!
|
Example 1
|
Why? Earlier I mentioned a feature of VRML 1.0 called state accumulation.
As non-technically as I can explain it, this is VRML Karma. What you
do affects what you do next. To see how this works, we will look at some
examples of these operations.
|
|
|
#VRML V1.0 ascii
DEF Root Separator {
DEF Column Separator {
Translation {
translation 0 5.3 7.9
}
DEF Cylinder Cylinder {
height 10.1
}
DEF Translation1 Translation {
translation 0 4.9 0
}
DEF Scale1 Scale {
scaleFactor 1 0.3 1
}
DEF Sphere1 Sphere {
radius 1.2
}
DEF Translation2 Translation {
translation 0 2 0
}
DEF Sphere2 Sphere {
}
DEF Translation3 Translation {
translation 0 1.7 0
}
DEF Sphere3 Sphere {
}
DEF Translation4 Translation {
translation 0 1.6 0
}
DEF Sphere4 Sphere {
}
DEF Translation5 Translation {
translation 0 -10.1
}
DEF Cone1 Cone {
bottomRadius 1.5
height 8
}
}
}
|
Example 2
|
When you load this example, you see a cylinder with squashed spheres
stacked on top, and sort of a skirt at the bottom. (hey... alien tastes!)
Notice two things:
- The Scale1 values are applied to ALL of the objects that follow. The
spheres are all squashed proportionally, and so is the cone. In the case of
the cone, the height is increased to compensate
- The translation values that are pushing the spheres to
the top of the column and the value that pushes the skirt to the bottom
are waaaay different. A Y value of 2 pushes the first sphere to the top,
with others decreasing proportional to their height after squashing and
their position in the stack. But Translation5 that pushes the cone has
a value of -33.6!
Weird? Not at all. The reason for this non-intuitive behavior is state
accumulation. Translation5 is also being scaled! That's right: transforms
don't just act on the object; they modify the coordinate space of the
object. That is why you were asked to think about boxes inside boxes. Each
object has it's own space (nice if you think about it) but unless it is in
it's own container (a separator) and outside the column separator, it gets jostled by any transforms or materials
in the tree above it. Try this
DEF Sphere4 Sphere {
}
}
Separator {
DEF Translation5 Translation {
translation 0 -33.0 0
}
DEF Cone1 Cone {
bottomRadius 1.5
height 8
}
}
|
|
|
The cone is now at the expected position, 33 meters below the column.
Aha! You think, "If I just put a separator around it, it will be in the
right place." Nope. Try it. You will still need a Y value of -33.6. Why?
Because of another feature called state leak. Values leak across separators.
So what good are they? In this example, not much. You want
to keep the cone on the same branch so you CAN affect them all with
the first translation. It is used to position the entire column structure
relative to other objects in the scene.
So how do you prevent leaks?
|
So how do you prevent leaks?
|
So how do you prevent leaks? To get a material or transform to affect only the objects you want,
create separate branches. Subdivide the branch at the top
of the tree into two or more branches. Put all of the transform and
material nodes that you want to affect both branches at the top (global)
and those that are to affect any subtree (local) inside the local separator.
Separator {
global nodes
Separator {
local nodes
}
Separator {
local nodes
}
}
|
|
|
The rule of thumb is: if you want to affect a group of objects as
a unit, put them inside a separator. A big advantage is when you convert
these to 2.0, applying motion interpolators is straightforward. Without
explaining this in detail, remember that a separator names a group of
objects to which one can apply an operation. If for example, you want
to move the entire object forward, you apply the operation to the
level indicated by the separator. To move a hand on an arm without
moving the arm, you apply the operation to the hand, not the arm. In 2.0,
a separator gets a different node type, but the conversion will be cleaner
if you use separators correctly now, and you have learned how to think
about grouping objects for animation.
Reusing Objects
There are two techniques for reusing an object
As you build your worlds, you will discover that some objects are reused
many times. While one can always copy objects, this leads to
fat files. If you change an object, you have to change all of the copies. Where one wants to apply separate behaviors as in VRML 2.0, sometimes there is
no choice but to copy, but for now let's look at strict reuse where the object is
declared once, then reused multiple times. There are two techniques for reusing an object:
- USE nodes that cite an object in the same file by DEF name
- WWWInline which includes an external .wrl file
|
Example 3
|
Which technique you use is typically an issue of granularity and scope. For example,
in the source code example provided with this article, the Temple reuses the
column object. Take a look at any neo-classical structure and you
will find many instances of reused objects that are simply scaled and repositioned.
When building a compound object, the USE node works well. WWWInlines are
more useful when one begins to lay out a scene of multiple compound objects.
We'll look at a technique for this in the next section. It is also useful for
maintaining a set of objects which are used in many worlds. For worlds that
consist of related scenes, this technique is excellent.
NOTE: These are rules of thumb. There are always exceptions.
To reuse the columns in the Temple example, the Separator is named and
then cited in the USE statement. Here is the code:
|
|
|
Separator {
Translation {
translation 0 5.3 7.9
}
DEF Column Separator {
.... nodes to define the column objects
}
}
Separator {
Translation {
translation -8.3 5.3 0
}
USE Column
}
|
|
|
That's all there is to it. Notice that the USE column is under its
own separator so that it can be positioned correctly. You could add any
transform or material here that you want to affect the instance. You can now repeat
this same code for each instance of the column.
|
Example 4
|
A WWWInline node is almost as simple. Because the objects are in
a different file, you have a bit more to do. Here is an example:
WWWInline {
name "temple.wrl"
bboxSize 10 0 10
bboxCenter 0 0 0
}
|
|
|
The first field, name has the name of the file to be inlined.
The bboxSize and bboxCenter fields name the extent or size and center
of the inlined object so a smart browser can optimize the presentation.
|
|
|
Here are some things to remember about using WWWInlines:
- Like a texture, an inlined object requires a separate reconnect.
This slows down the load cycle.
- You have to make sure all of the inlines are maintained and in
the right place in the source directory. It is easier to keep them
all in the same directory because most browsers default to the path
of the initially loaded file (a.k.a, a hub or main file).
- Don't include lots of inlines from other sources (e.g, other servers)
unless you make sure these files exist and are maintained. There are very
few good ways to make sure someone hasn't altered an inline file they
are responsible for, and they can do some very embarassing things. The other side
of the coin is that every visitor to your world is hitting their server.
This becomes a drag on their system and they will remove the object. It's best to
copy it and preserve their copyright information inside your copy.
- Don't nest inlines deeply. Inlines that inline inlines can create
cascading calls across the Internet. While not usually a problem, in
this era of brownouts, it is best to be conservative. Remember that
some of the inlined objects may be textured, and that means more reconnects.
Because in the publishing stage you will compress the files using the gzip
utility, size of any one file is not usually a problem. Download time and
rendering speed can be.
|
|
|
Again, these are just rules of thumb. The inlines don't really
make anything faster. Mainly, they are a convenience for maintaining
object libraries and authoring large scenes. Exercise good sense.
|
|
|
That's the basics of building objects. You will want to study the
VRML specification, or better, buy a good book to get all of the details
for using all of the nodes. I recommend two:
|
|
|
The VRML Sourcebook
Authors: Ames, Nadeau, and Moreland
John Wiley and Sons
ISBN: 0-471-14159-3
|
|
|
VRML: Browsing & Building Cyberspace
Author, Mark Pesce
New Riders Publishing
ISBN: 1-56205-498-8
|
|
Len Bullard is a systems analyst
and a married father of two children.
He splits his consciousness between his job as a hypermedia consultant
and his rock band of ten years, Ground Level Sound. His interests
in VRML spawned from a conviction that this was the technology that
would rejoin his divorced psyches and help him fight a lifelong
addiction to endorphins. He spends his copious spare time recording
original music with his band at their studio, Blind Dillo, performing
in the southeast region with GLS, beta testing and answering email.
|