Dinosaur Tutorial 3
 
   
 
   
 
 
 
   
Dinosaur Tutorial 3
 
   
 
   
 
 
 
   
Dinosaur Tutorial 3
 
   
 
   
 
 
3.7 mb zipped max file - requires the full version of ACT
 
divx avi - 2.4 mb
   
divx avi - 964KB
 
divx avi - 888KB
   
divx avi - 1.4 mb
 
jpg - 87KB
   
jpg - 125KB
 
jpg - 139KB
   
   
You can download the latest divx video codec from www.divx.com
 

Tutorial 3

Character Rigging - the neck of cgCharacter-a-saurus
Generalizing control

By Mark Snoswell. October 11th 2001

 

 
     
 
zipped Max file - 1.37MB
 
 

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)
  local tr = #(
  (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…  
  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
)

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.