Extending the Houdini Bend Node
Introduction
Sometime last year, I was watching a Cinema4D tutorial and realized that the bend modifier in C4D has some features that Houdini’s bend node was lacking, specifically the ability to rotate the modifier without fiddling with vector controls. So I set out to create an improved version of Houdini’s bend that allows for both the automatic creation of a capture region based on the bounding box of the shape, as well rotation of a capture region using either x,y,z rotation parameters or a viewport gizmo as you would find on the transform node.
This was done by creating a detail wrangle that generates a set of detail attributes that can be referenced in the capture fields on the regular bend node.
The Detail Wrangle
// Get bbox details for automatic capture region
vector bbox_min = getbbox_min(0);
vector bbox_max = getbbox_max(0);
vector bbox_center = getbbox_center(0);
// Calculate bbox width for automatic capture length
float length = bbox_max[chi("axis")] - bbox_min[chi("axis")];
// Vector array holding x, y, and z axis for auto capture
vector ident[] = {{1,0,0},{0,1,0},{0,0,1}};
// Create identity transform matrix to hold capture rotation
matrix rot = ident();
vector angles = chv("Rotate");
vector initial = ident[chi("axis")];
vector center = {0,0,0};
// Enable or disable auto capture
if (chi("toggle"))
{
length = chf("Manual_Length");
initial = chv("capture_direction");
center = normalize(initial) * length * 0.5;
}
angles = radians(angles);
// Rotate rot by the values of the "Rotate" parameter
rotate(rot, angles, 124);
// Make sure the axis of rotation is normalized
initial = normalize(initial);
// Apply the rotation held in rot to the initial axis.
initial *= rot;
// Create origin of capture vector for auto capture
vector capture_origin = bbox_center + (initial * (length * -0.5));
// If auto capture enabled, set capture origin to above
if (chi("toggle"))
{
capture_origin = chv("capture_origin");
if(1 - chi("rot_toggle"))
{
capture_origin = center + (initial * (length * -0.5));
}
}
// Create detail attribs to be referenced in the following bend node.
v@initial = initial;
v@capture_orig = capture_origin;
f@length = length;
I’ll break this down section by section.
The First Section
The first section is nothing but variable declaration and initialization. The first three lines simply create vectors holding information about the bounding box of the input geometry. bbox_min
holds the minimum x, y, and z of the bounding box, and bbox_max
holds the opposite. bbox_center
holds the centroid of the bounding box. These are used for automatic capture region creation.
Depending on which axis the user decides to create their capture on, length will calculate the width of the bounding box along that axis so that the capture length of the capture region is exactly as wide as the bounding box.
The choice of axis is stored in the ident/initial variables. Ident holds an array (essentially a list) of three different axis options, one of each representing x, y, and z. Initial holds the actual selection. I set this up this way so that the user could easily pick which axis they want to use from a dropdown menu.
The rot matrix holds a blank transform matrix that we will add rotations to later to apply to the capture axis.
// Enable or disable auto capture
if (chi("toggle"))
{
length = chf("Manual_Length");
initial = chv("capture_direction");
center = normalize(initial) * length * 0.5;
}
Second Section
The second block allows you to disable auto-capture. It lets you set your own capture direction and capture length, and as such the inside of the if-statement here just sets the length, initial, and center variables according to the manual capture parameters.
angles = radians(angles);
// Rotate rot by the values of the "Rotate" parameter
rotate(rot, angles, 124);
// Make sure the axis of rotation is normalized
initial = normalize(initial);
// Apply the rotation held in rot to the initial axis.
initial *= rot;
Third Section
The third block handles the rotation of the capture axis. It starts by converting the values of the angles variable from degrees to radians since that’s what the rotate() function looks for.
Next, we use the rotate() function to apply the rotations held in the angles variable (which holds the x, y, and z rotations from the “Rotate” parameter) to the rot transform matrix. A transform matrix is what actually allows us to apply a transformation to a shape in space, in this case a rotation. Our angles variable merely held values saying how much we wanted to rotate in x, y, and z, but that by itself isn’t enough to apply a rotation to a shape. We need to create the actual transform. rotate() is taking three arguments, the first being the name of the matrix variable we want to fill with our rotations. The second argument takes our angles vector, the set of rotation values we want to convert to a transform matrix. The final argument is strange to look at, but it’s just saying the order to read the rotation values held in angles. 124 corresponds to xyz (1 means x, 2 means y, and 4 means z). If we switched up the order of the numbers to something else, say, 412, then the x component of angles will be assigned to the z rotation, the y component to the x rotation, and so on. For most uses, just keep this value set to 124.
After running the rotate function, we make sure to normalize the “initial” vector. “Initial” holds the unrotated capture direction. We need to normalize it (i.e. set its length to 1) before rotating it to avoid unpredictable results.
Finally, we apply the transform matrix rot to initial by multiplying initial by rot.
Fourth Section
This section sets the capture origin aka the starting point of the capture vector. I’m currently working on a small update to this tool so I will come back to this post and fill this section out once I’ve finished implementing it.
In short, this is just setting the origin to be the center of the side of the bounding box.
// Create detail attribs to be referenced in the following bend node.
v@initial = initial;
v@capture_orig = capture_origin;
f@length = length;
Fifth Section
The fifth and final section here takes the initial, capture_origin, and length variables we’ve been working out up until this point and writes them out as detail attributes so that they can be referenced down the node chain.
The Bend Node
We’re almost done here. Next what we’re going to do is hook up the three detail attributes to the three capture parameters available on the vanilla Bend SOP. We do this by using the detail expression function, which allows us to read a detail attribute from a given node and takes three arguments.
For example: detail(0, "capture_orig", 1);
The first argument refers to which geometry to read the detail attribute from. Usually we put an integer here that refers to which node input we’re reading the attribute from. Usually we start counting from 0 in Houdini, so the first 0 means we will be reading the detail attribute off of the first node input.
The second argument is the name of the detail attribute we want read.
The final argument is the index of the value we want to read from. If we’re reading floats and integers, this will always be 0, however if we’re reading from a vector attribute, for example, we can only read one value at a time, eg. x, y, or z. This argument works like the first in the sense that we start counting up from zero, i.e. 0 corresponds to x, 1 corresponds to y, and 2 corresponds to z.
All together, the above expression means “read the y component of the detail attribute “capture_orig” from the first node input.
You can see below how each detail attribute created above maps to the default bend node capture parameters.
Cleaning Up
The last thing we do is clean up the attributes we created. We don’t need to add extra cruft to the scene we’re working on, so we’re going to make sure we remove anything we added that is no longer relevant. All we need is an attribute delete node selecting the three detail attributes we created above.
Did I say we were done?
Sorry, there is one more thing we’ll probably want to do, and that is make sure that we have viewport handles that control our new rotation setting. In order to keep this post from getting too huge, I’ll save that for a future post.