2.5. 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 node 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.

For this class, we need to implement actions that deal with materials: GLRender() and callback(). We will use doAction() (see the section called “The doAction() Method”), since it performs the same operation for both actions. The doAction() method for the Glow class updates the emissive color element based on the values of the color and brightness fields of the node.

The class header for our new node is shown in Example 2-1.

Example 2.1.  Glow.h

#include <Inventor/SbColor.h>
#include <Inventor/fields/SoSFColor.h>
#include <Inventor/fields/SoSFFloat.h>
#include <Inventor/nodes/SoSubNode.h>
class Glow : public SoNode {
   SO_NODE_HEADER(Glow);
 public:
   // Fields:
   SoSFColor      color;       // Color of glow
   SoSFFloat      brightness;  // Amount of glow (0-1)
   // 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 that
   // deal 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, rather than using the reference count mechanism.
   virtual ~Glow();
   // Holds emissive color. A pointer to this is stored in the
   // state.
   SbColor         emissiveColor;
};


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 use of the element in the example is also typical; most elements have simple set() methods to store values.

The source code for the Glow class is shown in Example 2-2.

Example 2.2.  Glow.c++

#include <Inventor/actions/SoCallbackAction.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/bundles/SoMaterialBundle.h>
#include <Inventor/elements/SoEmissiveColorElement.h>
#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. The 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");
}
// Constructor
Glow::Glow()
{
   // Do standard constructor tasks
   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));
}
// Destructor
Glow::~Glow()
{
}
// Implements GL render action.
void
Glow::GLRender(SoGLRenderAction *action)
{
   // 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);
   // For efficiency, Inventor nodes make sure that the first
   // defined material is always in GL, so shapes do not have to
   // send the first material each time. (This keeps caches from
   // being dependent on material values in many cases.) The
   // SoMaterialBundle class allows us to do this easily.
   SoMaterialBundle  mb(action);
   mb.forceSend(0);
}
// 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 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.
   if (! brightness.isIgnored()) {
     // Define the emissive color as the product of the
     // "brightness" and "color" fields. "emissiveColor" is an
     // instance variable. Since material elements contain
     // pointers to the actual values, we need to store the
     // value in the instance. (We could have defined the
     // fields to contain multiple values, in which case we
     // would have to store an array of emissive colors.)
     emissiveColor = color.getValue() * brightness.getValue();

     // Set the value of the emissive color element to our one
     // new emissive color. "this" is passed in to let the
     // caching mechanism know who set this element and to
     // handle overriding. (Note that this call will have no
     // effect if another node with a TRUE Override flag set
     // the element previously.)
     SoEmissiveColorElement::set(action->getState(), this,
                                 1, &emissiveColor);
   }
}