[Bf-blender-cvs] [d3caa9ec2dc] arcpatch-D8867_Nla_Merge_Strips: Feature: NLA: Evaluate Whole NLA Stack in Tweak Mode

Wayde Moss noreply at git.blender.org
Wed Oct 21 06:26:02 CEST 2020


Commit: d3caa9ec2dcecffac68deff3d1f728421bb92170
Author: Wayde Moss
Date:   Wed Oct 21 00:18:51 2020 -0400
Branches: arcpatch-D8867_Nla_Merge_Strips
https://developer.blender.org/rBd3caa9ec2dcecffac68deff3d1f728421bb92170

Feature: NLA: Evaluate Whole NLA Stack in Tweak Mode

Feature: NLA: show and evaluate whole NLA stack while in tweak mode:

**Note for reviewers**
This patch is relative to {D9247}. Apply that patch first, then apply this one afterward.

For reviewers, the two core functions changed are in anim_sys.c (BKE_animsys_nla_remap_keyframe_values and animsys_calculate_nla). I've separated the old animsys_evaluate_nla() into for_flush and for_keyframing variations to make the intent clear.

I should add that the nlastrip_evaluate() and recursive strip evaluate calls have been duplicated for inverting. They can be refactored but I've decided against it. I didn't want to refactor and add a new feature in a single patch. Keeping those calls nearly the same should make them easier to understand relative to the old implementation. I suppose I could've refactored first then made the patch, but without the duplication it could've been difficult to see the motivation for refactoring [...]

**Question**: I also noticed a UI-based confusion introduced by patch. If the animator has a nonpushed action then it has influence even while in tweak mode which is expected. However, the bounds of that influence is currently not drawn in any way while in tweak mode. It can be a surprise and source of confusion when the non-pushed action begins to influence the animation result. I should talk to the UI team about how it should be drawn. Or maybe it should be a separate patch so the UI ca [...]

**Problem/Solution**
This feature solves the problem of not being able to see and consider the final animation result while tweaking a strip. Before, as a user keyframes a strip, the upper strips would be disabled. Now, the user can optionally view the upper strip affects while keyframing. The strips above it are accounted for such that the final NLA result matches what the user visually keyframed. The feature pros and cons itself sounds self explanatory but let me know if I should explain more in depth.

**Implementation**
The core implementation is that each upper strip needs to be inverted separately, solving for the blend output of the lower stack. That goes on until we have solved for the blend output of the tweaked strip and lower stack. Then we use the old existing invert math to get the fcurve value of the tweak strip. The lower stack can be inverted as a group instead of separately because we're solving for the tweak strip's value, not the lower blended value, and it doesn't include the tweaked strip.

**Changes to old behavior (Evaluating without upper stack)**
Evaluating upper stack is optional and, for now, set as default (Tab). The RClick context menu and header menu exposes the option to evaluate without the upper stack (Ctrl+Tab).

Tweaked strip no longer uses animdata->act_influence since that's used by the non-pushed action.

**Bugfixes Included Relative to Public Version **
This includes bugfixes such as evaluating meta strips correctly when next to a transition {D8287}.

**Limitations Introduced by Patch**
Keyframing through a quaternion transition of (Combine<->Replace/Add/Sub/Mul) is not handled properly and will fail to insert a keyframe. I'm still working out the math, at least for the case of (Combine<->Replace). Other quaternion transition cases should be handled properly (Combine<->Combine) and (Add/Sub/Mul/Replace <-> Add/Sub/Mul/Replace). All non-quaternion transition cases should be handled properly. This limitation only applies when such transitions appear above the tweaked strip [...]

**Example Files**
Location Focused:
 {F8802904}
Quaternion Focused:
 {F8802907}
Scale Focused:
 {F8802906}

Each file has multiple objects in it that have simple NLA setups. It's just meant to save a little bit of time. Autokeying is on and keying sets match the file focus. Per check, just unhide one object at a time, enter tweak mode on the lowest strip, then try to keyframe. In most cases, your overall NLA results should be preserved when refreshing the keyframe(changing to different frame then back to the keyframed one). General failure cases include not being able to keyframe through full ( [...]

For location, it's easy to verify by using auto keying and snapping Suzanne to cursor. Refresh the frame and her origin should match the cursor. Generally, I just eyeball things and use the grid.

No specific Euler file since NLA evaluation is the same as location values. The Scale file only includes Combine strip examples since other blends modes evaluate the same as any other property, like location.

**Existing Limitations of NLA Not Solved By Patch**
If the tweaked strip's action evaluates multiple times in the same frame, then it's not solved for correctly. To support this is nontrivial. We have to allow keyframing at multiple frames and account for the different flags of each strip that uses the same action. The lower stack would have to be inverted separately just like the upper stack. Each strip may also sample the action using a different action range too.. Transitions between the linked action.. It's complicated. I expect it to  [...]
{F8883083}

Differential Revision: https://developer.blender.org/D8296

===================================================================

M	release/scripts/presets/keyconfig/keymap_data/blender_default.py
M	release/scripts/startup/bl_ui/space_nla.py
M	source/blender/blenkernel/BKE_animsys.h
M	source/blender/blenkernel/intern/anim_sys.c
M	source/blender/blenkernel/intern/nla.c
M	source/blender/blenkernel/nla_private.h
M	source/blender/editors/animation/keyframing.c
M	source/blender/editors/space_nla/nla_edit.c
M	source/blender/makesdna/DNA_anim_types.h
M	source/blender/makesrna/intern/rna_animation.c

===================================================================

diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index b27cdbec308..da85004664b 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -2162,6 +2162,9 @@ def km_nla_generic(_params):
          {"properties": [("isolate_action", True)]}),
         ("nla.tweakmode_exit", {"type": 'TAB', "value": 'PRESS', "shift": True},
          {"properties": [("isolate_action", True)]}),
+        ("nla.tweakmode_enter", {"type": 'TAB', "value": 'PRESS', "ctrl": True},
+         {"properties": [("use_upper_stack_evaluation", False)]}),
+        ("nla.tweakmode_exit", {"type": 'TAB', "value": 'PRESS', "ctrl": True},None),
         ("anim.channels_find", {"type": 'F', "value": 'PRESS', "ctrl": True}, None),
     ])
 
diff --git a/release/scripts/startup/bl_ui/space_nla.py b/release/scripts/startup/bl_ui/space_nla.py
index 4ecc4e7fdd9..44bea586dbc 100644
--- a/release/scripts/startup/bl_ui/space_nla.py
+++ b/release/scripts/startup/bl_ui/space_nla.py
@@ -198,6 +198,7 @@ class NLA_MT_edit(Menu):
         else:
             layout.operator("nla.tweakmode_enter", text="Start Editing Stashed Action").isolate_action = True
             layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions")
+            layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Exclude Upper Stack)").use_upper_stack_evaluation = False
 
 
 class NLA_MT_add(Menu):
@@ -260,6 +261,7 @@ class NLA_MT_context_menu(Menu):
         else:
             layout.operator("nla.tweakmode_enter", text="Start Editing Stashed Action").isolate_action = True
             layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions")
+            layout.operator("nla.tweakmode_enter", text="Start Tweaking Strip Actions (Exclude Upper Stack)").use_upper_stack_evaluation = False
 
         layout.separator()
 
diff --git a/source/blender/blenkernel/BKE_animsys.h b/source/blender/blenkernel/BKE_animsys.h
index e812d04c7d1..ec0e42c0998 100644
--- a/source/blender/blenkernel/BKE_animsys.h
+++ b/source/blender/blenkernel/BKE_animsys.h
@@ -217,8 +217,10 @@ struct NlaKeyframingContext *BKE_animsys_get_nla_keyframing_context(
     const struct AnimationEvalContext *anim_eval_context,
     const bool flush_to_original);
 bool BKE_animsys_nla_remap_keyframe_values(struct NlaKeyframingContext *context,
+                                           const AnimationEvalContext *anim_eval_context,
                                            struct PointerRNA *prop_ptr,
                                            struct PropertyRNA *prop,
+                                           char rna_path[],
                                            float *values,
                                            int count,
                                            int index,
diff --git a/source/blender/blenkernel/intern/anim_sys.c b/source/blender/blenkernel/intern/anim_sys.c
index 5e42ef5d100..f5cbfdeb635 100644
--- a/source/blender/blenkernel/intern/anim_sys.c
+++ b/source/blender/blenkernel/intern/anim_sys.c
@@ -1043,6 +1043,8 @@ static NlaEvalChannelSnapshot *nlaevalchan_snapshot_new(NlaEvalChannel *nec)
 
   nec_snapshot->channel = nec;
   nec_snapshot->length = length;
+  nlavalidmask_init(&nec_snapshot->invertible, length);
+  nlavalidmask_init(&nec_snapshot->raw_value_sampled, length);
 
   return nec_snapshot;
 }
@@ -1052,6 +1054,8 @@ static void nlaevalchan_snapshot_free(NlaEvalChannelSnapshot *nec_snapshot)
 {
   BLI_assert(!nec_snapshot->is_base);
 
+  nlavalidmask_free(&nec_snapshot->invertible);
+  nlavalidmask_free(&nec_snapshot->raw_value_sampled);
   MEM_freeN(nec_snapshot);
 }
 
@@ -1385,6 +1389,23 @@ static NlaEvalChannel *nlaevalchan_verify_key(NlaEvalData *nlaeval,
   return nec;
 }
 
+/** Unlike nlaevalchan_verify(), this will not create a channel if it does not exist. */
+static bool nlaevalchan_try_get(NlaEvalData *nlaeval, const char *path, NlaEvalChannel **r_nec)
+{
+  if (path == NULL) {
+    return false;
+  }
+
+  /* Lookup the path in the path based hash. */
+  NlaEvalChannel *p_path_nec = (NlaEvalChannel *)BLI_ghash_lookup(nlaeval->path_hash,
+                                                                  (void *)path);
+
+  if (p_path_nec) {
+    *r_nec = p_path_nec;
+  }
+  return p_path_nec != NULL;
+}
+
 /* Verify that an appropriate NlaEvalChannel for this path exists. */
 static NlaEvalChannel *nlaevalchan_verify(PointerRNA *ptr, NlaEvalData *nlaeval, const char *path)
 {
@@ -1656,6 +1677,127 @@ static bool nla_combine_quaternion_invert_get_fcurve_values(const float lower_va
   return true;
 }
 
+/** \returns true if solution exists and output written to. */
+static bool nla_blend_value_invert_get_lower_value(const int blendmode,
+                                                   const float fcurve_value,
+                                                   const float blended_value,
+                                                   const float influence,
+                                                   float *r_lower_value)
+{
+  switch (blendmode) {
+    case NLASTRIP_MODE_ADD:
+      /* Simply subtract the scaled value on to the stack. */
+      *r_lower_value = blended_value - (fcurve_value * influence);
+      return true;
+
+    case NLASTRIP_MODE_SUBTRACT:
+      /* Simply add the scaled value from the stack. */
+      *r_lower_value = blended_value + (fcurve_value * influence);
+      return true;
+
+    case NLASTRIP_MODE_MULTIPLY:
+
+      /** Division by zero. */
+      if (IS_EQF(-fcurve_value * influence, 1.0f - influence)) {
+        /** Resolve 0/0 to 1. */
+        if (IS_EQF(0.0f, blended_value)) {
+          *r_lower_value = 1;
+          return true;
+        }
+        /** Division by zero. */
+        return false;
+      }
+      /* Math:
+       *     blended_value = inf * (lower_value * fcurve_value) + (1 - inf) * lower_value
+       *                   = lower_value * (inf * fcurve_value + (1-inf))
+       *         lower_value = blended_value / (inf * fcurve_value + (1-inf))
+       */
+      *r_lower_value = blended_value / (influence * fcurve_value + (1.0f - influence));
+      return true;
+
+    case NLASTRIP_MODE_COMBINE:
+      BLI_assert(!"combine mode");
+      return false;
+
+    default:
+
+      /** No solution if lower strip has 0 influence. */
+      if (IS_EQF(1.0f, influence)) {
+        return false;
+      }
+
+      /** Math:
+       *
+       *  blended_value = lower_value * (1.0f - inf) + (fcurve_value * inf)
+       *  blended_value - (fcurve_value * inf) = lower_value * (1.0f - inf)
+       *  blended_value - (fcurve_value * inf) / (1.0f - inf) = lower_value
+       *
+       *  lower_value = blended_value - (fcurve_value * inf) / (1.0f - inf)
+       */
+      *r_lower_value = (blended_value - (fcurve_value * influence)) / (1.0f - influence);
+      return true;
+  }
+}
+
+/** \returns true if solution exists and output written to. */
+static bool nla_combine_value_invert_get_lower_value(const int mix_mode,
+                                                     float base_value,
+                                                     const float fcurve_value,
+                                                     const float blended_value,
+                                                     const float inf,
+                                                     float *r_lower_value)
+{
+  /* Perform blending. */
+  switch (mix_mode) {
+    case NEC_MIX_ADD:
+    case NEC_MIX_AXIS_ANGLE:
+      *r_lower_value = blended_value - (fcurve_value - base_value) * inf;
+      return true;
+    case NEC_MIX_MULTIPLY:
+      /** Division by zero. */
+      if (IS_EQF(0.0f, fcurve_value)) {
+        /** Resolve 0/0 to 1. */
+        if (IS_EQF(0.0f, blended_value)) {
+          *r_lower_value = 1.0f;
+          return true;
+        }
+        return false;
+      }
+
+      if (IS_EQF(0.0f, base_value)) {
+        base_value = 1.0f;
+      }
+
+      *r_lower_value = blended_value / powf(fcurve_value / base_value, inf);
+      return true;
+
+    default:
+      BLI_assert(!"invalid mix mode");
+      return false;
+  }
+}
+
+static void nla_combine_quaternion_invert_get_lower_values(const float fcurve_values[4],
+                                                           const float blended_values[4],
+                                                           const float influence,
+                                                           float r_lower_value[4])
+{
+  /* blended_value = lower_values @ fcurve_values^infl
+   * blended_value @ inv(fcurve_values^inf) = lower_values
+   *
+   * Returns: lower_values = blended_value @ inv(fcurve_values^inf) */
+
+  float tmp_fcurve[4], tmp_blended[4];
+
+  normalize_qt_qt(tmp_fcurve, fcurve_values);
+  normalize_qt_qt(tmp_blended, blended_values);
+
+  pow_qt_fl_normalized(tmp_fcurve, influence);
+  invert_qt_normalized(tmp_fcurve);
+
+  mul_qt_qtqt(r_lower_value, tmp_blended, tmp_fcurve);
+}
+
 /* Data about the current blend mode. */
 typedef struct NlaBlendData {
   NlaEvalSnapshot *snapshot;
@@ -1720,6 +1862,46 @@ static bool nlaeval_blend_value(NlaBlendData *blend,
   return true;
 }
 
+/* Storing lower values within snapshot's necs->values if invertible. Marks non-invertible channels
+ * and defers quaternion combine inversion. */
+static void nlaeval_blend_value_invert_get_lower_value(NlaBlendData *blend,
+                                                       NlaEvalChannelSnapshot *necs,
+                                                       const int array_index,
+                                                       const float fcurve_value)
+{
+  NlaEvalChannel *nec = necs->channel;
+  if (!nlaevalchan_validate_index_ex(nec, array_index)) {
+    /** Note: no need to disable bits. If index invalid, then the fcurve wouldn't contribute
+     * anyways. */
+    return;
+  }
+
+  float *const p_value = &necs->values[array_index];
+
+  if (blend->mode == NLASTRIP_MODE_COMBINE) {
+    /* Quaternion blending is deferred until all sub-channel values are known. */
+

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list