Copy Rotation: implement new mixing modes that actually work.

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
This commit is contained in:
Alexander Gavrilov 2019-09-04 12:06:59 +03:00
parent 9972d6c306
commit f4056e9ec3
Notes: blender-bot 2023-02-14 04:24:05 +01:00
Referenced by issue #85350, Copy Rotation World Space to World Space flips when applying Mix Before, After or Legacy
8 changed files with 169 additions and 19 deletions

View 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)

View 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);
}
}

View 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],

View 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`

View 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);
}
}
}
}
}
}

View 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;
}
}

View 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),

View 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");
}