February 25, 2012
So Maulik over at his blog is looking into developing multi-directional constraints. I’ve recently started looking into this and wanted to share my thoughts.
Essentially bi-directional/multi-directional constraints are a sub-set of regular transform space switching – crucially the difference comes not in the abstraction and application of spaces but in the ‘messaging’ of transformations. With a standard space switch your basically pushing the source objects transform into the targets and then multiplying it by an offset transform to keep it in its original place. Something like so:
1. We store the transform of B relative to A.
2. We set A’s transform to B’s relative to A’s parent space.
3. We apply the stored offset back.
A = B * A.parent^-1 * Offset
Deriving the type of switch (rotational/entire) is a trivial case of just returning that part of the matrix. With bi-directional/multi-directional constraints everything described above is that same, what changes is the messaging of the transforms being applied. If we take the example of two characters A and B playing a game of tug-of-war, we can see that if they both pull nothing happens. Each is sending its transform notifications to the other and its causing a stalemate. This is our cyclic dependency.
To rectify this you need to control how/when transform messages are sent. On initialization of the system only one party should be sending messages to the other – when the switch happens, you invalidate i.e switch the transform messages being sent to the controlled party with a neutral one e.g world. Now you can send transform messages in the opposite direction from the original controlled party to the controlling one:
1. B sends transform messages to A.
2. Invalidate A (so it’s messages being sent to it are neutral e.g from the world).
3. Send A’s transform messages to B.
If you’re software supports weak referencing things get easier – basically a weak reference (in 3ds max) monitors a nodes transforms without referencing the node directly. Only when changes occur does the monitor tell its actual dependencies – so how this works in practice is that you make the weak reference to the node, (an array of targets for example) but only pass transform messages once the nodes been invalidated. There’s some trickery too – adding a blank pointer to the target so it still updates for example.
1. Two nodes: A and B.
2. A’s targets = #(B,world) – weak references without referencing the targets themselves.
3. B’s targets = #(A,world).
4. A’s transform is set to world, B’s set to A.
5. Invalidate B’s by setting it to world.
6. Set A’s transform to B’s
Once you’ve set the transform, its messages flow through correctly. No cyclic nature happens because its has been invalidated first. I will try to discuss more on the ui side of this – it gets a little tricky..