Chapter 6. Creating an Engine

This chapter describes how to create new subclasses of SoEngine. New concepts introduced in this chapter include how notification of changes in field data propagates through the scene graph and how to block notification if desired. Be sure you are familiar with Chapter 13 in The Inventor Mentor before you continue in this chapter.

The first part of this chapter offers an overview of the steps required to create a new engine. When necessary, additional sections explain key concepts in further detail and list the relevant macros. Chapter examples show how to create six engine classes:

Overview

As described in The Inventor Mentor, all engines have at least one input and one output. The inputs are derived from SoField (either SoSF or SoMF fields). Outputs are of type SoEngineOutput. In addition, each engine has an evaluate() method, which uses the current input values and produces new output values. The file SoSubEngine.h contains the macros for defining new engine classes. The SO_ENGINE_HEADER() macro declares type identifier and naming variables and methods that all engine classes must support. The macro defines the static variables and methods declared in the SO_ENGINE_HEADER() macro. Other macros useful in creating new engine classes are mentioned in the following sections.

Creating a new engine requires these steps:

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

  2. Define and name each input and output of the engine (see “Defining Inputs and Outputs”).

  3. Define an initClass() method to initialize the type information (see “Initializing the Engine Class”).

  4. Define a constructor (see “Defining the Constructor”).

  5. Define a destructor.

  6. Implement an evaluate() method for the engine class (see “Notification and Evaluation”).

  7. If necessary, implement an inputChanged() method (see “Creating a Fan-in Engine” and “Creating a Second-Ticker Engine”).

  8. Implement a copy() method if the engine contains any nonfield instance data (see Chapter 2, “Creating a Node,” for more information on implementing a copy() method).

Defining Inputs and Outputs

Declare the inputs and outputs for the engine in the header file. For example:

SoSFTrigger on;
SoSFTrigger off;
SoSFTrigger toggle;
SoEngineOutput isOn;  // (SoSFBool)
SoEngineOutput isOff; // (SoSFBool)

Note that, by convention, each output is commented with the field type for that output. The output type isn't formally specified in the header file, though. It's set up in the constructor in the source file.

Also be sure to include the files for the field classes used by the inputs of your new engine.

Initializing the Engine Class

Implement an initClass() method, as described in Chapter 1, “Runtime Types”. The initClass() method sets up the type identifier and file format name information for the class. This method must be called before the engine can be used in an application. Use the SO_ENGINE_INIT_CLASS() macro within the initClass() method. For example:

void
FanIn::initClass()
{
   SO_ENGINE_INIT_CLASS(FanIn, SoEngine, "Engine");
}

Defining the Constructor

Implement the constructor for the class. Begin with the macro SO_ENGINE_CONSTRUCTOR(). Then use the macros SO_ENGINE_ADD_INPUT() and SO_ENGINE_ADD_OUTPUT() to add the engine inputs and outputs and to set default values for the outputs. Perform any other class-specific initialization, if needed. For example:

SoOnOff::SoOnOff()
   {
      SO_ENGINE_CONSTRUCTOR(SoOnOff);
      SO_ENGINE_ADD_INPUT(on, ());     // no default value for
                                       // trigger fields
      SO_ENGINE_ADD_INPUT(off, ());    // no default value
      SO_ENGINE_ADD_INPUT(toggle, ()); // no default value
      SO_ENGINE_ADD_OUTPUT(isOn, SoSFBool);
      SO_ENGINE_ADD_OUTPUT(isOff, SoSFBool);
      state = FALSE;             // engine is off by default
   }

Notice that this is where the outputs are bound to a particular field type.

Implementing an evaluate() Method

The evaluate() method is responsible for taking the input values and producing output values. The input values are read using the standard field getValue() routines. You can use the SO_ENGINE_OUTPUT() macro to set the values in the engine's outputs.


Note: SO_ENGINE_OUTPUT() is used for setting values in outputs. Do not perform any calculation inside this macro. Side effects are not allowed, because this macro loops through each connection. You can, however, call SO_ENGINE_OUTPUT() multiple times, once for each output, as shown in the SoComposeVec2f class (Examples 6-3 and 6-4).


Creating a Simple Engine

The following examples illustrate the basic steps outlined in “Overview”. Example 6-1 shows the header file for a simple engine, MultFloatVec3f, which multiplies a float value (SoSFFloat) by a vector value (SoSFVec3f) and produces an output of type SoSFVec3f. Example 6-2 shows the source file for this class.


Tip: The SoCalculator engine provides a built-in way of doing this type of simple arithmetic.


Example 6-1. MultFloatVec3f.h


#include <Inventor/engines/SoSubEngine.h>
#include <Inventor/fields/SoSFFloat.h>
#include <Inventor/fields/SoSFVec3f.h>

class MultFloatVec3f : public SoEngine {

   SO_ENGINE_HEADER(MultFloatVec3f);

 public:

   // Input fields: a scalar (float) and a vector
   SoSFFloat       scalar;
   SoSFVec3f       vector;

   // The output is a vector
   SoEngineOutput  product;  // (SoSFVec3f) product

   // Initializes this class for use in scene graphs. This
   // should be called after database initialization and before
   // any instance of this engine is constructed.
   static void initClass();

   // Constructor
   MultFloatVec3f();

 private:
   // Destructor. Since engines are never deleted explicitly,
   // this can be private.
   virtual ~MultFloatVec3f();

   // Evaluation method
   virtual void evaluate();
};

Example 6-2. MultFloatVec3f.c++


#include "MultFloatVec3f.h"

SO_ENGINE_SOURCE(MultFloatVec3f);

// Initializes the MultFloatVec3f class. This is a one-time
// thing that is done after database initialization and before
// any instance of this class is constructed.

void
MultFloatVec3f::initClass()
{
   // Initialize type id variables. The arguments to the macro
   // are: the name of the engine class, the class this is
   // derived from, and the name registered with the type
   // of the parent class.
   SO_ENGINE_INIT_CLASS(MultFloatVec3f, SoEngine, "Engine");
}

// Constructor

MultFloatVec3f::MultFloatVec3f()
{
   // Do standard constructor stuff
   SO_ENGINE_CONSTRUCTOR(MultFloatVec3f);

   // Define input fields and their default values
   SO_ENGINE_ADD_INPUT(scalar,  (0.0));
   SO_ENGINE_ADD_INPUT(vector,  (0.0, 0.0, 0.0));

   // Define the output, specifying its type
   SO_ENGINE_ADD_OUTPUT(product, SoSFVec3f);
}

// Destructor. Does nothing.

MultFloatVec3f::~MultFloatVec3f()
{
}

// This is the evaluation routine.

void
MultFloatVec3f::evaluate()
{
   // Compute the product of the input fields
   SbVec3f  prod = scalar.getValue() * vector.getValue();

   // "Send" the value to the output. In effect, we are setting
   // the value in all fields to which this output is connected.
   SO_ENGINE_OUTPUT(product, SoSFVec3f, setValue(prod));
}

Dealing with Multiple-Value Fields

This second engine has a slightly more complex evaluate() method. Before creating the output, it determines the longest input field, and replicates the last value in short fields that need to be filled out to match the number of values in the longest field. The SO_ENGINE_OUTPUT() macro is used to set the number of values in the fields connected from the output, as well as to set the values.

Creating an Engine with Multiple Inputs

Examples 6-3 and 6-4 show the header and source files for SoComposeVec2f. This engine has two inputs, x and y, of type SoMFFloat, and one output, an SoEngineOutput of type SoMFVec2f.

This engine illustrates a general policy for handling multiple-value inputs that is followed by all arithmetic engines in Inventor. This policy is useful for engines that process multiple-value inputs as “parallel” independent data sets, but it is not required. Because the x and y inputs of the SoComposeVec2f engine are multiple-value fields, they can each contain any number of values. Figure 6-1 illustrates a case where the last value of y is replicated to fill out the field with values to match up with the number of values provided in x .

Figure 6-1. Replicating Values in Fields with Fewer Values


Note that the constructor sets the default value for both input fields to 0.0.

Example 6-3. SoComposeVec2f.h


#include <Inventor/engines/SoSubEngine.h>
#include <Inventor/fields/SoMFFloat.h>
#include <Inventor/fields/SoMFVec2f.h>

class SoComposeVec2f : public SoEngine {

   SO_ENGINE_HEADER(SoComposeVec2f);

 public:

   // Inputs:
   SoMFFloat       x;
   SoMFFloat       y;

   // Output:
   SoEngineOutput  vector;  // (SoMFVec2f)

   // Initialization
   static void initClass();

   // Constructor
   SoComposeVec2f();

 private:
   // Destructor
   virtual ~SoComposeVec2f();

   // Evaluation method
   virtual void evaluate();
};

Example 6-4. SoComposeVec2f.c++


#include "SoComposeVec2f.h"

SO_ENGINE_SOURCE(SoComposeVec2f);

// Initializes the SoComposeVec2f class.

void
SoComposeVec2f::initClass()
{
   SO_ENGINE_INIT_CLASS(SoComposeVec2f, SoEngine, "Engine");
}

// Constructor

SoComposeVec2f::SoComposeVec2f()
{
   // Do standard constructor tasks
   SO_ENGINE_CONSTRUCTOR(SoComposeVec2f);

   // Define input fields and their default values
   SO_ENGINE_ADD_INPUT(x,  (0.0));
   SO_ENGINE_ADD_INPUT(y,  (0.0));

   // Define the output, specifying its type
   SO_ENGINE_ADD_OUTPUT(vector, SoMFVec2f);
}

// Destructor. Does nothing.
SoComposeVec2f::~SoComposeVec2f()
{
}

// This is the evaluation routine.
void
SoComposeVec2f::evaluate()
{
   // Figure out how many input values we have
   int numX = x.getNum();
   int numY = y.getNum();

   // We will output as many values as there are in the input
   // with the greater number of values
   int numToOutput = (numX > numY ? numX : numY);

   // Make sure that all of the fields connected from the output
   // have enough room for the results. The SoMField::setNum()
   // method does this.
   SO_ENGINE_OUTPUT(vector, SoMFVec2f, setNum(numToOutput));

   // Now output the vectors composed from the input values
   float xValue, yValue;
   int   i;
   for (i = 0; i < numToOutput; i++) {

     // If there are different numbers of values in the input
     // fields, repeat the last value as necessary.
     xValue = (i < numX ? x[i] : x[numX - 1]);
     yValue = (i < numY ? y[i] : y[numY - 1]);

     // Set the vector value in the indexed slot in all
     // connected fields
     SO_ENGINE_OUTPUT(vector, SoMFVec2f,
                     set1Value(i, xValue, yValue));
   }
}

Creating an Engine with Multiple Outputs

This engine class shows creating an engine with more than one output. Examples 6-5 and 6-6 illustrate an engine that decomposes a vector into its individual float values. The evaluate() method uses the getNum() method to determine how many input values there are. Then it uses the SoMField::setNum() method to ensure that the fields connected from this engine have enough room for the results, as in the previous example.

Example 6-5. SoDecomposeVec2f.h


#include <Inventor/engines/SoSubEngine.h>
#include <Inventor/fields/SoMFFloat.h>
#include <Inventor/fields/SoMFVec2f.h>

class SoDecomposeVec2f : public SoEngine {

   SO_ENGINE_HEADER(SoDecomposeVec2f);

 public:

   // Input:
   SoMFVec2f       vector;

   // Outputs:
   SoEngineOutput  x;       // (SoMFFloat)
   SoEngineOutput  y;       // (SoMFFloat)

   // Initialization
   static void initClass();

   // Constructor
   SoDecomposeVec2f();

 private:
   // Destructor
   virtual ~SoDecomposeVec2f();

   // Evaluation method
   virtual void evaluate();
};

Example 6-6. SoDecomposeVec2f.c++


#include "SoDecomposeVec2f.h"

SO_ENGINE_SOURCE(SoDecomposeVec2f);

// Initializes the SoDecomposeVec2f class.

void
SoDecomposeVec2f::initClass()
{
   SO_ENGINE_INIT_CLASS(SoDecomposeVec2f, SoEngine, "Engine");
}

// Constructor

SoDecomposeVec2f::SoDecomposeVec2f()
{
   // Do standard constructor tasks
   SO_ENGINE_CONSTRUCTOR(SoDecomposeVec2f);

   // Define input field and its default value
   SO_ENGINE_ADD_INPUT(vector,  (0.0, 0.0));

   // Define the outputs, specifying their types
   SO_ENGINE_ADD_OUTPUT(x, SoMFFloat);
   SO_ENGINE_ADD_OUTPUT(y, SoMFFloat);
}

// Destructor. Does nothing.

SoDecomposeVec2f::~SoDecomposeVec2f()
{
}

// This is the evaluation routine.

void
SoDecomposeVec2f::evaluate()
{
   // Figure out how many input values we have
   int numToOutput = vector.getNum();

   // Make sure that all of the fields connected from the
   // outputs have enough room for the results. The
   // SoMField::setNum() method does this.
   SO_ENGINE_OUTPUT(x, SoMFFloat, setNum(numToOutput));
   SO_ENGINE_OUTPUT(y, SoMFFloat, setNum(numToOutput));

   // Now output the values extracted from the input vectors
   for (int i = 0; i < numToOutput; i++) {
     SO_ENGINE_OUTPUT(x, SoMFFloat, set1Value(i, vector[i][0]));
     SO_ENGINE_OUTPUT(y, SoMFFloat, set1Value(i, vector[i][1]));
   }
}

Notification and Evaluation

If you are creating new engine classes, you may need to understand the following details on how changed values are propagated through an Inventor scene graph and engine network. Because evaluation can be slow, Inventor uses “lazy” evaluation for data in its engine network. Engines are evaluated only when their values are needed, and they are not reevaluated unnecessarily. For some engines, you might want to know whether a certain input has changed, and then take some action based on that knowledge. In such cases, you write an inputChanged() method for the engine. This section provides important background information on how and when an engine calls its inputChanged() and evaluate() methods. It also includes several examples of engines that implement an inputChanged() method.

Whenever a change is made to a field, notification propagates through the scene graph that the values dependent on this new value must be reevaluated. However, the evaluation does not occur until the value is requested through a call to getValue() (or getNum(), [ ], or getValues()). In other words, notification of a changed value is pushed through the scene graph. Any values needing reevaluation are marked as needing evaluation. Since no evaluation is performed at this time, notification is an efficient process.

Evaluation, by contrast, can be a slow process, depending on the nature of the engines. For this reason, evaluation is pulled through the scene graph on demand. Whenever a getValue() is called, if the field is connected to other fields or engines, those connections are traced, and fields marked as needing evaluation are evaluated before the new value is sent to its destination.

For example, consider the scene graph and engine network shown in Figure 6-2.

Figure 6-2. A Simple Engine Network


This scene graph contains four nodes: A, B, C, and D. Nodes B, C, and D contain fields that are connected to engines. For simplicity, assume that Nodes B, C, and D contain only one field, and Engines 1 and 2 have one input and one output each. Engine 1 adds 1 to its input and copies the result to its output. Engine 2 adds 2 to its input and copies the result to its output. The engines are connected to the nodes as shown in the diagram. If you change the value of the field in Node B from 1 to 3, the following things occur:

  1. Node B notifies A that its field has changed. If the scene graph is contained within a render area, A, in turn, notifies the redraw sensor.

  2. Node B notifies Engine 1 that its field needs to be evaluated. Then Engine 1's inputChanged() method is called.

  3. Engine 1 notifies the field in Node C, which is marked as needing evaluation. C notifies A, which notifies the redraw sensor.

  4. Engine 2's field is marked as needing evaluation, then its inputChanged() method is called.

  5. Engine 2 notifies Node D, which is marked as needing evaluation.
    D notifies A, which notifies the redraw sensor.

Steps 1 through 5 illustrate the push model of notification.

At this point, the value in Node B has changed to 3, but the value of the field in Node C is still 2, and the value of the field in Node D is still 4. When a getValue() is called on the field in Node D, the fields and engines that have been marked as needing evaluation are evaluated, and the new values propagate through the scene graph. At this point, the value of the field in Node C becomes 4, and the value of the field in Node D becomes 6.

If a getValue() is called on the field in Node C instead of Node D, however, Node D remains out of date, because its value was not needed.

An output, like a field connection, can be enabled and disabled. If an output is disabled, notification is not propagated through it, and none of the fields connected to it are marked as needing evaluation.

What Inventor Does behind the Scenes

Table 6-1 summarizes the process of notification and evaluation. Column 3 of the table provides some additional information about what Inventor does behind the scenes. Whenever setValue() or anything that changes a field is called by the application, Inventor automatically calls the engine's inputChanged() method. Similarly, whenever getValue() or any accessor of a field is called by the application, Inventor calls the engine's evaluate() method.

Note that the only time an engine knows when its input has changed is when Inventor calls its inputChanged() method. This notification is guaranteed to happen every time a setValue() occurs on one of the engine's inputs. Also, note that inputChanged() is always called before evaluate(). In fact, inputChanged() could be called several times before evaluate() is called (if several setValue() calls occur before a getValue() call occurs).

Table 6-1. Sequence for Notification and Evaluation

Program Calls

Effect

Inventor Calls

setValue()

Notification of the change is propagated through the scene graph

inputChanged()

getValue()

Updated values are pulled through the scene graph as each evaluate() method produces new output values

evaluate()


Blocking Notification

An engine can implement the inputChanged() method so that it blocks notification under certain conditions. An SoGate engine provides an example of blocking notification. When its enable field is set to FALSE (the default), notification of the setValue() calls does not propagate through it to the rest of the scene graph. However, if the trigger field is touched, the following occurs:

  • Inventor automatically calls inputChanged(). The inputChanged() method for the gate engines sets the enable field to TRUE.

When getValue() is called on a gate engine's output, the following occurs:

  • Inventor automatically calls evaluate() on the gate engine. The gate engines' evaluate() method outputs one value and sets the enable field to FALSE.

Here is the code for the gate engines' inputChanged() and evaluate() methods:

void
SoGate::inputChanged(SoField *whichInput)
{
   if (whichInput == &enable) 
      output.enable(enable.getValue());
   else if (whichInput == &trigger)
      output.enable(TRUE);
}

void 
SoGate::evaluate()
{
   trigger.getValue(); // Clears notification
   SO_ENGINE_OUTPUT(output, type, 
                    setValues(0, input.getNum(),
                    input.getValues(0)));
   // Get rid of any extra values in output
   SO_ENGINE_OUTPUT(output, type, setNum(input.getNum()));
   output.enable(enable.getValue());
}


Note: Do not call setValue() within an inputChanged() method. You can call getValue() within inputChanged(), but be aware that doing this could slow down notification because getValue() causes Inventor to pause and pull new values through the scene graph.


Creating a Fan-in Engine

The following engine provides a simple example of how an inputChanged() method is used to take different actions based on which input was touched. This engine accepts up to four inputs and produces one output (simulating fan-in of connections, which is not allowed directly). It simply copies the most recently touched input to the output. Example 6-7 shows the header file for the fan-in engine. Example 6-8 shows the source file for this class.

Example 6-7. FanIn.h


#include <Inventor/engines/SoSubEngine.h>
#include <Inventor/fields/SoSFFloat.h>

// This engine class takes up to four inputs (input0 through
// input3) and outputs the most recently changed one to the
// output.

class FanIn : public SoEngine {

 public:

   SO_ENGINE_HEADER(FanIn);

   // Inputs:
   SoSFFloat       input0;
   SoSFFloat       input1;
   SoSFFloat       input2;
   SoSFFloat       input3;

   // Output:
   SoEngineOutput  output;  // (SoSFFloat)

   // Initialization
   static void initClass();

   // Constructor
   FanIn();

 private:
   // This saves a pointer to the field that changed most
   // recently
   SoSFFloat       *lastChangedField;

   // Destructor
   virtual ~FanIn();

   // This is called when an input value changes - we will use
   // it to figure out which input was changed most recently
   virtual void inputChanged(SoField *whichField);

   // Evaluation method
   virtual void evaluate();
};

Example 6-8. FanIn.c++


#include "FanIn.h"

SO_ENGINE_SOURCE(FanIn);

// Initializes the FanIn class.

void
FanIn::initClass()
{
   SO_ENGINE_INIT_CLASS(FanIn, SoEngine, "Engine");
}

// Constructor

FanIn::FanIn()
{
   // Do standard constructor stuff
   SO_ENGINE_CONSTRUCTOR(FanIn);

   // Define input fields and their default values
   SO_ENGINE_ADD_INPUT(input0,  (0.0));
   SO_ENGINE_ADD_INPUT(input1,  (0.0));
   SO_ENGINE_ADD_INPUT(input2,  (0.0));
   SO_ENGINE_ADD_INPUT(input3,  (0.0));

   // Define the output, specifying its type
   SO_ENGINE_ADD_OUTPUT(output, SoSFFloat);

   // Initialize the pointer that indicates which field changed
   // most recently
   lastChangedField = NULL;
}

// Destructor. Does nothing.

FanIn::~FanIn()
{
}

// This is called when one of our input fields changes. We will
// use it to determine which input value to output.

void
FanIn::inputChanged(SoField *whichField)
{
   // Save a pointer to the field that changed; cast it to an
   // SoSFFloat, since that's the only input field type we have
   lastChangedField = (SoSFFloat *) whichField;
}

// This is the evaluation routine.

void
FanIn::evaluate()
{
   // If evaluate() is called, we must have been notified at
   // some point of a change (including a new connection), so
   // our lastChangedField should never be NULL here. Check it
   // anyway, just for completeness
   if (lastChangedField == NULL) {
     fprintf(stderr, "Uh-oh, there's a NULL lastChangedField"
             "in FanIn::evaluate()!\n");
     return;
   }

   // Output the value from the last changed field
   float   value = lastChangedField->getValue();
   SO_ENGINE_OUTPUT(output, SoSFFloat, setValue(value));
}

Creating a Second-Ticker Engine

As we saw with the gate engines, it's often convenient to enable and disable outputs in an inputChanged() method in order to control propagation of notification. The following engine illustrates how disabling outputs can be useful. This engine has one input, timeIn, and one output, timeOut. It outputs the time once a second and blocks notification in between “ticks.” This engine could be used if you want to render a scene only once a second. Because the realTime global field changes 60 times per second, you could use this engine to block notification in between. Example 6-9 shows the header file for the Ticker engine. Example 6-10 shows the source file for this class.


Note: Be careful when disabling outputs. If all outputs are disabled, evaluate() is never called.


Example 6-9. Ticker.h


#include <Inventor/engines/SoSubEngine.h>
#include <Inventor/fields/SoSFTime.h>

class Ticker : public SoEngine {

 public:

   SO_ENGINE_HEADER(Ticker);

   // Input:
   SoSFTime        timeIn;

   // Output:
   SoEngineOutput  timeOut;  // (SoSFTime)

   // Initialization
   static void initClass();

   // Constructor
   Ticker();

 private:
   // This saves the number of seconds of the last time this
   // engine was notified of a change
   double          lastSeconds;

   // Destructor
   virtual ~Ticker();

   // This is called when our input value changes - we will use
   // it to see if we need to output a new time value.
   virtual void inputChanged(SoField *whichField);

   // Evaluation method
   virtual void evaluate();
};

Example 6-10. Ticker.c++


#include <Inventor/SoDB.h>
#include "Ticker.h"

SO_ENGINE_SOURCE(Ticker);

// Initializes the Ticker class.

void
Ticker::initClass()
{
   SO_ENGINE_INIT_CLASS(Ticker, SoEngine, "Engine");
}

// Constructor

Ticker::Ticker()
{
   // Do standard constructor stuff
   SO_ENGINE_CONSTRUCTOR(Ticker);

   // Define input field and its default value
   SO_ENGINE_ADD_INPUT(timeIn,  (SbTime::zero()));

   // Define the output, specifying its type
   SO_ENGINE_ADD_OUTPUT(timeOut, SoSFTime);

   // Initialize the variable that stores the number of seconds
   // of the last time the input changed
   lastSeconds = -1.0;

   // Connect to the global "realTime" field by default. This
   // way, users do not have to make this connection explicitly,
   // and can change it if they want.
   timeIn.connectFrom(SoDB::getGlobalField("realTime"));
}

// Destructor. Does nothing.

Ticker::~Ticker()
{
}

// This is called when one of our input fields changes. We will
// use it to determine whether to produce any output. Since we
// have only 1 input field, we don't have to see which field
// changed.

void
Ticker::inputChanged(SoField *)
{

   // Get the current input time and get rid of any fractional
   // part, using the math library's floor() function
   SbTime   currentTime = timeIn.getValue();
   double   currentSeconds = floor(currentTime.getValue());

   // If the new number of seconds is different from the last
   // one we stored, enable the output. The next time this
   // engine is evaluated, the time will be output.
   if (currentSeconds != lastSeconds)
     timeOut.enable(TRUE);

   // Otherwise, make sure the output is disabled, since we
   // don't want to output any values until we cross the next
   // second barrier.
   else
     timeOut.enable(FALSE);
}

// This is the evaluation routine.

void
Ticker::evaluate()
{
   // Output the current number of seconds
   SbTime   currentTime(lastSeconds);
   SO_ENGINE_OUTPUT(timeOut, SoSFTime, setValue(currentTime));
}

Creating a New Field Converter

Whenever a connection is made between fields, inputs, or outputs and the types aren't the same, Inventor tries to insert an engine to convert from the source type to the destination type. Inventor maintains a table that lists the class of engine that converts between a given pair of types.

You can define new converters and add them to the table, either to support additional conversions between built-in field types, or to support conversions to or from new fields you have created (see Chapter 3). For convenience, a single class of engine can support several different types of conversions (for example, a single engine may be able to convert from a rational number field to a float, a short, or a long). When Inventor creates an instance of the field converter, it tells it the source and destination field types.

Overview

Creating a field converter is similar to creating other types of engines, with a few additional steps. The following checklist summarizes things you need to do when you create a new field converter.

  1. Select a name for the new field converter class and determine what class it is derived from.

  2. Define and name each input and output for the engine, as for a standard engine.

  3. Define the constructor, destructor, and evaluate() method, as for a standard engine.

  4. Declare two required virtual methods:

    virtual SoField *getInput(SoType type);

    virtual SoEngineOutput *getOutput(SoType type);

    These methods are called by Inventor to obtain the input and output it will connect to and from for a particular conversion.

Initializing the Field Converter Class

Implement the initClass() method. For each conversion that the engine supports, after the SO_ENGINE_INIT_CLASS() macro, call

SoDB::addConverter(typeIdOfSourceField, typeIdOfDestinationField,
YourEngine::getClassTypeId());

The evaluate() Method

If the converter has multiple inputs or outputs, its evaluate() method can check input.isConnected() and output.getNumConnections() to find out which conversion needs to be done. Or, the getInput() and getOutput() methods can save their parameters in instance variables and the evaluate() method can check them.

The getInput() and getOutput() Methods

The getInput() and getOutput() methods check the passed type and return the appropriate input or output. It is guaranteed that Inventor will never call these methods except for pairs of types registered with SoDB::addConverter().

Sample Field Converter

Example 6-11 shows the header file for a new field converter, ConvertSFShortToSFFloat. Example 6-12 shows the source code for this class.

Example 6-11. ConvertSFShortToSFFloat.h


#include <Inventor/engines/SoFieldConverter.h>
#include <Inventor/fields/SoSFShort.h>
#include <Inventor/fields/SoSFFloat.h>

class ConvertSFShortToSFFloat : public SoFieldConverter {

 public:

   SO_ENGINE_HEADER(ConvertSFShortToSFFloat);

   // Input:
   SoSFShort        input;

   // Output:
   SoEngineOutput   output;  // (SoSFFloat)

   // Initialization
   static void initClass();

   // Constructor
   ConvertSFShortToSFFloat();

 private:
   // Destructor
   virtual ~ConvertSFShortToSFFloat();

   // Evaluation method
   virtual void evaluate();

   // These must be defined for a field converter. They return
   // the input and output connections of the given types. In
   // our case, we have only one input and one output, so we
   // know that those will be the given types.
   virtual SoField *        getInput(SoType type);
   virtual SoEngineOutput * getOutput(SoType type);
};

Example 6-12. ConvertSFShortToSFFloat.c++


#include <Inventor/SoDB.h>
#include "ConvertSFShortToSFFloat.h"

SO_ENGINE_SOURCE(ConvertSFShortToSFFloat);

// Initializes the ConvertSFShortToSFFloat class.

void
ConvertSFShortToSFFloat::initClass()
{
   SO_ENGINE_INIT_CLASS(ConvertSFShortToSFFloat,
                        SoFieldConverter, "FieldConverter");

   // Register this converter's type with the Inventor database
   // to convert from a field (or engine output) of type
   // SoSFShort to a field of type SoSFFloat.
   // We only call this once, since this engine offers only one
   // type conversion.
   SoDB::addConverter(SoSFShort::getClassTypeId(),
                      SoSFFloat::getClassTypeId(),
                      getClassTypeId());
}

// Constructor

ConvertSFShortToSFFloat::ConvertSFShortToSFFloat()
{
   // Do standard constructor tasks
   SO_ENGINE_CONSTRUCTOR(ConvertSFShortToSFFloat);

   // Define input field and its default value
   SO_ENGINE_ADD_INPUT(input,  (0));

   // Define the output, specifying its type
   SO_ENGINE_ADD_OUTPUT(output, SoSFFloat);
}

// Destructor. Does nothing.

ConvertSFShortToSFFloat::~ConvertSFShortToSFFloat()
{
}

// This is the evaluation routine.

void
ConvertSFShortToSFFloat::evaluate()
{
   // Get the input value as a short, convert it to a float, and
   // output it
   float    value = (float) input.getValue();
   SO_ENGINE_OUTPUT(output, SoSFFloat, setValue(value));
}

// This returns the input field for the given type. Since we
// have only one input field, we don't have to check the type.

SoField *
ConvertSFShortToSFFloat::getInput(SoType)
{
   return &input;
}

// This does the same for the output.

SoEngineOutput *
ConvertSFShortToSFFloat::getOutput(SoType)
{
   return &output;
}