HoudiniVex
So you've pushed past hscript, ignored python for now, and seen the awesomeness of vops. You'll also probably start to get curious as to why there's at least 4 different ways to define point attributes, all with various idosyncrasies. Time to sweep all that away with Vex, specifically with the Wrangle Sop.
Wrangle Sops let you write little bits of Vex code, and come with a few UI niceties to make them quick and efficient to use. They come in various flavours (point, prim, detail, attribute), they're all the same thing, just with the 'what do i operate on' menu switched to point/prim/detail.
Best way to get into vex and wrangle nodes is to put down a 40x40 grid, append a point wrangle, and follow along:
Contents
- 1 Create a new attribute
- 2 Get existing attribute values
- 3 Implicit vs explicit attribute type
- 4 Create UI controls
- 5 Customise the UI elements
- 6 Common functions
- 7 Built-in attributes
- 8 Example: Random delete points by threshold
- 9 Example: Wave deformer
- 10 Attributes vs variables
- 11 Example: Rotation
- 12 Wrangles vs hscript vs vops vs everything else
- 13 Mixing vex and vops with snippets
- 14 Get values from other points, other geo
- 15 Blurring attributes with vex and point clouds
- 16 Get attributes from other inputs
- 17 Arrays
- 18 Search an array with find
- 19 Access group names procedurally in vex
- 20 Solver sop and wrangles for simulation
- 21 Solver and wrangle for branching structures
- 22 Get transform of objects with optransform
- 23 optransform to do a motion control style camera
- 24 More on rotation: Orient, Quaternions, Matricies, Offsets, stuff
- 25 ZOMG more rotation: Convert N to Orient with dihedral
- 26 Convert N and Up to Orient with maketransform
- 27 Remove points that don't have normals directly along an axis
- 28 Scaling with vex and matrices
- 29 xyzdist to get info about the closest prim to a position
- 30 Rubiks cube
- 31 Vex vs Vops
- 32 Vex snippet cheat sheet
- 33 Further learning
Create a new attribute
You want to define a new point float attribute 'foo'? Just type
@foo;
Hit ctrl-enter, look in the geometry spreadsheet, there you go, float attribute. The @ tells houdini that you want this to be an attribute, the default type is float.
You want it initialised?
@foo=1;
You want a vector attribute? Prepend 'v' before the '@'. To initialise, use curly braces if its just numbers, or use set() if you're doing something with functions or other attributes:
v@myvector={1,0,3}; v@other=set(0, @P.x, 1);
You can set the attribute type using the appropriate prefix:
// common ones f@foo = 12.234 // float i@foo = 5 // int v@myvector={1,0,3}; // vector // less common ones p@george; // quaternion, a 4 value vector 3@transform; // a 3x3 matrix // there's more, see below. :)
The full list is here: http://www.sidefx.com/docs/houdini15.0/vex/snippets#attributes
Get existing attribute values
You want myvector to get its values from the point position?
v@myvector=@P;
The @ symbol is used to get attributes as well as set them. All the attributes you see in the geometry spreadsheet, you can access them by sticking @ in front of their name.
You want it 1.5 times N?
v@myvector=@N*1.5;
To access an individual attribute of a vector or colour, use @attribute.channel. Eg to get just the y value of each point and divide in half:
@foo=@P.y / 2;
To set it is similar. Eg to set the red channel of each point to the sine of double the x position:
@Cd.r = sin( @P.x * 2 );
Vector attributes like Cd or P let you refer to components in several ways, use whatever is convenient. The component can be r/g/b, x/y/z, [0]/[1]/[2].
// These all do the same thing, set the first component of the vector: @Cd.r = 1; @Cd.x = 1; @Cd[0] = 1; // As are these, all setting the third component: @P.z = 6; @P.b = 6; @P[2] = 6;
Implicit vs explicit attribute type
Note that wrangles implicitly know certain common attribute types (@P, @Cd, @N, @v, @orient), but if you have your own attributes, Houdini will assume its a float unless told otherwise, easy mistake to make.
To ensure Houdini does the right thing, prepend the type. Eg, you've set @mycolour as a vector, and try to use it in a wrangle:
// BAD @Cd = @mycolour; // This will treat it as a float and only read the first value (ie, just the red channel of @mycolour) // GOOD @Cd = v@mycolour; // Explicitly tells the wrangle that its a vector.
I always forget to do this, shout at my wrangles for a bit, then finally remember to stick 'v' in front of my attribute names. No doubt you'll do the same. :)
Create UI controls
Say you have this:
@Cd.r = sin( @P.x * 5 );
But you want to change 5 to another number. You can keep altering the code with different numbers, or replace 5 with ch('scale') :
@Cd.r = sin( @P.x * ch('scale') );
ch() tells Houdini to look for a channel, which is what Houdini calls a UI component, usually a slider. Hit the little plug icon to the right of the text editor, Houdini scans the vex code, realises you've referred to a channel that doesn't exist yet, and makes a channel at the bottom of the wrangle UI named 'scale'. Start sliding it around, you'll see the colours update.
There's several channel types you can use. ch() and chf() both create a float channel. For the others:
// An int channel i@myint = chi('myint'); // A vector channel v@myvector = chv('awesome'); // A ramp channel f@foo = chramp('myramp',@P.x);
The last one is really handy, in one line you get to read one value (@P.x), remap it via the UI ramp widget, and feed that to @foo. I end up using loads of these all through my setups. It assumes the incoming values are in the 0-1 range, you often have to fit the values before feeding to the ramp. The fit function goes fit( value, oldmin, oldmax, newmin, newmax). Eg, remap X between -0.5 and 6, then use a ramp to feed those values into the red channel:
float tmp = fit(@P.x, -0.5,6,0,1); @Cd.r = chramp('myramp',tmp);
But why stop there? Define the start and end points with UI sliders!
float min = ch('min'); float max = ch('max'); float tmp = fit(@P.x, min,max,0,1); @Cd = 0; // lazy way to reset the colour to black before the next step @Cd.r = chramp('myramp',tmp);
Customise the UI elements
The plug button is a convenience function, it just scans for any channel references, and creates the default type, with a default value. Once the channel is made, you can right.click on the wrangle node and choose 'edit parameter interface' (or use the gear menu from the parameter panel), and change float ranges, the default value, the labels, whatever you want.
A handy thing I often do is to convert the default vector channel to a colour channel. I'll make as many vector channels as I need in vex, eg:
v@colour1 = chv('col1'); v@colour2 = chv('col2'); v@colour3 = chv('col3'); v@colour4 = chv('col4');
Then I'll hit the plug button, edit the parameter interface, shift-select all the channels I made, and change the type field to 'color', and to be extra saucy, change 'show color as' to 'hsv sliders'. Most handy.
Common functions
These appear in 99% of my vex wrangles:
- fit() - take a number between 2 values, fit it between 2 other values, usually 0-1. Eg, remap @u from range -5,20 to 0-1: foo = fit(@u, -5, 20, 0, 1);
- rand() - generate a random number between 0 and 1. Usually feed it the point id, so each point gets a random number: foo = rand(@ptnum);
- sin(), cos() - as you'd expect, but in radians.
- radians() - convert a number from degrees to radians: foo = radians(90);
- length() - measure the length of a vector. Eg, measure the distance of a point from the origin: dist = length(@P);
- distance() - measure the distance between two points: dist = distance(@P, v@mypoint);
Built-in attributes
There's a few built in variables you can use, easiest way to see them is to put down a point vop, and look at the global params. Here's the more common ones:
- @ptnum - the point id
- @numpt - total number of points
- @Time - current time, in seconds
- @Frame - current frame
- @primnum - the primitive id
- @numprim - the total number of primitives
Example: Random delete points by threshold
After Matt Ebb showed me this, I use it a million times a day. Scatter some points, put down a wrangle with this:
if ( rand(@ptnum) > ch('threshold') ) { removepoint(0,@ptnum); }
Use the plug button to create the threshold slider, now slide it up between 0 and 1, you'll see points get randomly deleted. What's going on:
- rand(@ptnum) -- each point gets a random number between 0 and 1 based on its id
- > ch('threshold') -- compare that random number to the threshold slider. If it's greater than the threshold...
- removepoint(0,@ptnum) -- ...delete the point. 0 means the first input, ie whatever you've connected into this wrangle, and @ptnum is the reference to the point.
Example: Wave deformer
A classic. Got your 40x40 grid ready? Good.
We'll make concentric sine waves that radiate from the center. That means we'll need to measure the distance of each point to the origin:
@d = length(@P);
We'll feed that to a sin(), and use that to directly set the y-position of each point:
@P.y = sin(@d);
Good, but the waves are too big for our default grid. Lets multiply @d by some factor, that'll give us more waves. Change the previous line to read:
@P.y = sin(@d*4);
Ok, but lets make that driven by a slider instead:
@P.y = sin(@d*ch('scale'));
Hit the plug button, play with the slider (you can push the slider past 1), see the waves move around.
To make the waves animate, add time to the inner term.
@P.y = sin(@d*ch('scale')+@Time);
wait, they're moving towards the center. Lets make it negative time:
@P.y = sin(@d*ch('scale')-@Time);
Or better, control it by a slider so you can drive the speed:
@P.y = sin(@d*ch('scale')+(ch('speed')*@Time));
That's messy, lets tidy up the whole thing. Nice thing about wrangles is you can split across multiple lines for readability:
@d = length(@P); @d *= ch('scale'); @speed = @Time*ch('speed'); @P.y = sin(@d+@speed);
Lets add 2 more things, a control for the maximum height, and a distance based falloff.
Height is easy, whatever the final result is, we'll multiply it by a channel reference; if its 1 it'll be unchanged, 0 will cancel out, other numbers will scale appropriately:
@P.y *= ch('height');
And finally a ramp to control the falloff. A ramp widget expects a variable in the 0-1 range, so the first thing we'll do is take the distance variable, and use fit() to map it between 1 and 0 (note we go 1 0, not 0 1; we want the center to be of maximum height). The start and end points will be controlled by channels:
@falloff = fit(@d, ch('start') , ch('end') , 1,0);
A ramp channel expects a name and a variable, the variable will be remapped through the ramp curve. We'll take the result and directly multiply it against P.y:
@P.y *= chramp('falloff', @falloff);
A minor annoyance here is that I expected the start and end to be in worldspace units, but the 'end' channel was a large number. I realised that's because we'd already multiplied d by the scaling channel to control the number of waves. A quick reshuffle of the code fixed that, here's the end result:
@d = length(@P); @speed = @Time * ch('speed'); @falloff = fit(@d, ch('start'), ch('end'),1,0); @P.y = sin(@d*ch('scale')+@speed); @P.y *= ch('height'); @P.y *= chramp('falloff', @falloff);
Attributes vs variables
The examples given above all use the @ syntax to get and set attributes on points. If you have more than a few wrangles, or if you don't need those attributes outside the wrangle, the geometry spreadsheet starts getting very messy, and all that extra point data takes up memory, especially if you have complex scenes.
Instead, you can create variables that only exist within the wrangle. The syntax is similar to other c-style languages; define the type with a full word, and don't use a prefix on the variable:
float foo; vector bar = {0,0,0}; matrix m; foo = 8; bar.x += foo;
Point attributes and vex variables live in seperate worlds, so you can have one of each with the same name, and they happily co-exist. I tend to avoid this in practice, easy to add an @ where you didn't mean, or remove one where you need it, and break things:
float dist = length(@P); // a local vex variable that only exists within this wrangle @dist = dist; // create a new point attribute, @dist, and assign the local variable 'dist' to it. Worlds collide!
Rewriting the previous example to use variables, and do the formal thing of declaring variables first so its clear to see whats going on:
float d; float speed; float falloff; d = length(@P); speed = @Time * ch('speed'); falloff = fit(d, ch('start'), ch('end'),1,0); @P.y = sin(d*ch('scale')+speed); @P.y *= ch('height'); @P.y *= chramp('falloff', falloff);
Example: Rotation
This chapter was a request from Patreon supporter jon3de, thanks Jonathan!
Rotate a single point with sin and cos
Say you have a single point at the origin, and you want to animate it going round in a circle. Sin() will give you a smooth wave bewteen 0 and 1. Cos() gives the same, but offset by half a wavelength. If you drive x by sin, and y by cos, and make the input to both @Time, the point will move in a circle:
@P.x = sin(@Time); @P.y = cos(@Time);
What if we have a box? Maybe we can add this to the existing @P, and it will rotate?
@P.x += sin(@Time); @P.y += cos(@Time);
Not quite. It translates the box in a circle, but it doesn't rotate.
Rotate geometry with sin and cos
The problem is all the points are getting an identical rotation value. What we need to do is get the offset of each point, but as a rotation offset. Casting your mind back to high school maths, you can use tan(), if given the x and y, will return you an angle. If you think about it, each point is a time offset away from the other points, so lets add this to @Time and see what happens:
float angle = atan(@P.x, @P.y); @P.x += sin(@Time+angle); @P.y += cos(@Time+angle);
It's rotating, but doing a weird scale thing as it rotates. We probably want to set the position rather than add to the existing position:
float angle = atan(@P.x, @P.y); @P.x = sin(@Time+angle); @P.y = cos(@Time+angle);
It's close, but if you compare to the original box, it's scaled slightly different. What's happening is we need to also get the radius (ie, the distance the point is from the origin), and multiply that by the rotation to get the correctly scaled value:
float angle = atan(@P.x, @P.y); float r = length(@P); @P.x = sin(@Time+angle)*r; @P.y = cos(@Time+angle)*r;
Try it on the pig. Uh oh, something funky's going on, his snout has ballooned out and is all strange. The problem is the radius; we're measuring the distance in 3d space, but all we need is the direct radius to the axis of rotation (ie, the z axis). If we measure the length just using @P.x and P.y, it should fix it:
float angle = atan(@P.x, @P.y); float r = length(set(@P.x, @P.y)); @P.x = sin(@Time+angle)*r; @P.y = cos(@Time+angle)*r;
Done! What we've done here is convert our regular coordinates to polar coordinates (angle and r), then back again. Just looking at the polar coordinates by itself is pretty interesting:
float angle = atan(@P.x, @P.y); float r = length(set(@P.x, @P.y)); @P.x = angle; @P.y = r; @P.z = 0;
If you look at that in wireframe, you can see that it looks like a cylindrical uv projection, because that's exactly what it is. The weird stretching is because when doing uv's you'd pick a seam point to split edges on, we haven't bothered to do that here.
Going back to the original rotation, if we take out the time attribute and replace it with a channel, we get a simple rotation tool:
float angle = atan(@P.x, @P.y); float r = length(set(@P.x, @P.y)); float amount = ch('amount'); @P.x = sin(amount+angle)*r; @P.y = cos(amount+angle)*r;
Click the button to get the slider, slide away. You'll note that an amount of '1' rotates by about 30 degrees, which doesn't make sense. Well it does; sin/cos/atan all work in radians. If we're assuming the slider is degrees, you need to convert it to radians too:
float angle = atan(@P.x, @P.y); float r = length(set(@P.x, @P.y)); float amount = radians(ch('amount')); @P.x = sin(amount+angle)*r; @P.y = cos(amount+angle)*r;
Rotate into a twirl or spiral
Now you can experiment with silly things. Rather than rotating all points equally, what if you scaled it based on radius? Ie,points near the origin won't get rotated, points far away get rotated a lot. You get a twirl. If you just add 'r' you get a subtle twirl, so I've added another slider to scale the twirl effect:
float angle = atan(@P.x, @P.y); float r = length(set(@P.x, @P.y)); float amount = radians(ch('amount')); float twirl = r*ch('twirl'); @P.x = sin(amount+angle+twirl)*r; @P.y = cos(amount+angle+twirl)*r;
Rotate with a matrix
That all works, but gets a little tricky if you want to do rotations that aren't perfectly aligned on the x/y/z axis. Another way to do rotation is with a matrix. You're using Houdini, so you're probably aware of matricies, but you're reading this tutorial, so like me you'd probably panic if someone asked you to apply a matrix transformation to some geometry. Well, don't worry, it's easier than it sounds. Here's the steps:
- Create a matrix
- Rotate the matrix
- Apply the matrix to our geometry
The last step is simple, to apply a matrix to a point, you just multiply it.
The other two things are easier to explain in code: So what we need is a matrix, and a way to rotate it. Amazingly, the function is called rotate. Here's how it all works:
// create a matrix matrix3 m = ident(); // rotate the matrix vector axis = {0,0,1}; float angle = radians(ch('amount')); rotate(m, angle, axis); // apply the rotation @P *= m;
So we create an empty matrix (ie, translate/rotate are 0 0 0, scale is 1 1 1). We then define the axis to rotate along, and the amount to rotate. Then we call rotate, which will modify the matrix m directly. Finally we multiply it against @P to apply the rotation per point.
Similar to above, you can do twirl effects by multiplying angle by the distance to to the axis, and scaling it to control the strength of the effect:
vector axis = {0,0,1}; float angle = radians(ch('amount')); angle *= ch('twirl')*length(set(@P.x, @P.y)); matrix3 m = ident(); rotate(m, angle, axis); @P *= m;
Because we work with an axis, that can be anything we want. A random vector, N, some arbitrary other thing, doesn't matter.
A nice bonus of using matrices to do rotation, is that it maps easily into @orient. When using the copy sop or instancing, if they find an @orient attribute, each copied shape will be rotated. @orient is a quaternion, a 4 value vector which is not easily manipulated by humans, but you can convert a matrix to a quaternion easily with quaternion().
Here's what you might do to get random rotations on every point, that you would then feed to a copy sop:
vector axis = vector(rand(@ptnum)); float angle = radians(ch('amount')); matrix3 m = ident(); rotate(m, angle, axis); @orient = quaternion(m);
Rotate prims around an edge
Based on what we know so far, it shouldn't be too hard to rotate primitives around an edge. (We'll assume we have a single triangle or quad for now). You treat the edge as your rotation axis, and rotate as shown earlier. If you remember your vector maths, to get the vector between 2 points, you subtract them. So assuming the edge we want is between point 0 and point 1, we could do this:
vector p0 = point(0,'P',0); vector p1 = point(0,'P',1); vector axis = p0-p1;
And use it to create a quaternion orient as we've seen already:
float angle = ch('angle'); matrix3 rotm = ident(); rotate(rotm, angle, axis); vector4 orient = quaternion(rotm);
Now if you were to apply this directly to the geometry, it would rotate, but it would rotate centered on the origin, which is wrong. The rotation has to be centered at the midpoint of the edge. Again casting your mind back to high school, you can get the midpoint of 2 points by adding, then divide by 2:
// get midpoint of edge vector pivot = (p0+p1)/2;
From here, there's probably a few ways in vex to make this do what we need, in my case I use instance(). This is a vex call that constructs a transformation matrix similar to what a copy sop creates per copy. This means you can feed it position, orient, pivot etc, and it does what we want. As such, we'll construct an orient quaternion first, use that as one of the inputs to instance() along with our pivot, and then use this to move our point:
// create a transform matrix using orient and pivot, and other default values vector N = 0; // not important cos we're using orient vector scale = 1; // ie, leave the scale as-is vector postrotation = 0; // also not important, we don't need extra rotation on our orient matrix m = instance(pivot, N, scale, postrotation, orient, pivot); // move the point! @P *= m;
Simple! Well, if we have many prims in a shape, then there's a few more book-keeping things to do. First, the prims will have to be split apart, so use a fuse sop in unique mode to do this. Then, to get the 0 and 1 point per prim, you can use primpoints(), which will return an array of the points in a primitive, in order. You can the just grab the 0 and 1 entries of that array:
int points[] = primpoints(0,@primnum); // list of points in prim // get @P of first and second point vector p0 = point(0,'P',points[0]); vector p1 = point(0,'P',points[1]);
Here's the full thing, for completeness:
int points[] = primpoints(0,@primnum); // list of points in prim // get @P of first and second point vector p0 = point(0,'P',points[0]); vector p1 = point(0,'P',points[1]); vector axis = p0-p1; float angle = ch('angle'); matrix3 rotm = ident(); rotate(rotm, angle, axis); vector4 orient = quaternion(rotm); // get midpoint of edge vector pivot = (p0+p1)/2; // create a transform matrix using orient and pivot, and other default values vector N = 0; // not important cos we're using orient vector scale = 1; // ie, leave the scale as-is vector postrotation = 0; // also not important, we don't need extra rotation on our orient matrix m = instance(pivot, N, scale, postrotation, orient, pivot); // move the point! @P *= m;
Rotate prims around an edge, alternate version
Jesse reminded me of another method; I mentioned earlier that to use the rotate matrix by itself won't work because it applied rotation around the origin. A simple way to fix this is to just move those primitives to the origin, do the rotation, then move it back. Looks like this:
int points[] = primpoints(0,@primnum); // list of points in prim // get @P of first and second point vector p0 = point(0,'P',points[0]); vector p1 = point(0,'P',points[1]); vector axis = p0-p1; float angle = ch('angle'); matrix3 rotm = ident(); rotate(rotm, angle, axis); // get midpoint of edge vector pivot = (p0+p1)/2; // move point to origin, rotate, move back @P -= pivot; @P *= rotm; @P += pivot;
Wrangles vs hscript vs vops vs everything else
Vex is as a general rule always going to be faster than using hscript, and will scale much better.
Vops are great, but often its the simple things that you can do in 2 lines in vex, that would take 7 or 8 nodes in vops. That said, certain operations are easier in vops, like using noise or loading other patterns, or building stuff when you don't exactly know what you're aiming for yet. After you're done, you can always re-write your setup in a wrangle if you think its better.
Re other sops, a wrangle can do the work of several other sops, often more efficiently, with the ability to make UI elements easily. Some examples:
- point sop - a lot of older tuts use point sops. Don't. Just say no.
- attribcreate sop - faster and easier in a wrangle. The only reason you might wanna use an attribcreate is to get local variables, but you'd only use them with a point sop, which we already agreed suck.
- vop bind and vop bind export - getting and setting point attributes is way easer with @ shortcuts than in vops.
- promote parameters in vops - again, ch('foo') vs r.click, bind, fiddle with names, realise its wrong, get parameters in a semi hung state, ugh
- channel referencing in hscript - the auto binding with ch() is the bees knees. That little plug button is the best thing ever.
That's being a little trite, all sops and vops are useful at some point, but the speed and elegance of wrangles is hard to beat.
Mixing vex and vops with snippets
Often I'll start something in vops, then realise part of my network would be much neater as a few lines of vex. When that happens, I'll drop in a 'snippet' vop, and I get something that looks very similar to a wrangle node. I'll then code whatever I need to code, and it happily co-exists with the vops around it.
Whatever attributes you wire into the snippet are now visible to the vex editor, without using @ prefixes. So if you wire in P and a user defined attribute 'myvector', you can just type the code
P = P * myvector;
Whatever you connect as an input to the snippet also becomes an output. It doesn't magically feed the result to downstream sops, it behaves like a vop node, so in the above example, you'd need to manually connect the outP output to P (or whatever else you need).
The 'inline code' vop is similar, but requires a little more handholding for the ins and outs. Because my vex stuff within vops is never very complicated, a snippet is fine for my needs, but here's my earlier 'inline code' notes just in case:
By default there are no outputs on an inline code vop, at the bottom of the parameter interface you go to the first disabled output, set its type (eg vector), and give it a name (eg, P). You then see a 'P' appear on the right side of the node, ready for you to connect to other things. Cos I'm an idiot, I tend to name these outputs explicitly, eg 'outP' rather than 'P'.
To assign values to that output, use a $ prefix. Eg, here I want to drive P.y by Cd.r. I've defined the output as outP on the UI.
P.y=Cd.r; $outP=P;
Get values from other points, other geo
Groundwork for the next step (and probably mentioned above in passing).
If you want the colour of the current point, you use @Cd. But what if you want the colour of point 5?
float otherCd = point(0, "Cd", 5);
Ie, the point() function lets you query attributes of other points (the first 0 means 'the first input to this point wrangle'). If you had another mesh connected to the 2nd input of the point wrangle, and you wanted to know the colour of point 5 in that mesh, change the geo number:
float otherCd = point(1, "Cd", 5);
Often this is used for transferring or blending attributes between similar meshes. Eg, you've got 2 meshes, and want to set the colour to be the addition of both. The point numbering will be identical, therefore you can use the same ptnum to get the 2nd mesh, and do what you need:
vector otherCd = point(1, "Cd", @ptnum); @Cd += otherCd;
Blurring attributes with vex and point clouds
Download scene: File:pc_blur.hipnc
Another thing that I learned but didn't really understand, forgot, learned again, forgot, then feared for a while, and now finally have an understanding of (I think).
You have Cd on a grid, you want to blur it. One way is to iterate through each point, look at its neighbours, add up the result, and divide by the number of neighbours. Because it's a grid, and the point numbering is consistent, you could do something like
int width = 50; // say the grid is 50 points wide vector left = point(0,'Cd', @ptnum-1); vector right = point(0,'Cd', @ptnum+1); vector top = point(0,'Cd', @ptnum+50); vector bottom = point(0,'Cd', @ptnum-50); @Cd = left + right + top + bottom; @Cd /= 4.0;
Works, but clunky, and it only works with a grid. Using the neighbour() and neighbourcount() functions is more generic (borrowed from odforce post):
int neighbours = neighbourcount(0, @ptnum); vector totalCd = 0; for (int i = 0; i < neighbours; i++) { int neighPtnum = neighbour(0, @ptnum, i); totalCd += point(0, "Cd", neighPtnum); } @Cd = totalCd/neighbours;
Groovy, nice and generic now.
However, there's an even shorter cleaner way, using point clouds:
int mypc = pcopen(0, 'P', @P, 1, 8); @Cd = pcfilter(mypc, 'Cd');
pcopen() is designed to open point cloud (.pc) files on disk, but by using the 0 reference, the wrangle will treat the incoming geo as a point cloud. You need to tell it how it will find values in the cloud, 99.999% of the time you'll look up positions in the cloud ('P') using the location of the current point (@P). It searches to a maximum distance (1), and returns a maximum number of points (8). Pcopen() returns a handle so you can refer to the point cloud later, here I store that in variable 'mypc'.
pcfilter() purpose is to look at the results from a point cloud, and return the filtered (blurred, averaged, call it what you want) result of the attribute you specify. Here, we tell it to use the cloud we just opened, and look up the Cd attribute. Because pcopen returned the closest 8 Cd results near the current point, pcfilter will return the average of those 8 points. In other words, we've just blurred Cd by its surrounding 8 points, essentially a 1 pixel blur.
Adding some channels, we can control the blur with a slider:
float maxdist = ch('maxdist'); int blur = chi('blur'); int pc = pcopen(0, 'P', @P, maxdist, blur); @Cd = pcfilter(pc, 'Cd');
maxdist doesn't really affect the result, its more for speed; if you know the search never has to be greater than a certain distance, these point cloud functions can run a lot faster. Also note that pcfilter() does a more clever job than the neighbour based method earlier, it knows to give far away points less importance than closer points.
Another use for this is vex based smoothing function, try this on a box in polygon mesh mode with lots of axis divisions . The only difference here is rather than blurring Cd, we're blurring P:
float maxdist = ch('maxdist'); int blur = chi('blur'); int pc = pcopen(0, 'P', @P, maxdist, blur); @P = pcfilter(pc, 'P');
There's a variety of functions to manipulate point clouds, most are prefixed with 'pc', so are handily grouped together in the vex docs: http://www.sidefx.com/docs/houdini15.0/vex/functions/ (skip down to the 'point clouds and 3d images' section)
Curious aside 1: The grid based way of doing this is basically what convolution filters do in image processing. A specific version of this that looks like an edge detect filter is called a laplacian filter, a word which I've seen come up now and then, but didn't really understand. Now I sorta do. More info: http://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm
Curious aside 2: If you've not used point clouds before, you might wonder why they exist, how are they better than doing point() or neighbour() lookups etc. They're just another way of storing 3d data, but by stripping out edges and polygons, some shortcuts can be taken to make certain operations be much faster. Getting the filtered results of sub-sections of geometry is one such operation, others are quickly iterating through near points, or approximating chunks of far away points with a simple average. The past 10 years of rendering has relied heavily on point clouds for effects like ambient occlusion and subsurface scattering, but have fallen out favour as brute force path tracing has become viable. Still, can be a handy trick for geometry processing in Houdini.
Get attributes from other inputs
As mentioned earlier, you have 2 similar meshes feeding into the one wrangle, and you want to access the matching P of the 2nd mesh:
vector otherP = point(1,"P", @ptnum); // do something with it
An alternative method is to prefix the attribute you want with @opinput1_ . This implicitly uses the same ptnum, saving you a call to point() :
vector otherP = @opinput1_P;
The same rules apply as for normal @ syntax, it will guess the type of known attributes (P, N, v etc), anything else will default to float unless you tell it otherwise:
float foo = @opinput1_myfloat; // fine, default is float float bar = f@opinput1_myfloat; // explicit type float myvector = @opinput1_thevector; // bug, as thevector will be returned as float float workingvector = v@opinput1_thevector; // better
Of course to access any other geo connected connected to the 3rd or 4th slot, increase the number (you might've guessed by now that 0 is the first input):
float two = @opinput1_myfloat; float three = @opinput2_myfloat; float four = @opinput3_myfloat;
I wish they'd picked a shorter string that opinputN_, as in practice somtimes it almost feels like point() is faster, but hey, nice to have options. :)
Arrays
Standard vex arrays are like most c-style languages:
int myarray[] = {1,2,3,4}; int foo = myarray[2];
You can create array attributes too by inserting '[]' between the type declaration and the @:
i[]@things = {1,2,3,4}; int foo = i[]@things[3];
You can use the opintputN syntax from before too, looks messy but it works. Eg, reading a float array from the 2nd input and storing it in a local vex variable array:
float otherthings[] = f[]@opinput1_thearray; int foo = otherthings[4];
You can append the array index you want and get even more hard to read:
int foo = f[]@opinput1_thearray[4];
Search an array with find
Super nice tip from one man Houdini army Tomas Slancik.
Say you want to set an int attib to be 1 on Frame 1, 25, 225,35. Ie, there's no pattern here, just specific frames.
My original naive approach is to throw it all in a if statement with a bunch of ORs (in Vex, like most C-derived languages, OR is 2 vertical pipes, ||):
int myint = 0; if (@Frame == 1 || @Frame == 25 || @Frame == 225 || @Frame == 35) { myint=1; }
Tomas suggested a much cleaner way using an array, and the find() function:
int myint = 0; int frames[] = {1, 25 , 66, 225 ,35, 666, 999, 123, 534}; if (find(frames, @Frame)>=0) { myint=1; }
Nice!
Access group names procedurally in vex
Download scene: File:group_random_delete_vex.hipnc
Chuffed with this one. A friend (hey Jonno) had a big complex object with lots of primitive groups, and wanted to randomly delete primitives by those groups. My first reaction was 'bah, easy', but on closer inspection it wasn't that clear cut. Yes you can use the group dropdown at the top of the blast or delete node, and even use wildcards and simple expressions to select things, but unless there's a text processing style trick you can use (eg, all groups are named 'group_123'), this isn't any good.
What was needed was to get an array of all the groups, and then you could refer to them by index (so rather than saying 'left_foot', you could say 'grouplist[2]'). Bit of digging, found that the group names are stored in a detail intrinsic attribute. Houdini hides a few 'bonus' attributes around, like a full transform matrix for prims, or their surface area, they can be found from the geo spreadsheet dropdown. The geo groups are stored at the detail level. Armed with this, we can extract the groups into a string array, and do what we want:
s[]@groups = detailintrinsic(0, 'primitivegroups'); i[]@prims = expandprimgroup(0, s[]@groups[1]);
The first line gets the array of groups, and stores it in another attribute 'groups', just so we can look at in in the geo spreadsheet and see it worked.
The second line reads the 2nd group ( groups[1] ), gets a list of all the primnums in that group, and stores that in an attribute, again to see it working.
Here's taking it further, using the above logic, and deleting a random group every frame:
string groups[] = detailintrinsic(0, 'primitivegroups'); int rand = int(rand(@Frame)*len(groups)); int prims[] = expandprimgroup(0, groups[rand]); // delete the prims int p; foreach (p; prims) { removeprim(0,p,1); }
It's worth pointing out that if you're defining the groups yourself, you're better off using an attribute to identify the pieces, and use that to do your random whatevers. Eg, assign every prim a @piece attribute, and do deletions based on that:
i@piece = @ptnum%3; if (@piece==1) { removeprim(0,@primnum,1); }
Doesn't work though if you have overlapping groups though, in which case its fancy shmantz intrinsic vex magic for you.
Solver sop and wrangles for simulation
Download scene: File:game_of_life_solver.hip
A whole other level of fun opens up when you drop a wrangle into a solver. As stated a few times elsewhere, a solver sop gives you access to the previous frame, meaning you put a wrangle inside and wire it up to the 'prev_frame' node, you can do simulation and accumulation effects. In this example I do a loose approximation of Conways 'game of life', simple cellular automata. I half remembered the logic from many years ago, but it's something like this:
- look for active neighbours north/south/east/west, and my own active state.
- if there's 1 active neighbour, and I'm active, then i'm now dead.
- If there's 2 active neighbours, and i'm dead, then I'm now active.
- if there's 4 active neighbours, I'm dead.
Setup some random pattern as a starting point, and running the solver, you'll get a changed pattern. This is then fed into the next frame, which changes the pattern, which is fed into the next frame etc.
The wrangle within the solver is very simple:
int left = prim(0,'Cd',@primnum-1); int right = prim(0,'Cd',@primnum+1); int top = prim(0,'Cd',@primnum+30); int bottom = prim(0,'Cd',@primnum-30); int total = left+right+top+bottom; if (total==1 && @Cd ==1 ){ @Cd =0 ; } if (total==2 && @Cd ==0 ){ @Cd =1 ; } if (total==4) { @Cd =0; }
Solver and wrangle for branching structures
Download scene: File:vex_brancher.hipnc
I watched this great video by Simon Holmedal about his work for Nike, lots of interesting branching structures, organic growth, fractals, really inspiring stuff.
So inspiring in fact, I had to try and replicate one of his experiments. The vex code at the core of it is pretty simple. I tag a starting point (or points) with @active=1, then in the solver sop run this code:
if (@active ==0) { float maxdist = ch('maxdist'); int maxpoints = 5; int pts[] = nearpoints(0,@P, maxdist, maxpoints); int pt ; foreach (pt;pts) { if (point(0,'active',pt)==1) { @active=1; int prim = addprim(0,'polyline'); addvertex(0,prim,@ptnum); addvertex(0,prim,pt); @age = @Frame; return; } } }
Seeing as I've posted it here on the vex tutorial page, I'll explain it line by line:
if (@active ==0) {
Only run the following code on non-active points, ie, on points that haven't been connected into lines yet.
float maxdist = ch('maxdist'); int maxpoints = 5; int pts[] = nearpoints(0,@P, maxdist, maxpoints); int pt ;
Setup some variables to feed the nearpoints() function. Give nearpoints() a position, it returns an array of the closest points (an array of their id's, or @ptnum's if you're using the vex terminology). You can also tell it the total number of points to return, and a maximum distance to search (which here I set using a channel slider). I also create a pt variable to use in the following loop.
foreach (pt;pts) {
This is a handy way to call a loop in vex; rather than the usual C way of incrementing a counter, you can ask vex to just loop over the elements in an array. Here, each time the loop runs, pt will be set to the next point found by the nearpoints() function earlier.
if (point(0,'active',pt)==1) {
Get the @active attribute of the point we're testing, if its active, run the next bit of code.
@active=1;
Because we've found an active point, we'll mark myself active. Because this vex will only run on non-active points (remember the first line of this wrangle), this stops all points from constantly trying to find connections.
int prim = addprim(0,'polyline'); addvertex(0,prim,@ptnum); addvertex(0,prim,pt);
Create a line from the found point to myself. This is covered elsewhere in more detail, but the idea is that you create an empty primitive in line mode, then add 2 verticies to that primitive, forming a line.
@age = @Frame;
Store the current frame as an @age attribute. We can use this later to colour the structure by age; points near the starting point(s) will have a low age, points far away will have a high age.
return;
Stops the foreach loop running as soon as its found an active point. If this line is commented out, it will try and connect itself to any of the nearest active points. This can create edges that link to existing edges, which loses the branching look. It's a cool look in itself (comment out the line and see), but I wanted branching.
There's a slightly fancier version in the hip that also tries to only chose points that follow the flow lines of some curl noise, which generates interesting swirly branches. It also works fine with points scattered inside a volume, making cool 3d structures.
Get transform of objects with optransform
Download scene: File:optransform_pig.hipnc
Copy positions from points to other points is easy enough, but if you want to read the transform of, say, a camera, the naive way might be to channel reference all the translate/rotate/scale values from the camera parameter pane.
A better way is with the optransform vex function. It returns the matrix for a given object, which you can then multiply your points by, or use cracktransform() to just extract the information you need.
Syntax is pretty simple:
matrix m = optransform('/obj/cam1'); @P *=m;
optransform to do a motion control style camera
Download scene: File:moco_example_scene.hip
Question Gary Jaeger asked on the Sidefx list, its something I've had at the back of my mind for years, finally got a chance to try it out.
We have an animated cube and a static camera, and we want to invert this, ie, have a static cube and animated camera. Think back to classic motion control shots of star wars, the spaceships models would be mostly static, and the camera would zoom around them to make it appear that they were flying.
If this were 2 pieces of geometry this is easy; read the matrix from one, invert it, apply to the other in vex. Unfortunately because we have a camera, and camera's can't be manipulated in vex, we have to find another way around.
Instead, we can use a rivet. In its simplest form, its like a copy sop with a single point; give it a path to a point, and you can then parent objects to the rivet, they'll stick to the point. As such I've made a geo object with a single point, and in a wrangle read the animated cube transform, invert the motion, apply it to the point:
matrix m = optransform('/obj/moving_cube'); @P *= invert(m);
But what about rotation? Looking at the rivet parameters, it supports rotation, but only via @N and @up. At this point I realised I knew how to go from @N and @up to a matrix or quaternion, but not the other way. After sleeping on it, realised its easier than I expected, and added this to my wrangle:
matrix m = optransform('/obj/moving_cube'); @P *= invert(m); matrix3 m_rot = matrix3(m); @N = {0,0,1}*invert(m_rot); @up = {1,0,0}* invert(m_rot);
When using @N and @up for rotation, @N points along the z-axis, @up points along the y-axis. As such, to recreate @N and @up from a matrix, just take a z vector {0,0,1} and multiply it by the matrix, and do the same for a y-vector {0,1,0}.
This didn't work for me at first, after sleeping on it I realised it was because I was using the full 4x4 (rotate+position+shear) rather than the correct 3x3 (just rotation). Explicitly casting to a 3x3 matrix fixed it.
In theory you could also use a cracktransform() to get the euler rotation values, and plug those onto the camera using hscript, but I find I'm now actively avoiding hscript more and more. This seems cleaner to me. That said, I'm sure someone will have an even cleaner solution than this...
More on rotation: Orient, Quaternions, Matricies, Offsets, stuff
This stuff has taken a while to sink into my brain, so don't worry if it doesn't make sense at first. A lot of this can be achieved via other means and a few more steps, but its good to have an elegant target to aim for.
So first, create an @orient that smoothly rotates. To recap, Houdini offers several ways to deal with rotation, ranked in order of precedence, with @orient being the highest. Orient is a quaternion, which is a 4 value vector.
Previously we've used a matrix for this (a 3x3 matrix to be exact), rotated it, and generated orient via the quaternion() function:
float angle = @Time; vector axis = rand(@ptnum); matrix3 m = ident(); rotate(m, angle, axis); @orient = quaternion(m);
Turns out I've been dong way too much work. A 3x3 matrix and a quaternion are mostly interchangeable, so it makes sense that some of the functions to create and manipulate them are also interchangeable. You can create a quaternion directly from an angle and axis:
float angle = @Time; vector axis = rand(@ptnum); @orient = quaternion(angle, axis);
Nice and clean. But there's a subtle problem with this, namely the random axis. Turns out that rand() vectors aren't as random as you'd expect, they tend to all aim along positive values along the x y and z axis.
If we want truly random vectors, Houdini provides several ways to generate this over a sphere, a cone, a hemisphere etc. It's a handful to type, but autocomplete is your friend, and the results are much better:
vector axis = sample_direction_uniform(rand(@ptnum));
Ie, you give it a random value between 0 and 1, it will return a random vector on a unit sphere. Neat. Here it is displayed with a visualizer in axes mode:
If we create a 1 unit tall box and a pig, and copy sop it onto the point, we get this:
What if we want to offset the pig to the end of the box though? (Yes ok, you could transform the pig first, then copy it on, but pretend we couldn't.) A way to do this is to take a vector that's 1 unit on the y axis (which was the original orientation and height of the box), rotate it to match @orient, then take a point and offset it by this modified vector.
That's what the qrotate function does; rotates a vector by a quaternion. In a second wrangle I take the same point, and shift it (the result needs to be normalized):
vector pos = qrotate(@orient, {0,1,0} ); @P += normalize(pos);
Now if we use the second point and copy the pig onto it, and leave the box on the first point, then merge the result, we get this:
Contrived example, sure, but the ability to shift a point in the orient space of another is pretty cool. Here's an even sillier example, where I setup a variable that cycles between 0 and 1, and mulitply that by the rotated vector, which has the effect of sliding the geo (circles in this case) up and down the boxes. I use the same variable to colour the circles and scale them because, well, why not? To do this without qrotate would probably involve stamps and other ugliness.
Incidentally, here's another way to rotate a vector by a quaternion. qconvert converts a quaternion back to a 3x3 matrix, then mutiply it with the vector as we've done before.
vector pos = {0,1,0}; matrix m = qconvert(@orient); pos *= m; @P += pos;
Download scene: File:offset_along_orient.hipnc
Ps: Neil Dickson offered some handy tips re random rotation; I had originally used sample_sphere_uniform, he corrected me thusly:
Note that sample_sphere_uniform will sample uniformly from vectors *inside* the unit sphere, whereas sample_direction_uniform will sample uniformly from vectors on the surface of a unit sphere, i.e. unit vectors a.k.a. direction vectors. The code that figures out the transformation to do should normalize the vectors, so it should be okay either way, but sample_direction_uniform is a little faster. You can also get uniform random orientation quaternions using sample_orientation_uniform, or uniform within some angle of a base orientation using sample_orientation_cone.
Thanks Neil!
ZOMG more rotation: Convert N to Orient with dihedral
You have some geo that you want to copy onto points, matching @N, but save this as @orient so it won't be ambiguous.
The copy sop assumes your geo points down the z-axis, so you need to work out how to rotate {0,0,1} onto @N.
Dihedral() does exactly this, it gives you a matrix that rotates one vector onto another.
We know by now that a rotation matrix and a quaternion are interchangeable, so all we do with that matrix is convert it to our orient quaternion:
matrix3 m = dihedral( {0,0,1} , @N); @orient = quaternion(m);
Convert N and Up to Orient with maketransform
Swapping between @N+@up and @orient, looks identical, hooray!
Download scene: File:convert_n_and_up_to_orient.hipnc
'Hang on' you might say, 'how is the previous solution not ambiguous?'. And you're right, without calling an up vector, we've not really determine a stable rotation at all. This has been bugging me for a few months, and as it typical for me, I was too proud to just ask people who would know.
For a while I thought the answer was the lookat() function, which according to the docs is
lookat(vector from, vector to, vector up);
So knowing that a copy sop aims the z axis down @N, I figured this would work:
lookat( {0,0,1}, @N, @up);
The result was... not right. Sort of stable, sort of wrong, and trying every permutation of vectors didn't work. Boo.
Today I read a forum post about makebasis, which seemed to be right thing, but no. By accident I started typing 'make' into the houdini help, and it popped up maketransform. Hey presto, that's the function:
matrix3 m = maketransform(@N,@up); @orient = quaternion(m);
The ultimate test is to setup some random @N and @up, rotate @up, then in a second wrangle convert to @orient. Enabling/disabling the second wrangle should cause no visual change, which you can see in the gif at the top of this chapter.
Remove points that don't have normals directly along an axis
Brilliant tip from the brilliant Matt Ebb. I have scattered points over cubes, they should have normals that point along one of X, -X, Y, -Y, Z, -Z. This being real life, some points are slight rotated, which ruins my end result.
I needed a way to identify points that didn't have normals directly along the X/Y/Z axis, and delete them. Matt came up with this gem:
if (max(abs(normalize(@N))) != 1) { removepoint(0,@ptnum); }
What's going on here?
- normalize(@N) - takes @N and makes it be of length 1.
- abs() - makes the values positive, so {1,0,0} would be unchanged, but {0,-1,0} would become {0,1,0}
- max() - given a vector, returns the biggest component of that vector.
So, what does that mean? Let's take 3 example vectors and run it through that process:
- {1,0,0} - if we normalize it, it returns the same vector. Abs it, also the same. Getting the max of that will return 1. We test if its equal to 1, it is, so the point remains.
- {0,-1,0} - normalize it, again its the same. Abs it, it becomes {0,1,0}. Max returns 1, so this point also stays.
- {1,-1,0} - normalise it, returns { 0.707, -0.707,0}. Abs it and its now { 0.707, 0.707,0}. The max of that is 0.707, so it fails the test.
Simple, clever. Cheers Matt!
Scaling with vex and matrices
Download scene: File:vex_matrix_scale.hipnc
Question from the houdini dischord channel you should all join; how to scale things using a matrix in vex. There's lots of ways of course, here's probably the simplest way if you have polygons:
matrix3 m = ident(); scale(m,chv('scale')); @P *= m;
First we define an empty matrix with ident(). Next we scale that matrix using a vector, here I'm being lazy and getting that vector from the UI, so chv('scale') reads the vector from the channel, and scale(m, myvector) directly scales the matrix in place, note that there's no return type on that call. Finally to apply this, you just multiply the points by the matrix as discussed earlier.
If you have primtives though, like a primitive sphere or primitive cone, remember that there's only a single point at the center of the primtive, so scaling that will have no effect. Instead primitives have their own hidden matrix, call the intrinsic transform. To set this you use the setprimitiveintrinsic function, the setup and scaling of the matrix is the same.
matrix3 m = ident(); scale(m,chv('scale')); setprimintrinsic(0,'transform',0,m);
Strictly speaking this is a primitive operation, so we really should change to a primitive wrangle, but in this case the point and the prim share the same id, so it doesn't really matter. The function call needs to know which geometry input to process, the name of the intrinsic attribute, which prim, and finally the value to set.
So
setpriminstrinsic ( input, instrinsic_name, which_prim, what_value);
becomes
setpriminstrinsic ( 0, 'transform', 0, m);
xyzdist to get info about the closest prim to a position
Download scene: File:xyzdist_example.hipnc
If you've used a ray sop, this is a similar thing. At its simplest, give it a position, it will tell you the distance to the closest primitive. More usefully, it can also tell you the primnum of that primitive, and the closest uv on that prim to your position. With this info you can then work out the position on the prim.
Like the matrix rotate() function, primnum and uv are set via pointers, so you create the variables you want first, then call them within the function:
float distance; int myprim; vector myuv; distance = xyzdist(1,@P, myprim, myuv);
To get the exact position of that uv on that prim, and the colour of the prim:
vector mypos = primuv(1,'P', myprim ,myuv); @Cd = prim(1,'Cd', myprim);
The above contrived gif shows all this in action. A point is orbiting the multicolour pig. xyzdist finds the distance, primnum, uv of the closest prim, a point is made at that location, its colour is set from the closest prim, a red line is drawn from the orbiting point to the new point, and finally a wireframe sphere is generated on the new point, its colour also matches the prim its currently on, and its radius is set from the distance returned from xyzdist, so it alwasy just touches the orbiting point.
Rubiks cube
Download scene: File:rubiks_cube.hipnc
As per usual I worked this out a while ago, forgot, took a few stabs to remember how I did it, fairly sure this method is cleaner than the original. Much thanks to Aeoll from the Discord forums for helping with the last bit I was missing.
This is a 3x3 box of points, with cubes copy sop'd to each point. This setup requires 2 things to start with, a random x/y/z axis to turn, and to select a slice on that axis to turn.
There's probably lots of very clever super compact ways to choose a random axis, I've gone something fairly lowbrow. Assume we have an integer 'randaxis' that can be 0 1 or 2, and a vector 'axis':
if (randaxis==0) axis = {1,0,0}; if (randaxis==1) axis = {0,1,0}; if (randaxis==2) axis = {0,0,1};
To generate randaxis, lets say we want it to randomly generate 0 1 or 2 every second. To do that we'll obviously need to use @Time as an input. To make time be stepped we can use floor(@Time). Feed that to rand to generate a random number between 0 and 1, multiply by 3 to get it between 0 and 2.999, and finally convert to an int to make it be only 0 1 or 2:
int randaxis = int(rand(floor(@Time))*3);
So lets say we've randomly chosen the x-axis, {1,0,0}. Now we need to choose the left, middle or right slice to rotate. The cube is 1 unit wide, meaning that we can say for certain the left points have @P.x as -0.5, the middle as 0, right as 0.5. The same will be true for @P.y when choosing a slice on the y axis, and @P.z on the z axis. As such, we'll setup a float 'slice' to be randomly -0.5, 0, or 0.5 and use it later (randslice is generated in a similar way to randaxis):
if (randslice==0) slice = -0.5; if (randslice==1) slice = 0; if (randslice==2) slice = 0.5;
Now we have an axis and a slice, how do we use this? We need to construct a test for every point against axis and slice, and if they pass, do stuff. Here's the test I use:
if (sum(@P*axis)==slice)
Sum(vector) returns the total of the vector components, so sum( {1,2,3} ) returns 6 (ie, 1+2+3). For the top front right corner of our cube, ie {0.5,0.5,0.5} we'd get 1.5.
But here we first multiply @P by 'axis'. Lets see what that does to a few different points. If axis = {1,0,0}, ie, the x axis:
- {0.5,0.5,0.5} * {1,0,0} = {0.5,0,0}
- {-0.5,0,-0.5} * {1,0,0} = (-0.5,0,0}
- {0,0,0} * {1,0,0} = {0,0,0}
Ie, it has the effect of cancelling the y and components. Running sum() on those results above returns 0.5, -0.5, 0. In other words, we've extracted the x component from the vector as a float.
The next part of the test checks compares that result to the 'slice' var. Lets say slice is 0.5, how do those results compare?
- 0.5 == 0.5, pass
- -0.5 != 0.5, fail
- 0 != 0.5, fail
So here we've said only points that have their @P.x match the slice we're interested in (0.5) pass, all other points fail.
Why this trickery? Well, we could do a big multi-level if statement that says if x-axis do this, else if y-axis do this, else if z-axis do this, then if slice1 do this etc... That gets very unwieldy and prone to errors. Here we've exploited some properties of how vectors multiply together to get a much cleaner test.
From here it's plain sailing. We know the axis to rotate around, so we do the usual matrix rotate dance, and update @P and @orient:
matrix3 m = ident(); float angle = $PI/2*@Time%1; rotate(m, angle, axis); @P *= m; @orient = quaternion(m);
If we didn't have colours on the cubes, or motion blur, we could leave this in a normal wrangle and call it done. As soon as you add colours, you get the result on the left in the gif; each second the cube resets, it never shuffles. This is a good example of proceduralism vs simulation; the gradual descent into an ever more mixed chaotic state would be difficult to maintain procedurally, as you'd need to record ahead of time the entire sequence of moves, and track them all, and apply them at every time step. I don't think its impossible, but not easy.
Instead we can do what happens in the right cube using a solver. Rather than set explicit rotations, we set a small delta of rotation, and accumulate it each frame. This means changing 'angle' in the above code block; rather than being driven by @Time%1 so it cycles, it uses @TimeInc (ie, 1/24th of a second in standard Houdini):
//angle = $PI/2*@Time%1; angle = $PI/2*@TimeInc;
Well, almost. In practice the final rotation isn't quite mathematically perfect, so rather than the pieces snapping into -0.5, 0 or 0.5, it goes to 0.000000001 or 0.49999999999. This is enough to throw the test, so pieces start to break. Aeoll from Discord was kind enough to show how to get around it, namely by modifying the test to allow a little bit of slack:
if (abs(sum(@P*axis) - slice) <= 0.001) {
There's more minor changes in the hip file (everything is driven by a time var 't' rather than @Time so I can speed things up or slow it down, and a few other things), but that's the core of the effect.
Vex vs Vops
If you've read this far, you might be thinking 'Wow, I'll never use vops again!', or 'Ew, vex looks horrible! I guess I'll stay with vops!'.
You don't have to choose one over the other! Remember, vops generate vex, so it makes sense that they can happily co-exist. Within vops, Houdini provides two handy nodes, 'inline code' and 'snippet'. Both let you write vex within vops, and wire attributes in and out of them just as you would with regular vop nodes. There's some subtle differences compared to wrangles, but the point is that you can use vops when that's a better fit (manipulating procedural textures like noise is much easier in vops), and swap to vex when it's the better option, eg if/for/while loops.
Vex snippet cheat sheet
I don't use these enough, so always forget the right way to use em.
Connect all your input attributes, refer to them directly without any $ or @ prefixes. Every input will have a correspoinding outname output, just connect the output you want to the next vop.
If you need a new placeholder variable, best to create a constant of the type you need, name it appropriately, set its value within the snippet, then use its out_name to feed to the rest of your network.
Eg, here I have a string being generated from the renderstate vop, and I want to make a random float from that. I know there's a vex command, random_shash() to do exactly that. So I make a snippet, connect the renderstate, create a float constant called 'myrand', wire that in, and in the snippet refer to the input names directly. I then take the output value of myrand, and connect that to my network. (I found for some reason that just the object_name by itself always returns the same result, so I append an underscore, that makes it behave. Weird.)
If the default names from your input nodes aren't to your liking, you can drop a null in-between to give you nicer names. Remember, a null can handle multiple attributes at once, handy:
Further learning
The 'bees and bombs' thread on odforce is a good place to start; lots of self contained looping animations created (mostly) in vex:
http://forums.odforce.net/topic/24056-learning-vex-via-animated-gifs-bees-bombs/
Ryoji CG has a handy collection of wrangle snippets:
https://sites.google.com/site/fujitarium/Houdini/sop/wrangle
The wrangle workshop isn't bad, but if I'm gonna critique moves a little slow (I'm also super impatient and prefer text to video, but thats me...)
Shadertoy is a great place for inspiration. The code isn't vex, and a lot of the files use techniques that don't directly map onto Houdini, but even then its handy to see a visual example of a technique you might want to implement, and the core algorithm can often be remapped to vex easily enough:
Processing, various javascript graphics libraries, older actionscript, all great to steal ideas from.