HDK
|
The most common reason for writing a new object operator is to implement a new transformation algorithm. This is achieved by computing a new transform matrix. As briefly explained in Object Cooking, the transform matrix calculation happens in OBJ_Node::cookMyObj() method, and you can overload it to implement own computation. However, when implementing a new object operator, often it is not necessary to dramatically alter this method, but rather it is sufficient to overload one of the following virtual methods, which compute a sub-component of the whole matrix:
On rare occasions, when overloading these methods is not sufficient, you will need to write own cookMyObj()
method and you can base it on the skeleton provided in Object Cooking. This is illustrated in OBJ_WorldAlign.C example and described in Overriding cookMyObj() section below.
flags()
.setTimeDep(true) in these method overrides if you compute a value based on the evaluation time explicitly. This is because objects break down the computations into smaller pieces than the entire cookMyObj()
process.You can find an example of a simple implementation of an object node in the OBJ_Shake.C file in the toolkit samples. This example implements an object that computes a randomly jittered translation matrix and appends it to the chain of other transforms to achieve an earthquake effect.
The shake object derives from OBJ_Geometry and inherits all of its parameters and adds own "jitter" parameter. The following code defines a list of parameters that the operator wants to add:
The following function retrieves the parameters from the base class and concatenates them with own parameters. The final list returned by this function will be passed to the OP_OperatorTable::newOperator() during the call that registers this operator.
Now that we defined the jitter parameter, we can evaluate it in the code using the following member methods:
The shakeIndirect
array is a cache for the index number of the "jitter" parameter value. This cache is initiated during the first call, and is reused during the subsequent calls. This speeds up evaluation by avoiding the search for the "jitter" name in the list in the OP_Parameters::evalFloat() call.
The base class, OBJ_Geometry, also uses a class static parameter index cache, which this derived class shares for the base class parameters. This is fine in this particular example, but if your derived class were to have a fully customized template list that changes the indices of these base class parameters, you would have to provide a different parameter index cache by overridding the OBJ_Geometry::getIndirect() method.
For example, you could do the following:
with the following in your constructor:
There is a standard method signature for a node instance factory function that creates the instances. It is used by houdini to create a new node:
Most of the time this factory function will simply create a new instance of the class you are implementing.
Now that both parameter list and factory function are available, you can register the new operator with Houdini. When a dynamic library is loaded by Houdini, it searches for the newObjectOperator()
function, and if found, it calls it. This mechanism is used to register the new operator in Houdini with the OP_OperatorTable::addOperator() method:
The "hdk_shake" is a unique name of the new object operator. The "hdk_" prefix is used to avoid name collisions in the future, when a native operator by the same name could be added.
At this point, Houdini will be able to create a new "hdk_shake" node that has a given set of parameters, but this node will not behave much differently than the standard geometry node (whose behavior it inherited). To make the behavior different, the following method alters the computation of the transform matrices:
The OBJ_Node::applyInputIndependentTransform() is overloaded because the translation jitters depend only on the node's parameter and not on any external nodes. The OBJ_Node::getParmTransform() was not overloaded because "jitter" parameter is not a standard translation, rotation, or scale parameter, but that method could conceivably be overloaded instead of OBJ_Node::applyInputIndependentTransform().
Notice that this method also calls setTimeDep() because the randomness of the jittering depends on the time, and when the time changes, the object needs to recompute to calculate a new transformation.
In some cases it may be insufficient to overload the virtual methods that are invoked from OBJ_Node::cookMyObj(), and you may need to overload the OBJ_Node::cookMyObj() itself. The OBJ_WorldAlign.C implements an operator that tries to align the orientation of the object to the world axes. Although, this could be achieved by overloading the OBJ_Node::buildLookAt() method, for the purpose of illustrating the technique, this example implements it by overloading OBJ_Node::cookMyObj():
In the above code, the main point of overloading the OBJ_Node::cookMyObj() method is to ultimately alter the OBJ_Node::myXform and OBJ_Node::myWorldXform matrices for the local and global transformations. Calling the base class OBJ_Geometry::cookMyObj() performs all the calculations inherited from the base class and it also dirties the world inverse matrix, which is recalculated the next time it is needed.