1
1

Compare commits

...

1 Commits

Author SHA1 Message Date
f873b85a39 Fix T70269: replace the Set Inverse operator with an eval-time update.
It is rather complicated and hacky to try computing the correct
parent inverse matrix for Child Of outside of constraint evaluation.
To avoid problems, change the Set Inverse operator to simply set
a flag for the matrix to be recomputed during evaluation, which is
how it already works for some other constraints like Stretch To.

The downside of this approach obviously is that if the constraint
is disabled, Set Inverse will actually happen when it is re-enabled,
rather than immediately.

In addition, this changes the way how the inverse matrix works when
some of the channels of the constraint are disabled. Specifically,
before this the channel flags were used to filter both the parent
and the inverse matrix, which makes no sense, and means that it
is impossible to make an inverse matrix that would actually fully
neutralize the effect of the constraint. Now only the parent matrix
is filtered, while inverse is applied fully. This change is not
backward compatible, but it should be OK because the old way was
effectively unusable, so it's likely nobody relies on it.

Differential Revision: https://developer.blender.org/D6091
2019-12-25 17:53:08 +03:00
4 changed files with 89 additions and 180 deletions

View File

@@ -856,95 +856,91 @@ static void childof_evaluate(bConstraint *con, bConstraintOb *cob, ListBase *tar
/* only evaluate if there is a target */
if (VALID_CONS_TARGET(ct)) {
float parmat[4][4];
float parmat[4][4], obmat[4][4];
/* simple matrix parenting */
if (data->flag == CHILDOF_ALL) {
copy_m4_m4(obmat, cob->matrix);
/* multiply target (parent matrix) by offset (parent inverse) to get
* the effect of the parent that will be exerted on the owner
*/
mul_m4_m4m4(parmat, ct->matrix, data->invmat);
/* now multiply the parent matrix by the owner matrix to get the
* the effect of this constraint (i.e. owner is 'parented' to parent)
*/
mul_m4_m4m4(cob->matrix, parmat, cob->matrix);
/* Simple matrix parenting. */
if ((data->flag & CHILDOF_ALL) == CHILDOF_ALL) {
copy_m4_m4(parmat, ct->matrix);
}
/* Filter the parent matrix by channel. */
else {
float invmat[4][4], tempmat[4][4];
float loc[3], eul[3], size[3];
float loco[3], eulo[3], sizo[3];
/* get offset (parent-inverse) matrix */
copy_m4_m4(invmat, data->invmat);
/* extract components of both matrices */
copy_v3_v3(loc, ct->matrix[3]);
mat4_to_eulO(eul, ct->rotOrder, ct->matrix);
mat4_to_size(size, ct->matrix);
copy_v3_v3(loco, invmat[3]);
mat4_to_eulO(eulo, cob->rotOrder, invmat);
mat4_to_size(sizo, invmat);
/* disable channels not enabled */
if (!(data->flag & CHILDOF_LOCX)) {
loc[0] = loco[0] = 0.0f;
loc[0] = 0.0f;
}
if (!(data->flag & CHILDOF_LOCY)) {
loc[1] = loco[1] = 0.0f;
loc[1] = 0.0f;
}
if (!(data->flag & CHILDOF_LOCZ)) {
loc[2] = loco[2] = 0.0f;
loc[2] = 0.0f;
}
if (!(data->flag & CHILDOF_ROTX)) {
eul[0] = eulo[0] = 0.0f;
eul[0] = 0.0f;
}
if (!(data->flag & CHILDOF_ROTY)) {
eul[1] = eulo[1] = 0.0f;
eul[1] = 0.0f;
}
if (!(data->flag & CHILDOF_ROTZ)) {
eul[2] = eulo[2] = 0.0f;
eul[2] = 0.0f;
}
if (!(data->flag & CHILDOF_SIZEX)) {
size[0] = sizo[0] = 1.0f;
size[0] = 1.0f;
}
if (!(data->flag & CHILDOF_SIZEY)) {
size[1] = sizo[1] = 1.0f;
size[1] = 1.0f;
}
if (!(data->flag & CHILDOF_SIZEZ)) {
size[2] = sizo[2] = 1.0f;
size[2] = 1.0f;
}
/* make new target mat and offset mat */
loc_eulO_size_to_mat4(ct->matrix, loc, eul, size, ct->rotOrder);
loc_eulO_size_to_mat4(invmat, loco, eulo, sizo, cob->rotOrder);
loc_eulO_size_to_mat4(parmat, loc, eul, size, ct->rotOrder);
}
/* multiply target (parent matrix) by offset (parent inverse) to get
* the effect of the parent that will be exerted on the owner
*/
mul_m4_m4m4(parmat, ct->matrix, invmat);
/* Compute the inverse matrix if requested. */
if (data->flag & CHILDOF_SET_INVERSE) {
invert_m4_m4(data->invmat, parmat);
/* now multiply the parent matrix by the owner matrix to get the
* the effect of this constraint (i.e. owner is 'parented' to parent)
*/
copy_m4_m4(tempmat, cob->matrix);
mul_m4_m4m4(cob->matrix, parmat, tempmat);
data->flag &= ~CHILDOF_SET_INVERSE;
/* without this, changes to scale and rotation can change location
* of a parentless bone or a disconnected bone. Even though its set
* to zero above. */
if (!(data->flag & CHILDOF_LOCX)) {
cob->matrix[3][0] = tempmat[3][0];
}
if (!(data->flag & CHILDOF_LOCY)) {
cob->matrix[3][1] = tempmat[3][1];
}
if (!(data->flag & CHILDOF_LOCZ)) {
cob->matrix[3][2] = tempmat[3][2];
/* Write the computed matrix back to the master copy if in COW evaluation. */
bConstraint *orig_con = constraint_find_original_for_update(cob, con);
if (orig_con != NULL) {
bChildOfConstraint *orig_data = orig_con->data;
copy_m4_m4(orig_data->invmat, data->invmat);
orig_data->flag &= ~CHILDOF_SET_INVERSE;
}
}
/* Multiply together the target (parent) matrix, parent inverse,
* and the owner transform matrixto get the effect of this constraint
* (i.e. owner is 'parented' to parent). */
mul_m4_series(cob->matrix, parmat, data->invmat, obmat);
/* Without this, changes to scale and rotation can change location
* of a parentless bone or a disconnected bone. Even though its set
* to zero above. */
if (!(data->flag & CHILDOF_LOCX)) {
cob->matrix[3][0] = obmat[3][0];
}
if (!(data->flag & CHILDOF_LOCY)) {
cob->matrix[3][1] = obmat[3][1];
}
if (!(data->flag & CHILDOF_LOCZ)) {
cob->matrix[3][2] = obmat[3][2];
}
}
}
@@ -4891,23 +4887,35 @@ static void objectsolver_evaluate(bConstraint *con, bConstraintOb *cob, ListBase
object = BKE_tracking_object_get_named(tracking, data->object);
if (object) {
float mat[4][4], obmat[4][4], imat[4][4], cammat[4][4], camimat[4][4], parmat[4][4];
float mat[4][4], obmat[4][4], imat[4][4], parmat[4][4];
float ctime = DEG_get_ctime(depsgraph);
float framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, ctime);
BKE_object_where_is_calc_mat4(camob, cammat);
BKE_tracking_camera_get_reconstructed_interpolate(tracking, object, framenr, mat);
invert_m4_m4(camimat, cammat);
mul_m4_m4m4(parmat, cammat, data->invmat);
invert_m4_m4(imat, mat);
mul_m4_m4m4(parmat, camob->obmat, imat);
copy_m4_m4(cammat, camob->obmat);
copy_m4_m4(obmat, cob->matrix);
invert_m4_m4(imat, mat);
/* Recalculate the inverse matrix if requested. */
if (data->flag & OBJECTSOLVER_SET_INVERSE) {
invert_m4_m4(data->invmat, parmat);
mul_m4_series(cob->matrix, cammat, imat, camimat, parmat, obmat);
data->flag &= ~OBJECTSOLVER_SET_INVERSE;
/* Write the computed matrix back to the master copy if in COW evaluation. */
bConstraint *orig_con = constraint_find_original_for_update(cob, con);
if (orig_con != NULL) {
bObjectSolverConstraint *orig_data = orig_con->data;
copy_m4_m4(orig_data->invmat, data->invmat);
orig_data->flag &= ~OBJECTSOLVER_SET_INVERSE;
}
}
mul_m4_series(cob->matrix, parmat, data->invmat, obmat);
}
}
}

View File

@@ -871,118 +871,13 @@ void CONSTRAINT_OT_limitdistance_reset(wmOperatorType *ot)
/* ------------- Child-Of Constraint ------------------ */
static void child_get_inverse_matrix_owner_bone(
Depsgraph *depsgraph, wmOperator *op, Scene *scene, Object *ob, float invmat[4][4])
{
/* For bone owner we want to do this in evaluated domain.
* BKE_pose_where_is / BKE_pose_where_is_bone relies on (re)evaluating parts of the scene
* and copying new evaluated stuff back to original.
*/
Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
bConstraint *con_eval = edit_constraint_property_get(op, ob_eval, CONSTRAINT_TYPE_CHILDOF);
/* nullify inverse matrix first */
unit_m4(invmat);
bPoseChannel *pchan_eval = BKE_pose_channel_active(ob_eval);
/* try to find a pose channel - assume that this is the constraint owner */
/* TODO: get from context instead? */
if (ob_eval && ob_eval->pose && pchan_eval) {
bConstraint *con_last;
/* calculate/set inverse matrix:
* We just calculate all transform-stack eval up to but not including this constraint.
* This is because inverse should just inverse correct for just the constraint's influence
* when it gets applied; that is, at the time of application, we don't know anything about
* what follows.
*/
float imat[4][4], tmat[4][4];
float pmat[4][4];
/* make sure we passed the correct constraint */
BLI_assert(BLI_findindex(&pchan_eval->constraints, con_eval) != -1);
/* 1. calculate posemat where inverse doesn't exist yet (inverse was cleared above),
* to use as baseline ("pmat") to derive delta from. This extra calc saves users
* from having pressing "Clear Inverse" first
*/
BKE_pose_where_is(depsgraph, scene, ob_eval);
copy_m4_m4(pmat, pchan_eval->pose_mat);
/* 2. knock out constraints starting from this one */
con_last = pchan_eval->constraints.last;
pchan_eval->constraints.last = con_eval->prev;
if (con_eval->prev) {
/* new end must not point to this one, else this chain cutting is useless */
con_eval->prev->next = NULL;
}
else {
/* constraint was first */
pchan_eval->constraints.first = NULL;
}
/* 3. solve pose without disabled constraints */
BKE_pose_where_is(depsgraph, scene, ob_eval);
/* 4. determine effect of constraint by removing the newly calculated
* pchan->pose_mat from the original pchan->pose_mat, thus determining
* the effect of the constraint
*/
invert_m4_m4(imat, pchan_eval->pose_mat);
mul_m4_m4m4(tmat, pmat, imat);
invert_m4_m4(invmat, tmat);
/* 5. restore constraints */
pchan_eval->constraints.last = con_last;
if (con_eval->prev) {
/* hook up prev to this one again */
con_eval->prev->next = con_eval;
}
else {
/* set as first again */
pchan_eval->constraints.first = con_eval;
}
/* 6. recalculate pose with new inv-mat applied */
/* this one is unnecessary? (DEG seems to update correctly without)
+ if we leave this in, we have to click "Set Inverse" twice to see updates...
BKE_pose_where_is(depsgraph, scene, ob_eval); */
}
}
static void child_get_inverse_matrix_owner_object(
Depsgraph *depsgraph, Scene *scene, Object *ob, bConstraint *con, float invmat[4][4])
{
/* nullify inverse matrix first */
unit_m4(invmat);
if (ob) {
Object workob;
/* make sure we passed the correct constraint */
BLI_assert(BLI_findindex(&ob->constraints, con) != -1);
UNUSED_VARS_NDEBUG(con);
/* use BKE_object_workob_calc_parent to find inverse - just like for normal parenting */
BKE_object_workob_calc_parent(depsgraph, scene, ob, &workob);
invert_m4_m4(invmat, workob.obmat);
}
}
/* ChildOf Constraint - set inverse callback */
static int childof_set_inverse_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
Object *ob = ED_object_active_context(C);
bConstraint *con = edit_constraint_property_get(op, ob, CONSTRAINT_TYPE_CHILDOF);
bChildOfConstraint *data = (con) ? (bChildOfConstraint *)con->data : NULL;
const int owner = RNA_enum_get(op->ptr, "owner");
/* despite 3 layers of checks, we may still not be able to find a constraint */
if (data == NULL) {
@@ -991,12 +886,8 @@ static int childof_set_inverse_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (owner == EDIT_CONSTRAINT_OWNER_OBJECT) {
child_get_inverse_matrix_owner_object(depsgraph, scene, ob, con, data->invmat);
}
else if (owner == EDIT_CONSTRAINT_OWNER_BONE) {
child_get_inverse_matrix_owner_bone(depsgraph, op, scene, ob, data->invmat);
}
/* Set a flag to request recalculation on next update. */
data->flag |= CHILDOF_SET_INVERSE;
ED_object_constraint_update(bmain, ob);
WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob);
@@ -1231,12 +1122,9 @@ void CONSTRAINT_OT_followpath_path_animate(wmOperatorType *ot)
static int objectsolver_set_inverse_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Scene *scene = CTX_data_scene(C);
Object *ob = ED_object_active_context(C);
bConstraint *con = edit_constraint_property_get(op, ob, CONSTRAINT_TYPE_OBJECTSOLVER);
bObjectSolverConstraint *data = (con) ? (bObjectSolverConstraint *)con->data : NULL;
const int owner = RNA_enum_get(op->ptr, "owner");
/* despite 3 layers of checks, we may still not be able to find a constraint */
if (data == NULL) {
@@ -1245,12 +1133,8 @@ static int objectsolver_set_inverse_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (owner == EDIT_CONSTRAINT_OWNER_OBJECT) {
child_get_inverse_matrix_owner_object(depsgraph, scene, ob, con, data->invmat);
}
else if (owner == EDIT_CONSTRAINT_OWNER_BONE) {
child_get_inverse_matrix_owner_bone(depsgraph, op, scene, ob, data->invmat);
}
/* Set a flag to request recalculation on next update. */
data->flag |= OBJECTSOLVER_SET_INVERSE;
ED_object_constraint_update(bmain, ob);
WM_event_add_notifier(C, NC_OBJECT | ND_CONSTRAINT, ob);

View File

@@ -1103,6 +1103,8 @@ typedef enum eChildOf_Flags {
CHILDOF_SIZEY = (1 << 7),
CHILDOF_SIZEZ = (1 << 8),
CHILDOF_ALL = 511,
/* Temporary flag used by the Set Inverse operator. */
CHILDOF_SET_INVERSE = (1 << 9),
} eChildOf_Flags;
/* Pivot Constraint */
@@ -1154,6 +1156,8 @@ typedef enum eCameraSolver_Flags {
/* ObjectSolver Constraint -> flag */
typedef enum eObjectSolver_Flags {
OBJECTSOLVER_ACTIVECLIP = (1 << 0),
/* Temporary flag used by the Set Inverse operator. */
OBJECTSOLVER_SET_INVERSE = (1 << 1),
} eObjectSolver_Flags;
/* ObjectSolver Constraint -> flag */

View File

@@ -973,11 +973,18 @@ static void rna_def_constraint_childof(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Scale Z", "Use Z Scale of Parent");
RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
prop = RNA_def_property(srna, "set_inverse_pending", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", CHILDOF_SET_INVERSE);
RNA_def_property_ui_text(
prop, "Set Inverse Pending", "Set to true to request recalculation of the inverse matrix");
RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
prop = RNA_def_property(srna, "inverse_matrix", PROP_FLOAT, PROP_MATRIX);
RNA_def_property_float_sdna(prop, NULL, "invmat");
RNA_def_property_multi_array(prop, 2, rna_matrix_dimsize_4x4);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_ui_text(prop, "Inverse Matrix", "Transformation matrix to apply before");
RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
}
static void rna_def_constraint_python(BlenderRNA *brna)
@@ -3152,6 +3159,12 @@ static void rna_def_constraint_object_solver(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Active Clip", "Use active clip defined in scene");
RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
prop = RNA_def_property(srna, "set_inverse_pending", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", OBJECTSOLVER_SET_INVERSE);
RNA_def_property_ui_text(
prop, "Set Inverse Pending", "Set to true to request recalculation of the inverse matrix");
RNA_def_property_update(prop, NC_OBJECT | ND_CONSTRAINT, "rna_Constraint_update");
/* object */
prop = RNA_def_property(srna, "object", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "object");