[Bf-committers] Notes on Action Baking FTR
Roland Hess
rolandh at reed-witting.com
Mon Feb 13 15:37:18 CET 2006
The Action Baking patch has been marked as postponed, pending a
(quite justified) review by Ton of the code (whose quite accurate and
probably more-than-polite comment was that the implementation is
"disputable"). This is a detailed, For-The-Record explanation of what
went into the patch, so that Ton or whoever ends up working on this
in the future can know better what they're dealing with, whether or
not they try to rescue this code or start from scratch.
1. Action Baking was in the back of my mind for BlenderPeople, but
the current implementation came from irc conversations with Toni
Alatalo, who was working on the same thing for Orange.
2. It was determined that Action Baking needed to do two things: bake
object-level motion, keyframed or path based, into bone-level
actions; and bake NLA into a single Action strip.
3. At first, the theory was to simply convert the object-level
keyframes, or curve points if using path animation, into keys for
bones in the appropriate bone spaces. It turned out that this does
not produce correct results. When recreating object-level motion at
bone level, it is necessary to use a sampling rate (1 sample every 10
frames produces passable results), in order to achieve good
approximate motion. Sampled locations can be taken from the object's
matrix every n frames, converted into bone space, and applied to an
armature's root bones. You only need to set keys for root bones (or
bones for which Hinge is set), as the rest of the bones down the
chain will follow the roots appropriately.
4. There were no NLA blending modes that accurately incorporated this
Baked motion into the pipeline, so a new one was created: Multiply.
Currently, Add mode blends bone position and rotations between
overlapping strips, while the default mode simply replaces the motion
of bones further back in the chain with those further forward.
Multiply mode adds the motion, using the previous strips' transform
as the start of transformation for the subsequent Multiplied strip.
The addition of this mode allowed motion baked from the object level
to be used properly in NLA.
5. I quickly realized that building a single Action to encompass
everything in NLA was a non-trivial task. The goal was to produce as
few keyframes as possible while retaining the original motion. The
more keyframes that Baking produced beyond what originally appeared
in the Actions, the less useful it would be. Here is the methodology
I ended up with:
NLA Baking Method
A. Create list of all keyframes set throughout NLA, taking into
account strip scaling, mode (Add, Hold, etc.) and layering.
B. Step through NLA at the keyframes generated by step A.
C. At each step, set keyframes for bones only if they were keyed at
this location in their original actions (adjusted for NLA
time/scaling)
D. Append new Action to NLA
Problems presented in this process were:
A. There was no simple way to get a list of keyframes from NLA. The
following method was devised:
i. Loop through linked list of NLA strips
ii. Create list of keyframes along current strip
iii. Convert list to NLA time
iv. Depending on blending modes and is any future strips obscure this
one, remove a portion of the keyframes
v. Sort list and remove duplicate entries
vi. Add to master list
vii. Repeat i-vi for all strips
viii. Sort master list and remove duplicate entries
B. Upon stepping through NLA, there was no existing way to determine
if a certain bone's Ipo value was based on an exact keyframe, or on
an interpolated value, without paging through each Ipo curve a second
time, examining each key poisition and adjusting for NLA time. To
remedy this, the following was done:
i. a "key" flag was added to the SDNA for bones
ii. upon the calculation of bone location/orientation in the already
existing code, a new check is made to see whether or not the location
is the result of a direct keyframe, or of interpolation.
iii. if it is the result of a direct keyframe, the key flag is set
for the appropriate transform of the bone
iv. during NLA Baking, the key flag is used, per bone per frame, to
determine which keys to set
C. Inaccuracies in NLA sampling were causing keyframes to disappear.
The actual frame time of a key in a scaled, moved NLA strip might be
something like 124.62. NLA was set up to only sample on whole
numbered frames. This caused bad sampling. For this reason, do_nla
was renamed do_nla_float, and altered to accept a float value for
it's calculations. do_nla was then added as a wrapper, pointing to
the do_nla_float function, and passing the current frame value as a
float, allowing coders to choose between using the old functionality
(simply calling do_nla), or calling do_nla_float directly, with a
more exact frame value. I know Ton has a big problem with this, as he
wants the frame to be the smallest slice of Blender time, and would
rather solve the time problem some other way, but this was the only
solution that presented itself for the animators' immediate
production needs.
6. The interface is fairly simple at the moment. The user selects and
Armature and an NLA strip, hits Shift-B, then chooses from the popup
menu: "Bake Object to Action; Bake NLA to Action; Bake All to
Action". The first bakes only object-level motion into an Action,
appends it to NLA and sets its mode to Multiply. It also unlinks
object-level Ipos and clears any parents the object may have had that
affected it's motion, so as not to have the motion doubled. The
second bakes the current NLA state into a single Action strip,
appending it to NLA in Replace mode. The third option does the first,
followed by the second, in effect Baking both object level and NLA
motion into a single Action strip.
7. A lack of nicety abounds in the actual implementation:
A. Although I have handled (I think) situations that would cause
crashes, there are probably a lot of fail conditions that I did not
address (didn't check all possible Action/NLA combinations for how
they react to the command, edit mode, etc.).
B. Newly created actions are given crappy default names, and sampling
resolution for object->bone baking is hard coded. Ideally, something
like this would have it's own floating panel, where the user could
set Action names ahead of time, set a resolution, perhaps even
designate an armature object, then hit a panel button to begin the
process.
C. Due to rounding errors and problems with some sections of the
animation code only dealing in whole frame numbers, I had to
introduce a fudge factor into the section where keyframes flags are
generated, leading to extra keys in some situations.
D. The determination of when to start baking object-level motion and
when to stop isn't the best. The current implementation attempts to
find object-level keyframes, then uses that range. Failing that, it
uses the range from first NLA key to last. If you're using path
animation, there is currently no guarantee that your path will fall
within this range.
E. The current code completely ignores bone scaling. Maybe it
shouldn't, but honest to Pete, you shouldn't be scaling bones in
character animation anyway!
F. There is almost certainly a more elegant way to do the function
world2bonespace that a better mind than mine is invited to come up
with.
F. I suspect that I abused the portion of the SDNA for Action strips
that designates strip mode (Add, Replace, Multiply). I do know that
what I did works. I also know enough to realize that it's not the
preferred method, but not enough to change it.
G. I believe I eliminated memory leaks from the function that builds
the list of NLA keyframes, but as this was my first time dealing with
creating, manipulating and destroying lists, I may have missed
something.
So, to whoever works on this problem in the future, I hope these
notes will be helpful!
--
Roland Hess - harkyman
More information about the Bf-committers
mailing list