Points, Lines, and Faces

Last time we looked at some more powerful alternatives to the primitive geometries. In this session, we will deal with perhaps the most powerful of the geometry nodes. This set of nodes gives you a great deal of control. They can be a bit more tedious to work with, but the results are often worth it.

Pointsets

We begin our study of this family of nodes by looking at the pointset. As usual, an example is better than a description, so here it goes:
pointset.wrl
#VRML V2.0 utf8

#pointset.wrl

#Demonstrates use of a simple pointset

Shape {
  appearance Appearance {
    material Material {
      # note emissive color makes these dots easier to see
      emissiveColor 1 1 1
    } # end material
  } # end appearance
  geometry PointSet {
    coord Coordinate {
      point [
        #base of pyramid
        -2 0  2,
        -2 0 -2,
         2 0 -2,
         2 0  2,
        #point
         0 2  0,
      ]  # end point
    } # end coord
  } # end geometry
} # end shape

# force browser into 'examine mode'
NavigationInfo{
  type "EXAMINE"
}
Notice that there is a new node in the geometry node. It is a PointSet. The pointset consists of a coordinate node. The next few geometries we look at will use this node. In this simple form, it holds an array of points. The points are coordinates in 3D space. Note that I used comments to describe where the points were. This becomes increasingly important as we do more with these pointsets.

The pointset itself is of limited use. We might use it to simulate constellations in an observatory (oooh, we could animate their motion realistically!!) or perhaps we could use them to simulate electrons in an atom. Both these applications would be stronger with animation, so let's hold off on that for a while longer.

On a practical note, you may have noticed that rather than using diffuseColor as I usually do, I went with emissiveColor in this model. That was done because diffuseColor describes how light reflects off of a surface. While that can be very effective with 3d objects, it causes some problems when I'm working with points, which are hard to see even if they are not shaded. EmissiveColor essentially turns 'off' the shading, as if this object was eminating the color from within. We will talk more about lighting and shading in another lesson. The concept here is this: If the shading effects are making it hard for you to see what's going on in a model, try switching to emissiveColor.

Indexed LineSets

Probably the best thing about pointsets is the way they are used in more complex structures. Here is an example:
lineset.wrl
#VRML V2.0 utf8

#lineset.wrl

#Demonstrates use of a simple indexed lineset

Shape {
  geometry IndexedLineSet {
    coord Coordinate {
      point [
        #base of pyramid
        -2 0  2,     # point 0, left and close
        -2 0 -2,     # point 1, left and back
         2 0 -2,     # point 2, right and back
         2 0  2,     # point 3, right and close
        #top of pyramid
         0 2  0,     #point 4, top of pyramid
      ]  # end point
    } # end coord
    coordIndex [
      #base
      0, 1, 2, 3, 0, -1,
      #vertical edges
      0, 4, -1,
      1, 4, -1,
      2, 4, -1,
      3, 4, -1,
    ] # end coordIndex

  } # end geometry
} # end shape
As you can see, this model was built on the last one. The geometry node containes an IndexedLineSet node, which holds a coord node and a coordIndex node. The coord node is just like that in the pointset. It holds an array of points in 3-space. The coordIndex node is like a 'connect the dots' game. The base is drawn by connecting the four points at the bottom of the pyramid. Essentially, we have said "draw from point 0 to 1, then 1 to 2, to 3, back to 0." The -1 tells the browser we are done drawing this set of lines. The remaining code fragments draw edges from each of the bottom vertices to the point of the pyramid.

Indexed line sets are terrific for making wireframe models. Because all the computer has to draw is lines, and the lines are not shaded, such models are typically very quickly drawn. Unfortunately, wireframe drawings are difficult to understand once they get complex.

Build Your Own!

In the space below, try to build a three-sided pyramid (mine had four sides). The base should be a triangle. You NEED to sketch this out on paper. You will really get lost trying to figure these coordinates out in your head. I have started you out with the values for my 4-sided pyramid.
point
coordIndex

Indexed Face Sets

We can take what we have learned about point and line sets one step further to create what is probably the ultimate in customizable geometries: the IndexedFaceSet. Here is an example that builds on our earlier worlds
faceset.wrl
#VRML V2.0 utf8

#faceeset.wrl

#extends previous examples to make a faceSet

Shape {
  appearance Appearance {
    material Material {
      diffuseColor 1 1 1
    } # end material
  } # end appearance

  geometry IndexedFaceSet {

    solid TRUE
    coord Coordinate {
      point [
        # the pointset hasn't changed
        #base of pyramid
        -2 0  2,     # point 0, left and close
        -2 0 -2,     # point 1, left and back
         2 0 -2,     # point 2, right and back
         2 0  2,     # point 3, right and close
        #top of pyramid
         0 2  0,     #point 4, top of pyramid
        ]  # end point
    } # end coord
    coordIndex [
      #base
      #note that now it is CRUCIAL that I go counter-clockwise
      #from the OUTSIDE.

      0, 1, 2, 3, 0, -1,
      #vertical faces
      #now rather than defining the edges, I am describing the faces
      0, 4, 1, -1,   # left side
      1, 4, 2, -1,   # back side
      2, 4, 3, -1,   # right side
      3, 4, 0, -1,   # front side
    ] # end coordIndex

  } # end geometry
} # end shape

NavigationInfo{
  type "EXAMINE"
} # end NavigationInfo
    

You can see what is happening here. This is quite similar to an indexed line set, except that the coordIndex now represents FACES or polygons, rather than lines. This distinction is really important. You had a lot more options in a line set. You could, for example, define each line of the base independantly. When you are defining a faceset, all the vertices that describe a polygon must be defined. In addition, you must be careful about the order in which the lines are described. The VRML browser tries to save time by drawing only one side of each face if the entity you are describing is solid (which is the default.) In this case, the pyramid will be closed. There should be no reason for the browser to draw and shade the INSIDE of the pyramid, just the outside. By using this feature, we are cutting our processing requirements in half. This can make a HUGE difference when we work with large or complex facesets.

We tell the browser which side of the face to draw by following a special convention: When describing the face, list the vertices in counter-clockwise order around the polygon. The counter-clockwise face is the one that will be drawn. The face you would see if you describe the points in a clockwise rotation will not be drawn.

Wow.
This seems scary, and it can be a pain to work around, but it is worth the effort.

If you are doing a very simple model, and you don't mind the extra processor time, you can set the SOLID node of the IndexedFaceSet to FALSE. This will draw BOTH sides of every polygon, costing more processor time, but not requiring you to be so careful about counter-clockwise description of your faces.

Sometimes you might describe an open shape that will require both sides of every polygon to be drawn. In that case, you will need to specify the solid node as FALSE. That is really why you are allowed to make the solid node false.

Adding color to indexed face sets

We already know how to add color to the Shape node, but that can be relatively crude. Using the color elements of the material node is an 'all or nothing' affair. You can't color the faces individually. It would be really great if there was a way to have more control...

You guessed it. Take a look at the code below:
colorFaces.wrl

#VRML V2.0 utf8

#colorFaces.wrl

#extends previous examples to illustrate color indices
# make N and S faces Red, E and W blue, and bottom green.


Shape {
  geometry IndexedFaceSet {

    solid TRUE
    coord Coordinate {
      point [
        # the pointset hasn't changed
        #base of pyramid
        -2 0  2,
        -2 0 -2,
         2 0 -2,
         2 0  2,
        #point
         0 2  0,
      ]  # end point
    } # end coord
    coordIndex [
      #base
      #note that now it is CRUCIAL that I go counter-clockwise
      0, 1, 2, 3, 0, -1,
      #vertical faces
      #now rather than defining the edges, I am describing the faces
      0, 4, 1, -1,
      1, 4, 2, -1,
      2, 4, 3, -1,
      3, 4, 0, -1,
    ] # end coordIndex
    colorPerVertex FALSE
    color Color {
      color [
        #defining a palette of colors to use in the colorIndex
        1 0 0 # color #0 is red
        0 1 0 # color #1 is green
        0 0 1 # color #2 is blue
      ] # end inner color group
    } # end color node
    colorIndex [
      # make the 0th face green (color 1)
      # make the 1st and 3rd faces red (color 0)
      # make the 2nd and 4th face blue (color 2)
      1 0 2 0 2 
    ] # end colorIndex

  } # end geometry
} # end shape

Upon examining the code, you no doubt noticed that this starts out just like a normal IndexedFaceSet. Things get a little strange in the color node. So far, this is the most obtuse syntax I have found in VRML. It's hard to get used to it, but all you really have to do is copy the structure and change it to fit your needs.

Anyway, here's how it works: The IndexedFaceSet contains a color field, which is usually filled by a Color (note the capitalization) node. This is not too unusual; remember material Material and appearance Appearance?

The Color node contains a field ALSO called color, but this thing holds an ARRAY of colors. (I would have called it colorList or colors, but nobody asked my opinion). This is just a place to store a palette of colors to use later. Note that I kept track of the index of each color in a comment. They are always numbered from 0 to n-1 (when there are n colors being described). You will see why keeping track of the numbers matters in just a moment.

The next field is the colorIndex. It expects an array of integers, with the same number of values as there are faces in the shape. It looks at the first value, and assigns the corresponding color to the first side. The next color is assigned to the next side, and so on.
Yeah, I know, it's a little strange. Try playing around with the values below:

Practice with color faceSets

Color list
colorIndex

A Useful Trick!!!

Often you will find yourself trying to figure out which face is which with an indexedFaceSet. This is especially true when some face is not showing up, because it has been described clockwise rather than counterClockwise. A great trick is to define a colorSet, and as a default, color all faces the same color. Then, to see which face is which, color only one. That way you can be sure you're working on the face you think you are. This is VERY handy.

Coloring by vertex rather than face

Here is an alternative to coloring by faces. You can also choose the colors of the vertices, and have the program automatically do a gradient fill of the faces. It's easier to show than to explain, so here's an example:
colorVertex.wrl
#VRML V2.0 utf8

#colorVertex.wrl

#extends previous examples to illustrate color indices
# This time we will illustrate color by VERTEX


Shape {
  geometry IndexedFaceSet {

    solid FALSE
    coord Coordinate {
      point [
        # the pointset hasn't changed
        #base of pyramid
        -2 0  2, # vertex 0
        -2 0 -2, # v 1
         2 0 -2, # v 2
         2 0  2, # v 3
        #point
         0 2  0, #v 4
      ]  # end point
    } # end coord
    coordIndex [
      #base
      0, 1, 2, 3, 0, -1,  # line 0
      #vertical faces    
      0, 1, 4, -1,        # l1
      1, 2, 4, -1,        # l2
      2, 3, 4, -1,        # l3
      3, 0, 4, -1,        # l4
    ] # end coordIndex

    colorPerVertex TRUE
    color Color {
      color [
        #defining a pallette of colors to use in the colorIndex
        1 0 0 # color #0 is red
        0 1 0 # color #1 is green
        0 0 1 # color #2 is blue
      ] # end inner color group
    } # end color node

    colorIndex [
      # now the colorIndex values are applied to VERTICES, not faces.
      1 0 2 0 1 -1 
      1 0 2 -1
      0 1 2 -1
      1 0 2 -1
      0 1 2 -1

    ] # end colorIndex

  } # end geometry
} # end shape

Using FaceSets to Model Floor Plans

One extremely common use of indexed face sets is to simulate a floor plan. This can be done with relative ease with such a system. Here's an example:
building.wrl
In the sake of brevity, I did not reproduce the source code here. You are welcome to look at it on your own, of course. It is very much like any other indexed faceSet.

Here's the strategy for devising a faceset from a floorplan.

How it's REALLY done.

As you can see, designing indexedFaceSets by hand is not for the faint - of - heart. They can be tricky little buggers, and the number of points can quickly get WAY out of hand. Yet when you look at commercially produced VRML code, it often contains tons of indexedFaceSet nodes. The reason they can do this is that you rarely create such a node (at least any more complex than the ones we have done here) by hand. This is where the commercial VR and CAD programs shine. You can use such a program to generate a very complex model visually. Usually, those programs create mainly Indexed faceSets. These files are really easy for the program to generate, but they can be monsters to edit by hand. If you want a nicer model than you can build by hand, use one of these guys, and use the inline node to add it to your world. If you want more power and control, you will have to build things by hand. As you have seen, we can do some pretty incredible stuff without going into IFS too often. We'll use it when we need it, but the primitive, text, and extrusion geometries can solve a large number of our problems quite well.

Laboratory Assignment

Use an indexedFaceSet to generate a floor plan showing the floor and walls of a structure with at least 3 rooms in it. Use appropriate nodes to color the walls. You WILL need to sketch this out on graph paper and have much of your work done on paper if you intend to complete the project. Here is a sample
© Andy Harris
Indiana University / Purdue University, Indianapolis
email: aharris@klingon.cs.iupui.edu
homepage: www.cs.iupui.edu/~aharris