007 3078 001

User Manual: 007-3078-001

Open the PDF directly: View PDF PDF.
Page Count: 144

Download007-3078-001
Open PDF In BrowserView PDF
Open Inventor™ 2.1 Porting and
Performance Tips

Document Number 007-3078-001

CONTRIBUTORS
Written by Renate Kempf and Josie Wernecke
Edited by Cindy Kleinfeld
Production by Gloria Ackley
Engineering contributions by Gavin Bell, Alan Norton, Helga Thorvaldsdóttir.
Cover design and illustration by Rob Aguilar, Rikk Carey, Dean Hodgkinson,
Erik Lindholm, and Kay Maitz
© Copyright 1995, Silicon Graphics, Inc.— All Rights Reserved
This document contains proprietary and confidential information of Silicon
Graphics, Inc. The contents of this document may not be disclosed to third parties,
copied, or duplicated in any form, in whole or in part, without the prior written
permission of Silicon Graphics, Inc.
RESTRICTED RIGHTS LEGEND
Use, duplication, or disclosure of the technical data contained in this document by
the Government is subject to restrictions as set forth in subdivision (c) (1) (ii) of the
Rights in Technical Data and Computer Software clause at DFARS 52.227-7013
and/or in similar or successor clauses in the FAR, or in the DOD or NASA FAR
Supplement. Unpublished rights reserved under the Copyright Laws of the United
States. Contractor/manufacturer is Silicon Graphics, Inc., 2011 N. Shoreline Blvd.,
Mountain View, CA 94043-1389.
Silicon Graphics, the Silicon Graphics logo, and OpenGL are registered trademarks
and Open Inventor, Indigo2, Extreme, IRIX, IRIS Annotator, IRIS InSight, CaseVision,
and WebSpace Author are trademarks of Silicon Graphics, Inc. Extreme is a
trademark used under license by Silicon Graphics Inc.

Open Inventor™ 2.1 Porting and Performance Tips
Document Number 007-3078-001

Contents

About This Guide ix
What This Guide Contains ix
What You Should Know Before Reading This Manual
Background Reading x
Conventions Used in This Guide x
1.

Porting to Open Inventor 2.1: Getting Started 1
Porting Applications With No Custom Classes 1
Porting Custom Classes 3

2.

Incompatible API Changes 5
Overview of Changes By Class Name
Changes to Shape Hints 8
Changes to Complexity 9
Changes to Materials and Colors 10
Changes to Normals 12
Changes to Texture Coordinates 12
Changes to Vertex-Based Shapes 13
Changes to Viewers 14
Miscellaneous Changes 16

3.

x

6

Scene Appearance Changes in Inventor 2.1 19

iii

Contents

4.

iv

New Features 21
Version-Related Changes 21
File Version 21
Inventor Revision Symbols 22
DSO Directories and Versions 22
File Reading and Writing 23
Support for VRML Files 23
User-Defined File Headers 23
SoOutput::setFloatPrecision 23
New Nodes 23
SoVertexProperty Node 24
SoLOD Node 26
SoLocateHighlight Node 26
VRML Nodes 26
New Fields 27
The “fields” Field 27
The “isA” Field 27
Miscellaneous Additions 28
SoGLRenderAction Render Abort Callback Changes 28
New Manipulator for Transformations 29
OpenGL Texture Object Extension 29
SoXtViewer Changes 29
New and Updated Utilities 30
File Downgrade Utility 30
Program for Converting Files to Use Vertex Properties 30
Program for Optimizing Scene Graphics 31
Program for Analyzing Rendering Performance 31

Contents

5.

6.

Incompatible Extender API Changes 33
Methods 33
The GLRender() Method 34
readInstance() Method 35
copy() Method 36
Elements 37
Changes in Shape Hints Element Methods
Changes in Binding Elements 37
Changes in Texture Elements 37
Changes in Material Elements 38
Implementation of Node Override 39
Shape Nodes 40
Generating Normals 40
Managing the Material State 40
Using the Material Bundle 44
Getting a Bounding Box 45
Material Property Nodes 45
Engine Evaluation 46

37

Optimizing Open Inventor Applications 47
Benchmarking Tips 48
Setting Performance Goals 48
Measuring Performance 49
Determining Bottlenecks 49
Modifying Your Application to Reduce Bottlenecks
Are You Finished Yet? 51
The Five Performance Commandments 52

50

v

Contents

Optimizing Rendering 52
Determining Whether Rendering Is the Problem 52
Isolating Rendering 53
Using the ivperf Utility to Analyze Rendering Performance
Correcting Window Clear Bottlenecks 55
Improving Traversal Performance 55
Organizing the Scene for Caching 56
Improving Material Change Bottlenecks 57
Optimizing Transformations 58
Performance Tip for Face Sets 58
Optimizing Textures 59
Optimizing Texture Management 59
Using Lights Efficiently 61
Optimizing Vertex Transformations 61
Optimizing Pixel Fill Operations 63
Correcting Problems ivperf Does Not Measure 63
Optimizing Everything Else 68
Useful Tools 69
Optimizing Memory Usage 69
Looking at CPU Usage 70
Optimizing Action Construction and Setup 70
Decreasing Notification Overhead 71
Picking and Handling Events 72
A.

vi

Creating a Node 73
Overview 74
Initializing the Node Class 75
Enabling Elements in the State 75
Inheritance Within the Element Stack 76
Defining the Constructor 76
Setting Up the Node’s Fields 77
Defining Enumerated Values for a Field 77

53

Contents

Implementing Actions 78
The doAction() Method 78
Changing and Examining State Elements 80
Element Bundles 81
The SoLazyElement 82
Creating a Property Node 82
Creating a Shape Node 88
Generating Primitives 88
Rendering 90
Picking 91
Getting a Bounding Box 93
Pyramid Node 94
Creating a Group Node 108
Child List 108
Hidden Children 108
Using the Path Code 109
What Happens If an Action Is Terminated? 111
Alternate Node 111
Using New Node Classes 116
Creating an Abstract Node Class 120
The copyContents() Method 120
The affectsState() Method 121
Uncacheable Nodes 121
Creating an Alternate Representation 122
Generating Default Normals 122
Index 123

vii

About This Guide

Open Inventor 2.1 Porting and Performance Tips explains how to port your Open Inventor™
2.0 application to Open Inventor 2.1. Emphasis is on making the porting process go
smoothly and on helping you make your application run as efficiently as possible, using
the various performance enhancements provided by Open Inventor 2.1.

What This Guide Contains
This guide consists of six chapters and one appendix:
Chapter 1, “Porting to Open Inventor 2.1: Getting Started,” steps you through the
porting process, pointing to other relevant information as appropriate.
Chapter 2, “Incompatible API Changes,” helps you find and correct for incompatible API
changes by first providing an overview list, then explaining each change in more detail.
Chapter 3, “Scene Appearance Changes in Inventor 2.1,” is useful if you run your Open
Inventor application after it has compiled successfully and find that the scene
appearance isn’t the same it was in Inventor 2.0. The chapter provides a table that
correlates scene appearance changes and the underlying API changes.
Chapter 4, “New Features,” describes the major new features in Open Inventor 2.1.
Chapter 5, “Incompatible Extender API Changes,” is for users who have customized
existing Inventor 2.0 classes or created subclasses of them. It explains the new features
and changes to the protected and extender methods.
Chapter 6, “Optimizing Open Inventor Applications,” explains how to determine what
is limiting the performance of your Open Inventor application, and provides suggestions
for improving its performance.
Appendix A, “Creating a Node,” provides an update to Chapter 2 of The Inventor
Toolmaker based on changes to Inventor in 2.1.

ix

About This Guide

What You Should Know Before Reading This Manual
To work successfully with this manual, you should know how to write and debug Open
Inventor applications. Most readers of this book probably port applications they wrote
themselves. If you haven’t worked with Open Inventor before, you are urged to read the
materials listed here and to gain some experience programming with Open Inventor
before you attempt to port an application.

Background Reading
The following books provide background and complementary information for this
manual. Books available in hard copy as well the IRIS InSight™ online viewer are
marked with (I):
•

Wernecke, Josie, and the Open Inventor Architecture Group. The Inventor Mentor:
Programming Object-Oriented 3D Graphics with Open Inventor, Release 2, Menlo Park:
Addison-Wesley Publishing Company, 1994.(I)

•

Open Inventor Architecture Group. Open Inventor C++ Reference Manual. Menlo
Park: Addison-Wesley Publishing Company, 1994.

•

Wernecke, Josie, and the Open Inventor Architecture Group. The Inventor Toolmaker.
Extending Open Inventor, Release 2. Menlo Park: Addison-Wesley Publishing
Company, 1994.(I)

Conventions Used in This Guide
This guide uses the following typographical conventions:

x

Italics

Filenames, IRIX command names, command-line options, function
parameters, flags, and book titles.

Fixed-width

Code examples and system output.

Bold

C++ class names and fields. Function or method names with
parentheses following the name, for example getPackedValue().

Chapter 1

1.

Porting to Open Inventor 2.1: Getting Started

This chapter gets you started porting to Open Inventor 2.1. It steps you
through the porting process, pointing to other relevant information as
appropriate.
The process of porting to Open Inventor 2.1 is much simpler if your
application has no custom classes derived from the Inventor classes. This
chapter therefore discusses the process in two separate sections:
•

“Porting Applications With No Custom Classes” on page 1

•

“Porting Custom Classes” on page 3

Porting Applications With No Custom Classes
To port an application without custom classes, follow these steps:
1.

Compile your application under Inventor 2.1.
If you have no errors, go to Step 4. Otherwise, continue with Step 2.

2.

If you have compile or link problems, go to “Overview of Changes By
Class Name” on page 6 and from there to the more detailed description.

3.

Fix all your problems in sequence and recompile, or recompile after
each fix. The approach you take depends on your application and your
personal preference.

4.

Run the application with the debug version of the library as follows:
■

Set the environment variable LD_LIBRARY_PATH to
/usr/lib/libInventorDebug to run your application with the debug
version of the library.

■

Note any debug messages that occur while running your
application and fix problems. (The messages should be
self-explanatory.)

1

Chapter 1: Porting to Open Inventor 2.1: Getting Started

5.

When your application compiles and links without errors, run it and
watch carefully whether there are changes to the appearance of your
scenes. Because some defaults changed, things may not look exactly as
they did before.

6.

If you find problems, go to Chapter 3, “Scene Appearance Changes in
Inventor 2.1.” The chapter provides a table that lets you access the API
problem description based on the appearance problem description.

After the application compiles and runs satisfactorily, you are urged to also
prepare it for the future by removing obsolete nodes, fields, and so on, that
are supported for compatibility only.
In some cases, the old version of the API is supported in addition to the new
version. This is done only to make the transition from 2.0 to 2.1 easier. To
flush out cases where your code is still using the old version, compile your
code with -DIV_STRICT as an option to cc.
Finally, optimize the application to really get the most out of Inventor 2.1 as
follows:

2

1.

Change the program and the scene graphs to take advantage of the new
vertexProperty field of vertex-based shapes.

2.

Look at the other new features discussed in Chapter 4, “New Features,”
and make sure you’re taking advantage of the available enhancements
and optimizations.

3.

Look at the information in Chapter 6, “Optimizing Open Inventor
Applications.” It has been updated for 2.1 from the booklet of the same
name (which was not previously available online). Optimize your
program using that information as appropriate.

Porting Custom Classes

Porting Custom Classes
If your application has custom classes, porting becomes more complex.
Consider doing the following:
1.

Attempt to update your classes as appropriate before you compile the
application for the first time. See Chapter 5, “Incompatible Extender
API Changes,” for information that’s useful when updating custom
classes.

2.

When you believe you’ve fixed most problems, go through the steps in
“Porting Applications With No Custom Classes” on page 1.

3.

If compiling or linking uncovers further problems with your custom
classes, see also the include files in /usr/include/Inventor.

3

Chapter 2

2.

Incompatible API Changes

The 2.1 release of Open Inventor provides significant performance
enhancements over previous releases. In some cases an infrequently used
feature was slowing down rendering, even when the feature wasn’t being
used. In Open Inventor 2.1, some of those features have therefore been
slightly modified and some have been removed altogether.
This chapter helps you find these incompatible changes, providing the
following information:
•

“Overview of Changes By Class Name” on page 6

•

“Changes to Shape Hints” on page 8

•

“Changes to Complexity” on page 9

•

“Changes to Materials and Colors” on page 10

•

“Changes to Normals” on page 12

•

“Changes to Texture Coordinates” on page 12

•

“Changes to Complexity” on page 9

•

“Changes to Viewers” on page 14

•

“Miscellaneous Changes” on page 16

Note: This chapter only lists incompatible API changes. For additions to the

API, see Chapter 4, “New Features.”

5

Chapter 2: Incompatible API Changes

Overview of Changes By Class Name
This section provides an overview of the incompatible API changes. If you’re
using the online version of this manual, you can access a change description
directly by clicking on it:

6

•

SoShapeHints—creaseAngle default value changed.

•

SoShapeHints, SoDrawStyle, SoClipPlane—Wireframe and clipped
objects behavior changed.

•

SoShapeHints, SoCube, SoCone, SoSphere, SoCylinder—Primitive
shapes behavior changed.

•

SoComplexity, nodes derived from SoVertexShape—Geometry is no
longer dropped for complexity values smaller than 0.5.

•

SoComplexity, SoTexture2—textureQuality field behavior changed.

•

SoComplexity, SoTexture2—Default texture quality is point sampling
on some systems.

•

SoComplexity, SoTexture2—Textures sometimes use only 12 bits of
color.

•

SoMaterial—Multiple materials support changed.

•

SoMaterialIndex—Obsolete.

•

SoColorIndex, SoLightModel—Indexed colors with PHONG light
model no longer supported.

•

SoPackedColor—Field for storing packed colors changed.

•

SoPackedColor—getPackedValue(), setPackedValue() methods
changed.

•

SoMaterialBinding, SoMaterial—Correct number of diffuse colors
required.

•

SoMaterialBinding, SoMaterial—Correct number of transparencies
required.

•

SoMaterialBinding—DEFAULT and NONE bindings obsolete.

•

SoNormal—Default normal no longer provided.

•

SoNormalBinding, SoNormal—Correct number of normals required.

•

SoNormalBinding, SoNormal—Normals generated automatically.

Overview of Changes By Class Name

•

SoNormalBinding—DEFAULT and NONE bindings obsolete.

•

SoTextureCoordinate2—Default texture coordinate no longer provided.

•

SoTextureCoordinate2—Automatic generation of texture coordinates as
needed.

•

SoTextureCoordinate2—Correct number of texture coordinates
required.

•

SoTextureCoordinateBinding—DEFAULT binding obsolete.

•

Shapes derived from SoNonIndexedShape—startIndex field obsolete.

•

Shapes derived from SoNonIndexedShape—
USE_REST_OF_VERTICES not fully supported.

•

SoXtViewer—Texture mapping interactive draw style change.

•

SoXtPlaneViewer, SoXtExamineViewer, SoXtWalkViewer—Key
bindings changed.

•

Shapes derived from SoNonIndexedShape—
USE_REST_OF_VERTICES not fully supported.

•

All nodes—Limited support for Override flag.

•

SoOutput—isASCIIHeader() and isBinaryHeader() no longer
supported.

•

SoCallback, SoEventCallback—Reset required after making direct
OpenGL calls.

•

SoLineHighlightRenderAction, SoBoxHighlightRenderAction,
SoGLRenderAction—Constructor arguments changed.

•

SoSFLong, SoMFLong, SoSFULong, SoMFULong—long changed to
int32.

•

All draggers and manipulators—Control key change.

7

Chapter 2: Incompatible API Changes

Changes to Shape Hints
•

SoShapeHints—creaseAngle default value changed.
The creaseAngle field is used when default normals are generated. The
default value has been changed from 0.5 radians to 0.0 radians. As a
result, default normals are generated faster and use less memory.
However, objects that were smoothly shaded by default by previous
versions of Inventor will have faceted normals in Inventor 2.1.

•

SoShapeHints, SoDrawStyle, SoClipPlane—Wireframe and clipped
objects behavior changed.
Drawing in wireframe mode (using the SoDrawStyle node) or clipped
mode (using SoClipPlane nodes) no longer turns off SOLID
SoShapeHints; clipped or wireframe objects have backfaces removed if
the appropriate SoShapeHints value is specified.

•

SoShapeHints, SoCube, SoCone, SoSphere, SoCylinder—Primitive
shapes behavior changed.
The primitive shapes (SoCube, SoCone, SoSphere, SoCylinder) no
longer automatically turn on shapeType = SOLID and vertexOrdering =
COUNTERCLOCKWISE on SoShapeHints nodes. You must insert an
SoShapeHints node at the top of your scenes to get the old primitive
behavior. Note that if you don’t insert an SoShapeHints node, backface
culling is disabled. This may slow down the frame rate.
On the other hand, you can now specify an SoShapeHints node of
shapeType = UNKNOWN_SHAPE_TYPE and vertexOrdering =
COUNTERCLOCKWISE if you want both the interior and exterior
surfaces of primitive shapes to be lit. If you only want the inside of the
object lit, for example if the viewer is always inside a sphere, you can
specify an SoShapeHints node with shapeType = SOLID and
vertexOrdering = CLOCKWISE.

8

Changes to Complexity

Changes to Complexity
•

SoComplexity, nodes derived from SoVertexShape—Geometry is no
longer dropped for complexity values smaller than 0.5.
When you applied complexity values of less than 0.5 to triangle strips
and other vertex-based shapes in Inventor 2.0, some of the geometry
was dropped (for example, alternate strips were deleted from a triangle
strip set). In Inventor 2.1 this no longer happens. No geometry is
deleted from such shapes because this was not an effective way of
speeding up the rendering of complex shapes.

•

SoComplexity, SoTexture2—textureQuality field behavior changed.
The textureQuality field of the SoComplexity node now takes effect
only when the next SoTexture2 node is traversed, not immediately as in
Inventor 2.0. As a result, mipmap creation for an SoTexture2 node can
be avoided if the texture quality is low enough. If you arranged your
scene graphs so that the textureQuality field of SoComplexity
followed the SoTexture2 nodes, re-arrange them so that the
SoComplexity node is traversed first.

•

SoComplexity, SoTexture2—Default texture quality is point sampling
on some systems.
On systems that do not provide hardware support for texture mapping,
the default textureQuality field is interpreted to mean point sampling
of the texture, and mipmaps are not used.

•

SoComplexity, SoTexture2—Textures sometimes use only 12 bits of
color.
If textureQuality is less than 0.8, only 12 bits of texture color is used
(instead of 24) on systems that support the OpenGL texture extension
(EXT_texture). This increases the fill rate but decreases visual quality.

9

Chapter 2: Incompatible API Changes

Changes to Materials and Colors
•

SoMaterial—Multiple materials support changed.
Open Inventor 2.1 no longer supports multiple values for the following
properties specified in an SoMaterial node:
–

ambientColor

–

specularColor

–

emissiveColor

–

shininess

Open Inventor 2.1 continues to support multiple values for:
–

diffuseColor

–

transparency

If you provide multiple transparencies, you must provide as many
transparencies as diffuse colors. If you provide just one transparency, it
applies to all materials in the shape.
For backward compatibility, the field types for ambientColor,
specularColor, emissiveColor, and shininess, have not changed. They
are still SoMF- fields.
Note that the new SoVertexProperty node provides explicit support
(and accelerated performance) for changing diffuse color and
transparency within a shape.
•

SoMaterialIndex—Obsolete.
The SoMaterialIndex node is no longer supported. For indexed color
rendering, use the SoColorIndex node instead.

•

SoColorIndex, SoLightModel—Indexed colors with PHONG light
model no longer supported.
The PHONG light model is no longer supported with indexed colors.
Indexed colors can still be used with BASE_COLOR lighting.

10

Changes to Materials and Colors

•

SoPackedColor—Field for storing packed colors changed.
The field for storing packed colors in the SoPackedColor node has been
changed to be more compatible with OpenGL. The field name is now
orderedRGBA, and the packing order is 0xrrggbbaa, where aa
represents the alpha value, and rr, gg, and bb represent the red, green,
and blue components of the color, respectively. In previous releases, the
field name was rgba and the packing order was 0xaabbggrr.

•

SoPackedColor—getPackedValue(), setPackedValue() methods
changed.
The getPackedValue() and setPackedValue() methods on the SbColor
class have changed.

•

–

getPackedValue() now takes a transparency value to pack with the
color.

–

setPackedValue() returns a transparency value along with the
SbColor. The transparency value, like the transparency field in
SoMaterial, ranges from 0.0 to 1.0, where 0.0 is fully opaque, and
1.0 is fully transparent.

SoMaterialBinding, SoMaterial—Correct number of diffuse colors
required.
Open Inventor 2.1 assumes the correct number of diffuse colors is
specified. When the material binding of a vertex-based shape is not
OVERALL, provide one diffuse color for each vertex, face, or part,
depending on whether PER_VERTEX, PER_FACE, or PER_PART
binding is used. If you don’t provide enough diffuse colors, you may
see unexpected results. Note that in previous releases, Inventor
cyclically reused the colors provided.

•

SoMaterialBinding, SoMaterial—Correct number of transparencies
required.
If not enough transparencies are provided, the first one is used.

•

SoMaterialBinding—DEFAULT and NONE bindings obsolete.
The DEFAULT and NONE material bindings are obsolete. Files
containing these bindings are transparently upgraded as if they
specified OVERALL materials. Applications that use DEFAULT or
NONE continue to work, unless they are compiled with -DIV_STRICT.

11

Chapter 2: Incompatible API Changes

Changes to Normals
•

SoNormal—Default normal no longer provided.
In previous releases, an SoNormal node contained one normal by
default. In Open Inventor 2.1 the default is not to provide any normals.

•

SoNormalBinding, SoNormal—Correct number of normals required.
If any normals are provided in a scene, Open Inventor 2.1 assumes the
correct number is specified. There should be one normal for each shape,
part, face, or vertex, depending on whether the normal binding is
specified as OVERALL, PER_PART, PER_FACE, or PER_VERTEX.

•

SoNormalBinding, SoNormal—Normals generated automatically.
In previous releases, normals were automatically generated only if the
normal binding was DEFAULT. In Open Inventor 2.1, normals are
always automatically generated if they are needed and if the user does
not provide any normals in the scene. The correct number of normals is
generated, depending on the normal binding.

•

SoNormalBinding—DEFAULT and NONE bindings obsolete.
The DEFAULT and NONE normal bindings are obsolete. Files
containing these bindings are transparently upgraded as if they
specified PER_VERTEX_INDEXED normals. Applications that use the
DEFAULT or NONE enums continue to work, unless they are compiled
with -DIV_STRICT.

Changes to Texture Coordinates
•

SoTextureCoordinate2—Default texture coordinate no longer
provided.
In previous releases, an SoTextureCoordinate2 node contained one
texture coordinate by default. In Open Inventor 2.1 the default is not to
provide any texture coordinates at all.

•

SoTextureCoordinate2—Automatic generation of texture coordinates
as needed.
In Open Inventor 2.1, texture coordinates are always automatically
generated if they are needed and no texture coordinates are provided in
the scene.

12

Changes to Vertex-Based Shapes

•

SoTextureCoordinate2—Correct number of texture coordinates
required.
If any texture coordinates are provided in a scene, Open Inventor 2.1
assumes there is one for each vertex. You will see unexpected results if
there are fewer texture coordinates than Inventor expected.

•

SoTextureCoordinateBinding—DEFAULT binding obsolete.
The DEFAULT for the SoTextureCoordinateBinding node is obsolete.
You can still specify PER_VERTEX or PER_VERTEX_INDEXED.

Changes to Vertex-Based Shapes
•

Shapes derived from SoNonIndexedShape—startIndex field obsolete.
The startIndex field of a vertex-based shape is replaced by the new
SoVertexProperty node.
Previously you could specify the vertex properties for multiple shape
nodes with one set of property nodes by having a different startIndex
for each shape. However, a nonzero startIndex field may not work as
expected when used with coordinates in an SoVertexProperty node.
Furthermore, confusion arises if some of the vertex properties come
from the vertexProperty field of the shape and others are inherited
from nodes in the scene.
To facilitate transition from Inventor 2.0, Inventor 2.1 continues to
support use of startIndex when all vertex properties are specified in the
state. You are urged to remove dependencies on this capability.

•

Shapes derived from SoNonIndexedShape—
USE_REST_OF_VERTICES not fully supported.
In previous releases, when a value of -1 was found in the numVertices
field of non-indexed shapes—for example, SoTriangleStripSet—all the
remaining values in the current coordinates were used. In Inventor 2.1,
this is not supported when the coordinates are specified through the
new vertexProperty field of the shape. Inventor 2.1 continues to
support the use of -1 for numVertices when the coordinates are
specified in the inherited state. You are urged to remove dependencies
on the old behavior.

13

Chapter 2: Incompatible API Changes

Changes to Viewers
The following changes to viewers were made in Inventor 2.1:
•

SoXtViewer—Texture mapping interactive draw style change.
On systems that do not have hardware support for texture mapping,
the viewers now by default set the interactive draw style to move
without textures. In other words, by default, the viewers show the
texture on these systems when the camera is not moving, but when you
interact with the camera, the texture is disabled. Use the
setDrawStyle() method of the SoXtViewer class to change the viewer
draw style. End users of the viewer can change the draw style using the
right-mouse popup menu.

•

SoXtPlaneViewer, SoXtExamineViewer, SoXtWalkViewer—Key
bindings changed.
The key bindings for the three viewers have changed to make them
more consistent with each other and to match WebSpace Navigator
Viewers.

The new bindings for SoXtPlaneViewer are the following:
Action

Result

Left mouse

Zoom in and out.

Ctrl + left mouse
Middle mouse

Translate up, down, left, right.

Ctrl + middle mouse Rotate around the viewer in forward direction (roll action).

14

 click

Alternative to Seek button. Press (but don’t hold) the 
key, then click on a target object.

Left mouse

Zoom in and out.

Ctrl + left mouse,
Middle mouse

Translate up, down, left, right.

Right mouse

Open pop-up menu.

Changes to Viewers

The key bindings for SoXtExaminerViewer have changed to be the
following:
Action

Result

Left mouse

Tumbles the virtual trackball.

Middle mouse

Translate up, down, left, and right

Ctrl + left mouse
Ctrl + middle mouse
Zoom in and out.
Left and middle mouse
 click

Alternative to Seek button. Press (but don’t hold) the 
key, then click on a target object.

Right mouse

Open pop-up menu.

The key bindings for SoXtWalkViewer have changed to be the following:
Action

Result

Left mouse

Walk mode. Hold the mouse button and move up/down for
forward/backward motion. Move right and left for turning.
Speed increases exponentially with the distance from the
mouse-down origin.

Ctrl + left mouse
Middle mouse

Translate up, down, left, right.

Ctrl + middle mouse Tilt the camera up/down and right/left. This lets you look
around while stopped.
 click

Alternative to Seek button. Press (but don’t hold) the 
key, then click on a target object.

 click

Press (but don’t hold) the  key, then click on a target
object to set the up direction to the surface normal. By
default, positive y is the up direction.

Right mouse

Open pop-up menu.

15

Chapter 2: Incompatible API Changes

Miscellaneous Changes
•

All nodes—Limited support for Override flag.
In Open Inventor 2.1, not all of the nodes support the Override flag. The
following nodes do support override:
–

SoColorIndex

–

SoComplexity

–

SoDrawStyle

–

SoFont

–

SoLightModel

–

SoMaterial

–

SoMaterialBinding

–

SoPackedColor

–

SoPickStyle

–

SoShapeHints

–

SoTexture2

Other node classes ignore the Override flag. Note that you can still set
the flag for those classes, but it has no effect.
An Override flag on an SoMaterial node also overrides the diffuseColor
and transparency fields of SoVertexProperty nodes (in the scene graph
or in the vertexProperty field of shapes). Likewise an Override flag in a
SoMaterialBinding node overrides the materialBinding field in a
vertex property node. However, you cannot override just the diffuse
color or just the transparency. If you override one, the other is obtained
from the state when the override occurs.
•

SoOutput—isASCIIHeader() and isBinaryHeader() no longer
supported.
The SoOutput methods isASCIIHeader() and isBinaryHeader() are no
longer supported. Use SoDB::isValidHeader() to verify that a header is
valid, or SoDB::getHeaderData() to see if a particular header is for
ASCII or binary files.

16

Miscellaneous Changes

•

SoCallback, SoEventCallback—Reset required after making direct
OpenGL calls.
You must notify the Inventor material state management mechanism by
calling SoGLLazyElement::reset() if you directly make any of the
following OpenGL calls from a callback node:
–

glColor()

–

glMaterial()

–

glColorMaterial()

–

glEnable() or glDisable(), with a GL_COLOR_MATERIAL,
GL_BLEND, GL_LIGHTING, or GL_POLYGON_STIPPLE
argument.

–

glPushAttrib() or glPopAttrib() with GL_ENABLE_BIT,
GL_LIGHTING_BIT, GL_POLYGON_BIT,
GL_POLYGON_STIPPLE_BIT

After the last OpenGL call in your callback node, you must call
SoGLLazyElement::reset(state, bitmask). The value of the bitmask
depends on which of the above OpenGL calls you made.
If you invoke glColor() or glMaterial(), logical-OR one or more of the
following bitmasks as argument to SoGLLazyElement::reset()
(depending on which components of the OpenGL material state you
are altering):
–

SoLazyElement::DIFFUSE_MASK

–

SoLazyElement::EMISSIVE_MASK

–

SoLazyElement::SPECULAR_MASK

–

SoLazyElement::SHININESS_MASK

–

SoLazyElement::AMBIENT_MASK

If you invoke glColorMaterial(), or enable or disable
GL_COLOR_MATERIAL, the corresponding mask is
SoLazyElement::COLOR_MATERIAL_MASK.
If you enable or disable GL_BLEND, use
SoLazyElement::BLENDING_MASK.

17

Chapter 2: Incompatible API Changes

If you alter the GL stipple transparency, either with glEnable(),
glDisable(), glPushAttrib(), or glPopAttrib(), use
SoLazyElement::TRANSPARENCY_MASK.
If you alter lighting, either with glEnable(), glDisable(),
glPushAttrib(), or glPopAttrib(), use
SoLazyElement::LIGHT_MODEL_MASK
•

SoLineHighlightRenderAction, SoBoxHighlightRenderAction,
SoGLRenderAction—Constructor arguments changed.
The constructor argument no longer takes useCurrentGLValues as an
argument.

•

SoSFLong, SoMFLong, SoSFULong, SoMFULong—long changed to
int32.
The So*Long fields have been changed to So*Int32 fields as follows:
–

SoSFLong -> SoSFInt32

–

SoSFULong -> SoSFUInt32

–

SoMFLong -> SoMFInt32

–

SoMFULong -> SoMFUInt32

You need to change your program to use the new fields, and to use the
types int32_t and uint32_t, defined in inttypes.h in place of long and
unsigned long. Use the longToInt32 utility program provided in the
inventor_dev.src.sample subsystem to help you do this.
To ease the transition, a typedef links the So*Long fields to SoInt32
fields and, where possible and feasible, methods taking arguments of
type long are supported.
Since the C++ compiler cannot distinguish overloaded functions by
return type alone, some methods are no longer supported. If you
compile your program with -DIV_STRICT, the So*Long fields cause a
compile time error.
•

All draggers and manipulators—Control key change.
All draggers and manipulators now use the  key where
they used the  key in Inventor 2.0.

18

Chapter 3

3.

Scene Appearance Changes in Inventor 2.1

Because certain fields in some nodes changed their defaults, and because of
other changes described in Chapter 2, your Inventor 2.0 application may
look different in Inventor 2.1 even if it compiles and runs fine.
Table 3-1 lists changes in appearance and points to information that helps
you fix the problem. If you’re working with the online version of the manual,
click on any “Reason” for more detailed information.
Table 3-1

Scene Appearance Changes in Inventor 2.1

Scene Appearance Change

Reason

Texture quality looks different.

SoComplexity, SoTexture2—textureQuality
field behavior changed.
SoComplexity, SoTexture2—Textures
sometimes use only 12 bits of color.
SoComplexity, SoTexture2—Default texture
quality is point sampling on some systems.

When you view a texture-mapped
object, the texture seems to
disappear.

SoXtViewer—Texture mapping interactive
draw style change.

Shapes that were smooth shaded
look faceted.

SoShapeHints—creaseAngle default value
changed.

When you set the draw style to
wireframe, some of the lines are
missing.

SoShapeHints, SoDrawStyle, SoClipPlane—
Wireframe and clipped objects behavior
changed.

A scene that has many spheres
(and/or cones, cubes, cylinders)
seems to render slower than before.

SoShapeHints, SoCube, SoCone, SoSphere,
SoCylinder—Primitive shapes behavior
changed.

19

Chapter 3: Scene Appearance Changes in Inventor 2.1

Table 3-1

Scene Appearance Changes in Inventor 2.1

Scene Appearance Change

Reason

Vertex-based shapes look the same,
regardless of the SoComplexity
value.

SoComplexity, nodes derived from
SoVertexShape—Geometry is no longer
dropped for complexity values smaller than
0.5.

SoMaterial—Multiple materials support
The material looks different than it
changed.
did in Inventor 2.0 for objects that
had a different material for every part
or every vertex specified.
Colors in a shape look wrong.

SoMaterial—Multiple materials support
changed.
SoMaterialBinding, SoMaterial—Correct
number of diffuse colors required.

A vertex-based shape used several
colors repeatedly for the different
parts and this no longer looks right.

SoMaterialBinding, SoMaterial—Correct
number of diffuse colors required.

In a program that contains direct calls SoCallback, SoEventCallback—Reset
to OpenGL, the materials look
required after making direct OpenGL calls.
wrong.

20

Chapter 4

4.

New Features

This chapter describes the major new features that distinguish Open
Inventor 2.1 from Open Inventor 2.0. It discusses the following topics:
•

“Version-Related Changes” on page 21

•

“File Reading and Writing” on page 23

•

“New Nodes” on page 23

•

“New Fields” on page 27

•

“Miscellaneous Additions” on page 28

•

“New and Updated Utilities” on page 30

Version-Related Changes
This section discusses various issues directly related to the version change:
•

“File Version”

•

“Inventor Revision Symbols”

•

“DSO Directories and Versions”

File Version
The file version has been updated to 2.1; files written out with Open Inventor
2.1 have the file header #Inventor V2.1 ascii or #Inventor V2.1 binary.
Inventor 2.1 programs are still able to read 2.0 and 1.0 files. Older programs
cannot read 2.1 files; the files must be converted to the appropriate version
with the ivdowngrade program discussed in “File Downgrade Utility” on
page 30.

21

Chapter 4: New Features

Inventor Revision Symbols
C preprocessor symbols SO_VERSION and SO_VERSION_REVISION have
been added to Inventor/SbBasic.h to identify the revision of Inventor being
compiled against:
•

SO_VERSION is the major version number (for Inventor 2.1, “2”)

•

SO_VERSION_REVISION is the minor revision number (for Inventor
2.1, “1”)

If you use parts of the Inventor API that changed between revisions, you can
use these symbols to conditionally compile (#ifdef) your code if you want
the same source to compile against multiple versions of Inventor.

DSO Directories and Versions
To avoid problems with incompatible code, the places Inventor searches for
DSOs (dynamic shared objects) implementing new nodes has been changed.
Inventor 2.1 searches in the following order:
LD_LIBRARY_PATH:/usr/lib:/lib
(normal rld rules)
.:/usr/local/lib/InventorDSO:/usr/lib/InventorDSO
(new)

If you’re running a setuid or setgid program, or if running as root, the order is:
/usr/lib:/lib
/usr/lib/InventorDSO

(normal rld rules)
(new)

The /usr/lib/Inventor and /usr/local/lib/Inventor directories were used for
Inventor 2.0. For example, if you created a new node of type NewNode and
created NewNode.so DSOs for both Inventor 2.0 and Inventor 2.1, you could
put the Inventor 2.0 NewNode.so in the /usr/local/lib/Inventor/ directory and
put the Inventor 2.1 NewNode.so in the /usr/local/lib/InventorDSO/ directory.
To avoid future problems with incompatible DSOs, Inventor 2.1 and future
releases of Inventor will search for DSOs that are given the same version
number as the Inventor libraries. For Inventor 2.1, DSOs for new nodes and
engines must be tagged with version “sgi3.0”. This is accomplished by
adding the flag -set_version “sgi3.0” when creating the DSO. See the reference
page for ld for more information on DSO versioning, and the Inventor
release notes for information on creating DSOs for new Inventor classes.

22

File Reading and Writing

File Reading and Writing
This section explains changes related to file reading and writing.

Support for VRML Files
The SoDB read methods now also read files with the VRML 1.0 header.

User-Defined File Headers
The SoDB class now includes methods for registering your own file headers.
This is useful, for example, if you have an application that supports a
superset of the Inventor file format, and you want to use the Inventor file
reading mechanism to parse the file. Similarly, you can specify your own
header for output files using methods of the SoOutput class. See the
reference pages for more details.

SoOutput::setFloatPrecision
You can now specify the precision Inventor uses when writing floating point
numbers in the ASCII file format. The new setFloatPrecision() method on
SoOutput takes one argument, an integer specifying the desired precision.
For example, a precision of 2 limits the output of floating point numbers to
2 significant digits.

New Nodes
This section discusses new nodes that don’t result in incompatible API
changes or scene appearance changes. It provides information about:
•

“SoVertexProperty Node”

•

“SoLOD Node”

•

“SoLocateHighlight Node”

•

“VRML Nodes”

23

Chapter 4: New Features

SoVertexProperty Node
A new node class, SoVertexProperty, has been added. The
SoVertexProperty node is a performance feature of Inventor 2.1; it does not
add functionality. Open Inventor applications that use this node may show
a speed improvement, particularly for non-cached rendering.
Description

The SoVertexProperty node allows you to specify in one node all the vertex
data for a vertex-based shape (that is, any shape derived from
SoVertexShape). This is more efficient than inheriting the data from several
different nodes. An SoVertexProperty node has the following fields:
vertex

the coordinates, specified as SoMFVec3f

normal

the normal vectors, specified as SoMFVec3f

normalBinding the normal binding, specified as SoSFEnum
orderedRGBA the packed color and alpha values, specified as
SoMFUInt32
materialBinding
the material binding, specified as SoSFEnum
texCoord

the texture coordinates, specified as SoMFVec2f

When the SoVertexProperty node is used, it replaces the SoCoordinate3,
SoNormal, SoNormalBinding, SoTextureCoordinate2, SoMaterial (when
used to specify multiple colors), and SoMaterialBinding nodes.
Defaults

The vertex, normal, orderedRGBA, and texCoord fields are all NULL by
default. If any of these fields are not specified, the shape inherits the values
from other nodes in the scene.
The default normalBinding is PER_VERTEX, and the default
materialBinding is OVERALL. However, the normalBinding is ignored if
the normal field is not used. Similarly, the materialBinding field is ignored
if the orderedRGBA field is not used.

24

New Nodes

Usage

The SoVertexProperty node can be used by the shape in either of two ways:
•

The SoVertexProperty node is placed in the scene and inherited by the
shape node.
This is the same model used for inheritance for other property nodes in
Inventor.

•

The vertex-based shapes have a new SoSFNode field, vertexProperty,
that allows you to directly include the SoVertexProperty node in the
shape.
This is a significant change from the inheritance model used for other
properties. The properties specified in the node apply only to the shape
in which it was included; they do not affect any subsequent shapes.

Performance Considerations

For maximum performance, observe the following rules of thumb:
•

Use the SoVertexProperty node to specify properties for the
vertex-based shapes if you have a scene that cannot be cached.

•

Use the vertexProperty field of the shapes to directly include the
SoVertexProperty node; don’t place the SoVertexProperty node in the
scene and inherit by the shape node.

•

Use the fields in the SoVertexProperty node to specify all the data you
need for a shape; don’t mix and match data from the SoVertexProperty
node and inherited values from other nodes.

•

Specify normals and texture coordinates if you need them; don’t rely on
the automatic generation, which is expensive if the scene cannot be
cached.

•

Don’t use the convenience feature of specifying the number of vertices
as -1 (that is, use all remaining vertices); specifying the actual number is
more efficient.

25

Chapter 4: New Features

SoLOD Node
Open Inventor 2.1 has an improved version of the node that provides
switching between different levels of detail in the scene. The new node is
named SoLOD, to avoid conflict with the old node, SoLevelOfDetail. For
compatibility, the old node is still supported.
The new SoLOD node switches between different levels, depending on the
distance of the object from the eye. The old SoLevelOfDetail node used
screen area to determine when to switch levels. Using distance is much faster
than using screen area.

SoLocateHighlight Node
SoLocateHighlight is a new, special separator that performs locate
highlighting. When the window cursor is over the contents of this separator,
it efficiently redraws itself in a different color. This is useful for indicating hot
spots in a scene. See the reference page for more details.

VRML Nodes
The Open Inventor file format formed the basis for the Virtual Reality
Modeling Language (VRML), the industry-standard, platform-independent
file format for 3D graphics on the Internet. VRML 1.0 includes a few nodes
that were not in Inventor 2.0; these nodes have been added to Open Inventor
2.1, which is now a superset of VRML 1.0. The new nodes in Open Inventor
2.1 are:
•

SoWWWAnchor

•

SoWWWInline

•

SoAsciiText

•

SoFontStyle

See the reference pages for more information on these nodes. For more
information on VRML, visit the World Wide Web page at
http://vrml.wired.com.

26

New Fields

New Fields
This section looks at two new fields, “The “fields” Field” and “The “isA”
Field.”

The “fields” Field
Inventor writes custom nodes with an extra field named fields that lists the
names of all fields in the node along with their types. Inventor 2.0 files could
not be read if a fields field was included in a node that was a standard part
of the library. To be fully compliant with VRML 1.0, Inventor 2.1 files now
support a fields field in all nodes.
For example, the following code fragment is legal in an Inventor 2.1 file:
Cube {
fields [SFFloat width,
SFFloat height,
SFFloat depth,]
width 10
height 4
depth 3
}

The “isA” Field
When a new node type is a superset of an existing node, and an
implementation for the new node type cannot be found, the new node type
can be treated as the existing node it is based on (with some loss of
functionality).
To support this, new node types can define an MFString field called isA
containing the names of the types of which it is a superset.
For example, assume a new subset of Material called ExtendedMaterial
adds the index of refraction as a material property. This type can be defined
as follows:

27

Chapter 4: New Features

ExtendedMaterial {
fields [ MFString isA, MFFloat indexOfRefraction,
MFColor ambientColor, MFColor diffuseColor,
MFColor specularColor, MFColor emissiveColor,
MFFloat shininess, MFFloat transparency ]
isA [ "Material" ]
indexOfRefraction .34
diffuseColor .8 .54 1
}

If the ExtendedMaterial node is not known, an alternateRep field
containing an SoMaterial node is automatically created.

Miscellaneous Additions
This section briefly discusses:
•

“SoGLRenderAction Render Abort Callback Changes”

•

“New Manipulator for Transformations”

•

“OpenGL Texture Object Extension”

•

“SoXtViewer Changes”

SoGLRenderAction Render Abort Callback Changes
The render abort callback in Open Inventor 2.1 no longer returns a simple
Boolean value. Instead, it returns an abort code (similar to the
SoCallbackAction::Response code) that is one of CONTINUE, ABORT,
PRUNE, or DELAY.

28

•

CONTINUE is the same as returning FALSE in previous versions of
Inventor; it means that rendering should continue as usual.

•

ABORT is the same as returning TRUE in previous versions; it
terminates the current render traversal.

•

PRUNE indicates that traversal should skip the current node and all
nodes under it.

Miscellaneous Additions

•

DELAY postpones traversal of the current node until after all other
nodes have been traversed, just like the SoAnnotation node.

PRUNE and DELAY are new features that allow applications to modify
Inventor’s standard rendering order.

New Manipulator for Transformations
A new manipulator, SoTransformerManip, has been added to the set of
Inventor manipulators. It provides a full interface for rotation, translation,
and scale in three dimensions. This new manipulator provides better
feedback than the older manipulators, and uses the new locate highlighting
feature to indicate which of its parts to pick. See the reference page for more
details.
SoTransformerManip is used in IRIS Annotator™ and WebSpace Author.™.

OpenGL Texture Object Extension
Open Inventor 2.1 makes use of the OpenGL extension for texture objects.
Consequently, you see improved texture mapping performance on systems
where this OpenGL extension is available.

SoXtViewer Changes
There are two changes to SoXtViewer:
•

When a viewer is in pick mode, you can temporarily switch to view
mode by holding down the  key.

•

The method SoXtViewer::setCursorEnabled() allows you to specify
whether the viewer is allowed to change the cursor over the render area
window. Disabling the cursor enables the application to set the cursor
directly. This is useful, for example, if you want to set a busy cursor in
the window.

29

Chapter 4: New Features

New and Updated Utilities
The following new and updated utilities are available in Inventor 2.1 and are
discussed in this section:
•

“File Downgrade Utility”

•

“Program for Converting Files to Use Vertex Properties”

•

“Program for Optimizing Scene Graphics”

•

“Program for Analyzing Rendering Performance”

Note: The utility program ivquicken is no longer supported.

File Downgrade Utility
A new utility program, /usr/sbin/ivdowngrade, takes an Inventor file and
converts it to Inventor version 2.0 or 1.0. See the reference page for more
details.

Program for Converting Files to Use Vertex Properties
The ivAddVP utility program helps convert Inventor files into 2.1 files that
use the vertexProperty field for the vertex-based shapes. The source code,
including a Makefile, is distributed in the inventor_dev.src.sample subsystem.
It is installed in the directory /usr/share/src/Inventor/tools/ivAddVP.

30

New and Updated Utilities

Program for Optimizing Scene Graphics
A new utility program, /usr/sbin/ivfix, restructures Inventor scene graphs for
improved rendering performance. ivfix first analyzes the organization of the
input scene graph and tries to sort it to take advantage of coherence. For
example, it tries to organize subgraphs by common textures, since switching
textures is expensive. Once sorting is complete, ivfix also tries to combine
subgraphs so that the final result has fewer nodes. Finally, ivfix “flattens” the
subgraphs, tessellating all shapes into triangles that are then organized into
triangle strips. For example, two spheres may be combined into one triangle
strip.
The source code to ivfix, including a Makefile, is distributed in the
inventor_dev.src.sample subsystem. It is installed in the directory
/usr/share/src/Inventor/tools/ivfix.

Program for Analyzing Rendering Performance
The utility program ivperf, for analyzing rendering performance, is provided
in source code format in the inventor_dev.src.sample subsystem. It is installed
in the directory /usr/share/src/Inventor/tools/ivperf. For information on ivperf,
see Chapter 6, “Optimizing Open Inventor Applications.”

31

Chapter 5

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” on page 33

•

“Elements” on page 37

•

“Shape Nodes” on page 40

•

“Material Property Nodes” on page 45

•

“Engine Evaluation” on page 46

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

“The GLRender() Method”

•

“readInstance() Method”

•

“copy() Method”

33

Chapter 5: Incompatible Extender API Changes

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

“Overriding Existing GLRender() Methods”

•

“Implementing SoSeparator GLRender() Methods Efficiently”

For additional information, see “How to Convert GLRender() Methods” on
page 42 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.

34

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

35

Chapter 5: Incompatible Extender API Changes

// 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” on
page 120 for more information.

36

Elements

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

“Changes in Shape Hints Element Methods”

•

“Changes in Binding Elements”

•

“Changes in Texture Elements”

•

“Changes in Material Elements”

•

“Implementation of Node Override”

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.

37

Chapter 5: Incompatible Extender API Changes

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:

38

•

SoDiffuseColorElement

•

SoSpecularColorElement

•

SoAmbientColorElement

•

SoEmissiveColorElement

•

SoTransparencyElement

•

SoShininessElement

•

SoLightModelElement

•

SoGLPolygonStippleElement

•

SoCurrentGLMaterialElement

•

SoGLColorIndexElement

Elements

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).

39

Chapter 5: Incompatible Extender API Changes

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

“Generating Normals”

•

“Managing the Material State”

•

“Using the Material Bundle”

•

“Getting a Bounding Box”

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.

40

Shape Nodes

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++.

41

Chapter 5: Incompatible Extender API Changes

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.

42

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()

Shape Nodes

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:

43

Chapter 5: Incompatible Extender API Changes

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:

44

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())

Material Property Nodes

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” on page 93 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

45

Chapter 5: Incompatible Extender API Changes

•

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 on page 84) 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.

46

Chapter 6

6.

Optimizing Open Inventor Applications

This chapter explains how to determine what is limiting the performance of
your Open Inventor application, and provides suggestions on how to
improve its performance.
Note: This chapter was previously available as a small booklet. The
information has been updated to Inventor 2.1 as appropriate.

The chapter discusses the following topics:
•

“Benchmarking Tips” on page 48—Describes a process for effectively
investigating and improving performance—discovering where an
application is spending its time is not a matter of random trial and
error.

•

“Optimizing Rendering” on page 52—Provides for each step in the
rendering process:

•

–

Information about that step.

–

A method for determining how much time your application is
spending in that step.

–

Techniques for reducing that time.

“Optimizing Everything Else” on page 68—Suggests ways of
structuring your scene and application for maximum performance
when doing tasks other than rendering (for example, picking or
modifying the scene).

For more information on Open Inventor programming in general and on
specific nodes, see The Inventor Mentor.

47

Chapter 6: Optimizing Open Inventor Applications

Benchmarking Tips
Like fixing bugs or eliminating memory leaks, performance tuning is a
necessary chore during application development. Proper organization and
planning can speed up this chore and make it more pleasant.
This section looks at the steps to take when optimizing your application.
discussing these topics:
1.

“Setting Performance Goals”

2.

“Measuring Performance”

3.

“Determining Bottlenecks”

4.

“Modifying Your Application to Reduce Bottlenecks”

5.

Finally, measuring your application against your goal again and
repeating steps 2, 3 and 4 above until performance meets your goal (see
“Are You Finished Yet?” on page 51).

Setting Performance Goals
Setting a performance goal helps you use your time wisely. Typically, you
should decide on a desired frame rate, such as running at 20 frames per
second with a particular scene. A reasonable performance goal for
interactive programs is a frame rate of at least 10 frames per second. Most
users find that frame rate acceptable for most tasks (more is always better, of
course).
When setting a goal, keep in mind the capabilities of your hardware. If the
absolute top speed for drawing polygons on your system is 60,000 unlit,
single-color triangles per second, don’t try to get 10 frames per second while
drawing 6,000 lit, color-per-vertex triangles. Write short OpenGL
benchmark programs, or feed test scene graphs to ivview -p to help set your
expectations.

48

Benchmarking Tips

Measuring Performance
It is important to have an objective way of measuring your application’s
performance. You’re likely to waste time on insignificant optimizations if
you just watch your application run and try to see if it seems faster.
Adding code to your application that measures the number of polygons in
your scene and how fast they are being rendered is fairly simple; see, for
example, the source code for ivview in /usr/share/src/Inventor/tools/ivview.
The osview utility can also be useful. The “swapbuf” number in the Graphics
section tells you how many frames per second your application is getting
(assuming that your application is double-buffered, and that it is the only
double-buffered application running).
Note: Don’t confuse osview with its graphical counterpart, gr_osview, which

doesn’t have this feature.
Be sure to keep good records of your application’s performance before you
start optimization. Comparing “before” and “after” performance numbers
ensures that you are not making things worse.

Determining Bottlenecks
Most applications spend most of their time executing a small part of the
code. Optimizing a procedure that is taking up only 5% of the total time is
probably not worthwhile: even if you manage to double the performance of
the procedure, the application will speed up by only 2.5%. In fact, on some
systems graphical operations can occur in parallel. For example, filling in
polygons and transforming polygon vertices might occur at the same time.
If the bottleneck is in the vertex transformation stage, increasing the pixel fill
time may not increase performance at all! Find the bottlenecks first, and then
work on improving them.
Finding bottlenecks is an experimental science. You should first come up
with a theory on where the bottleneck might be, then devise an experiment
that proves or disproves that theory. Create experiments that isolate one
small part of your application’s performance, and make sure you
understand what you are measuring every time you run an experiment.

49

Chapter 6: Optimizing Open Inventor Applications

“Optimizing Rendering” and “Optimizing Everything Else” describe
frequently encountered bottlenecks, show how to determine the amount of
time your application is spending in each of them, and give suggestions on
improving them. The following topics are discussed:
•

“Correcting Window Clear Bottlenecks”

•

“Improving Traversal Performance”

•

“Optimizing Pixel Fill Operations”

•

“Correcting Problems ivperf Does Not Measure”

•

“Correcting Culling Bottlenecks”

•

“Correcting Level of Detail Bottlenecks”

•

“Optimizing Memory Usage”

•

“Optimizing Action Construction and Setup”

•

“Decreasing Notification Overhead”

•

“Picking and Handling Events”

Feel free to start with bottlenecks you suspect are responsible for the most
noticeable slowdown. You can look at the sections in any order; just make
sure you always know what you are measuring and keep good records of
your experiments.

Modifying Your Application to Reduce Bottlenecks
When you apply a performance optimization, make sure that the
modification is really an improvement: don’t assume that all the suggestions
made in this (or any other) document automatically apply to your
application. For example, render culling usually increases performance.
However, if you have an application in which all objects are always visible,
render culling actually hurts performance.

50

Benchmarking Tips

Again, keep good records. Record what you do and how much it improves
performance. Try to minimize the number of things you change at any one
time; for example, if you make two “optimizations” and performance goes
up by 10%, the speedup might be caused by a 5% improvement for each
optimization, or might be caused by a 100% speedup caused by one
optimization and a 90% slowdown caused by the other! It is tempting to read
a document like this, make lots of changes, then see if the application gets
faster. This not only wastes time, it can also be counter-productive.

Are You Finished Yet?
One of the most frustrating things about optimizing an application’s
performance is that it can be difficult to determine when you are done. Once
you have successfully eliminated one bottleneck, something else becomes
the factor limiting performance. Before spending more time on optimization,
you should ask yourself:
•

Did you meet your performance goal? If you did, go home early. If not,
try to find other bottlenecks, consider eliminating features that hurt
performance, or reexamine your goals.

•

Is your application running at 60 or 72 frames per second?
Double-buffered programs never render faster than the refresh rate of
your monitor.

•

Do you need to experiment on different systems? Different systems
have different bottlenecks; on a system with very fast graphics, the
bottleneck is more likely to be either in the application’s code or in
Inventor’s code. On a system with slow graphics and a fast CPU, the
bottleneck is more likely to be inside the OpenGL calls. If your
application will be used on different types of systems, make sure
performance is acceptable on all of them.

51

Chapter 6: Optimizing Open Inventor Applications

The Five Performance Commandments
1.

Be scientific.

2.

Keep good records.

3.

Find bottlenecks.

4.

Change one thing at a time.

5.

Test all the types of systems your application supports.

Optimizing Rendering
The main goal of performance tuning is to make the application look and feel
faster. However, just because the goal is to make the application render
faster, don’t assume that rendering is the bottleneck.

Determining Whether Rendering Is the Problem
To find out whether rendering is the problem, modify your application so
that it does everything it normally does except render, and then measure its
performance. An easy way of getting your application to do everything but
rendering is to insert an SoSwitch node with its whichChild field set to
SO_SWITCH_NONE (the default) above your scene. So, for example,
modify your application’s code from:
myViewer->setSceneGraph(root);

To:
SoSwitch *renderOff = new SoSwitch;
renderOff->ref();
renderOff->addChild(root);
myViewer->setSceneGraph(renderOff);

This experiment gives an upper limit on how much you can improve your
application’s performance by increasing rendering performance. If your
application doesn’t run much faster after this change, then rendering is not
your bottleneck. See “Optimizing Everything Else” on page 68 for
information on optimizing the rest of your application.

52

Optimizing Rendering

Isolating Rendering
If you have determined that your application is spending a significant
amount of time rendering the scene, the next step is to isolate rendering from
the rest of the things your application does. This makes it easier to find out
where the bottleneck in rendering occurs. The easiest way to isolate
rendering is to write your scene to a file and then use the ivperf program to
perform a series of rendering experiments.
The code for writing your scene may look like the following:
SoOutput out;
if (!out.openFile(“myScene.iv”)) { ... error ... };
SoWriteAction wa(&out);
wa.apply(root);

Using the ivperf Utility to Analyze Rendering Performance
The ivperf utility reads in a scene graph and analyzes its rendering
performance. It estimates the time spent in each stage of the rendering
process while rendering the scene graph.
The process of rendering a single frame can be decomposed into five main
stages:
1.

Clearing the graphics window

2.

Traversing the Inventor scene graph

3.

Changing the graphics state (including materials, transformations, and
textures)

4.

Transforming vertices in the graphics pipeline

5.

Filling polygons

53

Chapter 6: Optimizing Open Inventor Applications

The sum of the times spent in these stages does not, in general, equal the
total time it takes to render the scene. Depending on the underlying
hardware platform and graphics pipeline, some or all of the above can
overlap with each other. Thus, completely eliminating one of the stages does
not necessarily speed up the application by the time taken by that stage.
ivperf takes this into account; it answers questions of the type “if I could
completely eliminate xxx from my scene, how much faster would rendering
be?” For example, if ivperf indicates that 50% of your time is spent changing
the material graphics state, then making your entire scene a single material
would make it render twice as fast. Knowing that materials are taking up a
significant part of your rendering time, you can then concentrate on
minimizing the number of material changes made by your scene.
If you have created your own node classes, either create DSO’s for them (see
“DSO Directories and Versions” on page 22) or call their initClass() methods
just after the call to SoInteraction:init() in the ivperf source and link their .o
files into ivperf.
The camera control used by ivperf is simplistic: it calls viewAll() for the scene
and just spins the scene around in front of the camera when benchmarking.
If you have a sophisticated walk-through or fly-through application that
uses level of detail and/or render culling, modify ivperf so that its camera
motion is more appropriate for your application. For example, have ivperf
use the following little scene instead of just SoPerspectiveCamera:
TransformSeparator {
Rotor { rotation 0 1 0 .1 speed .1 }
Translation { translation 100 0 0 }
PerspectiveCamera { nearDistance .1 farDistance 600 }
}

ivperf correctly reports the performance of changing scenes, as long as you
give it enough information. It automatically deals with scenes containing
engines and animation nodes, but if you are using an SoSensor to modify
the scene, you should mark nodes that your application frequently changes
by giving them the special name “NoCache.” For example, if your
application is frequently changing a transformation in the scene, the
transformation should appear in the file given to ivperf as:
DEF NoCache Transform { }

54

Optimizing Rendering

Correcting Window Clear Bottlenecks
The first step in the rendering process is clearing the window. It is easy to
forget this step, but depending on the size of your application’s window and
the type of system you are running on, clearing the window can take a
surprisingly long time. If your application’s main window is typically 1000
by 1000 pixels, run ivperf like this:
ivperf -w 1000,1000 myScene.iv

ivperf performs many different rendering experiments, and eventually prints
information on each rendering stage.
For example, on an Indigo2™ Extreme™ running IRIX 5.3, ivperf reports that
for rendering a simple cube in a 1000 by 1000 pixel window 46% of the time
is spent clearing the window.
Unfortunately, if clearing the window takes too much time, there is not a lot
you can do. One possibility is to make the window’s default size smaller
(while still allowing users to resize the window if necessary).

Improving Traversal Performance
After running ivperf, you know how much time your application spends
clearing the color and depth buffers. The next experiment is designed to find
out how much time Inventor spends traversing your scene. Traversal is the
process of walking through the scene graph, deciding which render method
needs to be called at each node. Inventor automatically caches the parts of
your scene that aren’t changing and that are expensive to traverse, building
an OpenGL display list and eliminating the traversal overhead.
If most of your scene is changing, or if your scene is not organized for
efficient caching, Inventor may not be able to build render caches, and
traversal might be the bottleneck in your application. ivperf measures the
difference between rendering your scene with nothing changing, and
rendering with the camera, engines, and nodes named “NoCache”
changing.

55

Chapter 6: Optimizing Open Inventor Applications

If traversing the scene is a bottleneck in your program, there are several
ways of reducing the traversal overhead:
•

Reduce the number of nodes in the scene. For example, eliminate
SoSeparator and SoGroup nodes that have only one child by replacing
them with the child.

•

Use the vertexProperty field of the vertex-based shapes to specify
coordinates, normals, texture coordinates, and colors. (See
“SoVertexProperty Node” on page 24).

•

Beware of features that require multiple traversals of the scene graph
for each render update. For example, avoid using accumulation buffer
antialiasing and SoAnnotation nodes, and use the transparency type
SoGLRenderAction::SCREEN_DOOR.

•

Organize your scene graph for caching. The next section discusses ways
of doing this. If you are using SoLOD nodes or render culling, also see
“Correcting Culling Bottlenecks” on page 66 and “Correcting Level of
Detail Bottlenecks” on page 67 for hints on optimizing those features,
which ivperf also reports as part of caching behavior.

Organizing the Scene for Caching
You may be able to organize your scene so that Inventor can build and use
render caches even if part of the scene is changing. Note that the following
things inhibit caching:
•

Changing fields in the scene destroys caches inside all SoSeparator
nodes above the node that changed. Even fields that do not affect
rendering, such as fields in the SoLabel or SoPickStyle nodes, destroy
caches if they are changed.
Tip: You can disable notification on these nodes using the

SoNode::enableNotify() method to keep changes to them from
destroying caches.
•

56

The SoLOD node (and the older SoLevelOfDetail node) breaks caches
above it whenever either the camera or any of the matrix nodes
affecting it change. Make the children of the SoLOD node SoSeparator
nodes, so that they will be cached. See “Correcting Level of Detail
Bottlenecks” for more information on efficient use of the SoLOD node.

Optimizing Rendering

•

Any shape using SCREEN_SPACE complexity breaks caches above it
whenever the camera or any of the matrix nodes affecting it change.

•

The SoText2 node breaks caches above it whenever the camera changes
(in order to correctly position and justify each line of text, it must
perform a calculation based on the camera). Since most applications
change the camera frequently, try to separate SoText2 nodes from the
other objects in your scene, to allow the other objects to be cached.
Tip: In Inventor 2.1, single-line, left-justified SoText2 nodes do not break
render caches.

•

Changing the override status of properties at the top of the scene, or
changing global properties such as SoDrawStyle or SoComplexity that
affect the rest of the scene, inhibits efficient caching. SoSeparator nodes
build multiple render caches (by default, a maximum of two) to handle
cases in which a small set of global properties are changed back and
forth, but you should avoid continuously changing a global property;
for example, putting an engine on the value field of an SoComplexity
node at the top of your scene is bad for caching.

For more information on Inventor’s render caching, see Chapter 9 of The
Inventor Mentor.

Improving Material Change Bottlenecks
If ivperf reports that material changes are the rendering bottleneck, try the
following:
•

Use fewer material nodes. Group objects by material, and use one
material node for several objects.
Tip: When the ivfix utility rearranges scene graphs, it groups objects by

material.
•

Changing between materials with different shininess values is much
more expensive than changing any of the other material properties.

•

If you are using shapes with a materialIndex field, try to sort their parts
by material index to minimize material changes. For example, try to
change:

57

Chapter 6: Optimizing Open Inventor Applications

IndexedFaceSet { materialIndex [ 0,1,0,1,0,1,0,1 ] ... }

to:
IndexedFaceSet { materialIndex [ 0,0,0,0,1,1,1,1 ] ... }

This works only for PER_PART_INDEXED or PER_FACE_INDEXED
material bindings.

Optimizing Transformations
For transformation, ivperf reports two numbers: the overhead of changing
the OpenGL transformation matrix between rendering shapes and the time
it takes to transform the vertices in your scene through that matrix. This
section helps with the former, giving suggestions on how to make Inventor
execute fewer OpenGL matrix operations. See “Optimizing Vertex
Transformations” on page 61 for hints on optimizing the transformation of
vertices.
•

To measure how much time transformations might be taking, ivperf
temporarily removes all transformations from your scene and then
measures how much faster it runs. Beware! This sometimes gives
unreliable results; for example, if all your objects become very large or
very small without the transformations, then more (or less) time may be
spent filling in pixels. If your scene uses render culling, removing the
transformations makes more (or fewer) of the objects culled, distorting
the results reported by ivperf.

•

Use SoRotation, SoRotationXYZ, SoScale, or SoTranslation nodes
instead of the general SoTransform node. However, don’t bother doing
this if you have to replace the SoTransform node with more than one of
the simpler nodes to get the same transformation.

Performance Tip for Face Sets
For best performance when creating SoFaceSet and SoIndexedFaceSet
shapes, arrange all the triangles first, then quads, and then other faces.

58

Optimizing Rendering

Optimizing Textures
If your scene contains textures, ivperf reports two numbers: the time you
would save if you could turn off textures completely, and the time you
would save if you could make your scene use only one texture. On systems
with texturing hardware, the number of textures used can dramatically
affect performance; see “Optimizing Texture Management” on page 59 for
hints on optimizing texture management. On systems without texture
mapping hardware, the bottleneck is probably filling in the textured
polygons.
Inventor 2.1 automatically does two things to speed up rendering on
systems without texture mapping hardware:
•

Inventor’s viewers display the scene untextured during interaction by
default.

•

Inventor uses lower-quality filters for minifying or magnifying
textures.

Optimizing Texture Management
If ivperf reports a lot of time is spent in texture management, then you are
running out of hardware texture memory. Try the following:
•

Use smaller textures. Use the izoom utility to scale down the images you
are using; inadvertently using one big image can easily fill up texture
memory on many systems.

•

Make textures a power of 2 wide and high. Textures of those
dimensions (for example 128 x 64 instead of 129 x 70) make startup
faster.

•

Reuse nodes. Inventor allows you to modify a texture once it has been
read into your application (using the image field of SoTexture2), and to
change the search path for textures (using methods on SoInput). It
therefore does not use the same texture memory for two different
SoTexture2 nodes with the same filename field. Be sure to reuse the
same SoTexture2 node instead of creating another node with the same
filename.

59

Chapter 6: Optimizing Open Inventor Applications

For example, this scene is inefficient:
Separator {
Texture2 { filename foo.rgb }
Cube { }
}
Sphere { }
Separator {
Texture2 { filename foo.rgb }
Text3 { string “Hello” }
}

This scene uses texture-memory efficiently:
Separator {
DEF foo Texture Texture2 { filename foo.rgb }
Cube { }
}
Sphere { }
Separator {
USE foo
Text3 { string “Hello” }
}

•

Textures are not shared if they are below an SoSeparator with
renderCaching turned on. Textures use less texture memory by
building an OpenGL display list or texture object containing the
OpenGL texture commands. However, if an OpenGL display list is
already created the first time an SoTexture2 node is traversed, it must
add the texture commands to the already open display list. Inventor’s
automatic caching algorithm handles this correctly; it is only a problem
if you turn caching on explicitly. For example, this scene uses twice as
much texture memory with the renderCaching field of the SoSeparator
set to ON:
Separator { renderCaching ON # BAD
DEF TEX Texture2 { filename foo.rgb }
Cube { }
... more stuff, other textures, etc...
USE TEX
Cube { }
}

60

Optimizing Rendering

•

Use SoLOD nodes to create simpler versions of your objects that are
not textured or use smaller texture images when the objects are far
away.

•

Use render culling so the textures for textured objects outside the view
volume are not used. For example, imagine a scene that contains 100
textured objects (each with a unique texture), but only 10 of them are in
the view volume at any given time. When the scene is rendered, only 10
of the textures need to be in texture memory at any given time,
resulting in much better texture management performance.

Using Lights Efficiently
If the scene given to ivperf contains light sources, ivperf informs you how
expensive they are compared to rendering your scene with just a single
directional light. If ivperf reports that lights are a significant performance
bottleneck, try to use fewer light sources, and use simpler lights (a
DirectionalLight is simpler than a PointLight, which is simpler than a
SpotLight). If possible, put lights inside separators so that they affect only
part of the scene, increasing performance for the rest of the scene.

Optimizing Vertex Transformations
If ivperf reports that vertex transformations (which include per-vertex
lighting calculations) take up a significant portion of the time it takes to
render a frame, you can do the following to optimize per-vertex operations:
•

Use fewer vertices in your objects. Use SoComplexity to turn down
complexity for Inventor’s primitive objects. If you are using a system
with hardware-accelerated texturing, texturing can be used to add
visual complexity with very few vertices.

61

Chapter 6: Optimizing Open Inventor Applications

62

•

Create less detailed versions of your objects and use SoLOD nodes so
that fewer vertices are drawn when objects are small. Use an empty
SoInfo node as the lowest level of detail so that objects disappear when
they get very small. A good rule of thumb for choosing levels of detail
is that the switch between levels of detail should be fairly obvious if
you are concentrating on the object; for most applications, the user
concentrates on objects in the foreground and does not notice
background objects “popping” between levels of detail. Beware that
SoLOD nodes cause smaller caches to be built, which may slow down
traversal. See “Correcting Level of Detail Bottlenecks” for more
information on efficient use of level of detail.

•

Make your vertices simpler. Try to use OVERALL rather than
PER_VERTEX material binding. Turn off fog. Note that these
suggestions are system-specific; on systems with a lot of hardware for
accelerated rendering, fogged vertices may be no slower than plain
vertices. Be sure to do a quick ivperf test before spending time
modifying your application.

•

Make sure you are not turning on two-sided lighting unnecessarily;
avoid SoShapeHints nodes that:
–

set vertexOrdering fields to COUNTERCLOCKWISE or
CLOCKWISE and

–

set shapeType fields to UNKNOWN_SHAPE_TYPE

•

If parts of your scene do not require lighting, use an SoLightModel
node set to model BASE_COLOR to turn off lighting for those parts of
the scene. However, be aware that turning lighting on and off can itself
become a bottleneck if done too often.

•

If you are using SoFaceSet or SoIndexedFaceSet, try using ivfix to
convert them into SoIndexedTriangleStripSet, which draws more
triangles with fewer vertices. Note that ivfix cannot create a mesh if
your objects have sharp facets or PER_FACE material or normal
bindings.

•

Watch out for expensive primitives with lots of vertices, like SoText3
and SoSphere. ivperf reports the number of triangles in your scene;
make sure the number is reasonable for your desired performance.

Optimizing Rendering

•

Organize your scene graph so that objects that are close to each other
spatially are under the same SoSeparator, and turn on render culling so
that Inventor won’t send those objects’ vertices when the objects are not
in view. See The Inventor Mentor, Chapter 9, for more information on
render culling.

•

See “Making Inventor Produce Efficient OpenGL” on page 64 for hints
on making Inventor produce more efficient OpenGL calls.

Optimizing Pixel Fill Operations
A common bottleneck on low-end systems is drawing the pixels in filled
polygons. This is especially common for applications that have just a few
large polygons, as opposed to applications that have lots of little polygons.
If ivperf reports that a large percentage of each frame is spent filling in pixels,
try to optimize your scene as follows:
•

Render your scene, or parts of your scene, in wireframe or as points
when possible. Viewers have “move wireframe” and “move points”
modes built in for exactly this case.

•

Some systems can fill flat-shaded polygons faster that Gouraud-shaded
polygons. Triangle strips and quad meshes set shademodel(FLAT) if
they have PER_FACE normals and don’t have PER_VERTEX materials
(and vice versa).

•

SCREEN_DOOR transparency (the default) is faster than blended
transparency on some systems (it is slower on other systems). Use the
setTransparencyType() method on either SoXtRenderArea or
SoGLRenderAction to change the transparency type.

Correcting Problems ivperf Does Not Measure
There are several performance problems that ivperf doesn’t catch. The
following sections describe them, and give hints on how to improve them.

63

Chapter 6: Optimizing Open Inventor Applications

Making Inventor Produce Efficient OpenGL

If your application is rendering only 10 frames per second with 1,000
triangles per frame, and you know that your graphics hardware is capable
of rendering 100,000 triangles per second (10,000 triangles per frame at 10
frames/second), and ivperf reports that your bottleneck is vertex
transformations, then your problem might be that Inventor is not making
efficient OpenGL calls.
Inventor is much more efficient at rendering multiple triangles if they are all
part of one node. For example, you can create a multifaceted polygonal
shape using a number of different coordinate and face set nodes, as shown
in the lower half of Figure 6-1. A much better technique is to put all the
coordinates for the polygonal shape into one SoCoordinate or
SoVertexProperty node, and the description of all the face sets into a second
SoFaceSet node, as shown in the upper half of Figure 6-1.
Tip: The ivfix utility program collapses multiple shapes into single triangle

strip sets.

64

Optimizing Rendering

Efficent

all_coords faceset

Not Efficent

coords1

faceset1

coords2
Figure 6-1

faceset2

coords3

faceset3

Condensing Face Sets Into Fewer Nodes

65

Chapter 6: Optimizing Open Inventor Applications

Using fewer nodes to get the same picture reduces traversal overhead for
scenes that cannot be cached. Note also that Inventor optimizes on a node by
node basis and generally can’t optimize across nodes.
An SoFaceSet or SoIndexedFaceSet has special code for drawing 3 and
4-vertex polygons. To take advantage of that, you must arrange the polygons
so that the 3-vertex polygons (if any) are first in the coordIndex array,
followed by the 4-vertex polygons, followed by the polygons with more than
4 vertices.
For some applications, consider implementing your own nodes that
implement the functionality of a subgraph of your scene. For example, a
molecular modeling application might implement a BallAndStick node
with fields specifying the atoms and bonds in a molecule, instead of using
the more general SoSphere, SoCylinder, SoMaterial, SoTransform, and
SoGroup nodes. If the molecular modeling application changes the
molecule frequently so Inventor cannot cache the scene, using a specialized
node could make traversal orders of magnitude faster (for example, a simple
water molecule scene graph with three atoms and two bonds might consist
of 20 nodes; replacing this with a single BallAndStick node would make
traversal 20 times faster). The BallAndStick node could also perform
application-specific optimizations not done by Inventor, such as not
drawing bonds between spheres whose radii were large enough that they
intersected, sorting the spheres and cylinders by color, and so on. See The
Inventor Toolmaker for complete information on implementing your own
nodes.
Correcting Culling Bottlenecks

If your application uses render culling, it may be spending most of its time
deciding whether or not objects should be culled. ivperf lumps this in with
bad caching behavior. To find out whether this is the case, use prof, pixie, or
the CaseVision™/WorkShop Performance Analyzer tools to look for a lot of
CPU time being spent in the SoSeparator::cullTest() or
SoBoundingBoxAction::apply() routines. See the reference pages for prof,
pixie, or cvspeed for information on using these tools.

66

Optimizing Rendering

If a large percentage of the rendering time is spent doing cull tests, try to
reorganize your scene so that more triangles are culled for each culling
SoSeparator. For example, if you have a city scene with thousands of
buildings, it may be better to perform one cull test for each city block rather
than the thousands of cull tests needed to decide whether or not each
individual building is visible. Doing this also allows Inventor to build larger
render caches, which may increase traversal speed.
Also, remember that render culling breaks render caches when the camera
or transformation matrices change, so double-check to make sure that no
SoSeparator nodes above an SoSeparator doing render culling have their
renderCaching fields set to ON.
If a lot of time is being spent inside SoGetBoundingBoxAction::apply(),
something is breaking bounding box caches.
Correcting Level of Detail Bottlenecks

If your application uses SoLOD nodes, it might be spending a significant
amount of time deciding which level of detail should be drawn. One way of
testing to see if this is the case is to temporarily replace all of the SoLOD
nodes in your scene with SoSwitch nodes set to traverse the highest level of
detail. Then run ivperf again and compare the results. If the SoSwitch node
scene is much faster, try doing the following:
•

Try to group objects so that one level of detail test determines the level
of detail for several objects. For example, if you have a group of 10
buildings that are near each other, use one level of detail node instead
of 10 level of detail nodes. Doing this also makes it easier for Inventor
to build larger render caches, which may increase performance by
increasing traversal speed.

•

Remember that level of detail nodes break render caches when the
camera or transformation matrices change, so make sure that no
SoSeparator nodes above an SoLOD have their renderCaching fields
set to ON.

•

Make sure you use the SoLOD node introduced in Inventor 2.1 instead
of the SoLevelOfDetail node. The SoLOD node is more efficient
because it uses the distance to a point as the switching criterion. See the
reference page for more detail.

67

Chapter 6: Optimizing Open Inventor Applications

Making Your Application Feel Faster

Sometimes it is worthwhile to sacrifice features temporarily to make your
application seem faster to the user. Inventor has several features that make
this easier:
•

Use the SoGLRenderAction::setAbortCallback() method to interrupt
rendering before the entire scene has been drawn. For this to be most
effective, you must organize your scene so that the most important
objects are drawn first, and you should abort only when it is important
that rendering happen quickly, even if the rendering is not complete,
such as when the user is interactively manipulating the scene.

•

Use one of the “Move ...” draw styles if you are using a viewer, so that a
simpler version of the scene is drawn when the user is interacting with
the viewer.

•

Use the start and finish callbacks of manipulators and components to
temporarily modify the scene to make it simpler while the user is
interacting with it.

Optimizing Everything Else
If you have determined that rendering is not your bottleneck, or if you have
already optimized rendering as much as possible and a significant amount
of time is still being spent doing something other than rendering, it’s time to
look for other bottlenecks. This section helps you find other bottlenecks, and
suggests Inventor-specific things to look for by discussing the following:

68

•

“Useful Tools”

•

“Optimizing Memory Usage”

•

“Looking at CPU Usage”

•

“Optimizing Action Construction and Setup”

•

“Decreasing Notification Overhead”

•

“Picking and Handling Events”

Optimizing Everything Else

Useful Tools
The standard performance analysis tools (prof, pixie, or the
CaseVision/WorkShop Performance Analyzer) make performance analysis
of the non-graphics part of your application easy. See the reference pages for
prof, pixie, or cvspeed for information on using these tools.

Optimizing Memory Usage
First, make sure your application isn’t running out of physical memory by
running gr_osview with the -a flag and looking for “swap” in the “CPU Wait”
usage bar. If your application is swapping, try to reduce its memory usage
as follows:
•

Turn off render caching. Call SoSeparator::setNumRenderCaches(0)
just after initializing Inventor to globally turn off automatic render
caching. You can also turn off render caching for parts of your scene
using the renderCaching field of SoSeparator.
The automatic caching algorithm in Inventor 2.1 avoids caching notes
that contain a large number of polygons.

•

If you are using caching, avoid using PER_FACE or
PER_FACE_INDEXED materials or normal bindings for
SoTriangleStripSet, SoIndexedTriangleStripSet, and SoQuadMesh
nodes. FACE bindings force Inventor to break each triangle or quad
into an individual triangle or quad, more than doubling the space the
node takes in the render cache.

•

If you have SoBaseColor or SoMaterial nodes containing just diffuse
colors, change them to SoPackedColor nodes, which use less memory.

•

Use instancing instead of duplicating geometry or properties wherever
possible. Instancing makes your scene graph take up less memory and
enables Inventor to build OpenGL display lists that are used more than
once. This is especially important for SoTexture2 nodes.

69

Chapter 6: Optimizing Open Inventor Applications

Looking at CPU Usage
If memory is not the problem, start by looking at “inclusive” CPU times for
your procedures (inclusive times include time spent in that procedure and
all procedures it calls; exclusive times are just the time spent in that
procedure). Ignore the very highest level routines like main() or
SoXt::mainLoop(); look for Inventor beginTraversal() methods or for
application routines that are taking a significant percentage of time. If a lot
of time is spent in SoGLRenderAction::beginTraversal(), see “Optimizing
Rendering” on page 52 for information on improving rendering
performance.
If your application is spending a lot of time in code written by you, you are
on your own! The rest of this section describes Inventor routines that often
show up on profile traces, describes what these routines do, and suggests
ways of using them more efficiently.

Optimizing Action Construction and Setup
Inventor actions perform a lot of work the first time they are applied to a
scene (subsequent traversals are very fast). Therefore, if your performance
traces show a lot of time being spent inside an action’s constructor or the
SoAction::setUpState() method, try to create an action once and reapply it
instead of constructing a new action. For example, if you often compute the
bounding boxes of some objects in the scene, keep an instance of an
SoBoundingBoxAction around that is reused:
static SoGetBoundingBoxAction *bbAction = NULL;
if (bbAction == NULL) bbAction = new SoGetBoundingBoxAction;
bbAction->apply(myScene);

instead of the much less efficient:
SoGetBoundingBoxAction bbAction;
// inefficient if called a lot!
bbAction.apply(myScene);

70

Optimizing Everything Else

Decreasing Notification Overhead
Every time you change a field in the scene, Inventor performs a process
called notification. A notification message travels up the scene graph to the
node’s parents, scheduling sensors, causing caches to be destroyed, and
marking any connections to engines or other fields as needing evaluation.
If your performance traces show a lot of time is being spent in a startNotify()
method, try the following to decrease notification overhead:
•

If you are modifying several values in a multiple-valued field, use the
setValues() methods or the startEditing()/finishEditing() methods
instead of repeatedly calling the set1Value() method.

•

Build scenes from the bottom up. Set leaf nodes’ fields first, then add
them to their parents, then add the parents to their parents, and so on.
For example, do this:
SoCube *myCube = new SoCube;
c->width = 10.0;
SoCylinder *myCylinder = new SoCylinder;
myCylinder->radius = 4.0;
SoSwitch *mySwitch = new SoSwitch;
mySwitch->whichChild = 0;
mySwitch->addChild(cube);
mySwitch->addChild(cylinder);
SoSeparator *root = new SoSeparator;
root->ref();
root->addChild(mySwitch);

instead of the less efficient:
SoSeparator *root = new SoSeparator;
root->ref();
SoSwitch *mySwitch = new SoSwitch;
root->addChild(mySwitch);
mySwitch->whichChild = 1;
SoCube *myCube = new SoCube;
mySwitch->addChild(myCube);
myCube->width = 4.0;
SoCylinder *myCylinder = new SoCylinder;
mySwitch->addChild(myCylinder);
myCylinder->radius = 4.0;

71

Chapter 6: Optimizing Open Inventor Applications

•

Using many path sensors can cause notification to become slow, since
an SoPathSensor is notified whenever any change happens underneath
the head node of the SoPath monitored by the SoPathSensor.
Note: SoPath itself does not have this problem in Inventor 2.X (but did

in Inventor 1.X).
•

Notification can be enabled or disabled on a per-node or per-engine
basis. Beware that because caching, sensors, and connections rely on
notification for proper operation, you must be very careful when using
this feature. See the SoFieldContainer reference page for information
on the enableNotify() method.

Picking and Handling Events
If your application profiles show a lot of time is spent inside the methods
SoHandleEventAction::beginTraversal() or
SoPickAction::beginTraversal(), try the following to improve picking
and/or event handling performance:

72

•

Insert SoPickStyle::UNPICKABLE nodes in your scene to turn off
picking for objects that should never be picked (for example “dead”
background graphics).

•

Insert SoPickStyle::BOUNDING_BOX nodes in your scene if you do
not need detailed picking information. This helps most for complicated
objects like SoText3 or SoTriangleStripSets with many triangles.

•

If you have objects with a lot (thousands) of polygons in them, break
them up into several objects under different separators, grouping
polygons that are close to each other. This allows SoSeparator pick
culling to quickly reject many of the triangles.

•

To speed up event handling, try to put active objects that respond to
events toward the left and top of the scene graph. An
SoHandleEventAction ends traversal as soon as a node reports that it
has handled the event.

•

If you write your own event callback node, or implement a node that
responds to events, be sure to use the grabEvents() method when
appropriate. Because grabbing short-circuits traversal of the scene, it is
a useful way to speed up event distribution.

Appendix A

A.

Creating a Node

This appendix explains how you can create new subclasses of SoNode. It
discusses enabling elements in the state, constructing a node, and
implementing actions for it. Chapter 1 in The Inventor Toolmaker provides
important background material on these concepts, and this appendix
assumes you are familiar with the material presented there.
Note: This appendix provides an update of Chapter 2, “Creating a Node,”

of The Inventor Toolmaker. Its main purpose is to help developers who are new
to Inventor work with Inventor 2.1 without being hindered by outdated
information. Developers familiar with Inventor may prefer to look at
Chapter 5, “Incompatible Extender API Changes” and at the revised
example programs instead of reading this complete appendix.
The first part of this appendix offers an overview of the steps required to
create a new node. When necessary, additional sections explain key concepts
in further detail and list the relevant macros. Next, the appendix examples
show how to create three new node classes:
•

“Creating a Property Node” on page 82

•

“Creating a Shape Node” on page 88

•

“Creating a Group Node” on page 108

Sections at the end of the chapter discuss the following:
•

“Using New Node Classes” on page 116

•

“Creating an Abstract Node Class” on page 120

•

“The copyContents() Method” on page 120

•

“The affectsState() Method” on page 121

•

“Uncacheable Nodes” on page 121

•

“Generating Default Normals” on page 122

•

“Creating an Alternate Representation” on page 122

73

Appendix A: Creating a Node

Overview
The file SoSubNode.h contains the macros for defining new node classes. The
SO_NODE_HEADER() macro declares type identifier and naming variables
and methods that all node classes must support. The SO_NODE_SOURCE()
macro defines the static variables and methods declared in the
SO_NODE_HEADER() macro. Other macros useful in creating new node
classes are mentioned in the following sections.
Creating a new node requires these steps:

74

1.

Select a name for the new node class and determine what class it is
derived from.

2.

Define and name each field in the node.

3.

Define an initClass() method to initialize the type information and to
ensure that the required elements are enabled in the state (see
“Initializing the Node Class” on page 75).

4.

Define a constructor (see “Defining the Constructor” on page 76).

5.

Implement the actions supported by the node (see “Implementing
Actions” on page 78).
■

For a property node, you usually need to implement the
GLRender() and callback() methods (see “Creating a Property
Node” on page 82). You may also need to implement
getBoundingBox(), getMatrix(), and other methods.

■

For a shape node, you need to implement the generatePrimitives()
method for the SoCallbackAction as well as the getBoundingBox()
method. You may want to implement a specific GLRender() or
rayPick() method as well (see “Creating a Shape Node” on
page 88). For vertex-based shapes, you may need to implement a
generateDefaultNormals() method.

■

For a group node, you need to implement all actions to ensure that
the children are traversed (see “Creating a Group Node” on
page 108).

6.

Implement a copyContents() method if the node contains any non-field
instance data (see “The copyContents() Method” on page 120).

7.

Implement an affectsState() method if it cannot be inherited from the
parent class (see “The affectsState() Method” on page 121).

Initializing the Node Class

Initializing the Node Class
As discussed in Chapter 1 of The Inventor Toolmaker, the initClass() method
sets up the type identifier and file format name information for the class. The
initialization macro for nodes, SO_NODE_INIT_CLASS(), does most of the
work for you. One additional task for you as the node writer is to enable each
of the elements in the state for each action the node supports. The following
subsections provide additional information about enabling elements in the
state.

Enabling Elements in the State
In the initClass() method, use the SO_ENABLE() macro (defined in
SoAction.h) to enable the elements required by your node in the state. To use
a simple example, SoDrawStyle enables these elements in the
SoGLRenderAction:
SO_ENABLE(SoGLRenderAction,
SO_ENABLE(SoGLRenderAction,
SO_ENABLE(SoGLRenderAction,
SO_ENABLE(SoGLRenderAction,

SoGLDrawStyleElement);
SoGLLinePatternElement);
SoGLLineWidthElement);
SoGLPointSizeElement);

SoDrawStyle also implements the SoCallbackAction. It enables these
elements in the SoCallbackAction:
SO_ENABLE(SoCallbackAction,
SO_ENABLE(SoCallbackAction,
SO_ENABLE(SoCallbackAction,
SO_ENABLE(SoCallbackAction,

SoDrawStyleElement);
SoLinePatternElement);
SoLineWidthElement);
SoPointSizeElement);

Tip: If you know that the element is already enabled by another node or

action, you can skip this step.
Now that these elements have been enabled, their values can be set and
queried. (The debugging version of Inventor generates an error if you try to
access or set an element that has not been enabled.)

75

Appendix A: Creating a Node

Inheritance Within the Element Stack
The example using SoDrawStyle elements in the previous section brings up
another feature of the element stack: Some elements have corresponding GL
versions that are derived from them. The SoGL version of an element
typically sends its value to OpenGL when it is set. SoGLDrawStyleElement
is derived from SoDrawStyleElement, and SoGLLinePatternElement is
derived from SoLinePatternElement. The parent element class and its
derived class share the same stack index.
If you try to enable two classes that share a stack index (for example,
SoGLDrawStyleElement and SoDrawStyleElement), only the more
derived class is actually enabled (in this case, SoGLDrawStyleElement).
However, you can always use the base class static method to set or get the
value for either the parent or the derived class. (You cannot, however, enable
only the parent version and then try to treat it as the derived GL version.)

Defining the Constructor
The constructor defines the fields for the node and sets up their default
values. If the fields contain enumerated values, their names and values are
defined in the constructor as well. Use the SO_NODE_CONSTRUCTOR()
macro to perform the basic work.
The SO_NODE_IS_FIRST_INSTANCE() macro returns a Boolean value that
can be tested in constructors. If your class requires considerable overhead
when it is initialized, you may want to perform this work only once when
the first instance of the class is created. For example, the SoCube class sets
up the coordinates and normals of the cube faces during construction of its
first instance. (You could put this code in the initClass() method, but putting
it in the constructor guarantees that someone is actually using your node
class first.)

76

Defining the Constructor

Setting Up the Node’s Fields
The SO_NODE_ADD_FIELD() macro defines the fields in the node and sets
up their default values. The first parameter is the name of the field. The
second parameter is the default field value, in parentheses. Using
SoDrawStyle as an example:
SO_NODE_ADD_FIELD(style, (SoDrawStyleElement::getDefault()));
SO_NODE_ADD_FIELD(lineWidth,
(SoLineWidthElement::getDefault()));
SO_NODE_ADD_FIELD(linePattern,
(SoLinePatternElement::getDefault()));
SO_NODE_ADD_FIELD(pointSize,
(SoPointSizeElement::getDefault()));

To add a field with a vector value, the syntax is as follows:
SO_NODE_ADD_FIELD(translation, (0.0, 0.0, 0.0));

Defining Enumerated Values for a Field
In the preceding example, the style field contains an enumerated value:
FILLED, LINES, POINTS, or INVISIBLE. Use the SO_NODE_DEFINE_ENUM_VALUE() macro to define the enumerated values. The first
parameter is the type of the enumerated value. The second parameter is its
value, as shown here:
SO_NODE_DEFINE_ENUM_VALUE(Style,
SO_NODE_DEFINE_ENUM_VALUE(Style,
SO_NODE_DEFINE_ENUM_VALUE(Style,
SO_NODE_DEFINE_ENUM_VALUE(Style,

FILLED);
LINES);
POINTS);
INVISIBLE);

Then, to specify that these enumerated values can be used in the style field
of the SoDrawStyle node, use the SO_NODE_SET_SF_ENUM_TYPE()
macro:
O_NODE_SET_SF_ENUM_TYPE(style, Style);

77

Appendix A: Creating a Node

Implementing Actions
Your next task is to implement each of the actions your new node supports.
The SoDrawStyle node, as you have already seen, supports two actions, the
SoGLRenderAction and the SoCallbackAction, in addition to the
SoSearchAction and the SoWriteAction, which it inherits from SoNode.
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.

The doAction() Method
For the GL render action, the SoDrawStyle node changes the values of 4
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 in the state to INVISIBLE. First it must check if
another node has overridden the draw style. The code to set the element’s
value is:
if (! style.isIgnored() &&
! SoOverrideElement::getDrawStyleOverride(state)) {
if (isOverride()) {
SoOverrideElement::setDrawStyleOverride(state, this,
TRUE);
}
SoDrawStyleElement::set(state,
(SoDrawStyleElement::Style) style.getValue());
}

For the callback action, SoDrawStyle 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, the common code is
put into a separate method that both the GL render and the callback actions
can call. By convention, this shared method used by property nodes is called
doAction(), which is a virtual method on SoNode. The code for the
draw-style node’s callback action followed by the node’s GL render action:
void
SoDrawStyle::callback(SoCallbackAction *action)
(

78

Implementing Actions

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

This is the draw-style node’s doAction() method:
SoDrawStyle::doAction(SoAction *action)
{
SoState
*state = action->getState();
if (! style.isIgnored() &&
! SoOverrideElement::getDrawStyleOverride(state)) {
if (isOverride()) {
SoOverrideElement::setDrawStyleOverride(
state, this, TRUE);}
SoDrawStyleElement::set(state,
(SoDrawStyleElement::Style)
style.getValue());
}
if (! pointSize.isIgnored() &&
! SoOverrideElement::getPointSizeOverride(state)) {
if (isOverride()) {
SoOverrideElement::setPointSizeOverride(
state, this, TRUE);}
SoPointSizeElement::set(state, pointSize.getValue());
}
if (! lineWidth.isIgnored() &&
! SoOverrideElement::getLineWidthOverride(state)) {
if (isOverride()) {
SoOverrideElement::setLineWidthOverride(state,
state, this, TRUE);}
SoLineWidthElement::set(state, lineWidth.getValue());
}

79

Appendix A: Creating a Node

if (! linePattern.isIgnored() &&
!SoOverrideElement::getLinePatternOverride(state)) {
if (isOverride()) {
SoOverrideElement::setLinePatternOverride(
state, this, TRUE);}
SoLinePatternElement::set(state,
linePattern.getValue());
}
}

The advantage of this scheme becomes apparent when you consider
extending the set of actions (see Chapter 4 of The Inventor Toolmaker). You can
define a new action class and implement a static method for 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 of The Inventor Toolmaker, each element class
provides methods for setting and querying its value. The static set() method
usually has two parameters, as shown in the previous section:
•

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

•

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, use:
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()

80

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

Implementing Actions

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 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 SoMaterialBinding node changes only the SoMaterialBindingElement
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. In Inventor 2.1, these functions are more efficiently performed by
using the SoLazyElement (see the next section for more information). The
following examples use the SoLazyElement instead of material bundles.
Methods on the SoMaterialBundle allow shapes to step easily through
sequential materials and to send the current material to OpenGL.
SoNormalBundle lets you step easily through sequential normals and
provides functions for generating default normals.
SoTextureCoordinateBundle lets you step through texture coordinates and
provides methods for generating texture coordinates if the shape is using
SoTextureCoordinatePlane or SoTextureCoordinateEnvironment.

81

Appendix A: Creating a Node

The SoLazyElement
Several elements that were responsible for material properties in Inventor
2.0 have been combined into two elements: SoLazyElement and
SoGLLazyElement, its derived OpenGL version.
•

SoLazyElement is responsible for setting and getting material state.

•

SoGLLazyElement is responsible for tracking the OpenGL state,
invalidating caches, and so on.

See “Changes in Material Elements” on page 38 for more information.

Creating a Property Node
The easiest way to learn how to add a node class is by example. The first
example creates a new property class called Glow, which modifies the
emissive color of the current material to make objects appear to glow. It has
a field called color, which is the color of the glow, and a float field called
brightness, ranging from 0 to 1, indicating how much the object should glow
and a transparency field, indicating the transparency of the glowing
material. Its value is between 0 (opaque) and 1 (invisible).
In Inventor 2.1, the transparency and diffuse color are combined in the state
into a packed color. To facilitate dealing with diffuse colors and transparency
independently, a class SoColorPacker is provided. In the example that
follows, the SoColorPacker is constructed in the Glow node constructor,
and is used whenever the transparency is set in the state.
The doAction() method of the Glow node must check whether the emissive
color or transparency is already being overridden by another material node.
The SoOverrideElement determines what is overridden.
For this class, the program needs to implement actions that deal with
materials: GLRender() and callback(). It uses doAction() (see “The
doAction() Method” on page 78) since it performs the same operation for
both actions. The doAction() method for the Glow class updates the lazy
color element based on the values of the color, transparency, and brightness
fields of the node.

82

Creating a Property Node

The class header for the new node is shown in Example A-1.
Example A-1
#include
#include
#include
#include

Glow.h






// Need SoColorPacker to put the transparency into the state:
class SoColorPacker;
class Glow : public SoNode {
SO_NODE_HEADER(Glow);
public:
// Fields:
SoSFColor color;
// Color of glow
SoSFFloat brightness;
// Amount of glow (0-1)
SoSFFloat transparency; // transparency of glowing object
// Initializes this class for use in scene graphs. This
// should be called after database initialization and
// before any instance of this node is constructed.
static void
initClass();
// Constructor
Glow();
protected:
// These implement supported actions. The only actions
// dealing with materials are the callback and GL render
// actions. We will inherit all other action methods from
// SoNode.
virtual void
GLRender(SoGLRenderAction *action);
virtual void
callback(SoCallbackAction *action);
// This implements generic traversal of Glow node, used in
// both of the above methods.
virtual void
doAction(SoAction *action);
private:
// Destructor. Private to keep people from trying to
// delete nodes instead of using reference count

83

Appendix A: Creating a Node

// mechanism.
virtual ~Glow();
// A pointer to the color packer is required in nodes
// that set diffuse or transparency. This will
// be initialized in constructor, deleted in destructor:
SoColorPacker *colorPacker;
// Keep a copy of the transparency that this node puts
// into the state:
float transpValue;

The Glow node is representative of most property nodes in that it is
concerned solely with editing the current traversal state, regardless of the
action being performed.
The source code for the Glow class is shown in Example A-2.
Example A-2
#include
#include
#include
#include

Glow.c++






#include "Glow.h"
SO_NODE_SOURCE(Glow);
//
// Initializes the Glow class. This is a one-time thing that
// is done after database initialization and before any
// instance of this class is constructed.
//
void
Glow::initClass()
{
// Initialize type id variables. Arguments to the macro
// are: the name of the node class, the class this is
// derived from, and the name registered with the type of
// the parent class.
SO_NODE_INIT_CLASS(Glow, SoNode, "Node");
}

84

Creating a Property Node

//
// Constructor
//
Glow::Glow()
{
// Do standard constructor stuff:
SO_NODE_CONSTRUCTOR(Glow);
// Add "color" field to the field data. The default value
// for this field is R=G=B=1, which is white.
SO_NODE_ADD_FIELD(color, (1.0, 1.0, 1.0));
// Add "brightness" field to the field data. The default
// value for this field is 0.
SO_NODE_ADD_FIELD(brightness, (0.0));
// Add "transparency" field to the field data.
// value is 0 (opaque).
SO_NODE_ADD_FIELD(transparency, (0.0));

Default

// Initialize the color Packer (required of any property
// node that uses an SoColorPacker to set diffuse color
// or transparency):
colorPacker = new SoColorPacker;
}
//
// Destructor
//
Glow::~Glow()
{
// Delete the color Packer:
delete colorPacker;
}
//
// Implements GL render action.
//
void
Glow::GLRender(SoGLRenderAction *action)

85

Appendix A: Creating a Node

{
// Set the elements in the state correctly. Note that we
// prefix the call to doAction() with the class name. This
// avoids problems if someone derives a new class from the
// Glow node and inherits the GLRender() method; Glow's
// doAction() will still be called in that case.
Glow::doAction(action);
//
//
//
//

Note that in Inventor 2.1 the GLRender method only
sets the color in the lazy element; it does not send
it to GL. This is for efficiency, since there is no
need to send it until it is really used.

}
//
// Implements callback action.
//
void
Glow::callback(SoCallbackAction *action)
{
// Set the elements in the state correctly.
Glow::doAction(action);
}
//
// Typical action implementation; it sets the correct element
// in the action's traversal state. We assume that the lazy
// element has been enabled.
//
void
Glow::doAction(SoAction *action)
{
// Make sure the "brightness" field is not ignored. If it
// is, then we don't need to change anything in the state.
// Also check that the emissive color is not being
// overridden; if it is, this node should not set it.
if (! brightness.isIgnored() &&
! SoOverrideElement::getEmissiveColorOverride
(action->getState())) {

86

Creating a Property Node

SbColor emissiveColor = color.getValue() *
brightness.getValue();
//Use the Lazy element to set emissive color.
//Note that this won’t actually send the color to GL.
SoLazyElement::setEmissive(action->getState(),
&emissiveColor);
}
// To send transparency we again check ignore flag and
// override element.
if (! transparency.isIgnored() &&
! SoOverrideElement::getTransparencyOverride
(action->getState())) {
// Keep a copy of the transparency that we are
// putting in the state:
transpValue = transparency.getValue();
// The color packer must be provided when
// transparency is set, so that the transparency
// will be merged with current diffuse color
// in the state:
SoLazyElement::setTransparency(action->getState(),
this, 1, &transpValue, colorPacker);
}
}

87

Appendix A: Creating a Node

Creating a Shape Node
This next example is more complicated than the property-node example,
because shape nodes need to access more of the state and implement
different behaviors for different actions. For example, a shape needs to draw
geometry during rendering, return intersection information during picking,
and compute its extent when getting a bounding box.
All shapes need to define at least two methods: generatePrimitives() and
getBoundingBox(). When you define the generatePrimitives() method for
your new class, you can inherit the GLRender() and rayPick() methods from
the base class, SoShape, because they use the generated primitives. This
feature saves time at the prototyping stage, since you need to implement
only the generatePrimitives() method, and rendering and picking are
provided at no extra cost. When you are ready for fine-tuning, you can
redefine these two methods to improve performance.

Generating Primitives
When a shape node is traversed to generate primitives for the
SoCallbackAction, it generates triangles, line segments, or points. The
information for each vertex of the triangle, line segment, or point is stored in
an instance of SoPrimitiveVertex. The shape fills in the information for each
vertex. Then, for each primitive generated (that is, triangle, line segment, or
point), an appropriate callback function is invoked by a method on
SoShape. For example, if the shape generates triangles, the triangle callback
function is invoked for every triangle generated. Filled shapes, such as
SoCone and SoQuadMesh, generate triangles (regardless of draw style),
line shapes (such as SoLineSet and SoIndexedLineSet) generate line
segments, and point shapes (such as SoPointSet) generate points.

88

Creating a Shape Node

SoPrimitiveVertex

The SoPrimitiveVertex contains all information for that vertex:
•

Point (coordinates, in object space)

•

Normal

•

Texture coordinates

•

Material index

•

A pointer to an instance of an SoDetail subclass (may be NULL)

The shape’s generatePrimitives() method sets each of these values.
The appropriate callback function can be invoked either automatically or
explicitly. If you want explicit control over when the callback function is
invoked, you can use the following methods provided by the SoShape class:
•

invokeTriangleCallbacks()

•

invokeLineSegmentCallbacks()

•

invokePointCallbacks()

To take advantage of the automatic mechanism, use these three methods,
provided by the SoShape base class as a convenience:
•

beginShape(action, shapeType)

•

shapeVertex(&vertex)

•

endShape()

The shapeType parameter is TRIANGLE_FAN, TRIANGLE_STRIP,
TRIANGLES, or POLYGON. For example, if you choose TRIANGLE_FAN,
this method performs the necessary triangulation and invokes the
appropriate callbacks for each successive triangle of the shape. This
mechanism is similar to OpenGL’s geometry calls.

89

Appendix A: Creating a Node

Creating Details

You may want your shape to store additional information in an SoDetail—
for example, what part of the shape each vertex belongs to. In this case, you
can use an existing subclass of SoDetail (see The Inventor Mentor, Chapter 9),
or you can create a new SoDetail subclass to hold the appropriate
information. By default, the pointer to the detail in SoPrimitiveVertex is
NULL.
If you decide to store information in an SoDetail, you create an instance of
the subclass and store a pointer to it in the SoPrimitiveVertex by calling
setDetail().

Rendering
For rendering, you may be able to inherit the GLRender() method from the
SoShape class. In this case, you define a generatePrimitives() method as
described in the previous sections. Each primitive is generated and then
rendered separately.
In other cases, you may want to write your own render method for the new
shape class, especially if it would be more efficient to send the vertex
information to OpenGL in some other form, such as triangle strips. The
Pyramid node created later in this appendix implements its own
GLRender() method. Before rendering, the shape should test whether it
needs to be rendered. You can use the SoShape::shouldGLRender()
method, which checks for INVISIBLE draw style, BOUNDING_BOX
complexity, delayed transparency, and render abort.
Inventor takes care of sending the draw-style value to OpenGL (where it is
handled by glPolygonMode()). This means that filled shapes are drawn
automatically as lines or points if the draw style indicates such. Note that if
your object is composed of lines, but the draw style is POINTS, you need to
handle that case explicitly. You need to check whether the draw-style
element in the state is points or lines and render the shape accordingly.
The lazy elements are used in the GLRender() method of the Pyramid to
send the colors to OpenGL. This has to be set up as follows:

90

Creating a Shape Node

•

SoLazyElement::getLightModel() is used to tell if lighting is on. If so, it
is necessary to send normals to OpenGL.

•

SoLazyGLElement::sendAllMaterial() is needed prior to rendering the
shape. This ensures that the OpenGL material state is in agreement
with the current Inventor state.

•

SoGLLazyElement::sendDiffuseByIndex() is required if more than
one diffuse color or transparency is sent. This issues the appropriate
OpenGL commands to send the specified color and transparency to
OpenGL.

•

SoGLLazyElement::reset() is required at the end of rendering, if colors
other than the first color were sent to OpenGL. This informs the lazy
element that the current color in the state is not the last one sent to GL.

Picking
For picking, you may also be able to inherit the rayPick() method from the
SoShape class. In this case, you define a generatePrimitives() method, and
the parent class rayPick() method tests the picking ray against each
primitive that has been generated. If the ray intersects the primitive, it
creates an SoPickedPoint. SoShape provides three virtual methods for
creating details:
•

createTriangleDetail()

•

createLineDetail()

•

createPointDetail()

The default methods return NULL, but your shape can override this to set
up and return a detail instance.
The Pyramid node created later in this appendix inherits the rayPick()
method from SoShape in this manner.
For some shapes, such as spheres and cylinders, it is more efficient to check
whether the picking ray intersects the object without tessellating the object
into primitives. In such cases, you can implement your own rayPick()
method and use the SoShape::shouldRayPick() method, which first checks
to see if the object is pickable.

91

Appendix A: Creating a Node

The following excerpt from the SoSphere class shows how to implement
your own rayPick() method:
void
SoSphere::rayPick(SoRayPickAction *action)
{
SbVec3f
enterPoint, exitPoint, normal;
SbVec4f
texCoord(0.0, 0.0, 0.0, 1.0);
SoPickedPoint
*pp;
// First see if the object is pickable.
if (! shouldRayPick(action))
return;
// Compute the picking ray in our current object space.
computeObjectSpaceRay(action);
// Create SbSphere with correct radius, centered at zero.
float
rad = (radius.isIgnored() ? 1.0 :
radius.getValue());
SbSphere
sph(SbVec3f(0., 0., 0.), rad);
// Intersect with pick ray. If found, set up picked
// point(s).
if (sph.intersect(action->getLine(), enterPoint,
exitPoint)) {
if (action->isBetweenPlanes(enterPoint) &&
(pp = action->addIntersection(enterPoint)) !=
NULL) {
normal = enterPoint;
normal.normalize();
pp->setObjectNormal(normal);
// This macro computes the s and t texture
// coordinates for the shape.
COMPUTE_S_T(enterPoint, texCoord[0], texCoord[1]);
pp->setObjectTextureCoords(texCoord);
}
if (action->isBetweenPlanes(exitPoint) &&
(pp = action->addIntersection(exitPoint)) != NULL){
normal = exitPoint;
normal.normalize();
pp->setObjectNormal(normal);

92

Creating a Shape Node

COMPUTE_S_T(exitPoint, texCoord[0], texCoord[1]);
texCoord[2] = texCoord[3] = 0.0;
pp->setObjectTextureCoords(texCoord);
}
}
}

Getting a Bounding Box
SoShape provides a getBoundingBox() method that your new shape class
can inherit. This method calls a virtual computeBBox() method, which you
need to define. (The computeBBox() method is also used during rendering
when bounding-box complexity is specified.)
If you are deriving a class from SoNonIndexedShape, you can use the
computeCoordBBox() method within your computeBBox() routine. This
method computes the bounding box by looking at the specified number of
vertices, starting at startIndex. It uses the minimum and maximum
coordinate values to form the diagonal for the bounding box and uses the
average of the vertices as the center of the object.
If you are deriving a class from SoIndexedShape, you can inherit
computeBBox() from the base SoIndexedShape class. This method uses all
nonnegative indices in the coordinates list to find the minimum and
maximum coordinate values. It uses the average of the coordinate values as
the center of the object.
For improved picking performance, the rayPick() action assumes that the
bounding boxes include only surfaces; not lines or points. If your shape
object is composed of lines and/or points, you must implement a
getBoundingBox() method similar to the following method from the
SoLineSet implementation:
void
SoLineSet::getBoundingBox(SoGetBoundingBoxAction *action)
{
// Let our parent class do the real work
SoNonIndexedShape::getBoundingBox(action);

93

Appendix A: Creating a Node

// If there are any open bounding box caches, tell them
// that they contain lines
SoBoundingBoxCache::setHasLinesOrPoints
(action->getState());
}

Pyramid Node
This example creates a Pyramid node, which has a square base at y = −1 and
its apex at (0.0, 1.0, 0.0). The code presented here is similar to that used for
other primitive (nonvertex-based) shapes, such as cones and cylinders. The
pyramid behaves like an SoCone, except that it always has four sides. And,
instead of a bottomRadius field, the Pyramid class has baseWidth and
baseDepth fields in addition to the parts and height fields.
Some of the work for all shapes can be done by methods on the base shape
class, SoShape. For example, SoShape::shouldGLRender() checks for
INVISIBLE draw style when rendering. SoShape::shouldRayPick() checks
for UNPICKABLE pick style when picking. This means that shape
subclasses can concentrate on their specific behaviors.
To define a vertex-based shape subclass, you probably want to derive your
class from either SoNonIndexedShape or SoIndexedShape. These classes
define some methods and macros that can make your job easier.
You may notice in this example that there are macros (defined in
SoSFEnum.h) that make it easy to deal with fields containing enumerated
types, such as the parts field of our node. Similar macros are found in
SoMFEnum.h and in the header files for the bitmask fields.
The class header for the Pyramid node is shown in Example A-3.
Example A-3

Pyramid.h

#include 
#include 
#include 
#include 
// SoShape.h includes SoSubNode.h; no need to include it here
// Pyramid texture coordinates are defined on the sides so

94

Creating a Shape Node

//
//
//
//

that the seam is along the left rear edge, wrapping
counterclockwise around the sides. The texture
coordinates on the base are set up so the texture is
right side up when the pyramid is tilted back.

class Pyramid : public SoShape {
SO_NODE_HEADER(Pyramid);
public:
enum Part {
SIDES = 0x01,
BASE = 0x02,
ALL
= 0x03,
};
// Fields
SoSFBitMask
SoSFFloat
SoSFFloat
SoSFFloat

parts;
baseWidth;
baseDepth;
height;

//
//
//
//

Pyramid parts:
The 4 side faces
The bottom square face
All parts

//
//
//
//

Visible parts
Width of base
Depth of base
Height, base to apex

// Initializes this class
static void
initClass();
// Constructor
Pyramid();
// Turns on/off a part of the pyramid. (Convenience)
void
addPart(Part part);
void
removePart(Part part);
// Returns whether a part is on or off. (Convenience)
SbBool
hasPart(Part part) const;
protected:
// This implements the GL rendering action. We inherit
// all other action behavior, including rayPick(), which
// is defined by SoShape to pick against all of the
// triangles created by generatePrimitives.
virtual void GLRender(SoGLRenderAction *action);
// Generates triangles representing a pyramid
virtual void generatePrimitives(SoAction *action);

95

Appendix A: Creating a Node

// This computes the bounding box and center of a pyramid.
// It is used by SoShape for the SoGetBoundingBoxAction;
// also to compute the correct box to render or pick when
// complexity is BOUNDING_BOX. Note that we do not have to
// define a getBoundingBox() method, since SoShape already
// takes care of that (using this method).
virtual void
computeBBox(SoAction *action,
SbBox3f &box, SbVec3f ¢er);
private:
// Face normals. These are static because they are
// computed once and are shared by all instances
static SbVec3f frontNormal, rearNormal;
static SbVec3f leftNormal, rightNormal;
static SbVec3f baseNormal;
// Destructor
virtual ~Pyramid();
// Computes and returns half-width, half-height, and
// half-depth based on current field values
void
getSize(float &halfWidth,
float &halfHeight,
float &halfDepth) const;
};

The source code for the Pyramid node is shown in Example A-4.
Example A-4

Pyramid.c++

/*----------------------------------------------------------* This is an example from the Inventor Toolmaker,
* chapter 2, example 4.
*
* Source file for "Pyramid" shape node.
*----------------------------------------------------------*/
#include
#include
#include
#include
#include
#include
#include
#include
#include

96











Creating a Shape Node

#include
#include
#include
#include




"Pyramid.h"

// Shorthand macro for testing whether the current parts
// field value (parts) includes a given part (part)
#define HAS_PART(parts, part) (((parts) & (part)) != 0)
SO_NODE_SOURCE(Pyramid);
// Normals to four side faces and to base
SbVec3f Pyramid::frontNormal, Pyramid::rearNormal;
SbVec3f Pyramid::leftNormal, Pyramid::rightNormal;
SbVec3f Pyramid::baseNormal;
//
// This initializes the Pyramid class.
//
void
Pyramid::initClass()
{
// Initialize type id variables
SO_NODE_INIT_CLASS(Pyramid, SoShape, "Shape");
}
//
// Constructor
//
Pyramid::Pyramid()
{
SO_NODE_CONSTRUCTOR(Pyramid);
SO_NODE_ADD_FIELD(parts,
(ALL));
SO_NODE_ADD_FIELD(baseWidth, (2.0));
SO_NODE_ADD_FIELD(baseDepth, (2.0));
SO_NODE_ADD_FIELD(height,
(2.0));
//
//
//
//
//
//

Set up static values and strings for the "parts"
enumerated type field. This allows the SoSFEnum class
to read values for this field. For example, the first
line below says that the first value (index 0) has the
value SIDES (defined in the header file) and is
represented in the file format by the string "SIDES".

97

Appendix A: Creating a Node

SO_NODE_DEFINE_ENUM_VALUE(Part, SIDES);
SO_NODE_DEFINE_ENUM_VALUE(Part, BASE);
SO_NODE_DEFINE_ENUM_VALUE(Part, ALL);
// Copy static information for "parts" enumerated type
// field into this instance.
SO_NODE_SET_SF_ENUM_TYPE(parts, Part);
// If this is the first time the constructor is called,
// set up the static normals.
if (SO_NODE_IS_FIRST_INSTANCE()) {
float invRoot5
= 1.0 / sqrt(5.0);
float invRoot5Twice = 2.0 * invRoot5;
frontNormal.setValue(0.0, invRoot5, invRoot5Twice);
rearNormal.setValue( 0.0, invRoot5, -invRoot5Twice);
leftNormal.setValue( -invRoot5Twice, invRoot5, 0.0);
rightNormal.setValue( invRoot5Twice, invRoot5, 0.0);
baseNormal.setValue(0.0, -1.0, 0.0);
}
}
//
// Destructor
//
Pyramid::~Pyramid()
{
}
//
// Turns on a part of the pyramid. (Convenience function.)
//
void
Pyramid::addPart(Part part)
{
parts.setValue(parts.getValue() | part);
}
//
// Turns off a part of the pyramid. (Convenience function.)
//
void

98

Creating a Shape Node

Pyramid::removePart(Part part)
{
parts.setValue(parts.getValue() & ~part);
}
//
// Returns whether a given part is on or off. (Convenience
// function.)
//
SbBool
Pyramid::hasPart(Part part) const
{
return HAS_PART(parts.getValue(), part);
}
//
// Implements the SoGLRenderAction for the Pyramid node.
//
void
Pyramid::GLRender(SoGLRenderAction *action)
{
// Access the state from the action.
SoState *state = action->getState();
// See which parts are enabled.
int curParts = (parts.isIgnored() ? ALL :
parts.getValue());
//
//
//
//
if

First see if the object is visible and should be
rendered now. This is a method on SoShape that checks
for INVISIBLE draw style, BOUNDING_BOX complexity, and
delayed transparency.
(! shouldGLRender(action))
return;

// Declare a pointer to a GLLazyElement.
// used if we send multiple colors.
SoGLLazyElement* lazyElt;

This will be

// In Inventor 2.1 we do not use beginSolidShape and
// endSolidShape. Instead, this information should be
// provided in shape hints.

99

Appendix A: Creating a Node

// Change the current GL matrix to draw the pyramid with
// the correct size. This is easier than modifying all of
// the coordinates and normals of the pyramid. (For extra
// efficiency, you can check if the field values are all
// set to default values - if so, then you can skip this
// step.) Scale world if necessary.
float
halfWidth, halfHeight, halfDepth;
getSize(halfWidth, halfHeight, halfDepth);
glPushMatrix();
glScalef(halfWidth, halfHeight, halfDepth);
// See if texturing is enabled. If so, we will have to
// send explicit texture coordinates. The "doTextures"
// flag will indicate if we care about textures at all.
//
//
//
//

Note this has changed slightly in Inventor version 2.1.
The texture coordinate type now is either FUNCTION or
DEFAULT. Texture coordinates are needed only for
DEFAULT textures.

SbBool doTextures =
(SoGLTextureEnabledElement::get(state) &&
SoTextureCoordinateElement::getType(state) !=
SoTextureCoordinateElement::FUNCTION);
// Determine if we need to send normals. Normals are
// necessary if we are not doing BASE_COLOR lighting.
// Use the lazy element to get the light model.
SbBool sendNormals = (SoLazyElement::getLightModel(state)
!= SoLazyElement::BASE_COLOR);
// Determine if there's a material bound per part.
SoMaterialBindingElement::Binding binding =
SoMaterialBindingElement::get(state);
SbBool materialPerPart =
(binding == SoMaterialBindingElement::PER_PART ||
binding ==
SoMaterialBindingElement::PER_PART_INDEXED);
// Issue a lazy element send.
// The send ensures all material state in GL is current.
SoGLLazyElement::sendAllMaterial(state);

100

Creating a Shape Node

//
//
//
//
//

Render the parts of the pyramid. We don't have to worry
about whether to render filled regions, lines, or
points, since that is already taken care of. We are
also ignoring complexity, which we could use to render
a more finely tesselated version of the pyramid.

// We'll use this macro to make the code easier. It uses
// the "point" variable to store the vertex point to send.
SbVec3f point;
#define SEND_VERTEX(x, y, z, s, t)
point.setValue(x, y, z);
if (doTextures)
glTexCoord2f(s, t);
glVertex3fv(point.getValue())

\
\
\
\

if (HAS_PART(curParts, SIDES)) {
//
//
//
//

Draw each side separately, so that normals are
correct. If sendNormals is TRUE, send face normals
with the polygons. Make sure the vertex order obeys
the right-hand rule.

glBegin(GL_TRIANGLES);
// Front face: left front, right front, apex
if (sendNormals)
glNormal3fv(frontNormal.getValue());
SEND_VERTEX(-1.0, -1.0, 1.0, .25, 0.0);
SEND_VERTEX( 1.0, -1.0, 1.0, .50, 0.0);
SEND_VERTEX( 0.0, 1.0, 0.0, .325, 1.0);
// Right face: right front, right rear, apex
if (sendNormals)
glNormal3fv(rightNormal.getValue());
SEND_VERTEX( 1.0, -1.0, 1.0, .50, 0.0);
SEND_VERTEX( 1.0, -1.0, -1.0, .75, 0.0);
SEND_VERTEX( 0.0, 1.0, 0.0, .625, 1.0);
// Rear face: right rear, left rear, apex
if (sendNormals)
glNormal3fv(rearNormal.getValue());
SEND_VERTEX( 1.0, -1.0, -1.0, .75, 0.0);
SEND_VERTEX(-1.0, -1.0, -1.0, 1.0, 0.0);
SEND_VERTEX( 0.0, 1.0, 0.0, .875, 1.0);

101

Appendix A: Creating a Node

// Left face: left rear, left front, apex
if (sendNormals)
glNormal3fv(leftNormal.getValue());
SEND_VERTEX(-1.0, -1.0, -1.0, 0.0, 0.0);
SEND_VERTEX(-1.0, -1.0, 1.0, .25, 0.0);
SEND_VERTEX( 0.0, 1.0, 0.0, .125, 1.0);
glEnd();
}
if (HAS_PART(curParts, BASE)) {
//
//
//
//
//

Send the next material if it varies per part.
Use SoGLLazyElement::sendDiffuseByIndex().
This also sends transparency, so that if
transparency type is not SCREEN_DOOR, there can be
a change of transparency across the shape.

if (materialPerPart){
//Obtain a current copy of the SoGLLazyElement;
//use this for the send.
lazyElt =
(SoGLLazyElement*)SoLazyElement::getInstance(state);
lazyElt->sendDiffuseByIndex(1);
}
if (sendNormals)
glNormal3fv(baseNormal.getValue());
// Base: left rear, right rear, right front, left front
glBegin(GL_QUADS);
SEND_VERTEX(-1.0, -1.0, -1.0, 0.0, 0.0);
SEND_VERTEX( 1.0, -1.0, -1.0, 1.0, 0.0);
SEND_VERTEX( 1.0, -1.0, 1.0, 1.0, 1.0);
SEND_VERTEX(-1.0, -1.0, 1.0, 0.0, 1.0);
glEnd();
}
// Restore the GL matrix.
glPopMatrix();
// Reset the diffuse color, if we sent it twice.
if (materialPerPart)
lazyElt->reset(state, SoLazyElement::DIFFUSE_MASK);

102

Creating a Shape Node

}
//
// Generates triangles representing a pyramid.
//
void
Pyramid::generatePrimitives(SoAction *action)
{
// The pyramid will generate 6 triangles: 1 for each side
// and 2 for the base. (Again, ignore complexity.)
// This variable is used to store each vertex.
SoPrimitiveVertex
pv;
// Access the state from the action.
SoState *state = action->getState();
// See which parts are enabled.
int curParts = (parts.isIgnored() ? ALL :
parts.getValue());
// We need the size to adjust the coordinates.
float halfWidth, halfHeight, halfDepth;
getSize(halfWidth, halfHeight, halfDepth);
// See if we have to use a texture coordinate function,
// rather than generating explicit texture coordinates.
SbBool useTexFunc =
(SoTextureCoordinateElement::getType(state) ==
SoTextureCoordinateElement::FUNCTION);
// If we need to generate texture coordinates with a
// function, we'll need an SoGLTextureCoordinateElement.
// Otherwise, we'll set up the coordinates directly.
const SoTextureCoordinateElement *tce;
SbVec4f texCoord;
if (useTexFunc)
tce = SoTextureCoordinateElement::getInstance(state);
else {
texCoord[2] = 0.0;
texCoord[3] = 1.0;
}
// Determine if there's a material bound per part.
SoMaterialBindingElement::Binding binding =

103

Appendix A: Creating a Node

SoMaterialBindingElement::get(state);
SbBool materialPerPart =
(binding == SoMaterialBindingElement::PER_PART ||
binding ==
SoMaterialBindingElement::PER_PART_INDEXED);
// We use this macro to make the code easier. It uses the
// "point" variable to store the primitive vertex's point.
SbVec3f point;
#define GEN_VERTEX(pv, x, y, z, s, t, normal)
point.setValue(halfWidth * x,
halfHeight * y,
halfDepth * z);
if (useTexFunc)
texCoord = tce->get(point, normal);
else {
texCoord[0] = s;
texCoord[1] = t;
}
pv.setPoint(point);
pv.setNormal(normal);
pv.setTextureCoords(texCoord);
shapeVertex(&pv)

\
\
\
\
\
\
\
\
\
\
\
\
\

if (HAS_PART(curParts, SIDES)) {
//
//
//
//
//

We will generate 4 triangles for the sides of the
pyramid. We can use the beginShape(), shapeVertex(),
andendShape() convenience functions on SoShape to
make the triangle generation easier and clearer.
The shapeVertex() call is built into the macro.

//
//
//
//

Note that there is no detail information for the
Pyramid. If there was, we would create an instance
of the correct subclass of SoDetail (such as
PyramidDetail) and call pv.setDetail(&detail) once.

beginShape(action, TRIANGLES);
// Front face: left front, right front, apex
GEN_VERTEX(pv, -1.0, -1.0, 1.0, .25, 0.0,
frontNormal);
GEN_VERTEX(pv, 1.0, -1.0, 1.0, .50, 0.0,
frontNormal);

104

Creating a Shape Node

GEN_VERTEX(pv,

0.0,

1.0,

0.0, .325, 1.0,
frontNormal);

// Right face: right front, right rear, apex
GEN_VERTEX(pv, 1.0, -1.0, 1.0, .50, 0.0,
rightNormal);
GEN_VERTEX(pv, 1.0, -1.0, -1.0, .75, 0.0,
rightNormal);
GEN_VERTEX(pv, 0.0, 1.0, 0.0, .625, 1.0,
rightNormal);
// Rear face: right rear, left rear, apex
GEN_VERTEX(pv, 1.0, -1.0, -1.0, .75, 0.0,
rearNormal);
GEN_VERTEX(pv, -1.0, -1.0, -1.0, 1.0, 0.0,
rearNormal);
GEN_VERTEX(pv, 0.0, 1.0, 0.0, .875, 1.0,
rearNormal);
// Left face: left rear, left front, apex
GEN_VERTEX(pv, -1.0, -1.0, -1.0, 0.0, 0.0,
leftNormal);
GEN_VERTEX(pv, -1.0, -1.0, 1.0, .25, 0.0,
leftNormal);
GEN_VERTEX(pv, 0.0, 1.0, 0.0, .125, 1.0,
leftNormal);
endShape();
}
if (HAS_PART(curParts, BASE)) {
// Increment the material index in the vertex if
// necessary. (The index is set to 0 by default.)
if (materialPerPart)
pv.setMaterialIndex(1);
// We will generate two triangles for the base, as a
// triangle strip.
beginShape(action, TRIANGLE_STRIP);

105

Appendix A: Creating a Node

// Base: left front, left rear, right front, right rear
GEN_VERTEX(pv, -1.0, -1.0, 1.0, 0.0, 1.0,
baseNormal);
GEN_VERTEX(pv, -1.0, -1.0, -1.0, 0.0, 0.0,
baseNormal);
GEN_VERTEX(pv, 1.0, -1.0, 1.0, 1.0, 1.0,
baseNormal);
GEN_VERTEX(pv, 1.0, -1.0, -1.0, 1.0, 0.0,
baseNormal);
endShape();
}
}
//
// Computes the bounding box and center of a pyramid.
//
void
Pyramid::computeBBox(SoAction *, SbBox3f &box, SbVec3f
¢er)
{
// Figure out what parts are active.
int curParts = (parts.isIgnored() ? ALL :
parts.getValue());
// If no part is active, set the bounding box to be tiny.
if (curParts == 0)
box.setBounds(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
else {
// These points define min and max extents of the box.
SbVec3f min, max;
// Compute the half-width, half-height, and half-depth
// of the pyramid. We'll use this info to set the min
// and max points.
float
halfWidth, halfHeight, halfDepth;
getSize(halfWidth, halfHeight, halfDepth);
min.setValue(-halfWidth, -halfHeight, -halfDepth);

106

Creating a Shape Node

// The maximum point depends on whether the SIDES are
// active. If not, only the base is present.
if (HAS_PART(curParts, SIDES))
max.setValue(halfWidth, halfHeight, halfDepth);
else
max.setValue(halfWidth, -halfHeight, halfDepth);
// Set the box to bound the two extreme points
box.setBounds(min, max);
}
// This defines the "natural center" of the pyramid. We
// could define it to be the center of the base, if we
// want, but let's just make it the center of the
// bounding box.
center.setValue(0.0, 0.0, 0.0);
}
//
// Computes and returns half-width, half-height, and
// half-depth based on current field values.
//
void
Pyramid::getSize(float &halfWidth,
float &halfHeight,
float &halfDepth) const
{
halfWidth = (baseWidth.isIgnored() ?
baseWidth.getValue() /
halfHeight = (height.isIgnored()
?
height.getValue()
/
halfDepth = (baseDepth.isIgnored() ?
baseDepth.getValue() /
}

1.0 :
2.0);
1.0 :
2.0);
1.0 :
2.0);

Tip: The easiest way to make sure your generatePrimitives() method is

working is to use it for rendering, by temporarily commenting out your
shape’s GLRender() method (if it has one).

107

Appendix A: Creating a Node

Creating a Group Node
The example discussed in this section illustrates how to create a group node
subclass. (It is unlikely, however, that you’ll need to create a new group
class.) Our example class, Alternate, traverses every other child (that is,
child 0, then child 2, and so on). Since, like the base SoGroup class, it has no
fields, this example also illustrates how to create a node with no fields.
If you do create a new group class, you probably need to define a new
traversal behavior for it. You may be able to inherit some of the traversal
behavior from the parent class. Most groups define a protected
traverseChildren() method that implements their “typical” traversal
behavior. Your new group can probably inherit the read() and write()
methods from SoGroup.

Child List
SoGroup, and all classes derived from it, store their children in an instance
of SoChildList. This extender class provides useful methods for group
classes, including the traverse() method, which has three forms:
traverse(action) traverses all children in the child list
traverse(action, firstChild, lastChild)
traverses the children from first child to last child, inclusive
traverse(action, childIndex)
traverses one child with the specified index

Hidden Children
If you want your new node to have children, but you don’t want to grant
public access to the child list, you can implement the node to have hidden
children. Node kits are an example of groups within the Inventor library that
have hidden children. Because node kits have a specific internal structure,
access to the children needs to be restricted. If you want the node to have
hidden children, it should not be derived from SoGroup, which has public
children only.

108

Creating a Group Node

SoNode provides a virtual getChildren() method that returns NULL by
default. To implement a new node with hidden children, you need to do the
following:
1.

Maintain an SoChildList for the node. This list can be a hierarchy of
nodes.

2.

Implement a getChildren() method that returns a pointer to the child
list. (SoPath uses getChildren() to maintain paths.)

Using the Path Code
Recall that an action can be applied to a node, a single path, or a path list.
Before a group can traverse its children, it needs to know what the action has
been applied to. The getPathCode() method of SoAction returns an
enumerated value that indicates whether the action is being applied to a
path and, if so, where this group node is in relation to the path or paths. The
values returned by getPathCode() are as follows:
NO_PATH

The action is not applied to a path (that is, the action is
applied to a node).

BELOW_PATH This node is at or below the last node in the path chain.
OFF_PATH

This node is not on the path chain (the node is to the left of
the path; it needs to be traversed if it affects the nodes in the
path).

IN_PATH

The node is in the chain of the path (but is not the last node).

For SoGroup, if the group’s path code is NO_PATH, BELOW_PATH, or
OFF_PATH, it traverses all of its children. (Even if a node is OFF_PATH, you
need to traverse it because it affects the nodes in the path to its right. Note,
though, that if an SoSeparator is OFF_PATH, you do not need to traverse it
because it does not have any effect on the path.) If a node is IN_PATH, you
may not need to traverse all children in the group, since children to the right
of the action path do not affect the nodes in the path. In this case,
getPathCode() returns the indices of the children that need to be traversed.
The traverseChildren() method for SoGroup looks like this:

109

Appendix A: Creating a Node

void
SoGroup::traverseChildren(SoAction *action)
{
int
numIndices;
const
int
*indices;
if (action->getPathCode(numIndices, indices)
== SoAction::IN_PATH)
children.traverse(action, 0, indices[numIndices - 1]);
// Traverse all children up to and including the
// last child to traverse.
else
children.traverse(action);

// traverse all children

}

The GL render, callback, handle event, pick, and search methods for
SoGroup all use traverseChildren(). The write method for SoGroup, which
can be inherited by most subclasses, tests each node in the group before
writing it out. The get matrix method does not use traverseChildren()
because it doesn’t need to traverse as much. If the path code for a group is
NO_PATH or BELOW_PATH, it does not traverse the children. Here is the
code for SoGroup::getMatrix():
void
SoGroup::getMatrix(SoGetMatrixAction *action)
{
int
numIndices;
const int
*indices;
switch (action->getPathCode(numIndices, indices)) {
case SoAction::NO_PATH:
case SoAction::BELOW_PATH:
break;
case SoAction::IN_PATH:
children.traverse(action, 0,
indices[numIndices - 1]);
break;
case SoAction::OFF_PATH:
children.traverse(action);
break;
}
}

110

Creating a Group Node

If a node is IN_PATH, the getMatrix() method traverses all the children in
the group up to and including the last child in the action path (but not the
children to the right of the path). If a node is OFF_PATH, the getMatrix()
method traverses all the children in the group, since they can affect what is
in the path.

What Happens If an Action Is Terminated?
Some actions, such as the GL render, handle event, and search actions, can
terminate prematurely—for example, when the node to search for has been
found. The SoAction class has a flag that indicates whether the action has
terminated. The SoChildList class checks this flag automatically, so this
termination is built into the SoChildList::traverse() methods, and the group
traversal methods do not need to check the flag.
The new Alternate class can inherit the read and write methods from
SoGroup. We just have to define the traversal behavior for the other actions.
Most of the other actions can be handled by the traverseChildren() method.
If your group subclass is derived from SoSeparator, you must write special
rendering methods that correspond to the different path nodes. See “The
GLRender() Method” on page 34.

Alternate Node
The class header for the Alternate node is shown in Example A-5.
Example A-5

Alternate.h

#include 
// SoGroup.h includes SoSubNode.h; no need to include it.
class Alternate : public SoGroup {
SO_NODE_HEADER(Alternate);
public:
// Initializes this class.
static void
initClass();

111

Appendix A: Creating a Node

// Default constructor
Alternate();
// Constructor that takes approximate number of children
// as a hint
Alternate::Alternate(int numChildren);
protected:
// Generic traversal of children for any action.
virtual void doAction(SoAction *action);
// These implement supported actions.
virtual void getBoundingBox(SoGetBoundingBoxAction
*action);
virtual void GLRender(SoGLRenderAction *action);
virtual void handleEvent(SoHandleEventAction *action);
virtual void pick(SoPickAction *action);
virtual void getMatrix(SoGetMatrixAction *action);
virtual void search(SoSearchAction *action);
private:
// Destructor
virtual ~Alternate();
};

The Alternate class source code is shown in Example A-6.
Example A-6
#include
#include
#include
#include
#include
#include
#include
#include

Alternate.c++








"Alternate.h"

SO_NODE_SOURCE(Alternate);
// This initializes the Alternate class.
void
Alternate::initClass()
{

112

Creating a Group Node

// Initialize type id variables
SO_NODE_INIT_CLASS(Alternate, SoGroup, "Group");
}
// Constructor
Alternate::Alternate()
{
SO_NODE_CONSTRUCTOR(Alternate);
}
// Constructor that takes approximate number of children.
Alternate::Alternate(int numChildren) : SoGroup(numChildren)
{
SO_NODE_CONSTRUCTOR(Alternate);
}
// Destructor
Alternate::~Alternate()
{
}
//
//
//
//

Each of these implements an action by calling the standard
traversal method. Note that (as in the Glow node source)
we prefix the call to doAction() with the name of the
class to avoid problems with derived classes.

void
Alternate::getBoundingBox(SoGetBoundingBoxAction *action)
{
Alternate::doAction(action);
}
void
Alternate::GLRender(SoGLRenderAction *action)
{
Alternate::doAction(action);
}
void
Alternate::handleEvent(SoHandleEventAction *action)
{
Alternate::doAction(action);
}

113

Appendix A: Creating a Node

void
Alternate::pick(SoPickAction *action)
{
Alternate::doAction(action);
}
//
//
//
//
//
//
//
//

This implements the traversal for the SoGetMatrixAction,
which is handled a little differently: it does not
traverse below the root node or tail of the path it is
applied to. Therefore, we need to compute the matrix only
if this group is in the middle of the current path chain
or is off the path chain (since the only way this could
be true is if the group is under a group that affects the
chain).

void
Alternate::getMatrix(SoGetMatrixAction *action)
{
int
numIndices;
const int
*indices;
// Use SoAction::getPathCode() to determine where this
// group is in relation to the path being applied to (if
// any).
switch (action->getPathCode(numIndices, indices)) {
case SoAction::NO_PATH:
case SoAction::BELOW_PATH:
// If there's no path, or we're off the end, do nothing.
break;
case SoAction::OFF_PATH:
case SoAction::IN_PATH:
// If we are in the path chain or we affect nodes in the
// path chain, traverse the children.
Alternate::doAction(action);
break;
}
}
//
//
//
//
//

114

This implements the traversal for the SoSearchAction,
which is also a little different. The search action is
set to either traverse all nodes in the graph or just
those that would be traversed during normal traversal. We
need to check that flag before traversing our children.

Creating a Group Node

void
Alternate::search(SoSearchAction *action)
{
// If the action is searching everything, then traverse
// all of our children as SoGroup would.
if (action->isSearchingAll())
SoGroup::search(action);
else {
// First, make sure this node is found if we are
// searching for Alternate (or group) nodes.
SoNode::search(action);
// Traverse the children in our usual way.
Alternate::doAction(action);
}
}
// This implements typical action traversal for an Alternate
// node, skipping every other child.
void
Alternate::doAction(SoAction *action)
{
int
numIndices;
const int
*indices;
// This will be set to the index of the last (rightmost)
// child to traverse.
int
lastChildIndex;
//
//
//
//
if

If this node is in a path, see which of our children
are in paths, and traverse up to and including the
rightmost of these nodes (the last one in the
"indices" array).
(action->getPathCode(numIndices, indices) ==
SoAction::IN_PATH)
lastChildIndex = indices[numIndices - 1];

// Otherwise, consider all of the children.
else
lastChildIndex = getNumChildren() - 1;
// Now we are ready to traverse the children, skipping
// every other one. For the SoGetBoundingBoxAction, we

115

Appendix A: Creating a Node

//
//
//
if

have to do some extra work in between each pair of
children - we have to make sure the center points get
averaged correctly.
(action->isOfType(
SoGetBoundingBoxAction::getClassTypeId())) {
SoGetBoundingBoxAction *bba =
(SoGetBoundingBoxAction *) action;
SbVec3f totalCenter(0.0, 0.0, 0.0);
int
numCenters = 0;
for (int i = 0; i <= lastChildIndex; i += 2) {
children->traverse(bba, i);
// If the traversal set a center point in the action,
// add it to the total and reset for the next child.
if (bba->isCenterSet()) {
totalCenter += bba->getCenter();
numCenters++;
bba->resetCenter();
}
}
//
//
//
if

Now, set the center to be the average. Since the
centers were already transformed, there's no need to
transform the average.
(numCenters != 0)
bba->setCenter(totalCenter / numCenters, FALSE);

}
// For all other actions, just traverse every other child.
else
for (int i = 0; i <= lastChildIndex; i += 2)
children->traverse(action, i);
}

Using New Node Classes
Node classes you have created must be initialized in every application that
uses them. Example A-7 shows how this is done, using the Glow, Pyramid,
and Alternate node classes defined in the previous examples. The program
reads a file (newNodes.iv, shown in Example A-8) that has a scene graph
containing instances of these nodes. It writes the scene graph to standard
output and then opens an examiner viewer to display the graph.

116

Using New Node Classes

You can see from this example that extender node classes should be
initialized after standard classes, which are initialized by SoDB::init(). In
this program, SoDB::init() is called by SoXt::init(). Also, base classes must
be initialized before any classes derived from them, since the initialization
macros for a node class refer to the parent class.
Notice in Example A-8 that the Pyramid and Glow nodes, because they are
not built into the Inventor library, write out their field names and types. (The
Alternate class has no fields.) See the discussion of the file format for new
(unknown) nodes in The Inventor Mentor, Chapter 11.
The isBuiltIn flag is a protected variable in SoFieldContainer, from which
SoNode is derived. If this flag is FALSE, field types are written out along
with the field values. By default, this flag is FALSE, but all Inventor classes
set it to TRUE. If you are building a toolkit that uses Inventor and want your
new classes to appear the same as Inventor classes, be sure to set this flag to
TRUE.
Example A-7
#include
#include
#include
#include
#include
#include

NewNodes.c++








// Header files for new node classes
#include "Glow.h"
#include "Pyramid.h"
#include "Alternate.h"
main(int, char **argv)
{
SoInput
myInput;
SoSeparator *root;
// Initialize Inventor and Xt.
Widget myWindow = SoXt::init(argv[0]);
if (myWindow == NULL) exit(1);
// Initialize the new node classes.
Glow::initClass();
Pyramid::initClass();

117

Appendix A: Creating a Node

Alternate::initClass();
if (! myInput.openFile("newNodes.iv")) {
fprintf(stderr, "Can't open \"newNodes.iv\"\n");
return 1;
}
root = SoDB::readAll(&myInput);
if (root == NULL) {
printf("File \"newNodes.iv\" contains bad data\n");
return 2;
}
root->ref();
// Write the graph to stdout.
SoWriteAction wa;
wa.apply(root);
// Render it.
SoXtExaminerViewer *myViewer =
new SoXtExaminerViewer(myWindow);
myViewer->setSceneGraph(root);
//The following results in high-quality transparency and
//will permit PER_PART material binding to vary
//transparency. Delete it and the pyramid will be
//rendered with the same SCREEN_DOOR transparency
//overall.
myViewer->setTransparencyType
(SoGLRenderAction::SORTED_OBJECT_BLEND);
myViewer->setTitle("NewNodes");
myViewer->show();
myViewer->viewAll();
SoXt::show(myWindow);
SoXt::mainLoop();
return 0;
}

118

Using New Node Classes

Example A-8

newNodes.iv

#Inventor V2.0 ascii
#
# Input file for "newNodes" example program
#
Separator {
MaterialBinding { value PER_PART }
Material {
diffuseColor
[.3 .6 .3, .9 .3 .2]
transparency
[ 0.8 , 0.0]
shininess
.5
}
# Skip every other child.
Alternate {
fields
[]
Pyramid {
fields
}

[]

Cube {}

# This child is skipped

Separator {
MaterialBinding { value OVERALL }
Glow {
fields [ SFColor color , SFFloat brightness ,
SFFloat transparency]
brightness .6
color
.8 .3 .3
transparency .9
}
Transform {
translation 3 .6 0
}
Pyramid {
fields
[SFFloat
height ]
height
3.2
}
}

119

Appendix A: Creating a Node

Sphere {}

# This child is skipped.

}
}

Creating an Abstract Node Class
Creating an abstract node class is slightly different from creating a
nonabstract one. Examples of abstract node classes are SoCamera, SoLight,
and SoShape.
First, abstract classes should use the ABSTRACT versions of the macros
described in SoSubNode.h. For example, the SoLight class makes this call in
its initClass() method:
SO_NODE_INIT_ABSTRACT_CLASS(SoLight, "Light", SoNode);

Second, the constructor for an abstract class should be protected, meaning
that it is impossible to create an instance of it.

The copyContents() Method
The copy() method defined for SoNode creates a copy of an instance of a
node. It invokes the virtual copyContents() method. If your node has no
data other than fields and public children, then the copyContents() methods
defined for SoNode and SoGroup should suffice.
However, if you have extra instance data in your node that needs to be
copied, you have to override the copyContents() method. For example, if the
Pyramid node class defined earlier contained a private integer member
variable called count (for some private reason), the copyContents() method
would look like this:
void
Pyramid::copyContents(const SoFieldContainer *fromFC,
SbBool copyConnections)
{
// Copy the usual stuff by calling the base class method.
SoShape::copyContents(fromFC, copyConnections);

120

The affectsState() Method

// Copy the "count" field explicitly.
count = ((Pyramid *)fromFC)->count;
}

The affectsState() Method
The affectsState() method on SoNode indicates whether a node has a net
effect on the state. (For example, SoSeparator changes the state, but it
restores the state, so there’s no net effect.) The default value for this method
is TRUE, but some node classes such as SoSeparator, SoShape, SoArray, and
SoMultipleCopy define it to be FALSE. When you define a new node class,
you may need to redefine its affectsState() method if it differs from that of
the parent class.

Uncacheable Nodes
You may create a new node whose effects should not be cached during
rendering or bounding-box computation. For example, the SoCallback node
allows a user to change the effect of the callback function, such as drawing a
cube instead of a sphere, without ever making an Inventor call.
Uncacheable nodes such as SoCallback should call:
SoCacheElement::invalidate(state)

which aborts construction of the current cache. This call can be made during
the render or bounding box action (the two actions that support caching).
The invalidate() method also turns off auto-caching on any SoSeparator
nodes over the uncacheable node.

121

Appendix A: Creating a Node

Creating an Alternate Representation
When you create a new node, you probably also want to create an alternate
representation for it that can be written to a file. For example, a good
alternate representation for the Glow node would be an SoMaterial node
with all fields ignored except for emissiveColor and transparency. The
alternate representation is in the form of a field called alternateRep, of type
SoSFNode. If your node is later read into an Inventor application that is not
linked with this new node, Inventor can render the node using this alternate
representation even though the node has not been initialized with the
database. (See Chapter 11 in The Inventor Mentor on reading in extender
nodes and engines.)
Within your program, when a change is made to the original node, you may
want the alternate representation to change as well. In this case, override the
write() method on SoNode to update the alternate representation, and then
have it call the write() method of the base class.

Generating Default Normals
If you define your own vertex-based shape class and the parent class does
not generate default normals, you need to generate default normals for
rendering with the Phong lighting model and for generating primitives.
SoVertexShape provides the generateDefaultNormals() method. Although
the specifics depend on the shape itself, SoNormalBundle provides
methods to facilitate this process.
Tip: If you define a node class that creates a node sensor attached to itself or

a field sensor attached to one of its fields, you need to redefine
readInstance() so that the sensor doesn’t fire when the node is read from a
file. Your readInstance() method needs to detach the sensor, call the
readInstance() method of the parent class, and then reattach the sensor.
Node kits provide the setUpConnections() method to make and break these
connections (see Chapter 7 in The Inventor Toolmaker).

122

Index

Numbers

B

3-vertex polygons, 66
4-vertex polygons, 66

beginShape(), 89
benchmark programs, 48
binding elements, 37
bindings
DEFAULT, 12
NONE, 12
bitmasks, 44
blended transparency, 63
blending, 43
bottlenecks, 49
culling, 66
level of detail, 67
bounding box, 93

A
abstract node classes, 120
actions
applying, 109
implementing, 78
optimizing, 70
See Also doAction()
terminating, 111
affectsState(), 121
Alternate group class, 111
Alternate group node, 108, 116
alternate representation, 122
AMBIENT_MASK, 44
analyzing rendering performance, 53
applications
optimizing, 47-72
porting, 1
applying actions, 109
automatic normal generation, 12
automatic texture coordinate generation, 12

C
caching, 81
dependencies, 39
invalidation, 121
normal bindings, 69
optimizing, 55
PER_FACE materials, 69
See also uncacheable nodes
turning off render caching, 69

123

Index

callbacks
finish, 68
generating primitives, 88
start, 68
camera control, 54
CaseVision/Workshop Performance Analyzer, 66
child list, 108
children
hidden, 108
traversing, 109-111
classes
problems caused by changes, 5
clearing windows, 55
clipped objects, 8
CLOCKWISE vertex ordering, 40
colors
changes, 10
diffuse, 46, 69
packed, 46
complexity, 9
changes, 9
SCREEN_SPACE, 57
computeBBox(), 93
computeCoordBBox(), 93
constructor
abstract classes, 120
constructors
for actions, 70
nodes, 76
copy(), 36, 120
copyContents(), 36
C pre-processor symbols, 22
creaseAngle default, 8
createLineDetail(), 91

124

createPointDetail(), 91
createTriangleDetail(), 91
creating details, 90
creating group classes, 108-116
creating nodes, 73-107
creating shape classes, 88-??
culling, 61, 63
bottlenecks, 66
current element, 43
custom classes, 33
for performance improvement, 66
optimizing, 54
porting, 3
cvspeed, 66

D
debugging library, 75
DEFAULT binding, 12, 13
default normal, 12
default texture coordinate, 12
details
creating, 90
DIFFUSE_MASK, 44
diffuse colors, 46, 69
DirectionalLight, 61
disabling notification, 56
-DIV_STRICT, 2, 12
doAction(), 78, 82
double-buffering, 51
downgrading files, 30
DSO, 22, 54

Index

E
element bundles, 81
elements
accessing, 80
enabling, 75
setting, 80
EMISSIVE_MASK, 44
enableNotify(), 72
enabling elements, 75
endShape(), 89
engines, 46
enumerated values, 77
event handling performance, 72
examples
group class, 111-116
property class, 83-87
shape class, 94-107
extender API changes, 33
extensions
texture object, 29

F
face set performance, 58
fields, 77
field types
writing, 117
file format
unknown nodes, 117
file header methods, 23
files
downgrading, 30
reading, 23
writing, 23
file version, 21
finish callback, 68

flags
isBuiltIn, 117
set_version, 22
flat-shaded polygons, 63
floating point precision, 23
fog, 62
frame rate, 51
performance goal, 48

G
generatePrimitives(), 88, 89, 91
generating normals, 12, 40
generating primitives, 88
get(), 80, 81
getBoundingBox(), 88, 93
getChildren(), 109
getInstance(), 80
getNum(), 81
getPathCode(), 109
GL_BLENDING, 43
GL_POLYGON_STIPPLE, 43
glColor(), 44
glColorMaterial(), 42
glMaterial, 44
Glow property class, 82, 83
Glow property node, 116
GLRender(), 34, 88, 90
GLRenderBelowPath(), 34
GLRenderInPath(), 34
GLRenderOffPath(), 34
glShadeModel(GL_FLAT), 43
Gouraud-shaded polygons, 63
gr_osview, 49
grabEvents(), 72

125

Index

group classes
creating, 108-116
example, 111-116
groups
hidden children, 108
path code, 109-111

ivfix, 31
ivperf, 31, 53
and culling, 66
and OpenGL, 64
camera control, 54
ivview -p, 48

H

K

hardware-dependent performance, 51
hardware texture mapping, 9
hidden children, 108

keeping records, 51

L
I
implementing actions, 78
inheritance
element stack, 76
initClass(), 75, 76
initClass() method
nodes, 120
initialization
node classes, 116
initializing nodes, 75
Internet, 26
invalidate(), 121
invokeLineSegmentCallbacks(), 89
invokePointCallbacks(), 89
invokeTriangleCallbacks(), 89
isBuiltIn flag, 117
isEngineModifying(), 46
isolating rendering, 53
ivAddVP, 30
ivdowngrade, 30

126

LD_LIBRARY_PATH, 1
level of detail bottlenecks, 67
lights
optimizing, 62
line segment callback function, 89
line segments, 88
LOD node, 26
longToInt32, 18

M
macros
abstract versions, 120
nodes, 74
manipulators, 29
masks, 44
material binding, 62
materialIndex field, 57
materials
changes, 10
material state elements, 38
measuring performance, 49

Index

memory
optimizing, 69
multiple colors, 43
multiple materials, 10

N
node classes
abstract, 120
extender
using, 116-120
initializing, 116, 117
node kits
hidden children, 108
node override, 38, 39
nodes
constructor, 76
creating, 73-107
initializing, 75
macros, 74
uncacheable, 121
NONE binding, 12
normals
changes, 12
default, 12
generating, 40
requirement for SoNormalBinding, 12
notification
disabling, 56
notification overhead, 71
numVertices field, 13

O
OpenGL, 76, 90
performance, 64
texture object extension, 29

OpenGL benchmark programs, 48
optimizing actions, 70
optimizing applications, 47
optimizing caching, 55
optimizing custom classes, 54
optimizing event handling, 72
optimizing face sets, 58
optimizing level of detail, 67
optimizing lights, 61
optimizing memory usage, 69
optimizing picking, 72
optimizing pixel fill, 63
optimizing rendering, 52
optimizing scene graphs, 31
optimizing textures, 59
optimizing transformations, 58
optimizing vertex transformations, 61
optimizing window clearing, 55
osview
swapbuf number, 49
Override flag, 16
override status
and caching, 57
overriding nodes, 38, 39

P
packed color, 46
path code, 109-111
PER_FACE materials, 69
performance, 47
hardware dependency, 51
hardware limitations, 48
interrupting rendering, 68
keeping records, 51
lights, 61

127

Index

measuring, 49
rendering test, 52
SoTransform, 58
performance goal, 48
picked point, 91
picking, 91
performance, 72
pixel filling
performance, 63
pixie, 66
point callback function, 89
PointLight, 61
points, 88
pop(), 43
porting, 1
custom classes, 3
pre-processor symbols, 22
primitives
generating, 88
performance, 62
primitive shapes, 8
prof, 66
property classes
creating, 82-87
example, 83-84
prototyping, 88
push(), 43
Pyramid shape node, 94, 116

R
rayPick(), 88, 91
read(), 108
reading files, 23
readInstance(), 35
reading children, 35

128

record-keeping, 51
render caching, 69
renderCaching ON, 60
render culling, 61, 63, 66
rendering, 90
analyzing performance, 53
experiments, 53
interrupt for performance, 68
isolating rendering, 53
optimizing rendering, 52
rendering methods, 34
rendering performance
analyzing with ivperf, 31
reset(), 44
revision symbols, 22

S
scene graphs
optimizing, 31
scene traversal, 55
SCREEN_DOOR transparency, 63
SCREEN_SPACE complexity, 57
sendAllMaterial(), 42
sendDiffuseByIndex(), 43
sending multiple colors, 43
sendNoMaterial(), 42
sendOnlyDiffuseColor(), 42
set(), 80
set_version flag, 22
setDetail(), 90
setDrawStyle(), 14
setFloatPrecision(), 23
setTransparencyType(), 63
shade model, 43
shademodel(FLAT), 63

Index

shape classes, 40
creating, 88-107
example, 94-107
SoMaterialBundle, 44
shape hints
changes, 8
element methods, 37
shapes
sending multiple colors, 43
vertex-based, 2
shapeVertex(), 89
shininess, 57
SHININESS_MASK, 44
shouldGLRender()
SoShape method, 90
shouldRayPick()
SoShape method, 91
SO_ENABLE(), 75
SO_ENGINE_OUTPUT, 46
SO_NODE_ADD_FIELD(), 77
SO_NODE_CONSTRUCTOR(), 76
SO_NODE_DEFINE_ENUM_VALUE(), 77
SO_NODE_HEADER(), 74
SO_NODE_INIT_CLASS(), 34, 75
SO_NODE_IS_FIRST_INSTANCE(), 76
SO_NODE_SET_SF_ENUM_TYPE(), 77
SO_NODE_SOURCE(), 74
SO_SWITCH_NODE, 52
SO_VERSION, 22
SO_VERSION_REVISION, 22
SoAction.h, 75
SoAsciiText, 26
SoBoundingBoxAction::apply(), 66
SoCallback, 121
SoCallbackAction, 88
SoChildList, 108

SoClipPlane
clipped objects, 8
wireframe objects, 8
SoColorPacker, 46
SoComplexity, 61
and caching, 57
complexity, 9
no hardware texture mapping, 9
textureQuality field, 9
SoCone
primitive shapes, 8
SoCoordinate, 64
SoCube
primitive shapes, 8
SoCylinder
primitive shapes, 8
SoDB
file header methods, 23
SoDetail, 89
SoDrawStyle
clipped objects, 8
wireframe objects, 8
SoElement, 39
override, 38
SoFaceSet, 64
3-vertex polygons, 66
4-vertex polygons, 66
optimizing, 62
performance, 58
SoFieldContainer, 117
SoFieldContainer enableNotify(), 72
SoFontStyle, 26
SoGLLazyElement, 38
SoGLLazyElement::reset(), 41
SoGLLazyElement::sendAllMaterial(), 42
SoGLLazyElement::sendDiffuseByIndex, 43
SoGLRenderAction::addMethod(), 34

129

Index

SoGLRenderAction::setAbortCallback(), 68
SoGLTextureQualityElement, 37
SoGroup, 108
implementation of copying, 36
SoIndexedFaceSet
3-vertex polygons, 66
4-vertex polygons, 66
performance, 58
SoIndexedShape, 93
SoLabel
caching, 56
SoLazyElement, 38
masks, 44
SoLevelOfDetail, 26
caching, 56
SoLightModel
BASE_COLOR, 62
SoLocateHighlight, 26
SoLOD, 26, 61, 62
caching, 56
optimizing, 67
SoMaterial
multiple materials, 10
override flag, 16
SoMaterialBinding
DEFAULT, 37
NONE, 37
override flag, 16
SoMaterialBundle, 81
new shape nodes, 44
SoNode::copy(), 36
SoNode::enableNotify(), 56
SoNode parameter, 39
SoNonIndexedShape, 13, 93
USE_REST_OF_VERTICES, 13
SoNormal
automatic normal generation, 12

130

default normal, 12
normal requirement, 12
SoNormalBinding
automatic normal generation, 12
DEFAULT binding, 12
NONE binding, 12
normal requirement, 12
SoNormalBundle, 81
SoNormalGenerator class constructor, 40
SoOutput
file header, 23
isASCIIHeader(), 16
isBinaryHeader(), 16
setFloatPrecision(), 23
SoOverrideElement, 39, 46
SoPickedPoint, 91
SoPickStyle
caching, 56
SoPrimitiveVertex, 89
SoReplacedElement
SoNode parameter, 39
SoSeparator
caching, 56
renderCaching ON, 60
SoSeparator::cullTest(), 66
SoShape, 88
SoShape::beginSolidShape(), 42
SoShape::endSolidShape(), 42
SoShapeHints
clipped objects, 8
creaseAngle default, 8
primitive shapes, 8
shapeType, 62
vertexOrdering, 62
wireframe objects, 8
SoShapeHintsElement
method changes, 37

Index

SoSphere
primitive shapes, 8
SoSubNode.h, 74
SoSwitch
whichChild field, 52
SoText2
and caching, 57
SoTexture2
no hardware texture mapping, 9
textureQuality field, 9
SoTextureBlendColorElement, 38
SoTextureCoordinate2
automatic coordinate generations, 12
default texture coordinate, 12
texture coordinate requirement, 13
SoTextureCoordinateBinding
DEFAULT binding, 13
SoTextureCoordinateBundle, 81
SoTextureModelElement, 38
SoTextureQualityElement, 37
SoTextureWrapSElement, 38
SoTextureWrapTElement, 38
SoTransform
performance, 58
SoTransformerManip, 29
SoTriangleStripSet
numVertices field, 13
SoType::overrideType(), 34
SoVertexProperty, 24
SoVertexShape
complexity, 9
SoWWWAnchor, 26
SoWWWInline, 26
SoXtViewer
texture mapping, 14
viewers, 14
SPECULAR_MASK, 44

sphere
rayPick(), 92-93
SpotLight, 61
start callback, 68
startNotify(), 71
state elements, 81
stipple transparency, 43
swapbuf in osview, 49

T
terminating actions, 111
texture coordinates
automatic generation, 12
changes, 12
default, 12
requirement, 13
texture mapping
and viewers, 14
texture object extension, 29
texture quality element, 37
textureQuality field, 9
textures
optimizing, 59
The Inventor Mentor, 90, 122
transformations
optimizing, 58
transparency, 43
and diffuse colors, 46
blended, 63
SCREEN_DOOR, 63
traversal, 55
traversal state
enabling elements, 75
traverse(), 108
traverseChildren(), 108

131

Index

triangle callback function, 88
triangle fans, 89
triangles, 88
triangle strips, 89

U
uncacheable nodes, 121
unknown nodes
file format, 117
USE_REST_OF_VERTICES, 13
user-defined file headers, 23

V
version number, 22
vertex-based shapes
changes, 13
vertex property field, 2
vertex ordering
CLOCKWISE, 40
vertex properties
converting files, 30
vertexProperty field, 2, 30
vertex property node, 24
vertex transformations
performance, 61
viewAll(), 54
viewers
and texture mapping, 14
Virtual Reality Modeling Language, 26
VRML
nodes, 26

132

W
wireframe, 63
wireframe objects, 8
write(), 108
writing files, 23

Tell Us About This Manual
As a user of Silicon Graphics products, you can help us to better understand your needs
and to improve the quality of our documentation.
Any information that you provide will be useful. Here is a list of suggested topics:
•

General impression of the document

•

Omission of material that you expected to find

•

Technical errors

•

Relevance of the material to the job you had to do

•

Quality of the printing and binding

Please send the title and part number of the document with your comments. The part
number for this document is 007-3078-001.
Thank you!

Three Ways to Reach Us
•

To send your comments by electronic mail, use either of these addresses:
–

On the Internet: techpubs@sgi.com

–

For UUCP mail (through any backbone site): [your_site]!sgi!techpubs

•

To fax your comments (or annotated copies of manual pages), use this
fax number: 650-932-0801

•

To send your comments by traditional mail, use this address:
Technical Publications
Silicon Graphics, Inc.
2011 North Shoreline Boulevard, M/S 535
Mountain View, California 94043-1389



Source Exif Data:
File Type                       : PDF
File Type Extension             : pdf
MIME Type                       : application/pdf
PDF Version                     : 1.2
Linearized                      : Yes
Create Date                     : 2001:05:16 19:27:59
Producer                        : Acrobat Distiller 4.0 for Windows
Modify Date                     : 2001:05:16 19:28:00-07:00
Page Count                      : 144
EXIF Metadata provided by EXIF.tools

Navigation menu