2.4. Implementing Actions

Your next task is to implement each of the actions your new node supports. The SoDrawStyle( C++ | Java | .NET ) node, as you have already seen, supports two actions, the SoGLRenderAction( C++ | Java | .NET ) and the SoCallbackAction( C++ | Java | .NET ), in addition to the SoSearchAction( C++ | Java | .NET ) and the SoWriteAction( C++ | Java | .NET ), which it inherits from SoNode( C++ | Java | .NET ).

[Tip]

Do not apply a new action within another action (because caching will not function properly). Also, if you are creating a new node, do not modify the node (for example, call setValue() on a field) within an action method.

For the GL render action, the SoDrawStyle( C++ | Java | .NET ) node changes the values of four elements in the state based on the value of the corresponding fields. For example, if its style field has a value of INVISIBLE, it changes the value of the SoDrawStyleElement( C++ | Java | .NET ) in the state to INVISIBLE. The corresponding code to set the element's value is


C++
if (! style.isIgnored())
   SoDrawStyleElement::set(state, this,
                           (SoDrawStyleElement::Style)
                           style.getValue());

For the callback action, the SoDrawStyle( C++ | Java | .NET ) node does the same thing: it sets the value of the element based on the value of the corresponding field in the node.

Since the two actions perform exactly the same tasks, this common code is put into a separate method that can be called by both the GL render and the callback actions. By convention, this shared method used by property nodes is called doAction() (which is a virtual method on SoNode( C++ | Java | .NET )). The code for the draw-style node's callback action is


C++
void
SoDrawStyle::callback(SoCallbackAction *action)
(
   doAction(action);
}

The code for the draw-style node's GL render action is also simple (and familiar):


C++
void
SoDrawStyle::GLRender(SoGLRenderAction *action)
(
   doAction(action);
}

To complete the story, here is the complete code for the draw-style node's doAction() method:


C++
void
SoDrawStyle::doAction(SoAction *action)
{
   SoState   *state = action->getState();
   
   if (! style.isIgnored())
      SoDrawStyleElement::set(state, this,
                              (SoDrawStyleElement::Style)
                              style.getValue());
   if (! lineWidth.isIgnored())
       SoLineWidthElement::set(state, this,
                               lineWidth.getValue());
   if (! linePattern.isIgnored())
       SoLinePatternElement::set(state, this,
                                 linePattern.getValue());
   if (! pointSize.isIgnored())
       SoPointSizeElement::set(state, this,
                               pointSize.getValue());
}

The advantage of this scheme becomes apparent when you consider extending the set of actions (see Chapter 4). You can define a new action class and implement a static method for SoNode( C++ | Java | .NET ) that calls doAction(). Then all properties that implement doAction() will perform the appropriate operation without needing any static methods for them.

As discussed in Chapter 1, each element class provides methods for setting and inquiring its value. The static set() method usually has three parameters, as shown in the previous section:

Most element classes also define a static get() method that returns the current value stored in an element instance. For example, to obtain the current draw style:


C++
style = SoDrawStyleElement::get(action->getState());

Elements that have multiple values may define a different sequence of get() methods. For example, the material color elements and coordinate element can contain many values. In these cases, the element class defines three methods:

Elements are designed to be small and specific, for two reasons. The first reason is that it should be possible for a node to change one aspect of the state without having to change any of the rest, including related elements. For example, the SoBaseColor( C++ | Java | .NET ) node changes only the SoDiffuseColorElement without affecting any other material elements. The second reason has to do with caching. It is easy to determine when any element's value has changed, since (typically) the whole element changes at once. Therefore, determining which nodes affect a cache is a straightforward task.

However, some elements are related to each other, and it's good to deal with them together for convenience and efficiency. Classes called bundles provide simple interfaces to collections of related elements.

Supported Inventor bundle classes are

The SoMaterialBundle class accesses all elements having to do with surface materials. Methods on the bundle allow shapes to step easily through sequential materials and to send the current material to OpenGL. The SoNormalBundle allows you to step easily through sequential normals and provides routines for generating default normals. The SoTextureCoordinateBundle allows you to step through texture coordinates and provides methods for generating texture coordinates if the shape is using SoTextureCoordinatePlane( C++ | Java | .NET ) or SoTextureCoordinateEnvironment( C++ | Java | .NET ).