Action Constraint: introduce a mix mode setting.
authorAlexander Gavrilov <angavrilov@gmail.com>
Sat, 23 Nov 2019 10:11:39 +0000 (13:11 +0300)
committerAlexander Gavrilov <angavrilov@gmail.com>
Tue, 24 Dec 2019 16:58:09 +0000 (19:58 +0300)
Currently the action channels are applied after the existing
transformation, as if the action controlled a child of the
bone. This is not very natural, but more importantly, the
transform tools are not designed to work conveniently with an
additional 'pseudo-child' transformation, resulting in effects
like an unexpected pivot location.

Implementing a Before mode that integrates the action channels
as if applied to a parent allows using the special transform
tool code intended for dealing with such constraints.

Note that in either mode, Action constraints should be added
in reverse order, putting a new constraint before the existing
ones that the Action was keyframed to work together.

In order to implement the option, extract a utility from
the Copy Transform constraint code for combining transforms
with special anti-shear scale handling that matches the
Aligned Inherit Scale mode.

The Before mode also requires switching the constraint to
the Local owner space, while the After mode can still use the
World space for efficiency as before. Since the constraint
doesn't have an Owner space option in the UI, this has to be
handled in an RNA setter.

For full backward compatibility, the original simple matrix
multiplication mode is preserved as the third option, but it
is not recommended due to creating shear.

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

release/scripts/startup/bl_ui/properties_constraint.py
source/blender/blenkernel/intern/constraint.c
source/blender/blenlib/BLI_math_matrix.h
source/blender/blenlib/intern/math_matrix.c
source/blender/editors/transform/transform_convert.c
source/blender/makesdna/DNA_constraint_types.h
source/blender/makesrna/intern/rna_constraint.c

index b75d67b5350296ea63674a6078063cb80ea9ca62..3fc54ff6d12da26de5f3e1fd04aba90a4733fe51 100644 (file)
@@ -492,6 +492,8 @@ class ConstraintButtonsPanel:
         col.prop(con, "frame_start", text="Start")
         col.prop(con, "frame_end", text="End")
 
+        layout.prop(con, "mix_mode", text="Mix")
+
     def LOCKED_TRACK(self, _context, layout, con):
         self.target_template(layout, con)
 
index c397fbcf1157b1d98205dae73377fbe84979e709..a17a09297c574e819271bbacc70caa57c1fabef7 100644 (file)
@@ -2062,36 +2062,21 @@ static void translike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *t
   bConstraintTarget *ct = targets->first;
 
   if (VALID_CONS_TARGET(ct)) {
-    if (data->mix_mode == TRANSLIKE_MIX_REPLACE) {
-      /* just copy the entire transform matrix of the target */
-      copy_m4_m4(cob->matrix, ct->matrix);
-    }
-    else {
-      float old_loc[3], old_rot[3][3], old_size[3];
-      float new_loc[3], new_rot[3][3], new_size[3];
-
-      /* Separate matrices so they can be combined in a way that avoids shear. */
-      mat4_to_loc_rot_size(old_loc, old_rot, old_size, cob->matrix);
-      mat4_to_loc_rot_size(new_loc, new_rot, new_size, ct->matrix);
-
-      switch (data->mix_mode) {
-        case TRANSLIKE_MIX_BEFORE:
-          mul_v3_m4v3(new_loc, ct->matrix, old_loc);
-          mul_m3_m3m3(new_rot, new_rot, old_rot);
-          mul_v3_v3(new_size, old_size);
-          break;
+    switch (data->mix_mode) {
+      case TRANSLIKE_MIX_REPLACE:
+        copy_m4_m4(cob->matrix, ct->matrix);
+        break;
 
-        case TRANSLIKE_MIX_AFTER:
-          mul_v3_m4v3(new_loc, cob->matrix, new_loc);
-          mul_m3_m3m3(new_rot, old_rot, new_rot);
-          mul_v3_v3(new_size, old_size);
-          break;
+      case TRANSLIKE_MIX_BEFORE:
+        mul_m4_m4m4_aligned_scale(cob->matrix, ct->matrix, cob->matrix);
+        break;
 
-        default:
-          BLI_assert(false);
-      }
+      case TRANSLIKE_MIX_AFTER:
+        mul_m4_m4m4_aligned_scale(cob->matrix, cob->matrix, ct->matrix);
+        break;
 
-      loc_rot_size_to_mat4(cob->matrix, new_loc, new_rot, new_size);
+      default:
+        BLI_assert(!"Unknown Copy Transforms mix mode");
     }
   }
 }
@@ -2555,6 +2540,9 @@ static void actcon_new_data(void *cdata)
 
   /* set type to 20 (Loc X), as 0 is Rot X for backwards compatibility */
   data->type = 20;
+
+  /* Set the mix mode to After Original with anti-shear scale handling. */
+  data->mix_mode = ACTCON_MIX_AFTER;
 }
 
 static void actcon_id_looper(bConstraint *con, ConstraintIDFunc func, void *userdata)
@@ -2695,18 +2683,28 @@ static void actcon_get_tarmat(struct Depsgraph *UNUSED(depsgraph),
   }
 }
 
-static void actcon_evaluate(bConstraint *UNUSED(con), bConstraintOb *cob, ListBase *targets)
+static void actcon_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *targets)
 {
+  bActionConstraint *data = con->data;
   bConstraintTarget *ct = targets->first;
 
   if (VALID_CONS_TARGET(ct)) {
-    float temp[4][4];
+    switch (data->mix_mode) {
+      case ACTCON_MIX_BEFORE:
+        mul_m4_m4m4_aligned_scale(cob->matrix, ct->matrix, cob->matrix);
+        break;
 
-    /* Nice and simple... we just need to multiply the matrices, as the get_target_matrix
-     * function has already taken care of everything else.
-     */
-    copy_m4_m4(temp, cob->matrix);
-    mul_m4_m4m4(cob->matrix, temp, ct->matrix);
+      case ACTCON_MIX_AFTER:
+        mul_m4_m4m4_aligned_scale(cob->matrix, cob->matrix, ct->matrix);
+        break;
+
+      case ACTCON_MIX_AFTER_FULL:
+        mul_m4_m4m4(cob->matrix, cob->matrix, ct->matrix);
+        break;
+
+      default:
+        BLI_assert(!"Unknown Action mix mode");
+    }
   }
 }
 
index 814b13fa47f66ed1dec9a62b53c8dd82e23e32d5..ac0f5f44c74bd65a3cfda3821297a461fc85a88c 100644 (file)
@@ -205,6 +205,8 @@ void mul_transposed_m3_v3(const float M[3][3], float r[3]);
 void mul_transposed_mat3_m4_v3(const float M[4][4], float r[3]);
 void mul_m3_v3_double(const float M[3][3], double r[3]);
 
+void mul_m4_m4m4_aligned_scale(float R[4][4], const float A[4][4], const float B[4][4]);
+
 void mul_m3_fl(float R[3][3], float f);
 void mul_m4_fl(float R[4][4], float f);
 void mul_mat3_m4_fl(float R[4][4], float f);
index 5118d8a698ed834afa983211bfe306f0a8380cc8..4d8a2f72eca2f9017638726e26358a14240fabf9 100644 (file)
@@ -1235,6 +1235,27 @@ bool invert_m4_m4(float inverse[4][4], const float mat[4][4])
 #endif
 }
 
+/**
+ * Combines transformations, handling scale separately in a manner equivalent
+ * to the Aligned Inherit Scale mode, in order to avoid creating shear.
+ * If A scale is uniform, the result is equivalent to ordinary multiplication.
+ */
+void mul_m4_m4m4_aligned_scale(float R[4][4], const float A[4][4], const float B[4][4])
+{
+  float loc_a[3], rot_a[3][3], size_a[3];
+  float loc_b[3], rot_b[3][3], size_b[3];
+  float loc_r[3], rot_r[3][3], size_r[3];
+
+  mat4_to_loc_rot_size(loc_a, rot_a, size_a, A);
+  mat4_to_loc_rot_size(loc_b, rot_b, size_b, B);
+
+  mul_v3_m4v3(loc_r, A, loc_b);
+  mul_m3_m3m3_uniq(rot_r, rot_a, rot_b);
+  mul_v3_v3v3(size_r, size_a, size_b);
+
+  loc_rot_size_to_mat4(R, loc_r, rot_r, size_r);
+}
+
 /****************************** Linear Algebra *******************************/
 
 void transpose_m3(float mat[3][3])
index db8f36883f8a7b11575685ccffb772d2f54aceb2..2001d42a5eb0727a6715fbde147b8a74dc80ce94 100644 (file)
@@ -1350,6 +1350,15 @@ bool constraints_list_needinv(TransInfo *t, ListBase *list)
             return true;
           }
         }
+        else if (con->type == CONSTRAINT_TYPE_ACTION) {
+          /* The Action constraint only does this in the Before mode. */
+          bActionConstraint *data = (bActionConstraint *)con->data;
+
+          if (ELEM(data->mix_mode, ACTCON_MIX_BEFORE) &&
+              ELEM(t->mode, TFM_ROTATION, TFM_TRANSLATION)) {
+            return true;
+          }
+        }
         else if (con->type == CONSTRAINT_TYPE_TRANSFORM) {
           /* Transform constraint needs it for rotation at least (r.57309),
            * but doing so when translating may also mess things up [#36203]
index 0baa11c30591a77bdbd81d15ac8e7a5cc4210ac7..f816041010bfa326498c37b18c0726e1bdb681f2 100644 (file)
@@ -336,6 +336,8 @@ typedef struct bActionConstraint {
   float min;
   float max;
   int flag;
+  char mix_mode;
+  char _pad[7];
   struct bAction *act;
   /** MAX_ID_NAME-2. */
   char subtarget[64];
@@ -865,6 +867,16 @@ typedef enum eActionConstraint_Flags {
   ACTCON_BONE_USE_OBJECT_ACTION = (1 << 0),
 } eActionConstraint_Flags;
 
+/* bActionConstraint.mix_mode */
+typedef enum eActionConstraint_MixMode {
+  /* Multiply the action transformation on the right. */
+  ACTCON_MIX_AFTER_FULL = 0,
+  /* Multiply the action transformation on the right, with anti-shear scale handling. */
+  ACTCON_MIX_AFTER,
+  /* Multiply the action transformation on the left, with anti-shear scale handling. */
+  ACTCON_MIX_BEFORE,
+} eActionConstraint_MixMode;
+
 /* Locked-Axis Values (Locked Track) */
 typedef enum eLockAxis_Modes {
   LOCK_X = 0,
index 6a10074810dff5e6276f0b1ce0f246975f0e1ed3..8e57de9baebcd5dc31e64407d76df4e122a9d6aa 100644 (file)
@@ -633,6 +633,23 @@ static void rna_ArmatureConstraint_target_clear(ID *id, bConstraint *con, Main *
   ED_object_constraint_dependency_tag_update(bmain, (Object *)id, con);
 }
 
+static void rna_ActionConstraint_mix_mode_set(PointerRNA *ptr, int value)
+{
+  bConstraint *con = (bConstraint *)ptr->data;
+  bActionConstraint *acon = (bActionConstraint *)con->data;
+
+  acon->mix_mode = value;
+
+  /* The After mode can be computed in world space for efficiency
+   * and backward compatibility, while Before requires Local. */
+  if (ELEM(value, ACTCON_MIX_AFTER, ACTCON_MIX_AFTER_FULL)) {
+    con->ownspace = CONSTRAINT_SPACE_WORLD;
+  }
+  else {
+    con->ownspace = CONSTRAINT_SPACE_LOCAL;
+  }
+}
+
 static void rna_ActionConstraint_minmax_range(
     PointerRNA *ptr, float *min, float *max, float *UNUSED(softmin), float *UNUSED(softmax))
 {
@@ -1618,6 +1635,29 @@ static void rna_def_constraint_action(BlenderRNA *brna)
       {0, NULL, 0, NULL, NULL},
   };
 
+  static const EnumPropertyItem mix_mode_items[] = {
+      {ACTCON_MIX_BEFORE,
+       "BEFORE",
+       0,
+       "Before Original",
+       "Apply the action channels before the original transformation, "
+       "as if applied to an imaginary parent with Aligned Inherit Scale"},
+      {ACTCON_MIX_AFTER,
+       "AFTER",
+       0,
+       "After Original",
+       "Apply the action channels after the original transformation, "
+       "as if applied to an imaginary child with Aligned Inherit Scale"},
+      {ACTCON_MIX_AFTER_FULL,
+       "AFTER_FULL",
+       0,
+       "After Original (Full Scale)",
+       "Apply the action channels after the original transformation, as if "
+       "applied to an imaginary child with Full Inherit Scale. This mode "
+       "can create shear and is provided only for backward compatibility"},
+      {0, NULL, 0, NULL, NULL},
+  };
+
   srna = RNA_def_struct(brna, "ActionConstraint", "Constraint");
   RNA_def_struct_ui_text(
       srna, "Action Constraint", "Map an action to the transform axes of a bone");
@@ -1626,6 +1666,16 @@ static void rna_def_constraint_action(BlenderRNA *brna)
 
   rna_def_constraint_target_common(srna);
 
+  prop = RNA_def_property(srna, "mix_mode", PROP_ENUM, PROP_NONE);
+  RNA_def_property_enum_sdna(prop, NULL, "mix_mode");
+  RNA_def_property_enum_items(prop, mix_mode_items);
+  RNA_def_property_enum_funcs(prop, NULL, "rna_ActionConstraint_mix_mode_set", NULL);
+  RNA_def_property_ui_text(
+      prop,
+      "Mix Mode",
+      "Specify how existing transformations and the action channels are combined");
+  RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
+
   prop = RNA_def_property(srna, "transform_channel", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "type");
   RNA_def_property_enum_items(prop, transform_channel_items);