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:
A simple engine called MultFloatVec3f
An engine with multiple inputs called SoComposeVec2f
An engine with multiple outputs called SoDecomposeVec2f
An engine that simulates fanning in of multiple connections called FanIn
An engine that controls propagation of its output value called Ticker
A field converter engine called ConvertSFShortToSFFloat
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:
Select a name for the new engine class and determine what class it is derived from.
Define and name each input and output of the engine (see “Defining Inputs and Outputs”).
Define an initClass() method to initialize the type information (see “Initializing the Engine Class”).
Define a constructor (see “Defining the Constructor”).
Define a destructor.
Implement an evaluate() method for the engine class (see “Notification and Evaluation”).
If necessary, implement an inputChanged() method (see “Creating a Fan-in Engine” and “Creating a Second-Ticker Engine”).
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).
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.
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"); } |
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.
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.
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.
#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(); }; |
#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)); } |
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.
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 .
Note that the constructor sets the default value for both input fields to 0.0.
#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(); }; |
#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)); } } |
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.
#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(); }; |
#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])); } } |
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.
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:
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.
Node B notifies Engine 1 that its field needs to be evaluated. Then Engine 1's inputChanged() method is called.
Engine 1 notifies the field in Node C, which is marked as needing evaluation. C notifies A, which notifies the redraw sensor.
Engine 2's field is marked as needing evaluation, then its inputChanged() method is called.
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.
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 |
---|---|---|
Notification of the change is propagated through the scene graph | ||
Updated values are pulled through the scene graph as each evaluate() method produces new output values |
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:
When getValue() is called on a gate engine's output, the following occurs:
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. |
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.
#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(); }; |
#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)); } |
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. |
#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(); }; |
#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)); } |
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.
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.
Select a name for the new field converter class and determine what class it is derived from.
Define and name each input and output for the engine, as for a standard engine.
Define the constructor, destructor, and evaluate() method, as for a standard engine.
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.
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());
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 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().
Example 6-11 shows the header file for a new field converter, ConvertSFShortToSFFloat. Example 6-12 shows the source code for this class.
#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); }; |
#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; } |