This continues on from the previous tutorial on rigging the neck.
Here I present a generalized version of the neck control script that will work with and axis setup you might have for your vertebra. It also uses 4 Max Plane Angle Manipulators for control and moves the script into a Ambient List controller for better management.
The Goal
To generalize the control script so that it can easily cope with any axis setup for the vertebra. At the same time we want to try and improve the interface by using only Plane Angle Manipulators.
The Script.
This is going to be short as the inner workings of the control script were explained in the last tutorial. All we want to do now is to set it up so that the script could handle any axis arrangement for the vertebra being controlled.
Here is how the twist function looked before and the lines required to call it…
fn roll_x ang tr = (tr.row2 *= (quat -ang tr.row1); tr.row3 = cross tr.row1 tr.row2; return tr)
ang = roll.value + (ang_inc = roll.value / neck_obj.count)
for j = 1 to 13 do neck_obj[j].transform = roll_x (ang -= ang_inc) neck_obj[j].transform
And here is the general form and the lines required to call it…
fn twistFn axis ang tr =
( case axis of
| |
( |
1: (tr.row2 *= (quat -ang tr.row1); tr.row3 = cross tr.row1 tr.row2; return tr) --X axis
2: (tr.row3 *= (quat -ang tr.row2); tr.row1 = cross tr.row2 tr.row3; return tr) --Y axis
3: (tr.row1 *= (quat -ang tr.row3); tr.row2 = cross tr.row3 tr.row1; return tr) --Z axis |
| |
) |
|
| ) |
|
|
ang = roll.value + (ang_inc = roll.value / neck_obj.count)
for j = 1 to 13 do neck_obj[j].transform = twistFn 1 (ang -= ang_inc) neck_obj[j].transform
All that has really happened is that we have added an axis parameter to the function call and used a case statement to handle the three different axis.
Here is the entire control script…
twistManip = $mSaurus__neck_twist.angle.controller
swingManip = $mSaurus__neck_swing.angle.controller
tiltAManip = $mSaurus__neck_tiltA.angle.controller
tiltBManip = $mSaurus__neck_tiltB.angle.controller
dependson $mSaurus_C_vert_14.transform.controller twistManip swingManip tiltAManip tiltBManip
fn twistFn axis ang tr =
| ( |
case axis of |
( |
1: (tr.row2 *= (quat -ang tr.row1); tr.row3 = cross tr.row1 tr.row2; return tr) --X axis |
| |
|
|
2: (tr.row3 *= (quat -ang tr.row2); tr.row1 = cross tr.row2 tr.row3; return tr) --Y axis |
| |
|
|
3: (tr.row1 *= (quat -ang tr.row3); tr.row2 = cross tr.row3 tr.row1; return tr) --Z axis |
| |
|
) |
|
| ) |
|
|
|
| fn FBa p U = (p*(U^2)/4) |
--weighting function for tiltA |
| fn FBb p U = (p*(exp -(((3.5*U)-1) ^ 2)) / 4) |
--weighting function for tiltB |
| fn applyRotations twist swing tiltA tiltB obj tr = |
|
| ( |
local tilt_TM = Matrix3 1 |
| |
local swing_TM = Matrix3 1 |
| |
local n = obj.count |
| |
local U = 0.0; |
| |
local UInc = 1.0/(n - 1.0) |
| |
local TiltAxis = 2 --Y axis |
| |
local SwingAxis = 3 --Z axis |
| |
local TwistAxis = 1 --X axis |
| |
local TiltAxisFlip = false |
| |
local SwingAxisFlip = false |
| |
local TwistAxisFlip = false |
| |
for j = 2 to n do |
|
| |
( |
local tilt_rot = ((FBa TiltB U) + (FBb TiltA U)) * (if TiltAxisFlip then -1 else 1) |
| |
|
case tiltAxis of |
| |
( |
1: tilt_TM = rotateXMatrix tilt_rot |
| |
|
2: tilt_TM = rotateYMatrix tilt_rot |
| |
|
3: tilt_TM = rotateZMatrix tilt_rot |
| |
) |
|
| |
|
local swing_rot = (-Swing / 8) * (if SwingAxisFlip then -1 else 1) |
| |
|
case swingAxis of |
| |
( |
1: swing_TM = rotateXMatrix swing_rot |
| |
|
2: swing_TM = rotateYMatrix swing_rot |
| |
|
3: swing_TM = rotateZMatrix swing_rot |
| |
) |
|
| |
|
obj[j].transform = tr[j] * inverse tr[j-1] * tilt_TM * swing_TM * obj[j-1].transform |
| |
|
U += UInc |
| |
) |
|
| |
|
local ang_inc = (-Twist / n) * (if TwistAxisFlip then -1 else 1) |
| |
|
local ang = 0.0 |
| |
|
for j = 2 to n do obj[j].transform = twistFn 1 (ang += ang_inc) obj[j].transform |
| ) |
|
|
| ( |
|
|
| |
|
local obj = #( |
| |
|
$mSaurus_C_vert_14,$mSaurus_C_vert_13,$mSaurus_C_vert_12,$mSaurus_C_vert_11, $mSaurus_C_vert_10,$mSaurus_C_vert_09,$mSaurus_C_vert_08,$mSaurus_C_vert_07,$mSaurus_C_vert_06, $mSaurus_C_vert_05,$mSaurus_C_vert_04,$mSaurus_C_vert_03,$mSaurus_C_vert_02,$mSaurus_C_vert_01) |
| |
(matrix3 [-4.07968e-007,0.979925,0.199368] [-1,-4.29179e-007,0] [0,-0.199368,0.979925] [-6.75772,-270.405,203.375]), (matrix3 [-3.08133e-007,0.987689,0.156434] [-0.999999,0.000101989,-0.000645905] [-0.000653907,-0.156433,0.987687] [-6.75497,-281.116,201.5]), (matrix3 [1.67151e-005,0.991445,0.130526] [-0.999992,0.000471645,-0.00345449] [-0.00348649,-0.130525,0.991437] [-6.74338,-290.983,200.157]), (matrix3 [0.000621876,0.999048,-0.0436188] [-0.999981,0.000361754,-0.00597167] [-0.00595021,0.0436217,0.999029] [-6.73711,-300.798,199.955]), (matrix3 [0.00247784,0.93358,-0.358361] [-0.999971,-0.000177045,-0.00737547] [-0.00694906,0.358369,0.933553] [-6.7496,-309.497,202.376]), (matrix3 [0.0038686,0.83867,-0.544627] [-0.999964,-0.00073271,-0.00823131] [-0.00730239,0.54464,0.838637] [-6.77807,-316.867,206.649]), (matrix3 [0.00442967,0.793352,-0.608747] [-0.999956,-0.00140325,-0.00910524] [-0.00807788,0.608761,0.793311] [-6.81506,-323.963,212.289]), (matrix3 [0.00442991,0.793353,-0.608747] [-0.999944,-0.00222531,-0.010177] [-0.00942855,0.608758,0.793298] [-6.85444,-331.142,218.261]), (matrix3 [0.00342017,0.852638,-0.52249] [-0.999929,-0.00295741,-0.0113717] [-0.0112412,0.522492,0.852568] [-6.88262,-338.149,223.035]), (matrix3 [0.00172078,0.920503,-0.390733] [-0.999896,-0.00400564,-0.0138403] [-0.0143052,0.390716,0.9204] [-6.89503,-345.858,226.866]), (matrix3 [-0.000789427,0.974365,-0.224971] [-0.999856,-0.00457183,-0.0162926] [-0.0169035,0.224926,0.974227] [-6.8907,-354.341,229.498]), (matrix3 [-0.00356831,0.998126,-0.0610912] [-0.999804,-0.00474449,-0.0191189] [-0.0193729,0.061011,0.997947] [-6.86346,-362.631,230.612]), (matrix3 [-0.00573862,0.998616,0.0522729] [-0.999745,-0.00458906,-0.0220837] [-0.0218133,-0.0523863,0.998387] [-6.82334,-370.047,230.713]), (matrix3 [-0.00691494,0.994506,0.104453] [-0.999651,-0.00421581,-0.0260388] [-0.0254554,-0.104596,0.994187] [-6.77272,-376.65,230.071]) |
|
| |
) |
|
| |
applyRotations twistManip.value swingManip.value tiltAManip.value tiltBManip.value obj tr |
|
| ) |
|
|
| 0.0 |
|
|
We have also isolated all the rotation code into a single function - applyRotations().
applyRotations twistManip.value swingManip.value tiltAManip.value tiltBManip.value obj tr
and the very first thing that we do in the function in define the axis and weather they are flipped or not…
| |
local TiltAxis = 2 |
--Y axis |
| |
local SwingAxis = 3 |
--Z axis |
| |
local TwistAxis = 1 |
--X axis |
| |
local TiltAxisFlip = false |
|
| |
local SwingAxisFlip = false |
|
| |
local TwistAxisFlip = false |
|
You could pass the axis and their flipped states as parameters - but in this case that's no easier than setting the place in this one place in the script.
The blend functions
The functions for blending the base and top rotations for tilt have also been isolated to separate functions that are called in just one place in the script.
| |
fn FBa p U = (p*(U^2)/4) |
--weighting function for tiltA |
| |
fn FBb p U = (p*(exp -(((3.5*U)-1) ^ 2)) / 4) |
--weighting function for tiltB |
| |
|
|
| |
… and then called inside the apply rotations function… |
|
| |
( |
local tilt_rot = ((FBa TiltB U) + (FBb TiltA U)) * (if TiltAxisFlip then -1 else 1) |
| |
( |
1: tilt_TM = rotateXMatrix tilt_rot |
| |
|
2: tilt_TM = rotateYMatrix tilt_rot |
| |
|
3: tilt_TM = rotateZMatrix tilt_rot |
| |
) |
|
| |
|
local swing_rot = (-Swing / 8) * (if SwingAxisFlip then -1 else 1) |
| |
( |
1: swing_TM = rotateXMatrix swing_rot |
| |
|
2: swing_TM = rotateYMatrix swing_rot |
| |
|
3: swing_TM = rotateZMatrix swing_rot |
| |
) |
|
| |
|
obj[j].transform = tr[j] * inverse tr[j-1] * tilt_TM * swing_TM * obj[j-1].transform
U += UInc
) |
As you can see it would also be very simple to de exactly the same for the swing is we wanted blended swing control for the base and top also.
Where to call the control script from.
After much testing and Max crashing I have decided that it's probably easiest just to turn the Ambient Light controller into a list controller. You can then simply add new MaxScript controllers for each new control you require. You just make sure they all finish with [0,0,0] so that they don't actually influence the ambient light!
There are several advantages to this:
| |
· You can still set the ambient light normally in the Environment window. |
| |
· All your control scripts run at render time. |
| |
· All your control scripts are in one list… without trigger scripts elsewhere. |
| |
· It is easy to see and change the order in which control scripts run. |
The interface - all manipulators.
This is perhaps the most important aspect of all. The control interface has been changed to a set of Plane Angle Manipulators. In manipulate mode these are instantly available without having to select anything and are exceptionally easy and accurate to use even if you are zoomed out quite a long way… and they always work the same regardless of the select mode you are in. All in all using only manipulators for everything is much easier then any other control mechanism I have tried.
The future:
I have already written a MaxScript "System Controller Manipulator" for spines in general. This completely eliminates all scripting - all you have to do is select the spine links to control and everything is done for you… and all the angle manipulators are in the single control manipulator… it's got visual guides for angle limits, proxy mode, all the axis options, the option to blend all three axis… everything I could think of ! I will not be posting the code for this… it Pushes MaxScript way beyond it's design limits and has numerous problems (triggers too many Max bugs). It does serve as a functional prototype for a full-blown C++ version - probably implemented as a System Controller. In testing it is by far the most powerful and easy to use version. And it only took about 5 min to set up two whole systems - one for the neck and one for the tail!
...anyway. The generalised control function is the core of it and that is where the code in this example came from.
|