diff options
author | Alexander Gavrilov <angavrilov@gmail.com> | 2018-12-23 18:43:01 +0300 |
---|---|---|
committer | Alexander Gavrilov <angavrilov@gmail.com> | 2019-01-14 19:14:28 +0300 |
commit | de1d3e5f5f4029be03195227197de1c42720f958 (patch) | |
tree | d4b9f2e022af1c864bb6c82e143a6d28da423f01 /source/blender/blenkernel | |
parent | 9c1a961dc423d2eb19b875564bb4bb3c0b297ca5 (diff) |
NLA: implement a new blending mode that intelligently combines actions.
The existing Add and Multiply blending modes have limited usability,
because the appropriate operation for meaningfully combining values
depends on the channel. This adds a new mode that chooses the operation
automatically based on property settings:
- Axis+Angle channels are summed, effectively averaging the
axis, but adding up the angle. Default is forced to 0.
- Quaternion channels use quaternion multiplication:
result = prev * value ^ influence
- Scale-like multiplicative channels use multiplication:
result = prev * (value / default) ^ influence
- Other channels use addition:
result = prev + (value - default) * influence
Inclusion of default in the computation ensures that combining
keyframed default values of properties keeps the default state,
even if the default isn't 0 or 1.
Strips with this mode can be keyframed normally in Tweak mode,
except that for quaternion rotation keyframing always inserts
all 4 channels, and the channel value sliders on the left side
of Graph/Action editors won't insert keys without Auto Key.
Quaternion keys are also automatically normalized.
Differential Revision: https://developer.blender.org/D4190
Diffstat (limited to 'source/blender/blenkernel')
-rw-r--r-- | source/blender/blenkernel/intern/anim_sys.c | 246 | ||||
-rw-r--r-- | source/blender/blenkernel/nla_private.h | 13 |
2 files changed, 254 insertions, 5 deletions
diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c index 8928ecbb5e2..2a11fba8eab 100644 --- a/source/blender/blenkernel/intern/anim_sys.c +++ b/source/blender/blenkernel/intern/anim_sys.c @@ -44,6 +44,8 @@ #include "BLI_dynstr.h" #include "BLI_listbase.h" #include "BLI_string_utils.h" +#include "BLI_math_rotation.h" +#include "BLI_math_vector.h" #include "BLT_translation.h" @@ -2191,6 +2193,10 @@ static void nlaeval_snapshot_free_data(NlaEvalSnapshot *snapshot) static void nlaevalchan_free_data(NlaEvalChannel *nec) { nlavalidmask_free(&nec->valid); + + if (nec->blend_snapshot != NULL) { + nlaevalchan_snapshot_free(nec->blend_snapshot); + } } /* Initialize a full NLA evaluation state structure. */ @@ -2243,6 +2249,17 @@ static void nlaevalchan_get_default_values(NlaEvalChannel *nec, float *r_values) PropertyRNA *prop = nec->key.prop; int length = nec->base_snapshot.length; + /* Use unit quaternion for quaternion properties. */ + if (nec->mix_mode == NEC_MIX_QUATERNION) { + unit_qt(r_values); + return; + } + /* Use all zero for Axis-Angle properties. */ + if (nec->mix_mode == NEC_MIX_AXIS_ANGLE) { + zero_v4(r_values); + return; + } + /* NOTE: while this doesn't work for all RNA properties as default values aren't in fact * set properly for most of them, at least the common ones (which also happen to get used * in NLA strips a lot, e.g. scale) are set correctly. @@ -2296,6 +2313,33 @@ static void nlaevalchan_get_default_values(NlaEvalChannel *nec, float *r_values) *r_values = 0.0f; } } + + /* Ensure multiplicative properties aren't reset to 0. */ + if (nec->mix_mode == NEC_MIX_MULTIPLY) { + for (int i = 0; i < length; i++) { + if (r_values[i] == 0.0f) { + r_values[i] = 1.0f; + } + } + } +} + +static char nlaevalchan_detect_mix_mode(NlaEvalChannelKey *key, int length) +{ + PropertySubType subtype = RNA_property_subtype(key->prop); + + if (subtype == PROP_QUATERNION && length == 4) { + return NEC_MIX_QUATERNION; + } + else if (subtype == PROP_AXISANGLE && length == 4) { + return NEC_MIX_AXIS_ANGLE; + } + else if (RNA_property_flag(key->prop) & PROP_PROPORTIONAL) { + return NEC_MIX_MULTIPLY; + } + else { + return NEC_MIX_ADD; + } } /* Verify that an appropriate NlaEvalChannel for this property exists. */ @@ -2324,6 +2368,8 @@ static NlaEvalChannel *nlaevalchan_verify_key(NlaEvalData *nlaeval, const char * nec->index = nlaeval->num_channels++; nec->is_array = is_array; + nec->mix_mode = nlaevalchan_detect_mix_mode(key, length); + nlavalidmask_init(&nec->valid, length); nec->base_snapshot.channel = nec; @@ -2409,6 +2455,10 @@ static float nla_blend_value(int blendmode, float old_value, float value, float */ return inf * (old_value * value) + (1 - inf) * old_value; + case NLASTRIP_MODE_COMBINE: + BLI_assert(!"combine mode"); + ATTR_FALLTHROUGH; + case NLASTRIP_MODE_REPLACE: default: /* TODO: do we really want to blend by default? it seems more uses might prefer add... */ /* do linear interpolation @@ -2419,6 +2469,33 @@ static float nla_blend_value(int blendmode, float old_value, float value, float } } +/* accumulate the old and new values of a channel according to mode and influence */ +static float nla_combine_value(int mix_mode, float base_value, float old_value, float value, float inf) +{ + /* optimisation: no need to try applying if there is no influence */ + if (IS_EQF(inf, 0.0f)) { + return old_value; + } + + /* perform blending */ + switch (mix_mode) { + case NEC_MIX_ADD: + case NEC_MIX_AXIS_ANGLE: + return old_value + (value - base_value) * inf; + + case NEC_MIX_MULTIPLY: + if (base_value == 0.0f) { + base_value = 1.0f; + } + return old_value * powf(value / base_value, inf); + + case NEC_MIX_QUATERNION: + default: + BLI_assert(!"invalid mix mode"); + return old_value; + } +} + /* compute the value that would blend to the desired target value using nla_blend_value */ static bool nla_invert_blend_value(int blend_mode, float old_value, float target_value, float influence, float *r_value) { @@ -2446,6 +2523,10 @@ static bool nla_invert_blend_value(int blend_mode, float old_value, float target return true; } + case NLASTRIP_MODE_COMBINE: + BLI_assert(!"combine mode"); + ATTR_FALLTHROUGH; + case NLASTRIP_MODE_REPLACE: default: *r_value = (target_value - old_value) / influence + old_value; @@ -2453,13 +2534,92 @@ static bool nla_invert_blend_value(int blend_mode, float old_value, float target } } +/* compute the value that would blend to the desired target value using nla_combine_value */ +static bool nla_invert_combine_value(int mix_mode, float base_value, float old_value, float target_value, float influence, float *r_value) +{ + switch (mix_mode) { + case NEC_MIX_ADD: + case NEC_MIX_AXIS_ANGLE: + *r_value = base_value + (target_value - old_value) / influence; + return true; + + case NEC_MIX_MULTIPLY: + if (base_value == 0.0f) { + base_value = 1.0f; + } + if (old_value == 0.0f) { + /* Resolve 0/0 to 1. */ + if (target_value == 0.0f) { + *r_value = base_value; + return true; + } + /* Division by zero. */ + return false; + } + else { + *r_value = base_value * powf(target_value / old_value, 1.0f / influence); + return true; + } + + case NEC_MIX_QUATERNION: + default: + BLI_assert(!"invalid mix mode"); + return false; + } +} + +/* accumulate quaternion channels for Combine mode according to influence */ +static void nla_combine_quaternion(const float old_values[4], const float values[4], float influence, float result[4]) +{ + float tmp_old[4], tmp_new[4]; + + normalize_qt_qt(tmp_old, old_values); + normalize_qt_qt(tmp_new, values); + + pow_qt_fl_normalized(tmp_new, influence); + mul_qt_qtqt(result, tmp_old, tmp_new); +} + +/* invert accumulation of quaternion channels for Combine mode according to influence */ +static void nla_invert_combine_quaternion(const float old_values[4], const float values[4], float influence, float result[4]) +{ + float tmp_old[4], tmp_new[4]; + + normalize_qt_qt(tmp_old, old_values); + normalize_qt_qt(tmp_new, values); + invert_qt_normalized(tmp_old); + + mul_qt_qtqt(result, tmp_old, tmp_new); + pow_qt_fl_normalized(result, 1.0f / influence); +} + /* Data about the current blend mode. */ typedef struct NlaBlendData { NlaEvalSnapshot *snapshot; int mode; float influence; + + NlaEvalChannel *blend_queue; } NlaBlendData; +/* Queue the channel for deferred blending. */ +static NlaEvalChannelSnapshot *nlaevalchan_queue_blend(NlaBlendData *blend, NlaEvalChannel *nec) +{ + if (!nec->in_blend) { + if (nec->blend_snapshot == NULL) { + nec->blend_snapshot = nlaevalchan_snapshot_new(nec); + } + + nec->in_blend = true; + nlaevalchan_snapshot_copy(nec->blend_snapshot, &nec->base_snapshot); + + nec->next_blend = blend->blend_queue; + blend->blend_queue = nec; + } + + return nec->blend_snapshot; +} + /* Accumulate (i.e. blend) the given value on to the channel it affects. */ static bool nlaeval_blend_value(NlaBlendData *blend, NlaEvalChannel *nec, int array_index, float value) { @@ -2479,15 +2639,58 @@ static bool nlaeval_blend_value(NlaBlendData *blend, NlaEvalChannel *nec, int ar return false; } - BLI_BITMAP_ENABLE(nec->valid.ptr, index); + if (nec->mix_mode == NEC_MIX_QUATERNION) { + /* For quaternion properties, always output all sub-channels. */ + BLI_bitmap_set_all(nec->valid.ptr, true, 4); + } + else { + BLI_BITMAP_ENABLE(nec->valid.ptr, index); + } NlaEvalChannelSnapshot *nec_snapshot = nlaeval_snapshot_ensure_channel(blend->snapshot, nec); + float *p_value = &nec_snapshot->values[index]; - nec_snapshot->values[index] = nla_blend_value(blend->mode, nec_snapshot->values[index], value, blend->influence); + if (blend->mode == NLASTRIP_MODE_COMBINE) { + /* Quaternion blending is deferred until all sub-channel values are known. */ + if (nec->mix_mode == NEC_MIX_QUATERNION) { + NlaEvalChannelSnapshot *blend_snapshot = nlaevalchan_queue_blend(blend, nec); + + blend_snapshot->values[index] = value; + } + else { + float base_value = nec->base_snapshot.values[index]; + + *p_value = nla_combine_value(nec->mix_mode, base_value, *p_value, value, blend->influence); + } + } + else { + *p_value = nla_blend_value(blend->mode, *p_value, value, blend->influence); + } return true; } +/* Finish deferred quaternion blending. */ +static void nlaeval_blend_flush(NlaBlendData *blend) +{ + NlaEvalChannel *nec; + + while ((nec = blend->blend_queue)) { + blend->blend_queue = nec->next_blend; + nec->in_blend = false; + + NlaEvalChannelSnapshot *nec_snapshot = nlaeval_snapshot_ensure_channel(blend->snapshot, nec); + NlaEvalChannelSnapshot *blend_snapshot = nec->blend_snapshot; + + if (nec->mix_mode == NEC_MIX_QUATERNION) { + nla_combine_quaternion(nec_snapshot->values, blend_snapshot->values, blend->influence, nec_snapshot->values); + } + else { + BLI_assert(!"mix quaternion"); + } + } +} + /* Blend the specified snapshots into the target, and free the input snapshots. */ static void nlaeval_snapshot_mix_and_free(NlaEvalData *nlaeval, NlaEvalSnapshot *out, NlaEvalSnapshot *in1, NlaEvalSnapshot *in2, float alpha) { @@ -2651,6 +2854,8 @@ static void nlastrip_evaluate_actionclip(PointerRNA *ptr, NlaEvalData *channels, nlaeval_blend_value(&blend, nec, fcu->array_index, value); } + nlaeval_blend_flush(&blend); + /* free temporary storage */ evaluate_fmodifiers_storage_free(storage); @@ -2835,6 +3040,12 @@ static void nla_eval_domain_action(PointerRNA *ptr, NlaEvalData *channels, bActi NlaEvalChannel *nec = nlaevalchan_verify(ptr, channels, fcu->rna_path); if (nec != NULL) { + /* For quaternion properties, enable all sub-channels. */ + if (nec->mix_mode == NEC_MIX_QUATERNION) { + BLI_bitmap_set_all(nec->valid.ptr, true, 4); + continue; + } + int idx = nlaevalchan_validate_index(nec, fcu->array_index); if (idx >= 0) { @@ -3160,11 +3371,36 @@ bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context, float *old_values = nec_snapshot->values; - for (int i = 0; i < count; i++) { - if (ELEM(index, i, -1)) { - if (!nla_invert_blend_value(blend_mode, old_values[i], values[i], influence, &values[i])) { + if (blend_mode == NLASTRIP_MODE_COMBINE) { + /* Quaternion combine handles all sub-channels as a unit. */ + if (nec->mix_mode == NEC_MIX_QUATERNION) { + if (r_force_all == NULL) { return false; } + + *r_force_all = true; + + nla_invert_combine_quaternion(old_values, values, influence, values); + } + else { + float *base_values = nec->base_snapshot.values; + + for (int i = 0; i < count; i++) { + if (ELEM(index, i, -1)) { + if (!nla_invert_combine_value(nec->mix_mode, base_values[i], old_values[i], values[i], influence, &values[i])) { + return false; + } + } + } + } + } + else { + for (int i = 0; i < count; i++) { + if (ELEM(index, i, -1)) { + if (!nla_invert_blend_value(blend_mode, old_values[i], values[i], influence, &values[i])) { + return false; + } + } } } diff --git a/source/blender/blenkernel/nla_private.h b/source/blender/blenkernel/nla_private.h index d995eb5dd39..310ff1e2dbf 100644 --- a/source/blender/blenkernel/nla_private.h +++ b/source/blender/blenkernel/nla_private.h @@ -92,6 +92,14 @@ typedef struct NlaEvalChannelSnapshot { /* Memory over-allocated to provide space for values. */ } NlaEvalChannelSnapshot; +/* NlaEvalChannel->mix_mode */ +enum eNlaEvalChannel_MixMode { + NEC_MIX_ADD, + NEC_MIX_MULTIPLY, + NEC_MIX_QUATERNION, + NEC_MIX_AXIS_ANGLE, +}; + /* Temp channel for accumulating data from NLA for a single property. * Handles array properties as a unit to allow intelligent blending. */ typedef struct NlaEvalChannel { @@ -104,6 +112,11 @@ typedef struct NlaEvalChannel { int index; bool is_array; + bool in_blend; + char mix_mode; + + struct NlaEvalChannel *next_blend; + NlaEvalChannelSnapshot *blend_snapshot; /* Mask of array items controlled by NLA. */ NlaValidMask valid; |