2.4. Implementing Actions

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

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.

The doAction() Method

For the GL render action, the SoDrawStyle SoDrawStyle SoDrawStyle 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 SoGLDrawStyleElement SoGLDrawStyleElement SoGLDrawStyleElement in the state to INVISIBLE. The corresponding code to set the element's value is

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

For the callback action, the SoDrawStyle SoDrawStyle SoDrawStyle 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 SoNode SoNode ). The code for the draw-style node's callback action is

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

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

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

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

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 SoNode SoNode that calls doAction(). Then all properties that implement doAction() will perform the appropriate operation without needing any static methods for them.

Changing and Examining State Elements

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:

  • The state from which to retrieve the element (which the element obtains from the given action)

  • A pointer to the node that is changing the element's value (used for caching purposes and for checking the Override flag)

  • The new value for the element

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:

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:

getInstance()

returns the top instance of the element in the state as a const pointer

getNum()

returns the number of values in the element

get( n )

returns the nth value in the element

Element Bundles

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 SoBaseColor SoBaseColor 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

  • SoMaterialBundle

  • SoNormalBundle

  • SoTextureCoordinateBundle

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 SoTextureCoordinatePlane SoTextureCoordinatePlane or SoTextureCoordinateEnvironment SoTextureCoordinateEnvironment SoTextureCoordinateEnvironment .