Copy Rotation: implement new mixing modes that actually work.
authorAlexander Gavrilov <angavrilov@gmail.com>
Wed, 4 Sep 2019 09:06:59 +0000 (12:06 +0300)
committerAlexander Gavrilov <angavrilov@gmail.com>
Fri, 6 Sep 2019 04:57:16 +0000 (07:57 +0300)
Upon close inspection, the way the Offset mode works in the
Copy Rotation constraint makes no sense, and in fact, destroys
the rotation of its owner unless either it's single axis, or
the order is set specifically to `ZYX Euler`.

Since it can't simply be changed because of backward compatibility
concerns, replace the checkbox with a dropdown that provides a set
of new modes that actually make sense.

Specifically, add a mode that simply adds Euler components together,
and two options that use matrix multiplication in different order.

The Python use_offset property is replaced with compatibility stubs.

Reviewers: brecht

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

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/blenloader/intern/versioning_280.c
source/blender/editors/transform/transform_convert.c
source/blender/makesdna/DNA_constraint_types.h
source/blender/makesrna/intern/rna_constraint.c

index 0ba9fd0630ca616c75ee74fe2f5a9180eb26a946..ac1ebedeba47a8a05f659fd16705c4adb68b7cb9 100644 (file)
@@ -392,7 +392,7 @@ class ConstraintButtonsPanel:
         sub.active = con.use_z
         sub.prop(con, "invert_z", text="Invert")
 
-        layout.prop(con, "use_offset")
+        layout.prop(con, "mix_mode", text="Mix")
 
         self.space_template(layout, con)
 
index ccacb85651c2ba78a2c02cfc083e126ece786324..803aae76422ae0912f08b8164b1c115236a2d8fd 100644 (file)
@@ -1802,12 +1802,10 @@ static void rotlike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
   bConstraintTarget *ct = targets->first;
 
   if (VALID_CONS_TARGET(ct)) {
-    float loc[3];
-    float eul[3], obeul[3];
-    float size[3];
+    float loc[3], size[3], oldrot[3][3], newrot[3][3];
+    float eul[3], obeul[3], defeul[3];
 
-    copy_v3_v3(loc, cob->matrix[3]);
-    mat4_to_size(size, cob->matrix);
+    mat4_to_loc_rot_size(loc, oldrot, size, cob->matrix);
 
     /* Select the Euler rotation order, defaulting to the owner. */
     short rot_order = cob->rotOrder;
@@ -1822,11 +1820,28 @@ static void rotlike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
      * some of them can be modified below (see bug T21875). */
     mat4_to_compatible_eulO(eul, obeul, rot_order, ct->matrix);
 
+    /* Prepare the copied euler rotation. */
+    bool legacy_offset = false;
+
+    switch (data->mix_mode) {
+      case ROTLIKE_MIX_OFFSET:
+        legacy_offset = true;
+        copy_v3_v3(defeul, obeul);
+        break;
+
+      case ROTLIKE_MIX_REPLACE:
+        copy_v3_v3(defeul, obeul);
+        break;
+
+      default:
+        zero_v3(defeul);
+    }
+
     if ((data->flag & ROTLIKE_X) == 0) {
-      eul[0] = obeul[0];
+      eul[0] = defeul[0];
     }
     else {
-      if (data->flag & ROTLIKE_OFFSET) {
+      if (legacy_offset) {
         rotate_eulO(eul, rot_order, 'X', obeul[0]);
       }
 
@@ -1836,10 +1851,10 @@ static void rotlike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
     }
 
     if ((data->flag & ROTLIKE_Y) == 0) {
-      eul[1] = obeul[1];
+      eul[1] = defeul[1];
     }
     else {
-      if (data->flag & ROTLIKE_OFFSET) {
+      if (legacy_offset) {
         rotate_eulO(eul, rot_order, 'Y', obeul[1]);
       }
 
@@ -1849,10 +1864,10 @@ static void rotlike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
     }
 
     if ((data->flag & ROTLIKE_Z) == 0) {
-      eul[2] = obeul[2];
+      eul[2] = defeul[2];
     }
     else {
-      if (data->flag & ROTLIKE_OFFSET) {
+      if (legacy_offset) {
         rotate_eulO(eul, rot_order, 'Z', obeul[2]);
       }
 
@@ -1861,10 +1876,36 @@ static void rotlike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
       }
     }
 
+    /* Add the euler components together if needed. */
+    if (data->mix_mode == ROTLIKE_MIX_ADD) {
+      add_v3_v3(eul, obeul);
+    }
+
     /* Good to make eulers compatible again,
      * since we don't know how much they were changed above. */
     compatible_eul(eul, obeul);
-    loc_eulO_size_to_mat4(cob->matrix, loc, eul, size, rot_order);
+    eulO_to_mat3(newrot, eul, rot_order);
+
+    /* Mix the rotation matrices: */
+    switch (data->mix_mode) {
+      case ROTLIKE_MIX_REPLACE:
+      case ROTLIKE_MIX_OFFSET:
+      case ROTLIKE_MIX_ADD:
+        break;
+
+      case ROTLIKE_MIX_BEFORE:
+        mul_m3_m3m3(newrot, newrot, oldrot);
+        break;
+
+      case ROTLIKE_MIX_AFTER:
+        mul_m3_m3m3(newrot, oldrot, newrot);
+        break;
+
+      default:
+        BLI_assert(false);
+    }
+
+    loc_rot_size_to_mat4(cob->matrix, loc, newrot, size);
   }
 }
 
index 00c301a01bc74f2f760ee54cb953f9215059e31f..814b13fa47f66ed1dec9a62b53c8dd82e23e32d5 100644 (file)
@@ -310,7 +310,7 @@ void mat4_to_size_fix_shear(float r[3], const float M[4][4]);
 
 void translate_m4(float mat[4][4], float tx, float ty, float tz);
 void rotate_m4(float mat[4][4], const char axis, const float angle);
-void rescale_m4(float mat[4][4], float scale[3]);
+void rescale_m4(float mat[4][4], const float scale[3]);
 void transform_pivot_set_m4(float mat[4][4], const float pivot[3]);
 
 void mat3_to_rot_size(float rot[3][3], float size[3], const float mat3[3][3]);
@@ -320,6 +320,10 @@ void mat4_decompose(float loc[3], float quat[4], float size[3], const float wmat
 
 void mat3_polar_decompose(const float mat3[3][3], float r_U[3][3], float r_P[3][3]);
 
+void loc_rot_size_to_mat4(float R[4][4],
+                          const float loc[3],
+                          const float rot[3][3],
+                          const float size[3]);
 void loc_eul_size_to_mat4(float R[4][4],
                           const float loc[3],
                           const float eul[3],
index 7c1b44978e2e69c518969508398850393fe56c0a..e9d196ccdbba158c07fae6708e6f37119d9f7d65 100644 (file)
@@ -2164,7 +2164,7 @@ void rotate_m4(float mat[4][4], const char axis, const float angle)
 }
 
 /** Scale a matrix in-place. */
-void rescale_m4(float mat[4][4], float scale[3])
+void rescale_m4(float mat[4][4], const float scale[3])
 {
   mul_v3_fl(mat[0], scale[0]);
   mul_v3_fl(mat[1], scale[1]);
@@ -2356,6 +2356,20 @@ bool equals_m4m4(const float mat1[4][4], const float mat2[4][4])
           equals_v4v4(mat1[2], mat2[2]) && equals_v4v4(mat1[3], mat2[3]));
 }
 
+/**
+ * Make a 4x4 matrix out of 3 transform components.
+ * Matrices are made in the order: `scale * rot * loc`
+ */
+void loc_rot_size_to_mat4(float mat[4][4],
+                          const float loc[3],
+                          const float rot[3][3],
+                          const float size[3])
+{
+  copy_m4_m3(mat, rot);
+  rescale_m4(mat, size);
+  copy_v3_v3(mat[3], loc);
+}
+
 /**
  * Make a 4x4 matrix out of 3 transform components.
  * Matrices are made in the order: `scale * rot * loc`
index 54d2bd8499f380836ef754f5163383ebc16445d6..0d108704b4e0572a78d83fc29629875508049eae 100644 (file)
@@ -727,6 +727,17 @@ static void do_version_constraints_copy_scale_power(ListBase *lb)
   }
 }
 
+static void do_version_constraints_copy_rotation_mix_mode(ListBase *lb)
+{
+  for (bConstraint *con = lb->first; con; con = con->next) {
+    if (con->type == CONSTRAINT_TYPE_ROTLIKE) {
+      bRotateLikeConstraint *data = (bRotateLikeConstraint *)con->data;
+      data->mix_mode = (data->flag & ROTLIKE_OFFSET) ? ROTLIKE_MIX_OFFSET : ROTLIKE_MIX_REPLACE;
+      data->flag &= ~ROTLIKE_OFFSET;
+    }
+  }
+}
+
 static void do_versions_seq_alloc_transform_and_crop(ListBase *seqbase)
 {
   for (Sequence *seq = seqbase->first; seq != NULL; seq = seq->next) {
@@ -3797,5 +3808,17 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
         do_version_bones_inherit_scale(&arm->bonebase);
       }
     }
+
+    /* Convert the Offset flag to the mix mode enum. */
+    if (!DNA_struct_elem_find(fd->filesdna, "bRotateLikeConstraint", "char", "mix_mode")) {
+      LISTBASE_FOREACH (Object *, ob, &bmain->objects) {
+        do_version_constraints_copy_rotation_mix_mode(&ob->constraints);
+        if (ob->pose) {
+          LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
+            do_version_constraints_copy_rotation_mix_mode(&pchan->constraints);
+          }
+        }
+      }
+    }
   }
 }
index 69bc6afd23f4ab42e9b447c8351f2a9335c79ed1..14eee2318cde6af7af9a5bb7d9d728680f7d82f2 100644 (file)
@@ -1330,7 +1330,8 @@ bool constraints_list_needinv(TransInfo *t, ListBase *list)
           /* CopyRot constraint only does this when rotating, and offset is on */
           bRotateLikeConstraint *data = (bRotateLikeConstraint *)con->data;
 
-          if ((data->flag & ROTLIKE_OFFSET) && (t->mode == TFM_ROTATION)) {
+          if (ELEM(data->mix_mode, ROTLIKE_MIX_OFFSET, ROTLIKE_MIX_BEFORE) &&
+              ELEM(t->mode, TFM_ROTATION)) {
             return true;
           }
         }
index 42d58cb34d01ff4d4a41b194a9537719e7221a65..a7f900ddc9b79f6ee2275c1f0485a1e7ff401f43 100644 (file)
@@ -272,7 +272,8 @@ typedef struct bRotateLikeConstraint {
   struct Object *tar;
   int flag;
   char euler_order;
-  char _pad[3];
+  char mix_mode;
+  char _pad[2];
   /** MAX_ID_NAME-2. */
   char subtarget[64];
 } bRotateLikeConstraint;
@@ -747,9 +748,25 @@ typedef enum eCopyRotation_Flags {
   ROTLIKE_X_INVERT = (1 << 4),
   ROTLIKE_Y_INVERT = (1 << 5),
   ROTLIKE_Z_INVERT = (1 << 6),
+#ifdef DNA_DEPRECATED
   ROTLIKE_OFFSET = (1 << 7),
+#endif
 } eCopyRotation_Flags;
 
+/* bRotateLikeConstraint.mix_mode */
+typedef enum eCopyRotation_MixMode {
+  /* Replace rotation channel values. */
+  ROTLIKE_MIX_REPLACE = 0,
+  /* Legacy Offset mode - don't use. */
+  ROTLIKE_MIX_OFFSET,
+  /* Add Euler components together. */
+  ROTLIKE_MIX_ADD,
+  /* Multiply the copied rotation on the left. */
+  ROTLIKE_MIX_BEFORE,
+  /* Multiply the copied rotation on the right. */
+  ROTLIKE_MIX_AFTER,
+} eCopyRotation_MixMode;
+
 /* bLocateLikeConstraint.flag */
 typedef enum eCopyLocation_Flags {
   LOCLIKE_X = (1 << 0),
index 05154dcc20e4e6cdbc741545efd2c7482a57ebc5..79e38717569fd085f4577b90c3b2e1d1f4215011 100644 (file)
@@ -530,6 +530,24 @@ static void rna_Constraint_ik_type_set(struct PointerRNA *ptr, int value)
   }
 }
 
+/* DEPRECATED: use_offset replaced with mix_mode */
+static bool rna_Constraint_RotLike_use_offset_get(struct PointerRNA *ptr)
+{
+  bConstraint *con = ptr->data;
+  bRotateLikeConstraint *rotlike = con->data;
+  return rotlike->mix_mode != ROTLIKE_MIX_REPLACE;
+}
+
+static void rna_Constraint_RotLike_use_offset_set(struct PointerRNA *ptr, bool value)
+{
+  bConstraint *con = ptr->data;
+  bRotateLikeConstraint *rotlike = con->data;
+  bool curval = (rotlike->mix_mode != ROTLIKE_MIX_REPLACE);
+  if (curval != value) {
+    rotlike->mix_mode = (value ? ROTLIKE_MIX_OFFSET : ROTLIKE_MIX_REPLACE);
+  }
+}
+
 static const EnumPropertyItem *rna_Constraint_owner_space_itemf(bContext *UNUSED(C),
                                                                 PointerRNA *ptr,
                                                                 PropertyRNA *UNUSED(prop),
@@ -1298,6 +1316,28 @@ static void rna_def_constraint_rotate_like(BlenderRNA *brna)
   StructRNA *srna;
   PropertyRNA *prop;
 
+  static const EnumPropertyItem mix_mode_items[] = {
+      {ROTLIKE_MIX_REPLACE, "REPLACE", 0, "Replace", "Replace the original rotation with copied"},
+      {ROTLIKE_MIX_ADD, "ADD", 0, "Add", "Add euler component values together"},
+      {ROTLIKE_MIX_BEFORE,
+       "BEFORE",
+       0,
+       "Before Original",
+       "Apply copied rotation before original, as if the constraint target is a parent"},
+      {ROTLIKE_MIX_AFTER,
+       "AFTER",
+       0,
+       "After Original",
+       "Apply copied rotation after original, as if the constraint target is a child"},
+      {ROTLIKE_MIX_OFFSET,
+       "OFFSET",
+       0,
+       "Offset (Legacy)",
+       "Combine rotations like the original Offset checkbox. Does not work well for "
+       "multiple axis rotations"},
+      {0, NULL, 0, NULL, NULL},
+  };
+
   srna = RNA_def_struct(brna, "CopyRotationConstraint", "Constraint");
   RNA_def_struct_ui_text(srna, "Copy Rotation Constraint", "Copy the rotation of the target");
   RNA_def_struct_sdna_from(srna, "bRotateLikeConstraint", "data");
@@ -1341,9 +1381,19 @@ static void rna_def_constraint_rotate_like(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Euler Order", "Explicitly specify the euler rotation order");
   RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
 
+  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_ui_text(
+      prop, "Mix Mode", "Specify how the copied and existing rotations are combined");
+  RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
+
+  /* DEPRECATED: replaced with mix_mode */
   prop = RNA_def_property(srna, "use_offset", PROP_BOOLEAN, PROP_NONE);
-  RNA_def_property_boolean_sdna(prop, NULL, "flag", ROTLIKE_OFFSET);
-  RNA_def_property_ui_text(prop, "Offset", "Add original rotation into copied rotation");
+  RNA_def_property_boolean_funcs(
+      prop, "rna_Constraint_RotLike_use_offset_get", "rna_Constraint_RotLike_use_offset_set");
+  RNA_def_property_ui_text(
+      prop, "Offset", "DEPRECATED: Add original rotation into copied rotation");
   RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
 }