Joy of Vex Day 15
copy sop, simple instance attributes (pscale)
Copy sop basics
Back to the trusty grid, create a box, copytopoints sop, connect the box to the first input of the copy sop, and wrangle to the second:
If you display the copy sop, you get the expected result; a box is copied to each point of the grid.
The copy sop is designed to look for certain attributes on the second input, and will use those to affect how the geo feeding the first input is copied. These attributes come up in a few places across houdini, they're all listed here as instance attributes:
http://www.sidefx.com/docs/houdini/copy/instanceattrs.html
Lets start with a simple one, pscale. This is a float, and will uniformly scale the copied geo. So lets drive that with a slider:
@pscale = ch('pscale');
@pscale = ch('pscale');
Now you can adjust all the boxes so they don't intersect. Nice. But hey, you know whats coming next...
float d, t;
t = @Time * ch('speed');
d = length(@P);
d *= ch('frequency');
d += t;
d = fit(sin(d),-1,1,ch('min'),ch('max'));
@pscale = d;
float d, t;
t = @Time * ch('speed');
d = length(@P);
d *= ch('frequency');
d += t;
d = fit(sin(d),-1,1,ch('min'),ch('max'));
@pscale = d;
Surprise surprise, sine waves, but now controlling the scale of your cubes.
Another attribute is @scale, which is a vector, so you can non-linearly scale the copied geo. In the following code I'm using another way to create a vector, set. Time for another quick aside...
Vectors and set
I covered this earlier, but worth a refresher as we'll use it today. We've already done lots of direct assignment of vectors using curly braces:
@scale = {1, 5 , 2.2};
@scale = {1, 5 , 2.2};
And setting individual components using variables/attributes with dot notation:
@scale.x = 1;
@scale.y = d;
@scale.z = @Cd.g;
@scale.x = 1;
@scale.y = d;
@scale.z = @Cd.g;
But if you try and do the curly braces assignment with variables and attributes, it will error:
@scale = {1, d, @Cd.g}; // nope
@scale = {1, d, @Cd.g}; // nope
To get around this, use set, and regular brackets (its a function, functions use regular brackets) :
@scale = set(1, d, @Cd.g); // better
@scale = set(1, d, @Cd.g); // better
@scale
Ok, so now we'll set the y scale of the boxes from d:
float min, max, d, t;
min = ch('min');
max = ch('max');
t = @Time * ch('speed');
d = length(@P);
d *= ch('frequency');
d += t;
d = fit(sin(d),-1,1,min,max);
@scale = set(min, d, min);
float min, max, d, t;
min = ch('min');
max = ch('max');
t = @Time * ch('speed');
d = length(@P);
d *= ch('frequency');
d += t;
d = fit(sin(d),-1,1,min,max);
@scale = set(min, d, min);
Play with the numbers and... wait wot? The boxes are scaling along z, not y. Why? Swapping the variables around in the set call, seems you need to scale along z to affect height:
@scale = set(min, min, d);
@scale = set(min, min, d);
This is a "fun" quirk of houdini and the copy sop; it assumes that your geo's up-axis is along z, and if the 2nd input (called the template geo) has @N, it will point z along N. Basically it's rotating our copies 90 degrees. Oh houdini. This makes more sense if you swap cubes for pigs, and you can see that it lies them on their back, noses in the air.
But oh noes! If you do that, houdini becomes painfully slow! What to do? On the copy sop, enable 'pack and instance'. Doing this converts the 1st input to a packed prim, so now rather than having to modify 1.15 million polys every frame, its only modifying the transforms of 400 packed instances (with a 20x20 grid in my case).
Switch back to boxes, remember that all the stuff we've done so far with @P or @Cd still works here (turn off 'pack and instance', will explain why in a bit)
float min, max, d, t;
min = ch('min');
max = ch('max');
t = @Time * ch('speed');
d = length(@P);
d *= ch('frequency');
d += t;
d = fit(sin(d),-1,1,min,max);
@scale = set(min, max, d);
@P.y += d/2;
d = fit(d, min, max, 0,1);
@Cd = vector(chramp('color',d));
float min, max, d, t;
min = ch('min');
max = ch('max');
t = @Time * ch('speed');
d = length(@P);
d *= ch('frequency');
d += t;
d = fit(sin(d),-1,1,min,max);
@scale = set(min, max, d);
@P.y += d/2;
d = fit(d, min, max, 0,1);
@Cd = vector(chramp('color',d));
There I'm setting the height to be half of d, this has the effect of looking like it locks the bottom of the boxes in place.
For @Cd I've introduced yet another trick; if you wrap chramp() in a vector cast, houdini then assumes you want a colour ramp, very handy. When I tested this at first, I noticed the boxes weren't using the full colour range I specified, then remebered I'd already clipped d with a fit function. It a super lazy cheat move, I just ran another fit with the values reversed, so it goes from min|max back to the 0|1 range the chramp expects.
Try turning 'pack and instance' back on again, you might get colour, you might not. While packed geometry is very powerful, and it should render fine with mantra (and I think most other 3rd party renderers), making these things work with the openGL viewport is tricky. It's more likely to fail if you swap to the pig, but as if to make me feel stupid, I'm doing it now, clicking and unclicking the pack button, its fine.
Anyway, surface appearance for packed geo isn't guaranteed in the viewport, don't worry too much about it if you don't see colour there, it will likely be there when you render, or if you really need to preview, turn off pack on the copy sop, or append an unpack sop to return it to regular geo.
Exercises
- Use noise to drive the scale shapes
- Combine noise and sine to get noisy waves
- What happens if you affect @N?
- The boxes wobble/rotate a bit. Can you work out why, and fix it?
- can you make the boxes squash and stretch, ie feed different-but-syncronised scales to 2 axis, so when its fat on x, its thin on y, and vice versa? Here's what I came up with after some playing around with the last code example. The only extra thing I'm using is frac() and trunc(), everything else is using stuff that's been covered in the lessons so far. Good luck. 😃
prev: JoyOfVex14 this: JoyOfVex15 next: JoyOfVex16
main menu: JoyOfVex