Computer Graphics Workshop '96 Lecture Notes

1/10/96

Today's topics
Lessons from the last problem set

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 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. 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 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
  • 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". However, if you forgot to ref your root node in the scene graph example from the first lecture, the scene would not appear. This happens because the rendering process applies an action to the scene graph; the root node's reference count would be incremented to 1 at the start of the rendering process, and then decremented to zero at the end. This would cause the root node to be deleted, and therefore all of its children (because their reference counts would be decremented to zero, as well).

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.

Fields

Nodes may contain data elements within fields. Each field within a node is also a class, 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 two main reasons for doing this:

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.

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.

Some of the more important Sb types are as follows:

Most fields are containers for scene basic data types. This means that there are consistent interfaces to the elements stored within a field; for example, most Sb data types support the setValue and getValue methods for whatever data type they store.

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 creates a pointer to this object), because in Scheme all 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/thingworld/Ivy/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.

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 '96 home page

$Id: index.html,v 1.10 1996/01/10 18:12:10 kbrussel Exp $