You have the aim constraint under your belt. You can guess how a pole vector’s motion will change rotations with a look. You’re feeling a new sense of power and a desire to accomplish… things.
Now what?
Let’s start with something simple. Remember that old standby, the Rivet script, that I mentioned in my last post? Every good tutorial DVD I’ve bought over the years had it. Every rigger I’ve ever met uses it. As scripts go, it’s probably the most useful 2k on HighEnd3D.com.
But did you know it’s also broken?
Let me go back for a moment. A year ago, while I was working under a talented rigger who liked the Farenheit Digital system for creating nurbs ribbon rigs, was saddened by the fact that all licenses of Maya at our company were Maya Complete save two: his, and mine. This meant that the standard way of building ribbons, where you attach a Maya Hair follicle to the surface, wasn’t going to work as Maya Hair only comes with Unlimited. He mentioned something about using an aim constraint and a pointOnSurfaceNode or through the decomposeMatrix node to accomplish the same, although it didn’t work as well. So I was tasked with writing a python node plugin that accomplished the task. It worked well and quickly enough; 40 or so of them were live in the final rigs.
However I prefer to keep the number of non-standard nodes in rigs to a minimum. At my current place of work we realized a need for a follicle-like setup again, so I started researching.
At one point we’d thought we could solve the problem with the Rivet script. Rivet takes two edges, lofts them into a nurbs surface with history, then attaches to the surface a locator using a pointOnSurfaceInfo and an aim constraint. When the lofted surface is relatively square and doesn’t deform much, this works fine. When you try to use just the pointOnSurfaceInfo and aim constraint setup on a longer nurbs surface that deforms and bends, however, we found that the locators do not behave properly. Past a certain amount of twisting, the locators would rotate off in odd directions.
I played with the script and found that the pointOnSurfaceInfo node was feeding the surface normal into the aim constraint as the aim vector, with one of the tangent vectors as Up. Because of this, the aim constraint was causing the locator to flip. The way aim constraints work makes up vectors into suggestions, not rules. It also makes the third axis a product of the other two, as I showed in my last post.
In the end it was a simple fix: instead of using the surface normal (which wasn’t an illogical choice), I fed both surface tangents into the aim constraint and let the third axis, the normal, be the derived one. Since the tangent u and v vectors are always orthogonal regardless of how much you distort the surface, and since they always run in the right directions along the surface, you can be certain that the surface normal — a third orthogonal vector — will still end in the right place. (I bet the surface normal is derived from the cross product of the two tangent vectors anyway, internally.) No need for a custom node or to force the loading of decomposeMatrix; so far I haven’t seen any problems with this setup.
Steps for those who want to try this at home:
1) Create a pointOnSurfaceInfo node attached to your nurbs surface. Set it’s U and V parameters to get the output to the right place on your surface.
2) Use the createNode command to make an aimConstraint node.
3) Plug the pointOnSurfaceInfo’s tangentU into the aimConstraint’s target[0].targetTranslate, and the tangentV into the constraint’s up vector.
#1 by Animatix on 2009/09/03 - 11:30 pm
I was having a bit of trouble following the steps to recreate this better rivet. I was wondering if you could give me a better break down. Thanks.
#2 by katt on 2009/09/04 - 1:49 am
Hi Animatix,
The steps are:
1) Create a locator, the nurbs surface you want to use, and an aimConstraint (createNode “aimConstraint”)
2) On the nurbs surface, create a pointOnSurfaceInfo node (pointOnSurface -ch on)
3) Connect the pointOnSurfaceInfo node’s .position plug into the locator’s .translate.
4) Connect the pointOnSurfaceInfo node’s .tangentU plug into the aimConstraint’s .target[0].targetTranslate.
5) Connect the pointOnSurfaceInfo node’s .tangentV plug into the aimConstraint’s .worldUpVector.
6) Connect the aimConstraint’s .constraintRotate plug into the locator’s .rotate.
That’s it. I think. I’m doing this off the top of my head at the moment after a long day. 😉
#3 by exceptional basis on 2009/12/04 - 10:57 pm
I found that using this method with an aimConstraint does not produce acceptable rotation results, as described (or in quite a few other configurations). however, using a normalConstraint from the deformed surface to the transform being driven and plugging the .tangentV attribute from the POSI node into the normalConstraint’s .worldUpVector attribute does work very well.
#4 by katt on 2009/12/04 - 11:05 pm
I dunno, EB — the aim constraint works perfectly for me in all situations with parametric surfaces. I haven’t tried the normal constraint for things like this because I’ve never had any issues. Can you give me a more specific example where the aimConstraint failed on you?
#5 by exceptional basis on 2009/12/05 - 2:36 am
here’s some (sloppy) MEL:
//begin mel
{
//new file
file -f -new;
//make a simple d3 nurbs surface
string $plane[] = `nurbsPlane -ch on -o on -po 0 -ax 0 1 0 -w 5 -lr .1` ;
//deform the surface at each CV along V
select -r nurbsPlane1.cv[0][0:3] ;
CreateCluster;
select -r nurbsPlane1.cv[1][0:3] ;
CreateCluster;
select -r nurbsPlane1.cv[2][0:3] ;
CreateCluster;
select -r nurbsPlane1.cv[3][0:3] ;
CreateCluster;
//make a hierarchy of the clusters
parent cluster2Handle cluster1Handle;
parent cluster3Handle cluster4Handle;
//make and connect a pointOnSurtfaceInfo node
string $posi = `createNode pointOnSurfaceInfo`;
connectAttr ($plane[0] + “.worldSpace[0]”) ($posi + “.inputSurface”);
//add a reader group and give it attr for controlling the read point of the posi node,
//make the local axis visible and make it grabbable
string $readr = `group -em -n “reader”`;
addAttr -ln “posU” -dv .5 -at double $readr;
setAttr -e-keyable true ($readr + “.posU”);
addAttr -ln “posV” -dv .5 -at double $readr;
setAttr -e-keyable true ($readr + “.posV”);
setAttr ($readr + “.displayHandle”) 1;
setAttr ($readr +”.displayLocalAxis”) 1;
//make the aim constraint for conversion of the output vectors of the posi node to euler rotations
string $aim = `createNode aimConstraint`;
//drive the reader’s translation
connectAttr ($posi + “.position”) ($readr + “.t”) ;
//hook up the UV driver attrs to the posi node
connectAttr ($readr + “.posU” ) ($posi + “.parameterU” );
connectAttr ($readr + “.posV” ) ($posi + “.parameterV” );
//connect aim constrain as specified
connectAttr ($posi + “.tangentU”) ($aim + “.target[0].targetTranslate”) ;
connectAttr ($posi + “.tangentV”) ($aim + “.worldUpVector”) ;
connectAttr ($aim + “.constraintRotate” ) ($readr + “.r” );
//set some keys
setKeyframe -breakdown 0 |cluster4Handle.rotate;
currentTime 24 ;
setAttr cluster4Handle.ry 70;
setKeyframe -breakdown 0 |cluster4Handle.rotate;
//playback the scene. the z rotation does not work in a useful way, although the
//x axis is pinned to the U isoparm. The rotation does not “follow” the V isoparm of the surface
//one way this can be remedied by using a normal constraint (from the surface to the reader)
//instead of an aim constraint, and feeding the .tangentV of the posi node into the
//.worldUpVector of the normal constraint
//This could have been setup so that we “follow” the U parameter instead of the V,
//by altering the clusters and the length/ratio of the surface, however, what we are seeing
//is a shearing of the U and V parameter vectors in relation to each other. There will never
//be shearing between U (or V) and the surface normal: that will always be 90 degrees, so
//it’s probably a superior method of reading the vectors produced by the surface and posi node.
}
#6 by katt on 2009/12/12 - 7:11 pm
Hi again EB,
I tried your code and tried plugging the .tv into the normalConstraint’s worldUpVector. I think I’m fuzzy on what you’re trying to accomplish.
From what I can see with your code, the reader object is behaving exactly as expected. Now, in your example I do get some perceived shearing when I set reader.posU to 0.84. Maybe that’s what you’re talking about?
The first problem I see here is that your V degree should be 1. It was either in the original Art of Rigging or the Farenheit DVDs, but for a ribbon to work well you need to keep the number of points down to a minimum. Here’s an example modified from your code. Scrub through the animation; you’ll see that the Z axis always aims out properly. Again there’s a bit of perceived shearing in extreme circumstances, but in my experience that shearing never adversely effects a rig using ribbons.
If that’s not what you mean, then I’m a little lost here. 🙂
file -new -force;
string $np[] = `nurbsPlane -d 3 -ch 0 -o on -po 0 -ax 0 1 0 -w 5 -lr .1`;
rebuildSurface -ch 0 -rpo 1 -rt 0 -end 1 -kr 0 -kcp 0 -kc 0 -su 3 -du 3 -sv 1 -dv 1 -tol 0.00393701 -fr 0 -dir 2 $np[0];
select -r nurbsPlane1.cv[0:1][0:1] ;
CreateCluster;
select -r nurbsPlane1.cv[2][0:1] ;
CreateCluster;
select -r nurbsPlane1.cv[3][0:1];
CreateCluster;
select -r nurbsPlane1.cv[4:5][0:1] ;
CreateCluster;
//make a hierarchy of the clusters
parent cluster2Handle cluster1Handle;
parent cluster3Handle cluster4Handle;
//make and connect a pointOnSurtfaceInfo node
string $posi = `createNode pointOnSurfaceInfo`;
connectAttr ($np[0] + ".worldSpace[0]") ($posi + ".inputSurface");
//add a reader group and give it attr for controlling the read point of the posi node,
//make the local axis visible and make it grabbable
string $readr = `group -em -n "reader"`;
addAttr -ln "posU" -dv .5 -min 0 -max 1 -at "double" $readr;
setAttr -e-keyable true ($readr + ".posU");
addAttr -ln "posV" -dv .5 -min 0 -max 1 -at double $readr;
setAttr -e-keyable true ($readr + ".posV");
setAttr ($readr + ".displayHandle") 1;
setAttr ($readr +".displayLocalAxis") 1;
//make the aim constraint for conversion of the output vectors of the posi node to euler rotations
string $aim = `createNode aimConstraint`;
//drive the reader's translation
connectAttr ($posi + ".position") ($readr + ".t") ;
//hook up the UV driver attrs to the posi node
connectAttr ($readr + ".posU" ) ($posi + ".parameterU" );
connectAttr ($readr + ".posV" ) ($posi + ".parameterV" );
//connect aim constrain as specified
connectAttr ($posi + ".tangentU") ($aim + ".target[0].targetTranslate") ;
connectAttr ($posi + ".tangentV") ($aim + ".worldUpVector") ;
connectAttr ($aim + ".constraintRotate" ) ($readr + ".r" );
setAttr "cluster1Handle.ty" 2.5;
setAttr "cluster4Handle.ty" 1.25;
setAttr "cluster2Handle.rx" -80;
setAttr "cluster4Handle.ry" 140;
setAttr "cluster1Handle.ry" 85;
setAttr "cluster4Handle.ry" 45;
currentTime 1;
setAttr reader.posU 0;
setKeyframe "reader.posU";
currentTime 24;
setAttr reader.posU 1;
setKeyframe "reader.posU";
#7 by exceptional basis on 2009/12/18 - 7:43 pm
Hey,
There’s something happening with the ” symbol here which prevents code from running when copied from this thread and pasted into a script editor on OSX. I had to run it through a plaintext converter to get it to work.
Anyway, your example works, but I was posting the above as an indicator of a situation of where it might not work, and a normal constraint would work a bit better.
Having a non-1 V degree won’t affect the result of either example. It’s true that using a smaller number of points is more efficient, but maya eats NURBS for breakfast…you’d need an incredibly huge ribbon before doubling the V CVs would make any difference in evaluation time.
Just because someone wrote it in a book, doesn’t mean it’s right!
#8 by katt on 2009/12/20 - 2:13 pm
Ahh sorry– for some odd reason it converted my quotes?
I find that the number of points *does* in fact affect the result. When you have non-1V degree surfaces, you end up with less intuitive motion on the locator. It’s not about evaluation time, but rather about keeping the tangent vectors crossing each other in predictable ways.
By clustering a line of V cv’s and then rotating that line around the cluster, the path the U surface isoparms take through the surface becomes a bit skewed.
In the end as long as the rig doesn’t flip out and makes pictures, that’s all that matters; if you’ve had success with the normal constraint then I can’t say anything about that. I don’t use it because I haven’t had any problems with my method and since I know the surface normal is created from the cross product tangent U and V vectors, I like using the source vectors. The point of the post was that the rivet script which everyone uses can be improved upon, and that by using a script only as a black box of functionality you miss out on learning more about your software under the hood, as well as on potential fixes for poor behavior.