The second project where I was given the opportunity to deploy pose space deformation (term that i will now refer as PSD ): was for an animated TV pilot “margouillaz” by Tik-Tak_production/Ulrich Boyer.
A margouillat stands for the name of a local lizard species that lives under the roof of most house in Reunion Island.
For this project i was assigned the task of creating the body skinning and deformation of the main character, Frederic Brun-Picard was responsible for the character setup, and facial deformation.
– ( Above : several view of the shoulder region in action : the mesh has been subdivide twice and the head was removed in order to focus on the body deformation ) –
My first moves when I start working on this character was to stay away from PSD for ball joint ( think shoulder or hips ): my last experience was a bit of nightmare and I was cautious not to reproduce the same errors.
My client was using maya for their character related work : It was important to take some time to translate what I learn in 3dstudio max about character setup and deformation into the equivalent maya procedure.
Another point to consider was that as a contractor the puppet I work on was to maintain good deformation on a large range of motion, I didn’t have the luxury to polish the look of the deformation with camera based deformation correction. My last resort was to reconsider my opinion and to rely on a difficult technique where good results are guaranteed at the cost of time.
The main challenge for this project was therefore to develop a more efficient workflow to create :
- a more robust twist extraction mechanism
- a more compact and bulletproof pose reader
- a method to manage and mirror PSD data and setup
- a mesh torsion workflow
1)Mesh torsion workflow:
In this project I was given the freedom to modify the topology of the main character to better meet my requirement:
- this mesh was to be as the same time light enough to sculpt correctives shapes quickly, but still allow the desired shaping of articulation .
- the skin of the character must convey the presence of structural element beneath it even simplified or anatomically incorrect.
- as a toon character, the body region must have enough division to allow proper squash and stretch .
- the body region needs also enough division to distribute smoothly its twisting
The first step to achieve my goal was to partition the body in order to isolate each zone from each other.I was then free to start dealing with the tail and spine region without fearing to loose any work done.
As general rule , I assign one twist bone per division in the length of a body region, the key idea is to split the torsion evenly across multiple elements.
In the picture above we can see a well know phenomenon in the world of linear blend skinning , the candy-wrapper effect : the arm loose its volume while twisting.
The easiest workaround for this type of character with very simple arm shape is to very carefully craft the scaling of the twist joint and link it to the amount of torsion found in this limb.
This relationship can be created through the use of expression, set driven key, or a network of nodes.
I was not happy with this solution: what I wanted was a procedure to handle this specific task automatically , and also a procedure that can be share and reuse on other character. The first step to build this tool was to investigate the collision deformer available in maya:
- The oldest and straightforward tool built in maya is the sculpt deformer
- The second and most recent tool is Michael’s Comet cmuscle system
The main idea was to split a complex problem into several simple operation , in this case repelling a set of vertices to maintain roughly the volume of this limb.
At a higher resolution level the result of the collision deformer was fairly good, but its was giving poor result in my current setup : the different body piece was meant to be use as influence object in a contiguous mesh with the same number of division. This render mesh was then subdivided in the rendering phase of the project.
After some test it became quite clear that there wasn’t enough division in several body part to support a wide range of torsion .The solution I came up with was to split my process even further and to add an extra step in the previous workflow:
- the character mesh is partition into several region.
- these polygonal object are then skinned independently with a skinCluster and fine tuned with a pose space deformer.
- next we use these body region as influence object in contiguous mesh with the same number of division.
- at this point the mesh is subdivided with a low iteration value .
- in the last step we compute the necessary collision detection , and perform some point relaxation .
2) Twist extraction mechanism:
One day I found in cgtalk forum, a link to a website that was covering the creation of a lookAt constraint in a script controller. In his website Felix Joleanes describes a method to fix the flipping issue that usually arise with twist bones.
More information about euler, matrix and quaternion theory can be found at the following address: ( http://joleanes.com/tutorials/flippingless/flippingless_01.php )
What really impressed me at that time was the robustness of his solution, and the amount of commitment shown explaining every part of his script. All the necessary information were here to convert his script into a full blown regular maya node.
As quick remainder the script explained by Felix was:
theTargetVector=(Target.transform.position * Inverse Parent.transform)-NodePos.value theAxis=Normalize (cross theTargetVector [1,0,0]) theAngle=acos (dot (Normalize theTargetVector) [1,0,0])
– ( Above : gif image from “http://joleanes.com/tutorials/flippingless/flippingless_02.php” illustrating a quaternion built from an axis and an angle) –
The conversion in python was pretty straightforward and was working as advertised:
theTargetVector = targetPosition_Hdle.asVector().normal() referenceVector = OpenMaya.MVector(1,0,0) theAxis = OpenMaya.MVector() theAxis = referenceVector ^ theTargetVector # We can take avantage of maya API : instead of using # the arc cosine of the projection of theTargetVector onto referenceVector # use the angle function found in maya MVector class. theAngle = referenceVector.angle( theTargetVector ) aimQuaternion = OpenMaya.MQuaternion(theAngle ,theAxis) eulerRotationValue = aimQuaternion.asEulerRotation() outputHandle.set3Double( math.degrees(eulerRotationValue.x), math.degrees(eulerRotationValue.y), math.degrees(eulerRotationValue.z) )
Although the code above is perfectly valid, when I created this node, I was not really understanding its inner working, and was mislead by the previous picture , it simply returns the rotation of referenceVector into theTargetVector about their mutually perpendicular axis.
All the magic and smooth interpolation behavior is derived from the quaternion created from this two vector and not from an axis/Angle value.
theTargetVector = targetPosition_Hdle.asVector().normal() referenceVector = OpenMaya.MVector(1,0,0) aimQuaternion = OpenMaya.MQuaternion() aimQuaternion = referenceVector.rotateTo(theTargetVector) eulerRotationValue = aimQuaternion.asEulerRotation() outputHandle.set3Double( math.degrees(eulerRotationValue.x), math.degrees(eulerRotationValue.y), math.degrees(eulerRotationValue.z) )
– ( Above : the same code was refactored with the inclusion of the rotateTo function of maya MVector class ) –
( Above : two examples of this custom dependecy node )
Ground in trigonometry this quatermain (contraction and play on words from quaternion /aim, in reference of Richard Chamberlain starring in Shogun and Alan Quatermain ) node inherits the same limitation: the cross product of 2 vectors or the angle between them has a valid range from 0.0 to PI radians ( 0.0 to 180.0 degrees ) , hence the singularity happening when the target vector points in the opposite direction of the reference vector.
In a practical application , when the target vector enters the cone of singularity , the interpolation behavior remains the same but is more abrupt. Its seems more safe to limit the valid range of motion of this node outside this security zone.
3) Pose reader :
In maya 8.5, Alias has interfaced Python to the C++ Maya API , enabling more people to access maya core functionality with an easier language . This Python Maya API can
- be used in combination with regular python commands to modify a maya scene.
- create scripted plug-ins to add new functionality to Maya.
- create standalone scripts that can be run from the command line.
Slowly over the years i start to create several custom deformer and nodes , in order to be more productive and test new ideas in the area of character deformation.
This was possible with the following online resources:
- http://groups.google.com/group/python_inside_maya : a mailing list with a lots of interesting discussion on the python maya API.
- http://www.comet-cartoons.com/3ddocs/mayaAPI/ : michael comet notes about compound and array attributes was invaluable.
- http://nccastaff.bournemouth.ac.uk/jmacey/RobTheBloke/www/mayaapi.html : An in depth introduction to the maya API by Rob Bateman , we are now fully aware that ‘if we are using MPxCommand we are doing it wrong’ .
- http://www.davidgould.com/Books/index.html : Complete Maya Programming I & II was also invaluable to start studying the API.
- http://web.archive.org/web/20050308105432/http://www.ewertb.com/maya/api/ : a web cache of Bryan Ewert How-To’s and Tutorials .
My goal for this project was to use michael Comet’s poseDeformer with 2 dependency nodes to fit it in an additive corrective shapes workflow. This open source plugin can be retrieve at:
- http://www.comet-cartoons.com/maya.html : plugin version for Maya 6.0 to 8.0
- http://www.djx.com.au/blog/downloads/ : plugin for All version of Maya , kindly recompiled by David Johnson.
- http://www.tokeru.com/~/MayaRigging#poseDeformer : a tutorial to grasp the basic concept of this tool.
The regular workflow of this poseDeformer in maya involves 2 plugins and their corresponding script and user interface :
- the poseDeformer which can properly stores shapes in joint space and reapplies them after the skinCluster.
- the poseReader that read pose angles to drive sculpted corrective shapes.
The first part of this workflow is really solid. All the steps are clearly defined and the user interface enables us to manage all the attributes and shapes database in an efficient way.
The second part of the workflow was more challenging : Each poseReader was to be configured with a proper maximum and minimum angle and a proper interpolation mode, to ensure a smooth transition between key pose.
In large dataset ( like the shoulder area where the motion of the scapula and humerus were combined ) I was not able to properly isolate each pose and it was throwing the model off balance.
The two dependency nodes written were an alternate solution to enforce a more stable behavior:
- the key idea was ,around an articulation, to partition the space into several sector which are triggered in a sequential manner. ( a more in-depth explanation of an early version of this custom pose reader can be found here )
Loosely based on a geographic coordinate system,the purpose of the first node was to translate a position into a set of spherical coordinates.
In my coordinate system the North / South direction was replaced by the Up / Down name, and West /East by Front / Rear.
cartesianPosition = cartesian_Hdle.asVector().normal() azimuthAngle = math.degrees( math.atan2(cartesianPosition.z , cartesianPosition.x ) ) elevationAngle = math.degrees( math.asin( cartesianPosition.y ) ) azimuthHandle.setDouble( azimuthAngle ) elevationHandle.setDouble( elevationAngle )
Above we can see a small code portion of this sextant node, an easy way to read it is :
- “first measure the position of a point along the equator line”.
- “then compute the elevation value”.
When the elevation of a sample point is at the zenith of the sphere ( with a value of either 90.0 or -90 degrees ) there is no defined value for the azimuth angle, so I choose to override it with a value of 180.0 degrees .
The second dependency node role was to compute an array of pose weight. This decision was motivated from the following observation:
- My workflow always involves several corrective shapes working together.
- Each shape influence is driven by one or several elements ( an expression , a set driven key or a network of nodes ) that needs to be properly setup
To cope with the sheer number of element involved, it was more relevant to solve the second point by grouping the computation of all the driver object in one node.
In the first part of his computation this hemisphere node execute the following procedure:
- Read a set of spherical coordinates to determine the amount of azimuth and elevation of a sample point
- From their sign split each channel into 2 direction Front / Rear and Up / Down .
At this point ,this node reads for these 4 direction their respective division and range in order to compute a set of sector weight.This list of value is finally written in multi attribute where each component can be access individually and link to a corrective shape.
Above, the sector Front1_U2 -> Front2_Up3 is constructed as follow:
- When a traveling point on a sphere’s surface cross the Front1 and Up2 line , this sector start to be triggered
- and is in full effect when we reach the point Front2_Up3 or when this point remains above the Front2 and Up3 line.
As side note we can immediately see that the weight of a sector is directly related to the weight of the point in its upper opposite corner.
This is a linear interpolation in 2 dimension on almost every part of the sphere except in the azimuth and elevation line where the interpolation is perform from one point to the other.
Each sector override the effect of the previous one : an interesting side effect of this behavior is the smooth interpolation at the pole of the sphere even if we jump from one quadrant to another.
Overall I was quite satisfied with these tools specialized with ball joint management , but it was not enough to deal the complexity of the shoulder region.
In real life the position and orientation of an arm is the result of the combined action of the collarbone, scapula, and humerus. My goal was not to simulate an anatomically correct shoulder but to achieve a stylized yet intricate behavior.
With this illustration i would like talk about the method created to solve this problem:
- As a continuous mesh we can see the torso of the character connected to the head.
- On each side a ball and a cut up arm part
What is important in this setup is that, the ball symbolizing the head of the humerus , is projected from a pivot point onto the torso , to conform and slide along this surface.
The humerus is then configured to inherits only the front and rear rotation of the scapula.
The last purpose of this preliminary work was to mix more easily the contribution of two ball joint. It was thus necessary to modify the hemisphere node to be more generic.
( Above : new version of a sextant node )
To complete this task , the azimuth/Elevation segmentation procedure was transferred into the compute method of the sextant node. Four new output compound attributes was then created to transmit the relevant data to the hemisphere node.
In a same manner of an XML document , a compound attribute in the maya api allow a node developer to group and nest several attribute of any type in an hierarchy.
In my node this attribute was the parent of a string array and a double array. ( As an example when the front division attribute has a value of 2 , the front Prefix array will be fill with [ ‘FR1’, ‘FR2’ ] based on the front Id value ).
It was important to interleave an array of prefix name with numerical value , for debugging purposes, script access and attributes layout in an external object.
After closer inspection it became clear that at its core this hemisphere node was mixing two types of data: a list of compound element independent of all its surrounding and a list that is mixed with each element of this primary list.
To illustrate this concept, one can easily understand that a point cannot be at the same time in a front and rear sector ( or to be general with the other attributes declared as Sector Input ), but can be mix with the sector Up3,Up5 or Down2 ( all the element connected to Variation Input Bundle list Attribute )
To allow a recursive usage of this node the easiest solution was to output the same type of compound data that the sextant node was computing.
After some test, it was then simple to rewrite the hemisphere node with the aforementioned specification.
4)Setup mirror procedure :
Very early in this project the number of elements to manage were overwhelming : skincluster , correctives shapes, duplicate portion of limbs hierarchy …
This issue is traditionally deal with script , and is most useful to mirror all or some character’s element.
Before writing some code, I usually take some time combining regular maya element to achieve my goal, and I was lucky enough to find an intermediate solution that was fast to implement and is stable .
The only drawback was the dirty trick that was involved : the dreaded non uniform scaling ( caution: don’t do it at home, 3dsmax kids ).
Before breaking a rule it is important understand it : The reason non uniform scaling is frown upon and avoid like the plague is because it produces shearing when combined with rotation transformation.
In my case mirroring was done by negatively scaling the rootNode of the body region on the x axis without suffering any odd artifacts.
( notice the nasty behavior of this perfectly round sphere )
To finalize a puppet and mirror a body region 5 steps were required:
- Only one side of a body region was setup and build in an external maya scene file.
- To counter name clashing at import time in the deformation rig scene , every nodes and element are uniquely named.
- To maintain a clean and organize scene, all dependency node are store in a dagContainer that can be parent in carefully craft hierarchy of group nodes.
- At the top level of this body region hierarchy is placed a transform node that can be scale negatively on one axis .
- And finally the body region joint hierarchy is constraint to the relevant element on the deformation rig .
In maya 2010, Autodesk introduced the concept of asset container in maya : ( heavily influenced by Houdini digital Asset ).The purpose of this new node was to enable user to encapsulate DAG and dependency node into a logical entity.
What was useful to me in this project was the related scripting command container flags :
- the nodeList flag which returns all the elements included in this set.
- the connectionList flag which is useful to detect if some nodes where not included in the container at creation time.
Encapsulating a body region in dagContainer was also important to enforce proper naming convention before it was imported in the main deformation scene.
At this stage all the content of the external body region maya scene is imported and assigned a temporary namespace .
Finally the namespace of the body region is converted into a regular prefix name.