HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SOP Concepts

General Concepts

SOPs networks are responsible for creating and modifying Houdini geometry. In SOPs, geometry is represented as a Detail, as implemented by the GU_Detail class.

SOPs without inputs usually generate their own geometry when they cook, such as SOP_Circle. All other SOPs, when asked to cook, first ask their relevant input SOPs for their cooked geometry, only cooking own detail afterward, thus recursively traversing up the hierarchy tree along all node input connections.

This has the effect of cooking the most remote nodes first, passing the detail data to its children, and gradually approaching the display or render SOP node which initiated the cook request.

Cooking SOPs

The actual modification or creation of the geometry itself is done in SOP_Node::cookMySop() in each node. The context parameter which this function receives passes in the time at which to cook and can be accessed through context.getTime().

HDK provides several useful function to access input geometry in a SOP while cooking:

  • SOP_Node::inputGeo(int index, float t) - This function returns a pointer to the input's geometry of an input specified by index and cooked at time t.
  • SOP_Node::duplicateSource(int index, float t, GU_Detail *other_gdp=0, int clean=1) - This method duplicates one of the input sources specified by the index (0 to num inputs) at time t. It copies the geometry from the input into the gdp of the SOP. If the other_gdp is specified then the gdp associated with the SOP is unaffected and the geometry is placed into other_gdp. If the clean flag is set, the target gdp (either the other_gdp, or the gdp of the SOP) will be cleared before the copy is done.
  • SOP_Node::duplicateChangedSource(int index, float t, int *changed_flag = 0) - This method will duplicate the input geometry into the SOP's gdp. The duplication will only occur if the input has changed since the last time this function was called. This is useful if the SOP's cook method is just changing attributes of the incoming geometry - without adding or removing attributes. For example, the transform SOP simply moves point position of the input around. Thus, the transform doesn't really have to copy the input geometry every time it cooks, it can simply move the points of the duplicated source based on a transform of the input geometry. If the input geometry has changed, or there's an error, the changed_flag will be set to one, otherwise, it will be zero.

Groups in SOPs

A number of SOPs have an option to perform its operation only on a certain group or set of groups in the input geometry. If this is the case, you will want to determine the actual geometry group that is defined within the group string field. The three methods below allow this. If there is more than one group specified the resulting group will be the union of the input groups. If you do not pass in a gdp then it is assumed that the SOP's gdp is used to search for groups.

If the SOP is interested in receiving both primitive and point group information then parseGroups should be used. It will return both a point and primitive group based on the incoming pattern.

If you are just interested in one type then parsePrimitiveGroups or parsePointGroups may be used. They return a pointer to a GA_PrimitiveGroup or a GA_PointGroup respectively. If nothing was found (nil) is returned.

Note
In the process of creating these groups, extra groups are created. If you do not wish them to be passed along they may be destroyed by calling destroyAdhocGroup(group).

Another useful method, SOP_Node::forEachGroupMatchingMask(const char *pattern, GroupOperation operation, void *data, GA_GroupType type, GU_Detail *which_gdp), is related to the three methods defined above. However, instead of returning a single group to act upon, this method will perform an operation on each existing group that matches the specified pattern mask.

GroupOperation is a typedef to a member function that takes the group and void * data pointer as arguments.

typedef void (SOP_Node::*GroupOperation)(GA_Group *, void *);

Node Flags

Depending on what part of Houdini is requesting geometry, a SOP network cooks starting from the node whose display flag is set (if, for example, the geometry is being requested by an OpenGL viewport) or from the node whose render flag is set (if the geometry is being rendered). These can be queried by the OP_Network::getDisplayNodePtr() and OP_Network::getRenderNodePtr() methods.

In addition, setting the bypass flag on a SOP node (using OP_Node::setBypass()) will avoid cooking that node, and will pass its input geometry to the node connected to its output unmodified.

Finally, locking the node (OP_Node::setLock()) will preserve its geometry, and leave it unmodified regardless of whether input geometry changes or not.

Building Custom SOPs

For a detailed explanation on how to build custom SOPs and manipulate their geometry, see Creating Geometry and The Many Ways To Create a SOP.

Retrieving Cooked Data

SOP nodes operate on geometry, so to get access to data within a SOP, one only needs to get access to its stored GU_Detail. This can be accomplished using the following code:

void accessGeometry(SOP_Node* sop_node, fpreal cook_time)
{
// Get our parent.
OP_Node *parent_node = sop_node->getParent();
// Store the cooking status of our parent node.
bool was_cooking = false;
if(parent_node)
{
was_cooking = parent_node->isCookingRender();
parent_node->setCookingRender(true);
}
// Create a context with the time we want the geometry at.
OP_Context context(cook_time);
// Get a handle to the geometry.
GU_DetailHandle gd_handle = sop_node->getCookedGeoHandle(context);
// Check if we have a valid detail handle.
if(!gd_handle.isNull())
{
// Finally, get at the actual GU_Detail.
const GU_Detail* gdp = gd_handle.gdp();
// Do something with our geometry.
// ...
}
// Restore the cooking flag, if we changed it.
if(parent_node)
parent_node->setCookingRender(was_cooking);
}

Transformations

SOP nodes only operate on geometry, their scene transformations derived from their creator OBJ nodes. To determine the world transform from a SOP node, you need to first obtain it's creator as demonstrated below.

bool getWorldTransform(SOP_Node* sop_node, fpreal cook_time, UT_DMatrix4 &xform)
{
OP_Context context(cook_time);
OBJ_Node * creator = CAST_OBJNODE(sop_node->getCreator());
return creator->getLocalToWorldTransform(context, xform);
}