Constraint Networks 2
Intro
Hello my baby, hello my honey, hello my ragtime gal
A tutorial where I started trying to simulate leaves, and ended up making Rich Lord inspired walking bugs.
Lots of people contributed to this in various ways, I'm gonna call out Raph Gadot for setting up the initial challenge, Luke Gravett, Rubens Quadir, Ian Farnsworth, Henry Foster, Jake Rice, Rich Lord. Thanks!
Summary: Set up packed objects, animate their @orient and @P. Lock their animation, reset @orient, feed as packed geo to rbd. Branch the shapes to make a constraint network, frozen copy named after the shape, animated one with varying @orient that is named null. Make a constraint polyline, feed as a constraint network to drive springs, they'll drive the rbd objects.
If you haven't yet, probably worth reading the first section of this tutorial in ConstraintNetworks.
Oh, you're just here for the walking creatures? Fiiiiine, skip down to the bottom. 😃
Leaves
This started as a work question; we had some nice procedural animation of leaves in a tree; branches were being moved around, had some clever chops based spring lag and wind in the leaves. Being procedural and not sim, you could see in close up shots the leaves were intersecting. I got asked to find a way to fix that.
There might be a super cool procedural way (and given enough time I'd love to try), but my immediate reaction was 'Pfft, send the leaves to RBD, easy.'. So, question was, how to keep the existing animation, but have RBD be layered on top to do do the de-intersect?
After a few false starts, here's what I came up with:
Download scene: leaf_single_rbd_constraint.hipnc
The wireframe shows the animation I've setup in sops, the solid geo is what bullet RBD is doing; it's trying to follow the incoming animation as best it can. The sops network looks like this:
Red nodes build the leaf geo, yellow ones create a point with an animated orient attrib to make the leaf flap. The wrangle is this:
float angle;
vector n, up;
vector4 pitch;
angle = sin(@Time*ch('flap_speed'));
angle *= ch('flap_amp');
n = @P;
n = normalize(n);
up = {0,1,0};
@orient = quaternion(maketransform(n,up));
pitch = quaternion(angle,{1,0,0});
@orient = qmultiply(@orient,pitch);
float angle;
vector n, up;
vector4 pitch;
angle = sin(@Time*ch('flap_speed'));
angle *= ch('flap_amp');
n = @P;
n = normalize(n);
up = {0,1,0};
@orient = quaternion(maketransform(n,up));
pitch = quaternion(angle,{1,0,0});
@orient = qmultiply(@orient,pitch);
The green nodes are a timeshift to freeze the animation, and a wrangle to reset @orient to {0,0,0,1}, ie, no rotation.
The blue nodes create polyline prims for the constraint network, they need a little more explaining.
Constraint network sops setup
I've had several helpful people tell me I can do it all in one wrangle, and yes you definitely can (my work setup does this), but I've found by the time I get all the details sorted, a sop network like this is clearer to understand, and easier to debug.
Like in the previous constraint network page, I create polyline prims using the packed geo names. It uses the same idea of @name matching, so if a point on one of these polylines matches the @name of a packed object in rbd, they'll be connected, and points with an empty @name become worldspace constraints.
What's new here compared to the previous setups is introducing animation. If the worldspace constraint moves, or has animating @orient, that will be transferred to other points on the polyline, and hence to the connected packed prims.
A step that tripped me up for a long time, which is obvious in hindsight; if your driving animation in this way, then you have to kill any animation on the things being driven. That means the packed geo (hence the step above to timeshift and lock @orient), and the point on the polyline with a matching @name should also be frozen. In essence, this setup transfers the animation from the original packed object to the 'other' end of the constraint primitive.
Also, while its possible to have a single constraint for both position and rotation, its more flexible to create one for each, so the position constraint can be super strong, but the rotation has a bit of slack and spring in it.
So:
- Add sop to convert the packed prim back to a point
- Set @id from @ptnum
- Branch 2 copies of the point; one which is timeshift frozen to frame 1, the other keeps the orient animation, @name=''
- Merge, feed to an add sop to create a polyline, because the points both share the same @id, I tell the add sop to make polylines based on @id, meaning we get a single polyine of 2 points per packed prim. Note that because the points are directly on top of each other, the polyline is invisible!
- Now that we have a polyline, we setup the usual constraint attribs; @constraint_name, @constraint_type, @restlength. Here I again split into 2, so I get one constraint for position, one for rotation.
- Merged, bring into the rbd dopnet.
The dopnet itself is pretty straightforward; packed rbd setup with constraint network, the constraints match on the @constraint_name strings setup earlier.
More leaves more animation
That's a mild amount of setup, the payoff is you can have lots of points feeding the system, and then have animation moving the packed shapes around, it all responds as expected:
Download scene: leaves_rbd_with_anim.hipnc
Tweaking the behaviour of this is straightforward too; springs are pretty knowable; the spring strength controls how springy it is (which here equates to the frequency of the oscillations as well as how well it tries to follow the target @P and @orient), and damping controls how much energy it loses over time. I find settings of 3000 for strength, and take off 2 zeros for damping (so 30) work well:
wind via torque and speed limit for stability
Packed rbd can use a lot of the pop nodes, super handy. I found occasionally the animation coming from upstream departments would be too extreme on 1 frame, making the leaves wig out, or it was just too jiggly with springs. A speed limit pop added to the network was an easy fix, just needs some tweaking for the max spin so that it removes the jittering or crazy anim, but doesn't make the whole sim look like its running underwater or in slow motion:
At this point on the production shots the chops wind and flap motion started to look a bit strange. As such we decided to remove it, and go with dops wind instead. A geo wrangle to set @torque based on noise worked well here:
v@torque = curlnoise(@P+@Time*2)*200;
v@torque = curlnoise(@P+@Time*2)*200;
Download scene: leaves_rbd_speedlimit_and_torque.hipnc
From leaves to creatures
Another side benefit of springs and rbd is its pretty forgiving; going back to the original problem of trying to deintersect stuff, it works remarkably well. While experimenting I came up with this stress-test; turn the leaves to face inwards, have them all intersect with slightly offset flap timings, see what happens. The result was fun:
After staring at it for a while, I thought hmm... that sorta looks alive..
Was curious what happens if I bypassed the position constraint, added gravity, and let them fall onto a ground plane. They became leaf jumping beans; the animated orient was generating enough force to move them about on the floor:
Thus encouraged, I remembered Rich Lords fantastic rbd creatures. I figured all I'd need to do here is add a body, and rather than have the animated parts have their @name attribute be an empty string, it'd be @name='body' to attach them. And it worked!
Download scene: creature_simple.hipnc
Note that the spring strength needs a substantial boost; the earlier setups were essentially leaves floating in a vaccum with zero gravity, now that they support the weight of the body under gravity, have friction against the floor and so on, they need to be much stronger.
Lots of fun things to try next:
- Adjust the flap vex to make the legs move front to back as well as up and down
- Add more constraints to get segmented legs
- Segmented bodies
- Steering behaviours
- Bot fights!
Mistakes I made so you don't have to
- Pivot location. The packed prim pivot is important to set correctly. Its possible to do with an assemble sop, wrangles, pack sops etc, but its fiddly. I found the easiest way was to use a copy to points sop, set it to pack and instance, and the pivot mode set to 'origin'. In this case, because I modelled the leaf pointing down the z-axis, with the base of the leaf at the origin, it got placed exactly where I expected, and didn't jump when I added the constraint network.
- Using @pivot (don't). - I was sure I had to use the @pivot attrib to make this all be correct, but as long as I had the above step working, its not necessary, one less thing to track
- Unless you need to use @pivot. - Co worker and lovely person Dave Brown found an edge case, if you're doing the grow-shapes-from-small-to-big trick that is outlined elsewhere on the site, then yes, you need to track pivot. Example incoming...
- Balancing forces vs spring strength. - Once you use springs a few times, you get used to that thing of setting a spring strength of 1, sim a few frames, see it fall through the floor, 10, few frames, fall, 100, few frames, fall more slowly, 1000, almost there, 3000, ah, fixed. As the spring string goes further up, your other forces, say @torque, has to be boosted to fight the springs. Similarly when you wind the spring strength down, wind the external forces down.
- Hard constraints vs spring constraints - My work setup uses hard (pin) constraints for position, and spring for rotation, as for leaves I want it hard-locked to the branch geo. For walking creatures though, the bit of 'suspension' you get in springs for both is handy.
- Cone twist constraints - work, but are tricky. They give you a range of motion in x y z, but I've found springs are much easier to deal with.
- Try and remove jitter and instability, make things worse. The constraints have options to introduce a bit of slack or error compensation. When I was getting jittery or unstable sims, I'd try messing with these, seem to get good results in a small test case, then find systems explode horribly when applied to a full complex setup. Having tried lots of values, generally I'm leaving them at their defaults now. In particular setting 'error reduction parameter' on the hard constraint too high really breaks things. The default is 0.2, I found setting it above 0.8 can break stuff. Setting 'constraint force mixing' too high can also break things.
- Jitter due to badly placed rbd objects - In my tree setups I would occasionally have 2 leaves pinned to grow from the same point on a branch. When this got into dops the collision geo would try to push the leaves apart, but the constraints would try and pull them together, would get crazy jittering.
- Keep an eye on collision geo - related to the previous point, leaves might look ok in sops, but when they got their convex hull collision geo created, due to the curved shape of the leaves and long stems, the leaves would end up colliding through each other more than you'd expect, more jittering or strangeness.
- Cheat that collision geo by deleting sticky outy bits - more a tip than a mistake, but I found a lazy fix for the above issue; the long stem on the leaves caused the convex hull collusion geo to be bigger than expected, but I had too many leaves to make using concave collision geo an option. Instead I did a super cheap trick; I just deleted the lower third of the leaves, so the stem was removed, leaving just the bulk of the leaf shape. The pivot and constraints still stay where they should (because the leaves are modelled with their attachment point at the origin, and I tell the copy sop to maintain that pivot), and the stems don't really collide much with things anyway. As such I got a much more stable setup, that still stayed nice and fast. All I had to do was copy the animation from the rbd sim back to the original leaves. Example coming!
- Copying animation from rbd to original geo doesn't always work - tricky one this! So I'd use the transform pieces sop, whos primary purpose is to do exactly this, to transfer rbd anim from a low res sim to matching high res pieces. I found that about 30% of the time the result would be misaligned, which I couldn't explain. Co-worker Rubens Fredrick recognised it as a known issue, and supplied the following vex based solution. You need to read the 'packedfulltransform' prim intrinsic, which is a full 4x4 matrix, and extract orient and scale from this. In a point wrangle, original high res geo sitting at the origin goes to first input, the low res rbd sim goes to the second input:
matrix m4 = primintrinsic(0,'packedfulltransform',@ptnum);
matrix3 m3 = matrix3(m4);
@orient = quaternion(m3);
@scale = cracktransform(0,0,2,0,m4);
v@pivot = primintrinsic(0,'pivot',@ptnum);
matrix m4 = primintrinsic(0,'packedfulltransform',@ptnum);
matrix3 m3 = matrix3(m4);
@orient = quaternion(m3);
@scale = cracktransform(0,0,2,0,m4);
v@pivot = primintrinsic(0,'pivot',@ptnum);
and the more silly errors I make all the time even though I should know better by now:
- misspelling attribute names. - @constriant_name, @constranjt_naem, I'll shout at my setups for 5 minutes, and realise I'm using incorrect names. probably time to make some wrangle presets to save me from myself.
- putting prim attributes on points, point attribtutes on prims - my muscle memory to quickly type 'pw' for point wrangle, when I should be making a prim wrangle, happens far too often.
- not freezing the inputs. - again when I'm rushing I'll forget to put down the timeshift sops, and scratch my head for ages as the system goes crazy.
You want more? Wow, you're keen. Better head to ConstraintNetworks3.