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.
The class header for our new node is shown in Example 2-1.
#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();
static void exitClass();
// Constructor
Glow();
private:
// 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;
};
#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" );
}
void
Glow::exitClass()
{
SO__NODE_EXIT_CLASS( Glow );
}
// 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.)
SoLazyElement::setEmissive( action->getState(), emissiveColor );
}
}