diff options
author | Alexander Gavrilov <angavrilov@gmail.com> | 2018-07-08 17:10:15 +0300 |
---|---|---|
committer | Alexander Gavrilov <angavrilov@gmail.com> | 2019-05-13 19:17:53 +0300 |
commit | 0264d8390fa28153e8a122ed419faba936944bd9 (patch) | |
tree | 18882a606ca9cbb119cc708d34852638748c3429 /source | |
parent | 93cabce3a156a07a76c26d7f3e78c3bc3212f623 (diff) |
Apply Pose as Rest Pose: implement an Only Selected bones option.
The most difficult part is handling parent-child relations correctly:
when a parent is applied, the children should be moved accordingly,
and when applying a child, it should not include transformation from
unapplied parents. All this requires walking bones as a tree, instead
of a flat list.
Limitation: Applying bones with non-uniform scaling without also applying
children will distort non-rest posing on said children for reasons related
to T54159 (basically, non-uniform scale plus rotation creates shear, and
Blender matrix decomposition utilities don't have tools to deal with it).
Reviewers: campbellbarton, brecht, mont29
Differential Revision: https://developer.blender.org/D3775
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/editors/armature/pose_transform.c | 337 |
1 files changed, 270 insertions, 67 deletions
diff --git a/source/blender/editors/armature/pose_transform.c b/source/blender/editors/armature/pose_transform.c index 6207d5412cf..49b66429515 100644 --- a/source/blender/editors/armature/pose_transform.c +++ b/source/blender/editors/armature/pose_transform.c @@ -59,6 +59,9 @@ #include "ED_screen.h" #include "ED_util.h" +#include "UI_interface.h" +#include "UI_resources.h" + #include "armature_intern.h" /* ********************************************** */ @@ -87,6 +90,229 @@ static void applyarmature_fix_boneparents(const bContext *C, Scene *scene, Objec } } +/* Sets the bone head, tail and roll to match the supplied parameters. */ +static void applyarmature_set_edit_position(EditBone *curbone, + const float pose_mat[4][4], + const float new_tail[3], + float r_new_arm_mat[4][4]) +{ + /* Simply copy the head/tail values from pchan over to curbone. */ + copy_v3_v3(curbone->head, pose_mat[3]); + copy_v3_v3(curbone->tail, new_tail); + + /* Fix roll: + * 1. find auto-calculated roll value for this bone now + * 2. remove this from the 'visual' y-rotation + */ + { + float premat[3][3], pmat[3][3]; + float delta[3]; + + /* Obtain new auto y-rotation. */ + sub_v3_v3v3(delta, curbone->tail, curbone->head); + + copy_m3_m4(pmat, pose_mat); + mat3_vec_to_roll(pmat, delta, &curbone->roll); + + /* Compute new rest pose matrix if requested. */ + if (r_new_arm_mat) { + vec_roll_to_mat3(delta, curbone->roll, premat); + copy_m4_m3(r_new_arm_mat, premat); + copy_v3_v3(r_new_arm_mat[3], pose_mat[3]); + } + } +} + +/* Copy properties over from pchan to curbone and reset channels. */ +static void applyarmature_transfer_properties(EditBone *curbone, + bPoseChannel *pchan, + const bPoseChannel *pchan_eval) +{ + /* Combine pose and rest values for bendy bone settings, + * then clear the pchan values (so we don't get a double-up). + */ + if (pchan->bone->segments > 1) { + /* Combine rest/pose values. */ + curbone->curve_in_x += pchan_eval->curve_in_x; + curbone->curve_in_y += pchan_eval->curve_in_y; + curbone->curve_out_x += pchan_eval->curve_out_x; + curbone->curve_out_y += pchan_eval->curve_out_y; + curbone->roll1 += pchan_eval->roll1; + curbone->roll2 += pchan_eval->roll2; + curbone->ease1 += pchan_eval->ease1; + curbone->ease2 += pchan_eval->ease2; + + curbone->scale_in_x *= pchan_eval->scale_in_x; + curbone->scale_in_y *= pchan_eval->scale_in_y; + curbone->scale_out_x *= pchan_eval->scale_out_x; + curbone->scale_out_y *= pchan_eval->scale_out_y; + + /* Reset pose values. */ + pchan->curve_in_x = pchan->curve_out_x = 0.0f; + pchan->curve_in_y = pchan->curve_out_y = 0.0f; + pchan->roll1 = pchan->roll2 = 0.0f; + pchan->ease1 = pchan->ease2 = 0.0f; + pchan->scale_in_x = pchan->scale_in_y = 1.0f; + pchan->scale_out_x = pchan->scale_out_y = 1.0f; + } + + /* Clear transform values for pchan. */ + zero_v3(pchan->loc); + zero_v3(pchan->eul); + unit_qt(pchan->quat); + unit_axis_angle(pchan->rotAxis, &pchan->rotAngle); + pchan->size[0] = pchan->size[1] = pchan->size[2] = 1.0f; + + /* Set anim lock. */ + curbone->flag |= BONE_UNKEYED; +} + +/* Adjust the current edit position of the bone using the pose space matrix. */ +static void applyarmature_adjust_edit_position(bArmature *arm, + bPoseChannel *pchan, + const float delta_mat[4][4], + float r_new_arm_mat[4][4]) +{ + EditBone *curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name); + float delta[3], new_tail[3], premat[3][3], new_pose[4][4]; + + /* Current orientation matrix. */ + sub_v3_v3v3(delta, curbone->tail, curbone->head); + vec_roll_to_mat3(delta, curbone->roll, premat); + + /* New location and orientation. */ + mul_m4_m4m3(new_pose, delta_mat, premat); + mul_v3_m4v3(new_pose[3], delta_mat, curbone->head); + mul_v3_m4v3(new_tail, delta_mat, curbone->tail); + + applyarmature_set_edit_position(curbone, new_pose, new_tail, r_new_arm_mat); +} + +/* Data about parent position for Apply To Selected mode. */ +typedef struct ApplyArmature_ParentState { + Bone *bone; + + /* New rest position of the bone with scale included. */ + float new_rest_mat[4][4]; + /* New arm_mat of the bone == new_rest_mat without scale. */ + float new_arm_mat[4][4]; +} ApplyArmature_ParentState; + +/* Recursive walk for Apply To Selected mode; pstate NULL unless child of an applied bone. */ +static void applyarmature_process_selected_rec(bArmature *arm, + bPose *pose, + bPose *pose_eval, + Bone *bone, + ListBase *selected, + ApplyArmature_ParentState *pstate) +{ + bPoseChannel *pchan = BKE_pose_channel_find_name(pose, bone->name); + const bPoseChannel *pchan_eval = BKE_pose_channel_find_name(pose_eval, bone->name); + + if (!pchan || !pchan_eval) + return; + + ApplyArmature_ParentState new_pstate = {.bone = bone}; + + if (BLI_findptr(selected, pchan, offsetof(CollectionPointerLink, ptr.data))) { + /* SELECTED BONE: Snap to final pose transform minus unapplied parent effects. + * + * I.e. bone position with accumulated parent effects but no local + * transformation will match the original final pose_mat. + * + * Pose channels are reset as expected. + */ + EditBone *curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name); + BoneParentTransform invparent; + float new_tail[3]; + + if (pchan->parent) { + BoneParentTransform old_bpt, new_bpt; + float offs_bone[4][4]; + + /* Parent effects on the bone transform that have to be removed. */ + BKE_bone_offset_matrix_get(bone, offs_bone); + BKE_bone_parent_transform_calc_from_matrices( + bone->flag, offs_bone, bone->parent->arm_mat, pchan_eval->parent->pose_mat, &old_bpt); + + /* Applied parent effects that have to be kept, if any. */ + float(*new_parent_pose)[4] = pstate ? pstate->new_rest_mat : bone->parent->arm_mat; + BKE_bone_parent_transform_calc_from_matrices( + bone->flag, offs_bone, bone->parent->arm_mat, new_parent_pose, &new_bpt); + + BKE_bone_parent_transform_invert(&old_bpt); + BKE_bone_parent_transform_combine(&new_bpt, &old_bpt, &invparent); + } + else { + BKE_bone_parent_transform_clear(&invparent); + } + + /* Apply change without inherited unapplied parent transformations. */ + BKE_bone_parent_transform_apply(&invparent, pchan_eval->pose_mat, new_pstate.new_rest_mat); + + copy_v3_fl3(new_tail, 0.0, bone->length, 0.0); + mul_m4_v3(new_pstate.new_rest_mat, new_tail); + + applyarmature_set_edit_position( + curbone, new_pstate.new_rest_mat, new_tail, new_pstate.new_arm_mat); + applyarmature_transfer_properties(curbone, pchan, pchan_eval); + + pstate = &new_pstate; + } + else if (pstate) { + /* UNSELECTED CHILD OF SELECTED: Include applied parent effects. + * + * The inherited transform of applied (selected) bones is baked + * into the rest pose so that the final bone position doesn't + * change. + * + * Pose channels are not changed, with the exception of the inherited + * applied parent scale being baked into the location pose channel. + */ + BoneParentTransform bpt; + float offs_bone[4][4], delta[4][4], old_chan_loc[3]; + + /* Include applied parent effects. */ + BKE_bone_offset_matrix_get(bone, offs_bone); + BKE_bone_parent_transform_calc_from_matrices( + bone->flag, offs_bone, pstate->bone->arm_mat, pstate->new_rest_mat, &bpt); + + unit_m4(new_pstate.new_rest_mat); + BKE_bone_parent_transform_apply(&bpt, new_pstate.new_rest_mat, new_pstate.new_rest_mat); + + /* Bone location channel in pose space relative to bone head. */ + mul_v3_mat3_m4v3(old_chan_loc, bpt.loc_mat, pchan_eval->loc); + + /* Apply the change to the rest bone position. */ + invert_m4_m4(delta, bone->arm_mat); + mul_m4_m4m4(delta, new_pstate.new_rest_mat, delta); + + applyarmature_adjust_edit_position(arm, pchan, delta, new_pstate.new_arm_mat); + + /* Location pose channel has to be updated, because it is affected + * by parent scaling, and the rest pose has no scale by definition. */ + if (!(bone->flag & BONE_CONNECTED) && !is_zero_v3(old_chan_loc)) { + float inv_parent_arm[4][4]; + + /* Compute the channel coordinate space matrices for the new rest state. */ + invert_m4_m4(inv_parent_arm, pstate->new_arm_mat); + mul_m4_m4m4(offs_bone, inv_parent_arm, new_pstate.new_arm_mat); + BKE_bone_parent_transform_calc_from_matrices( + bone->flag, offs_bone, pstate->new_arm_mat, pstate->new_arm_mat, &bpt); + + /* Re-apply the location to keep the final effect. */ + invert_m4(bpt.loc_mat); + mul_v3_mat3_m4v3(pchan->loc, bpt.loc_mat, old_chan_loc); + } + + pstate = &new_pstate; + } + + for (Bone *child = bone->childbase.first; child; child = child->next) { + applyarmature_process_selected_rec(arm, pose, pose_eval, child, selected, pstate); + } +} + /* set the current pose as the restpose */ static int apply_armature_pose2bones_exec(bContext *C, wmOperator *op) { @@ -99,7 +325,9 @@ static int apply_armature_pose2bones_exec(bContext *C, wmOperator *op) bArmature *arm = BKE_armature_from_object(ob); bPose *pose; bPoseChannel *pchan; - EditBone *curbone; + ListBase selected_bones; + + const bool use_selected = RNA_boolean_get(op->ptr, "selected"); /* don't check if editmode (should be done by caller) */ if (ob->type != OB_ARMATURE) { @@ -119,80 +347,37 @@ static int apply_armature_pose2bones_exec(bContext *C, wmOperator *op) "transforms stored are relative to the old rest pose"); } + /* Find selected bones before switching to edit mode. */ + if (use_selected) { + CTX_data_selected_pose_bones(C, &selected_bones); + + if (!selected_bones.first) { + return OPERATOR_CANCELLED; + } + } + /* Get editbones of active armature to alter */ ED_armature_to_edit(arm); /* get pose of active object and move it out of posemode */ pose = ob->pose; - for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) { - const bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); - curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name); - - /* simply copy the head/tail values from pchan over to curbone */ - copy_v3_v3(curbone->head, pchan_eval->pose_head); - copy_v3_v3(curbone->tail, pchan_eval->pose_tail); - - /* fix roll: - * 1. find auto-calculated roll value for this bone now - * 2. remove this from the 'visual' y-rotation - */ - { - float premat[3][3], imat[3][3], pmat[3][3], tmat[3][3]; - float delta[3], eul[3]; - - /* obtain new auto y-rotation */ - sub_v3_v3v3(delta, curbone->tail, curbone->head); - vec_roll_to_mat3(delta, 0.0f, premat); - invert_m3_m3(imat, premat); - - /* get pchan 'visual' matrix */ - copy_m3_m4(pmat, pchan_eval->pose_mat); - - /* remove auto from visual and get euler rotation */ - mul_m3_m3m3(tmat, imat, pmat); - mat3_to_eul(eul, tmat); - - /* just use this euler-y as new roll value */ - curbone->roll = eul[1]; + if (use_selected) { + /* The selected only mode requires a recursive walk to handle parent-child relations. */ + for (Bone *bone = arm->bonebase.first; bone; bone = bone->next) { + applyarmature_process_selected_rec(arm, pose, ob_eval->pose, bone, &selected_bones, NULL); } - /* combine pose and rest values for bendy bone settings, - * then clear the pchan values (so we don't get a double-up) - */ - if (pchan->bone->segments > 1) { - /* combine rest/pose values */ - curbone->curve_in_x += pchan_eval->curve_in_x; - curbone->curve_in_y += pchan_eval->curve_in_y; - curbone->curve_out_x += pchan_eval->curve_out_x; - curbone->curve_out_y += pchan_eval->curve_out_y; - curbone->roll1 += pchan_eval->roll1; - curbone->roll2 += pchan_eval->roll2; - curbone->ease1 += pchan_eval->ease1; - curbone->ease2 += pchan_eval->ease2; - curbone->scale_in_x *= pchan_eval->scale_in_x; - curbone->scale_in_y *= pchan_eval->scale_in_y; - curbone->scale_out_x *= pchan_eval->scale_out_x; - curbone->scale_out_y *= pchan_eval->scale_out_y; - - /* reset pose values */ - pchan->curve_in_x = pchan->curve_out_x = 0.0f; - pchan->curve_in_y = pchan->curve_out_y = 0.0f; - pchan->roll1 = pchan->roll2 = 0.0f; - pchan->ease1 = pchan->ease2 = 0.0f; - pchan->scale_in_x = pchan->scale_in_y = 1.0f; - pchan->scale_out_x = pchan->scale_out_y = 1.0f; - } - - /* clear transform values for pchan */ - zero_v3(pchan->loc); - zero_v3(pchan->eul); - unit_qt(pchan->quat); - unit_axis_angle(pchan->rotAxis, &pchan->rotAngle); - pchan->size[0] = pchan->size[1] = pchan->size[2] = 1.0f; + BLI_freelistN(&selected_bones); + } + else { + for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) { + const bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); + EditBone *curbone = ED_armature_ebone_find_name(arm->edbo, pchan->name); - /* set anim lock */ - curbone->flag |= BONE_UNKEYED; + applyarmature_set_edit_position(curbone, pchan_eval->pose_mat, pchan_eval->pose_tail, NULL); + applyarmature_transfer_properties(curbone, pchan, pchan_eval); + } } /* convert editbones back to bones, and then free the edit-data */ @@ -212,6 +397,17 @@ static int apply_armature_pose2bones_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void apply_armature_pose2bones_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + wmWindowManager *wm = CTX_wm_manager(C); + PointerRNA ptr; + + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + uiItemR(layout, &ptr, "selected", 0, NULL, ICON_NONE); +} + void POSE_OT_armature_apply(wmOperatorType *ot) { /* identifiers */ @@ -222,9 +418,16 @@ void POSE_OT_armature_apply(wmOperatorType *ot) /* callbacks */ ot->exec = apply_armature_pose2bones_exec; ot->poll = ED_operator_posemode; + ot->ui = apply_armature_pose2bones_ui; /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_USE_EVAL_DATA; + + RNA_def_boolean(ot->srna, + "selected", + false, + "Selected Only", + "Only apply the selected bones (with propagation to children)"); } /* set the current pose as the restpose */ |