Chapter 5. Shapes, Properties, and Binding

Chapter Objectives

After reading this chapter, you'll be able to do the following:

For convenience, shapes are divided into two categories: simple shapes and complex shapes. Simple shapes are self-contained nodes that hold their own geometrical parameters. Complex shapes, in contrast, may refer to other nodes for their coordinates and normals. This chapter also discusses important property nodes, including material, draw-style, and lighting-
style nodes. Other chapter examples illustrate key concepts pertaining to geometric transformations and to binding nodes for materials and normals.

Simple Shapes

All shape nodes are derived from the abstract base class SoShape. Inventor provides the following simple shapes:

  • Cube (you specify the width, height, and depth)

  • Cone (you specify the height and bottom radius)

  • Sphere (you specify the radius)

  • Cylinder (you specify the height and the radius)

Figure 5-1 shows the portion of the class tree that contains shape classes.

Complex Shapes

Complex shapes, such as triangle strip sets and face sets, require at least a set of coordinates. If the lighting is set to PHONG, complex shapes also require a set of surface normals, as shown in Figure 5-2. Coordinates and normals are defined by separate nodes in the scene graph so that this information can be shared by other nodes.

Examples of complex shapes include the following:

  • Face set, indexed face set

  • Line set, indexed line set

  • Triangle strip set, indexed triangle strip set

  • Point set

  • Quad mesh

  • NURBS curve and surface

    Figure 5-1. Shape-Node Classes


An SoCoordinate3 node sets the current coordinates in the rendering state to the specified points. This node contains one field (point), which is of type SoMFVec3f. For example:

SbVec3f 				verts[6];
SoCoordinate3			 *coord = new SoCoordinate3;
// ...Initialize vertices array ...
coord->point.setValues(0, 6, verts);

An SoNormal node sets the current surface normals in the rendering state
to the specified vectors. This node contains one field, vector, of type SoMFVec3f.


Tip: Normals can also be generated automatically by Inventor, in which case you do not need an SoNormal node. See “Generating Normals Automatically” for further information.

Figure 5-2. Nodes Used to Create a Simple Indexed Face Set


Face Set

An SoFaceSet is a shape node that represents a polygonal object formed by constructing faces out of the current coordinates, current normals, current materials, and current textures. It uses the values within each node in the order they are given. (To use coordinates, normals, and materials in a different order, use the SoIndexedFaceSet node, described in the next section.)

Example 5-1 creates an obelisk using a face set composed of eight faces. The scene graph for this example is shown in Figure 5-3. Ignore the normal binding node for now. This node is explained in “Binding Nodes”. “Face-Set Example”shows the image created by this example.

Figure 5-3. Scene Graph for Face Set Example


Example 5-1. Creating a Face Set


//  Eight polygons. The first four are triangles 
//  The second four are quadrilaterals for the sides.
static float vertices[28][3] =
{
   { 0, 30, 0}, {-2,27, 2}, { 2,27, 2},            //front tri
   { 0, 30, 0}, {-2,27,-2}, {-2,27, 2},            //left  tri
   { 0, 30, 0}, { 2,27,-2}, {-2,27,-2},            //rear  tri
   { 0, 30, 0}, { 2,27, 2}, { 2,27,-2},            //right tri
   {-2, 27, 2}, {-4,0, 4}, { 4,0, 4}, { 2,27, 2},  //front quad
   {-2, 27,-2}, {-4,0,-4}, {-4,0, 4}, {-2,27, 2},  //left  quad
   { 2, 27,-2}, { 4,0,-4}, {-4,0,-4}, {-2,27,-2},  //rear  quad
   { 2, 27, 2}, { 4,0, 4}, { 4,0,-4}, { 2,27,-2}   //right quad
};

// Number of vertices in each polygon:
static long numvertices[8] = {3, 3, 3, 3, 4, 4, 4, 4};

// Normals for each polygon:
static float norms[8][3] =
{ 
   {0, .555,  .832}, {-.832, .555, 0}, //front, left tris
   {0, .555, -.832}, { .832, .555, 0}, //rear, right tris
   {0, .0739,  .9973}, {-.9972, .0739, 0},//front, left quads
   {0, .0739, -.9973}, { .9972, .0739, 0},//rear, right quads
};

SoSeparator *
makeObeliskFaceSet()
{
   SoSeparator *obelisk = new SoSeparator();
   obelisk->ref();

   // Define the normals used:
   SoNormal *myNormals = new SoNormal;
   myNormals->vector.setValues(0, 8, norms);
   obelisk->addChild(myNormals);
   SoNormalBinding *myNormalBinding = new SoNormalBinding;
   myNormalBinding->value = SoNormalBinding::PER_FACE;
   obelisk->addChild(myNormalBinding);

   // Define material for obelisk
   SoMaterial *myMaterial = new SoMaterial;
   myMaterial->diffuseColor.setValue(.4, .4, .4);
   obelisk->addChild(myMaterial);

   // Define coordinates for vertices
   SoCoordinate3 *myCoords = new SoCoordinate3;
   myCoords->point.setValues(0, 28, vertices);
   obelisk->addChild(myCoords);

   // Define the FaceSet
   SoFaceSet *myFaceSet = new SoFaceSet;
   myFaceSet->numVertices.setValues(0, 8, numvertices);
   obelisk->addChild(myFaceSet);

   obelisk->unrefNoDelete();
   return obelisk;
}


Tip: When you construct a scene graph, be sure that you have used as few nodes as possible to accomplish your goals. For example, to create a multifaceted polygonal shape, it's best to put all the coordinates for the shape into one SoCoordinate node and put the description of all the face sets into a single SoFaceSet (or SoIndexedFaceSet) node rather than using multiple nodes for each face.


Indexed Face Set

An SoIndexedFaceSet node is a shape node that represents a polygonal object formed by constructing faces out of the current coordinates, using the current surface normals, current materials, and current texture. In contrast to the SoFaceSet node, this node can use those values in any order. This node class contains four fields with indices that specify the ordering:

coordIndex (SoMFLong) 


contains indices into the coordinates list. These indices connect coordinates to form a set of faces. A value of SO_END_FACE_INDEX (-1) indicates the end of one face and the start of the next face. This field is always used.

materialIndex (SoMFLong) 


contains indices into the current material(s) for the materials of the face set. This field is used only when some type of indexed material binding is specified in the SoMaterialBinding node. See “Binding Nodes”.

normalIndex (SoMFLong) 


contains indices into the current normals for the vertices of the face set. This field is used only when indexed normal binding (either per vertex or per face) is specified in the SoNormalBinding node. See “Binding Nodes”.

textureCoordIndex (SoMFLong) 


contains indices of the texture coordinates that are applied to the shape (see Chapter 7).

Be sure that the indices contained in the indexed face set can actually be found in the coordinates and normals lists, or errors will occur.


Note: If you use the SoShapeHints node to specify that the vertices are counterclockwise, you must specify the vertex indices according to the right-hand rule. The right-hand rule states that if you place the fingers of your right hand around the face following the direction in which the vertices are specified, your thumb points in the general direction of the geometric normal. Alternatively, you can specify the vertices in clockwise order. In this case, the direction of the geometric normal is determined by the left-hand rule.

Example 5-2 creates the first stellation of the dodecahedron from an indexed face set. Each of the twelve intersecting faces is a pentagon. The scene graph diagram for this example is shown in Figure 5-4. “Indexed Face-Set Example” shows the image created by this example.

Figure 5-4. Scene Graph for Indexed Face-Set Example


Example 5-2. Creating an Indexed Face Set


// Positions of all of the vertices:
//
static float vertexPositions[12][3] =
{
   { 0.0000,  1.2142,  0.7453},  // top

   { 0.0000,  1.2142, -0.7453},  // points surrounding top
   {-1.2142,  0.7453,  0.0000},
   {-0.7453,  0.0000,  1.2142}, 
   { 0.7453,  0.0000,  1.2142}, 
   { 1.2142,  0.7453,  0.0000},

   { 0.0000, -1.2142,  0.7453},  // points surrounding bottom
   {-1.2142, -0.7453,  0.0000}, 
   {-0.7453,  0.0000, -1.2142},
   { 0.7453,  0.0000, -1.2142}, 
   { 1.2142, -0.7453,  0.0000}, 

   { 0.0000, -1.2142, -0.7453}, // bottom
};


// Connectivity, information; 12 faces with 5 vertices each },
// (plus the end-of-face indicator for each face):

static long indices[72] =
{
   1,  2,  3,  4, 5, SO_END_FACE_INDEX, // top face

   0,  1,  8,  7, 3, SO_END_FACE_INDEX, // 5 faces about top
   0,  2,  7,  6, 4, SO_END_FACE_INDEX,
   0,  3,  6, 10, 5, SO_END_FACE_INDEX,
   0,  4, 10,  9, 1, SO_END_FACE_INDEX,
   0,  5,  9,  8, 2, SO_END_FACE_INDEX, 

    9,  5, 4, 6, 11, SO_END_FACE_INDEX, // 5 faces about bottom
   10,  4, 3, 7, 11, SO_END_FACE_INDEX,
    6,  3, 2, 8, 11, SO_END_FACE_INDEX,
    7,  2, 1, 9, 11, SO_END_FACE_INDEX,
    8,  1, 5,10, 11, SO_END_FACE_INDEX,

    6,  7, 8, 9, 10, SO_END_FACE_INDEX, // bottom face
};
 
// Colors for the 12 faces
static float colors[12][3] =
{
   {1.0, .0, 0}, { .0,  .0, 1.0}, {0, .7,  .7}, { .0, 1.0,  0},
   { .7, .7, 0}, { .7,  .0,  .7}, {0, .0, 1.0}, { .7,  .0, .7},
   { .7, .7, 0}, { .0, 1.0,  .0}, {0, .7,  .7}, {1.0,  .0,  0}
};

// Routine to create a scene graph representing a dodecahedron
SoSeparator *
makeStellatedDodecahedron()
{
   SoSeparator *result = new SoSeparator;
   result->ref();

   // Define colors for the faces
   SoMaterial *myMaterials = new SoMaterial;
   myMaterials->diffuseColor.setValues(0, 12, colors);
   result->addChild(myMaterials);
   SoMaterialBinding *myMaterialBinding = new SoMaterialBinding;
   myMaterialBinding->value = SoMaterialBinding::PER_FACE;
   result->addChild(myMaterialBinding);

   // Define coordinates for vertices

   // Define coordinates for vertices
   SoCoordinate3 *myCoords = new SoCoordinate3;
   myCoords->point.setValues(0, 12, vertexPositions);
   result->addChild(myCoords);

   // Define the IndexedFaceSet, with indices into
   // the vertices:
   SoIndexedFaceSet *myFaceSet = new SoIndexedFaceSet;
   myFaceSet->coordIndex.setValues(0, 72, indices);
   result->addChild(myFaceSet);

   result->unrefNoDelete();
   return result;
}

Triangle Strip Set

The SoTriangleStripSet node constructs triangle strips out of the vertices located at the current coordinates. It is one of the fastest ways to draw polygonal objects in Inventor. The triangle strip set uses the current coordinates, in order, starting at the index specified by the startIndex field. (If no index is specified, it starts at the first index.)

The numVertices field indicates the number of vertices to use for each triangle strip in the set. The triangle strip set is described as follows:

static long numVertices[2] =
{
   32, // flag
   8   // pole
};
SoTriangleStripSet *myStrips = new SoTriangleStripSet;
myStrips->numVertices.setValues(0, 2, numVertices);

Because the numVertices field contains an array with two values, two triangle strips are created. The first strip (the flag) is made from the first 32 coordinate values. The second strip (the flagpole) is made from the next 8 coordinates. Face 0 determines the vertex ordering—in this case, counterclockwise.


Tip: Triangle strip sets and quad meshes are generally faster to render than face sets.

Example 5-3 shows the code for creating a pennant-shaped flag. Figure 5-5 shows the scene graph for this example. “Triangle Strip Set Example” shows the resulting image.

Figure 5-5. Scene Graph for Triangle Strip Set Example


Example 5-3. Creating a Triangle Strip Set


// Positions of all of the vertices:

static float vertexPositions[40][3] =
{
   {  0,   12,    0 }, {   0,   15,    0},
   {2.1, 12.1,  -.2 }, { 2.1, 14.6,  -.2},
   {  4, 12.5,  -.7 }, {   4, 14.5,  -.7},
   {4.5, 12.6,  -.8 }, { 4.5, 14.4,  -.8},
   {  5, 12.7,   -1 }, {   5, 14.4,   -1},
   {4.5, 12.8, -1.4 }, { 4.5, 14.6, -1.4},
   {  4, 12.9, -1.6 }, {   4, 14.8, -1.6},
   {3.3, 12.9, -1.8 }, { 3.3, 14.9, -1.8},
   {  3,   13, -2.0 }, {   3, 14.9, -2.0}, 
   {3.3, 13.1, -2.2 }, { 3.3, 15.0, -2.2},
   {  4, 13.2, -2.5 }, {   4, 15.0, -2.5},
   {  6, 13.5, -2.2 }, {   6, 14.8, -2.2},
   {  8, 13.4,   -2 }, {   8, 14.6,   -2},
   { 10, 13.7, -1.8 }, {  10, 14.4, -1.8},
   { 12,   14, -1.3 }, {  12, 14.5, -1.3},
   { 15, 14.9, -1.2 }, {  15,   15, -1.2},

   {-.5, 15,   0 }, { -.5, 0,   0},   // the flagpole
   {  0, 15,  .5 }, {   0, 0,  .5},
   {  0, 15, -.5 }, {   0, 0, -.5},
   {-.5, 15,   0 }, { -.5, 0,   0}
};


// Number of vertices in each strip.
static long numVertices[2] =
{
   32, // flag
   8   // pole
};
 
// Colors for the 12 faces
static float colors[2][3] =
{
   { .5, .5,  1 }, // purple flag
   { .4, .4, .4 }, // grey flagpole
};

// Routine to create a scene graph representing a pennant.
SoSeparator *
makePennant()
{
   SoSeparator *result = new SoSeparator;
   result->ref();

   // A shape hints tells the ordering of polygons. 
   // This ensures double-sided lighting.
   SoShapeHints *myHints = new SoShapeHints;
   myHints->vertexOrdering = SoShapeHints::COUNTERCLOCKWISE;
   result->addChild(myHints);

   // Define colors for the strips
   SoMaterial *myMaterials = new SoMaterial;
   myMaterials->diffuseColor.setValues(0, 2, colors);
   result->addChild(myMaterials);
   SoMaterialBinding *myMaterialBinding = new SoMaterialBinding;
   myMaterialBinding->value = SoMaterialBinding::PER_PART;
   result->addChild(myMaterialBinding);

   // Define coordinates for vertices
   SoCoordinate3 *myCoords = new SoCoordinate3;
   myCoords->point.setValues(0, 40, vertexPositions);
   result->addChild(myCoords);

   // Define the TriangleStripSet, made of two strips.
   SoTriangleStripSet *myStrips = new SoTriangleStripSet;
   myStrips->numVertices.setValues(0, 2, numVertices);
   result->addChild(myStrips);

   result->unrefNoDelete();
   return result;
}

Quad Mesh

The SoQuadMesh node constructs quadrilaterals from the vertices located at the current coordinates. It uses the coordinates in order, starting at the index specified by the startIndex field. (If no index is specified, it starts at the first index.)

The verticesPerColumn and verticesPerRow fields indicate the number of vertices in the columns and rows of the mesh. Example 5-4 creates a quad mesh as follows:

SoQuadMesh *myQuadMesh = new SoQuadMesh;
myQuadMesh->verticesPerRow = 12;
myQuadMesh->verticesPerColumn = 5;

Each row in this quad mesh contains 12 vertices. Each column contains 5 vertices. Figure 5-6 shows the scene graph for this example. “Quad Mesh Example” shows the resulting image.

Figure 5-6. Scene Graph for Quad Mesh Example


Example 5-4. Creating a Quad Mesh


// Positions of all of the vertices:

static float vertexPositions[160][3] =
{  // 1st row
   {-13.0,  0.0, 1.5}, {-10.3, 13.7, 1.2}, { -7.6, 21.7, 1.0}, 
   { -5.0, 26.1, 0.8}, { -2.3, 28.2, 0.6}, { -0.3, 28.8, 0.5},
   {  0.3, 28.8, 0.5}, {  2.3, 28.2, 0.6}, {  5.0, 26.1, 0.8}, 
   {  7.6, 21.7, 1.0}, { 10.3, 13.7, 1.2}, { 13.0,  0.0, 1.5},
   // 2nd row
   {-10.0,  0.0, 1.5}, { -7.9, 13.2, 1.2}, { -5.8, 20.8, 1.0}, 
   { -3.8, 25.0, 0.8}, { -1.7, 27.1, 0.6}, { -0.2, 27.6, 0.5},
   {  0.2, 27.6, 0.5}, {  1.7, 27.1, 0.6}, {  3.8, 25.0, 0.8}, 
   {  5.8, 20.8, 1.0}, {  7.9, 13.2, 1.2}, { 10.0,  0.0, 1.5},
   // 3rd row
   {-10.0,  0.0,-1.5}, { -7.9, 13.2,-1.2}, { -5.8, 20.8,-1.0}, 
   { -3.8, 25.0,-0.8}, { -1.7, 27.1,-0.6}, { -0.2, 27.6,-0.5},
   {  0.2, 27.6,-0.5}, {  1.7, 27.1,-0.6}, {  3.8, 25.0,-0.8}, 
   {  5.8, 20.8,-1.0}, {  7.9, 13.2,-1.2}, { 10.0,  0.0,-1.5},
   // 4th row 
   {-13.0,  0.0,-1.5}, {-10.3, 13.7,-1.2}, { -7.6, 21.7,-1.0}, 
   { -5.0, 26.1,-0.8}, { -2.3, 28.2,-0.6}, { -0.3, 28.8,-0.5},
   {  0.3, 28.8,-0.5}, {  2.3, 28.2,-0.6}, {  5.0, 26.1,-0.8}, 
   {  7.6, 21.7,-1.0}, { 10.3, 13.7,-1.2}, { 13.0,  0.0,-1.5},
   // 5th row
   {-13.0,  0.0, 1.5}, {-10.3, 13.7, 1.2}, { -7.6, 21.7, 1.0}, 
   { -5.0, 26.1, 0.8}, { -2.3, 28.2, 0.6}, { -0.3, 28.8, 0.5},
   {  0.3, 28.8, 0.5}, {  2.3, 28.2, 0.6}, {  5.0, 26.1, 0.8}, 
   {  7.6, 21.7, 1.0}, { 10.3, 13.7, 1.2}, { 13.0,  0.0, 1.5}
};

// Routine to create a scene graph representing an arch.
SoSeparator *
makeArch()
{
   SoSeparator *result = new SoSeparator;
   result->ref();

   // Define the material
   SoMaterial *myMaterial = new SoMaterial;
   myMaterial->diffuseColor.setValue(.78, .57, .11);
   result->addChild(myMaterial);

   // Define coordinates for vertices
   SoCoordinate3 *myCoords = new SoCoordinate3;
   myCoords->point.setValues(0, 60, vertexPositions);
   result->addChild(myCoords);

   // Define the QuadMesh.
   SoQuadMesh *myQuadMesh = new SoQuadMesh;
   myQuadMesh->verticesPerRow = 12;

   myQuadMesh->verticesPerColumn = 5;
   result->addChild(myQuadMesh);

   result->unrefNoDelete();
   return result;
}

Property Nodes

This section describes a number of important property classes, all of which are derived from SoNode:

  • SoMaterial, which sets the ambient color, diffuse color, specular color, emissive color, shininess, and transparency of the current material

  • SoDrawStyle, which tells shape nodes which drawing technique to use during rendering

  • SoLightModel, which tells shape nodes how to compute lighting calculations during rendering

  • SoEnvironment, which allows you to simulate various atmospheric effects, such as fog, haze, pollution, and smoke, and to describe other global environmental attributes such as ambient lighting and light attenuation

  • SoShapeHints, which provides additional information regarding vertex shapes to allow Inventor to optimize certain rendering features

  • SoComplexity, which allows you to specify the extent to which shape objects are subdivided into polygons, as well as the general degree of texture complexity and level of detail

  • SoUnits, which allows you to define a standard unit of measurement for all subsequent shapes in the scene graph

Each of these classes affects different elements of the rendering state, as described later in this section. Figure 5-7 shows the portion of the class tree for property nodes.

Figure 5-7. Property-Node Classes


Material Node

An SoMaterial node includes the following fields:

ambientColor (SoMFColor) 


reflected color of an object in response to the ambient lighting in the scene. The default value for this field is [0.2, 0.2, 0.2].

diffuseColor (SoMFColor) 


an object's base color. The default value for this field is
[0.8, 0.8, 0.8].

specularColor (SoMFColor) 


reflective quality of an object's highlights. The default value for this field is [0.0, 0.0, 0.0].

emissiveColor (SoMFColor) 


light produced by an object. The default value for this field is [0.0, 0.0, 0.0].

shininess (SoMFFloat) 


degree of shininess of an object's surface, ranging from 0.0 for a diffuse surface with no shininess to a maximum of 1.0 for a highly polished surface. The default value for this field is 0.2.

transparency (SoMFFloat) 


degree of transparency of an object's surface, ranging from 0.0 for an opaque surface to 1.0 for a completely transparent surface. The default value for this field is 0.0.


Tip: The transparency type is specified in the render action (see Chapter 9).

An example of setting values in an SoMaterial node is the following:

SoMaterial *gold	  = new SoMaterial;
//Set material values
gold->ambientColor.setValue(.3, .1, .1);
gold->diffuseColor.setValue(.8, .7, .2);
gold->specularColor.setValue(.4, .3, .1);
gold->shininess = .4;

Since gold is opaque, you can use the default value of 0.0 for the transparency field.

SoBaseColor, another class derived from SoNode, replaces only the diffuse color field of the current material and has no effect on other material fields.


Tip: If you are changing only the diffuse color of an object, use an SoBaseColor node in place of an SoMaterial node. For example, to represent a complex terrain that uses many different diffuse colors, use one SoMaterial node for the ambient, specular, and emissive color values, and then use one SoBaseColor node with multiple values for the changing diffuse colors. The SoBaseColor class is also useful when the light model is BASE_COLOR (see “Light-Model Node”).


Draw-Style Node

An SoDrawStyle node includes the following fields:

style (SoSFEnum) 

current drawing style. Values for this field are

 

SoDrawStyle::FILLED
filled regions (default)

SoDrawStyle::LINES
nonfilled outlines

SoDrawStyle::POINTS
points

SoDrawStyle::INVISIBLE
not drawn at all

pointSize (SoSFFloat) 


(for POINTS style) radius of points, in printer's points. The default value is 0.0. A value of 0.0 indicates to use the fastest value for rendering, which is typically 1.0. If this value is not 0.0, the point size is scaled by the amount required to keep it a constant size, which depends on the pixels per inch of the viewport region.


Tip: Draw-style LINES and POINTS look best with a BASE_COLOR lighting model.


lineWidth (SoSFFloat) 


(for LINES style) line width, in printer's points (1 inch = 72.27 printer's points). Values can range from 0.0 to 256.0. The default value is 0.0, which indicates to use the fastest value for rendering.

linePattern (SoSFUShort) 


(for LINES style) current line-stipple pattern. Values can range from 0 (invisible) to 0xffff (solid). The default value is 0xffff.

“Drawing Styles (FILLED, LINES, POINTS)” shows the same object rendered in different drawing styles.

Light-Model Node

An SoLightModel node includes the following field:

model (SoSFEnum)  


current lighting model applied to all subsequent shape nodes in the scene graph. The lighting model tells the shape node how to compute lighting calculations during rendering. Values for this field are as follows:

 

SoLightModel::BASE_COLOR
ignores light sources and uses only the diffuse color and transparency of the current material.

 

SoLightModel::PHONG
uses the OpenGL Phong lighting model, which takes into account all light sources in the scene and the object's surface orientation with respect to the lights. This lighting model (the default) usually requires at least one light in the scene. (There may be emissive color and ambient lighting also.)


Note: In Inventor, shading (such as Gouraud or flat) is dictated by the combination of the material specification of the object, the lighting model, and the normal bindings. A shading model is not explicitly specified.

Figure In-5 and Figure In-6 show the same scene with the different lighting models. (Figure In-5 uses BASE_COLOR, and Figure In-6 uses PHONG.)

SoMaterial and SoBaseColor can be used along with any drawing style and any lighting model. In some cases, however, some of the material attributes might be ignored. For example, if you specify BASE_COLOR for the SoLightModel model field, only the diffuse color and transparency of the current material are used. But what happens if you specify only a base color (with SoBaseColor) and subsequently select the Phong lighting model for SoLightModel? In this case, Inventor uses the base color for the diffuse color and the default or current material element values for the other SoMaterial fields.


Note: By default, the light model is PHONG. For images to render correctly, you need to specify normals and light sources. If you want to see only colored objects, change the light model to BASE_COLOR and use SoBaseColor to specify only the base (diffuse) color.


Environment Node

You can use the SoEnvironment node to simulate various atmospheric effects such as fog, haze, pollution, and smoke. For general purposes, these atmospheric effects are grouped under the term fog. The difference between fog and haze, for example, is simply the color and density.

Specifically, the SoEnvironment node allows you to specify the color and intensity of the ambient lighting, the light attenuation for point lights and spotlights, and the type, color, and visibility factor for fog. Figure In-7 shows the effects of an SoEnvironment node. This image uses a value of FOG for the fog type. The fogColor is (0.2, 0.2, 0.46).

An SoEnvironment node includes the following fields:

ambientIntensity (SoSFFloat) 


intensity of ambient light in the scene. This field is used with Phong lighting.

ambientColor (SoSFColor) 


color of ambient light in the scene. This field is used with Phong lighting.

attenuation (SoSFVec3f) 


defines how light drops off with distance from a light source. You can specify squared, linear, and constant attenuation coefficients with respect to the distance of the light from the object's surface. (The three components of the vector are the squared, linear, and constant coefficients, in that order.) This field is used with Phong lighting.

fogType (SoSFEnum) 


type of fog. Values for this field are

 

SoEnvironment::NONE
no fog (default)

SoEnvironment::HAZE
opacity of the fog increases linearly with the distance from the camera

SoEnvironment::FOG
opacity of the fog increases exponentially with the distance from the camera

 

SoEnvironment::SMOKE
increase in fog opacity is an exponential-squared increase with the distance from the camera

fogColor (SoSFColor) 


color of the fog.

fogVisibility (SoSFFloat) 


the distance at which fog totally obscures the objects in the scene. For the default value (0.0), this distance is adjusted to equal the far plane of the camera. Otherwise, it is used as is.


Tip: For realistic scenes, clear the window to the fog color before drawing the fogged objects (see the SoXtRenderArea::setBackgroundColor() method.)


Shape-Hints Node

By default, Inventor does not assume anything about how the vertices in a vertex shape are ordered, whether its surface is closed or open, or whether the faces of the shape are convex or concave. If you know that the vertices are in a consistent order, that the shape is closed, or that the shape faces are convex, you can use the SoShapeHints node to notify Inventor so that it can optimize certain rendering features.

The SoShapeHints node has four fields:

vertexOrdering (SoSFEnum)  


provides hints about the ordering of the faces of a vertex-based shape derived from SoVertexShape. This field describes the ordering of all the vertices of all the faces of the shape when it is viewed from the outside.

 

Values for this field are

 

SoShapeHints::UNKNOWN_ORDERING
the ordering of the vertices is not known (the default)

SoShapeHints::CLOCKWISE
the vertices for each face are specified in clockwise order

SoShapeHints::COUNTERCLOCKWISE
the vertices for each face are specified in counterclockwise order

shapeType (SoSFEnum) 


SoShapeHints::UNKNOWN_SHAPE_TYPE
the shape type is not known (the default)

SoShapeHints::SOLID
the shape is a solid object (not an open surface)

faceType (SoSFEnum) 


SoShapeHints::UNKNOWN_FACE_TYPE
the face type is not known

SoShapeHints::CONVEX
all faces of the shape are convex
(the default)

creaseAngle (SoSFFloat) 


used for automatic normal generation. See “Generating Normals Automatically”.

If the shapeType is SOLID and the vertexOrdering is either CLOCKWISE or COUNTERCLOCKWISE, Inventor turns on backface culling and turns off two-sided lighting. If the shapeType is not SOLID and the vertexOrdering is either CLOCKWISE or COUNTERCLOCKWISE, Inventor turns off backface culling and turns on two-sided lighting. In all other cases, backface culling and two-sided lighting are both off. If you use the SoShapeHints node, be sure to describe the object accurately; otherwise, objects may be rendered incorrectly.


Tip: In general, the more information you specify with the shape-hints node, the faster the rendering speed. The exception to this rule is when shapeType is not SOLID and the vertexOrdering is either CLOCKWISE or COUNTERCLOCKWISE. In this case, rendering may be slower because two-sided lighting is automatically turned on and backface culling is turned off.


Complexity Node

Use the SoComplexity node to indicate the amount of subdivision into polygons for subsequent shape nodes in the scene graph. This node has three fields:

type (SoSFEnum) 


general type of complexity. Values for this field are

 

SoComplexity::OBJECT_SPACE
(the default) bases the subdivision on the object itself, regardless of where it is on the screen or which parts are closer to the viewer.

SoComplexity::SCREEN_SPACE
bases the complexity on the amount of screen space occupied by the object. Objects requiring the full screen require more detail; small objects require less detail. The result is that objects that are closer to the viewer usually receive more detail than objects that are farther away. This type of complexity is more expensive to compute than the others. In addition, it invalidates the render cache when the camera moves (see the discussion of render caching in Chapter 9).

SoComplexity::BOUNDING_BOX
renders a bounding box in place of the shape. This type is used for speed, when exact shapes are not required. It uses the current drawing style to render the box.

value (SoSFFloat) 


a value that provides a hint about the amount of subdivision desired, where 0.0 is minimum complexity and 1.0 is maximum complexity. The default is 0.5.

textureQuality (SoSFFloat) 


a value that provides a hint about the quality of texture mapping used on the object. The trade-off is between speed of rendering and quality of texturing. A value of 0.0 indicates maximum speed (possibly turning off texturing completely), and 1.0 indicates finest texture quality. The default is 0.5.

“Specifying Different Levels of Complexity (left: OBJECT_SPACE; right: SCREEN_SPACE)” shows the same object with different levels of complexity. The spheres at the left use object-space complexity and a complexity value of .5. The spheres at the right use screen-space complexity and a complexity value of .06. The NURBS examples in Chapter 8 use the SoComplexity node.


Tip: Simpler scenes render more quickly than complex scenes. For example, to increase rendering speed, use fewer lights, turn off textures or specify a lower texture-quality value, and choose a simpler drawing style, such as wireframe, and a lower complexity value. The viewer pop-up menu allows you to disable certain of these features for faster rendering.


Units Node

Inventor lets you define your data in a variety of different units. It uses meters as its default units, but you can use the SoUnits node to specify a different unit of measurement. The units node acts like a scale node by scaling subsequent shapes into the specified units. SoUnits can adjust the amount it scales an object by checking to see if any other units have been defined. The units node adjusts the scale so that the previously defined units are no longer in effect.

The SoUnits node has one field:

units (SoSFEnum) 

defines the current unit of measurement to be applied to all subsequent shapes in the scene graph. Possible values are as follows:

SoUnits::METERS
SoUnits::CENTIMETERS
SoUnits::MILLIMETERS
SoUnits::MICROMETERS
SoUnits::MICRONS
SoUnits::NANOMETERS
SoUnits::ANGSTROMS
SoUnits::KILOMETERS
SoUnits::FEET
SoUnits::INCHES
SoUnits::POINTS
SoUnits::YARDS
SoUnits::MILES
SoUnits::NAUTICAL_MILES

To render your data in units other than these, use an SoUnits node to set the current units back to meters, followed by a scale node that scales from meters into the desired units.

Binding Nodes

Materials and normals are bound to shape nodes in different ways. The first part of this discussion focuses on material binding, which is how the current materials specified in an SoMaterial node are mapped onto the geometry of the shape nodes that use that particular material. Since normal binding is analogous to material binding, this initial discussion focuses on material binding. (See Example 5-1 earlier in this chapter for an example of using a normal binding node.)

An SoMaterialBinding node contains a value that describes how to bind materials to shapes. These values include the following:

SoMaterialBinding::DEFAULT 


uses the “best” binding for each shape. Most shapes interpret this binding as OVERALL.

SoMaterialBinding::NONE 


uses no material.

SoMaterialBinding::OVERALL 


uses the first current material for the entire shape.

SoMaterialBinding::PER_PART 


binds one material to each part in the shape. The definition of part depends on the shape. For face sets and cubes, a part is a face. For line sets, a part is a line segment. For cylinders, a part is the sides, top, or bottom.

SoMaterialBinding::PER_PART_INDEXED 


binds one material to each part by index.

SoMaterialBinding::PER_FACE 


binds one material to each face in the shape.

SoMaterialBinding::PER_FACE_INDEXED 


binds one material to each face by index (for indexed vertex shapes).

SoMaterialBinding::PER_VERTEX 


binds one material to each vertex in the shape.

SoMaterialBinding::PER_VERTEX_INDEXED 


binds one material to each vertex by index (for indexed vertex shapes).

Each shape node interprets the binding type somewhat differently. For example, an SoSphere node does not have parts, faces, or indices, so those binding types (PER_PART, PER_FACE, PER_VERTEX) are meaningless for spheres. You can regard the value specified in the material-binding node as a hint to the shape about binding. If you specify a value that makes no sense for a particular shape, such as PER_FACE for a cylinder, the shape interprets the information the best it can (in this case, it uses OVERALL, since a cylinder has no faces). See the Open Inventor C++ Reference Manual for information on how each shape interprets the different binding types.

Suppose you specify PER_PART for a cylinder. The cylinder has three parts (sides, top, bottom). If the current material contains three values—for example, orange, purple, yellow—those values are used for the three parts of the cylinder, producing orange sides, a purple top, and a yellow bottom. But what happens if the number of current materials is greater than the number of parts? As you might guess, Inventor simply ignores the extra materials if they're not required. (If the current material list contains five values, your cylinder ignores the last two values.)

If the current material contains fewer values than the binding requires, Inventor cycles through the current values as often as needed. For example, if you specify PER_FACE for a cube and the current materials list contains three values (violet, periwinkle, teal), the results are as follows:

Face 1 

violet

Face 2 

periwinkle

Face 3 

teal

Face 4 

violet

Face 5 

periwinkle

Face 6 

teal

Indexed Binding

So far, you've been using the values in the current material in order. You can, however, also use the current material values in a new order if you specify either PER_FACE_INDEXED or PER_VERTEX_INDEXED for an indexed vertex shape or PER_PART_INDEXED for a shape that has parts. When you use these types of binding, Inventor refers to the materials-index field of the shape node (for example, SoIndexedFaceSet, SoIndexedLineSet). Instead of starting with the first material and working through the list, Inventor indexes into the materials list in whatever order you specify.

As an example, consider a tetrahedron, represented as an SoIndexedFaceSet. The current materials list (in an SoMaterial node) contains the following values:

Material List

0 

peach

1 

khaki

2 

white

and the materialIndex field (in an SoIndexedFaceSet node) contains these values:

Material Index

1

1

0

2

If you specify PER_FACE (not indexed), Inventor ignores the materialIndex field and cycles through the materials list in order:

Face 1 

peach

Face 2 

khaki

Face 3 

white

Face 4 

peach

On the other hand, if you specify PER_FACE_INDEXED, Inventor uses the materialIndex field to pull values out of the materials list as follows:

Face 1 

khaki

Face 2 

khaki

Face 3 

peach

Face 4 

white

This indexing is economical, since you can use a single, small set of materials for a wide variety of objects and purposes.

Binding per Vertex

Inventor offers two types of per-vertex binding: PER_VERTEX and PER_VERTEX_INDEXED. With nonindexed material binding per vertex, Inventor simply selects materials in order from the materials list and assigns a material to each vertex of the shape node. It then interpolates the materials between the vertices and across the faces of the shape.

Nuances (Advanced)

An SoMaterial node contains six fields, each of which holds multiple values. However, the number of values in these six fields may not be equal. You might have five different values in the ambient, diffuse, specular, and emissive fields, but only two values in the shininess field and one in the transparency field. In such cases, Inventor chooses a cycle equal to the field with the greatest number of values (in this case, five). In a field with fewer values, its last value is repeated until the end of the cycle.

When PER_VERTEX binding is specified, a value of -1 (the default) for the materialIndex field or the normalIndex field in an SoIndexedFaceSet (or any other indexed shape node) indicates to use the coordinate indices for materials or normals. The defined constants SO_END_LINE_INDEX, SO_END_FACE_INDEX, and SO_END_STRIP_INDEX can be used for this specification. This saves time and space and ensures that the indices match up. When you use a “special” coordinate index (such as SO_END_FACE_INDEX), the corresponding material index is skipped over so that the arrays of indices match.


Tip: For better performance, use PER_FACE or PER_FACE_INDEXED binding with one material node and one face-set node that defines multiple polygons, instead of OVERALL binding with multiple material nodes and multiple face set nodes.


Using a Material-Binding Node

Example 5-5 illustrates different types of material binding using the dodecahedron created in Example 5-2 (the common code has been omitted here). The scene graph for the example is shown in Figure 5-8. When you run the program, you can type a number to select the type of material binding, as follows:

  • 0 for PER_FACE (see Figure In-8)

  • 1 for PER_VERTEX_INDEXED (see Figure In-9)

  • 2 for PER_FACE_INDEXED (see Figure In-10)

    Figure 5-8. Scene Graph for Material Binding Example


    Example 5-5. Using Different Material Bindings


       // Which material to use to color the faces 
       // half red & half blue
       static long materialIndices[12] = {
          0, 0, 0, 0, 0, 0,
          1, 1, 1, 1, 1, 1,
       };
    
       switch(whichBinding) {
         case 0:
           // Set up binding to use a different color for each face 
           myBinding->value = SoMaterialBinding::PER_FACE;
           break;
         case 1:
           // Set up binding to use a different color at each 
           // vertex, BUT, vertices shared between faces will 
           // have the same color.
           myBinding->value = SoMaterialBinding::PER_VERTEX_INDEXED;
           break;
         case 2:
           myBinding->value = SoMaterialBinding::PER_FACE_INDEXED;
           myIndexedFaceSet->materialIndex.setValues(
                    0, 12, materialIndices);
           break;
       }
    

Normal Binding

Normals are bound to shapes in almost the same manner as materials. The type of normal binding specified in an SoNormalBinding node is a hint to the shape node about how to apply the current normals to that shape. Indexed shape nodes such as SoIndexedFaceSet and SoIndexedTriangle-
StripSet
contain a normalIndex field used to store indices into the normals list (in an SoNormal node). If the type of binding specified does not require indices (for example, PER_VERTEX), the normalIndex field is not used.

The main difference between indexed normals and indexed materials is that indexed normals do not cycle. If used, normals must match up exactly with the faces, vertices, or parts of the object. If the normals do not match exactly, then default normals are generated (see the following section). You must specify enough normals to bind to faces, parts, or vertices.

Generating Normals Automatically

Normals can be generated automatically for any shape derived from SoVertexShape. Because this process involves a great deal of computation, we recommend that you use automatic caching or explicitly turn on render caching so that the results are saved and can be reused (see Chapter 9 for more information on caching). Inventor generates normals automatically if needed for rendering and

  • DEFAULT normal binding is used and

  • You do not specify any normals or the number of normals is different from the number of vertices

When Inventor generates normals automatically, it looks at the creaseAngle field of the SoShapeHints node. The crease angle is defined as the angle between the normals for two adjoining faces. This angle indicates the maximum angle size at which separate normals are drawn for adjoining faces. For example, if the crease angle is one radian and the normals for two adjoining faces form an angle less than or equal to one radian, the faces share the same normal, which causes the edge to be shaded smoothly. If the normals for the faces form an angle greater than one radian, Inventor calculates separate normals for each face, which creates a crease. If you want an object to appear sharply faceted, specify 0 as the creaseAngle. If you want an object to appear completely smooth, specify PI as the creaseAngle.

Transformations

Unlike other property nodes, transformation nodes do not replace the current geometric transformation element in the action state. Instead, they have a cumulative effect on the current geometric transformation. In Figure 5-9, for example, the transformations in node xfm1 are applied first, followed by the transformations in node xfm2.

Figure 5-9. Cumulative Effect of Transformation Nodes


The cube is affected by only the transformation in xfm1. The sphere, however, is affected by both xfm1 and xfm2.

SoTransform Node

An SoTransform node includes the following fields:

translation (SoSFVec3f) 


the translation in x, y, and z. The default value is
[0.0 0.0 0.0].

rotation (SoSFRotation) 


the rotation in terms of an axis and an angle. The default value is [0.0 0.0 1.0], 0.0.

scaleFactor (SoSFVec3f) 


the scaling factor in x, y, and z. The default value for this field is [1.0 1.0 1.0].

scaleOrientation (SoSFRotation) 


the rotation to apply before the scale is applied. The default value is [0.0 0.0 1.0], 0.0.

center (SoSFVec3f) 


the center point for rotation and scaling. The default value for this field is [0.0 0.0 0.0].


Tip: If you are using only one of the fields in an SoTransform node, you can substitute the corresponding “lightweight” version. For rotations, use SoRotation or SoRotationXYZ; for translations, use SoTranslation; and for scaling, use SoScale.


Order of Transformations

Within each SoTransform node, the fields are applied so that the last field in the node (the center) affects the shape object first. The order is first the center, followed by the scale orientation, the scaling factor, the rotation, and the translation.

Figure 5-10 and “Effects of Ordering Transformation Fields” show how different ordering of transformations produces different results. At the left of , “Effects of Ordering Transformation Fields” the temple is scaled, rotated, and then translated. The transform node closest to the shape object affects the object first. You thus need to read backward through the code to see how the effects of the transformations are felt. At the right of , “Effects of Ordering Transformation Fields” the temple is rotated, then scaled and translated. Example 5-6 shows the code for the two sets of transformations.

Figure 5-10. Two Groups with Transformations in Different Order


Example 5-6. Changing the Order of Transformations


#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <Inventor/SoDB.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoRotationXYZ.h>
#include <Inventor/nodes/SoScale.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoTranslation.h>

main(int, char **argv)
{
   // Initialize Inventor and Xt
   Widget myWindow = SoXt::init(argv[0]);
   if (myWindow == NULL) exit(1);

   SoSeparator *root = new SoSeparator;
   root->ref();

   // Create two separators, for left and right objects.
   SoSeparator *leftSep = new SoSeparator;
   SoSeparator *rightSep = new SoSeparator;
   root->addChild(leftSep);
   root->addChild(rightSep);
   // Create the transformation nodes.
   SoTranslation *leftTranslation  = new SoTranslation;
   SoTranslation *rightTranslation = new SoTranslation;
   SoRotationXYZ *myRotation = new SoRotationXYZ;
   SoScale *myScale = new SoScale;

   // Fill in the values.
   leftTranslation->translation.setValue(-1.0, 0.0, 0.0);
   rightTranslation->translation.setValue(1.0, 0.0, 0.0);
   myRotation->angle = M_PI/2;   // 90 degrees
   myRotation->axis = SoRotationXYZ::X;
   myScale->scaleFactor.setValue(2., 1., 3.);

   // Add transforms to the scene.
   leftSep->addChild(leftTranslation);   // left graph
   leftSep->addChild(myRotation);        // then rotated
   leftSep->addChild(myScale);           // first scaled

   rightSep->addChild(rightTranslation); // right graph
   rightSep->addChild(myScale);          // then scaled
   rightSep->addChild(myRotation);       // first rotated

   // Read an object from file. (as in example 4.2.Lights)
   SoInput myInput;
   if (!myInput.openFile("temple.iv")) 
      return (1);
   SoSeparator *fileContents = SoDB::readAll(&myInput);
   if (fileContents == NULL) return (1);

   // Add an instance of the object under each separator.
   leftSep->addChild(fileContents);
   rightSep->addChild(fileContents);

   // Construct a renderArea and display the scene.
   SoXtExaminerViewer *myViewer = 
            new SoXtExaminerViewer(myWindow);
   myViewer->setSceneGraph(root);
   myViewer->setTitle("Transform Ordering");
   myViewer->viewAll();
   myViewer->show();

   SoXt::show(myWindow);
   SoXt::mainLoop();
}