15.12. Arithmetic Engines

By convention, all inputs and outputs for the arithmetic engines in Inventor are multiple-value (MF) fields. If you supply a value of type SoSF, it is automatically converted to an MF field. Another important feature is that if you supply an array of values for one of the inputs, the output will also be an array (an MF value). If an engine has more than one input, some inputs may have more values than others. For example, input1 might have five values and input2 might have only three values. In such cases, the last value of the field with fewer values is repeated as necessary to fill out the array. (Here, the third value of input2 would be repeated two more times.)

As shown in Figure 15.14, “ SoBoolOperation Engine, the Boolean engine, SoBoolOperation SoBoolOperation SoBoolOperation , has two Boolean inputs (a and b) and one SoSFEnum SoSFEnum input (operation) that describes the operation to be performed.


The value for operation can be one of the following:

Operation

Output Is TRUE If

CLEA

R

never TRUE

SET

always TRUE

A

A is TRUE

NOT_A

A is FALSE

B

B is TRUE

NOT_B

B is FALSE

A_OR_B

A is TRUE or B is TRUE

NOT_A_OR_B

A is FALSE or B is TRUE

A_OR_NOT_B

A is TRUE or B is FALSE

NOT_A_OR_NOT_B

A is FALSE or B is FALSE

A_AND_B

A and B are TRUE

NOT_A_AND_B

A is FALSE and B is TRUE

A_AND_NOT_B

A is TRUE and B is FALSE

NOT_A_AND_NOT_B

A and B are FALSE

A_EQUALS_B

A equals B

A_NOT_EQUALS_B

A does not equal B

This engine has two outputs, output and inverse. The inverse field is TRUE if output is FALSE, and vice versa. If either of the inputs contains an array of values (they are of type SoMFBool SoMFBool SoMFBool ), the output will also contain an array of values.

Example 15.5, “ Using a Boolean Engine modifies Example 15.4, “ Using a Gate Engine ” and adds a Boolean engine to make the motion of the smaller duck depend on the motion of the larger duck. The smaller duck moves when the larger duck is still. Figure 15.15, “ Swimming Ducks Controlled by a Boolean Engine shows an image created by this example.


Example 15.5.  Using a Boolean Engine

// Bigger duck group
SoSeparator *bigDuck = new SoSeparator;
root->addChild(bigDuck);
SoRotationXYZ *bigDuckRotXYZ = new SoRotationXYZ;
bigDuck->addChild(bigDuckRotXYZ);
SoTransform *bigInitialTransform = new SoTransform;
bigInitialTransform->translation.setValue(0., 0., 3.5);
bigInitialTransform->scaleFactor.setValue(6., 6., 6.);
bigDuck->addChild(bigInitialTransform);
bigDuck->addChild(duckObject);

// Smaller duck group
SoSeparator *smallDuck = new SoSeparator;
root->addChild(smallDuck);
SoRotationXYZ *smallDuckRotXYZ = new SoRotationXYZ;
smallDuck->addChild(smallDuckRotXYZ);
SoTransform *smallInitialTransform = new SoTransform;
smallInitialTransform->translation.setValue(0., -2.24, 1.5);
smallInitialTransform->scaleFactor.setValue(4., 4., 4.);
smallDuck->addChild(smallInitialTransform);
smallDuck->addChild(duckObject);

// Use a gate engine to start/stop the rotation of 
// the bigger duck.
SoGate *bigDuckGate = 
         new SoGate(SoMFFloat::getClassTypeId());
SoElapsedTime *bigDuckTime = new SoElapsedTime;
bigDuckGate->input->connectFrom(&bigDuckTime->timeOut); 
bigDuckRotXYZ->axis = SoRotationXYZ::Y;
bigDuckRotXYZ->angle.connectFrom(bigDuckGate->output);

// Each mouse button press will enable/disable the gate 
// controlling the bigger duck.
SoEventCallback *myEventCB = new SoEventCallback;
myEventCB->addEventCallback(
         SoMouseButtonEvent::getClassTypeId(),
         myMousePressCB, bigDuckGate);
root->addChild(myEventCB);

// Use a Boolean engine to make the rotation of the smaller
// duck depend on the bigger duck.  The smaller duck moves
// only when the bigger duck is still.
SoBoolOperation *myBoolean = new SoBoolOperation;
myBoolean->a.connectFrom(&bigDuckGate->enable);
myBoolean->operation = SoBoolOperation::NOT_A;

SoGate *smallDuckGate = new
         SoGate(SoMFFloat::getClassTypeId());
SoElapsedTime *smallDuckTime = new SoElapsedTime;
smallDuckGate->input->connectFrom(&smallDuckTime->timeOut); 
smallDuckGate->enable.connectFrom(&myBoolean->output); 
smallDuckRotXYZ->axis = SoRotationXYZ::Y;
smallDuckRotXYZ->angle.connectFrom(smallDuckGate->output);
    
// Bigger duck group
SoSeparator bigDuck = new SoSeparator();
root.AddChild(bigDuck);
SoRotationXYZ bigDuckRotXYZ = new SoRotationXYZ();
bigDuck.AddChild(bigDuckRotXYZ);
SoTransform bigInitialTransform = new SoTransform();
bigInitialTransform.translation.SetValue(0.0f, 0.0f, 3.5f);
bigInitialTransform.scaleFactor.SetValue(6.0f, 6.0f, 6.0f);
bigDuck.AddChild(bigInitialTransform);
bigDuck.AddChild(duckObject);

// Smaller duck group
SoSeparator smallDuck = new SoSeparator();
root.AddChild(smallDuck);
SoRotationXYZ smallDuckRotXYZ = new SoRotationXYZ();
smallDuck.AddChild(smallDuckRotXYZ);
SoTransform smallInitialTransform = new SoTransform();
smallInitialTransform.translation.SetValue(0.0f, -2.24f, 1.5f);
smallInitialTransform.scaleFactor.SetValue(4.0f, 4.0f, 4.0f);
smallDuck.AddChild(smallInitialTransform);
smallDuck.AddChild(duckObject);

// Use a gate engine to start/stop the rotation of 
// the bigger duck.
bigDuckGate = new SoGate(typeof(SoMFFloat));
bigDuckTime = new SoElapsedTime();
bigDuckGate.input.ConnectFrom(bigDuckTime.timeOut); 
bigDuckRotXYZ.axis.Value = SoRotationXYZ.AxisType.Y;  // Y axis
bigDuckRotXYZ.angle.ConnectFrom(bigDuckGate.output);

// Each mouse button press will enable/disable the gate 
// controlling the bigger duck.
SoEventCallback myEventCB = new SoEventCallback();
myEventCB.AddEventCallback(typeof(SoMouseButtonEvent), myMousePressCB);
root.AddChild(myEventCB);

// Use a Boolean engine to make the rotation of the smaller
// duck depend on the bigger duck.  The smaller duck moves
// only when the bigger duck is still.
myBoolean = new SoBoolOperation();
myBoolean.a.ConnectFrom(bigDuckGate.enable);
myBoolean.operation.SetValue(SoBoolOperation.Operations.NOT_A);

smallDuckGate = new SoGate(typeof(SoMFFloat));
smallDuckTime = new SoElapsedTime();
smallDuckGate.input.ConnectFrom(smallDuckTime.timeOut); 
smallDuckGate.enable.ConnectFrom(myBoolean.output);
smallDuckRotXYZ.axis.Value = SoRotationXYZ.AxisType.Y;  // Y axis
smallDuckRotXYZ.angle.ConnectFrom(smallDuckGate.output);
    
// Bigger duck group
SoSeparator bigDuck = new SoSeparator();
SoRotationXYZ bigDuckRotXYZ = new SoRotationXYZ();
SoTransform bigInitialTransform = new SoTransform();
bigInitialTransform.translation.setValue(0.0f, 0.0f, 3.5f);
bigInitialTransform.scaleFactor.setValue(6.0f, 6.0f, 6.0f);
{
  bigDuck.addChild(bigDuckRotXYZ);
  bigDuck.addChild(bigInitialTransform);
  bigDuck.addChild(duckObject);
}

// Smaller duck group
SoSeparator smallDuck = new SoSeparator();
SoRotationXYZ smallDuckRotXYZ = new SoRotationXYZ();
SoTransform smallInitialTransform = new SoTransform();
smallInitialTransform.translation.setValue(0.0f, -2.24f, 1.5f);
smallInitialTransform.scaleFactor.setValue(4.0f, 4.0f, 4.0f);
{
  smallDuck.addChild(smallDuckRotXYZ);
  smallDuck.addChild(smallInitialTransform);
  smallDuck.addChild(duckObject);
}

// Use a gate engine to start/stop the rotation of
// the bigger duck.
SoEventCallback myEventCB = new SoEventCallback();
SoBoolOperation myBoolean = new SoBoolOperation();
try {
  SoGate bigDuckGate = new SoGate(SoMFFloat.class);
  SoElapsedTime bigDuckTime = new SoElapsedTime();
  bigDuckGate.input.connectFrom(bigDuckTime.timeOut);
  bigDuckRotXYZ.axis.setValue(SoRotationXYZ.AxisType.Y); // Y axis
  bigDuckRotXYZ.angle.connectFrom(bigDuckGate.output);

  // Each mouse button press will enable/disable the gate
  // controlling the bigger duck.
  myEventCB.addEventCallback(SoMouseButtonEvent.class,
                             new MousePressCB(bigDuckGate));

  // Use a Boolean engine to make the rotation of the smaller
  // duck depend on the bigger duck.  The smaller duck moves
  // only when the bigger duck is still.
  myBoolean.a.connectFrom(bigDuckGate.enable);
  myBoolean.operation.setValue(SoBoolOperation.Operations.NOT_A.getValue());
}
catch (Exception e) {
  e.printStackTrace();
}

try {
  SoGate smallDuckGate = new SoGate(SoMFFloat.class);
  SoElapsedTime smallDuckTime = new SoElapsedTime();
  smallDuckGate.input.connectFrom(smallDuckTime.timeOut);
  smallDuckGate.enable.connectFrom(myBoolean.output);
  smallDuckRotXYZ.axis.setValue(SoRotationXYZ.AxisType.Y); // Y axis
  smallDuckRotXYZ.angle.connectFrom(smallDuckGate.output);
}
catch (Exception e) {
  e.printStackTrace();
}
    

The calculator engine, SoCalculator SoCalculator SoCalculator , is similar to the Boolean engine, but it handles a wider range of operations and has more inputs and outputs. As shown in Figure 15.16, “ SoCalculator Engine, this engine has the following inputs and outputs:

Inputs

SoMFFloat

a, b, c, d, e, f, g, h

 

SoMFVec3f

A, B, C, D, E, F, G, H

 

SoMFString

expression

 

 

 

Outputs

SoEngineOutput

oa, ob, oc, od (SoMFFloat)

 

SoEngineOutput

oA, oB, oC, oD (SoMFVec3f)

The expression input, shown at the bottom of the engine, is of type SoMFString SoMFString SoMFString and is of the form:

lhs = rhs

lhs (lefthand side) can be any one of the outputs or a temporary variable. This engine provides eight temporary floating-point variables (ta – th) and eight temporary vector variables (tA – tH).

rhs (righthand side) supports the following operators:

Type of Operator

Example

Binary operators

+ - * / < > >= <= == != && ||

Unary operators

- !

Ternary operator

cond ? trueexpr : falseexpr

Parentheses

( expr )

Vector indexing

vec [int]

Functions

func( expr, ... )

Terms

integer or floating-point constants; named constants such as MAXFLOAT, MINFLOAT, M_LOG2E, M_PI; the names of the calculator engine's inputs, outputs, and temporary variables (a, b, A, B, oa, ob, ta, tb, tA, tB, and so on)


See the Open Inventor C++ Reference Manual for detailed information on using these operators.

Here is a simple example of using the calculator engine. It uses the following inputs and outputs:

Inputs

Outputs

2 vectors (A, B)

oA (f times the negation of the cross product of A and B)

2 scalars (a, f)

oa (convert a from degrees to radians)

To specify the expression for a calculator engine called calc, the code would be

calc->expression.set1Value(0, "oa = a * M_PI / 180");
calc->expression.set1Value(1, "oA = -f * cross(A, B)");
    
calc.expression[0] = "oa = a * M_PI / 180";
calc.expression[1] = "oA = -f * cross(A, B)";
    
calc.expression.set1Value(0, "oa = a * M_PI / 180");
calc.expression.set1Value(1, "oA = -f * cross(A, B)");
    

Multiple expressions are evaluated in order, so a variable assigned a value in an earlier expression can be used in the righthand side of a later expression. Several expressions can be specified in one string, separated by semicolons.

The expressions can also operate on arrays. If one input contains fewer values than another input, the last value is replicated as necessary to fill out the array. All the expressions will be applied to all elements of the arrays. For example, if input a contains multiple values and input b contains the value 1.0, then the expression “oa = a + b” will add 1 to all of the elements in a.

Example 15.6, “ Using a Calculator Engine shows using the calculator engine to move a flower along a path. The calculator engine computes a closed, planar curve. The output of the engine is connected to the translation applied to a flower object, which then moves along the path of the curve. Figure 15.17, “ Scene Graph for Calculator Engine Example shows the scene graph for this example. The dancing flower is shown in Figure 15.18, “ Using a Calculator Engine to Constrain an Object's Movement.


Example 15.6.  Using a Calculator Engine

// Flower group
SoSeparator *flowerGroup = new SoSeparator;
root->addChild(flowerGroup);

// Read the flower object from a file and add to the group
if (!myInput.openFile("flower.iv")) 
   exit(1);
SoSeparator *flower= SoDB::readAll(&myInput);
if (flower == NULL) 
   exit(1);

// Set up the flower transformations
SoTranslation *danceTranslation = new SoTranslation;
SoTransform *initialTransform = new SoTransform;
flowerGroup->addChild(danceTranslation);
initialTransform->scaleFactor.setValue(10., 10., 10.);
initialTransform->translation.setValue(0., 0., 5.);
flowerGroup->addChild(initialTransform);
flowerGroup->addChild(flower);
        
// Flower group
SoSeparator flowerGroup = new SoSeparator();
root.AddChild(flowerGroup);

// Read the flower object from a file and add to the group
myInput.OpenFile("../../../../../data/flower.iv");

SoSeparator flower = SoDB.ReadAll(myInput);

// Set up the flower transformations
SoTranslation danceTranslation = new SoTranslation();
SoTransform initialTransform = new SoTransform();
flowerGroup.AddChild(danceTranslation);
initialTransform.scaleFactor.SetValue(10.0f, 10.0f, 10.0f);
initialTransform.translation.SetValue(0.0f, 0.0f, 5.0f);
flowerGroup.AddChild(initialTransform);
flowerGroup.AddChild(flower);
        
 // Flower group
SoSeparator flowerGroup = new SoSeparator();
root.addChild(flowerGroup);

// Read the flower object from a file and add to the group
myInput.openFile("../../../../../data/flower.iv");

SoSeparator flower = SoDB.readAll(myInput);

// Set up the flower transformations
SoTranslation danceTranslation = new SoTranslation();
SoTransform initialTransform = new SoTransform();
flowerGroup.addChild(danceTranslation);
initialTransform.scaleFactor.setValue(10.0f, 10.0f, 10.0f);
initialTransform.translation.setValue(0.0f, 0.0f, 5.0f);
flowerGroup.addChild(initialTransform);
flowerGroup.addChild(flower);
        


// Set up an engine to calculate the motion path:
// r = 5*cos(5*theta); x = r*cos(theta); z = r*sin(theta)
// Theta is incremented using a time counter engine,
// and converted to radians using an expression in
// the calculator engine.
SoCalculator *calcXZ = new SoCalculator; 
SoTimeCounter *thetaCounter = new SoTimeCounter;

thetaCounter->max = 360;
thetaCounter->step = 4;
thetaCounter->frequency = 0.075;

calcXZ->a.connectFrom(&thetaCounter->output);    
calcXZ->expression.set1Value(0, "ta=a*M_PI/180");   // theta
calcXZ->expression.set1Value(1, "tb=5*cos(5*ta)");  // r
calcXZ->expression.set1Value(2, "td=tb*cos(ta)");   // x 
calcXZ->expression.set1Value(3, "te=tb*sin(ta)");   // z 
calcXZ->expression.set1Value(4, "oA=vec3f(td,0,te)"); 
danceTranslation->translation.connectFrom(&calcXZ->oA);
      
// Set up an engine to calculate the motion path:
// r = 5*cos(5*theta); x = r*cos(theta); z = r*sin(theta)
// Theta is incremented using a time counter engine,
// and converted to radians using an expression in
// the calculator engine.
calcXZ = new SoCalculator();
thetaCounter = new SoTimeCounter();

thetaCounter.max.Value = (360);
thetaCounter.step.Value = (4);
thetaCounter.frequency.Value = (0.075f);

calcXZ.a.ConnectFrom(thetaCounter.output);
calcXZ.expression[0] = "ta=a*M_PI/180"; // theta
calcXZ.expression[1] = "tb=5*cos(5*ta)"; // r
calcXZ.expression[2] = "td=tb*cos(ta)"; // x 
calcXZ.expression[3] = "te=tb*sin(ta)"; // z 
calcXZ.expression[4] = "oA=vec3f(td,0,te)";
danceTranslation.translation.ConnectFrom(calcXZ.oA);
    
thetaCounter.max.setValue((short)360);
thetaCounter.step.setValue((short)4);
thetaCounter.frequency.setValue(0.075f);

calcXZ.a.connectFrom(thetaCounter.output);
calcXZ.expression.set1Value(0, "ta=a*M_PI/180"); // theta
calcXZ.expression.set1Value(1, "tb=5*cos(5*ta)"); // r
calcXZ.expression.set1Value(2, "td=tb*cos(ta)"); // x 
calcXZ.expression.set1Value(3, "te=tb*sin(ta)"); // z 
calcXZ.expression.set1Value(4, "oA=vec3f(td,0,te)");
danceTranslation.translation.connectFrom(calcXZ.oA);