Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Gavrilov <angavrilov@gmail.com>2018-11-12 19:41:53 +0300
committerAlexander Gavrilov <angavrilov@gmail.com>2018-12-14 22:19:18 +0300
commitde662e7cd3b3ca45c4220e3830c32e2b3496c9fe (patch)
tree79b78cc746c5121ac4c964dffef94d961fd14975 /source/blender/blenkernel
parentfc74903f272a01784e0376c9ee12c2673017285d (diff)
NLA: insert keyframes correctly for strips with non-Replace mode.
NLA strips support using the keyframe values in a variety of ways: adding, subtracting, multiplying, linearly mixing with the result of strips located below in the stack. This is intended for layering tweaks on top of a base animation. However, when inserting keyframes into such strips, it simply inserts the final value of the property, irrespective of these settings. This in fact makes the feature nearly useless. To fix this it is necessary to evaluate the NLA stack below the edited strip and correctly compute the raw key that would produce the intended final value, according to the mode and influence. Differential Revision: https://developer.blender.org/D3927
Diffstat (limited to 'source/blender/blenkernel')
-rw-r--r--source/blender/blenkernel/BKE_animsys.h9
-rw-r--r--source/blender/blenkernel/intern/anim_sys.c230
-rw-r--r--source/blender/blenkernel/nla_private.h15
3 files changed, 226 insertions, 28 deletions
diff --git a/source/blender/blenkernel/BKE_animsys.h b/source/blender/blenkernel/BKE_animsys.h
index 43618109e91..f85782d9caa 100644
--- a/source/blender/blenkernel/BKE_animsys.h
+++ b/source/blender/blenkernel/BKE_animsys.h
@@ -48,6 +48,7 @@ struct Scene;
struct bAction;
struct bActionGroup;
struct bContext;
+struct NlaKeyframingContext;
/* ************************************* */
/* AnimData API */
@@ -171,6 +172,14 @@ void BKE_fcurves_main_cb(struct Main *bmain, ID_FCurve_Edit_Callback func, void
/* ************************************* */
// TODO: overrides, remapping, and path-finding api's
+/* ------------ NLA Keyframing --------------- */
+
+typedef struct NlaKeyframingContext NlaKeyframingContext;
+
+struct NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context(struct ListBase *cache, struct Depsgraph *depsgraph, struct PointerRNA *ptr, struct AnimData *adt, float ctime);
+bool BKE_animsys_nla_remap_keyframe_value(struct NlaKeyframingContext *context, struct PointerRNA *prop_ptr, struct PropertyRNA *prop, int index, float *r_value);
+void BKE_animsys_free_nla_keyframing_context_cache(struct ListBase *cache);
+
/* ************************************* */
/* Evaluation API */
diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c
index 9cdf94b3253..579509608c3 100644
--- a/source/blender/blenkernel/intern/anim_sys.c
+++ b/source/blender/blenkernel/intern/anim_sys.c
@@ -2120,6 +2120,8 @@ static NlaEvalChannel *nlaevalchan_verify(PointerRNA *ptr, ListBase *channels, F
return nec;
}
+static float nla_blend_value(int blendmode, float old_value, float value, float inf);
+
/* 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)
{
@@ -2140,28 +2142,33 @@ static void nlaevalchan_accumulate(NlaEvalChannel *nec, NlaEvalStrip *nes, float
if (nes->strip_mode == NES_TIME_TRANSITION_END)
inf *= nes->strip_time;
+ nec->value = nla_blend_value(blendmode, nec->value, value, inf);
+}
+
+/* accumulate the old and new values of a channel according to mode and influence */
+static float nla_blend_value(int blendmode, 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;
+ if (IS_EQF(inf, 0.0f)) {
+ return old_value;
+ }
/* perform blending */
switch (blendmode) {
case NLASTRIP_MODE_ADD:
/* simply add the scaled value on to the stack */
- nec->value += (value * inf);
- break;
+ return old_value + (value * inf);
case NLASTRIP_MODE_SUBTRACT:
/* simply subtract the scaled value from the stack */
- nec->value -= (value * inf);
- break;
+ return old_value - (value * inf);
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;
+ return inf * (old_value * value) + (1 - inf) * old_value;
case NLASTRIP_MODE_REPLACE:
default: /* TODO: do we really want to blend by default? it seems more uses might prefer add... */
@@ -2169,8 +2176,41 @@ static void nlaevalchan_accumulate(NlaEvalChannel *nec, NlaEvalStrip *nes, float
* - 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;
+ return old_value * (1.0f - inf) + (value * inf);
+ }
+}
+
+/* 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)
+{
+ switch (blend_mode) {
+ case NLASTRIP_MODE_ADD:
+ *r_value = (target_value - old_value) / influence;
+ return true;
+
+ case NLASTRIP_MODE_SUBTRACT:
+ *r_value = (old_value - target_value) / influence;
+ return true;
+
+ case NLASTRIP_MODE_MULTIPLY:
+ if (old_value == 0.0f) {
+ /* Resolve 0/0 to 1. */
+ if (target_value == 0.0f) {
+ *r_value = 1.0f;
+ return true;
+ }
+ /* Division by zero. */
+ return false;
+ }
+ else {
+ *r_value = (target_value - old_value) / influence / old_value + 1.0f;
+ return true;
+ }
+
+ case NLASTRIP_MODE_REPLACE:
+ default:
+ *r_value = (target_value - old_value) / influence + old_value;
+ return true;
}
}
@@ -2481,11 +2521,10 @@ void nladata_flush_channels(Depsgraph *depsgraph, PointerRNA *ptr, ListBase *cha
/**
* NLA Evaluation function - values are calculated and stored in temporary "NlaEvalChannels"
*
- * \note This is exported so that keyframing code can use this for make use of it for anim layers support
- *
* \param[out] echannels Evaluation channels with calculated values
+ * \param[out] r_context If not NULL, data about the currently edited strip is stored here and excluded from value calculation.
*/
-static void animsys_evaluate_nla(Depsgraph *depsgraph, ListBase *echannels, PointerRNA *ptr, AnimData *adt, float ctime)
+static void animsys_evaluate_nla(Depsgraph *depsgraph, ListBase *echannels, PointerRNA *ptr, AnimData *adt, float ctime, NlaKeyframingContext *r_context)
{
NlaTrack *nlt;
short track_index = 0;
@@ -2493,9 +2532,12 @@ static void animsys_evaluate_nla(Depsgraph *depsgraph, ListBase *echannels, Poin
ListBase estrips = {NULL, NULL};
NlaEvalStrip *nes;
+ NlaStrip dummy_strip_buf;
- NlaStrip dummy_strip = {NULL}; /* dummy strip for active action */
+ /* dummy strip for active action */
+ NlaStrip *dummy_strip = r_context ? &r_context->strip : &dummy_strip_buf;
+ memset(dummy_strip, 0, sizeof(*dummy_strip));
/* 1. get the stack of strips to evaluate at current time (influence calculated here) */
for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next, track_index++) {
@@ -2538,39 +2580,69 @@ static void animsys_evaluate_nla(Depsgraph *depsgraph, ListBase *echannels, Poin
/* make dummy NLA strip, and add that to the stack */
ListBase dummy_trackslist;
- dummy_trackslist.first = dummy_trackslist.last = &dummy_strip;
+ dummy_trackslist.first = dummy_trackslist.last = dummy_strip;
- if ((nlt) && !(adt->flag & ADT_NLA_EDIT_NOMAP)) {
+ /* Strips with a user-defined time curve don't get properly remapped for editing
+ * at the moment, so mapping them just for display may be confusing. */
+ bool is_inplace_tweak = (nlt) && !(adt->flag & ADT_NLA_EDIT_NOMAP) && !(adt->actstrip->flag & NLASTRIP_FLAG_USR_TIME);
+
+ if (is_inplace_tweak) {
/* edit active action in-place according to its active strip, so copy the data */
- memcpy(&dummy_strip, adt->actstrip, sizeof(NlaStrip));
- dummy_strip.next = dummy_strip.prev = NULL;
+ memcpy(dummy_strip, adt->actstrip, sizeof(NlaStrip));
+ dummy_strip->next = dummy_strip->prev = NULL;
}
else {
/* set settings of dummy NLA strip from AnimData settings */
- dummy_strip.act = adt->action;
+ dummy_strip->act = adt->action;
/* action range is calculated taking F-Modifiers into account (which making new strips doesn't do due to the troublesome nature of that) */
- calc_action_range(dummy_strip.act, &dummy_strip.actstart, &dummy_strip.actend, 1);
- dummy_strip.start = dummy_strip.actstart;
- dummy_strip.end = (IS_EQF(dummy_strip.actstart, dummy_strip.actend)) ? (dummy_strip.actstart + 1.0f) : (dummy_strip.actend);
+ calc_action_range(dummy_strip->act, &dummy_strip->actstart, &dummy_strip->actend, 1);
+ dummy_strip->start = dummy_strip->actstart;
+ dummy_strip->end = (IS_EQF(dummy_strip->actstart, dummy_strip->actend)) ? (dummy_strip->actstart + 1.0f) : (dummy_strip->actend);
+
+ /* Always use the blend mode of the strip in tweak mode, even if not in-place. */
+ if (nlt && adt->actstrip) {
+ dummy_strip->blendmode = adt->actstrip->blendmode;
+ dummy_strip->extendmode = adt->actstrip->extendmode;
+ }
+ else {
+ dummy_strip->blendmode = adt->act_blendmode;
+ dummy_strip->extendmode = adt->act_extendmode;
+ }
- dummy_strip.blendmode = adt->act_blendmode;
- dummy_strip.extendmode = adt->act_extendmode;
- dummy_strip.influence = adt->act_influence;
+ dummy_strip->influence = adt->act_influence;
/* NOTE: must set this, or else the default setting overrides, and this setting doesn't work */
- dummy_strip.flag |= NLASTRIP_FLAG_USR_INFLUENCE;
+ dummy_strip->flag |= NLASTRIP_FLAG_USR_INFLUENCE;
}
/* add this to our list of evaluation strips */
- nlastrips_ctime_get_strip(depsgraph, &estrips, &dummy_trackslist, -1, ctime);
+ if (r_context == NULL) {
+ nlastrips_ctime_get_strip(depsgraph, &estrips, &dummy_trackslist, -1, ctime);
+ }
+ /* If computing the context for keyframing, store data there instead of the list. */
+ else {
+ /* The extend mode here effectively controls whether it is possible to keyframe beyond the ends. */
+ dummy_strip->extendmode = is_inplace_tweak ? NLASTRIP_EXTEND_NOTHING : NLASTRIP_EXTEND_HOLD;
+
+ r_context->eval_strip = nes = nlastrips_ctime_get_strip(depsgraph, NULL, &dummy_trackslist, -1, ctime);
+
+ /* These setting combinations require no data from strips below, so exit immediately. */
+ if ((nes == NULL) || (dummy_strip->blendmode == NLASTRIP_MODE_REPLACE && dummy_strip->influence == 1.0f)) {
+ BLI_freelistN(&estrips);
+ return;
+ }
+ }
}
else {
/* special case - evaluate as if there isn't any NLA data */
/* TODO: this is really just a stop-gap measure... */
if (G.debug & G_DEBUG) printf("NLA Eval: Stopgap for active action on NLA Stack - no strips case\n");
- animsys_evaluate_action(depsgraph, ptr, adt->action, ctime);
+ if (r_context == NULL) {
+ animsys_evaluate_action(depsgraph, ptr, adt->action, ctime);
+ }
+
BLI_freelistN(&estrips);
return;
}
@@ -2601,7 +2673,7 @@ static void animsys_calculate_nla(Depsgraph *depsgraph, PointerRNA *ptr, AnimDat
* and also when the user jumps between different times instead of moving sequentially... */
/* evaluate the NLA stack, obtaining a set of values to flush */
- animsys_evaluate_nla(depsgraph, &echannels, ptr, adt, ctime);
+ animsys_evaluate_nla(depsgraph, &echannels, ptr, adt, ctime, NULL);
/* flush effects of accumulating channels in NLA to the actual data they affect */
nladata_flush_channels(depsgraph, ptr, &echannels);
@@ -2610,6 +2682,108 @@ static void animsys_calculate_nla(Depsgraph *depsgraph, PointerRNA *ptr, AnimDat
BLI_freelistN(&echannels);
}
+/* ---------------------- */
+
+/**
+ * Prepare data necessary to compute correct keyframe values for NLA strips
+ * with non-Replace mode or influence different from 1.
+ *
+ * @param cache List used to cache contexts for reuse when keying multiple channels in one operation.
+ * @param ptr RNA pointer to the Object with the animation.
+ * @return Keyframing context, or NULL if not necessary.
+ */
+NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context(
+ struct ListBase *cache, struct Depsgraph *depsgraph, struct PointerRNA *ptr, struct AnimData *adt, float ctime)
+{
+ /* No remapping needed if NLA is off or no action. */
+ if ((adt == NULL) || (adt->action == NULL) || (adt->nla_tracks.first == NULL) || (adt->flag & ADT_NLA_EVAL_OFF)) {
+ return NULL;
+ }
+
+ /* No remapping if editing an ordinary Replace action with full influence. */
+ if (!(adt->flag & ADT_NLA_EDIT_ON) && (adt->act_blendmode == NLASTRIP_MODE_REPLACE && adt->act_influence == 1.0f)) {
+ return NULL;
+ }
+
+ /* Try to find a cached context. */
+ NlaKeyframingContext *ctx = BLI_findptr(cache, adt, offsetof(NlaKeyframingContext, adt));
+
+ if (ctx == NULL) {
+ /* Allocate and evaluate a new context. */
+ ctx = MEM_callocN(sizeof(*ctx), "NlaKeyframingContext");
+ ctx->adt = adt;
+
+ animsys_evaluate_nla(depsgraph, &ctx->nla_channels, ptr, adt, ctime, ctx);
+
+ BLI_assert(ELEM(ctx->strip.act, NULL, adt->action));
+ BLI_addtail(cache, ctx);
+ }
+
+ return ctx;
+}
+
+/**
+ * Apply correction from the NLA context to the value about to be keyframed.
+ *
+ * @param context Context to use (may be NULL).
+ * @param prop_ptr Property about to be keyframed.
+ * @param index Array index within the property.
+ * @param[in,out] r_value Value to correct.
+ * @return False if correction fails due to a division by zero.
+ */
+bool BKE_animsys_nla_remap_keyframe_value(struct NlaKeyframingContext *context, struct PointerRNA *prop_ptr, struct PropertyRNA *prop, int index, float *r_value)
+{
+ /* No context means no correction. */
+ if (context == NULL || context->strip.act == NULL) {
+ return true;
+ }
+
+ /* If the strip is not evaluated, it is the same as zero influence. */
+ if (context->eval_strip == NULL) {
+ return false;
+ }
+
+ /* Full influence Replace strips also require no correction. */
+ int blend_mode = context->strip.blendmode;
+ float influence = context->strip.influence;
+
+ if (blend_mode == NLASTRIP_MODE_REPLACE && influence == 1.0f) {
+ return true;
+ }
+
+ /* Zero influence is division by zero. */
+ if (influence <= 0.0f) {
+ return false;
+ }
+
+ /* Find the evaluation channel for the NLA stack below current strip. */
+ PathResolvedRNA rna = { .ptr = *prop_ptr, .prop = prop, .prop_index = index };
+ NlaEvalChannel *nec = nlaevalchan_find_match(&context->nla_channels, &rna);
+
+ /* Replace strips ignore influence when they are the first to modify this channel. */
+ if (nec == NULL && blend_mode == NLASTRIP_MODE_REPLACE) {
+ return true;
+ }
+
+ /* Invert the effect of blending modes. */
+ float old_value = nec ? nec->value : nlaevalchan_init_value(&rna);
+
+ return nla_invert_blend_value(blend_mode, old_value, *r_value, influence, r_value);
+}
+
+/**
+ * Free all cached contexts from the list.
+ */
+void BKE_animsys_free_nla_keyframing_context_cache(struct ListBase *cache)
+{
+ for (NlaKeyframingContext *ctx = cache->first; ctx; ctx = ctx->next) {
+ MEM_SAFE_FREE(ctx->eval_strip);
+ BLI_freelistN(&ctx->nla_channels);
+ }
+
+ BLI_freelistN(cache);
+}
+
/* ***************************************** */
/* Overrides System - Public API */
diff --git a/source/blender/blenkernel/nla_private.h b/source/blender/blenkernel/nla_private.h
index 67728445f3a..0ab48b5ef2c 100644
--- a/source/blender/blenkernel/nla_private.h
+++ b/source/blender/blenkernel/nla_private.h
@@ -79,6 +79,21 @@ typedef struct NlaEvalChannel {
float value; /* value of this channel */
} NlaEvalChannel;
+/* Information about the currently edited strip and ones below it for keyframing. */
+typedef struct NlaKeyframingContext {
+ struct NlaKeyframingContext *next, *prev;
+
+ /* AnimData for which this context was built. */
+ struct AnimData *adt;
+
+ /* Data of the currently edited strip (copy, or fake strip for the main action). */
+ NlaStrip strip;
+ NlaEvalStrip *eval_strip;
+
+ /* Evaluated NLA stack below the current strip. */
+ ListBase nla_channels;
+} NlaKeyframingContext;
+
/* --------------- NLA Functions (not to be used as a proper API) ----------------------- */
/* convert from strip time <-> global time */