Copy Transforms: implement Remove Target Shear and more Mix options.

This constraint can be naturally viewed as a prototype for a future
4x4 matrix math node (or subset thereof), since its basic semantics
already is matrix assignment. Thus it makes sense to add math options
to this constraint to increase flexibility in the meantime.

This patch adds support for several operations that would be useful:

- An option to remove shear in the incoming target matrix.

  Shear is known to cause issues for various mathematical operations,
  so an option to remove it at key points is useful.

  Constraints based on Euler like Copy Rotation and Limit Rotation
  already have always enabled shear removal built in, because their
  math doesn't work correctly with shear.

  In the future node system shear removal would be a separate node
  (and currently Limit Rotation can be used as a Remove Shear constraint).
  However removing shear from the result of the target space conversion
  before mixing (similar to Copy Rotation) has to be built into
  Copy Transforms itself as an option.

- More ways to combine the target and owner matrices.

  Similar to multiple Inherit Scale modes for parenting, there are
  multiple ways one may want to combine matrices based on context.
  This implements 3 variants for each of the Before/After modes
  (one of them already existing).

  - Full implements regular matrix multiplication as the most basic
    option. The downside is the risk of creating shear.
  - Aligned emulates the 'anti-shear' Aligned Inherit Scale mode,
    and basically uses Full for location, and Split for rotation/scale.
    (This choice already existed.)
  - Split Channels combines location, rotation and scale separately.

  Looking at D7547 there is demand for Split Channels in some cases,
  so I think it makes sense to include it in Copy Transforms too, so that
  the Mix menu items can be identical for it and the Action constraint.

Differential Revision: https://developer.blender.org/D9469
This commit is contained in:
Alexander Gavrilov 2020-11-04 19:29:27 +03:00
parent 01e1944cd4
commit bc8ae58727
Notes: blender-bot 2023-02-14 01:35:49 +01:00
Referenced by issue #83908, Streamlining advanced rigging of B-Bone chains.
8 changed files with 183 additions and 11 deletions

View File

@ -505,6 +505,7 @@ class ConstraintButtonsPanel:
self.target_template(layout, con)
layout.prop(con, "remove_target_shear")
layout.prop(con, "mix_mode", text="Mix")
self.space_template(layout, con)

View File

@ -2235,17 +2235,47 @@ static void translike_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *t
bConstraintTarget *ct = targets->first;
if (VALID_CONS_TARGET(ct)) {
float target_mat[4][4];
copy_m4_m4(target_mat, ct->matrix);
/* Remove the shear of the target matrix if enabled.
* Use Y as the axis since it's the natural default for bones. */
if (data->flag & TRANSLIKE_REMOVE_TARGET_SHEAR) {
orthogonalize_m4_stable(target_mat, 1, false);
}
/* Finally, combine the matrices. */
switch (data->mix_mode) {
case TRANSLIKE_MIX_REPLACE:
copy_m4_m4(cob->matrix, ct->matrix);
copy_m4_m4(cob->matrix, target_mat);
break;
/* Simple matrix multiplication. */
case TRANSLIKE_MIX_BEFORE_FULL:
mul_m4_m4m4(cob->matrix, target_mat, cob->matrix);
break;
case TRANSLIKE_MIX_AFTER_FULL:
mul_m4_m4m4(cob->matrix, cob->matrix, target_mat);
break;
/* Aligned Inherit Scale emulation. */
case TRANSLIKE_MIX_BEFORE:
mul_m4_m4m4_aligned_scale(cob->matrix, ct->matrix, cob->matrix);
mul_m4_m4m4_aligned_scale(cob->matrix, target_mat, cob->matrix);
break;
case TRANSLIKE_MIX_AFTER:
mul_m4_m4m4_aligned_scale(cob->matrix, cob->matrix, ct->matrix);
mul_m4_m4m4_aligned_scale(cob->matrix, cob->matrix, target_mat);
break;
/* Fully separate handling of channels. */
case TRANSLIKE_MIX_BEFORE_SPLIT:
mul_m4_m4m4_split_channels(cob->matrix, target_mat, cob->matrix);
break;
case TRANSLIKE_MIX_AFTER_SPLIT:
mul_m4_m4m4_split_channels(cob->matrix, cob->matrix, target_mat);
break;
default:

View File

@ -211,6 +211,7 @@ 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_m4_m4m4_split_channels(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);

View File

@ -1290,6 +1290,9 @@ bool invert_m4_m4(float inverse[4][4], const float mat[4][4])
* 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.
*
* Note: this effectively takes output location from simple multiplication,
* and uses mul_m4_m4m4_split_channels for rotation and scale.
*/
void mul_m4_m4m4_aligned_scale(float R[4][4], const float A[4][4], const float B[4][4])
{
@ -1307,6 +1310,25 @@ void mul_m4_m4m4_aligned_scale(float R[4][4], const float A[4][4], const float B
loc_rot_size_to_mat4(R, loc_r, rot_r, size_r);
}
/**
* Separately combines location, rotation and scale of the input matrices.
*/
void mul_m4_m4m4_split_channels(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);
add_v3_v3v3(loc_r, loc_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 R[3][3])

View File

@ -849,10 +849,13 @@ bool constraints_list_needinv(TransInfo *t, ListBase *list)
/* Copy Transforms constraint only does this in the Before mode. */
bTransLikeConstraint *data = (bTransLikeConstraint *)con->data;
if (ELEM(data->mix_mode, TRANSLIKE_MIX_BEFORE) &&
if (ELEM(data->mix_mode, TRANSLIKE_MIX_BEFORE, TRANSLIKE_MIX_BEFORE_FULL) &&
ELEM(t->mode, TFM_ROTATION, TFM_TRANSLATION)) {
return true;
}
if (ELEM(data->mix_mode, TRANSLIKE_MIX_BEFORE_SPLIT) && ELEM(t->mode, TFM_ROTATION)) {
return true;
}
}
else if (con->type == CONSTRAINT_TYPE_ACTION) {
/* The Action constraint only does this in the Before mode. */

View File

@ -316,8 +316,9 @@ typedef struct bSameVolumeConstraint {
/* Copy Transform Constraint */
typedef struct bTransLikeConstraint {
struct Object *tar;
int flag;
char mix_mode;
char _pad[7];
char _pad[3];
/** MAX_ID_NAME-2. */
char subtarget[64];
} bTransLikeConstraint;
@ -810,6 +811,12 @@ typedef enum eCopyScale_Flags {
SIZELIKE_UNIFORM = (1 << 5),
} eCopyScale_Flags;
/* bTransLikeConstraint.flag */
typedef enum eCopyTransforms_Flags {
/* Remove shear from the target matrix. */
TRANSLIKE_REMOVE_TARGET_SHEAR = (1 << 0),
} eCopyTransforms_Flags;
/* bTransLikeConstraint.mix_mode */
typedef enum eCopyTransforms_MixMode {
/* Replace rotation channel values. */
@ -818,6 +825,14 @@ typedef enum eCopyTransforms_MixMode {
TRANSLIKE_MIX_BEFORE = 1,
/* Multiply the copied transformation on the right, with anti-shear scale handling. */
TRANSLIKE_MIX_AFTER = 2,
/* Multiply the copied transformation on the left, handling loc/rot/scale separately. */
TRANSLIKE_MIX_BEFORE_SPLIT = 3,
/* Multiply the copied transformation on the right, handling loc/rot/scale separately. */
TRANSLIKE_MIX_AFTER_SPLIT = 4,
/* Multiply the copied transformation on the left, using simple matrix multiplication. */
TRANSLIKE_MIX_BEFORE_FULL = 5,
/* Multiply the copied transformation on the right, using simple matrix multiplication. */
TRANSLIKE_MIX_AFTER_FULL = 6,
} eCopyTransforms_MixMode;
/* bTransformConstraint.to/from */

View File

@ -1624,18 +1624,48 @@ static void rna_def_constraint_transform_like(BlenderRNA *brna)
0,
"Replace",
"Replace the original transformation with copied"},
{0, "", 0, NULL, NULL},
{TRANSLIKE_MIX_BEFORE_FULL,
"BEFORE_FULL",
0,
"Before Original (Full)",
"Apply copied transformation before original, using simple matrix multiplication as if "
"the constraint target is a parent in Full Inherit Scale mode. "
"Will create shear when combining rotation and non-uniform scale"},
{TRANSLIKE_MIX_BEFORE,
"BEFORE",
0,
"Before Original",
"Apply copied transformation before original, as if the constraint target is a parent. "
"Scale is handled specially to avoid creating shear"},
"Before Original (Aligned)",
"Apply copied transformation before original, as if the constraint target is a parent in "
"Aligned Inherit Scale mode. This effectively uses Full for location and Split Channels "
"for rotation and scale"},
{TRANSLIKE_MIX_BEFORE_SPLIT,
"BEFORE_SPLIT",
0,
"Before Original (Split Channels)",
"Apply copied transformation before original, handling location, rotation and scale "
"separately, similar to a sequence of three Copy constraints"},
{0, "", 0, NULL, NULL},
{TRANSLIKE_MIX_AFTER_FULL,
"AFTER_FULL",
0,
"After Original (Full)",
"Apply copied transformation after original, using simple matrix multiplication as if "
"the constraint target is a child in Full Inherit Scale mode. "
"Will create shear when combining rotation and non-uniform scale"},
{TRANSLIKE_MIX_AFTER,
"AFTER",
0,
"After Original",
"Apply copied transformation after original, as if the constraint target is a child. "
"Scale is handled specially to avoid creating shear"},
"After Original (Aligned)",
"Apply copied transformation after original, as if the constraint target is a child in "
"Aligned Inherit Scale mode. This effectively uses Full for location and Split Channels "
"for rotation and scale"},
{TRANSLIKE_MIX_AFTER_SPLIT,
"AFTER_SPLIT",
0,
"After Original (Split Channels)",
"Apply copied transformation after original, handling location, rotation and scale "
"separately, similar to a sequence of three Copy constraints"},
{0, NULL, 0, NULL, NULL},
};
@ -1653,6 +1683,12 @@ static void rna_def_constraint_transform_like(BlenderRNA *brna)
RNA_define_lib_overridable(true);
prop = RNA_def_property(srna, "remove_target_shear", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", TRANSLIKE_REMOVE_TARGET_SHEAR);
RNA_def_property_ui_text(
prop, "Remove Target Shear", "Remove shear from the target transformation before combining");
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);

View File

@ -375,6 +375,70 @@ class CustomSpaceTest(AbstractConstraintTests):
)))
class CopyTransformsTest(AbstractConstraintTests):
layer_collection = 'Copy Transforms'
def test_mix_mode_object(self):
"""Copy Transforms: all mix modes for objects"""
constraint = bpy.data.objects["Copy Transforms.object.owner"].constraints["Copy Transforms"]
constraint.mix_mode = 'REPLACE'
self.matrix_test('Copy Transforms.object.owner', Matrix((
(-0.7818737626075745, 0.14389121532440186, 0.4845699667930603, -0.017531070858240128),
(-0.2741589844226837, -0.591389000415802, -1.2397242784500122, -0.08039521425962448),
(0.04909384995698929, -1.0109175443649292, 0.7942137122154236, 0.1584688276052475),
(0.0, 0.0, 0.0, 1.0)
)))
constraint.mix_mode = 'BEFORE_FULL'
self.matrix_test('Copy Transforms.object.owner', Matrix((
(-1.0791258811950684, -0.021011866629123688, 0.3120136260986328, 0.9082338809967041),
(0.2128538191318512, -0.3411901891231537, -1.7376484870910645, -0.39762523770332336),
(-0.03584420680999756, -1.0162957906723022, 0.8004404306411743, -0.9015425443649292),
(0.0, 0.0, 0.0, 1.0)
)))
constraint.mix_mode = 'BEFORE'
self.matrix_test('Copy Transforms.object.owner', Matrix((
(-0.9952367544174194, -0.03077685832977295, 0.05301344022154808, 0.9082338809967041),
(-0.013416174799203873, -0.39984768629074097, -1.8665285110473633, -0.39762523770332336),
(0.03660336509346962, -0.9833710193634033, 0.75728839635849, -0.9015425443649292),
(0.0, 0.0, 0.0, 1.0)
)))
constraint.mix_mode = 'BEFORE_SPLIT'
self.matrix_test('Copy Transforms.object.owner', Matrix((
(-0.9952367544174194, -0.03077685832977295, 0.05301344022154808, -1.0175310373306274),
(-0.013416174799203873, -0.39984768629074097, -1.8665285110473633, 0.9196047782897949),
(0.03660336509346962, -0.9833710193634033, 0.75728839635849, 0.1584688276052475),
(0.0, 0.0, 0.0, 1.0)
)))
constraint.mix_mode = 'AFTER_FULL'
self.matrix_test('Copy Transforms.object.owner', Matrix((
(-0.8939255475997925, -0.2866469621658325, 0.7563635110855103, -0.964445173740387),
(-0.09460853785276413, -0.73727947473526, -1.0267245769500732, 0.9622588753700256),
(0.37042146921157837, -1.1893107891082764, 1.0113294124603271, 0.21314144134521484),
(0.0, 0.0, 0.0, 1.0)
)))
constraint.mix_mode = 'AFTER'
self.matrix_test('Copy Transforms.object.owner', Matrix((
(-0.9033845067024231, -0.2048732340335846, 0.7542480826377869, -0.964445173740387),
(-0.1757974475622177, -0.6721230745315552, -1.5190268754959106, 0.9622588753700256),
(0.38079890608787537, -0.7963172793388367, 1.0880682468414307, 0.21314144134521484),
(0.0, 0.0, 0.0, 1.0)
)))
constraint.mix_mode = 'AFTER_SPLIT'
self.matrix_test('Copy Transforms.object.owner', Matrix((
(-0.9033845067024231, -0.2048732340335846, 0.7542480826377869, -1.0175310373306274),
(-0.1757974475622177, -0.6721230745315552, -1.5190268754959106, 0.9196047782897949),
(0.38079890608787537, -0.7963172793388367, 1.0880682468414307, 0.1584688276052475),
(0.0, 0.0, 0.0, 1.0)
)))
def main():
global args
import argparse