Chapter 5. Incompatible Extender API Changes

This chapter is for developers who have customized existing Inventor 2.0 classes or have created subclasses of them. If you have problems porting an application with custom classes or subclasses, this chapter helps you solve them by explaining the new features and changes to the protected and extender methods.


Note: To make your transition to Inventor 2.1 easier, the old version of the API is still supported in some cases. To flush out cases where your code is still using the old version, compile your program with -DIV_STRICT.

This chapter discusses the following topics:

Methods

This section provides information about changed methods, discussing the following topics:

The GLRender() Method

There are two potential changes when working with a GLRender() method:

For additional information, see “How to Convert GLRender() Methods” in “Managing the Material State.”

Overriding Existing GLRender() Methods

Overriding an existing node's GLRender() method by registering a method using SoGLRenderAction::addMethod() no longer works. SoGLRenderAction no longer uses the static action/method table for its traversal, but instead calls GLRender() methods directly (see “Implementing SoSeparator GLRender() Methods Efficiently”).

Instead of calling SoGLRenderAction::addMethod(), implement a subclass of the node(s) with the GLRender() methods you wish to override and use the SoType::overrideType() method in its initClass() routine instead of the normal SO_NODE_INIT_CLASS() macro. See the comments in SoType.h for more information on SoType::overrideType().

Implementing SoSeparator GLRender() Methods Efficiently

To make rendering traversal faster, SoNode now defines four different rendering methods: GLRender(), GLRenderBelowPath(), GLRenderInPath(), and GLRenderOffPath(). By default, the three new methods just call GLRender(). However, for optimal rendering speed, group nodes can redefine GLRender() to call one of the other methods based on the current path code (the SoAction::getPathCode() method returns the current path code).

For example, SoSeparator defines a GLRender() method that calls SoSeparator::GLRenderBelowPath() or SoSeparator::GLRenderInPath(), based on the path code. Once GLRenderBelowPath() is called, traversal is faster, since the path code is guaranteed not to change underneath the separator, and the separator can just call its children's GLRenderBelowPath() methods.

If you have a class derived from SoSeparator and you implemented a GLRender() method for that class, you need to implement GLRenderBelowPath() and GLRenderInPath() methods; otherwise, the methods of the SoSeparator are called and your special rendering code is not executed.

readInstance() Method

This section discusses two changes to the readInstance() method: “Additional Argument to readInstance()” and “Implementation of readInstance() for Group Nodes Changed.”

Additional Argument to readInstance()

If you have node or engine subclasses with readInstance() methods, you have to add an extra argument to your readInstance() method; SoFieldContainer::readInstance() now takes the following arguments:

SoInput *in, unsigned short flags

If you call your base class's readInstance() method, you must pass it the flags argument. Otherwise, you can simply ignore this argument.

Implementation of readInstance() for Group Nodes Changed

If a group node reads children directly, you should have readInstance() code that emulates the readInstance() code of SoGroup and checks to see if children were written in the binary file format, as follows:

// Reads stuff into instance of SoGroup. Returns FALSE on 
// error. Also deals with field data (if any), so this method 
// should also be useful for most subclasses of SoGroup.
//
// Use: protected
SbBool
SoGroup::readInstance(SoInput *in, unsigned short flags)
{
    SbBool readOK = TRUE;
    // First, turn off notification for this node
    SbBool saveNotify = enableNotify(FALSE);
    // Read field info. We can't just call
    // SoNode::readInstance() to read the fields here
    // because we need to tell the SoFieldData that it's ok 
    // if a name is found that is not a valid field name - 
    // it could be the name of a child node.
    SbBool notBuiltIn; // Not used
    readOK = getFieldData()->read(in, this, FALSE, 
                                  notBuiltIn);
    if (!readOK) return readOK;
    // If binary BUT was written without children (which can 
    // happen if it was read as an unknown node and then 
    // written out in binary), don't try to read children:
    if (!in->isBinary() || (flags & IS_GROUP)) 
        	readOK = readChildren(in);
    
    // Re-enable notification
    enableNotify(saveNotify);
    return readOK;
}

This change was made so that binary files containing unknown group nodes with no children are correctly read.

copy() Method

Node copying now works correctly. For example, multiple references to a node (through children, fields, or connections) under the node to be copied are no longer copied individually; multiple instances of a single copy are created instead.

These changes required a change to the extender API. The changes affect you only if you have created a new node class and have implemented a copy() method for it that differs from that of its base class.

In Inventor 2.1, the SoNode::copy() method is no longer virtual. Instead, the copy() method creates an empty node of the correct type and then calls the virtual copyContents() method on it to copy from the existing node. The default implementation of copyContents() for SoNode copies the field values. The implementation for SoGroup copies the children as well. If your node has other (non-field, non-child) information that needs to be copied, redefine copyContents() for it. See “The copyContents() Method” for more information.

Elements

This section looks at element changes and related information in Inventor 2.1, discussing the following topics:

Changes in Shape Hints Element Methods

The SoShapeHintsElement methods setFilled(), setClipped(), isFilled(), isClipped(), getDefaultIsFilled(), and getDefaultIsClipped() are no longer supported.

Changes in Binding Elements

The DEFAULT and NONE values are obsolete for the nodes SoMaterialBinding and SoNormalBinding. The corresponding elements contain enum values for DEFAULT and NONE that make them the same as OVERALL for materials and PER_VERTEX_INDEXED for normals. If the code is compiled with -DIV_STRICT, these elements do not include values for DEFAULT and NONE, and any reference to them causes a compile-time error.

Changes in Texture Elements

The element SoGLTextureQualityElement is obsolete. Use SoTextureQualityElement instead.

The following elements (and their OpenGL counterparts) are replaced by SoTextureImageElement (and its OpenGL counterpart):

  • SoTextureBlendColorElement

  • SoTextureModelElement

  • SoTextureWrapSElement

  • SoTextureWrapTElement

Changes in Material Elements

Several elements that were responsible for material properties have been combined into two elements: SoLazyElement and its derived OpenGL version SoGLLazyElement.

  • SoLazyElement is responsible for setting and getting material state.

  • SoGLLazyElement is responsible for tracking the OpenGL state, and issuing send commands, invalidating caches, and so on.

Both elements are required, because some actions (for example callback actions) do not involve OpenGL. The following elements and their OpenGL counterparts are replaced by the lazy elements:

  • SoDiffuseColorElement

  • SoSpecularColorElement

  • SoAmbientColorElement

  • SoEmissiveColorElement

  • SoTransparencyElement

  • SoShininessElement

  • SoLightModelElement

  • SoGLPolygonStippleElement

  • SoCurrentGLMaterialElement

  • SoGLColorIndexElement

Implementation of Node Override

In Inventor 2.0, the SoElement base class automatically implemented override for all elements. Setting override on a node automatically caused an override flag to be set on every element that node overrode.

In Inventor 2.1, a node has to explicitly implement override. A new element, SoOverrideElement, has methods for getting and setting a bit for each element that can be overridden. Nodes must cooperate with one another by not doing anything if the bit for a particular element is set and by setting the bit for an element if the node's Override flag is set.

Many of the set methods on Inventor's built-in elements no longer take SoNode * parameters. In cases where the only reason for the SoNode * parameter was to test whether the node had its override set, the SoNode * parameter was removed. Elements that are subclasses of SoReplacedElement still take an SoNode * parameter in their set routines for cache dependencies.

This change improves performance and cache dependencies on override elements. Performance is improved because many nodes don't need override and they no longer have to pay for it. Cache dependencies are improved because the setting of the SoOverrideElement sets up the right dependencies.

If you created your own element and want to implement override behavior for it, you can either add and set override methods to the element (this is easier to implement) or implement another element that stores override information (this is better for caching).

Shape Nodes

This section provides information about implementing shape nodes with Inventor 2.1, discussing the following topics:

Generating Normals

The SoNormalGenerator class constructors take an argument that determines whether or not the polygons passed are in clockwise or counterclockwise order. Inventor now correctly generates normals for built-in shapes when the SoShapeHints node sets the vertex ordering to CLOCKWISE.

You don't have to change extender shapes that use SoNormalBundle to generate normals for them. You do have to modify extender shapes that use the SoNormalGenerator class directly to get the vertex ordering out of the ShapeHintsElement and pass TRUE or FALSE to the SoNormalGenerator constructor.

Managing the Material State

Inventor 2.1 has a new, more efficient mechanism for managing the material state during scene traversal. This section first discusses the new SoLazyElement and SoGLLazyElement, then provides instructions for converting GLRender() methods that use the lazy elements.

Using SoLazyElement and SoGLLazyElement

The SoMaterialBundle is still supported for backward compatibility, but you should use SoLazyElement and SoGLLazyElement for improved performance. You also need to use the lazy elements if you are issuing any of the following OpenGL calls:

  • glColor()

  • glMaterial()

  • glColorMaterial()

  • glEnable() or glDisable(), with the following arguments:

    • GL_COLOR_MATERIAL

    • GL_BLEND

    • GL_LIGHTING

    • GL_POLYGON_STIPPLE

  • glPushAttrib() or glPopAttrib(), with the following bits:

    • GL_ENABLE_BIT

    • GL_LIGHTING_BIT

    • GL_POLYGON_BIT

    • GL_POLYGON_STIPPLE_BIT

The lazy element tracks the OpenGL state for all components of the lazy element. If you issue OpenGL calls that change that state, you must call SoGLLazyElement::reset() to inform the lazy element that the OpenGL state has changed.

It is important to keep track of state::push() and pop() calls that occur while rendering, because the identity of the current lazy element will probably change during a push and pop. This can cause problems if SoMaterialBundle is used as in the Inventor 2.0 example code /usr/share/src/inventor/examples/Toolmaker/02.Nodes/Pyramid.c++.

The SoShape::beginSolidShape() and SoShape::endSolidShape() methods are unnecessary and actually can cause problems in that example, because the destructor for the material bundle is invoked after a pop(). This potentially invalidates state that is no longer current. The example code has been revised to work correctly in Inventor 2.1 (see also Appendix A, “Creating a Node”).

How to Convert GLRender() Methods

This section provides step-by-step instructions for converting a shape node's GLRender() method to use the lazy elements.

The shape nodes are responsible for managing transparency state, managing glShadeModel, issuing glColorMaterial(), and issuing OpenGL calls to send graphics state.

  • In the simplest case (one color per shape), you need only call SoGLLazyElement::sendAllMaterial(), once, prior to rendering the shape.

  • If there are multiple colors or transparencies in the shape, you may need to do more.

In the most general case, follow these steps:

  1. If your node is setting a new value in the lazy element state, call SoLazyElement::setColorMaterial(), setBlending(), and so on.

    When ColorMaterial is TRUE, glColor() calls modify the current GL_DIFFUSE color. By default, ColorMaterial is FALSE. You should only set it if it needs to be TRUE, and be sure to set it back to FALSE at the end of rendering.

  2. After you've set all values, but before any geometry has been sent to OpenGL, issue one of the following calls:

    • SoGLLazyElement::sendAllMaterial()

    • SoGLLazyElement::sendNoMaterial()

    • SoGLLazyElement::sendOnlyDiffuseColor()

    The sendAllMaterial() call ensures that all of the state managed by the lazy element is current in OpenGL. If the light model is BASE_COLOR, use SendOnlyDiffuseColor() instead, to avoid unnecessary OpenGL calls to set ambient colors, specular colors, and so on. If you are providing your own glMaterial() or glColor() calls, use sendNoMaterial(). This ensures that the OpenGL state is current with respect to transparency, light model, color material, and so on.

  3. To send additional materials, for example, PER_VERTEX or PER_FACE, to OpenGL, call SoGLLazyElement::sendDiffuseByIndex() or issue OpenGL commands.

  4. The OpenGL shade model is by default GL_SMOOTH. If you need to have flat shading, invoke glShadeModel(GL_FLAT) before rendering, but be sure to set it back to GL_SMOOTH at completion of GLRender().

  5. If you are managing transparency, it may be necessary to call glEnable() or glDisable() with arguments GL_BLENDING or GL_POLYGON_STIPPLE. Be sure to issue SoGLLazyElement::reset() on the transparency components of the SoGLLazyElement, namely TRANSPARENCY and BLENDING, if you called glEnable() or glDisable() to affect transparency.

  6. If you use stipple transparency, only the first transparency value in the state is used to determine the transparency value in the stipple pattern. If you use other forms (additive or blending transparency), and if you provide as many transparency values as diffuse colors, then transparency varies across the shape, with a transparency being associated with each diffuse color.

  7. If you do not want to issue OpenGL calls but need to have multiple colors sent while rendering a shape, use the sendDiffuseByIndex() routine for all color send calls after the first. You can use this routine to send a specific diffuse color and/or transparency from the state. When you invoke lazyElt::sendDiffuseByIndex(), make sure the lazy element that you are using is current, that is, that there is no push() or pop() between:

    SoGLLazyElement* lazyElt = 
          (SoGLLazyElement*)SoLazyElement::getInstance(state);
    

    and

    lazyElt->sendDiffuseByIndex();
    

  8. If you issued glColor() or glMaterial() calls, or call sendDiffuseByIndex() during rendering, you have to inform the lazy element that the OpenGL state is not the same as the Inventor state. Call SoGLLazyElement::reset(state, bitmask) at the completion of rendering, with bitmask the logical OR of one or more of the following bitmasks, depending on which material you sent:

    • SoLazyElement::DIFFUSE_MASK

    • SoLazyElement::AMBIENT_MASK

    • SoLazyElement::EMISSIVE_MASK

    • SoLazyElement::SPECULAR_MASK

    • SoLazyElement::SHININESS_MASK

    Make sure that the lazy element to which reset() is applied is current. Don't issue a push() or pop() between obtaining the lazy element and applying reset().

    After rendering, call SoLazyElement::setColorMaterial(FALSE) to disable glColorMaterial(), if color material was enabled.

Using the Material Bundle

For backward compatibility, the material state of a shape can be managed (as in Inventor 2.0) by use of SoMaterialBundle. You have to set up the GLRender() method of the shape as follows:

Constructor:

SoMaterialBundle mb(action);

Prior to first rendering:

mb.sendFirst()

For each new material:

mb.send(index)

Destructor:

(implicitly invoked at the end of GLRender())

For correct use of the material bundle, there should not be a push() or pop() of state between mb.sendFirst() and the invocation of the destructor, for example, consider the following code fragment:

beginSolidShape(action)
{//Scope material bundle
   SoMaterialBundle mb; 
   ...
}
endSolidShape(action);

Do not, for example, invoke SoShape::endSolidShape() until after the end of the scope of the material bundle because it pops the state, for example:

{//Don't do it this way
 SoMaterialBundle mb; 
 beginSolidShape(action)
 ...
 endSolidShape(action)
}

Getting a Bounding Box

If an Inventor 2.1 shape consists of lines and/or points, you must implement a getBoundingBox() method that calls first the parents method and then:

SoBoundingBoxCache::setHasLinesOrPoints(action -> getState())

See “Getting a Bounding Box” for more detailed information.

Material Property Nodes

If you implement a property node to manage material state (colors, light model, or transparency), note that in the doAction(action) method (that is, when the node is traversed), material properties are set in the state by SoLazyElement methods (see SoLazyElement.h):

  • setTransparency

  • setDiffuse

  • setAmbient

  • setSpecular

  • setShininess

  • setEmissive

  • setPacked

  • setLightModel

  • setColorIndices

It is not necessary send materials to OpenGL during traversal of property nodes in Inventor 2.1. For efficiency, the lazy element delays such sends until they are required by the shape node.

If you set either the diffuse color or the transparency, but not both, use SoColorPacker to pack the material you are setting into a packed color in the state. If you are setting both diffuse color and transparency, you should pack them both into a packed color and use SoLazyElement::setPackedColor to set them in the state. See the Glow example (Example A-2) for an illustration of how SoColorPacker is used.

Before issuing a set() on an element, if that element can be overridden, check SoOverrideElement for the status of the override on that element. It is now the responsibility of the application to test for override, and to not set the corresponding property in the state if override is set.

Engine Evaluation

If you've written engines that do not use the SO_ENGINE_OUTPUT macro but instead directly write into the fields they are connected to then you have to make the following change: before writing to a connected field, you must check if the field is not read only (!field::isReadOnly()) instead of checking if the field is engine modifying (field::isEngineModifying()). This change was made as part of the optimization of engine notification and evaluation.