Rotation and Quaternions in Houdini (Patreon)
Content
A common task for creators in Houdini is distributing objects to points and giving those objects random size and orientation. Many natural phenomena can be recreated this way, from trees in a forest to asteroids in space to rocks on a beach. Houdini provides a comprehensive tool to accomplish this, the Copy to Points node. It's relatively easy to set the size and other simple attributes of objects, but one major stumbling block for beginner and intermediate users is controlling rotation.
Why is this a stumbling block? Well, let's open open the Copy to Points documentation and, in particular, click on the instancing point attributes link. This page gives us a list of all the attributes that Copy to Points looks for when creating instances of an object. We see expected attributes such as pscale and P (position), but the first item on the list is orient and it's a... float4 (quaternion). What's that all about?!
As the name implies, the orient attribute controls the orientation of instanced objects. We're all used to conceptualizing orientation/rotation in terms of 3-dimensional axes. All 3D software interfaces are built around this. It's very intuitive: everything has up/down, left/right and front/back axes. We can create any possible orientation for an object by rotating around X, Y and Z. We usually measure rotation in degrees, with 0 degrees meaning unchanged and plus or minus 360 degrees being one full rotation.
This is great most of the time, but there is a major limitation: a phenomenon known as gimbal lock. If we create complex rotations using 3 axes we can end up in a situation where we're locked out of rotating around a particular axis. For example, if our rotations end up aligning the Y axis with X, then we lose the ability to rotate around Y from that point on as X and Y share the same plane.
This is where quaternions come in. Very broadly, a quaternion is a way of representing 3-dimenional rotation using 4 dimensions: X, Y, Z, and W. The first three represent the axis of the object in question and W is the rotation, in radians (not degrees), around that axis. The math underlying this is complicated, but those interested in further study can start here. The important thing for us to know is that gimbal lock can be avoided by calculating rotation in this way. As such, it's a robust and flexible approach that is often essential to take advantage of.
With the background out of the way, let's go back to Copy to Points and explore how this works in practice in Houdini!
To start, we'll create a very simple setup. Make a box and create a single point using a Point Generate. Connect them to a Copy to Points node:
Let's also randomize the colors of the box surfaces to more clearly see how it's rotating when we get to that step. Add an Attribute Randomize and set the Attribute Class to Primitives.
Next, let's get familiar with attribute creation and see how it influences Copy to Points' behavior. There are many ways to create attributes and assign values in Houdini, but let's use the VEX language since it's very low level and will help us understand exactly what's going on behind the scenes.
First, create a Point Wrangle. (Fun fact: all the different wrangles in the Tab Menu are the exact same node, they just have a different Run Over value selected.) Connect the wrangle to the single point we created.
Now, let's create a pscale attribute. This will be a single floating point number. As the docs above state, pscale is uniform scale: it applies to the X, Y and Z dimensions equally. Let's say we wanted to make our box twice as big:
You can press Ctrl-Enter to activate the code you typed in the wrangle, or simply click elsewhere in the Houdini window to change focus. The box instance picks up pscale from our point and doubles its size.
We can do the same with P, which is the position of our point. P is a vector (X, Y and Z). It already exists, so we can update it without declaring a type, like so:
That moved the box to the coordinates {2, 2, 2}. If we wanted to move only on the X axis we could do:
@P.x = 2;
Or:
@P = {2, 0, 0};
Ok! We now understand the basics of Copy to Points and how we can create and alter attributes on the target points to control our instanced geometry.
Using this knowledge, let's try creating an orient attribute to rotate our cube. We know from the docs that it has 4 dimensions. We can create a parameter interface to more easily experiment with different values. Enter the VEX shown here, then click the create spare parameter icon to give us access to all four values in the GUI.
Let's set the W (rotation) value to 1 and try rotating our box:
Well, this is behaving strangely! The main things you'll notice is that the rotations no longer apply to the original/world axis and that the higher the values become the slower the rotation happens.
What we're running into here is the difficulty of conceptualizing quaternion rotation. It's a 4-dimensional property and we're trying to think about it in 3 dimensions.
So, what are we to do if we want to rotate our box by, say, {-10, 5, 30} degrees? We'd have no hope of randomly guessing what quaternion we'd need to provide for orient and looking up the math and doing it manually doesn't sound very practical.
Thankfully, Houdini provides conversion functions for both quaternions and radians. Let's delete our old wrangle and make a new one with the following code:
v@degrees = chv("Rotation");@orient = eulertoquaternion(radians(@degrees), 0);
We now have a parameter called Rotation with three axis rotation values in degrees. The second line of code simply takes our degrees input, converts to radians, then converts that to a quaternion. The final "0" indicates a rotation order of XYZ. ("1" would be XZY, "2" YXZ, etc.) Let's put in our desired -10, 5, and 30 values:
Our cube is rotated exactly the amount we'd expect. We can switch to the geometry spreadsheet to see the conversion:
That's right, -10, 5, 30 degrees translates to a quaternion of -0.0953524, 0.0194367, 0.261261, 0.96035. Good thing Houdini takes care of that for us!
Now that we've got a working proof of concept, let's expand this to create many instances with random rotation, as we would want to do when creating a forest, asteroid belt or beach.
First, let's replace our single point with random points scattered onto a grid:
Connect this to a Copy to Points and add a Point Wrangle:
In the wrangle we'll create a few variables and generate three random rotation values for each point. The VEX random function creates a value between 0 and 1. In this case we'll want to have possible negative rotation as well for maximum randomness. We can use a fit function to spread out our 0 to 1 values from -1 to 1.
I added a "Clamp" parameter to set the maximum rotation allowed, in degrees. This is just a multiplier on our random values. If we set a clamp of 360, for example, the rotation range will be -360 to 360.
I also created a "Seed" value to mix up the random number generator's seed. Note that I put in an arbitrary additive value to provide different seeds for the Y and Z values. These could be any number. It's just something to make the seeds different so we don't have the same rotation on each axis.
i@seed = chi("Seed");i@clamp = chi("Clamp");
f@val1 = fit(random(@ptnum + @seed), 0, 1, -1, 1) * @clamp;f@val2 = fit(random(@ptnum + @seed + 3817), 0, 1, -1 ,1) * @clamp;f@val3 = fit(random(@ptnum + @seed + 501), 0, 1, -1, 1) * @clamp;
v@rotation = (@val1, @val2, @val3);
@orient = eulertoquaternion(radians(@rotation), 0);
Here we go, completely randomized rotation of each of our instances!
If typing out code isn't your cup of tea, you can also create this same setup visual programming style using VOPs. I find this approach much more cumbersome and confusing, but the option is available.
We're getting a bit far afield here, but here's the final VOP network that reproduces our earlier VEX setup. Leave a comment below if you'd like me to show step by step how I created it!
Conclusion
As is often the case with Houdini, everything we covered here is fairly technical and requires knowledge of math and Houdini's unique quirks. Once you have the setup working it's extremely powerful, flexible and customizable. If you put in the time you can truly accomplish anything with Houdini. Plus, the knowledge is transferrable to any other 3D app. Quaternions are universally used; it's great to know what they are, how they work and when to implement them.