When using the NLA editor for layered animation, it defaults to REPLACE blend mode. When you switch the blend mode to ADD (or SUBTRACT/REPLACE), Blender doesn’t change the values or read them differently, it simply performs those math functions on the curve values as they were set.
When new keyframes are inserted, they also insert for the REPLACE blend mode.
I made an addon that automatically tweaks keyframe values according to the set blend mode. So, when an object’s value is 4 and you move it 3 units forward, making the final value 7, and the blend mode is ADD, it doesn’t add 7 + 4 = 11, but instead adds 4 + 3 = 7 (the intended value).
The python math I use is:
diffference = (abs(old_value-new_value) * (-1 if new_value < old_value else 1) )
if blend_mode == 'ADD': keyframe_value.co[1] = difference;
if blend_mode == 'SUBTRACT': keyframe_value.co[1] = difference * -1 ;
old_value = combined curve value from the previous NLA tracks (using math copied from how Blender already reads track layers)
new_value = The currently set property/value, which by default, the keyframe is set to.
(note: I perform extra steps when blend is MULTIPLY)
When I try to apply this same math/logic to the NLA’s curve reader values in anim_sys.c, instead of modifying the keyframes (ie, take out the middleman), the results are consistently not what I wanted.
Either the values get read as REPLACE (erasing the lower animation), double the results, or inverts things.
Original code section:
/* accumulate (i.e. blend) the given value on to the channel it affects */
static void nlaevalchan_accumulate(NlaEvalChannel *nec, NlaEvalStrip *nes, float value, bool newChan)
{
NlaStrip *strip = nes->strip;
short blendmode = strip->blendmode;
float inf = strip->influence;
/* for replace blend mode, and if this is the first strip,
* just replace the value regardless of the influence */
if (newChan && blendmode == NLASTRIP_MODE_REPLACE) {
nec->value = value;
return;
}
/* if this is being performed as part of transition evaluation, incorporate
* an additional weighting factor for the influence
*/
if (nes->strip_mode == NES_TIME_TRANSITION_END)
inf *= nes->strip_time;
/* optimisation: no need to try applying if there is no influence */
if (IS_EQF(inf, 0.0f)) return;
/* perform blending */
switch (blendmode) {
case NLASTRIP_MODE_ADD:
/* simply add the scaled value on to the stack */
nec->value += (value * inf);/
break;
case NLASTRIP_MODE_SUBTRACT:
/* simply subtract the scaled value from the stack */
nec->value -= (value * inf);
break;
case NLASTRIP_MODE_MULTIPLY:
/* multiply the scaled value with the stack */
/* Formula Used:
* result = fac * (a * b) + (1 - fac) * a
*/
nec->value = inf * (nec->value * value) + (1 - inf) * nec->value;
break;
case NLASTRIP_MODE_REPLACE:
default: /* TODO: do we really want to blend by default? it seems more uses might prefer add... */
/* do linear interpolation
* - the influence of the accumulated data (elsewhere, that is called dstweight)
* is 1 - influence, since the strip's influence is srcweight
*/
nec->value = nec->value * (1.0f - inf) + (value * inf);
break;
}
}
When I plug my code in:
/* accumulate (i.e. blend) the given value on to the channel it affects */
static void nlaevalchan_accumulate(NlaEvalChannel *nec, NlaEvalStrip *nes, float value, bool newChan)
{
NlaStrip *strip = nes->strip;
short blendmode = strip->blendmode;
float inf = strip->influence;
/* for replace blend mode, and if this is the first strip,
* just replace the value regardless of the influence */
if (newChan && blendmode == NLASTRIP_MODE_REPLACE) {
nec->value = value;
return;
}
/* diffference = (abs(old_value-new_value) * (-1 if new_value < old_value else 1) ); */
float difference = fabs(nec->value - value);
if (value < nec->value) difference *= -1.0f;
else difference *= 1.0f;
/* if this is being performed as part of transition evaluation, incorporate
* an additional weighting factor for the influence
*/
if (nes->strip_mode == NES_TIME_TRANSITION_END)
inf *= nes->strip_time;
/* optimisation: no need to try applying if there is no influence */
if (IS_EQF(inf, 0.0f)) return;
/* perform blending */
switch (blendmode) {
case NLASTRIP_MODE_ADD:
/* simply add the scaled value on to the stack */
nec->value += (difference * inf);
break;
case NLASTRIP_MODE_SUBTRACT:
/* simply subtract the scaled value from the stack */
nec->value -= (difference * inf);
break;
case NLASTRIP_MODE_MULTIPLY:
/* multiply the scaled value with the stack */
/* Formula Used:
* result = fac * (a * b) + (1 - fac) * a
*/
nec->value = inf * (nec->value * difference) + (1 - inf) * nec->value;
break;
case NLASTRIP_MODE_REPLACE:
default: /* TODO: do we really want to blend by default? it seems more uses might prefer add... */
/* do linear interpolation
* - the influence of the accumulated data (elsewhere, that is called dstweight)
* is 1 - influence, since the strip's influence is srcweight
*/
nec->value = nec->value * (1.0f - inf) + (value * inf);
break;
}
}
The math looks like it should work to me, since when changing the keyframe to the difference in python, that “difference” is the value that the “value” internal uses, so swapping the value with the difference should get the same result.
The problem is this particular code basically overwrites the previous animation data.
I don’t know if there’s a property that I set incorrectly or in the wrong location, or even what language this stuff is written in.
I wanted to write this internally because it would be THE fastest implementation, instead of all the checks I do to find the values in python, and without running too many times, dropping performance. Here’s a link to the proof-of-concept addon+video demonstration: https://blenderartists.org/t/1100149
Anyone know what’s wrong with this code and how to get it working?
Maybe I needed to update a value somewhere else as well, or position them differently?