VRMLSite Magazine

VRML 2.0 URLs
VRML by the Pound
People Linking to Us
Subscribe for Free
Tell a Friend
We'll Link to You

IntranetSite
ChannelSite

Aereal, Inc.
Instant VRML World

Spotlight BackContentsNext
view me!


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:

  1. X = horizontal. Positive goes right. Negative goes left.
  2. Y = vertical. Positive goes up. Negative goes down.
  3. 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:

  1. 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
  2. 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:

  1. USE nodes that cite an object in the same file by DEF name
  2. 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.
BackContentsNext
© 1997 Aereal, Inc. Please send suggestions to suggest@vrmlsite.com.