diff options
-rw-r--r-- | source/blender/blenkernel/intern/anim_sys.c | 246 | ||||
-rw-r--r-- | source/blender/blenkernel/nla_private.h | 13 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_anim_types.h | 3 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_nla.c | 6 |
4 files changed, 261 insertions, 7 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; diff --git a/source/blender/makesdna/DNA_anim_types.h b/source/blender/makesdna/DNA_anim_types.h index 15203812b2d..4df9c51609c 100644 --- a/source/blender/makesdna/DNA_anim_types.h +++ b/source/blender/makesdna/DNA_anim_types.h @@ -718,7 +718,8 @@ typedef enum eNlaStrip_Blend_Mode { NLASTRIP_MODE_REPLACE = 0, NLASTRIP_MODE_ADD, NLASTRIP_MODE_SUBTRACT, - NLASTRIP_MODE_MULTIPLY + NLASTRIP_MODE_MULTIPLY, + NLASTRIP_MODE_COMBINE, } eNlaStrip_Blend_Mode; /* NLA Strip Extrpolation Mode */ diff --git a/source/blender/makesrna/intern/rna_nla.c b/source/blender/makesrna/intern/rna_nla.c index 7b5b2f13eef..ceedb5078a2 100644 --- a/source/blender/makesrna/intern/rna_nla.c +++ b/source/blender/makesrna/intern/rna_nla.c @@ -488,7 +488,11 @@ static void rna_NlaTrack_solo_set(PointerRNA *ptr, bool value) /* enum defines exported for rna_animation.c */ const EnumPropertyItem rna_enum_nla_mode_blend_items[] = { {NLASTRIP_MODE_REPLACE, "REPLACE", 0, "Replace", - "Result strip replaces the accumulated results by amount specified by influence"}, + "The strip values replace the accumulated results by amount specified by influence"}, + {NLASTRIP_MODE_COMBINE, "COMBINE", 0, "Combine", + "The strip values are combined with accumulated results by appropriately using addition, " + "multiplication, or quaternion math, based on channel type"}, + {0, "", 0, NULL, NULL}, {NLASTRIP_MODE_ADD, "ADD", 0, "Add", "Weighted result of strip is added to the accumulated results"}, {NLASTRIP_MODE_SUBTRACT, "SUBTRACT", 0, "Subtract", "Weighted result of strip is removed from the accumulated results"}, |