diff options
author | Campbell Barton <ideasman42@gmail.com> | 2021-06-22 07:04:30 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2021-06-22 07:23:37 +0300 |
commit | 338be95874bddec300a863c9583652cda0ccf5de (patch) | |
tree | a0a0a22466b5c33bce5daa3bc7af18bfafd30152 | |
parent | 9ac56bad4c08750edfdf5ad8e98e257fb6ed4474 (diff) |
Fix bone select failing with end-points outside the view
Apply the same fix for T32214 (edge-select failing) to bones
which also failed when their end-points were outside of the view.
- Add V3D_PROJ_TEST_CLIP_CONTENT support for edit & pose bone iterator
and use for selection operators.
- Remove unnecessarily complicated checks with pose-mode lasso tagging.
- Correct error in pose-mode LassoSelectUserData.is_changed
(currently harmless as it's not read back).
-rw-r--r-- | source/blender/editors/space_view3d/view3d_iterators.c | 340 | ||||
-rw-r--r-- | source/blender/editors/space_view3d/view3d_select.c | 132 |
2 files changed, 295 insertions, 177 deletions
diff --git a/source/blender/editors/space_view3d/view3d_iterators.c b/source/blender/editors/space_view3d/view3d_iterators.c index 0eb594fb3dc..5e0101e4160 100644 --- a/source/blender/editors/space_view3d/view3d_iterators.c +++ b/source/blender/editors/space_view3d/view3d_iterators.c @@ -97,6 +97,110 @@ static int content_planes_from_clip_flag(const ARegion *region, return planes_len; } +/** + * Edge projection is more involved since part of the edge may be behind the view + * or extend beyond the far limits. In the case of single points, these can be ignored. + * However it just may still be visible on screen, so constrained the edge to planes + * defined by the port to ensure both ends of the edge can be projected, see T32214. + * + * \note This is unrelated to #V3D_PROJ_TEST_CLIP_BB which must be checked separately. + */ +static bool view3d_project_segment_to_screen_with_content_clip_planes( + const ARegion *region, + const float v_a[3], + const float v_b[3], + const eV3DProjTest clip_flag, + const rctf *win_rect, + const float content_planes[][4], + const int content_planes_len, + /* Output. */ + float r_screen_co_a[2], + float r_screen_co_b[2]) +{ + /* Clipping already handled, no need to check in projection. */ + eV3DProjTest clip_flag_nowin = clip_flag & ~V3D_PROJ_TEST_CLIP_WIN; + + const eV3DProjStatus status_a = ED_view3d_project_float_object( + region, v_a, r_screen_co_a, clip_flag_nowin); + const eV3DProjStatus status_b = ED_view3d_project_float_object( + region, v_b, r_screen_co_b, clip_flag_nowin); + + if ((status_a == V3D_PROJ_RET_OK) && (status_b == V3D_PROJ_RET_OK)) { + if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { + if (!BLI_rctf_isect_segment(win_rect, r_screen_co_a, r_screen_co_b)) { + return false; + } + } + } + else { + if (content_planes_len == 0) { + return false; + } + + /* Both too near, ignore. */ + if ((status_a & V3D_PROJ_TEST_CLIP_NEAR) && (status_b & V3D_PROJ_TEST_CLIP_NEAR)) { + return false; + } + + /* Both too far, ignore. */ + if ((status_a & V3D_PROJ_TEST_CLIP_FAR) && (status_b & V3D_PROJ_TEST_CLIP_FAR)) { + return false; + } + + /* Simple cases have been ruled out, clip by viewport planes, then re-project. */ + float v_a_clip[3], v_b_clip[3]; + if (!clip_segment_v3_plane_n( + v_a, v_b, content_planes, content_planes_len, v_a_clip, v_b_clip)) { + return false; + } + + if ((ED_view3d_project_float_object(region, v_a_clip, r_screen_co_a, clip_flag_nowin) != + V3D_PROJ_RET_OK) || + (ED_view3d_project_float_object(region, v_b_clip, r_screen_co_b, clip_flag_nowin) != + V3D_PROJ_RET_OK)) { + return false; + } + + /* No need for #V3D_PROJ_TEST_CLIP_WIN check here, + * clipping the segment by planes handle this. */ + } + + return true; +} + +/** + * Project an edge, points that fail to project are tagged with #IS_CLIPPED. + */ +static bool view3d_project_segment_to_screen_with_clip_tag(const ARegion *region, + const float v_a[3], + const float v_b[3], + const eV3DProjTest clip_flag, + /* Output. */ + float r_screen_co_a[2], + float r_screen_co_b[2]) +{ + int count = 0; + + if (ED_view3d_project_float_object(region, v_a, r_screen_co_a, clip_flag) == V3D_PROJ_RET_OK) { + count++; + } + else { + r_screen_co_a[0] = IS_CLIPPED; /* weak */ + /* screen_co_a[1]: intentionally don't set this so we get errors on misuse */ + } + + if (ED_view3d_project_float_object(region, v_b, r_screen_co_b, clip_flag) == V3D_PROJ_RET_OK) { + count++; + } + else { + r_screen_co_b[0] = IS_CLIPPED; /* weak */ + /* screen_co_b[1]: intentionally don't set this so we get errors on misuse */ + } + + /* Caller may want to know this value, for now it's not needed. */ + return count != 0; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -261,76 +365,6 @@ void mesh_foreachScreenVert( /** \name Edit-Mesh: For Each Screen Mesh Edge * \{ */ -/** - * Edge projection is more involved since part of the edge may be behind the view - * or extend beyond the far limits. In the case of single points, these can be ignored. - * However it just may still be visible on screen, so constrained the edge to planes - * defined by the port to ensure both ends of the edge can be projected, see T32214. - * - * \note This is unrelated to #V3D_PROJ_TEST_CLIP_BB which must be checked separately. - */ -static bool mesh_foreachScreenEdge_shared_project_and_test(const ARegion *region, - const float v_a[3], - const float v_b[3], - const eV3DProjTest clip_flag, - const rctf *win_rect, - const float content_planes[][4], - const int content_planes_len, - /* Output. */ - float r_screen_co_a[2], - float r_screen_co_b[2]) -{ - /* Clipping already handled, no need to check in projection. */ - eV3DProjTest clip_flag_nowin = clip_flag & ~V3D_PROJ_TEST_CLIP_WIN; - - const eV3DProjStatus status_a = ED_view3d_project_float_object( - region, v_a, r_screen_co_a, clip_flag_nowin); - const eV3DProjStatus status_b = ED_view3d_project_float_object( - region, v_b, r_screen_co_b, clip_flag_nowin); - - if ((status_a == V3D_PROJ_RET_OK) && (status_b == V3D_PROJ_RET_OK)) { - if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { - if (!BLI_rctf_isect_segment(win_rect, r_screen_co_a, r_screen_co_b)) { - return false; - } - } - } - else { - if (content_planes_len == 0) { - return false; - } - - /* Both too near, ignore. */ - if ((status_a & V3D_PROJ_TEST_CLIP_NEAR) && (status_b & V3D_PROJ_TEST_CLIP_NEAR)) { - return false; - } - - /* Both too far, ignore. */ - if ((status_a & V3D_PROJ_TEST_CLIP_FAR) && (status_b & V3D_PROJ_TEST_CLIP_FAR)) { - return false; - } - - /* Simple cases have been ruled out, clip by viewport planes, then re-project. */ - float v_a_clip[3], v_b_clip[3]; - if (!clip_segment_v3_plane_n( - v_a, v_b, content_planes, content_planes_len, v_a_clip, v_b_clip)) { - return false; - } - - if ((ED_view3d_project_float_object(region, v_a_clip, r_screen_co_a, clip_flag_nowin) != - V3D_PROJ_RET_OK) || - (ED_view3d_project_float_object(region, v_b_clip, r_screen_co_b, clip_flag_nowin) != - V3D_PROJ_RET_OK)) { - return false; - } - - /* No need for #V3D_PROJ_TEST_CLIP_WIN check here, - * clipping the segment by planes handle this. */ - } - - return true; -} - static void mesh_foreachScreenEdge__mapFunc(void *userData, int index, const float v_a[3], @@ -343,15 +377,15 @@ static void mesh_foreachScreenEdge__mapFunc(void *userData, } float screen_co_a[2], screen_co_b[2]; - if (!mesh_foreachScreenEdge_shared_project_and_test(data->vc.region, - v_a, - v_b, - data->clip_flag, - &data->win_rect, - data->content_planes, - data->content_planes_len, - screen_co_a, - screen_co_b)) { + if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, + v_a, + v_b, + data->clip_flag, + &data->win_rect, + data->content_planes, + data->content_planes_len, + screen_co_a, + screen_co_b)) { return; } @@ -430,15 +464,15 @@ static void mesh_foreachScreenEdge_clip_bb_segment__mapFunc(void *userData, } float screen_co_a[2], screen_co_b[2]; - if (!mesh_foreachScreenEdge_shared_project_and_test(data->vc.region, - v_a_clip, - v_b_clip, - data->clip_flag, - &data->win_rect, - data->content_planes, - data->content_planes_len, - screen_co_a, - screen_co_b)) { + if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, + v_a_clip, + v_b_clip, + data->clip_flag, + &data->win_rect, + data->content_planes, + data->content_planes_len, + screen_co_a, + screen_co_b)) { return; } @@ -728,35 +762,51 @@ void armature_foreachScreenBone(struct ViewContext *vc, ED_view3d_check_mats_rv3d(vc->rv3d); - for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { - if (EBONE_VISIBLE(arm, ebone)) { - float screen_co_a[2], screen_co_b[2]; - int points_proj_tot = 0; + float content_planes[6][4]; + int content_planes_len; + rctf win_rect; - /* project head location to screenspace */ - if (ED_view3d_project_float_object(vc->region, ebone->head, screen_co_a, clip_flag) == - V3D_PROJ_RET_OK) { - points_proj_tot++; - } - else { - screen_co_a[0] = IS_CLIPPED; /* weak */ - /* screen_co_a[1]: intentionally don't set this so we get errors on misuse */ - } + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + content_planes_len = content_planes_from_clip_flag( + vc->region, vc->obedit, clip_flag, content_planes); + win_rect.xmin = 0; + win_rect.ymin = 0; + win_rect.xmax = vc->region->winx; + win_rect.ymax = vc->region->winy; + } + else { + content_planes_len = 0; + } - /* project tail location to screenspace */ - if (ED_view3d_project_float_object(vc->region, ebone->tail, screen_co_b, clip_flag) == - V3D_PROJ_RET_OK) { - points_proj_tot++; - } - else { - screen_co_b[0] = IS_CLIPPED; /* weak */ - /* screen_co_b[1]: intentionally don't set this so we get errors on misuse */ - } + for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { + if (!EBONE_VISIBLE(arm, ebone)) { + continue; + } - if (points_proj_tot) { /* at least one point's projection worked */ - func(userData, ebone, screen_co_a, screen_co_b); + float screen_co_a[2], screen_co_b[2]; + const float *v_a = ebone->head, *v_b = ebone->tail; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, + v_a, + v_b, + clip_flag, + &win_rect, + content_planes, + content_planes_len, + screen_co_a, + screen_co_b)) { + continue; + } + } + else { + if (!view3d_project_segment_to_screen_with_clip_tag( + vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { + continue; } } + + func(userData, ebone, screen_co_a, screen_co_b); } } @@ -783,36 +833,52 @@ void pose_foreachScreenBone(struct ViewContext *vc, ED_view3d_check_mats_rv3d(vc->rv3d); + float content_planes[6][4]; + int content_planes_len; + rctf win_rect; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + content_planes_len = content_planes_from_clip_flag( + vc->region, ob_eval, clip_flag, content_planes); + win_rect.xmin = 0; + win_rect.ymin = 0; + win_rect.xmax = vc->region->winx; + win_rect.ymax = vc->region->winy; + } + else { + content_planes_len = 0; + } + for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) { - if (PBONE_VISIBLE(arm_eval, pchan->bone)) { - bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); - float screen_co_a[2], screen_co_b[2]; - int points_proj_tot = 0; - - /* project head location to screenspace */ - if (ED_view3d_project_float_object( - vc->region, pchan_eval->pose_head, screen_co_a, clip_flag) == V3D_PROJ_RET_OK) { - points_proj_tot++; - } - else { - screen_co_a[0] = IS_CLIPPED; /* weak */ - /* screen_co_a[1]: intentionally don't set this so we get errors on misuse */ - } + if (!PBONE_VISIBLE(arm_eval, pchan->bone)) { + continue; + } - /* project tail location to screenspace */ - if (ED_view3d_project_float_object( - vc->region, pchan_eval->pose_tail, screen_co_b, clip_flag) == V3D_PROJ_RET_OK) { - points_proj_tot++; + bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); + float screen_co_a[2], screen_co_b[2]; + const float *v_a = pchan_eval->pose_head, *v_b = pchan_eval->pose_tail; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, + v_a, + v_b, + clip_flag, + &win_rect, + content_planes, + content_planes_len, + screen_co_a, + screen_co_b)) { + continue; } - else { - screen_co_b[0] = IS_CLIPPED; /* weak */ - /* screen_co_b[1]: intentionally don't set this so we get errors on misuse */ - } - - if (points_proj_tot) { /* at least one point's projection worked */ - func(userData, pchan, screen_co_a, screen_co_b); + } + else { + if (!view3d_project_segment_to_screen_with_clip_tag( + vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { + continue; } } + + func(userData, pchan, screen_co_a, screen_co_b); } } diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index fcd735d63a2..02ed9f6d791 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -525,39 +525,12 @@ static void do_lasso_select_pose__do_tag(void *userData, return; } - bool is_point_done = false; - int points_proj_tot = 0; - - /* project head location to screenspace */ - if (screen_co_a[0] != IS_CLIPPED) { - points_proj_tot++; - if (BLI_rcti_isect_pt(data->rect, UNPACK2(screen_co_a)) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), INT_MAX)) { - is_point_done = true; - } - } - - /* project tail location to screenspace */ - if (screen_co_b[0] != IS_CLIPPED) { - points_proj_tot++; - if (BLI_rcti_isect_pt(data->rect, UNPACK2(screen_co_b)) && - BLI_lasso_is_point_inside( - data->mcoords, data->mcoords_len, UNPACK2(screen_co_b), INT_MAX)) { - is_point_done = true; - } - } - - /* if one of points selected, we skip the bone itself */ - if ((is_point_done == true) || ((is_point_done == false) && (points_proj_tot == 2) && - BLI_lasso_is_edge_inside(data->mcoords, - data->mcoords_len, - UNPACK2(screen_co_a), - UNPACK2(screen_co_b), - INT_MAX))) { + if (BLI_rctf_isect_segment(data->rect_fl, screen_co_a, screen_co_b) && + BLI_lasso_is_edge_inside( + data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), UNPACK2(screen_co_b), INT_MAX)) { pchan->bone->flag |= BONE_DONE; + data->is_changed = true; } - data->is_changed |= is_point_done; } static void do_lasso_tag_pose(ViewContext *vc, Object *ob, @@ -581,7 +554,11 @@ static void do_lasso_tag_pose(ViewContext *vc, ED_view3d_init_mats_rv3d(vc_tmp.obact, vc->rv3d); - pose_foreachScreenBone(&vc_tmp, do_lasso_select_pose__do_tag, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + /* Treat bones as clipped segments (no joints). */ + pose_foreachScreenBone(&vc_tmp, + do_lasso_select_pose__do_tag, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); } static bool do_lasso_select_objects(ViewContext *vc, @@ -1071,6 +1048,34 @@ static void do_lasso_select_armature__doSelectBone(void *userData, ebone->temp.i = is_inside_flag | (is_ignore_flag >> 16); } +static void do_lasso_select_armature__doSelectBone_clip_content(void *userData, + EditBone *ebone, + const float screen_co_a[2], + const float screen_co_b[2]) +{ + LassoSelectUserData *data = userData; + bArmature *arm = data->vc->obedit->data; + if (!EBONE_VISIBLE(arm, ebone)) { + return; + } + + const int is_ignore_flag = ebone->temp.i << 16; + int is_inside_flag = ebone->temp.i & ~0xFFFF; + + /* - When #BONESEL_BONE is set, there is nothing to do. + * - When #BONE_ROOTSEL or #BONE_TIPSEL have been set - they take priority over bone selection. + */ + if (is_inside_flag & (BONESEL_BONE | BONE_ROOTSEL | BONE_TIPSEL)) { + return; + } + + if (BLI_lasso_is_edge_inside( + data->mcoords, data->mcoords_len, UNPACK2(screen_co_a), UNPACK2(screen_co_b), INT_MAX)) { + is_inside_flag |= BONESEL_BONE; + } + + ebone->temp.i = is_inside_flag | (is_ignore_flag >> 16); +} static bool do_lasso_select_armature(ViewContext *vc, const int mcoords[][2], @@ -1094,9 +1099,18 @@ static bool do_lasso_select_armature(ViewContext *vc, ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + /* Operate on fully visible (non-clipped) points. */ armature_foreachScreenBone( vc, do_lasso_select_armature__doSelectBone, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + /* Operate on bones as segments clipped to the viewport bounds + * (needed to handle bones with both points outside the view). + * A separate pass is needed since clipped coordinates can't be used for selecting joints. */ + armature_foreachScreenBone(vc, + do_lasso_select_armature__doSelectBone_clip_content, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + data.is_changed |= ED_armature_edit_select_op_from_tagged(vc->obedit->data, sel_op); if (data.is_changed) { @@ -4102,8 +4116,11 @@ static bool pose_circle_select(ViewContext *vc, ED_view3d_init_mats_rv3d(vc->obact, vc->rv3d); /* for foreach's screen/vert projection */ - pose_foreachScreenBone( - vc, do_circle_select_pose__doSelectBone, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + /* Treat bones as clipped segments (no joints). */ + pose_foreachScreenBone(vc, + do_circle_select_pose__doSelectBone, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); if (data.is_changed) { ED_pose_bone_select_tag_update(vc->obact); @@ -4150,7 +4167,11 @@ static void do_circle_select_armature__doSelectBone(void *userData, return; } + /* When true, ignore in the next pass. */ + ebone->temp.i = false; + bool is_point_done = false; + bool is_edge_done = false; int points_proj_tot = 0; /* project head location to screenspace */ @@ -4178,17 +4199,39 @@ static void do_circle_select_armature__doSelectBone(void *userData, * otherwise there is no way to circle select joints alone */ if ((is_point_done == false) && (points_proj_tot == 2) && edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { - if (data->select) { - ebone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); - } - else { - ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); - } + SET_FLAG_FROM_TEST(ebone->flag, data->select, BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + is_edge_done = true; data->is_changed = true; } + if (is_point_done || is_edge_done) { + ebone->temp.i = true; + } + data->is_changed |= is_point_done; } +static void do_circle_select_armature__doSelectBone_clip_content(void *userData, + struct EditBone *ebone, + const float screen_co_a[2], + const float screen_co_b[2]) +{ + CircleSelectUserData *data = userData; + bArmature *arm = data->vc->obedit->data; + + if (!(data->select ? EBONE_SELECTABLE(arm, ebone) : EBONE_VISIBLE(arm, ebone))) { + return; + } + + /* Set in the first pass, needed so circle select prioritizes joints. */ + if (ebone->temp.i == true) { + return; + } + + if (edge_inside_circle(data->mval_fl, data->radius, screen_co_a, screen_co_b)) { + SET_FLAG_FROM_TEST(ebone->flag, data->select, BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL); + data->is_changed = true; + } +} static bool armature_circle_select(ViewContext *vc, const eSelectOp sel_op, const int mval[2], @@ -4207,9 +4250,18 @@ static bool armature_circle_select(ViewContext *vc, ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + /* Operate on fully visible (non-clipped) points. */ armature_foreachScreenBone( vc, do_circle_select_armature__doSelectBone, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + /* Operate on bones as segments clipped to the viewport bounds + * (needed to handle bones with both points outside the view). + * A separate pass is needed since clipped coordinates can't be used for selecting joints. */ + armature_foreachScreenBone(vc, + do_circle_select_armature__doSelectBone_clip_content, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + if (data.is_changed) { ED_armature_edit_sync_selection(arm->edbo); ED_armature_edit_validate_active(arm); |