Computer Graphics Workshop '97 Lecture Notes

1/8/97

Today's topics
Organization of Open Inventor

Most of the programmatic interaction that goes on with Open Inventor involves nodes in the scene graph. These nodes often contain data elements known as fields, which encapsulate information Inventor uses to perform certain actions, such as rendering the scene.

In this lecture we will explore some of the reasons behind this organization to help you get a better feel for what the toolkit is capable of. We will also describe some of the other data types commonly used in Open Inventor, and how to use these data types from within Scheme.

Nodes

A node is an object (in the sense that the characters in the 6.001 adventure game were objects) which stores some piece of information. Each node has an associated data type, or class, which defines the operations, or methods, which can be performed on that node and the data which that node can store.

All nodes are derived from the base type SoBase. This means they are specializations of this data type; a node can do everything an object of type SoBase could do, and more.

Reference counting

This base type provides many useful facilities, the most important of which (for now) is reference counting. (Note that Inventor's reference counting mechanism for nodes is different from any internal mechanism used for garbage collection in the Scheme interpreter.) Each node stores internally the number of times another node points to it. This allows Inventor to know when a node is no longer being used and that it should be deleted. This is a necessary facility, because while the Inventor programmer is allowed to allocate memory for new nodes, he/she is not allowed to deallocate this memory explicitly. All deallocation is handled by Inventor. This prevents the scene database, SoDB (which keeps track of all nodes in the program), from ever attempting to use an invalid node.

When a node is first created, its reference count is zero. The following manipulations increment a node's reference count by 1:

  • Making this node a child of another one
  • Initiating an action upon this node (example: beginning a render of the scene)
  • Manually incrementing its reference count (example: "(-> my-node 'ref)")

The following manipulations decrement a node's reference count by 1:

  • Removing this node from its parent node
  • Deleting this node's parent node (or one of its parents, if it's shared)
  • Completing an action initiated upon this node (example: finishing of a scene render)
  • Manually decrementing its reference count (example: "(-> my-node 'unref)")

A node is deleted when its reference count is decremented to zero. This is why a newly created node does not immediately "disappear".

Most of the time, you will not have to worry about reference counts. Just make sure you ref your root node, and ref any node you want to keep around for a while. For example, let's say we define a new Ship data type in Scheme, which has an internal scene graph looking like this:

                         ship-root
                             |
                -----------------------------
                |            |              |
           Translation  RotationXYZ  (ship-geometry...)
The translation and rotation nodes allow us to position each ship independently in the world; when we create a new ship, we add its scene graph to the global one. If we want to make several ships, we can re-use the same ship-geometry subgraph for each one. However, when a ship is destroyed, ship-root will be removed from the global scene graph, and all of the nodes below it will be unref'ed. Unless we explicitly ref'ed the root node of the ship-geometry subgraph, it would be deleted when the last ship was removed from the scene graph. (Note that this might be okay, depending on the design of your application; perhaps there is always at least one ship present.)

Warning: If you attempt to use a node which has been deleted, the Scheme interpreter will probably crash. Such severe errors are usually due to nodes being silently deleted by Inventor when you expect them to remain around.

Fields

Nodes usually contain data elements within fields. Each field within a node is also a complex object, rather than a basic data type; for example, if a node needed to contain a single floating point value, it would have an SoSFFloat field data member rather than just a float, as in a C data structure.

There are three main reasons for doing this:

Let's look at a demonstration illustrating the second point. This code disables the automatic redraw mechanism (which unfortunately breaks the 3D manipulation functions) and replaces it with a nearly-equivalent one which prints a line every time the scene is re-rendered. When the material's diffuse color is changed, the scene is automatically redrawn.

;; Example to illustrate when rendering occurs. ;; Set up scene graph (define root (new-SoSeparator)) (-> root 'ref) (define mat (new-SoMaterial)) (-> (-> mat 'diffuseColor) 'setValue 0.8 0.2 0.2) (-> root 'addChild mat) (-> root 'addChild (new-SoCone)) (define v (examiner root)) ;; examiner defined in CGWInit.scm ;; (see notes from lecture 1) ;; Render callback (define (render-cb user-data sensor) (display "Performing a render\n") (-> v 'render)) ;; Create callback object for node sensor (define info (new-SchemeSoCBInfo)) (-> (-> info 'callbackName) 'setValue "render-cb") ;; Instantiate node sensor and attach to root (define render-sensor (new-SoNodeSensor (get-scheme-sensor-cb) (void-cast info))) (-> v 'setAutoRedraw 0) (-> render-sensor 'setPriority (- (SoXtRenderArea::getDefaultRedrawPriority) 1)) (-> render-sensor 'attach root) ;; Couple of color changes (-> (-> mat 'diffuseColor) 'setValue 0.2 0.8 0.2) ;; Green (-> (-> mat 'diffuseColor) 'setValue 0.2 0.2 0.8) ;; Blue (-> (-> mat 'diffuseColor) 'setValue 0.8 0.2 0.2) ;; Red ;; Code to detach/attach the render sensor (-> render-sensor 'detach) (-> render-sensor 'attach root)

There are two primary types of fields; single-valued and multiple-valued. A multiple-valued field, as the name implies, can store multiple values of a certain data type. Single-valued fields start with the prefix "SoSF"; multiple-values fields have the prefix "SoMF". For example, a field which could contain multiple floating point values is called an SoMFFloat. One which holds multiple (x,y,z) floating-point triplets is called an SoMFVec3f.

We will see an example of field-to-field connections and the use of engines in the second problem set, although we won't officially discuss engines until later.

Warning: you should never create a field which is not connected to a node (for example, by calling new-SoMFVec3f). If you want to store data outside of a node (like a list of coordinates), you should create multiple SbVec3fs (see below) and store them in a Scheme list or vector.

Basic data types and naming conventions

Upon first inspection, the names of Inventor classes seem strange: they all have either an "So" or "Sb" prefix. The difference between them is important. "So" stands for scene object, and data types with this prefix are usually related to the scene graph; for example, all nodes have an "So" prefix. "Sb" stands for scene basic, and refers to basic data types such as vectors, matrices, and colors.

Sb data types usually have setValue and getValue methods. However, they do not have the notification mechanism (see the description of fields, above) which fields have. This means that when the setValue method is called on a basic data type, there is never any automatic propagation of that value anywhere else in the scene graph; that is, Sb data types are "simple" data types, while fields are containers for these data types. Nodes are field containers.

Some of the more important Sb types are as follows:

How to read the man pages

The manual pages for Open Inventor are written in C++. However, it is easy to convert the information in them into Scheme syntax.

To start reading the manual pages, it is probably most convenient to start xman, a graphical utility for reading the manual. Type xman & at an Athena prompt to start it. If you are not using the standard Athena SGI setup, you will need to execute the following line in an xterm before typing xman:

setenv MANPATH $MANPATH\:/usr/share/catman:/usr/catman

The manual pages are under the (3) Subroutines (Inventor) section.

Let's look at the (abbreviated) man page for SbVec3f as an example. This basic data type encapsulates a vector of three floating point values.

NAME
SbVec3f - 3D vector class
INHERITS FROM
SbVec3f
SYNOPSIS
Methods from class SbVec3f:

SbVec3f()
SbVec3f(const float v[3])
SbVec3f(float x, float y, float z)
const float *getValue()
SbVec3f &setValue(const float v[3])
SbVec3f &setValue(float x, float y, float z)

The "synopsis" section is what we are concerned with. The first three lines of that section indicate that there are three ways of creating a new SbVec3f: one with no arguments, one with a Scheme vector of three floating point values, and one with three separate floating point values:

(define my-vec (new-SbVec3f))
(define my-vec (new-SbVec3f '#(1.0 2.0 3.0)))
(define my-vec (new-SbVec3f 1.0 2.0 3.0))

The next line indicates that the "right way" (there is another, but we're ignoring it for now) to get the values stored in an SbVec3f is by calling its getValue method. This method returns a Scheme vector of three floats, which can be deconstructed using the standard Scheme vector-ref function.

> (define my-vec (new-SbVec3f 1.0 2.0 3.0))
#<unspecified>
> (define my-scheme-vector (-> my-vec 'getValue))
#<unspecified>
> (vector-ref my-scheme-vector 0)
1.0
The next two lines show how to change the value stored within a previously-constructed SbVec3f. The first takes a Scheme vector of three floating point values as an argument; the second takes three floating point values as arguments.
> (define my-vec (new-SbVec3f 0 0 0))
#<unspecified>
> (-> my-vec 'getValue)
#(0.0 0.0 0.0)
> (-> my-vec 'setValue '#(1 2 3))
#<SbVec3f: 101b13f0>
> (-> my-vec 'getValue)
#(1.0 2.0 3.0)
> (-> my-vec 'setValue 4 5 6)
#<SbVec3f: 101b13f0>
> (-> my-vec 'getValue)
#(4.0 5.0 6.0)
As another example, let's try accessing a field of a node, SoTransform. You did this in the first problem set; let's see how it works.

NAME
SoTransform - general 3D geometric transformation node
INHERITS FROM
SoBase > SoFieldContainer > SoNode > SoTransformation > SoTransform
SYNOPSIS
Fields from class SoTransform:

SoSFVec3f translation
SoSFRotation rotation

Methods from class SoTransform:

SoTransform()

Let's define our new transform and try to access the translation field:

> (define my-transform (new-SoTransform))
#<unspecified>
> (-> my-transform 'translation)
#<SoSFVec3f: 101a4918>
So clearly this syntax returns the field contained within our transformation node. But how do we modify it? Let's look at the man page for SoSFVec3f:

NAME
SoSFVec3f - field containing a three-dimensional vector
INHERITS FROM
SoField > SoSField > SoSFVec3f
SYNOPSIS
Methods from class SoSFVec3f:

void setValue(float x, float y, float z)

If we wanted to modify the contents of this node's translation field, therefore, we have to call the setValue method of this field:

> (define my-field (-> my-transform 'translation))
#<unspecified>
> (-> my-field 'setValue 1.0 2.0 3.0)
#<unspecified>
Or, leaving out the intermediate definition of my-field, we arrive at the same syntax we used for problem set 1:
> (-> (-> my-transform 'translation) 'setValue 1.0 2.0 3.0)
#<unspecified>
The basic rule for arguments to methods in Scheme is that the data types must match. For example, in the following method of SoGroup:

void addChild(SoNode *child)

We have already used this method extensively. The argument must be of type SoNode (that is, some specialization of the class, or subclass of, SoNode). You can ignore the "*" (which indicates a pointer to an object), because in Scheme all C pointers are handled by the interpreter.

Special cases for data types are as follows:

More information about the Scheme interface to Inventor is located in the file

/mit/iap-cgw/Ivy.doc

You should now be able to understand the general format of the manual pages for Open Inventor and be able to understand how to access at least the basic methods and fields from Scheme. The second problem set will involve using some of the unused fields from the first problem set to further modify your scene graph.

Some good Inventor Mentor examples to look at are 02.3.Trackball, 03.1.Molecule, 03.2.Robot, and 02.2.EngineSpin.

Next lecture

Next time we will discuss how to read and write Open Inventor scene graphs from the Open Inventor file format, more ways to organize your scene graphs, and begin to discuss how to create polygon-based shapes.

Back to the CGW '97 home page

$Id: index.html,v 1.16 1997/01/08 19:29:24 kbrussel Exp $