Chapters 1 through 5 are devoted to extending the set of nodes, actions, and elements in the database. Understanding how to create new nodes and actions requires some background information that was not needed in The Inventor Mentor. Important concepts discussed in this chapter include the method list, state and elements, the stack index, caching, runtime typing, and using extender macros. Most of the topics introduced in this chapter are discussed in detail in later chapters. Figure 1-1 shows a summary of key SoEXTENDER classes.
If it weren't for extensibility, all actions could have been implemented simply as virtual functions on nodes. For example, GL rendering could have been a virtual function on the SoNode class. Adding a new node class would require implementing virtual functions for those actions that could not be inherited from the base class.
However, adding a new action would be considerably harder. It is impossible for you to add a new virtual function to the SoNode class or to any of the existing Inventor node classes. The only way to add a new action would be to derive classes from all nodes that would support it and to define the new method for those classes.
Inventor implements actions as separate classes to solve this problem. Each action maintains a list of static methods, one for each node class (see Figure 1-2). When an action is applied to the root of a scene graph, Inventor uses the method list to look up the method for each node in the scene graph, based on the type identifier for the node class. See “Runtime Types” for more information on class type identifiers.
For convenience, the base SoNode class registers static methods for all built-in Inventor actions; each of these methods calls a corresponding virtual function for that action. For example, SoNode registers a static method for SoGetBoundingBoxAction that calls the virtual getBoundingBox() method. All classes derived from SoNode can redefine getBoundingBox() in standard object-oriented fashion. If a class does not redefine a method, it inherits the method from its parent class
Most of the virtual methods on SoNode do nothing and must be redefined by the individual node classes. A few, however, such as the SoNode virtual methods for search() and write(), actually implement the action, since most nodes perform the action in the same way. In most cases, nodes can simply inherit these methods without redefining them.
Details on how to add a new node class are given in Chapter 2. Adding a new field class is described in Chapter 3.
If you add a new action class to Inventor, you need to set up the list of methods for the nodes that support the action (shown in Figure 1-2). This list contains one pointer to the static action method for each node class that supports the action. Adding a new action is described in Chapter 4.
The Inventor database maintains a traversal state that is used when an action is applied to a scene graph. As described in The Inventor Mentor, the traversal state is a class (SoState) used by Inventor to store state information during execution of an action. Typically, the scene graph is traversed from top to bottom and from left to right. The traversal state is modified by the nodes encountered during this traversal. State is the way nodes in the scene graph communicate with each other. For example, shape nodes need to know whether the draw style is INVISIBLE in order to know whether to draw themselves. They use the draw-style element in the state to find out.
For simplicity and extensibility, each integral piece of information in the state is stored in a separate element. For example, the current diffuse color of the material is stored in an instance of the SoDiffuseColorElement class.
Each action class has its own notion of enabling certain elements. These elements are enabled when an instance of the action is created. By default, all elements in the state are disabled, which means that they can't be set or inquired. Both nodes and actions can enable the elements they require in the state for a particular action class. The list of elements to enable is set up ahead of time, typically in the initClass() method for the node or action before instances of either are constructed. The macro SO_ENABLE() provides a convenient way for nodes to enable elements for a particular action class. For example, the SoPickStyle node requires the pick-style element when picking, so its initClass() method enables this element in the pick action as follows:
SO_ENABLE(SoPickAction, SoPickStyleElement); |
A side effect of this call is that SoPickStyleElement is also enabled in the SoRayPickAction class, which is derived from SoPickAction. Each action class inherits the enabled elements of its parent.
The SoGLRenderAction enables the SoViewportRegionElement, because the action is responsible for setting up this element in the state (the viewport region is specified in the action's constructor). This is how the SoGLRenderAction enables the SoViewportRegionElement:
enableElement(SoViewportRegionElement::getClassTypeId()); |
(Nodes typically use the SO_ENABLE() macro, and actions use the enableElement() method, since it's simple.) It doesn't hurt to enable an element in the state more than once (for example, a node and an action might enable the same element). If you are using the debugging library and you try to set or inquire an element that has not been enabled, an error message is printed. (See Appendix C of The Inventor Mentor.)
When an action is applied to a scene graph, the action builds an instance of SoState and passes a list of enabled elements to the state constructor. (If the list changes after an action is constructed, the action automatically rebuilds its state to include the newly enabled elements the next time the action is applied.)
Adding new element classes to Inventor is described in Chapter 5.
When a node is traversed, it may need to change the value of certain elements in the state. For the SoDrawStyle node, the code to change the current value of the lineWidth element looks like this:
if (! lineWidth.isIgnored()) SoLineWidthElement::set(action->state, this, lineWidth.getValue()); |
In this fragment, this is the node that is setting the value, and lineWidth.getValue() is the new value for the element.
Each enabled element has its own stack in the state with a unique stack index. Separators save and restore the state by pushing and popping these element stacks. The top element in the stack contains the current value for that element during traversal. Here is a brief summary of what happens behind the scenes when a node sets the value of an element in the state:
The element class asks the state for a modifiable instance of the element, passing in its stack index.
The state looks into the appropriate stack and creates a new instance on top of the stack, if necessary. It first checks whether the current value of the element was set by a node with its Override flag set to TRUE. If this flag is set, it returns NULL, since you can't modify the value.
If the Override flag is not set, the new top instance of the element is returned to the element class.
The element changes the value in this instance.
Figure 1-3 summarizes the relationship among nodes, actions, elements, and the state.
Each element provides a static get() method that returns its value from the top element in the stack. For example,
width = SoLineWidthElement::get(state); |
returns the line width from the top element in the stack. Elements with multiple values provide a different sequence of get() methods, as described in Chapter 2.
Supported element classes are listed here. The class name of each listed element is preceded by So and followed by Element. For brevity, the names are abbreviated in this list. Thus, the first item is SoAmbientColorElement.
These elements store information about the current set of materials:
AmbientColor | ambient color of current material | |
DiffuseColor | diffuse color of current material | |
EmissiveColor | emissive color of current material | |
Shininess | shininess value of current material | |
SpecularColor | specular color of current material | |
Transparency | transparency of current material |
These elements store information about the current lighting model and light source:
LightAttenuation |
| |
LightModel | current lighting model |
These elements store information relevant to textures:
TextureBlendColor |
| |
TextureCoordinateBinding |
| |
TextureCoordinate |
| |
TextureImage | current texture image | |
TextureMatrix | current cumulative texture transformation matrix | |
TextureModel | how texture is applied to material component | |
TextureWrapS | how image wraps in s (horizontal) direction | |
TextureWrapT | how image wraps in t (vertical) direction |
These elements contain information about how to draw and interact with shapes:
ClipPlane | current set of clipping planes | |
Complexity | current complexity | |
ComplexityType |
| |
Coordinate | current set of coordinates | |
CreaseAngle | cutoff crease angle for smoothing when computing default normals | |
DrawStyle | current drawing style (filled, lines, and so on) | |
MaterialBinding |
| |
NormalBinding |
| |
Normal | current set of surface normals | |
PickStyle | current pick style |
These elements store profile information (for 3D text and NURBS surfaces):
ProfileCoordinate |
| |
Profile | set of current profiles |
These elements store information about transformations:
BBoxModelMatrix |
| |
LocalBBoxMatrix |
| |
ModelMatrix | current cumulative object transformation matrix | |
ProjectionMatrix |
| |
Units | current factor for converting between units of measure | |
ViewingMatrix | current camera viewing transformation matrix |
These elements are related to text objects:
FontName | name of current font | |
FontSize | size of current font |
These elements hold information about current GL modes:
LinePattern | current dash pattern for drawing lines | |
LineWidth | current width for drawing lines | |
PointSize | current point size | |
ShapeHints | current shape hints |
These elements hold information about viewing:
CullVolume | current culling volume | |
FocalDistance | current focal distance of the camera | |
PickRay | current ray to use for picking | |
ViewVolume | current viewing volume | |
ViewportRegion |
|
These elements hold traversal information:
Cache | whether caching is active, whether cache is valid, and currently open caches (see “Caching”) | |
Switch | current child to traverse in switch nodes |
Many of the elements named above have GL-specific versions that are used only for rendering. The following elements have only GL versions:
GLCacheContext |
| |
GLColorIndex | index into the current GL color map of the base color of the current surface material | |
CurrentGLMateria |
l | |
GLLightId | integer identifier of current source (counts from 0) | |
GLMaterialIndex |
| |
GLRenderPass | current rendering pass for multi-pass rendering (an integer) | |
GLTextureEnabled |
| |
GLTextureQuality |
| |
GLUpdateArea |
|
Table 1-1 lists all elements and the actions for which they are enabled by default. The elements enabled for the line highlight and box highlight render actions are the same as those for the GL render action.
Table 1-1. Elements Enabled for Each Action
CB = Callback |
CB |
GLR |
BB |
GM |
HE |
P |
RP |
S |
---|---|---|---|---|---|---|---|---|
AmbientColor | X | X |
|
|
|
|
|
|
BBoxModelMatrix |
|
| X |
|
|
|
|
|
Cache |
| X | X |
|
|
|
|
|
ClipPlane | X | X |
|
|
| X | X |
|
Complexity | X | X | X |
|
| X | X |
|
ComplexityType | X | X | X |
|
| X | X |
|
Coordinate | X | X | X |
|
| X | X |
|
CreaseAngle | X | X | X |
|
| X | X |
|
CullVolume |
| X |
|
|
|
|
|
|
CurrentGLMaterial |
| X |
|
|
|
|
|
|
DiffuseColor | X | X |
|
|
|
|
|
|
DrawStyle | X | X |
|
|
|
|
|
|
EmissiveColor | X | X |
|
|
|
|
|
|
FocalDistance | X | X | X |
|
|
| X |
|
FontName | X | X | X |
|
| X | X |
|
FontSize | X | X | X |
|
| X | X |
|
GLAmbientColor |
| X |
|
|
|
|
|
|
GLCacheContext |
| X |
|
|
|
|
|
|
GLClipPlane |
| X |
|
|
|
|
|
|
GLColorIndex |
| X |
|
|
|
|
|
|
GLCoordinate |
| X |
|
|
|
|
|
|
GLDiffuseColor |
| X |
|
|
|
|
|
|
GLDrawStyle |
| X |
|
|
|
|
|
|
GLEmissiveColor |
| X |
|
|
|
|
|
|
GLLightId |
| X |
|
|
|
|
|
|
GLLightModel |
| X |
|
|
|
|
|
|
GLLinePattern |
| X |
|
|
|
|
|
|
GLLineWidth |
| X |
|
|
|
|
|
|
GLMaterialIndex |
| X |
|
|
|
|
|
|
GLModelMatrix |
| X |
|
|
|
|
|
|
GLNormal |
| X |
|
|
|
|
|
|
GLPointSize |
| X |
|
|
|
|
|
|
GLPolygonStipple |
| X |
|
|
|
|
|
|
GLProjectionMatrix |
| X |
|
|
|
|
|
|
GLRenderPass |
| X |
|
|
|
|
|
|
GLShapeHints |
| X |
|
|
|
|
|
|
GLShininess |
| X |
|
|
|
|
|
|
GLSpecularColor |
| X |
|
|
|
|
|
|
GLTextureBlend- |
| X |
|
|
|
|
|
|
GLTextureCoordi- |
| X |
|
|
|
|
|
|
GLTextureEnabled |
| X |
|
|
|
|
|
|
GLTextureImage |
| X |
|
|
|
|
|
|
GLTextureMatrix |
| X |
|
|
|
|
|
|
GLTextureModel |
| X |
|
|
|
|
|
|
GLTextureQuality |
| X |
|
|
|
|
|
|
GLTextureWrapS |
| X |
|
|
|
|
|
|
GLTextureWrapT |
| X |
|
|
|
|
|
|
GLUpdateArea |
| X |
|
|
|
|
|
|
GLViewingMatrix |
| X |
|
|
|
|
|
|
GLViewportRegion |
| X |
|
|
|
|
|
|
LightAttenuation |
| X |
|
|
|
|
|
|
LightModel | X | X |
|
|
|
|
|
|
LinePattern | X | X |
|
|
|
|
|
|
LineWidth | X | X |
|
|
|
|
|
|
LocalBBoxMatrix |
|
| X |
|
|
|
|
|
MaterialBinding | X | X |
|
|
| X | X |
|
ModelMatrix | X | X |
|
|
| X | X |
|
NormalBinding | X | X |
|
|
| X | X |
|
Normal | X | X |
|
|
| X | X |
|
PickRay |
|
|
|
|
|
| X |
|
PickStyle | X |
|
|
|
| X | X |
|
PointSize | X | X |
|
|
|
|
|
|
ProfileCoordinate | X | X | X |
|
| X | X |
|
Profile | X | X | X |
|
| X | X |
|
ProjectionMatrix | X | X | X |
|
|
| X |
|
ShapeHints | X | X | X |
|
| X | X |
|
Shininess | X | X |
|
|
|
|
|
|
SpecularColor | X | X |
|
|
|
|
|
|
Switch | X | X | X | X | X | X | X | X |
TextureBlendColor | X | X |
|
|
|
|
|
|
TextureCoordinate- | X | X |
|
|
| X | X |
|
TextureCoordinate | X | X |
|
|
| X | X |
|
TextureImage | X | X |
|
|
|
|
|
|
TextureMatrix | X | X |
|
|
| X | X |
|
TextureModel | X | X |
|
|
|
|
|
|
TextureWrapS | X | X |
|
|
|
|
|
|
TextureWrapT | X | X |
|
|
|
|
|
|
Transparency | X | X |
|
|
|
|
|
|
Units | X | X | X | X |
| X | X |
|
ViewVolume | X | X | X |
| X |
| X |
|
ViewingMatrix | X | X | X |
|
|
| X |
|
ViewportRegion | X | X | X | X | X | X | X |
|
The Inventor Mentor offers an introduction to caching. This section provides additional background information on how the Inventor caching mechanism works. For more information, see also the discussion of matches() in Chapter 5.
Elements provide the mechanism in Inventor for keeping track of scene graph dependencies. When a node uses Inventor elements, information is automatically stored that enables Inventor to determine whether a given cache is still valid, or whether values in the cache or values affecting the cache have changed, requiring a new cache to be built.
In cases where an element's value is stored as a simple floating point or integer value, Inventor simply compares the value of the element in the state with the value of the element stored in the cache. If the values are the same, the cache is still valid.
In other cases, where an element's value may be composed of a large number of values, Inventor checks to see which node or nodes set the value, as follows. (The SoCoordinateElement is an example of an element that uses this mechanism.) Every node instance in a scene graph is automatically assigned a unique identification number, referred to as its node ID. This node ID is updated when any field in the node changes. Whenever a node sets an element value, its node ID is stored in the state along with the value. When a cache is built, this node ID is also stored in the cache along with the element value. When Inventor needs to determine whether a given cache is valid, it compares the node ID in the state with the node ID in the cache. If both node IDs match, then the cache is still valid.
For elements that accumulate values in the state, such as transformations, a list of all node IDs that have modified the element is stored along with the element's value in the cache and in the state. If both lists of node IDs match, the cache is valid.
If you are creating a new node class that uses Inventor elements, caching will work automatically, since the necessary information will be stored in the state and compared appropriately with those in the cache. However, if you create a new node that depends on something that is not an element (for example, it might depend on a global variable), then you need to be sure that your new node is never cached, since it does not store the correct dependency information in the state. Use the SoCacheElement:: invalidate() method to specify that this new node should not be cached. The more versatile solution is, of course, to use elements so that they can automatically set up the caching dependencies for you. You can derive your own element class if necessary (see Chapter 5).
The SoType class keeps track of runtime type information in Inventor. When initialized, many classes request a unique SoType. You can then use this type to find out the actual class of an instance when only its base class is known, or to create an instance of a particular class, given its type or name. Useful type-related methods, which are provided by the macros for implementing most classes, include
All nodes, node kits, manipulators, actions, elements, fields, engines, events, and details in Inventor must have a static method to initialize the class. This method, which must be called initClass(), sets up the type-identifier and file-format name information for the class. Standard Inventor classes are initialized during SoDB::init(), SoInteraction::init(), SoNodeKit::init(), or SoXt::init(). For extender classes, the initClass() method must be called after the database is initialized and before any instance of the class is constructed. The order for initializing is elements first, actions, then nodes.
For any new class that supports runtime typing, the initClass() method must be defined in the header file and implemented in the source file. Most classes have associated macros that can be used within the initClass() method.
A number of macros are provided to make subclassing easier for you. Each chapter lists the relevant macros and where they are defined. For example, SoNode provides SO_NODE_INIT_CLASS() and SO_NODE_INIT_-
ABSTRACT_CLASS() in the SoSubNode.h file. SoField provides SO_SFIELD_INIT() and SO_MFIELD_INIT() in SoSubField.h. The chapter examples illustrate how to use the macros.
Most classes provide macros for use in header files, source files, and class initialization routines. Within these general categories, macros are provided for both abstract and nonabstract classes. The include file for the macros generally is the name of the base class, prefixed by Sub—for example, SoSubNode.h contains the macros for defining new node classes, and SoSubAction.h contains the macros for defining new action classes.