diff options
Diffstat (limited to 'source/blender/editors/space_view3d/view3d_iterators.c')
-rw-r--r-- | source/blender/editors/space_view3d/view3d_iterators.c | 510 |
1 files changed, 379 insertions, 131 deletions
diff --git a/source/blender/editors/space_view3d/view3d_iterators.c b/source/blender/editors/space_view3d/view3d_iterators.c index bdb2c3874db..2a381bb96cb 100644 --- a/source/blender/editors/space_view3d/view3d_iterators.c +++ b/source/blender/editors/space_view3d/view3d_iterators.c @@ -50,15 +50,179 @@ #include "ED_screen.h" #include "ED_view3d.h" +/* -------------------------------------------------------------------- */ +/** \name Internal Clipping Utilities + * \{ */ + +/** + * Calculate clipping planes to use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. + * + * Planes are selected from the viewpoint using `clip_flag` + * to detect which planes should be applied (maximum 6). + * + * \return The number of planes written into `planes`. + */ +static int content_planes_from_clip_flag(const ARegion *region, + const Object *ob, + const eV3DProjTest clip_flag, + float planes[6][4]) +{ + BLI_assert(clip_flag & V3D_PROJ_TEST_CLIP_CONTENT); + + float *clip_xmin = NULL, *clip_xmax = NULL; + float *clip_ymin = NULL, *clip_ymax = NULL; + float *clip_zmin = NULL, *clip_zmax = NULL; + + int planes_len = 0; + + /* The order of `planes` has been selected based on the likelihood of points being fully + * outside the plane to increase the chance of an early exit in #clip_segment_v3_plane_n. + * With "near" being most likely and "far" being unlikely. + * + * Otherwise the order of axes in `planes` isn't significant. */ + + if (clip_flag & V3D_PROJ_TEST_CLIP_NEAR) { + clip_zmin = planes[planes_len++]; + } + if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { + clip_xmin = planes[planes_len++]; + clip_xmax = planes[planes_len++]; + clip_ymin = planes[planes_len++]; + clip_ymax = planes[planes_len++]; + } + if (clip_flag & V3D_PROJ_TEST_CLIP_FAR) { + clip_zmax = planes[planes_len++]; + } + + BLI_assert(planes_len <= 6); + if (planes_len != 0) { + RegionView3D *rv3d = region->regiondata; + float projmat[4][4]; + ED_view3d_ob_project_mat_get(rv3d, ob, projmat); + planes_from_projmat(projmat, clip_xmin, clip_xmax, clip_ymin, clip_ymax, clip_zmin, clip_zmax); + } + 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; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Private User Data Structures + * \{ */ + typedef struct foreachScreenObjectVert_userData { - void (*func)(void *userData, MVert *mv, const float screen_co_b[2], int index); + void (*func)(void *userData, MVert *mv, const float screen_co[2], int index); void *userData; ViewContext vc; eV3DProjTest clip_flag; } foreachScreenObjectVert_userData; typedef struct foreachScreenVert_userData { - void (*func)(void *userData, BMVert *eve, const float screen_co_b[2], int index); + void (*func)(void *userData, BMVert *eve, const float screen_co[2], int index); void *userData; ViewContext vc; eV3DProjTest clip_flag; @@ -73,8 +237,16 @@ typedef struct foreachScreenEdge_userData { int index); void *userData; ViewContext vc; - rctf win_rect; /* copy of: vc.region->winx/winy, use for faster tests, minx/y will always be 0 */ eV3DProjTest clip_flag; + + rctf win_rect; /* copy of: vc.region->winx/winy, use for faster tests, minx/y will always be 0 */ + + /** + * Clip plans defined by the the view bounds, + * use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. + */ + float content_planes[6][4]; + int content_planes_len; } foreachScreenEdge_userData; typedef struct foreachScreenFace_userData { @@ -91,7 +263,11 @@ typedef struct foreachScreenFace_userData { * use the object matrix in the usual way. */ -/* ------------------------------------------------------------------------ */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Vertex + * \{ */ static void meshobject_foreachScreenVert__mapFunc(void *userData, int index, @@ -120,6 +296,7 @@ void meshobject_foreachScreenVert( void *userData, eV3DProjTest clip_flag) { + BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); foreachScreenObjectVert_userData data; Mesh *me; @@ -150,17 +327,17 @@ static void mesh_foreachScreenVert__mapFunc(void *userData, { foreachScreenVert_userData *data = userData; BMVert *eve = BM_vert_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(eve, BM_ELEM_HIDDEN))) { + return; + } - if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { - float screen_co[2]; - - if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != - V3D_PROJ_RET_OK) { - return; - } - - data->func(data->userData, eve, screen_co, index); + float screen_co[2]; + if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != + V3D_PROJ_RET_OK) { + return; } + + data->func(data->userData, eve, screen_co, index); } void mesh_foreachScreenVert( @@ -189,38 +366,37 @@ void mesh_foreachScreenVert( BKE_mesh_foreach_mapped_vert(me, mesh_foreachScreenVert__mapFunc, &data, MESH_FOREACH_NOP); } -/* ------------------------------------------------------------------------ */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Mesh Edge + * \{ */ static void mesh_foreachScreenEdge__mapFunc(void *userData, int index, - const float v0co[3], - const float v1co[3]) + const float v_a[3], + const float v_b[3]) { foreachScreenEdge_userData *data = userData; BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { + return; + } - if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { - float screen_co_a[2]; - float screen_co_b[2]; - eV3DProjTest clip_flag_nowin = data->clip_flag & ~V3D_PROJ_TEST_CLIP_WIN; - - if (ED_view3d_project_float_object(data->vc.region, v0co, screen_co_a, clip_flag_nowin) != - V3D_PROJ_RET_OK) { - return; - } - if (ED_view3d_project_float_object(data->vc.region, v1co, screen_co_b, clip_flag_nowin) != - V3D_PROJ_RET_OK) { - return; - } - - if (data->clip_flag & V3D_PROJ_TEST_CLIP_WIN) { - if (!BLI_rctf_isect_segment(&data->win_rect, screen_co_a, screen_co_b)) { - return; - } - } - - data->func(data->userData, eed, screen_co_a, screen_co_b, index); + float screen_co_a[2], screen_co_b[2]; + 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; } + + data->func(data->userData, eed, screen_co_a, screen_co_b, index); } void mesh_foreachScreenEdge(ViewContext *vc, @@ -254,11 +430,23 @@ void mesh_foreachScreenEdge(ViewContext *vc, ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ } + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + data.content_planes_len = content_planes_from_clip_flag( + vc->region, vc->obedit, clip_flag, data.content_planes); + } + else { + data.content_planes_len = 0; + } + BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); BKE_mesh_foreach_mapped_edge(me, mesh_foreachScreenEdge__mapFunc, &data); } -/* ------------------------------------------------------------------------ */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Edge (Bounding Box Clipped) + * \{ */ /** * Only call for bound-box clipping. @@ -266,46 +454,36 @@ void mesh_foreachScreenEdge(ViewContext *vc, */ static void mesh_foreachScreenEdge_clip_bb_segment__mapFunc(void *userData, int index, - const float v0co[3], - const float v1co[3]) + const float v_a[3], + const float v_b[3]) { foreachScreenEdge_userData *data = userData; BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { + return; + } BLI_assert(data->clip_flag & V3D_PROJ_TEST_CLIP_BB); - if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { - float v0co_clip[3]; - float v1co_clip[3]; - - if (!clip_segment_v3_plane_n(v0co, v1co, data->vc.rv3d->clip_local, 4, v0co_clip, v1co_clip)) { - return; - } - - float screen_co_a[2]; - float screen_co_b[2]; - - /* Clipping already handled, no need to check in projection. */ - eV3DProjTest clip_flag_nowin = data->clip_flag & - ~(V3D_PROJ_TEST_CLIP_WIN | V3D_PROJ_TEST_CLIP_BB); - - if (ED_view3d_project_float_object(data->vc.region, v0co_clip, screen_co_a, clip_flag_nowin) != - V3D_PROJ_RET_OK) { - return; - } - if (ED_view3d_project_float_object(data->vc.region, v1co_clip, screen_co_b, clip_flag_nowin) != - V3D_PROJ_RET_OK) { - return; - } - - if (data->clip_flag & V3D_PROJ_TEST_CLIP_WIN) { - if (!BLI_rctf_isect_segment(&data->win_rect, screen_co_a, screen_co_b)) { - return; - } - } + float v_a_clip[3], v_b_clip[3]; + if (!clip_segment_v3_plane_n(v_a, v_b, data->vc.rv3d->clip_local, 4, v_a_clip, v_b_clip)) { + return; + } - data->func(data->userData, eed, screen_co_a, screen_co_b, index); + float screen_co_a[2], screen_co_b[2]; + 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; } + + data->func(data->userData, eed, screen_co_a, screen_co_b, index); } /** @@ -339,6 +517,14 @@ void mesh_foreachScreenEdge_clip_bb_segment(ViewContext *vc, data.userData = userData; data.clip_flag = clip_flag; + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + data.content_planes_len = content_planes_from_clip_flag( + vc->region, vc->obedit, clip_flag, data.content_planes); + } + else { + data.content_planes_len = 0; + } + BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); if ((clip_flag & V3D_PROJ_TEST_CLIP_BB) && (vc->rv3d->clipbb != NULL)) { @@ -350,7 +536,11 @@ void mesh_foreachScreenEdge_clip_bb_segment(ViewContext *vc, } } -/* ------------------------------------------------------------------------ */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Face Center + * \{ */ static void mesh_foreachScreenFace__mapFunc(void *userData, int index, @@ -359,14 +549,17 @@ static void mesh_foreachScreenFace__mapFunc(void *userData, { foreachScreenFace_userData *data = userData; BMFace *efa = BM_face_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(efa, BM_ELEM_HIDDEN))) { + return; + } - if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { - float screen_co[2]; - if (ED_view3d_project_float_object(data->vc.region, cent, screen_co, data->clip_flag) == - V3D_PROJ_RET_OK) { - data->func(data->userData, efa, screen_co, index); - } + float screen_co[2]; + if (ED_view3d_project_float_object(data->vc.region, cent, screen_co, data->clip_flag) != + V3D_PROJ_RET_OK) { + return; } + + data->func(data->userData, efa, screen_co, index); } void mesh_foreachScreenFace( @@ -375,6 +568,7 @@ void mesh_foreachScreenFace( void *userData, const eV3DProjTest clip_flag) { + BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); foreachScreenFace_userData data; Mesh *me = editbmesh_get_eval_cage_from_orig( @@ -398,7 +592,11 @@ void mesh_foreachScreenFace( } } -/* ------------------------------------------------------------------------ */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Nurbs: For Each Screen Vertex + * \{ */ void nurbs_foreachScreenVert(ViewContext *vc, void (*func)(void *userData, @@ -486,7 +684,11 @@ void nurbs_foreachScreenVert(ViewContext *vc, } } -/* ------------------------------------------------------------------------ */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Meta: For Each Screen Meta-Element + * \{ */ /* ED_view3d_init_mats_rv3d must be called first */ void mball_foreachScreenElem(struct ViewContext *vc, @@ -510,7 +712,11 @@ void mball_foreachScreenElem(struct ViewContext *vc, } } -/* ------------------------------------------------------------------------ */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Lattice: For Each Screen Vertex + * \{ */ void lattice_foreachScreenVert(ViewContext *vc, void (*func)(void *userData, BPoint *bp, const float screen_co[2]), @@ -543,7 +749,11 @@ void lattice_foreachScreenVert(ViewContext *vc, } } -/* ------------------------------------------------------------------------ */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Armature: For Each Screen Bone + * \{ */ /* ED_view3d_init_mats_rv3d must be called first */ void armature_foreachScreenBone(struct ViewContext *vc, @@ -559,39 +769,59 @@ 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; + + 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 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 */ - } + for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { + if (!EBONE_VISIBLE(arm, ebone)) { + continue; + } - /* 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 */ + 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; } - - if (points_proj_tot) { /* at least one point's projection worked */ - func(userData, ebone, 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, ebone, screen_co_a, screen_co_b); } } -/* ------------------------------------------------------------------------ */ +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Pose: For Each Screen Bone + * \{ */ /* ED_view3d_init_mats_rv3d must be called first */ /* almost _exact_ copy of #armature_foreachScreenBone */ @@ -610,35 +840,53 @@ 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); } } + +/** \} */ |