制作(cooking)
当你写自己的节点,这个过程一定程度上取决于你的网络类型的写作,落实代码——提供操作函数利用定义的参数来控制它的运作是必须的。通常你将从网络类型的根(比如SOP_Node或DOP_Node)划出子类,然后依赖它的父类,你需要重写相应的虚拟函数为你的节点来执行特定的操作。比如,SOP_Node子类将覆盖SOP_Node::cookMySop().一些自定义操作符的例子都装备了工具包(toolkit),详细的文件中可以找到Houdini Operators页面上的模块。
局部变量
每个OP_Node可以是在其制作(cook)的范围定义的本地变量(比如,当制作方法被唤时激活)这些变量被定义OP_Operator构造器中。目前,变量可以浮点或字符串值。
OP_Operator构造器获取一个CH_LocalVariable的数组,这个类包含一个字符串(代表变量名)和一个唯一的整数(求值使用)因此,比如,为一个操作符定义4个局部变量,我们可能有下面的代码:
enum {
VAR_PT, // Current point
VAR_NPT, // Total number of points
VAR_ID, // Particle ID
VAR_LIFE, // Life time
};
// Next, we define our local variable table
CH_LocalVariable
SOP_MyNode::myVariables[] = {
{ "PT", VAR_PT },
{ "NPT", VAR_NPT },
{ "ID", VAR_ID },
{ "LIFE", VAR_LIFE },
{ 0 } // End the table with a null entry
};
在对OP_Node参数求值时,这些变量变得“激活”,假如在一个表达式中变量被找到,子类的OP_Parameters::getVariableValue()版本将被调用,这是你的责任,为返回有关变量的值,对于局部字符串变量,重写OP_Parameters::getVariableString()代替,从上面的例子继续,我们有
SOP_MyNode::getVariableValue(int index, int thread)
{
GEO_Point *ppt;
int *id;
float *life;
// The implications of this check is that we will always return 0
// whenever this local variable is evaluated outside of cooking.
if (myCurrPoint < 0) // Sorry, we're in an invalid state
return 0;
switch (index)
{
case VAR_PT:
return myCurrPoint;
case VAR_NPT:
return myTotalPoints;
case VAR_ID:
ppt = myInputGeo->points()(myCurrPoint);
id = ppt->castAttribData<int>(myInputIdOffset);
return (float)*id;
case VAR_LIFE:
ppt = myInputGeo->points()(myCurrPoint);
life = ppt->castAttribData<float>(myInputLifeOffset);
return life[0]/life[1];
}
// Not one of our variables, must delegate to the base class.
return SOP_Node::getVariableValue(index, thread);
}
对于制作(cook)代码可能类似
SOP_MyNode::cookMySop(OP_Context &context)
{
float t = context.getTime();
...
// Now, we loop through each point, doing expression
// evaluation INSIDE the loop.
for (myCurrPoint = 0; myCurrPoint < myTotalPoints; myCurrPoint++)
{
// When evaluating parms, it's possible that the expression
// would use one of our local variables, so
pos.x() = evalFloat("posx", 0, t);
...
}
myCurrPoint = -1; // Make sure to flag that we're done cooking
...
}
对于设置标志myCurrPoint为-1循环的外部原因是,有可能你getVariableValue方法被相关环境激活,其原因是复杂的,但它必须在对话框中显示参数值(比如,参数需要被调用,但是 OP_Node没有被制作(cooked))如果这样,它试图返回一个合理的值
相关性
主题
名称相关性
数据相关性
名称相关性表明对于houdini,一些节点或参数对其他一些节点或参数名称相关。例如,假设我们有一个节点的路径参数。那么当节点的路径参数是指被重命名,houdini相应更新路径的参数值。这些依赖通常是静态的,通常是通过标记字符串参数使用下列PRM_TypeExtended选项之一指定:
PRM_TYPE_DYNAMIC_PATH
通过节点选择图标按钮指定一节点路径参数。如果这被使用,那么你应该添加指定的预期节点类型为了获取PRM_SpareData对象的路径参数,如PRM_SpareData::objPath 或PRM_SpareData::sopPath.
PRM_TYPE_DYNAMIC_PATH_LIST
类似PRM_TYPE_DYNAMIC_PATH,除了它接受一个节点的路径列表。这是为什么这些依赖关系应建立静态的原因(相比动态的在制作(cook)时间)是因为这需要更新,即使没有发生节点被制作(cooked)(如加载后立即在hbatch场景文件)。
PRM_Template
SOP_MyNode::myTemplateList[] = {
// ...
PRM_Template(PRM_STRING, PRM_TYPE_DYNAMIC_PATH,
1, &sopParmName, 0, 0, 0, 0, &PRM_SpareData::objPath),
// ...
PRM_Template()
};
数据相关性
在houdini中,制作调用是作为一个数据依赖图的地方。本图的边通常是在网络视图窗格中显示的线。但是,如果另外一些节点数据制作通过另外一些方式(通过一些参数的值像对象合并SOP),它们必须明确通过调用OP_Node这种依赖性::addExtraInput()。这让houdini知道,当其他节点的数据发生变化,这个节点也需要recooked
Elements in the UI express an interest on the nodes so that when the nodes are dirtied, they are notified to recook. When a parameter changes, everything in the graph that depends on the parameter's data is dirtied accordingly. Then when the UI element recooks the interested node, the graph is traversed, cooking the nodes which are dirty.
Since data dependencies can occur depending on the value of parameters, the data dependency graph has the potential to change every time a node is cooked, or a parameter is evaluated. For this reason, extra inputs are cleared as soon as they are traversed upon the dirty propagation. Thus, it is important that if a node depends on another node, it must call OP_Node::addExtraInput() upon every cook.
SOP_MyNode::cookMySop(OP_Context &context)
{
float t = context.getTime();
UT_DMatrix4 xform(1.0); // identity
OBJ_Node * obj_node;
UT_String obj_path;
// ...
evalString(obj_path, "objpath", 0, t);
obj_node = findOBJNode(obj_path);
if (obj_node)
{
obj_node->getLocalToWorldTransform(context, xform); // use its data
addExtraInput(obj_node, OP_INTEREST_DATA); // tell Houdini
}
// ...
}