PDA

View Full Version : Free Maya Plugin - Shoulder Constraint


AdamMechtley
01-12-2009, 01:42 AM
Hi everyone!

I made a new Maya plugin using Python that I wanted to share: a shoulder constraint!

Basically the plugin is a constraint that you hook up to the first twist joint in a shoulder to control its rotation. I've seen a lot of other implementations for this problem, but a lot of them are prone to flipping (either because they use euler angles or a static up-vector constraint) or are a pain to set up because they are partially data-driven or add a bunch of extra transforms to your hierarchy (like Max's character studio or Michael Comet's pose deformer). Because the whole thing is in Python you can see all the code for everything I did too. If you're interested in porting this into game engine code to run procedurally in real-time then let me know--when you strip out a lot of the options I include in the constraint, the whole operation is really only a few cycles if you are working on a console with a VPU.

You can download a ZIP file from my site here http://6ixsetstudios.com/downloads/python/AM_ShoulderConstraint.zip
The ZIP file contains the following files:



AM_ShoulderConstraint.py

This is the plugin node. Put this into one of the folders in your plugin path and load it in Maya from Window -> Settings/Preferences -> Plug-in Manager.

AM_ShoulderConstraintOptions.mel


This script is required unless you want to make all your connections by hand. Add it to a folder in your scripts path and run AM_ShoulderConstraintOptions from the script editor. When you see the option box, input the appropriate data, select your shoulder, then select your constrained joint and press create.

AM_SetupShoulder.mel


This script lets you automate the whole process of setting up the twist joints for a shoulder on a base skeleton. You MUST add both this and AM_ShoulderConstraintOptions.mel to a folder in your scripts path, as it relies on some global procedures defined in AM_ShoulderConstraintOptions.mel. Once you have added them both, run AM_SetupShoulder from the script editor. When you see the option box, input the appropriate data, the select one or more elbow joints in the scene and press create.

AM_ShoulderConstraint.ma


This is an example file showing you the constraint in action in a bunch of different configurations

I built in help menus for the tools, but please let me know if you have any questions or problems. Thanks in advance for any feedback you all have!

NolanSW
01-12-2009, 06:41 AM
Awesome Adam. Thanks for sharing. Look forward to giving it a try.

ShadowM8
01-12-2009, 09:41 AM
Hey Adam,
Thanks for sharing the plug-in and source code! It would be nice to see how you tackled the problem. Correct me if I am wrong but from a quick glance it seems that you have target quaternions representing the desired rotations of the roll joints in each situation? Similar to what CS Biped does?

Overall it seems to work pretty well although there's still unwanted behavior if you rotate over 180 degrees around the spin axis.

Cheers!

AdamMechtley
01-12-2009, 05:50 PM
Hi Shadowm8,

In principle what I am doing is not terribly different from Character Studio's implementation. It's hard to say exactly since I am on a Mac and I don't know if CS's implementation is available or described in the max SDK or documentation anyway. I will go out on a limb though and say that what I am doing is a far simpler algorithm. CS defines 6 twist poses and, as I understand it, blends from one to another based on the current orientation's arc relative to the barycenter of the nearest 3 orientations with a twist pose defined for them. That means there is a lot more math and an extra slerp at any given time.

As you can see in my code, I am defining 3 target quaternions and the whole thing is driven by a single input--the elevation angle of the shoulder. So the whole thing is basically one dot product to determine which two quaternions to slerp between, one slerp, and then (because I allow for the user to input any arbitrary axes) rotating the pretransformed object into target orientation. So as I hinted, when I use this in real-time I can sort of cut out the last step because all of my characters are oriented the same way and use the same raised angle offset value. My approach also doesn't require the additional setup of CS or an approach using the Comet plugin since my target quaternions are all defined in code ;)

As for your other point I am not entirely sure I understand. It sounds like what you are talking about is a related, but ultimately different problem. If you use my plugin with the default raised angle offset of 45 (or even use like 90 or something) then you never rotate an arc larger than 180 degrees between the first twist joint and the animated joint in a normal human range of motion (so I suppose a cartoon character could if his arm spins wildly for some reason). If you use a negative raised angle offset then it might be possible like if the character is doing a pull-up or trying to get a weapon from behind his back.

At any rate I wasn't too worried about this because I think there are solutions out there for interpolating a larger arc (and that's not what my plugin is designed to do) and a negative raised angle offset isn't really how a normal human arm works anyway. When I raise my arm above my head, the skin that was on the top of my deltoid when my arm was outstretched slides around to the back (somewhere around my 45 degree raised angle offset).

mlefevre
01-12-2009, 10:12 PM
Neat plugin! Can't wait to test it out further. I'm still trying to get my head around the problems that arise with the shoulder. Not had much exposure to it as of yet.

Had a quick look through your blog, definitely bookmarking!

Thanks!

ShadowM8
01-12-2009, 10:20 PM
Thanks for the explanation.
As far as I recall CS SDK does not give much clues as to how their roll joints operate.

I agree that for a humanoid character 180 degrees of freedom is sufficient, however greater range of motion never hurts as animators always like to push their poses beyond what would be expected.

While it definitely saves time having the target quaternions defined in code, allowing the users to interactively store these poses would make this even more robust!

Overall it was great to see how tackled the problem.

I'm working on a Maya implementation of my solution so hopefully it will be available soon.

AdamMechtley
01-16-2009, 11:38 PM
Hi again everyone,

I just completed a hip constraint with similar setup tools, which you can download from my site here: http://6ixsetstudios.com/downloads/python/AM_HipConstraint.zip

While working on it, I found a pretty unforgivable error in my original math for the shoulder constraint, so I put up an update for it as well. You can get that here: http://6ixsetstudios.com/downloads/python/AM_ShoulderConstraint.zip

Thanks again for any feedback!

archanex
01-17-2009, 07:46 PM
I'm a bit confused as to what this plugin actually does?, you said

I've seen a lot of other implementations for this problem, but a lot of them are prone to flipping (either because they use euler angles or a static up-vector constraint) or are a pain to set up because they are partially data-driven or add a bunch of extra transforms to your hierarchy (like Max's character studio or Michael Comet's pose deformer)

however I'm not sure what problem you are referring to specifically. It seems to be something with twist poses?

eek
01-17-2009, 10:48 PM
Hi Shadowm8,

In principle what I am doing is not terribly different from Character Studio's implementation. It's hard to say exactly since I am on a Mac and I don't know if CS's implementation is available or described in the max SDK or documentation anyway. I will go out on a limb though and say that what I am doing is a far simpler algorithm. CS defines 6 twist poses and, as I understand it, blends from one to another based on the current orientation's arc relative to the barycenter of the nearest 3 orientations with a twist pose defined for them. That means there is a lot more math and an extra slerp at any given time.

As you can see in my code, I am defining 3 target quaternions and the whole thing is driven by a single input--the elevation angle of the shoulder. So the whole thing is basically one dot product to determine which two quaternions to slerp between, one slerp, and then (because I allow for the user to input any arbitrary axes) rotating the pretransformed object into target orientation. So as I hinted, when I use this in real-time I can sort of cut out the last step because all of my characters are oriented the same way and use the same raised angle offset value. My approach also doesn't require the additional setup of CS or an approach using the Comet plugin since my target quaternions are all defined in code ;)

As for your other point I am not entirely sure I understand. It sounds like what you are talking about is a related, but ultimately different problem. If you use my plugin with the default raised angle offset of 45 (or even use like 90 or something) then you never rotate an arc larger than 180 degrees between the first twist joint and the animated joint in a normal human range of motion (so I suppose a cartoon character could if his arm spins wildly for some reason). If you use a negative raised angle offset then it might be possible like if the character is doing a pull-up or trying to get a weapon from behind his back.

At any rate I wasn't too worried about this because I think there are solutions out there for interpolating a larger arc (and that's not what my plugin is designed to do) and a negative raised angle offset isn't really how a normal human arm works anyway. When I raise my arm above my head, the skin that was on the top of my deltoid when my arm was outstretched slides around to the back (somewhere around my 45 degree raised angle offset).


Yes this is basically what i do i.e your blending between quaternions using a dot product essentially. (Theres a post about it on my a blog a while back) - and hell i borrowed this idea off valves rigs :)

The neat thing with this system, is that you can use one system to ride another, i.e you drive one system with this which inturn is the driver for another copy riding it - the power of this is that you can do twist and deformation systems in one to simulate wrist like deformation really nicely. So one system becomes the spaces for the second to work it but at a slerp'd value of 0.5. I.e the wrist twists about its bones but also deforms about its muscles. You can use these 'cradles' as i call them everywhere.

AdamMechtley
01-19-2009, 04:47 PM
Archanex: I plan to do a video for this at some point in the future to explain this step by step, but I will try to do it here with words briefly. Basically, when you create a twist structure for a shoulder, you have a bunch of twist joints between your first twist joint and your shoulder that just blend between these two points. So your first twist joint is like 0 percent twist and your shoulder is 100 percent twist. Three twist joints in between would be weighted to each extreme like 75-25, 50-50, and 25-75. This is all pretty self-explanatory, but the problem to solve is: what is the first joint, the one that is 0 percent twist, supposed to do? How do you control it and what does 0 percent twist look like?

If you try to do this with euler angles, i.e. your first twist joint just inherits pitch and yaw from the shoulder but always has 0 rotation around its twist axis, you have two problems. First, if the shoulder gimbal locks, then this joint will screw up. Second, your first twist joint and your shoulder joint can easily be over 180 degrees apart in a normal range of motion (like doing a pull-up), which would make the twist joints in between flip.

Another possibility is to make the first twist joint an aim constraint and constrain its up vector to something like the collar bone's up axis. This configuration also has two problems. First, your aim axis will cross your up-axis when the arm is lowered to the side, causing the first joint to flip. Second, again, your first twist joint and your shoulder joint can easily be over 180 degrees apart in a normal range of motion (like doing a pull-up), which would make the twist joints in between flip.

Therefore, one common solution is to define discrete "poses" for the twist joint when the shoulder is rotated certain ways. Character Studio in Max allows this, and the same thing can be accomplished with the Comet Pose Deformer in Maya. Although this works, it requires additional setup time to define poses, and requires a fair amount of CPU cycles (which may matter if we want to do this procedurally in a game).

What my plugin does is basically the same thing as this last possibility. There are certain "poses" for the twist joint when the shoulder is rotated certain ways. However, the key difference is that I decided that these poses are roughly the same in humanoid characters, and so do not require the setup and the implementation of data storage/retrieval. The poses are defined programmatically in code. Similarly, because of how I set the rotation of this twist joint, I do not need poses for a bunch of different shoulder orientations or the computational overhead to blend between three arbitrary quaternions. The whole process is basically one dot product to find the current elevation angle, one slerp based on this dot product, and two cross products to set the final rotation. While it is possible to accomplish this in Maya using out-of-the-box tools, the plugin eliminates the requirement of having a bunch of little "helper objects" in the hierarchy to perform the calculation.

Eek: It is good to know that I am not crazy and that other people are in fact doing similar things ;) I originally implemented this some years ago for a WWE game for THQ and never really talked much about it to anyone because I thought at the time it was a pretty obvious/intuitive way of handling this and figured it was what most people did. Since then, however, I still saw a lot of implementations that struck me as overly complex or somehow limited and so thought it might help to freely share this with people.

I'm not entirely sure what you mean with the forearm though. I set the rotation of the final forearm twist joint with two cross products and then just slerp all the joints in between. I actually just put up a video on my site about this if you're interested: http://www.6ixsetstudios.com/tutorials/rigging/#02_forearm

eek
01-19-2009, 08:22 PM
Eek: I'm not entirely sure what you mean with the forearm though. I set the rotation of the final forearm twist joint with two cross products and then just slerp all the joints in between. I actually just put up a video on my site about this if you're interested: http://www.6ixsetstudios.com/tutorials/rigging/#02_forearm

I mean, that by having one of these system ride ontop of another you can simlate, both twisting of the forearm and deformation, i.e up/down, side to side rotation inconjunction with the twist. :)

NolanSW
01-20-2009, 06:21 PM
Hey Adam,
Ok, I understand the need for the forearm setup but for the shoulder setup, I'm not sure if something this complex is needed. I'm not trying to invalidate you work here but to better understand.

So to clarify the problem with the shoulder twist is we want a twist joint to be negated by any twisting by the shoulder so that we have a nice smooth transition from the shoulder and the bicep/forearm. The problem with constraints in this area is that you need extra nodes to keep the up axis consistent and so that the constraint does not flip. Is this correct? Anybody feel free to jump in.

I'm just trying to see if this setup has any more advantages over the one I currently use.
For my setup I duplicate the shoulder joint, delete the children and direct connect the 2 non-twisting channels from the shoulder to the new no-twist joint. So this new joint will bend and rise but not twist with the shoulder. Then I transfer some of the skinning from the shoulder to this new no-twist joint. Plus to avoid gimbal lock I change the rotate order for the shoulder and no-twist joint to YZX (Y = Rise, Z = Bend and X = twist). Thus X will be the last rotation to be evaluated.

AdamMechtley
01-21-2009, 04:13 AM
Hi Sean,

Thanks for posting. Do you have an example of what you mentioned here? I followed your instructions as well as I understood them, and I get a totally borked result for the twist joint unless the rotation order on both the shoulder and no-twist joint has the twist axis first. If the twist axis is first, then of course I still get problems with Gimbal lock.

The second issue is that you do in fact want this first twist joint to twist somewhat when the arm is raised (this is what the raised angle offset attribute in my plugin does). If this does not happen, your intermediate twist joints will bork out if your character's arm is raised and his elbows are to his front, as when doing a pull-up or uppercut or something. This is of course because the up axis on your no-twist joint and the up axis on your shoulder joint are 180 degrees or more apart. Moreover, as I mentioned, this approximates how your deltoid slides into a posterior location when your arm is raised. Now, theoretically, if what you are saying works, maybe it is possible to write a simple expression to do this if the arm is raised above 0, but even then I don't know how you could make that work very easily unless your twist axis is first, in which case you are back in a situation of possible Gimbal lock.

As for how complex this is, I think this can only really be called complex in the sense that I wrote a plugin for it instead of using out-of-the-box tools. Setting up my constraint is only as complicated as setting up an aim constraint (in fact, all my plugin really is, is an aim constraint that blends between different up vectors in code based on the elevation of the shoulder). In terms of the math, what I am doing is actually much simpler than how Maya handles euler angles, especially for joints which have an additional pretransformation. For those of us working on games then, this has the added benefit that my algorithm is very few CPU cycles if we want to make it evaluate in real-time.

eek
01-21-2009, 06:10 AM
Hi Sean,

Thanks for posting. Do you have an example of what you mentioned here? I followed your instructions as well as I understood them, and I get a totally borked result for the twist joint unless the rotation order on both the shoulder and no-twist joint has the twist axis first. If the twist axis is first, then of course I still get problems with Gimbal lock.

The second issue is that you do in fact want this first twist joint to twist somewhat when the arm is raised (this is what the raised angle offset attribute in my plugin does). If this does not happen, your intermediate twist joints will bork out if your character's arm is raised and his elbows are to his front, as when doing a pull-up or uppercut or something. This is of course because the up axis on your no-twist joint and the up axis on your shoulder joint are 180 degrees or more apart. Moreover, as I mentioned, this approximates how your deltoid slides into a posterior location when your arm is raised. Now, theoretically, if what you are saying works, maybe it is possible to write a simple expression to do this if the arm is raised above 0, but even then I don't know how you could make that work very easily unless your twist axis is first, in which case you are back in a situation of possible Gimbal lock.

As for how complex this is, I think this can only really be called complex in the sense that I wrote a plugin for it instead of using out-of-the-box tools. Setting up my constraint is only as complicated as setting up an aim constraint (in fact, all my plugin really is, is an aim constraint that blends between different up vectors in code based on the elevation of the shoulder). In terms of the math, what I am doing is actually much simpler than how Maya handles euler angles, especially for joints which have an additional pretransformation. For those of us working on games then, this has the added benefit that my algorithm is very few CPU cycles if we want to make it evaluate in real-time.

With valve if i remember in one of there interviews it ran off the gpu - so yes its pretty quick.

NolanSW
01-21-2009, 04:45 PM
Oops, your right. I told you the wrong rotate order. Our software here uses the opposite rotate order so I was getting them confused. So the rotate order was XZY ( then maya evaluates it in YZX order).

I don't have the file available to upload but of you take a look at some of the video here you should see some of the deformation.
http://www.snolan.net/2009/01/17/copytransfer-skin-weight/

AdamMechtley
01-21-2009, 05:45 PM
Hi Sean,

Thanks for clarifying, but I still get problems with the method you are describing. In the example I am testing, I have X pointing toward the elbow and Z pointing toward the character's front. So on my left shoulder Y points up when the arm is out the the side, and on the right shoulder Y points down when the arm is out to the side.

If I make the the rotate order XYZ or XZY I get problems.

XYZ: Rotate the shoulder forward so it is pointing forward. Now rotate the arm slightly in either direction up or down and you will see the Y-axs quickly flips out of place between being on the outside of the body and the inside of the body.

XZY: Adduct the shoulder so it is pointing down and then rotate it up so the shoulder is pointing forward. The Y-axis on the twist joint is now pointing down instead of up.

Using my plugin in either of these situations behaves correctly though.

Eek: Yes the dot-product is a single cycle operation on a GPU (or on a VPU on a console), which also means the cross-product is only three cycles :)

ShadowM8
01-21-2009, 07:30 PM
Hey Adam,
Ok, I understand the need for the forearm setup but for the shoulder setup, I'm not sure if something this complex is needed. I'm not trying to invalidate you work here but to better understand.

So to clarify the problem with the shoulder twist is we want a twist joint to be negated by any twisting by the shoulder so that we have a nice smooth transition from the shoulder and the bicep/forearm. The problem with constraints in this area is that you need extra nodes to keep the up axis consistent and so that the constraint does not flip. Is this correct? Anybody feel free to jump in.

I'm just trying to see if this setup has any more advantages over the one I currently use.
For my setup I duplicate the shoulder joint, delete the children and direct connect the 2 non-twisting channels from the shoulder to the new no-twist joint. So this new joint will bend and rise but not twist with the shoulder. Then I transfer some of the skinning from the shoulder to this new no-twist joint. Plus to avoid gimbal lock I change the rotate order for the shoulder and no-twist joint to YZX (Y = Rise, Z = Bend and X = twist). Thus X will be the last rotation to be evaluated.

Sean,
The problem with that setup is that it assume X always represents the spin, which is not the case. Rotating on just the Z and Y can introduce the spin around the X axis even if the value of X is 0. This is why a more complex solution is needed.
So for example, stretch your arm out palm down, bring it beside your body and then move it in front of it. You palm is no longer flat with the floor. Thus a 90 degree of rotation occurred around the spin axis yet your X value didn't move. This is why you saw the compensation in the capture I posted of my constraint plug-in.

tonytouch
01-21-2009, 08:48 PM
I'm not entirely sure what you mean with the forearm though. I set the rotation of the final forearm twist joint with two cross products and then just slerp all the joints in between. I actually just put up a video on my site about this if you're interested: http://www.6ixsetstudios.com/tutorials/rigging/#02_forearm

that is a very nice tutorial ( +180/-180 forearmtwist ) !

------------------------
for the shoulder- and hip-Constraints :
thanx a lot -> for sharing your API_Python-Knowledge ( quaternion-rotation )

ONE THING :
i figured out a strange-behaviour , and i am not sure , from what this might come .

e.g. inframe 12 , if the rotateX-value of to joint is between 0 and -.51 , the whole system collapses - it might not be very often the case - that the X-rotation , is in that range - but maybe you find it interesting to know .

currentTime 12;
setAttr "ex1Root|LeftHip.rotateX" -0.01;
setAttr "ex1Root|LeftHip.rotateX" -0.51;


maybe this info helps you , in order to make the constraints even more powerful :)

AdamMechtley
01-21-2009, 09:05 PM
Simon,

Thanks! The problem you have noticed is actually a problem with Maya's orientation constraint on the intermediate twist joints, which I currently force to use the "shortest" interpolation type. If you change This type to "no flip" then the problem disappears. I will look into this and find out which interpolation type works 100% of the time and force it in my setup tool.

Herzlichen dank! Ich überprüfe das Problem schnellst möglich!

eek
01-22-2009, 06:02 AM
If anyones interested (and ill probably do it anyway) i can post the math on my blog. I tend to use laTex but its not supported here, but basically:

You have a center and a driver, and set of transforms relative to this center. Essentially your doing a summation of interpolated quaternions using the arccos of a dot product, which is produces a weight. Since my angles (from the dot product) only exist in a [0,90] degree range -

ang = (acos (dot n1 n1)
for i = 1 to quats do ( r += slerp quat 0 0 0 1 quats[i] ang (where 0 > ang > 90))

Now you could also normalize the t weights , putting them into a normalized weight space so if you summing three quats, with arcos of say 45 degrees, thats 0.5, if you where to normalize this it would become a summation of weights = 0.33 across the angles. Like so:

for i = 1 to t.count do (sum += t[i])
t[i]W = ti/sum

So youd be summing the the slerp'd quaternions using normalized weights, which could i think give cleaner blending across your summed quaternion space.

As for twist its a simple extension of the system, you use a direction relative to the summed quaternion, to dictate the upnode for the twist down the shoulder or thigh. The wrist does this too, but you dont need the direction you can use the relative space of the summed quaternion to the drive, and rip off the x rotation. It'll never flip out or pop, and inturn layering another summed quat on top gives you up/down side to side deformation for free.

As said lots of companies, valve, softimage, cs have done it before - its the most stable method of this type of deformation i know. And its very customizable.

As you said , i guess its a 'summed quaterion upvector derived from a set of vector way points'.

CGTalk Moderation
01-22-2009, 06:02 AM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.