10 March, 2012

Group Constraint Update

It has been a great learning experience while building this constraint system. As I posted before, I have been working on a constraint that allows dynamic relationship between a group of transforms. Hence I have called this a "Group Constraint". I think this constraint will be very useful in building rigs where the driver in the group needs to be switched during animation. One such example could be a foot rig where multiple contact points are required. This constraint should allow bi-directionality(or multi-way directionality) so that between two or more transforms anyone can be the driver at any time during animation.

First step for me was to find out how to calculate the final output when switching driver transform in the group. I was suspecting that I would need some complex calculations, however after doing some research and learning a great deal about visualizing matrix multiplication I found that I just needed a simple calculation. Having a proper structure for attribute also helped in simplifying the calculations. First I used python to experiment and confirm that my logic would work the way I want and then I started building plugin using C++. I finished the basics first, building input-output attribtue structure, using setDependentsDirty to establish relationship and reading the attributes from DataBlock. When I tested this code I always got zero values in all the attributes in compute method. Later I found what the problem was and how to fix it (read about it Here).

Now I have got basic switching between drivers working. And during the switch all the offsets maintained. One of the advantages of the method that I am using is that all the followers are free to move while being controlled by the driver node. This allows for more freedom in animating. My main goal is to make this system such that there shall be no pop when switching between drivers, even when animation keys are updated. I am also looking for a way to avoid having to key driver attribute twice in consecutive frames to make a switch. Enum attribute type allows such stepped keys as default behavior, however the challenge is to calculate the output at this switch point. The way Group Constraint stores the offsets and how final output is calculated should allow for such feature, but I have to test it to see if it works in all cases.

The biggest challenge and what will actually be the backbone for many of the features is linked with detecting animation events. Exo Switch Constraint also utilizes callback functions to detect keying on the driver (master) node. However, I intend to use it for different purposes and it will allow me to correct any pop at the switching points when animation is updated. There are two options available in Maya to listen on animation update events:
  1. Listen for changes in all animation curves and in the callback check if animation change is related to the constraint node.
  2. Listen for only particular animation curves that are linked to input transforms of the constraint.
First option is actually simpler in a way that I don't have to keep track of animation nodes connected to constraint's inputs. However, it can affect the performance if a lot of attributes are being keyed which I do frequently while animating and I am sure many animators do too. So I have decided to take the second option. But it comes with a price of complexity. Second approach requires to detect when the animation nodes get connected to (or disconnected from) each individual element of  transform's translate, rotate and scale attributes. When connected we need to add callback for each animation node. And when animation is removed each of such callbacks should be removed for that constraint input. The callback function should know which input the call belongs to and process the event accordingly. Depending on number of inputs it can reach up to 30-40 callbacks. So I need to generalize the callback function. To do so I decided to pass some specific information(meta data) to this function in (void*) clientData pointer when callback is added. However, I need to allocate memory dynamically for such data so that it persists beyond its local scope. And this allocated memory needs to be freed when we remove the callback, otherwise we will get memory leakage. So it gets a little bit complicated to pass info through pointers during event calls and freeing the memory when we no longer need the callbacks. But when done it will be a flexible solution to manage multiple callbacks.

I haven't gotten around to create a demo of the first stage yet. But I would really like to share it some time soon to get some feedback. Let me know if you guys have any particular feature in mind for such constraint.


  1. This sounds pretty interesting, cant wait to see a demo.

  2. I'm wrestling with the fact that regular space switching and dependent switching may have to exist in isolation.

    1. I think there is no optimal solution to this problem that we would like. And we need to incorporate some tricks that we might not feel comfortable to introduce. It would be interesting to see what you come up with.

    2. Yes its a tricky one - with the exotools constraint it seems its really all about one grand master of the system you switch between. There is a 'constraint' node that holds all the keys of the switches and by the looks of it all the animation keyframes too.

      Now the issues i see are really about understanding usability -Firstly, internally you can either push the offset to the driver/driven parent space or keep its parent space intact and push the offset to the grandparent i.e a group in world space. I personally like to keep the parent space intact - not a fan of animation frame popping a frame before/after.

      Secondly it seems with the exotools there no real notifications working - i don't get a sense that this object is the new master of the system and this is where the system was in before the switch other than the drop-down list. Maybe this is ok because the master can only be one of the node in the system e.g.

      Your system comprises of nodes a,b,c & d

      When the master is a, b c & d all switch to get driven by a. When b becomes the master its transform is invalidated and the rest of the other nodes switch. And so on..

      Now the issues is of hierarchy:

      If we have a, b, c in our 'pseudo' system, with a being able to switch between b and c. And b being able to switch between a and c - (i.e a dependency of a-b and non dependency of c) if a switches to be driven by c at frame 20 and b does too how do you edit the keys if there all in one constraint?

      When the constraint is on one node, with multiple non-dependent targets each switch key corresponds to only one of its targets.

      Likewise with a dependency system only one of the nodes is the master of the system. The keys of the switch are just to invalidate the transforms and re-validate a new drive. Crucially in both of these there is a sense of 'sync'

      Only one target in the none-dependency system
      Only one driver in dependency system

      How these can talk to each other is something my brain is slowly unraveling - in truth a target in a non-dependency system can be a master in a dependent system if its known about.

      Its sort of like a crowd, you can have several groups talking internally between themselves, but you can also have a 'speaker' steering everyone when the time comes. Its the notion that the people in the internal groups know about the speaker but don't act on it until clear notification - i.e invalidate there internal messages/transforms and switch to main speaker/driver. Switching between a non-dependent to a dependent mechanic.

    3. I got the basic switching ready and it was actually quite easy. But I think the dependency issue gets more complicated when you add time into the mix. Also, think about user jumping frames, in that case we should have some knowledge of not only who the master is but about the last switch before current frame. At least that's what I have come to conclude based on my logic.
      Dealing with user feedback is important too, but I am thinking to add it at the last stage. It can be done using some custom locator for display that highlights the current master.

    4. In the exotools, it has a option to select the current master during playback. I think that should work quite well.
      In a way, the need to make sure everything is synced is counter to the way maya's lazy evaluation system. So while i was developing my system, I have to find way around it and make sure things are synced up by using callbacks.

  3. I am also writing my version of bi-directional constraint. I reference many of the idea from the exotools constraint. My plugin can now do switching between drivers, and also turn on and off individual objects without popping. I am left with how to manage the keyframes

    I am debating how i should manage the offset translate and rotate keys. Should they be "linked" to the driver attribute (or the on/off attribute)? When you delete a key from the driver graph, should you automatically delete any offset key found or should you allow the user to do it himself. Any thoughts?

    I am also curious about the logic behind your system, mainly how u prevent any dependency cycle. I don't mind sharing my idea (I feel idea should be free !) though i can not share my code though since i am developing for my company.

    1. It took me some time, but I found a solution using a technique I posted some time back on my blog (http://leftbulb.blogspot.com/2012/01/freedom-of-constrained-node.html) to solve cyclic dependency.

      For me dealing with animation is the tricky part. It's not so much about what to do, but how to track consistently without introducing bugs. I am not keying anything for offset, I just store transforms with a keyframe number. Depending on your implementation you might have to track all the key changes and reflect that change where you key the driver.

      I am also curious to know how you are dealing with cyclic problem. If you could share it would be great.

    2. The main cyclic problem i encountered in my initial development is that i am dirtying the wrong outputs (outputs to objects that are not driven - the drivers and the objects with weight 0) and there is no way for me to find out consistently who is the driver in the setDependencyDirty. It is because the value of the driver attribute may not be the latest if u query it. In the end, i solved it by using a python instance variable. Thats the main problem i wrestled with for quite a long time. Haa...

      You mention u are storing transform with keyframe number? do u store it as data in your node? So what happen if the user want to shift the keyframe for the driver? Will you system also change the keyframe in your transform data?

    3. For me cyclic dependency isn't the issue - basically your invalidating the transform (ui, button press), then changing the switch.

      Generally this 'invalidation' process is definable, i.e detach (invalidate) from system, set as master (attach to it).

      The problem is un-controllable invalidation, i.e changing/deleting keyframes - After watching a rigging dojo interview with Jan Berger of MCS something tells me im on the right track. Basically in MCS when an invalidation isnt allowed eg. A switched to B when B is already switched to A a warning is thrown.

      How i check for this warning is an interesting one. Basically you need to check the validation of the switch from changes via keys before the messaging to do the switch fires and throw a warning or undo.

    4. Charles,
      The most challenging part I have found is the updating of offsets when a key is changed and there are so many cases one needs to consider for such scenario. Also, if you want to eliminate pop, you have to match the nodes correctly at each switch, however this can cause cascading effect in existing animation. So if a user goes into graph editor and moves translate keys on some early keyframe then we have to update all subsequent driver change keyframes to make sure all the drivers match when switching. I will write more on this in my next post.