5.4. 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 SoMaterial 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, “ Creating a Face Set earlier in this chapter for an example of using a normal binding node.)

An SoMaterialBinding SoMaterialBinding 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 SoSphere 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 SoIndexedFaceSet SoIndexedFaceSet , SoIndexedLineSet SoIndexedLineSet 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 SoIndexedFaceSet SoIndexedFaceSet . The current materials list (in an SoMaterial SoMaterial SoMaterial node) contains the following values:

Material List

 0 peach 1 khaki 2 white

and the materialIndex field (in an SoIndexedFaceSet SoIndexedFaceSet 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.

An SoMaterial SoMaterial 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 SoIndexedFaceSet 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.8, “ Using Different Material Bindings illustrates different types of material binding using the dodecahedron created in Example 5.2, “ Creating an Indexed Face Set (the common code has been omitted here). The scene graph for the example is shown in Figure 5.23, “ Scene Graph for Material Binding Example. 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 B.7, “ Plate 7)

• 1 for PER_VERTEX_INDEXED (see Figure B.8, “ Plate 8)

• 2 for PER_FACE_INDEXED (see Figure B.9, “ Plate 9)

Example 5.8.  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;
}
```
```// Which material to use to color the faces
// half red & half blue
static int[] materialIndices = new int[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.SetValue((int)SoMaterialBinding.Bindings.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.SetValue((int)SoMaterialBinding.Bindings.PER_VERTEX_INDEXED);
break;
case 2:
myBinding.value.SetValue((int)SoMaterialBinding.Bindings.PER_FACE_INDEXED);
myIndexedFaceSet.materialIndex.SetValues(0, materialIndices);
break;
}
```
```// Which material to use to color the faces
// half red & half blue
int[] materialIndices = new int[]
{
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.setValue(SoMaterialBinding.Bindings.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.setValue(SoMaterialBinding.Bindings.PER_VERTEX_INDEXED);
break;
case 2:
myBinding.value.setValue(SoMaterialBinding.Bindings.PER_FACE_INDEXED);
myIndexedFaceSet.materialIndex.setValues(0, 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 SoNormalBinding 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 SoIndexedFaceSet SoIndexedFaceSet and SoIndexedTriangle- StripSet contain a normalIndex field used to store indices into the normals list (in an SoNormal SoNormal 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 SoVertexShape 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 8, Applying Actions 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 SoShapeHints 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.